/* * This is the source code of Telegram for Android v. 5.x.x. * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * * Copyright Nikolai Kudashov, 2013-2018. */ package org.telegram.messenger; import android.appwidget.AppWidgetManager; import android.content.SharedPreferences; import android.os.SystemClock; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextUtils; import android.text.style.ForegroundColorSpan; import android.util.LongSparseArray; import android.util.Pair; import android.util.SparseArray; import android.util.SparseIntArray; import org.telegram.PhoneFormat.PhoneFormat; import org.telegram.SQLite.SQLiteCursor; import org.telegram.SQLite.SQLiteDatabase; import org.telegram.SQLite.SQLitePreparedStatement; import org.telegram.messenger.support.SparseLongArray; import org.telegram.tgnet.NativeByteBuffer; import org.telegram.tgnet.RequestDelegate; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Adapters.DialogsSearchAdapter; import org.telegram.ui.EditWidgetActivity; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicLong; import androidx.annotation.UiThread; public class MessagesStorage extends BaseController { public interface IntCallback { void run(int param); } public interface BooleanCallback { void run(boolean param); } private DispatchQueue storageQueue = new DispatchQueue("storageQueue"); private SQLiteDatabase database; private File cacheFile; private File walCacheFile; private File shmCacheFile; private AtomicLong lastTaskId = new AtomicLong(System.currentTimeMillis()); private SparseArray> tasks = new SparseArray<>(); private int lastDateValue = 0; private int lastPtsValue = 0; private int lastQtsValue = 0; private int lastSeqValue = 0; private int lastSecretVersion = 0; private byte[] secretPBytes = null; private int secretG = 0; private int lastSavedSeq = 0; private int lastSavedPts = 0; private int lastSavedDate = 0; private int lastSavedQts = 0; private ArrayList dialogFilters = new ArrayList<>(); private SparseArray dialogFiltersMap = new SparseArray<>(); private LongSparseArray unknownDialogsIds = new LongSparseArray<>(); private int mainUnreadCount; private int archiveUnreadCount; private volatile int pendingMainUnreadCount; private volatile int pendingArchiveUnreadCount; private CountDownLatch openSync = new CountDownLatch(1); private static volatile MessagesStorage[] Instance = new MessagesStorage[UserConfig.MAX_ACCOUNT_COUNT]; private final static int LAST_DB_VERSION = 78; public static MessagesStorage getInstance(int num) { MessagesStorage localInstance = Instance[num]; if (localInstance == null) { synchronized (MessagesStorage.class) { localInstance = Instance[num]; if (localInstance == null) { Instance[num] = localInstance = new MessagesStorage(num); } } } return localInstance; } private void ensureOpened() { try { openSync.await(); } catch (Throwable ignore) { } } public int getLastDateValue() { ensureOpened(); return lastDateValue; } public void setLastDateValue(int value) { ensureOpened(); lastDateValue = value; } public int getLastPtsValue() { ensureOpened(); return lastPtsValue; } public int getMainUnreadCount() { return mainUnreadCount; } public int getArchiveUnreadCount() { return archiveUnreadCount; } public void setLastPtsValue(int value) { ensureOpened(); lastPtsValue = value; } public int getLastQtsValue() { ensureOpened(); return lastQtsValue; } public void setLastQtsValue(int value) { ensureOpened(); lastQtsValue = value; } public int getLastSeqValue() { ensureOpened(); return lastSeqValue; } public void setLastSeqValue(int value) { ensureOpened(); lastSeqValue = value; } public int getLastSecretVersion() { ensureOpened(); return lastSecretVersion; } public void setLastSecretVersion(int value) { ensureOpened(); lastSecretVersion = value; } public byte[] getSecretPBytes() { ensureOpened(); return secretPBytes; } public void setSecretPBytes(byte[] value) { ensureOpened(); secretPBytes = value; } public int getSecretG() { ensureOpened(); return secretG; } public void setSecretG(int value) { ensureOpened(); secretG = value; } public MessagesStorage(int instance) { super(instance); //storageQueue.setPriority(Thread.MAX_PRIORITY); storageQueue.postRunnable(() -> openDatabase(1)); } public SQLiteDatabase getDatabase() { return database; } public DispatchQueue getStorageQueue() { return storageQueue; } @UiThread public void bindTaskToGuid(Runnable task, int guid) { ArrayList arrayList = tasks.get(guid); if (arrayList == null) { arrayList = new ArrayList<>(); tasks.put(guid, arrayList); } arrayList.add(task); } @UiThread public void cancelTasksForGuid(int guid) { ArrayList arrayList = tasks.get(guid); if (arrayList == null) { return; } for (int a = 0, N = arrayList.size(); a < N; a++) { storageQueue.cancelRunnable(arrayList.get(a)); } tasks.remove(guid); } @UiThread public void completeTaskForGuid(Runnable runnable, int guid) { ArrayList arrayList = tasks.get(guid); if (arrayList == null) { return; } arrayList.remove(runnable); if (arrayList.isEmpty()) { tasks.remove(guid); } } public long getDatabaseSize() { long size = 0; if (cacheFile != null) { size += cacheFile.length(); } if (shmCacheFile != null) { size += shmCacheFile.length(); } /*if (walCacheFile != null) { size += walCacheFile.length(); }*/ return size; } public void openDatabase(int openTries) { File filesDir = ApplicationLoader.getFilesDirFixed(); if (currentAccount != 0) { filesDir = new File(filesDir, "account" + currentAccount + "/"); filesDir.mkdirs(); } cacheFile = new File(filesDir, "cache4.db"); walCacheFile = new File(filesDir, "cache4.db-wal"); shmCacheFile = new File(filesDir, "cache4.db-shm"); boolean createTable = false; //cacheFile.delete(); if (!cacheFile.exists()) { createTable = true; } try { database = new SQLiteDatabase(cacheFile.getPath()); database.executeFast("PRAGMA secure_delete = ON").stepThis().dispose(); database.executeFast("PRAGMA temp_store = MEMORY").stepThis().dispose(); database.executeFast("PRAGMA journal_mode = WAL").stepThis().dispose(); database.executeFast("PRAGMA journal_size_limit = 10485760").stepThis().dispose(); if (createTable) { if (BuildVars.LOGS_ENABLED) { FileLog.d("create new database"); } database.executeFast("CREATE TABLE messages_holes(uid INTEGER, start INTEGER, end INTEGER, PRIMARY KEY(uid, start));").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS uid_end_messages_holes ON messages_holes(uid, end);").stepThis().dispose(); database.executeFast("CREATE TABLE media_holes_v2(uid INTEGER, type INTEGER, start INTEGER, end INTEGER, PRIMARY KEY(uid, type, start));").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS uid_end_media_holes_v2 ON media_holes_v2(uid, type, end);").stepThis().dispose(); database.executeFast("CREATE TABLE scheduled_messages(mid INTEGER PRIMARY KEY, uid INTEGER, send_state INTEGER, date INTEGER, data BLOB, ttl INTEGER, replydata BLOB)").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS send_state_idx_scheduled_messages ON scheduled_messages(mid, send_state, date);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS uid_date_idx_scheduled_messages ON scheduled_messages(uid, date);").stepThis().dispose(); database.executeFast("CREATE TABLE messages(mid INTEGER PRIMARY KEY, uid INTEGER, read_state INTEGER, send_state INTEGER, date INTEGER, data BLOB, out INTEGER, ttl INTEGER, media INTEGER, replydata BLOB, imp INTEGER, mention INTEGER, forwards INTEGER, replies_data BLOB, thread_reply_id INTEGER)").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS uid_mid_read_out_idx_messages ON messages(uid, mid, read_state, out);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS uid_date_mid_idx_messages ON messages(uid, date, mid);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS mid_out_idx_messages ON messages(mid, out);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS task_idx_messages ON messages(uid, out, read_state, ttl, date, send_state);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS send_state_idx_messages2 ON messages(mid, send_state, date);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS uid_mention_idx_messages ON messages(uid, mention, read_state);").stepThis().dispose(); database.executeFast("CREATE TABLE download_queue(uid INTEGER, type INTEGER, date INTEGER, data BLOB, parent TEXT, PRIMARY KEY (uid, type));").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS type_date_idx_download_queue ON download_queue(type, date);").stepThis().dispose(); database.executeFast("CREATE TABLE user_contacts_v7(key TEXT PRIMARY KEY, uid INTEGER, fname TEXT, sname TEXT, imported INTEGER)").stepThis().dispose(); database.executeFast("CREATE TABLE user_phones_v7(key TEXT, phone TEXT, sphone TEXT, deleted INTEGER, PRIMARY KEY (key, phone))").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS sphone_deleted_idx_user_phones ON user_phones_v7(sphone, deleted);").stepThis().dispose(); database.executeFast("CREATE TABLE dialogs(did INTEGER PRIMARY KEY, date INTEGER, unread_count INTEGER, last_mid INTEGER, inbox_max INTEGER, outbox_max INTEGER, last_mid_i INTEGER, unread_count_i INTEGER, pts INTEGER, date_i INTEGER, pinned INTEGER, flags INTEGER, folder_id INTEGER, data BLOB)").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS date_idx_dialogs ON dialogs(date);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS last_mid_idx_dialogs ON dialogs(last_mid);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS unread_count_idx_dialogs ON dialogs(unread_count);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS last_mid_i_idx_dialogs ON dialogs(last_mid_i);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS unread_count_i_idx_dialogs ON dialogs(unread_count_i);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS folder_id_idx_dialogs ON dialogs(folder_id);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS flags_idx_dialogs ON dialogs(flags);").stepThis().dispose(); database.executeFast("CREATE TABLE dialog_filter(id INTEGER PRIMARY KEY, ord INTEGER, unread_count INTEGER, flags INTEGER, title TEXT)").stepThis().dispose(); database.executeFast("CREATE TABLE dialog_filter_ep(id INTEGER, peer INTEGER, PRIMARY KEY (id, peer))").stepThis().dispose(); database.executeFast("CREATE TABLE dialog_filter_pin_v2(id INTEGER, peer INTEGER, pin INTEGER, PRIMARY KEY (id, peer))").stepThis().dispose(); database.executeFast("CREATE TABLE randoms(random_id INTEGER, mid INTEGER, PRIMARY KEY (random_id, mid))").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS mid_idx_randoms ON randoms(mid);").stepThis().dispose(); database.executeFast("CREATE TABLE enc_tasks_v2(mid INTEGER PRIMARY KEY, date INTEGER, media INTEGER)").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS date_idx_enc_tasks_v2 ON enc_tasks_v2(date);").stepThis().dispose(); database.executeFast("CREATE TABLE messages_seq(mid INTEGER PRIMARY KEY, seq_in INTEGER, seq_out INTEGER);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS seq_idx_messages_seq ON messages_seq(seq_in, seq_out);").stepThis().dispose(); database.executeFast("CREATE TABLE params(id INTEGER PRIMARY KEY, seq INTEGER, pts INTEGER, date INTEGER, qts INTEGER, lsv INTEGER, sg INTEGER, pbytes BLOB)").stepThis().dispose(); database.executeFast("INSERT INTO params VALUES(1, 0, 0, 0, 0, 0, 0, NULL)").stepThis().dispose(); database.executeFast("CREATE TABLE media_v2(mid INTEGER PRIMARY KEY, uid INTEGER, date INTEGER, type INTEGER, data BLOB)").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS uid_mid_type_date_idx_media ON media_v2(uid, mid, type, date);").stepThis().dispose(); database.executeFast("CREATE TABLE bot_keyboard(uid INTEGER PRIMARY KEY, mid INTEGER, info BLOB)").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS bot_keyboard_idx_mid ON bot_keyboard(mid);").stepThis().dispose(); database.executeFast("CREATE TABLE chat_settings_v2(uid INTEGER PRIMARY KEY, info BLOB, pinned INTEGER, online INTEGER, inviter INTEGER, links INTEGER)").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS chat_settings_pinned_idx ON chat_settings_v2(uid, pinned) WHERE pinned != 0;").stepThis().dispose(); database.executeFast("CREATE TABLE user_settings(uid INTEGER PRIMARY KEY, info BLOB, pinned INTEGER)").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS user_settings_pinned_idx ON user_settings(uid, pinned) WHERE pinned != 0;").stepThis().dispose(); database.executeFast("CREATE TABLE chat_pinned_v2(uid INTEGER, mid INTEGER, data BLOB, PRIMARY KEY (uid, mid));").stepThis().dispose(); database.executeFast("CREATE TABLE chat_pinned_count(uid INTEGER PRIMARY KEY, count INTEGER, end INTEGER);").stepThis().dispose(); database.executeFast("CREATE TABLE chat_hints(did INTEGER, type INTEGER, rating REAL, date INTEGER, PRIMARY KEY(did, type))").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS chat_hints_rating_idx ON chat_hints(rating);").stepThis().dispose(); database.executeFast("CREATE TABLE botcache(id TEXT PRIMARY KEY, date INTEGER, data BLOB)").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS botcache_date_idx ON botcache(date);").stepThis().dispose(); database.executeFast("CREATE TABLE users_data(uid INTEGER PRIMARY KEY, about TEXT)").stepThis().dispose(); database.executeFast("CREATE TABLE users(uid INTEGER PRIMARY KEY, name TEXT, status INTEGER, data BLOB)").stepThis().dispose(); database.executeFast("CREATE TABLE chats(uid INTEGER PRIMARY KEY, name TEXT, data BLOB)").stepThis().dispose(); database.executeFast("CREATE TABLE enc_chats(uid INTEGER PRIMARY KEY, user INTEGER, name TEXT, data BLOB, g BLOB, authkey BLOB, ttl INTEGER, layer INTEGER, seq_in INTEGER, seq_out INTEGER, use_count INTEGER, exchange_id INTEGER, key_date INTEGER, fprint INTEGER, fauthkey BLOB, khash BLOB, in_seq_no INTEGER, admin_id INTEGER, mtproto_seq INTEGER)").stepThis().dispose(); database.executeFast("CREATE TABLE channel_users_v2(did INTEGER, uid INTEGER, date INTEGER, data BLOB, PRIMARY KEY(did, uid))").stepThis().dispose(); database.executeFast("CREATE TABLE channel_admins_v3(did INTEGER, uid INTEGER, data BLOB, PRIMARY KEY(did, uid))").stepThis().dispose(); database.executeFast("CREATE TABLE contacts(uid INTEGER PRIMARY KEY, mutual INTEGER)").stepThis().dispose(); database.executeFast("CREATE TABLE user_photos(uid INTEGER, id INTEGER, data BLOB, PRIMARY KEY (uid, id))").stepThis().dispose(); database.executeFast("CREATE TABLE dialog_settings(did INTEGER PRIMARY KEY, flags INTEGER);").stepThis().dispose(); database.executeFast("CREATE TABLE web_recent_v3(id TEXT, type INTEGER, image_url TEXT, thumb_url TEXT, local_url TEXT, width INTEGER, height INTEGER, size INTEGER, date INTEGER, document BLOB, PRIMARY KEY (id, type));").stepThis().dispose(); database.executeFast("CREATE TABLE stickers_v2(id INTEGER PRIMARY KEY, data BLOB, date INTEGER, hash INTEGER);").stepThis().dispose(); database.executeFast("CREATE TABLE stickers_featured(id INTEGER PRIMARY KEY, data BLOB, unread BLOB, date INTEGER, hash INTEGER);").stepThis().dispose(); database.executeFast("CREATE TABLE stickers_dice(emoji TEXT PRIMARY KEY, data BLOB, date INTEGER);").stepThis().dispose(); database.executeFast("CREATE TABLE hashtag_recent_v2(id TEXT PRIMARY KEY, date INTEGER);").stepThis().dispose(); database.executeFast("CREATE TABLE webpage_pending(id INTEGER, mid INTEGER, PRIMARY KEY (id, mid));").stepThis().dispose(); database.executeFast("CREATE TABLE sent_files_v2(uid TEXT, type INTEGER, data BLOB, parent TEXT, PRIMARY KEY (uid, type))").stepThis().dispose(); database.executeFast("CREATE TABLE search_recent(did INTEGER PRIMARY KEY, date INTEGER);").stepThis().dispose(); database.executeFast("CREATE TABLE media_counts_v2(uid INTEGER, type INTEGER, count INTEGER, old INTEGER, PRIMARY KEY(uid, type))").stepThis().dispose(); database.executeFast("CREATE TABLE keyvalue(id TEXT PRIMARY KEY, value TEXT)").stepThis().dispose(); database.executeFast("CREATE TABLE bot_info(uid INTEGER PRIMARY KEY, info BLOB)").stepThis().dispose(); database.executeFast("CREATE TABLE pending_tasks(id INTEGER PRIMARY KEY, data BLOB);").stepThis().dispose(); database.executeFast("CREATE TABLE requested_holes(uid INTEGER, seq_out_start INTEGER, seq_out_end INTEGER, PRIMARY KEY (uid, seq_out_start, seq_out_end));").stepThis().dispose(); database.executeFast("CREATE TABLE sharing_locations(uid INTEGER PRIMARY KEY, mid INTEGER, date INTEGER, period INTEGER, message BLOB, proximity INTEGER);").stepThis().dispose(); database.executeFast("CREATE TABLE shortcut_widget(id INTEGER, did INTEGER, ord INTEGER, PRIMARY KEY (id, did));").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS shortcut_widget_did ON shortcut_widget(did);").stepThis().dispose(); database.executeFast("CREATE TABLE emoji_keywords_v2(lang TEXT, keyword TEXT, emoji TEXT, PRIMARY KEY(lang, keyword, emoji));").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS emoji_keywords_v2_keyword ON emoji_keywords_v2(keyword);").stepThis().dispose(); database.executeFast("CREATE TABLE emoji_keywords_info_v2(lang TEXT PRIMARY KEY, alias TEXT, version INTEGER, date INTEGER);").stepThis().dispose(); database.executeFast("CREATE TABLE wallpapers2(uid INTEGER PRIMARY KEY, data BLOB, num INTEGER)").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS wallpapers_num ON wallpapers2(num);").stepThis().dispose(); database.executeFast("CREATE TABLE unread_push_messages(uid INTEGER, mid INTEGER, random INTEGER, date INTEGER, data BLOB, fm TEXT, name TEXT, uname TEXT, flags INTEGER, PRIMARY KEY(uid, mid))").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS unread_push_messages_idx_date ON unread_push_messages(date);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS unread_push_messages_idx_random ON unread_push_messages(random);").stepThis().dispose(); database.executeFast("CREATE TABLE polls(mid INTEGER PRIMARY KEY, id INTEGER);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS polls_id ON polls(id);").stepThis().dispose(); //version database.executeFast("PRAGMA user_version = " + LAST_DB_VERSION).stepThis().dispose(); } else { int version = database.executeInt("PRAGMA user_version"); if (BuildVars.LOGS_ENABLED) { FileLog.d("current db version = " + version); } if (version == 0) { throw new Exception("malformed"); } try { SQLiteCursor cursor = database.queryFinalized("SELECT seq, pts, date, qts, lsv, sg, pbytes FROM params WHERE id = 1"); if (cursor.next()) { lastSeqValue = cursor.intValue(0); lastPtsValue = cursor.intValue(1); lastDateValue = cursor.intValue(2); lastQtsValue = cursor.intValue(3); lastSecretVersion = cursor.intValue(4); secretG = cursor.intValue(5); if (cursor.isNull(6)) { secretPBytes = null; } else { secretPBytes = cursor.byteArrayValue(6); if (secretPBytes != null && secretPBytes.length == 1) { secretPBytes = null; } } } cursor.dispose(); } catch (Exception e) { FileLog.e(e); try { database.executeFast("CREATE TABLE IF NOT EXISTS params(id INTEGER PRIMARY KEY, seq INTEGER, pts INTEGER, date INTEGER, qts INTEGER, lsv INTEGER, sg INTEGER, pbytes BLOB)").stepThis().dispose(); database.executeFast("INSERT INTO params VALUES(1, 0, 0, 0, 0, 0, 0, NULL)").stepThis().dispose(); } catch (Exception e2) { FileLog.e(e2); } } if (version < LAST_DB_VERSION) { updateDbToLastVersion(version); } } } catch (Exception e) { FileLog.e(e); if (openTries < 3 && e.getMessage().contains("malformed")) { if (openTries == 2) { cleanupInternal(true); for (int a = 0; a < 2; a++) { getUserConfig().setDialogsLoadOffset(a, 0, 0, 0, 0, 0, 0); getUserConfig().setTotalDialogsCount(a, 0); } getUserConfig().saveConfig(false); } else { cleanupInternal(false); } openDatabase(openTries == 1 ? 2 : 3); } } loadDialogFilters(); loadUnreadMessages(); loadPendingTasks(); try { openSync.countDown(); } catch (Throwable ignore) { } } private void updateDbToLastVersion(final int currentVersion) { storageQueue.postRunnable(() -> { try { int version = currentVersion; if (version < 4) { database.executeFast("CREATE TABLE IF NOT EXISTS user_photos(uid INTEGER, id INTEGER, data BLOB, PRIMARY KEY (uid, id))").stepThis().dispose(); database.executeFast("DROP INDEX IF EXISTS read_state_out_idx_messages;").stepThis().dispose(); database.executeFast("DROP INDEX IF EXISTS ttl_idx_messages;").stepThis().dispose(); database.executeFast("DROP INDEX IF EXISTS date_idx_messages;").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS mid_out_idx_messages ON messages(mid, out);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS task_idx_messages ON messages(uid, out, read_state, ttl, date, send_state);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS uid_date_mid_idx_messages ON messages(uid, date, mid);").stepThis().dispose(); database.executeFast("CREATE TABLE IF NOT EXISTS user_contacts_v6(uid INTEGER PRIMARY KEY, fname TEXT, sname TEXT)").stepThis().dispose(); database.executeFast("CREATE TABLE IF NOT EXISTS user_phones_v6(uid INTEGER, phone TEXT, sphone TEXT, deleted INTEGER, PRIMARY KEY (uid, phone))").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS sphone_deleted_idx_user_phones ON user_phones_v6(sphone, deleted);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS mid_idx_randoms ON randoms(mid);").stepThis().dispose(); database.executeFast("CREATE TABLE IF NOT EXISTS sent_files_v2(uid TEXT, type INTEGER, data BLOB, PRIMARY KEY (uid, type))").stepThis().dispose(); database.executeFast("CREATE TABLE IF NOT EXISTS download_queue(uid INTEGER, type INTEGER, date INTEGER, data BLOB, PRIMARY KEY (uid, type));").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS type_date_idx_download_queue ON download_queue(type, date);").stepThis().dispose(); database.executeFast("CREATE TABLE IF NOT EXISTS dialog_settings(did INTEGER PRIMARY KEY, flags INTEGER);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS unread_count_idx_dialogs ON dialogs(unread_count);").stepThis().dispose(); database.executeFast("UPDATE messages SET send_state = 2 WHERE mid < 0 AND send_state = 1").stepThis().dispose(); fixNotificationSettings(); database.executeFast("PRAGMA user_version = 4").stepThis().dispose(); version = 4; } if (version == 4) { database.executeFast("CREATE TABLE IF NOT EXISTS enc_tasks_v2(mid INTEGER PRIMARY KEY, date INTEGER)").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS date_idx_enc_tasks_v2 ON enc_tasks_v2(date);").stepThis().dispose(); database.beginTransaction(); SQLiteCursor cursor = database.queryFinalized("SELECT date, data FROM enc_tasks WHERE 1"); SQLitePreparedStatement state = database.executeFast("REPLACE INTO enc_tasks_v2 VALUES(?, ?)"); if (cursor.next()) { int date = cursor.intValue(0); NativeByteBuffer data = cursor.byteBufferValue(1); if (data != null) { int length = data.limit(); for (int a = 0; a < length / 4; a++) { state.requery(); state.bindInteger(1, data.readInt32(false)); state.bindInteger(2, date); state.step(); } data.reuse(); } } state.dispose(); cursor.dispose(); database.commitTransaction(); database.executeFast("DROP INDEX IF EXISTS date_idx_enc_tasks;").stepThis().dispose(); database.executeFast("DROP TABLE IF EXISTS enc_tasks;").stepThis().dispose(); database.executeFast("ALTER TABLE messages ADD COLUMN media INTEGER default 0").stepThis().dispose(); database.executeFast("PRAGMA user_version = 6").stepThis().dispose(); version = 6; } if (version == 6) { database.executeFast("CREATE TABLE IF NOT EXISTS messages_seq(mid INTEGER PRIMARY KEY, seq_in INTEGER, seq_out INTEGER);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS seq_idx_messages_seq ON messages_seq(seq_in, seq_out);").stepThis().dispose(); database.executeFast("ALTER TABLE enc_chats ADD COLUMN layer INTEGER default 0").stepThis().dispose(); database.executeFast("ALTER TABLE enc_chats ADD COLUMN seq_in INTEGER default 0").stepThis().dispose(); database.executeFast("ALTER TABLE enc_chats ADD COLUMN seq_out INTEGER default 0").stepThis().dispose(); database.executeFast("PRAGMA user_version = 7").stepThis().dispose(); version = 7; } /*if (version == 7 && version < 8) { database.executeFast("CREATE TABLE IF NOT EXISTS secret_holes(uid INTEGER, seq_in INTEGER, seq_out INTEGER, data BLOB, PRIMARY KEY (uid, seq_in, seq_out));").stepThis().dispose(); database.executeFast("PRAGMA user_version = 8").stepThis().dispose(); version = 8; }*/ if (version == 7 || version == 8 || version == 9) { database.executeFast("ALTER TABLE enc_chats ADD COLUMN use_count INTEGER default 0").stepThis().dispose(); database.executeFast("ALTER TABLE enc_chats ADD COLUMN exchange_id INTEGER default 0").stepThis().dispose(); database.executeFast("ALTER TABLE enc_chats ADD COLUMN key_date INTEGER default 0").stepThis().dispose(); database.executeFast("ALTER TABLE enc_chats ADD COLUMN fprint INTEGER default 0").stepThis().dispose(); database.executeFast("ALTER TABLE enc_chats ADD COLUMN fauthkey BLOB default NULL").stepThis().dispose(); database.executeFast("ALTER TABLE enc_chats ADD COLUMN khash BLOB default NULL").stepThis().dispose(); database.executeFast("PRAGMA user_version = 10").stepThis().dispose(); version = 10; } if (version == 10) { database.executeFast("CREATE TABLE IF NOT EXISTS web_recent_v3(id TEXT, type INTEGER, image_url TEXT, thumb_url TEXT, local_url TEXT, width INTEGER, height INTEGER, size INTEGER, date INTEGER, PRIMARY KEY (id, type));").stepThis().dispose(); database.executeFast("PRAGMA user_version = 11").stepThis().dispose(); version = 11; } if (version == 11 || version == 12) { database.executeFast("DROP INDEX IF EXISTS uid_mid_idx_media;").stepThis().dispose(); database.executeFast("DROP INDEX IF EXISTS mid_idx_media;").stepThis().dispose(); database.executeFast("DROP INDEX IF EXISTS uid_date_mid_idx_media;").stepThis().dispose(); database.executeFast("DROP TABLE IF EXISTS media;").stepThis().dispose(); database.executeFast("DROP TABLE IF EXISTS media_counts;").stepThis().dispose(); database.executeFast("CREATE TABLE IF NOT EXISTS media_v2(mid INTEGER PRIMARY KEY, uid INTEGER, date INTEGER, type INTEGER, data BLOB)").stepThis().dispose(); database.executeFast("CREATE TABLE IF NOT EXISTS media_counts_v2(uid INTEGER, type INTEGER, count INTEGER, PRIMARY KEY(uid, type))").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS uid_mid_type_date_idx_media ON media_v2(uid, mid, type, date);").stepThis().dispose(); database.executeFast("CREATE TABLE IF NOT EXISTS keyvalue(id TEXT PRIMARY KEY, value TEXT)").stepThis().dispose(); database.executeFast("PRAGMA user_version = 13").stepThis().dispose(); version = 13; } if (version == 13) { database.executeFast("ALTER TABLE messages ADD COLUMN replydata BLOB default NULL").stepThis().dispose(); database.executeFast("PRAGMA user_version = 14").stepThis().dispose(); version = 14; } if (version == 14) { database.executeFast("CREATE TABLE IF NOT EXISTS hashtag_recent_v2(id TEXT PRIMARY KEY, date INTEGER);").stepThis().dispose(); database.executeFast("PRAGMA user_version = 15").stepThis().dispose(); version = 15; } if (version == 15) { database.executeFast("CREATE TABLE IF NOT EXISTS webpage_pending(id INTEGER, mid INTEGER, PRIMARY KEY (id, mid));").stepThis().dispose(); database.executeFast("PRAGMA user_version = 16").stepThis().dispose(); version = 16; } if (version == 16) { database.executeFast("ALTER TABLE dialogs ADD COLUMN inbox_max INTEGER default 0").stepThis().dispose(); database.executeFast("ALTER TABLE dialogs ADD COLUMN outbox_max INTEGER default 0").stepThis().dispose(); database.executeFast("PRAGMA user_version = 17").stepThis().dispose(); version = 17; } if (version == 17) { database.executeFast("CREATE TABLE bot_info(uid INTEGER PRIMARY KEY, info BLOB)").stepThis().dispose(); database.executeFast("PRAGMA user_version = 18").stepThis().dispose(); version = 18; } if (version == 18) { database.executeFast("DROP TABLE IF EXISTS stickers;").stepThis().dispose(); database.executeFast("CREATE TABLE IF NOT EXISTS stickers_v2(id INTEGER PRIMARY KEY, data BLOB, date INTEGER, hash INTEGER);").stepThis().dispose(); database.executeFast("PRAGMA user_version = 19").stepThis().dispose(); version = 19; } if (version == 19) { database.executeFast("CREATE TABLE IF NOT EXISTS bot_keyboard(uid INTEGER PRIMARY KEY, mid INTEGER, info BLOB)").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS bot_keyboard_idx_mid ON bot_keyboard(mid);").stepThis().dispose(); database.executeFast("PRAGMA user_version = 20").stepThis().dispose(); version = 20; } if (version == 20) { database.executeFast("CREATE TABLE search_recent(did INTEGER PRIMARY KEY, date INTEGER);").stepThis().dispose(); database.executeFast("PRAGMA user_version = 21").stepThis().dispose(); version = 21; } if (version == 21) { database.executeFast("CREATE TABLE IF NOT EXISTS chat_settings_v2(uid INTEGER PRIMARY KEY, info BLOB)").stepThis().dispose(); SQLiteCursor cursor = database.queryFinalized("SELECT uid, participants FROM chat_settings WHERE uid < 0"); SQLitePreparedStatement state = database.executeFast("REPLACE INTO chat_settings_v2 VALUES(?, ?)"); while (cursor.next()) { int chat_id = cursor.intValue(0); NativeByteBuffer data = cursor.byteBufferValue(1); if (data != null) { TLRPC.ChatParticipants participants = TLRPC.ChatParticipants.TLdeserialize(data, data.readInt32(false), false); data.reuse(); if (participants != null) { TLRPC.TL_chatFull chatFull = new TLRPC.TL_chatFull(); chatFull.id = chat_id; chatFull.chat_photo = new TLRPC.TL_photoEmpty(); chatFull.notify_settings = new TLRPC.TL_peerNotifySettingsEmpty_layer77(); chatFull.exported_invite = null; chatFull.participants = participants; NativeByteBuffer data2 = new NativeByteBuffer(chatFull.getObjectSize()); chatFull.serializeToStream(data2); state.requery(); state.bindInteger(1, chat_id); state.bindByteBuffer(2, data2); state.step(); data2.reuse(); } } } state.dispose(); cursor.dispose(); database.executeFast("DROP TABLE IF EXISTS chat_settings;").stepThis().dispose(); database.executeFast("ALTER TABLE dialogs ADD COLUMN last_mid_i INTEGER default 0").stepThis().dispose(); database.executeFast("ALTER TABLE dialogs ADD COLUMN unread_count_i INTEGER default 0").stepThis().dispose(); database.executeFast("ALTER TABLE dialogs ADD COLUMN pts INTEGER default 0").stepThis().dispose(); database.executeFast("ALTER TABLE dialogs ADD COLUMN date_i INTEGER default 0").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS last_mid_i_idx_dialogs ON dialogs(last_mid_i);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS unread_count_i_idx_dialogs ON dialogs(unread_count_i);").stepThis().dispose(); database.executeFast("ALTER TABLE messages ADD COLUMN imp INTEGER default 0").stepThis().dispose(); database.executeFast("CREATE TABLE IF NOT EXISTS messages_holes(uid INTEGER, start INTEGER, end INTEGER, PRIMARY KEY(uid, start));").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS uid_end_messages_holes ON messages_holes(uid, end);").stepThis().dispose(); database.executeFast("PRAGMA user_version = 22").stepThis().dispose(); version = 22; } if (version == 22) { database.executeFast("CREATE TABLE IF NOT EXISTS media_holes_v2(uid INTEGER, type INTEGER, start INTEGER, end INTEGER, PRIMARY KEY(uid, type, start));").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS uid_end_media_holes_v2 ON media_holes_v2(uid, type, end);").stepThis().dispose(); database.executeFast("PRAGMA user_version = 23").stepThis().dispose(); version = 23; } if (version == 23 || version == 24) { database.executeFast("DELETE FROM media_holes_v2 WHERE uid != 0 AND type >= 0 AND start IN (0, 1)").stepThis().dispose(); database.executeFast("PRAGMA user_version = 25").stepThis().dispose(); version = 25; } if (version == 25 || version == 26) { database.executeFast("CREATE TABLE IF NOT EXISTS channel_users_v2(did INTEGER, uid INTEGER, date INTEGER, data BLOB, PRIMARY KEY(did, uid))").stepThis().dispose(); database.executeFast("PRAGMA user_version = 27").stepThis().dispose(); version = 27; } if (version == 27) { database.executeFast("ALTER TABLE web_recent_v3 ADD COLUMN document BLOB default NULL").stepThis().dispose(); database.executeFast("PRAGMA user_version = 28").stepThis().dispose(); version = 28; } if (version == 28 || version == 29) { database.executeFast("DELETE FROM sent_files_v2 WHERE 1").stepThis().dispose(); database.executeFast("DELETE FROM download_queue WHERE 1").stepThis().dispose(); database.executeFast("PRAGMA user_version = 30").stepThis().dispose(); version = 30; } if (version == 30) { database.executeFast("ALTER TABLE chat_settings_v2 ADD COLUMN pinned INTEGER default 0").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS chat_settings_pinned_idx ON chat_settings_v2(uid, pinned) WHERE pinned != 0;").stepThis().dispose(); database.executeFast("CREATE TABLE IF NOT EXISTS users_data(uid INTEGER PRIMARY KEY, about TEXT)").stepThis().dispose(); database.executeFast("PRAGMA user_version = 31").stepThis().dispose(); version = 31; } if (version == 31) { database.executeFast("DROP TABLE IF EXISTS bot_recent;").stepThis().dispose(); database.executeFast("CREATE TABLE IF NOT EXISTS chat_hints(did INTEGER, type INTEGER, rating REAL, date INTEGER, PRIMARY KEY(did, type))").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS chat_hints_rating_idx ON chat_hints(rating);").stepThis().dispose(); database.executeFast("PRAGMA user_version = 32").stepThis().dispose(); version = 32; } if (version == 32) { database.executeFast("DROP INDEX IF EXISTS uid_mid_idx_imp_messages;").stepThis().dispose(); database.executeFast("DROP INDEX IF EXISTS uid_date_mid_imp_idx_messages;").stepThis().dispose(); database.executeFast("PRAGMA user_version = 33").stepThis().dispose(); version = 33; } if (version == 33) { database.executeFast("CREATE TABLE IF NOT EXISTS pending_tasks(id INTEGER PRIMARY KEY, data BLOB);").stepThis().dispose(); database.executeFast("PRAGMA user_version = 34").stepThis().dispose(); version = 34; } if (version == 34) { database.executeFast("CREATE TABLE IF NOT EXISTS stickers_featured(id INTEGER PRIMARY KEY, data BLOB, unread BLOB, date INTEGER, hash INTEGER);").stepThis().dispose(); database.executeFast("PRAGMA user_version = 35").stepThis().dispose(); version = 35; } if (version == 35) { database.executeFast("CREATE TABLE IF NOT EXISTS requested_holes(uid INTEGER, seq_out_start INTEGER, seq_out_end INTEGER, PRIMARY KEY (uid, seq_out_start, seq_out_end));").stepThis().dispose(); database.executeFast("PRAGMA user_version = 36").stepThis().dispose(); version = 36; } if (version == 36) { database.executeFast("ALTER TABLE enc_chats ADD COLUMN in_seq_no INTEGER default 0").stepThis().dispose(); database.executeFast("PRAGMA user_version = 37").stepThis().dispose(); version = 37; } if (version == 37) { database.executeFast("CREATE TABLE IF NOT EXISTS botcache(id TEXT PRIMARY KEY, date INTEGER, data BLOB)").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS botcache_date_idx ON botcache(date);").stepThis().dispose(); database.executeFast("PRAGMA user_version = 38").stepThis().dispose(); version = 38; } if (version == 38) { database.executeFast("ALTER TABLE dialogs ADD COLUMN pinned INTEGER default 0").stepThis().dispose(); database.executeFast("PRAGMA user_version = 39").stepThis().dispose(); version = 39; } if (version == 39) { database.executeFast("ALTER TABLE enc_chats ADD COLUMN admin_id INTEGER default 0").stepThis().dispose(); database.executeFast("PRAGMA user_version = 40").stepThis().dispose(); version = 40; } if (version == 40) { fixNotificationSettings(); database.executeFast("PRAGMA user_version = 41").stepThis().dispose(); version = 41; } if (version == 41) { database.executeFast("ALTER TABLE messages ADD COLUMN mention INTEGER default 0").stepThis().dispose(); database.executeFast("ALTER TABLE user_contacts_v6 ADD COLUMN imported INTEGER default 0").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS uid_mention_idx_messages ON messages(uid, mention, read_state);").stepThis().dispose(); database.executeFast("PRAGMA user_version = 42").stepThis().dispose(); version = 42; } if (version == 42) { database.executeFast("CREATE TABLE IF NOT EXISTS sharing_locations(uid INTEGER PRIMARY KEY, mid INTEGER, date INTEGER, period INTEGER, message BLOB);").stepThis().dispose(); database.executeFast("PRAGMA user_version = 43").stepThis().dispose(); version = 43; } if (version == 43) { database.executeFast("PRAGMA user_version = 44").stepThis().dispose(); version = 44; } if (version == 44) { database.executeFast("CREATE TABLE IF NOT EXISTS user_contacts_v7(key TEXT PRIMARY KEY, uid INTEGER, fname TEXT, sname TEXT, imported INTEGER)").stepThis().dispose(); database.executeFast("CREATE TABLE IF NOT EXISTS user_phones_v7(key TEXT, phone TEXT, sphone TEXT, deleted INTEGER, PRIMARY KEY (key, phone))").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS sphone_deleted_idx_user_phones ON user_phones_v7(sphone, deleted);").stepThis().dispose(); database.executeFast("PRAGMA user_version = 45").stepThis().dispose(); version = 45; } if (version == 45) { database.executeFast("ALTER TABLE enc_chats ADD COLUMN mtproto_seq INTEGER default 0").stepThis().dispose(); database.executeFast("PRAGMA user_version = 46").stepThis().dispose(); version = 46; } if (version == 46) { database.executeFast("DELETE FROM botcache WHERE 1").stepThis().dispose(); database.executeFast("PRAGMA user_version = 47").stepThis().dispose(); version = 47; } if (version == 47) { database.executeFast("ALTER TABLE dialogs ADD COLUMN flags INTEGER default 0").stepThis().dispose(); database.executeFast("PRAGMA user_version = 48").stepThis().dispose(); version = 48; } if (version == 48) { database.executeFast("CREATE TABLE IF NOT EXISTS unread_push_messages(uid INTEGER, mid INTEGER, random INTEGER, date INTEGER, data BLOB, fm TEXT, name TEXT, uname TEXT, flags INTEGER, PRIMARY KEY(uid, mid))").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS unread_push_messages_idx_date ON unread_push_messages(date);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS unread_push_messages_idx_random ON unread_push_messages(random);").stepThis().dispose(); database.executeFast("PRAGMA user_version = 49").stepThis().dispose(); version = 49; } if (version == 49) { database.executeFast("CREATE TABLE IF NOT EXISTS user_settings(uid INTEGER PRIMARY KEY, info BLOB, pinned INTEGER)").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS user_settings_pinned_idx ON user_settings(uid, pinned) WHERE pinned != 0;").stepThis().dispose(); database.executeFast("PRAGMA user_version = 50").stepThis().dispose(); version = 50; } if (version == 50) { database.executeFast("DELETE FROM sent_files_v2 WHERE 1").stepThis().dispose(); database.executeFast("ALTER TABLE sent_files_v2 ADD COLUMN parent TEXT").stepThis().dispose(); database.executeFast("DELETE FROM download_queue WHERE 1").stepThis().dispose(); database.executeFast("ALTER TABLE download_queue ADD COLUMN parent TEXT").stepThis().dispose(); database.executeFast("PRAGMA user_version = 51").stepThis().dispose(); version = 51; } if (version == 51) { database.executeFast("ALTER TABLE media_counts_v2 ADD COLUMN old INTEGER").stepThis().dispose(); database.executeFast("PRAGMA user_version = 52").stepThis().dispose(); version = 52; } if (version == 52) { database.executeFast("CREATE TABLE IF NOT EXISTS polls(mid INTEGER PRIMARY KEY, id INTEGER);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS polls_id ON polls(id);").stepThis().dispose(); database.executeFast("PRAGMA user_version = 53").stepThis().dispose(); version = 53; } if (version == 53) { database.executeFast("ALTER TABLE chat_settings_v2 ADD COLUMN online INTEGER default 0").stepThis().dispose(); database.executeFast("PRAGMA user_version = 54").stepThis().dispose(); version = 54; } if (version == 54) { database.executeFast("DROP TABLE IF EXISTS wallpapers;").stepThis().dispose(); database.executeFast("PRAGMA user_version = 55").stepThis().dispose(); version = 55; } if (version == 55) { database.executeFast("CREATE TABLE IF NOT EXISTS wallpapers2(uid INTEGER PRIMARY KEY, data BLOB, num INTEGER)").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS wallpapers_num ON wallpapers2(num);").stepThis().dispose(); database.executeFast("PRAGMA user_version = 56").stepThis().dispose(); version = 56; } if (version == 56 || version == 57) { database.executeFast("CREATE TABLE IF NOT EXISTS emoji_keywords_v2(lang TEXT, keyword TEXT, emoji TEXT, PRIMARY KEY(lang, keyword, emoji));").stepThis().dispose(); database.executeFast("CREATE TABLE IF NOT EXISTS emoji_keywords_info_v2(lang TEXT PRIMARY KEY, alias TEXT, version INTEGER);").stepThis().dispose(); database.executeFast("PRAGMA user_version = 58").stepThis().dispose(); version = 58; } if (version == 58) { database.executeFast("CREATE INDEX IF NOT EXISTS emoji_keywords_v2_keyword ON emoji_keywords_v2(keyword);").stepThis().dispose(); database.executeFast("ALTER TABLE emoji_keywords_info_v2 ADD COLUMN date INTEGER default 0").stepThis().dispose(); database.executeFast("PRAGMA user_version = 59").stepThis().dispose(); version = 59; } if (version == 59) { database.executeFast("ALTER TABLE dialogs ADD COLUMN folder_id INTEGER default 0").stepThis().dispose(); database.executeFast("ALTER TABLE dialogs ADD COLUMN data BLOB default NULL").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS folder_id_idx_dialogs ON dialogs(folder_id);").stepThis().dispose(); database.executeFast("PRAGMA user_version = 60").stepThis().dispose(); version = 60; } if (version == 60) { database.executeFast("DROP TABLE IF EXISTS channel_admins;").stepThis().dispose(); database.executeFast("DROP TABLE IF EXISTS blocked_users;").stepThis().dispose(); database.executeFast("PRAGMA user_version = 61").stepThis().dispose(); version = 61; } if (version == 61) { database.executeFast("DROP INDEX IF EXISTS send_state_idx_messages;").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS send_state_idx_messages2 ON messages(mid, send_state, date);").stepThis().dispose(); database.executeFast("PRAGMA user_version = 62").stepThis().dispose(); version = 62; } if (version == 62) { database.executeFast("CREATE TABLE IF NOT EXISTS scheduled_messages(mid INTEGER PRIMARY KEY, uid INTEGER, send_state INTEGER, date INTEGER, data BLOB, ttl INTEGER, replydata BLOB)").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS send_state_idx_scheduled_messages ON scheduled_messages(mid, send_state, date);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS uid_date_idx_scheduled_messages ON scheduled_messages(uid, date);").stepThis().dispose(); database.executeFast("PRAGMA user_version = 63").stepThis().dispose(); version = 63; } if (version == 63) { database.executeFast("DELETE FROM download_queue WHERE 1").stepThis().dispose(); database.executeFast("PRAGMA user_version = 64").stepThis().dispose(); version = 64; } if (version == 64) { database.executeFast("CREATE TABLE IF NOT EXISTS dialog_filter(id INTEGER PRIMARY KEY, ord INTEGER, unread_count INTEGER, flags INTEGER, title TEXT)").stepThis().dispose(); database.executeFast("CREATE TABLE IF NOT EXISTS dialog_filter_ep(id INTEGER, peer INTEGER, PRIMARY KEY (id, peer))").stepThis().dispose(); database.executeFast("PRAGMA user_version = 65").stepThis().dispose(); version = 65; } if (version == 65) { database.executeFast("CREATE INDEX IF NOT EXISTS flags_idx_dialogs ON dialogs(flags);").stepThis().dispose(); database.executeFast("PRAGMA user_version = 66").stepThis().dispose(); version = 66; } if (version == 66) { database.executeFast("CREATE TABLE dialog_filter_pin_v2(id INTEGER, peer INTEGER, pin INTEGER, PRIMARY KEY (id, peer))").stepThis().dispose(); database.executeFast("PRAGMA user_version = 67").stepThis().dispose(); version = 67; } if (version == 67) { database.executeFast("CREATE TABLE IF NOT EXISTS stickers_dice(emoji TEXT PRIMARY KEY, data BLOB, date INTEGER);").stepThis().dispose(); database.executeFast("PRAGMA user_version = 68").stepThis().dispose(); version = 68; } if (version == 68) { executeNoException("ALTER TABLE messages ADD COLUMN forwards INTEGER default 0"); database.executeFast("PRAGMA user_version = 69").stepThis().dispose(); version = 69; } if (version == 69) { executeNoException("ALTER TABLE messages ADD COLUMN replies_data BLOB default NULL"); executeNoException("ALTER TABLE messages ADD COLUMN thread_reply_id INTEGER default 0"); database.executeFast("PRAGMA user_version = 70").stepThis().dispose(); version = 70; } if (version == 70) { database.executeFast("CREATE TABLE IF NOT EXISTS chat_pinned_v2(uid INTEGER, mid INTEGER, data BLOB, PRIMARY KEY (uid, mid));").stepThis().dispose(); database.executeFast("PRAGMA user_version = 71").stepThis().dispose(); version = 71; } if (version == 71) { executeNoException("ALTER TABLE sharing_locations ADD COLUMN proximity INTEGER default 0"); database.executeFast("PRAGMA user_version = 72").stepThis().dispose(); version = 72; } if (version == 72) { database.executeFast("CREATE TABLE IF NOT EXISTS chat_pinned_count(uid INTEGER PRIMARY KEY, count INTEGER, end INTEGER);").stepThis().dispose(); database.executeFast("PRAGMA user_version = 73").stepThis().dispose(); version = 73; } if (version == 73) { executeNoException("ALTER TABLE chat_settings_v2 ADD COLUMN inviter INTEGER default 0"); database.executeFast("PRAGMA user_version = 74").stepThis().dispose(); version = 74; } if (version == 74) { database.executeFast("CREATE TABLE IF NOT EXISTS shortcut_widget(id INTEGER, did INTEGER, ord INTEGER, PRIMARY KEY (id, did));").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS shortcut_widget_did ON shortcut_widget(did);").stepThis().dispose(); database.executeFast("PRAGMA user_version = 75").stepThis().dispose(); version = 75; } if (version == 75) { executeNoException("ALTER TABLE chat_settings_v2 ADD COLUMN links INTEGER default 0"); database.executeFast("PRAGMA user_version = 76").stepThis().dispose(); version = 76; } if (version == 76) { executeNoException("ALTER TABLE enc_tasks_v2 ADD COLUMN media INTEGER default -1"); database.executeFast("PRAGMA user_version = 77").stepThis().dispose(); version = 77; } if (version == 77) { database.executeFast("DROP TABLE IF EXISTS channel_admins_v2;").stepThis().dispose(); database.executeFast("CREATE TABLE IF NOT EXISTS channel_admins_v3(did INTEGER, uid INTEGER, data BLOB, PRIMARY KEY(did, uid))").stepThis().dispose(); database.executeFast("PRAGMA user_version = 78").stepThis().dispose(); version = 78; } if (version == 78) { } } catch (Exception e) { FileLog.e(e); } }); } private void executeNoException(String query) { try { database.executeFast(query).stepThis().dispose(); } catch (Exception e) { FileLog.e(e); } } private void cleanupInternal(boolean deleteFiles) { lastDateValue = 0; lastSeqValue = 0; lastPtsValue = 0; lastQtsValue = 0; lastSecretVersion = 0; mainUnreadCount = 0; archiveUnreadCount = 0; pendingMainUnreadCount = 0; pendingArchiveUnreadCount = 0; dialogFilters.clear(); dialogFiltersMap.clear(); unknownDialogsIds.clear(); lastSavedSeq = 0; lastSavedPts = 0; lastSavedDate = 0; lastSavedQts = 0; secretPBytes = null; secretG = 0; if (database != null) { database.close(); database = null; } if (deleteFiles) { if (cacheFile != null) { cacheFile.delete(); cacheFile = null; } if (walCacheFile != null) { walCacheFile.delete(); walCacheFile = null; } if (shmCacheFile != null) { shmCacheFile.delete(); shmCacheFile = null; } } } public void cleanup(final boolean isLogin) { storageQueue.postRunnable(() -> { cleanupInternal(true); openDatabase(1); if (isLogin) { Utilities.stageQueue.postRunnable(() -> getMessagesController().getDifference()); } }); } public void saveSecretParams(final int lsv, final int sg, final byte[] pbytes) { storageQueue.postRunnable(() -> { try { SQLitePreparedStatement state = database.executeFast("UPDATE params SET lsv = ?, sg = ?, pbytes = ? WHERE id = 1"); state.bindInteger(1, lsv); state.bindInteger(2, sg); NativeByteBuffer data = new NativeByteBuffer(pbytes != null ? pbytes.length : 1); if (pbytes != null) { data.writeBytes(pbytes); } state.bindByteBuffer(3, data); state.step(); state.dispose(); data.reuse(); } catch (Exception e) { FileLog.e(e); } }); } private void fixNotificationSettings() { storageQueue.postRunnable(() -> { try { LongSparseArray ids = new LongSparseArray<>(); SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); Map values = preferences.getAll(); for (Map.Entry entry : values.entrySet()) { String key = entry.getKey(); if (key.startsWith("notify2_")) { Integer value = (Integer) entry.getValue(); if (value == 2 || value == 3) { key = key.replace("notify2_", ""); long flags; if (value == 2) { flags = 1; } else { Integer time = (Integer) values.get("notifyuntil_" + key); if (time != null) { flags = ((long) time << 32) | 1; } else { flags = 1; } } try { ids.put(Long.parseLong(key), flags); } catch (Exception e) { e.printStackTrace(); } } } } try { database.beginTransaction(); SQLitePreparedStatement state = database.executeFast("REPLACE INTO dialog_settings VALUES(?, ?)"); for (int a = 0; a < ids.size(); a++) { state.requery(); state.bindLong(1, ids.keyAt(a)); state.bindLong(2, ids.valueAt(a)); state.step(); } state.dispose(); database.commitTransaction(); } catch (Exception e) { FileLog.e(e); } } catch (Throwable e) { FileLog.e(e); } }); } public long createPendingTask(final NativeByteBuffer data) { if (data == null) { return 0; } final long id = lastTaskId.getAndAdd(1); storageQueue.postRunnable(() -> { try { SQLitePreparedStatement state = database.executeFast("REPLACE INTO pending_tasks VALUES(?, ?)"); state.bindLong(1, id); state.bindByteBuffer(2, data); state.step(); state.dispose(); } catch (Exception e) { FileLog.e(e); } finally { data.reuse(); } }); return id; } public void removePendingTask(final long id) { storageQueue.postRunnable(() -> { try { database.executeFast("DELETE FROM pending_tasks WHERE id = " + id).stepThis().dispose(); } catch (Exception e) { FileLog.e(e); } }); } private void loadPendingTasks() { storageQueue.postRunnable(() -> { try { SQLiteCursor cursor = database.queryFinalized("SELECT id, data FROM pending_tasks WHERE 1"); while (cursor.next()) { final long taskId = cursor.longValue(0); NativeByteBuffer data = cursor.byteBufferValue(1); if (data != null) { int type = data.readInt32(false); switch (type) { case 0: { final TLRPC.Chat chat = TLRPC.Chat.TLdeserialize(data, data.readInt32(false), false); if (chat != null) { Utilities.stageQueue.postRunnable(() -> getMessagesController().loadUnknownChannel(chat, taskId)); } break; } case 1: { final int channelId = data.readInt32(false); final int newDialogType = data.readInt32(false); Utilities.stageQueue.postRunnable(() -> getMessagesController().getChannelDifference(channelId, newDialogType, taskId, null)); break; } case 2: case 5: case 8: case 10: case 14: { final TLRPC.Dialog dialog = new TLRPC.TL_dialog(); dialog.id = data.readInt64(false); dialog.top_message = data.readInt32(false); dialog.read_inbox_max_id = data.readInt32(false); dialog.read_outbox_max_id = data.readInt32(false); dialog.unread_count = data.readInt32(false); dialog.last_message_date = data.readInt32(false); dialog.pts = data.readInt32(false); dialog.flags = data.readInt32(false); if (type >= 5) { dialog.pinned = data.readBool(false); dialog.pinnedNum = data.readInt32(false); } if (type >= 8) { dialog.unread_mentions_count = data.readInt32(false); } if (type >= 10) { dialog.unread_mark = data.readBool(false); } if (type >= 14) { dialog.folder_id = data.readInt32(false); } final TLRPC.InputPeer peer = TLRPC.InputPeer.TLdeserialize(data, data.readInt32(false), false); AndroidUtilities.runOnUIThread(() -> getMessagesController().checkLastDialogMessage(dialog, peer, taskId)); break; } case 3: { long random_id = data.readInt64(false); TLRPC.InputPeer peer = TLRPC.InputPeer.TLdeserialize(data, data.readInt32(false), false); TLRPC.TL_inputMediaGame game = (TLRPC.TL_inputMediaGame) TLRPC.InputMedia.TLdeserialize(data, data.readInt32(false), false); getSendMessagesHelper().sendGame(peer, game, random_id, taskId); break; } case 4: { final long did = data.readInt64(false); final boolean pin = data.readBool(false); final TLRPC.InputPeer peer = TLRPC.InputPeer.TLdeserialize(data, data.readInt32(false), false); AndroidUtilities.runOnUIThread(() -> getMessagesController().pinDialog(did, pin, peer, taskId)); break; } case 6: { final int channelId = data.readInt32(false); final int newDialogType = data.readInt32(false); final TLRPC.InputChannel inputChannel = TLRPC.InputChannel.TLdeserialize(data, data.readInt32(false), false); Utilities.stageQueue.postRunnable(() -> getMessagesController().getChannelDifference(channelId, newDialogType, taskId, inputChannel)); break; } case 7: { final int channelId = data.readInt32(false); int constructor = data.readInt32(false); TLObject request = TLRPC.TL_messages_deleteMessages.TLdeserialize(data, constructor, false); if (request == null) { request = TLRPC.TL_channels_deleteMessages.TLdeserialize(data, constructor, false); } if (request == null) { removePendingTask(taskId); } else { final TLObject finalRequest = request; AndroidUtilities.runOnUIThread(() -> getMessagesController().deleteMessages(null, null, null, 0, channelId, true, false, false, taskId, finalRequest)); } break; } case 9: { final long did = data.readInt64(false); final TLRPC.InputPeer peer = TLRPC.InputPeer.TLdeserialize(data, data.readInt32(false), false); AndroidUtilities.runOnUIThread(() -> getMessagesController().markDialogAsUnread(did, peer, taskId)); break; } case 11: { TLRPC.InputChannel inputChannel; final int mid = data.readInt32(false); final int channelId = data.readInt32(false); final int ttl = data.readInt32(false); if (channelId != 0) { inputChannel = TLRPC.InputChannel.TLdeserialize(data, data.readInt32(false), false); } else { inputChannel = null; } AndroidUtilities.runOnUIThread(() -> getMessagesController().markMessageAsRead(mid, channelId, inputChannel, ttl, taskId)); break; } case 12: case 19: case 20: removePendingTask(taskId); break; case 21: { Theme.OverrideWallpaperInfo info = new Theme.OverrideWallpaperInfo(); long id = data.readInt64(false); info.isBlurred = data.readBool(false); info.isMotion = data.readBool(false); info.color = data.readInt32(false); info.gradientColor = data.readInt32(false); info.rotation = data.readInt32(false); info.intensity = (float) data.readDouble(false); boolean install = data.readBool(false); info.slug = data.readString(false); info.originalFileName = data.readString(false); AndroidUtilities.runOnUIThread(() -> getMessagesController().saveWallpaperToServer(null, info, install, taskId)); break; } case 13: { final long did = data.readInt64(false); final boolean first = data.readBool(false); final int onlyHistory = data.readInt32(false); final int maxIdDelete = data.readInt32(false); final boolean revoke = data.readBool(false); TLRPC.InputPeer inputPeer = TLRPC.InputPeer.TLdeserialize(data, data.readInt32(false), false); AndroidUtilities.runOnUIThread(() -> getMessagesController().deleteDialog(did, first ? 1 : 0, onlyHistory, maxIdDelete, revoke, inputPeer, taskId)); break; } case 15: { TLRPC.InputPeer inputPeer = TLRPC.InputPeer.TLdeserialize(data, data.readInt32(false), false); Utilities.stageQueue.postRunnable(() -> getMessagesController().loadUnknownDialog(inputPeer, taskId)); break; } case 16: { final int folderId = data.readInt32(false); int count = data.readInt32(false); ArrayList peers = new ArrayList<>(); for (int a = 0; a < count; a++) { TLRPC.InputDialogPeer inputPeer = TLRPC.InputDialogPeer.TLdeserialize(data, data.readInt32(false), false); peers.add(inputPeer); } AndroidUtilities.runOnUIThread(() -> getMessagesController().reorderPinnedDialogs(folderId, peers, taskId)); break; } case 17: { final int folderId = data.readInt32(false); int count = data.readInt32(false); ArrayList peers = new ArrayList<>(); for (int a = 0; a < count; a++) { TLRPC.TL_inputFolderPeer inputPeer = TLRPC.TL_inputFolderPeer.TLdeserialize(data, data.readInt32(false), false); peers.add(inputPeer); } AndroidUtilities.runOnUIThread(() -> getMessagesController().addDialogToFolder(null, folderId, -1, peers, taskId)); break; } case 18: { final long dialogId = data.readInt64(false); final int channelId = data.readInt32(false); int constructor = data.readInt32(false); TLObject request = TLRPC.TL_messages_deleteScheduledMessages.TLdeserialize(data, constructor, false); if (request == null) { removePendingTask(taskId); } else { final TLObject finalRequest = request; AndroidUtilities.runOnUIThread(() -> MessagesController.getInstance(currentAccount).deleteMessages(null, null, null, dialogId, channelId, true, true, false, taskId, finalRequest)); } break; } case 22: { TLRPC.InputPeer inputPeer = TLRPC.InputPeer.TLdeserialize(data, data.readInt32(false), false); AndroidUtilities.runOnUIThread(() -> getMessagesController().reloadMentionsCountForChannel(inputPeer, taskId)); break; } } data.reuse(); } } cursor.dispose(); } catch (Exception e) { FileLog.e(e); } }); } public void saveChannelPts(final int channelId, final int pts) { storageQueue.postRunnable(() -> { try { SQLitePreparedStatement state = database.executeFast("UPDATE dialogs SET pts = ? WHERE did = ?"); state.bindInteger(1, pts); state.bindInteger(2, -channelId); state.step(); state.dispose(); } catch (Exception e) { FileLog.e(e); } }); } private void saveDiffParamsInternal(final int seq, final int pts, final int date, final int qts) { try { if (lastSavedSeq == seq && lastSavedPts == pts && lastSavedDate == date && lastQtsValue == qts) { return; } SQLitePreparedStatement state = database.executeFast("UPDATE params SET seq = ?, pts = ?, date = ?, qts = ? WHERE id = 1"); state.bindInteger(1, seq); state.bindInteger(2, pts); state.bindInteger(3, date); state.bindInteger(4, qts); state.step(); state.dispose(); lastSavedSeq = seq; lastSavedPts = pts; lastSavedDate = date; lastSavedQts = qts; } catch (Exception e) { FileLog.e(e); } } public void saveDiffParams(final int seq, final int pts, final int date, final int qts) { storageQueue.postRunnable(() -> saveDiffParamsInternal(seq, pts, date, qts)); } public void updateMutedDialogsFiltersCounters() { storageQueue.postRunnable(() -> resetAllUnreadCounters(true)); } public void setDialogFlags(final long did, final long flags) { storageQueue.postRunnable(() -> { try { int oldFlags = 0; SQLiteCursor cursor = database.queryFinalized("SELECT flags FROM dialog_settings WHERE did = " + did); if (cursor.next()) { oldFlags = cursor.intValue(0); } cursor.dispose(); if (flags == oldFlags) { return; } database.executeFast(String.format(Locale.US, "REPLACE INTO dialog_settings VALUES(%d, %d)", did, flags)).stepThis().dispose(); resetAllUnreadCounters(true); } catch (Exception e) { FileLog.e(e); } }); } public void putPushMessage(MessageObject message) { storageQueue.postRunnable(() -> { try { NativeByteBuffer data = new NativeByteBuffer(message.messageOwner.getObjectSize()); message.messageOwner.serializeToStream(data); long messageId = message.getId(); if (message.messageOwner.peer_id.channel_id != 0) { messageId |= ((long) message.messageOwner.peer_id.channel_id) << 32; } int flags = 0; if (message.localType == 2) { flags |= 1; } if (message.localChannel) { flags |= 2; } SQLitePreparedStatement state = database.executeFast("REPLACE INTO unread_push_messages VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)"); state.requery(); state.bindLong(1, message.getDialogId()); state.bindLong(2, messageId); state.bindLong(3, message.messageOwner.random_id); state.bindInteger(4, message.messageOwner.date); state.bindByteBuffer(5, data); if (message.messageText == null) { state.bindNull(6); } else { state.bindString(6, message.messageText.toString()); } if (message.localName == null) { state.bindNull(7); } else { state.bindString(7, message.localName); } if (message.localUserName == null) { state.bindNull(8); } else { state.bindString(8, message.localUserName); } state.bindInteger(9, flags); state.step(); data.reuse(); state.dispose(); } catch (Exception e) { FileLog.e(e); } }); } private static class ReadDialog { public int lastMid; public int date; public int unreadCount; } public void readAllDialogs(int folderId) { storageQueue.postRunnable(() -> { try { ArrayList usersToLoad = new ArrayList<>(); ArrayList chatsToLoad = new ArrayList<>(); ArrayList encryptedChatIds = new ArrayList<>(); final LongSparseArray dialogs = new LongSparseArray<>(); SQLiteCursor cursor; if (folderId >= 0) { cursor = database.queryFinalized(String.format(Locale.US, "SELECT did, last_mid, unread_count, date FROM dialogs WHERE unread_count > 0 AND folder_id = %1$d", folderId)); } else { cursor = database.queryFinalized("SELECT did, last_mid, unread_count, date FROM dialogs WHERE unread_count > 0"); } while (cursor.next()) { long did = cursor.longValue(0); if (DialogObject.isFolderDialogId(did)) { continue; } ReadDialog dialog = new ReadDialog(); dialog.lastMid = cursor.intValue(1); dialog.unreadCount = cursor.intValue(2); dialog.date = cursor.intValue(3); dialogs.put(did, dialog); int lower_id = (int) did; int high_id = (int) (did >> 32); if (lower_id != 0) { if (lower_id < 0) { if (!chatsToLoad.contains(-lower_id)) { chatsToLoad.add(-lower_id); } } else { if (!usersToLoad.contains(lower_id)) { usersToLoad.add(lower_id); } } } else { if (!encryptedChatIds.contains(high_id)) { encryptedChatIds.add(high_id); } } } cursor.dispose(); final ArrayList users = new ArrayList<>(); final ArrayList chats = new ArrayList<>(); final ArrayList encryptedChats = new ArrayList<>(); if (!encryptedChatIds.isEmpty()) { getEncryptedChatsInternal(TextUtils.join(",", encryptedChatIds), encryptedChats, usersToLoad); } if (!usersToLoad.isEmpty()) { getUsersInternal(TextUtils.join(",", usersToLoad), users); } if (!chatsToLoad.isEmpty()) { getChatsInternal(TextUtils.join(",", chatsToLoad), chats); } AndroidUtilities.runOnUIThread(() -> { getMessagesController().putUsers(users, true); getMessagesController().putChats(chats, true); getMessagesController().putEncryptedChats(encryptedChats, true); for (int a = 0; a < dialogs.size(); a++) { long did = dialogs.keyAt(a); ReadDialog dialog = dialogs.valueAt(a); getMessagesController().markDialogAsRead(did, dialog.lastMid, dialog.lastMid, dialog.date, false, 0, dialog.unreadCount, true, 0); } }); } catch (Exception e) { FileLog.e(e); } }); } private TLRPC.messages_Dialogs loadDialogsByIds(String ids, ArrayList usersToLoad, ArrayList chatsToLoad, ArrayList encryptedToLoad) throws Exception { TLRPC.messages_Dialogs dialogs = new TLRPC.TL_messages_dialogs(); ArrayList replyMessages = new ArrayList<>(); LongSparseArray replyMessageOwners = new LongSparseArray<>(); SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT d.did, d.last_mid, d.unread_count, d.date, m.data, m.read_state, m.mid, m.send_state, s.flags, m.date, d.pts, d.inbox_max, d.outbox_max, m.replydata, d.pinned, d.unread_count_i, d.flags, d.folder_id, d.data FROM dialogs as d LEFT JOIN messages as m ON d.last_mid = m.mid LEFT JOIN dialog_settings as s ON d.did = s.did WHERE d.did IN (%s) ORDER BY d.pinned DESC, d.date DESC", ids)); while (cursor.next()) { long dialogId = cursor.longValue(0); TLRPC.Dialog dialog = new TLRPC.TL_dialog(); dialog.id = dialogId; dialog.top_message = cursor.intValue(1); dialog.unread_count = cursor.intValue(2); dialog.last_message_date = cursor.intValue(3); dialog.pts = cursor.intValue(10); dialog.flags = dialog.pts == 0 || (int) dialog.id > 0 ? 0 : 1; dialog.read_inbox_max_id = cursor.intValue(11); dialog.read_outbox_max_id = cursor.intValue(12); dialog.pinnedNum = cursor.intValue(14); dialog.pinned = dialog.pinnedNum != 0; dialog.unread_mentions_count = cursor.intValue(15); int dialog_flags = cursor.intValue(16); dialog.unread_mark = (dialog_flags & 1) != 0; long flags = cursor.longValue(8); int low_flags = (int) flags; dialog.notify_settings = new TLRPC.TL_peerNotifySettings(); if ((low_flags & 1) != 0) { dialog.notify_settings.mute_until = (int) (flags >> 32); if (dialog.notify_settings.mute_until == 0) { dialog.notify_settings.mute_until = Integer.MAX_VALUE; } } dialog.folder_id = cursor.intValue(17); dialogs.dialogs.add(dialog); NativeByteBuffer data = cursor.byteBufferValue(4); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); if (message != null) { message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); MessageObject.setUnreadFlags(message, cursor.intValue(5)); message.id = cursor.intValue(6); int date = cursor.intValue(9); if (date != 0) { dialog.last_message_date = date; } message.send_state = cursor.intValue(7); message.dialog_id = dialog.id; dialogs.messages.add(message); addUsersAndChatsFromMessage(message, usersToLoad, chatsToLoad); try { if (message.reply_to != null && message.reply_to.reply_to_msg_id != 0 && ( message.action instanceof TLRPC.TL_messageActionPinMessage || message.action instanceof TLRPC.TL_messageActionPaymentSent || message.action instanceof TLRPC.TL_messageActionGameScore)) { if (!cursor.isNull(13)) { NativeByteBuffer data2 = cursor.byteBufferValue(13); if (data2 != null) { message.replyMessage = TLRPC.Message.TLdeserialize(data2, data2.readInt32(false), false); message.replyMessage.readAttachPath(data2, getUserConfig().clientUserId); data2.reuse(); if (message.replyMessage != null) { addUsersAndChatsFromMessage(message.replyMessage, usersToLoad, chatsToLoad); } } } if (message.replyMessage == null) { long messageId = message.reply_to.reply_to_msg_id; if (message.reply_to.reply_to_peer_id != null) { if (message.reply_to.reply_to_peer_id.channel_id != 0) { messageId |= ((long) message.reply_to.reply_to_peer_id.channel_id) << 32; } } else if (message.peer_id.channel_id != 0) { messageId |= ((long) message.peer_id.channel_id) << 32; } if (!replyMessages.contains(messageId)) { replyMessages.add(messageId); } replyMessageOwners.put(dialog.id, message); } } } catch (Exception e) { FileLog.e(e); } } else { data.reuse(); } } int lower_id = (int) dialog.id; int high_id = (int) (dialog.id >> 32); if (lower_id != 0) { if (lower_id > 0) { if (!usersToLoad.contains(lower_id)) { usersToLoad.add(lower_id); } } else { if (!chatsToLoad.contains(-lower_id)) { chatsToLoad.add(-lower_id); } } } else { if (!encryptedToLoad.contains(high_id)) { encryptedToLoad.add(high_id); } } } cursor.dispose(); if (!replyMessages.isEmpty()) { SQLiteCursor replyCursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid, date, uid FROM messages WHERE mid IN(%s)", TextUtils.join(",", replyMessages))); while (replyCursor.next()) { NativeByteBuffer data = replyCursor.byteBufferValue(0); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); message.id = replyCursor.intValue(1); message.date = replyCursor.intValue(2); message.dialog_id = replyCursor.longValue(3); addUsersAndChatsFromMessage(message, usersToLoad, chatsToLoad); TLRPC.Message owner = replyMessageOwners.get(message.dialog_id); if (owner != null) { owner.replyMessage = message; message.dialog_id = owner.dialog_id; } } } replyCursor.dispose(); } return dialogs; } private void loadDialogFilters() { storageQueue.postRunnable(() -> { try { ArrayList usersToLoad = new ArrayList<>(); ArrayList chatsToLoad = new ArrayList<>(); ArrayList encryptedToLoad = new ArrayList<>(); ArrayList dialogsToLoad = new ArrayList<>(); SparseArray filtersById = new SparseArray<>(); usersToLoad.add(getUserConfig().getClientUserId()); SQLiteCursor filtersCursor = database.queryFinalized("SELECT id, ord, unread_count, flags, title FROM dialog_filter WHERE 1"); boolean updateCounters = false; while (filtersCursor.next()) { MessagesController.DialogFilter filter = new MessagesController.DialogFilter(); filter.id = filtersCursor.intValue(0); filter.order = filtersCursor.intValue(1); filter.pendingUnreadCount = filter.unreadCount = -1;//filtersCursor.intValue(2); filter.flags = filtersCursor.intValue(3); filter.name = filtersCursor.stringValue(4); dialogFilters.add(filter); dialogFiltersMap.put(filter.id, filter); filtersById.put(filter.id, filter); if (filter.pendingUnreadCount < 0) { updateCounters = true; } for (int a = 0; a < 2; a++) { SQLiteCursor cursor2; if (a == 0) { cursor2 = database.queryFinalized("SELECT peer, pin FROM dialog_filter_pin_v2 WHERE id = " + filter.id); } else { cursor2 = database.queryFinalized("SELECT peer FROM dialog_filter_ep WHERE id = " + filter.id); } while (cursor2.next()) { long did = cursor2.longValue(0); int lower_id = (int) did; if (a == 0) { if (lower_id != 0) { filter.alwaysShow.add(lower_id); } int pin = cursor2.intValue(1); if (pin != Integer.MIN_VALUE) { filter.pinnedDialogs.put(did, pin); if (!dialogsToLoad.contains(did)) { dialogsToLoad.add(did); } } } else { if (lower_id != 0) { filter.neverShow.add(lower_id); } } if (lower_id < 0) { if (!chatsToLoad.contains(-lower_id)) { chatsToLoad.add(-lower_id); } } else if (lower_id > 0) { if (!usersToLoad.contains(lower_id)) { usersToLoad.add(lower_id); } } else { int high_id = (int) (did >> 32); if (!encryptedToLoad.contains(high_id)) { encryptedToLoad.add(high_id); } } } cursor2.dispose(); } } filtersCursor.dispose(); Collections.sort(dialogFilters, (o1, o2) -> { if (o1.order > o2.order) { return 1; } else if (o1.order < o2.order) { return -1; } return 0; }); if (updateCounters) { calcUnreadCounters(true); } TLRPC.messages_Dialogs dialogs; if (!dialogsToLoad.isEmpty()) { dialogs = loadDialogsByIds(TextUtils.join(",", dialogsToLoad), usersToLoad, chatsToLoad, encryptedToLoad); } else { dialogs = new TLRPC.TL_messages_dialogs(); } final ArrayList users = new ArrayList<>(); final ArrayList chats = new ArrayList<>(); ArrayList encryptedChats = new ArrayList<>(); if (!encryptedToLoad.isEmpty()) { getEncryptedChatsInternal(TextUtils.join(",", encryptedToLoad), encryptedChats, usersToLoad); } if (!usersToLoad.isEmpty()) { getUsersInternal(TextUtils.join(",", usersToLoad), users); } if (!chatsToLoad.isEmpty()) { getChatsInternal(TextUtils.join(",", chatsToLoad), chats); } getMessagesController().processLoadedDialogFilters(new ArrayList<>(dialogFilters), dialogs, null, users, chats, encryptedChats, 0); } catch (Exception e) { FileLog.e(e); } }); } private int[][] contacts = new int[][]{new int[2], new int[2]}; private int[][] nonContacts = new int[][]{new int[2], new int[2]}; private int[][] bots = new int[][]{new int[2], new int[2]}; private int[][] channels = new int[][]{new int[2], new int[2]}; private int[][] groups = new int[][]{new int[2], new int[2]}; private int[] mentionChannels = new int[2]; private int[] mentionGroups = new int[2]; private LongSparseArray dialogsWithMentions = new LongSparseArray<>(); private LongSparseArray dialogsWithUnread = new LongSparseArray<>(); private void calcUnreadCounters(boolean apply) { try { for (int a = 0; a < 2; a++) { for (int b = 0; b < 2; b++) { contacts[a][b] = nonContacts[a][b] = bots[a][b] = channels[a][b] = groups[a][b] = 0; } } dialogsWithMentions.clear(); dialogsWithUnread.clear(); final ArrayList users = new ArrayList<>(); final ArrayList encUsers = new ArrayList<>(); final ArrayList chats = new ArrayList<>(); ArrayList usersToLoad = new ArrayList<>(); ArrayList chatsToLoad = new ArrayList<>(); ArrayList encryptedToLoad = new ArrayList<>(); LongSparseArray dialogsByFolders = new LongSparseArray<>(); SQLiteCursor cursor = database.queryFinalized("SELECT did, folder_id, unread_count, unread_count_i FROM dialogs WHERE unread_count > 0 OR flags > 0 UNION ALL " + "SELECT did, folder_id, unread_count, unread_count_i FROM dialogs WHERE unread_count_i > 0"); while (cursor.next()) { int folderId = cursor.intValue(1); long did = cursor.longValue(0); int unread = cursor.intValue(2); int mentions = cursor.intValue(3); if (unread > 0) { dialogsWithUnread.put(did, unread); } if (mentions > 0) { dialogsWithMentions.put(did, mentions); } /*if (BuildVars.DEBUG_VERSION) { FileLog.d("unread chat " + did + " counters = " + unread + " and " + mentions); }*/ dialogsByFolders.put(did, folderId); int lower_id = (int) did; int high_id = (int) (did >> 32); if (lower_id != 0) { if (lower_id < 0) { if (!chatsToLoad.contains(-lower_id)) { chatsToLoad.add(-lower_id); } } else { if (!usersToLoad.contains(lower_id)) { usersToLoad.add(lower_id); } } } else { if (!encryptedToLoad.contains(high_id)) { encryptedToLoad.add(high_id); } } } cursor.dispose(); SparseArray usersDict = new SparseArray<>(); SparseArray chatsDict = new SparseArray<>(); SparseArray encUsersDict = new SparseArray<>(); SparseArray encryptedChatsByUsersCount = new SparseArray<>(); SparseArray mutedDialogs = new SparseArray<>(); SparseArray archivedDialogs = new SparseArray<>(); if (!usersToLoad.isEmpty()) { getUsersInternal(TextUtils.join(",", usersToLoad), users); for (int a = 0, N = users.size(); a < N; a++) { TLRPC.User user = users.get(a); boolean muted = getMessagesController().isDialogMuted(user.id); int idx1 = dialogsByFolders.get(user.id); int idx2 = muted ? 1 : 0; if (muted) { mutedDialogs.put(user.id, true); } if (idx1 == 1) { archivedDialogs.put(user.id, true); } if (user.bot) { bots[idx1][idx2]++; } else if (user.self || user.contact) { contacts[idx1][idx2]++; } else { nonContacts[idx1][idx2]++; } usersDict.put(user.id, user); } } if (!encryptedToLoad.isEmpty()) { ArrayList encUsersToLoad = new ArrayList<>(); ArrayList encryptedChats = new ArrayList<>(); getEncryptedChatsInternal(TextUtils.join(",", encryptedToLoad), encryptedChats, encUsersToLoad); if (!encUsersToLoad.isEmpty()) { getUsersInternal(TextUtils.join(",", encUsersToLoad), encUsers); for (int a = 0, N = encUsers.size(); a < N; a++) { TLRPC.User user = encUsers.get(a); encUsersDict.put(user.id, user); } for (int a = 0, N = encryptedChats.size(); a < N; a++) { TLRPC.EncryptedChat encryptedChat = encryptedChats.get(a); TLRPC.User user = encUsersDict.get(encryptedChat.user_id); if (user == null) { continue; } long did = ((long) encryptedChat.id) << 32; boolean muted = getMessagesController().isDialogMuted(did); int idx1 = dialogsByFolders.get(did); int idx2 = muted ? 1 : 0; if (muted) { mutedDialogs.put(user.id, true); } if (idx1 == 1) { archivedDialogs.put(user.id, true); } if (user.self || user.contact) { contacts[idx1][idx2]++; } else { nonContacts[idx1][idx2]++; } int count = encryptedChatsByUsersCount.get(user.id, 0); encryptedChatsByUsersCount.put(user.id, count + 1); } } } if (!chatsToLoad.isEmpty()) { getChatsInternal(TextUtils.join(",", chatsToLoad), chats); for (int a = 0, N = chats.size(); a < N; a++) { TLRPC.Chat chat = chats.get(a); if (chat.migrated_to instanceof TLRPC.TL_inputChannel || ChatObject.isNotInChat(chat)) { dialogsWithUnread.remove(-chat.id); dialogsWithMentions.remove(-chat.id); continue; } boolean muted = getMessagesController().isDialogMuted(-chat.id, chat); int idx1 = dialogsByFolders.get(-chat.id); int idx2 = muted && dialogsWithMentions.indexOfKey(-chat.id) < 0 ? 1 : 0; if (muted) { mutedDialogs.put(-chat.id, true); } if (idx1 == 1) { archivedDialogs.put(-chat.id, true); } if (ChatObject.isChannel(chat) && !chat.megagroup) { channels[idx1][idx2]++; } else { groups[idx1][idx2]++; } chatsDict.put(chat.id, chat); } } /*if (BuildVars.DEBUG_VERSION) { for (int b = 0; b < 2; b++) { FileLog.d("contacts = " + contacts[b][0] + ", " + contacts[b][1]); FileLog.d("nonContacts = " + nonContacts[b][0] + ", " + nonContacts[b][1]); FileLog.d("groups = " + groups[b][0] + ", " + groups[b][1]); FileLog.d("channels = " + channels[b][0] + ", " + channels[b][1]); FileLog.d("bots = " + bots[b][0] + ", " + bots[b][1]); } }*/ for (int a = 0, N = dialogFilters.size(); a < N + 2; a++) { MessagesController.DialogFilter filter; int flags; if (a < N) { filter = dialogFilters.get(a); if (filter.pendingUnreadCount >= 0) { continue; } flags = filter.flags; } else { filter = null; flags = MessagesController.DIALOG_FILTER_FLAG_ALL_CHATS; if (a == N) { if (!getNotificationsController().showBadgeMuted) { flags |= MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED; } flags |= MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_ARCHIVED; } else { flags |= MessagesController.DIALOG_FILTER_FLAG_ONLY_ARCHIVED; } } int unreadCount = 0; if ((flags & MessagesController.DIALOG_FILTER_FLAG_CONTACTS) != 0) { if ((flags & MessagesController.DIALOG_FILTER_FLAG_ONLY_ARCHIVED) == 0) { unreadCount += contacts[0][0]; if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) == 0) { unreadCount += contacts[0][1]; } } if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_ARCHIVED) == 0) { unreadCount += contacts[1][0]; if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) == 0) { unreadCount += contacts[1][1]; } } } if ((flags & MessagesController.DIALOG_FILTER_FLAG_NON_CONTACTS) != 0) { if ((flags & MessagesController.DIALOG_FILTER_FLAG_ONLY_ARCHIVED) == 0) { unreadCount += nonContacts[0][0]; if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) == 0) { unreadCount += nonContacts[0][1]; } } if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_ARCHIVED) == 0) { unreadCount += nonContacts[1][0]; if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) == 0) { unreadCount += nonContacts[1][1]; } } } if ((flags & MessagesController.DIALOG_FILTER_FLAG_GROUPS) != 0) { if ((flags & MessagesController.DIALOG_FILTER_FLAG_ONLY_ARCHIVED) == 0) { unreadCount += groups[0][0]; if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) == 0) { unreadCount += groups[0][1]; } } if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_ARCHIVED) == 0) { unreadCount += groups[1][0]; if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) == 0) { unreadCount += groups[1][1]; } } } if ((flags & MessagesController.DIALOG_FILTER_FLAG_CHANNELS) != 0) { if ((flags & MessagesController.DIALOG_FILTER_FLAG_ONLY_ARCHIVED) == 0) { unreadCount += channels[0][0]; if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) == 0) { unreadCount += channels[0][1]; } } if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_ARCHIVED) == 0) { unreadCount += channels[1][0]; if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) == 0) { unreadCount += channels[1][1]; } } } if ((flags & MessagesController.DIALOG_FILTER_FLAG_BOTS) != 0) { if ((flags & MessagesController.DIALOG_FILTER_FLAG_ONLY_ARCHIVED) == 0) { unreadCount += bots[0][0]; if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) == 0) { unreadCount += bots[0][1]; } } if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_ARCHIVED) == 0) { unreadCount += bots[1][0]; if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) == 0) { unreadCount += bots[1][1]; } } } if (filter != null) { for (int b = 0, N2 = filter.alwaysShow.size(); b < N2; b++) { int did = filter.alwaysShow.get(b); if (did > 0) { for (int i = 0; i < 2; i++) { SparseArray dict = i == 0 ? usersDict : encUsersDict; TLRPC.User user = dict.get(did); if (user != null) { int count; if (i == 0) { count = 1; } else { count = encryptedChatsByUsersCount.get(did, 0); if (count == 0) { continue; } } int flag; if (user.bot) { flag = MessagesController.DIALOG_FILTER_FLAG_BOTS; } else if (user.self || user.contact) { flag = MessagesController.DIALOG_FILTER_FLAG_CONTACTS; } else { flag = MessagesController.DIALOG_FILTER_FLAG_NON_CONTACTS; } if ((flags & flag) == 0) { unreadCount += count; } else if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) != 0 && mutedDialogs.indexOfKey(user.id) >= 0) { unreadCount += count; } else if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_ARCHIVED) != 0 && archivedDialogs.indexOfKey(user.id) >= 0) { unreadCount += count; } } } } else { TLRPC.Chat chat = chatsDict.get(-did); if (chat != null) { int flag; if (ChatObject.isChannel(chat) && !chat.megagroup) { flag = MessagesController.DIALOG_FILTER_FLAG_CHANNELS; } else { flag = MessagesController.DIALOG_FILTER_FLAG_GROUPS; } if ((flags & flag) == 0) { unreadCount++; } else if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) != 0 && mutedDialogs.indexOfKey(-chat.id) >= 0 && dialogsWithMentions.indexOfKey(-chat.id) < 0) { unreadCount++; } else if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_ARCHIVED) != 0 && archivedDialogs.indexOfKey(-chat.id) >= 0) { unreadCount++; } } } } for (int b = 0, N2 = filter.neverShow.size(); b < N2; b++) { int did = filter.neverShow.get(b); if (did > 0) { for (int i = 0; i < 2; i++) { SparseArray dict = i == 0 ? usersDict : encUsersDict; TLRPC.User user = dict.get(did); if (user != null) { int count; if (i == 0) { count = 1; } else { count = encryptedChatsByUsersCount.get(did, 0); if (count == 0) { continue; } } int flag; if (user.bot) { flag = MessagesController.DIALOG_FILTER_FLAG_BOTS; } else if (user.self || user.contact) { flag = MessagesController.DIALOG_FILTER_FLAG_CONTACTS; } else { flag = MessagesController.DIALOG_FILTER_FLAG_NON_CONTACTS; } if ((flags & flag) != 0) { if (((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_ARCHIVED) == 0 || archivedDialogs.indexOfKey(user.id) < 0) && ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) == 0 || mutedDialogs.indexOfKey(user.id) < 0)) { unreadCount -= count; } } } } } else { TLRPC.Chat chat = chatsDict.get(-did); if (chat != null) { int flag; if (ChatObject.isChannel(chat) && !chat.megagroup) { flag = MessagesController.DIALOG_FILTER_FLAG_CHANNELS; } else { flag = MessagesController.DIALOG_FILTER_FLAG_GROUPS; } if ((flags & flag) != 0) { if (((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_ARCHIVED) == 0 || archivedDialogs.indexOfKey(-chat.id) < 0) && ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) == 0 || mutedDialogs.indexOfKey(-chat.id) < 0 || dialogsWithMentions.indexOfKey(-chat.id) >= 0)) { unreadCount--; } } } } } filter.pendingUnreadCount = unreadCount; /*if (BuildVars.DEBUG_VERSION) { FileLog.d("filter " + filter.name + " flags = " + filter.flags + " unread count = " + filter.pendingUnreadCount); }*/ if (apply) { filter.unreadCount = unreadCount; } } else if (a == N) { pendingMainUnreadCount = unreadCount; if (apply) { mainUnreadCount = unreadCount; } } else if (a == N + 1) { pendingArchiveUnreadCount = unreadCount; if (apply) { archiveUnreadCount = unreadCount; } } } } catch (Exception e) { FileLog.e(e); } } private void saveDialogFilterInternal(MessagesController.DialogFilter filter, boolean atBegin, boolean peers) { try { if (!dialogFilters.contains(filter)) { if (atBegin) { dialogFilters.add(0, filter); } else { dialogFilters.add(filter); } dialogFiltersMap.put(filter.id, filter); } SQLitePreparedStatement state = database.executeFast("REPLACE INTO dialog_filter VALUES(?, ?, ?, ?, ?)"); state.bindInteger(1, filter.id); state.bindInteger(2, filter.order); state.bindInteger(3, filter.unreadCount); state.bindInteger(4, filter.flags); state.bindString(5, filter.name); state.step(); state.dispose(); if (peers) { database.executeFast("DELETE FROM dialog_filter_ep WHERE id = " + filter.id).stepThis().dispose(); database.executeFast("DELETE FROM dialog_filter_pin_v2 WHERE id = " + filter.id).stepThis().dispose(); database.beginTransaction(); state = database.executeFast("REPLACE INTO dialog_filter_pin_v2 VALUES(?, ?, ?)"); for (int a = 0, N = filter.alwaysShow.size(); a < N; a++) { long key = filter.alwaysShow.get(a); state.requery(); state.bindInteger(1, filter.id); state.bindLong(2, key); state.bindInteger(3, filter.pinnedDialogs.get(key, Integer.MIN_VALUE)); state.step(); } for (int a = 0, N = filter.pinnedDialogs.size(); a < N; a++) { long key = filter.pinnedDialogs.keyAt(a); if ((int) key != 0) { continue; } state.requery(); state.bindInteger(1, filter.id); state.bindLong(2, key); state.bindInteger(3, filter.pinnedDialogs.valueAt(a)); state.step(); } state.dispose(); state = database.executeFast("REPLACE INTO dialog_filter_ep VALUES(?, ?)"); for (int a = 0, N = filter.neverShow.size(); a < N; a++) { state.requery(); state.bindInteger(1, filter.id); state.bindLong(2, filter.neverShow.get(a)); state.step(); } state.dispose(); database.commitTransaction(); } } catch (Exception e) { FileLog.e(e); } } public void checkLoadedRemoteFilters(TLRPC.Vector vector) { storageQueue.postRunnable(() -> { try { SparseArray filtersToDelete = new SparseArray<>(); for (int a = 0, N = dialogFilters.size(); a < N; a++) { MessagesController.DialogFilter filter = dialogFilters.get(a); filtersToDelete.put(filter.id, filter); } ArrayList filtersOrder = new ArrayList<>(); ArrayList usersToLoad = new ArrayList<>(); HashMap usersToLoadMap = new HashMap<>(); ArrayList chatsToLoad = new ArrayList<>(); HashMap chatsToLoadMap = new HashMap<>(); ArrayList dialogsToLoad = new ArrayList<>(); HashMap dialogsToLoadMap = new HashMap<>(); ArrayList filtersToSave = new ArrayList<>(); HashMap> filterUserRemovals = new HashMap<>(); HashMap> filterDialogRemovals = new HashMap<>(); HashSet filtersUnreadCounterReset = new HashSet<>(); for (int a = 0, N = vector.objects.size(); a < N; a++) { TLRPC.TL_dialogFilter newFilter = (TLRPC.TL_dialogFilter) vector.objects.get(a); filtersOrder.add(newFilter.id); int newFlags = 0; if (newFilter.contacts) { newFlags |= MessagesController.DIALOG_FILTER_FLAG_CONTACTS; } if (newFilter.non_contacts) { newFlags |= MessagesController.DIALOG_FILTER_FLAG_NON_CONTACTS; } if (newFilter.groups) { newFlags |= MessagesController.DIALOG_FILTER_FLAG_GROUPS; } if (newFilter.broadcasts) { newFlags |= MessagesController.DIALOG_FILTER_FLAG_CHANNELS; } if (newFilter.bots) { newFlags |= MessagesController.DIALOG_FILTER_FLAG_BOTS; } if (newFilter.exclude_muted) { newFlags |= MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED; } if (newFilter.exclude_read) { newFlags |= MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_READ; } if (newFilter.exclude_archived) { newFlags |= MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_ARCHIVED; } MessagesController.DialogFilter filter = dialogFiltersMap.get(newFilter.id); if (filter != null) { filtersToDelete.remove(newFilter.id); boolean changed = false; boolean unreadChanged = false; if (!TextUtils.equals(filter.name, newFilter.title)) { changed = true; filter.name = newFilter.title; } if (filter.flags != newFlags) { filter.flags = newFlags; changed = true; unreadChanged = true; } HashSet existingIds = new HashSet<>(filter.alwaysShow); existingIds.addAll(filter.neverShow); HashSet existingDialogsIds = new HashSet<>(); LinkedHashMap secretChatsMap = null; if (filter.pinnedDialogs.size() != 0) { ArrayList pinArray = new ArrayList<>(); boolean hasSecret = false; for (int c = 0, N2 = filter.pinnedDialogs.size(); c < N2; c++) { long did = filter.pinnedDialogs.keyAt(c); if ((int) did == 0) { hasSecret = true; } pinArray.add(did); } if (hasSecret) { secretChatsMap = new LinkedHashMap<>(); LongSparseArray pinnedDialogs = filter.pinnedDialogs; Collections.sort(pinArray, (o1, o2) -> { int idx1 = pinnedDialogs.get(o1); int idx2 = pinnedDialogs.get(o2); if (idx1 > idx2) { return 1; } else if (idx1 < idx2) { return -1; } return 0; }); for (int c = 0, N2 = pinArray.size(); c < N2; c++) { long did = pinArray.get(c); if ((int) did != 0) { continue; } secretChatsMap.put(c, did); } } } for (int c = 0, N2 = filter.pinnedDialogs.size(); c < N2; c++) { long did = filter.pinnedDialogs.keyAt(c); int lowerId = (int) did; if (lowerId == 0) { continue; } existingDialogsIds.add(lowerId); existingIds.remove(lowerId); } for (int c = 0; c < 2; c++) { ArrayList fromArray = c == 0 ? newFilter.include_peers : newFilter.exclude_peers; ArrayList toArray = c == 0 ? filter.alwaysShow : filter.neverShow; if (c == 0) { filter.pinnedDialogs.clear(); for (int b = 0, N2 = newFilter.pinned_peers.size(); b < N2; b++) { TLRPC.InputPeer peer = newFilter.pinned_peers.get(b); Integer id; if (peer.user_id != 0) { id = peer.user_id; } else { id = -(peer.chat_id != 0 ? peer.chat_id : peer.channel_id); } if (!filter.alwaysShow.contains(id)) { filter.alwaysShow.add(id); } int index = filter.pinnedDialogs.size(); if (secretChatsMap != null) { Long did; while ((did = secretChatsMap.remove(index)) != null) { filter.pinnedDialogs.put(did, index); index++; } } filter.pinnedDialogs.put(id, index); existingIds.remove(id); if (!existingDialogsIds.remove(id)) { changed = true; if (!dialogsToLoadMap.containsKey(id)) { dialogsToLoad.add(id); dialogsToLoadMap.put(id, peer); } } } if (secretChatsMap != null) { for (LinkedHashMap.Entry entry : secretChatsMap.entrySet()) { filter.pinnedDialogs.put(entry.getValue(), filter.pinnedDialogs.size()); } } } for (int b = 0, N2 = fromArray.size(); b < N2; b++) { TLRPC.InputPeer peer = fromArray.get(b); if (peer.user_id != 0) { Integer uid = peer.user_id; if (!existingIds.remove(uid)) { changed = true; if (!toArray.contains(uid)) { toArray.add(uid); } if (!usersToLoadMap.containsKey(uid)) { usersToLoad.add(uid); usersToLoadMap.put(uid, peer); unreadChanged = true; } } } else { Integer chatId = peer.chat_id != 0 ? peer.chat_id : peer.channel_id; Integer lowerId = -chatId; if (!existingIds.remove(lowerId)) { changed = true; if (!toArray.contains(lowerId)) { toArray.add(lowerId); } if (!chatsToLoadMap.containsKey(chatId)) { chatsToLoad.add(chatId); chatsToLoadMap.put(chatId, peer); unreadChanged = true; } } } } } if (!existingIds.isEmpty()) { filterUserRemovals.put(filter.id, existingIds); unreadChanged = true; changed = true; } if (!existingDialogsIds.isEmpty()) { filterDialogRemovals.put(filter.id, existingDialogsIds); changed = true; } if (changed) { filtersToSave.add(filter); } if (unreadChanged) { filtersUnreadCounterReset.add(filter.id); } } else { filter = new MessagesController.DialogFilter(); filter.id = newFilter.id; filter.flags = newFlags; filter.name = newFilter.title; filter.pendingUnreadCount = -1; for (int c = 0; c < 2; c++) { if (c == 0) { for (int b = 0, N2 = newFilter.pinned_peers.size(); b < N2; b++) { TLRPC.InputPeer peer = newFilter.pinned_peers.get(b); Integer id; if (peer.user_id != 0) { id = peer.user_id; } else { id = -(peer.chat_id != 0 ? peer.chat_id : peer.channel_id); } if (!filter.alwaysShow.contains(id)) { filter.alwaysShow.add(id); } filter.pinnedDialogs.put(id, filter.pinnedDialogs.size() + 1); if (!dialogsToLoadMap.containsKey(id)) { dialogsToLoad.add(id); dialogsToLoadMap.put(id, peer); } } } ArrayList fromArray = c == 0 ? newFilter.include_peers : newFilter.exclude_peers; ArrayList toArray = c == 0 ? filter.alwaysShow : filter.neverShow; for (int b = 0, N2 = fromArray.size(); b < N2; b++) { TLRPC.InputPeer peer = fromArray.get(b); if (peer.user_id != 0) { Integer uid = peer.user_id; if (!toArray.contains(uid)) { toArray.add(uid); } if (!usersToLoadMap.containsKey(uid)) { usersToLoad.add(uid); usersToLoadMap.put(uid, peer); } } else { Integer chatId = peer.chat_id != 0 ? peer.chat_id : peer.channel_id; Integer lowerId = -chatId; if (!toArray.contains(lowerId)) { toArray.add(lowerId); } if (!chatsToLoadMap.containsKey(chatId)) { chatsToLoad.add(chatId); chatsToLoadMap.put(chatId, peer); } } } } filtersToSave.add(filter); } } TLRPC.messages_Dialogs dialogs; if (!dialogsToLoad.isEmpty()) { dialogs = loadDialogsByIds(TextUtils.join(",", dialogsToLoad), usersToLoad, chatsToLoad, new ArrayList<>()); for (int a = 0, N = dialogs.dialogs.size(); a < N; a++) { TLRPC.Dialog dialog = dialogs.dialogs.get(a); dialogsToLoadMap.remove((int) dialog.id); } } else { dialogs = new TLRPC.TL_messages_dialogs(); } ArrayList users = new ArrayList<>(); if (!usersToLoad.isEmpty()) { getUsersInternal(TextUtils.join(",", usersToLoad), users); for (int a = 0, N = users.size(); a < N; a++) { TLRPC.User user = users.get(a); usersToLoadMap.remove(user.id); } } ArrayList chats = new ArrayList<>(); if (!chatsToLoad.isEmpty()) { getChatsInternal(TextUtils.join(",", chatsToLoad), chats); for (int a = 0, N = chats.size(); a < N; a++) { TLRPC.Chat chat = chats.get(a); chatsToLoadMap.remove(chat.id); } } if (usersToLoadMap.isEmpty() && chatsToLoadMap.isEmpty() && dialogsToLoadMap.isEmpty()) { processLoadedFilterPeersInternal(dialogs, null, users, chats, filtersToSave, filtersToDelete, filtersOrder, filterDialogRemovals, filterUserRemovals, filtersUnreadCounterReset); } else { getMessagesController().loadFilterPeers(dialogsToLoadMap, usersToLoadMap, chatsToLoadMap, dialogs, new TLRPC.TL_messages_dialogs(), users, chats, filtersToSave, filtersToDelete, filtersOrder, filterDialogRemovals, filterUserRemovals, filtersUnreadCounterReset); } } catch (Exception e) { FileLog.e(e); } }); } private void processLoadedFilterPeersInternal(TLRPC.messages_Dialogs pinnedDialogs, TLRPC.messages_Dialogs pinnedRemoteDialogs, ArrayList users, ArrayList chats, ArrayList filtersToSave, SparseArray filtersToDelete, ArrayList filtersOrder, HashMap> filterDialogRemovals, HashMap> filterUserRemovals, HashSet filtersUnreadCounterReset) { boolean anythingChanged = false; putUsersAndChats(users, chats, true, false); for (int a = 0, N = filtersToDelete.size(); a < N; a++) { deleteDialogFilterInternal(filtersToDelete.valueAt(a)); anythingChanged = true; } for (Integer id : filtersUnreadCounterReset) { MessagesController.DialogFilter filter = dialogFiltersMap.get(id); if (filter == null) { continue; } filter.pendingUnreadCount = -1; } for (HashMap.Entry> entry : filterUserRemovals.entrySet()) { MessagesController.DialogFilter filter = dialogFiltersMap.get(entry.getKey()); if (filter == null) { continue; } HashSet set = entry.getValue(); filter.alwaysShow.removeAll(set); filter.neverShow.removeAll(set); anythingChanged = true; } for (HashMap.Entry> entry : filterDialogRemovals.entrySet()) { MessagesController.DialogFilter filter = dialogFiltersMap.get(entry.getKey()); if (filter == null) { continue; } HashSet set = entry.getValue(); for (Integer id : set) { filter.pinnedDialogs.remove((long) id); } anythingChanged = true; } for (int a = 0, N = filtersToSave.size(); a < N; a++) { saveDialogFilterInternal(filtersToSave.get(a), false, true); anythingChanged = true; } boolean orderChanged = false; for (int a = 0, N = dialogFilters.size(); a < N; a++) { MessagesController.DialogFilter filter = dialogFilters.get(a); int order = filtersOrder.indexOf(filter.id); if (filter.order != order) { filter.order = order; anythingChanged = true; orderChanged = true; } } if (orderChanged) { Collections.sort(dialogFilters, (o1, o2) -> { if (o1.order > o2.order) { return 1; } else if (o1.order < o2.order) { return -1; } return 0; }); saveDialogFiltersOrderInternal(); } int remote = anythingChanged ? 1 : 2; calcUnreadCounters(true); getMessagesController().processLoadedDialogFilters(new ArrayList<>(dialogFilters), pinnedDialogs, pinnedRemoteDialogs, users, chats, null, remote); } protected void processLoadedFilterPeers(TLRPC.messages_Dialogs pinnedDialogs, TLRPC.messages_Dialogs pinnedRemoteDialogs, ArrayList users, ArrayList chats, ArrayList filtersToSave, SparseArray filtersToDelete, ArrayList filtersOrder, HashMap> filterDialogRemovals, HashMap> filterUserRemovals, HashSet filtersUnreadCounterReset) { storageQueue.postRunnable(() -> processLoadedFilterPeersInternal(pinnedDialogs, pinnedRemoteDialogs, users, chats, filtersToSave, filtersToDelete, filtersOrder, filterDialogRemovals, filterUserRemovals, filtersUnreadCounterReset)); } private void deleteDialogFilterInternal(MessagesController.DialogFilter filter) { try { dialogFilters.remove(filter); dialogFiltersMap.remove(filter.id); database.executeFast("DELETE FROM dialog_filter WHERE id = " + filter.id).stepThis().dispose(); database.executeFast("DELETE FROM dialog_filter_ep WHERE id = " + filter.id).stepThis().dispose(); database.executeFast("DELETE FROM dialog_filter_pin_v2 WHERE id = " + filter.id).stepThis().dispose(); } catch (Exception e) { FileLog.e(e); } } public void deleteDialogFilter(MessagesController.DialogFilter filter) { storageQueue.postRunnable(() -> deleteDialogFilterInternal(filter)); } public void saveDialogFilter(MessagesController.DialogFilter filter, boolean atBegin, boolean peers) { storageQueue.postRunnable(() -> { saveDialogFilterInternal(filter, atBegin, peers); calcUnreadCounters(false); AndroidUtilities.runOnUIThread(() -> { ArrayList filters = getMessagesController().dialogFilters; for (int a = 0, N = filters.size(); a < N; a++) { filters.get(a).unreadCount = filters.get(a).pendingUnreadCount; } mainUnreadCount = pendingMainUnreadCount; archiveUnreadCount = pendingArchiveUnreadCount; getNotificationCenter().postNotificationName(NotificationCenter.updateInterfaces, MessagesController.UPDATE_MASK_READ_DIALOG_MESSAGE); }); }); } public void saveDialogFiltersOrderInternal() { try { SQLitePreparedStatement state = database.executeFast("UPDATE dialog_filter SET ord = ?, flags = ? WHERE id = ?"); for (int a = 0, N = dialogFilters.size(); a < N ;a++) { MessagesController.DialogFilter filter = dialogFilters.get(a); state.requery(); state.bindInteger(1, filter.order); state.bindInteger(2, filter.flags); state.bindInteger(3, filter.id); state.step(); } state.dispose(); } catch (Exception e) { FileLog.e(e); } } public void saveDialogFiltersOrder() { storageQueue.postRunnable(this::saveDialogFiltersOrderInternal); } public void loadUnreadMessages() { storageQueue.postRunnable(() -> { try { ArrayList usersToLoad = new ArrayList<>(); ArrayList chatsToLoad = new ArrayList<>(); ArrayList encryptedChatIds = new ArrayList<>(); final LongSparseArray pushDialogs = new LongSparseArray<>(); SQLiteCursor cursor = database.queryFinalized("SELECT d.did, d.unread_count, s.flags FROM dialogs as d LEFT JOIN dialog_settings as s ON d.did = s.did WHERE d.unread_count > 0"); StringBuilder ids = new StringBuilder(); int currentTime = getConnectionsManager().getCurrentTime(); while (cursor.next()) { long flags = cursor.longValue(2); boolean muted = (flags & 1) != 0; int mutedUntil = (int) (flags >> 32); if (cursor.isNull(2) || !muted || mutedUntil != 0 && mutedUntil < currentTime) { long did = cursor.longValue(0); if (DialogObject.isFolderDialogId(did)) { continue; } int count = cursor.intValue(1); pushDialogs.put(did, count); if (ids.length() != 0) { ids.append(","); } ids.append(did); int lower_id = (int) did; int high_id = (int) (did >> 32); if (lower_id != 0) { if (lower_id < 0) { if (!chatsToLoad.contains(-lower_id)) { chatsToLoad.add(-lower_id); } } else { if (!usersToLoad.contains(lower_id)) { usersToLoad.add(lower_id); } } } else { if (!encryptedChatIds.contains(high_id)) { encryptedChatIds.add(high_id); } } } } cursor.dispose(); ArrayList replyMessages = new ArrayList<>(); SparseArray> replyMessageOwners = new SparseArray<>(); final ArrayList messages = new ArrayList<>(); final ArrayList pushMessages = new ArrayList<>(); final ArrayList users = new ArrayList<>(); final ArrayList chats = new ArrayList<>(); final ArrayList encryptedChats = new ArrayList<>(); int maxDate = 0; if (ids.length() > 0) { cursor = database.queryFinalized("SELECT read_state, data, send_state, mid, date, uid, replydata FROM messages WHERE uid IN (" + ids.toString() + ") AND out = 0 AND read_state IN(0,2) ORDER BY date DESC LIMIT 50"); while (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(1); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); MessageObject.setUnreadFlags(message, cursor.intValue(0)); message.id = cursor.intValue(3); message.date = cursor.intValue(4); message.dialog_id = cursor.longValue(5); messages.add(message); maxDate = Math.max(maxDate, message.date); int lower_id = (int) message.dialog_id; addUsersAndChatsFromMessage(message, usersToLoad, chatsToLoad); message.send_state = cursor.intValue(2); if (message.peer_id.channel_id == 0 && !MessageObject.isUnread(message) && lower_id != 0 || message.id > 0) { message.send_state = 0; } if (lower_id == 0 && !cursor.isNull(5)) { message.random_id = cursor.longValue(5); } try { if (message.reply_to != null && message.reply_to.reply_to_msg_id != 0 && ( message.action instanceof TLRPC.TL_messageActionPinMessage || message.action instanceof TLRPC.TL_messageActionPaymentSent || message.action instanceof TLRPC.TL_messageActionGameScore)) { if (!cursor.isNull(6)) { data = cursor.byteBufferValue(6); if (data != null) { message.replyMessage = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); message.replyMessage.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); if (message.replyMessage != null) { addUsersAndChatsFromMessage(message.replyMessage, usersToLoad, chatsToLoad); } } } if (message.replyMessage == null) { long messageId = message.reply_to.reply_to_msg_id; if (message.reply_to.reply_to_peer_id != null) { if (message.reply_to.reply_to_peer_id.channel_id != 0) { messageId |= ((long) message.reply_to.reply_to_peer_id.channel_id) << 32; } } else if (message.peer_id.channel_id != 0) { messageId |= ((long) message.peer_id.channel_id) << 32; } if (!replyMessages.contains(messageId)) { replyMessages.add(messageId); } ArrayList arrayList = replyMessageOwners.get(message.reply_to.reply_to_msg_id); if (arrayList == null) { arrayList = new ArrayList<>(); replyMessageOwners.put(message.reply_to.reply_to_msg_id, arrayList); } arrayList.add(message); } } } catch (Exception e) { FileLog.e(e); } } } cursor.dispose(); database.executeFast("DELETE FROM unread_push_messages WHERE date <= " + maxDate).stepThis().dispose(); cursor = database.queryFinalized("SELECT data, mid, date, uid, random, fm, name, uname, flags FROM unread_push_messages WHERE 1 ORDER BY date DESC LIMIT 50"); while (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); data.reuse(); message.id = cursor.intValue(1); message.date = cursor.intValue(2); message.dialog_id = cursor.longValue(3); message.random_id = cursor.longValue(4); String messageText = cursor.isNull(5) ? null : cursor.stringValue(5); String name = cursor.isNull(6) ? null : cursor.stringValue(6); String userName = cursor.isNull(7) ? null : cursor.stringValue(7); int flags = cursor.intValue(8); if (MessageObject.getFromChatId(message) == 0) { int lowerId = (int) message.dialog_id; if (lowerId > 0) { message.from_id = new TLRPC.TL_peerUser(); message.from_id.user_id = lowerId; } } int lower_id = (int) message.dialog_id; if (lower_id > 0) { if (!usersToLoad.contains(lower_id)) { usersToLoad.add(lower_id); } } else if (lower_id < 0) { if (!chatsToLoad.contains(-lower_id)) { chatsToLoad.add(-lower_id); } } pushMessages.add(new MessageObject(currentAccount, message, messageText, name, userName, (flags & 1) != 0, (flags & 2) != 0, (message.flags & 0x80000000) != 0, false)); addUsersAndChatsFromMessage(message, usersToLoad, chatsToLoad); } } cursor.dispose(); if (!replyMessages.isEmpty()) { cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid, date, uid FROM messages WHERE mid IN(%s)", TextUtils.join(",", replyMessages))); while (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); message.id = cursor.intValue(1); message.date = cursor.intValue(2); message.dialog_id = cursor.longValue(3); addUsersAndChatsFromMessage(message, usersToLoad, chatsToLoad); ArrayList arrayList = replyMessageOwners.get(message.id); if (arrayList != null) { for (int a = 0; a < arrayList.size(); a++) { TLRPC.Message m = arrayList.get(a); m.replyMessage = message; } } } } cursor.dispose(); } if (!encryptedChatIds.isEmpty()) { getEncryptedChatsInternal(TextUtils.join(",", encryptedChatIds), encryptedChats, usersToLoad); } if (!usersToLoad.isEmpty()) { getUsersInternal(TextUtils.join(",", usersToLoad), users); } if (!chatsToLoad.isEmpty()) { getChatsInternal(TextUtils.join(",", chatsToLoad), chats); for (int a = 0; a < chats.size(); a++) { TLRPC.Chat chat = chats.get(a); if (chat != null && (ChatObject.isNotInChat(chat) || chat.min || chat.migrated_to != null)) { long did = -chat.id; database.executeFast("UPDATE dialogs SET unread_count = 0 WHERE did = " + did).stepThis().dispose(); database.executeFast(String.format(Locale.US, "UPDATE messages SET read_state = 3 WHERE uid = %d AND mid > 0 AND read_state IN(0,2) AND out = 0", did)).stepThis().dispose(); chats.remove(a); a--; pushDialogs.remove(did); for (int b = 0; b < messages.size(); b++) { TLRPC.Message message = messages.get(b); if (message.dialog_id == did) { messages.remove(b); b--; } } } } } } Collections.reverse(messages); AndroidUtilities.runOnUIThread(() -> getNotificationsController().processLoadedUnreadMessages(pushDialogs, messages, pushMessages, users, chats, encryptedChats)); } catch (Exception e) { FileLog.e(e); } }); } public void putWallpapers(final ArrayList wallPapers, int action) { storageQueue.postRunnable(() -> { try { if (action == 1) { database.executeFast("DELETE FROM wallpapers2 WHERE 1").stepThis().dispose(); } database.beginTransaction(); SQLitePreparedStatement state; if (action != 0) { state = database.executeFast("REPLACE INTO wallpapers2 VALUES(?, ?, ?)"); } else { state = database.executeFast("UPDATE wallpapers2 SET data = ? WHERE uid = ?"); } for (int a = 0, N = wallPapers.size(); a < N; a++) { TLRPC.TL_wallPaper wallPaper = (TLRPC.TL_wallPaper) wallPapers.get(a); state.requery(); NativeByteBuffer data = new NativeByteBuffer(wallPaper.getObjectSize()); wallPaper.serializeToStream(data); if (action != 0) { state.bindLong(1, wallPaper.id); state.bindByteBuffer(2, data); state.bindInteger(3, action == 2 ? -1 : a); } else { state.bindByteBuffer(1, data); state.bindLong(2, wallPaper.id); } state.step(); data.reuse(); } state.dispose(); database.commitTransaction(); } catch (Exception e) { FileLog.e(e); } }); } public void getWallpapers() { storageQueue.postRunnable(() -> { SQLiteCursor cursor = null; try { cursor = database.queryFinalized("SELECT data FROM wallpapers2 WHERE 1 ORDER BY num ASC"); final ArrayList wallPapers = new ArrayList<>(); while (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.TL_wallPaper wallPaper = (TLRPC.TL_wallPaper) TLRPC.WallPaper.TLdeserialize(data, data.readInt32(false), false); data.reuse(); if (wallPaper != null) { wallPapers.add(wallPaper); } } } AndroidUtilities.runOnUIThread(() -> NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.wallpapersDidLoad, wallPapers)); } catch (Exception e) { FileLog.e(e); } finally { if (cursor != null) { cursor.dispose(); } } }); } public void addRecentLocalFile(final String imageUrl, final String localUrl, final TLRPC.Document document) { if (imageUrl == null || imageUrl.length() == 0 || ((localUrl == null || localUrl.length() == 0) && document == null)) { return; } storageQueue.postRunnable(() -> { try { if (document != null) { SQLitePreparedStatement state = database.executeFast("UPDATE web_recent_v3 SET document = ? WHERE image_url = ?"); state.requery(); NativeByteBuffer data = new NativeByteBuffer(document.getObjectSize()); document.serializeToStream(data); state.bindByteBuffer(1, data); state.bindString(2, imageUrl); state.step(); state.dispose(); data.reuse(); } else { SQLitePreparedStatement state = database.executeFast("UPDATE web_recent_v3 SET local_url = ? WHERE image_url = ?"); state.requery(); state.bindString(1, localUrl); state.bindString(2, imageUrl); state.step(); state.dispose(); } } catch (Exception e) { FileLog.e(e); } }); } public void deleteUserChatHistory(long did, int channelId, int fromId) { storageQueue.postRunnable(() -> { try { final ArrayList mids = new ArrayList<>(); SQLiteCursor cursor = database.queryFinalized("SELECT data FROM messages WHERE uid = " + did); ArrayList filesToDelete = new ArrayList<>(); ArrayList namesToDelete = new ArrayList<>(); ArrayList> idsToDelete = new ArrayList<>(); try { while (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); if (message != null) { message.readAttachPath(data, getUserConfig().clientUserId); if (UserObject.isReplyUser(did) && MessageObject.getPeerId(message.fwd_from.from_id) == fromId || MessageObject.getFromChatId(message) == fromId && message.id != 1) { mids.add(message.id); addFilesToDelete(message, filesToDelete, idsToDelete, namesToDelete, false); } } data.reuse(); } } } catch (Exception e) { FileLog.e(e); } cursor.dispose(); deleteFromDownloadQueue(idsToDelete, true); AndroidUtilities.runOnUIThread(() -> { getFileLoader().cancelLoadFiles(namesToDelete); getMessagesController().markDialogMessageAsDeleted(mids, did); }); markMessagesAsDeletedInternal(mids, channelId, false, false); updateDialogsWithDeletedMessagesInternal(mids, null, channelId); getFileLoader().deleteFiles(filesToDelete, 0); if (!mids.isEmpty()) { AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.messagesDeleted, mids, channelId, false)); } } catch (Exception e) { FileLog.e(e); } }); } private boolean addFilesToDelete(TLRPC.Message message, ArrayList filesToDelete, ArrayList> ids, ArrayList namesToDelete, boolean forceCache) { if (message == null) { return false; } int type = 0; long id = 0; TLRPC.Document document = MessageObject.getDocument(message); TLRPC.Photo photo = MessageObject.getPhoto(message); if (MessageObject.isVoiceMessage(message)) { id = document.id; type = DownloadController.AUTODOWNLOAD_TYPE_AUDIO; } else if (MessageObject.isStickerMessage(message) || MessageObject.isAnimatedStickerMessage(message)) { id = document.id; type = DownloadController.AUTODOWNLOAD_TYPE_PHOTO; } else if (MessageObject.isVideoMessage(message) || MessageObject.isRoundVideoMessage(message) || MessageObject.isGifMessage(message)) { id = document.id; type = DownloadController.AUTODOWNLOAD_TYPE_VIDEO; } else if (document != null) { id = document.id; type = DownloadController.AUTODOWNLOAD_TYPE_DOCUMENT; } else if (photo != null) { TLRPC.PhotoSize photoSize = FileLoader.getClosestPhotoSizeWithSize(photo.sizes, AndroidUtilities.getPhotoSize()); if (photoSize != null) { id = photo.id; type = DownloadController.AUTODOWNLOAD_TYPE_PHOTO; } } if (id != 0) { ids.add(new Pair<>(id, type)); } if (photo != null) { for (int a = 0, N = photo.sizes.size(); a < N; a++) { TLRPC.PhotoSize photoSize = photo.sizes.get(a); String name = FileLoader.getAttachFileName(photoSize); if (!TextUtils.isEmpty(name)) { namesToDelete.add(name); } File file = FileLoader.getPathToAttach(photoSize, forceCache); if (file.toString().length() > 0) { filesToDelete.add(file); } } return true; } else if (document != null) { String name = FileLoader.getAttachFileName(document); if (!TextUtils.isEmpty(name)) { namesToDelete.add(name); } File file = FileLoader.getPathToAttach(document, forceCache); if (file.toString().length() > 0) { filesToDelete.add(file); } for (int a = 0, N = document.thumbs.size(); a < N; a++) { TLRPC.PhotoSize photoSize = document.thumbs.get(a); file = FileLoader.getPathToAttach(photoSize); if (file.toString().length() > 0) { filesToDelete.add(file); } } return true; } return false; } public void deleteDialog(final long did, final int messagesOnly) { storageQueue.postRunnable(() -> { try { if (messagesOnly == 3) { int lastMid = -1; SQLiteCursor cursor = database.queryFinalized("SELECT last_mid FROM dialogs WHERE did = " + did); if (cursor.next()) { lastMid = cursor.intValue(0); } cursor.dispose(); if (lastMid != 0) { return; } } if ((int) did == 0 || messagesOnly == 2) { SQLiteCursor cursor = database.queryFinalized("SELECT data FROM messages WHERE uid = " + did); ArrayList filesToDelete = new ArrayList<>(); ArrayList namesToDelete = new ArrayList<>(); ArrayList> idsToDelete = new ArrayList<>(); try { while (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); addFilesToDelete(message, filesToDelete, idsToDelete, namesToDelete, false); } } } catch (Exception e) { FileLog.e(e); } cursor.dispose(); deleteFromDownloadQueue(idsToDelete, true); AndroidUtilities.runOnUIThread(() -> getFileLoader().cancelLoadFiles(namesToDelete)); getFileLoader().deleteFiles(filesToDelete, messagesOnly); } if (messagesOnly == 0 || messagesOnly == 3) { database.executeFast("DELETE FROM dialogs WHERE did = " + did).stepThis().dispose(); database.executeFast("DELETE FROM chat_pinned_v2 WHERE uid = " + did).stepThis().dispose(); database.executeFast("DELETE FROM chat_pinned_count WHERE uid = " + did).stepThis().dispose(); database.executeFast("DELETE FROM channel_users_v2 WHERE did = " + did).stepThis().dispose(); database.executeFast("DELETE FROM search_recent WHERE did = " + did).stepThis().dispose(); int lower_id = (int) did; int high_id = (int) (did >> 32); if (lower_id != 0) { if (lower_id < 0) { database.executeFast("DELETE FROM chat_settings_v2 WHERE uid = " + (-lower_id)).stepThis().dispose(); } } else { database.executeFast("DELETE FROM enc_chats WHERE uid = " + high_id).stepThis().dispose(); } } else if (messagesOnly == 2) { SQLiteCursor cursor = database.queryFinalized("SELECT last_mid_i, last_mid FROM dialogs WHERE did = " + did); int messageId = -1; if (cursor.next()) { long last_mid_i = cursor.longValue(0); long last_mid = cursor.longValue(1); SQLiteCursor cursor2 = database.queryFinalized("SELECT data FROM messages WHERE uid = " + did + " AND mid IN (" + last_mid_i + "," + last_mid + ")"); try { while (cursor2.next()) { NativeByteBuffer data = cursor2.byteBufferValue(0); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); if (message != null) { message.readAttachPath(data, getUserConfig().clientUserId); } data.reuse(); if (message != null) { messageId = message.id; } } } } catch (Exception e) { FileLog.e(e); } cursor2.dispose(); database.executeFast("DELETE FROM messages WHERE uid = " + did + " AND mid != " + last_mid_i + " AND mid != " + last_mid).stepThis().dispose(); database.executeFast("DELETE FROM messages_holes WHERE uid = " + did).stepThis().dispose(); database.executeFast("DELETE FROM bot_keyboard WHERE uid = " + did).stepThis().dispose(); database.executeFast("DELETE FROM media_counts_v2 WHERE uid = " + did).stepThis().dispose(); database.executeFast("DELETE FROM media_v2 WHERE uid = " + did).stepThis().dispose(); database.executeFast("DELETE FROM media_holes_v2 WHERE uid = " + did).stepThis().dispose(); getMediaDataController().clearBotKeyboard(did, null); SQLitePreparedStatement state5 = database.executeFast("REPLACE INTO messages_holes VALUES(?, ?, ?)"); SQLitePreparedStatement state6 = database.executeFast("REPLACE INTO media_holes_v2 VALUES(?, ?, ?, ?)"); if (messageId != -1) { createFirstHoles(did, state5, state6, messageId); } state5.dispose(); state6.dispose(); updateWidgets(did); } cursor.dispose(); return; } database.executeFast("UPDATE dialogs SET unread_count = 0, unread_count_i = 0 WHERE did = " + did).stepThis().dispose(); database.executeFast("DELETE FROM messages WHERE uid = " + did).stepThis().dispose(); database.executeFast("DELETE FROM bot_keyboard WHERE uid = " + did).stepThis().dispose(); database.executeFast("DELETE FROM media_counts_v2 WHERE uid = " + did).stepThis().dispose(); database.executeFast("DELETE FROM media_v2 WHERE uid = " + did).stepThis().dispose(); database.executeFast("DELETE FROM messages_holes WHERE uid = " + did).stepThis().dispose(); database.executeFast("DELETE FROM media_holes_v2 WHERE uid = " + did).stepThis().dispose(); getMediaDataController().clearBotKeyboard(did, null); AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.needReloadRecentDialogsSearch)); resetAllUnreadCounters(false); updateWidgets(did); } catch (Exception e) { FileLog.e(e); } }); } public void onDeleteQueryComplete(final long did) { storageQueue.postRunnable(() -> { try { database.executeFast("DELETE FROM media_counts_v2 WHERE uid = " + did).stepThis().dispose(); } catch (Exception e) { FileLog.e(e); } }); } public void getDialogPhotos(final int did, final int count, final long max_id, final int classGuid) { storageQueue.postRunnable(() -> { try { SQLiteCursor cursor; if (max_id != 0) { cursor = database.queryFinalized(String.format(Locale.US, "SELECT data FROM user_photos WHERE uid = %d AND id < %d ORDER BY rowid ASC LIMIT %d", did, max_id, count)); } else { cursor = database.queryFinalized(String.format(Locale.US, "SELECT data FROM user_photos WHERE uid = %d ORDER BY rowid ASC LIMIT %d", did, count)); } final TLRPC.photos_Photos res = new TLRPC.TL_photos_photos(); ArrayList messages = new ArrayList<>(); while (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.Photo photo = TLRPC.Photo.TLdeserialize(data, data.readInt32(false), false); if (data.remaining() > 0) { messages.add(TLRPC.Message.TLdeserialize(data, data.readInt32(false), false)); } else { messages.add(null); } data.reuse(); res.photos.add(photo); } } cursor.dispose(); Utilities.stageQueue.postRunnable(() -> getMessagesController().processLoadedUserPhotos(res, messages, did, count, max_id, true, classGuid)); } catch (Exception e) { FileLog.e(e); } }); } public void clearUserPhotos(final int uid) { storageQueue.postRunnable(() -> { try { database.executeFast("DELETE FROM user_photos WHERE uid = " + uid).stepThis().dispose(); } catch (Exception e) { FileLog.e(e); } }); } public void clearUserPhoto(final int uid, final long pid) { storageQueue.postRunnable(() -> { try { database.executeFast("DELETE FROM user_photos WHERE uid = " + uid + " AND id = " + pid).stepThis().dispose(); } catch (Exception e) { FileLog.e(e); } }); } public void resetDialogs(final TLRPC.messages_Dialogs dialogsRes, final int messagesCount, final int seq, final int newPts, final int date, final int qts, final LongSparseArray new_dialogs_dict, final LongSparseArray new_dialogMessage, final TLRPC.Message lastMessage, final int dialogsCount) { storageQueue.postRunnable(() -> { try { int maxPinnedNum = 0; ArrayList dids = new ArrayList<>(); int totalPinnedCount = dialogsRes.dialogs.size() - dialogsCount; final LongSparseArray oldPinnedDialogNums = new LongSparseArray<>(); ArrayList oldPinnedOrder = new ArrayList<>(); ArrayList orderArrayList = new ArrayList<>(); for (int a = dialogsCount; a < dialogsRes.dialogs.size(); a++) { TLRPC.Dialog dialog = dialogsRes.dialogs.get(a); orderArrayList.add(dialog.id); } SQLiteCursor cursor = database.queryFinalized("SELECT did, pinned FROM dialogs WHERE 1"); while (cursor.next()) { long did = cursor.longValue(0); int pinnedNum = cursor.intValue(1); int lower_id = (int) did; if (lower_id != 0) { dids.add(lower_id); if (pinnedNum > 0) { maxPinnedNum = Math.max(pinnedNum, maxPinnedNum); oldPinnedDialogNums.put(did, pinnedNum); oldPinnedOrder.add(did); } } } Collections.sort(oldPinnedOrder, (o1, o2) -> { Integer val1 = oldPinnedDialogNums.get(o1); Integer val2 = oldPinnedDialogNums.get(o2); if (val1 < val2) { return 1; } else if (val1 > val2) { return -1; } return 0; }); while (oldPinnedOrder.size() < totalPinnedCount) { oldPinnedOrder.add(0, 0L); } cursor.dispose(); String ids = "(" + TextUtils.join(",", dids) + ")"; database.beginTransaction(); database.executeFast("DELETE FROM chat_pinned_count WHERE uid IN " + ids).stepThis().dispose(); database.executeFast("DELETE FROM chat_pinned_v2 WHERE uid IN " + ids).stepThis().dispose(); database.executeFast("DELETE FROM dialogs WHERE did IN " + ids).stepThis().dispose(); database.executeFast("DELETE FROM messages WHERE uid IN " + ids).stepThis().dispose(); database.executeFast("DELETE FROM polls WHERE 1").stepThis().dispose(); database.executeFast("DELETE FROM bot_keyboard WHERE uid IN " + ids).stepThis().dispose(); database.executeFast("DELETE FROM media_v2 WHERE uid IN " + ids).stepThis().dispose(); database.executeFast("DELETE FROM messages_holes WHERE uid IN " + ids).stepThis().dispose(); database.executeFast("DELETE FROM media_holes_v2 WHERE uid IN " + ids).stepThis().dispose(); database.commitTransaction(); for (int a = 0; a < totalPinnedCount; a++) { TLRPC.Dialog dialog = dialogsRes.dialogs.get(dialogsCount + a); if (dialog instanceof TLRPC.TL_dialog && !dialog.pinned) { continue; } int oldIdx = oldPinnedOrder.indexOf(dialog.id); int newIdx = orderArrayList.indexOf(dialog.id); if (oldIdx != -1 && newIdx != -1) { if (oldIdx == newIdx) { Integer oldNum = oldPinnedDialogNums.get(dialog.id); if (oldNum != null) { dialog.pinnedNum = oldNum; } } else { long oldDid = oldPinnedOrder.get(newIdx); Integer oldNum = oldPinnedDialogNums.get(oldDid); if (oldNum != null) { dialog.pinnedNum = oldNum; } } } if (dialog.pinnedNum == 0) { dialog.pinnedNum = (totalPinnedCount - a) + maxPinnedNum; } } putDialogsInternal(dialogsRes, 0); saveDiffParamsInternal(seq, newPts, date, qts); int totalDialogsLoadCount = getUserConfig().getTotalDialogsCount(0); int[] dialogsLoadOffset = getUserConfig().getDialogLoadOffsets(0); int dialogsLoadOffsetId; int dialogsLoadOffsetDate; int dialogsLoadOffsetChannelId = 0; int dialogsLoadOffsetChatId = 0; int dialogsLoadOffsetUserId = 0; long dialogsLoadOffsetAccess = 0; totalDialogsLoadCount += dialogsRes.dialogs.size(); dialogsLoadOffsetId = lastMessage.id; dialogsLoadOffsetDate = lastMessage.date; if (lastMessage.peer_id.channel_id != 0) { dialogsLoadOffsetChannelId = lastMessage.peer_id.channel_id; dialogsLoadOffsetChatId = 0; dialogsLoadOffsetUserId = 0; for (int a = 0; a < dialogsRes.chats.size(); a++) { TLRPC.Chat chat = dialogsRes.chats.get(a); if (chat.id == dialogsLoadOffsetChannelId) { dialogsLoadOffsetAccess = chat.access_hash; break; } } } else if (lastMessage.peer_id.chat_id != 0) { dialogsLoadOffsetChatId = lastMessage.peer_id.chat_id; dialogsLoadOffsetChannelId = 0; dialogsLoadOffsetUserId = 0; for (int a = 0; a < dialogsRes.chats.size(); a++) { TLRPC.Chat chat = dialogsRes.chats.get(a); if (chat.id == dialogsLoadOffsetChatId) { dialogsLoadOffsetAccess = chat.access_hash; break; } } } else if (lastMessage.peer_id.user_id != 0) { dialogsLoadOffsetUserId = lastMessage.peer_id.user_id; dialogsLoadOffsetChatId = 0; dialogsLoadOffsetChannelId = 0; for (int a = 0; a < dialogsRes.users.size(); a++) { TLRPC.User user = dialogsRes.users.get(a); if (user.id == dialogsLoadOffsetUserId) { dialogsLoadOffsetAccess = user.access_hash; break; } } } for (int a = 0; a < 2; a++) { getUserConfig().setDialogsLoadOffset(a, dialogsLoadOffsetId, dialogsLoadOffsetDate, dialogsLoadOffsetUserId, dialogsLoadOffsetChatId, dialogsLoadOffsetChannelId, dialogsLoadOffsetAccess); getUserConfig().setTotalDialogsCount(a, totalDialogsLoadCount); } getUserConfig().draftsLoaded = false; getUserConfig().saveConfig(false); getMessagesController().completeDialogsReset(dialogsRes, messagesCount, seq, newPts, date, qts, new_dialogs_dict, new_dialogMessage, lastMessage); } catch (Exception e) { FileLog.e(e); } }); } public void putDialogPhotos(int did, TLRPC.photos_Photos photos, ArrayList messages) { if (photos == null) { return; } storageQueue.postRunnable(() -> { try { database.executeFast("DELETE FROM user_photos WHERE uid = " + did).stepThis().dispose(); SQLitePreparedStatement state = database.executeFast("REPLACE INTO user_photos VALUES(?, ?, ?)"); for (int a = 0, N = photos.photos.size(); a < N; a++) { TLRPC.Photo photo = photos.photos.get(a); if (photo instanceof TLRPC.TL_photoEmpty) { continue; } state.requery(); int size = photo.getObjectSize(); if (messages != null) { size += messages.get(a).getObjectSize(); } NativeByteBuffer data = new NativeByteBuffer(size); photo.serializeToStream(data); if (messages != null) { messages.get(a).serializeToStream(data); } state.bindInteger(1, did); state.bindLong(2, photo.id); state.bindByteBuffer(3, data); state.step(); data.reuse(); } state.dispose(); } catch (Exception e) { FileLog.e(e); } }); } public void emptyMessagesMedia(final ArrayList mids) { storageQueue.postRunnable(() -> { try { ArrayList filesToDelete = new ArrayList<>(); ArrayList namesToDelete = new ArrayList<>(); ArrayList> idsToDelete = new ArrayList<>(); final ArrayList messages = new ArrayList<>(); SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid, date, uid FROM messages WHERE mid IN (%s)", TextUtils.join(",", mids))); while (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); if (message.media != null) { if (!addFilesToDelete(message, filesToDelete, idsToDelete, namesToDelete, true)) { continue; } else { if (message.media.document != null) { message.media.document = new TLRPC.TL_documentEmpty(); } else if (message.media.photo != null) { message.media.photo = new TLRPC.TL_photoEmpty(); } } message.media.flags = message.media.flags &~ 1; message.id = cursor.intValue(1); message.date = cursor.intValue(2); message.dialog_id = cursor.longValue(3); messages.add(message); } } } cursor.dispose(); deleteFromDownloadQueue(idsToDelete, true); if (!messages.isEmpty()) { SQLitePreparedStatement state = database.executeFast("REPLACE INTO messages VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?)"); for (int a = 0; a < messages.size(); a++) { TLRPC.Message message = messages.get(a); NativeByteBuffer data = new NativeByteBuffer(message.getObjectSize()); message.serializeToStream(data); state.requery(); state.bindLong(1, message.id); state.bindLong(2, message.dialog_id); state.bindInteger(3, MessageObject.getUnreadFlags(message)); state.bindInteger(4, message.send_state); state.bindInteger(5, message.date); state.bindByteBuffer(6, data); state.bindInteger(7, (MessageObject.isOut(message) || message.from_scheduled ? 1 : 0)); state.bindInteger(8, message.ttl); if ((message.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0) { state.bindInteger(9, message.views); } else { state.bindInteger(9, getMessageMediaType(message)); } int flags = 0; if (message.stickerVerified == 0) { flags |= 1; } else if (message.stickerVerified == 2) { flags |= 2; } state.bindInteger(10, flags); state.bindInteger(11, message.mentioned ? 1 : 0); state.bindInteger(12, message.forwards); NativeByteBuffer repliesData = null; if (message.replies != null) { repliesData = new NativeByteBuffer(message.replies.getObjectSize()); message.replies.serializeToStream(repliesData); state.bindByteBuffer(13, repliesData); } else { state.bindNull(13); } if (message.reply_to != null) { state.bindInteger(14, message.reply_to.reply_to_top_id != 0 ? message.reply_to.reply_to_top_id : message.reply_to.reply_to_msg_id); } else { state.bindInteger(14, 0); } state.step(); data.reuse(); if (repliesData != null) { repliesData.reuse(); } } state.dispose(); AndroidUtilities.runOnUIThread(() -> { for (int a = 0; a < messages.size(); a++) { getNotificationCenter().postNotificationName(NotificationCenter.updateMessageMedia, messages.get(a)); } }); } AndroidUtilities.runOnUIThread(() -> getFileLoader().cancelLoadFiles(namesToDelete)); getFileLoader().deleteFiles(filesToDelete, 0); } catch (Exception e) { FileLog.e(e); } }); } public void updateMessagePollResults(long pollId, TLRPC.Poll poll, TLRPC.PollResults results) { storageQueue.postRunnable(() -> { try { ArrayList mids = null; SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT mid FROM polls WHERE id = %d", pollId)); while (cursor.next()) { if (mids == null) { mids = new ArrayList<>(); } mids.add(cursor.longValue(0)); } cursor.dispose(); if (mids != null) { database.beginTransaction(); for (int a = 0, N = mids.size(); a < N; a++) { Long mid = mids.get(a); cursor = database.queryFinalized(String.format(Locale.US, "SELECT data FROM messages WHERE mid = %d", mid)); if (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); if (message.media instanceof TLRPC.TL_messageMediaPoll) { TLRPC.TL_messageMediaPoll media = (TLRPC.TL_messageMediaPoll) message.media; if (poll != null) { media.poll = poll; } if (results != null) { MessageObject.updatePollResults(media, results); } SQLitePreparedStatement state = database.executeFast("UPDATE messages SET data = ? WHERE mid = ?"); data = new NativeByteBuffer(message.getObjectSize()); message.serializeToStream(data); state.requery(); state.bindByteBuffer(1, data); state.bindLong(2, mid); state.step(); data.reuse(); state.dispose(); } } } else { database.executeFast(String.format(Locale.US, "DELETE FROM polls WHERE mid = %d", mid)).stepThis().dispose(); } cursor.dispose(); } database.commitTransaction(); } } catch (Exception e) { FileLog.e(e); } }); } public void updateMessageReactions(long dialogId, int msgId, int channelId, TLRPC.TL_messageReactions reactions) { storageQueue.postRunnable(() -> { try { database.beginTransaction(); long mid = msgId; if (channelId != 0) { mid |= ((long) channelId) << 32; } SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT data FROM messages WHERE mid = %d", mid)); if (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); if (message != null) { message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); MessageObject.updateReactions(message, reactions); SQLitePreparedStatement state = database.executeFast("UPDATE messages SET data = ? WHERE mid = ?"); NativeByteBuffer data2 = new NativeByteBuffer(message.getObjectSize()); message.serializeToStream(data2); state.requery(); state.bindByteBuffer(1, data2); state.bindLong(2, mid); state.step(); data2.reuse(); state.dispose(); } else { data.reuse(); } } } cursor.dispose(); database.commitTransaction(); } catch (Exception e) { FileLog.e(e); } }); } public void getNewTask(final ArrayList oldTask, final int channelId) { storageQueue.postRunnable(() -> { try { if (oldTask != null) { String ids; if (channelId != 0) { StringBuilder builder = new StringBuilder(); for (int a = 0, N = oldTask.size(); a < N; a++) { long messageId = oldTask.get(a); messageId |= ((long) channelId) << 32; if (builder.length() > 0) { builder.append(","); } builder.append(messageId); } ids = builder.toString(); } else { ids = TextUtils.join(",", oldTask); } database.executeFast(String.format(Locale.US, "DELETE FROM enc_tasks_v2 WHERE mid IN(%s)", ids)).stepThis().dispose(); } int date = 0; int channelId1 = -1; boolean media = false; ArrayList arr = null; SQLiteCursor cursor = database.queryFinalized("SELECT mid, date, media FROM enc_tasks_v2 WHERE date = (SELECT min(date) FROM enc_tasks_v2)"); while (cursor.next()) { long mid = cursor.longValue(0); if (channelId1 == -1) { channelId1 = (int) (mid >> 32); if (channelId1 < 0) { channelId1 = 0; } } date = cursor.intValue(1); if (arr == null) { arr = new ArrayList<>(); } int m = (int) mid; arr.add(m); int isMedia = cursor.intValue(2); if (isMedia == -1) { media = m > 0; } else { media = isMedia != 0; } } cursor.dispose(); getMessagesController().processLoadedDeleteTask(date, arr, media, channelId1); } catch (Exception e) { FileLog.e(e); } }); } public void markMentionMessageAsRead(final int messageId, final int channelId, final long did) { storageQueue.postRunnable(() -> { try { long mid = messageId; if (channelId != 0) { mid |= ((long) channelId) << 32; } database.executeFast(String.format(Locale.US, "UPDATE messages SET read_state = read_state | 2 WHERE mid = %d", mid)).stepThis().dispose(); SQLiteCursor cursor = database.queryFinalized("SELECT unread_count_i FROM dialogs WHERE did = " + did); int old_mentions_count = 0; if (cursor.next()) { old_mentions_count = Math.max(0, cursor.intValue(0) - 1); } cursor.dispose(); database.executeFast(String.format(Locale.US, "UPDATE dialogs SET unread_count_i = %d WHERE did = %d", old_mentions_count, did)).stepThis().dispose(); LongSparseArray sparseArray = new LongSparseArray<>(1); sparseArray.put(did, old_mentions_count); if (old_mentions_count == 0) { updateFiltersReadCounter(null, sparseArray, true); } getMessagesController().processDialogsUpdateRead(null, sparseArray); } catch (Exception e) { FileLog.e(e); } }); } public void markMessageAsMention(final long mid) { storageQueue.postRunnable(() -> { try { database.executeFast(String.format(Locale.US, "UPDATE messages SET mention = 1, read_state = read_state & ~2 WHERE mid = %d", mid)).stepThis().dispose(); } catch (Exception e) { FileLog.e(e); } }); } public void resetMentionsCount(final long did, final int count) { storageQueue.postRunnable(() -> { try { int prevUnreadCount = 0; SQLiteCursor cursor = database.queryFinalized("SELECT unread_count_i FROM dialogs WHERE did = " + did); if (cursor.next()) { prevUnreadCount = cursor.intValue(0); } cursor.dispose(); if (prevUnreadCount != 0 || count != 0) { if (count == 0) { database.executeFast(String.format(Locale.US, "UPDATE messages SET read_state = read_state | 2 WHERE uid = %d AND mention = 1 AND read_state IN(0, 1)", did)).stepThis().dispose(); } database.executeFast(String.format(Locale.US, "UPDATE dialogs SET unread_count_i = %d WHERE did = %d", count, did)).stepThis().dispose(); LongSparseArray sparseArray = new LongSparseArray<>(1); sparseArray.put(did, count); getMessagesController().processDialogsUpdateRead(null, sparseArray); if (count == 0) { updateFiltersReadCounter(null, sparseArray, true); } } } catch (Exception e) { FileLog.e(e); } }); } public void createTaskForMid(final int messageId, final int channelId, final int time, final int readTime, final int ttl, final boolean inner) { storageQueue.postRunnable(() -> { try { int minDate = Math.max(time, readTime) + ttl; SparseArray> messages = new SparseArray<>(); final ArrayList midsArray = new ArrayList<>(); long mid = messageId; if (channelId != 0) { mid |= ((long) channelId) << 32; } midsArray.add(mid); messages.put(minDate, midsArray); AndroidUtilities.runOnUIThread(() -> { if (!inner) { markMessagesContentAsRead(midsArray, 0); } getNotificationCenter().postNotificationName(NotificationCenter.messagesReadContent, midsArray); }); SQLitePreparedStatement state = database.executeFast("REPLACE INTO enc_tasks_v2 VALUES(?, ?, ?)"); for (int a = 0; a < messages.size(); a++) { int key = messages.keyAt(a); ArrayList arr = messages.get(key); for (int b = 0; b < arr.size(); b++) { state.requery(); state.bindLong(1, arr.get(b)); state.bindInteger(2, key); state.bindInteger(3, 1); state.step(); } } state.dispose(); database.executeFast(String.format(Locale.US, "UPDATE messages SET ttl = 0 WHERE mid = %d", mid)).stepThis().dispose(); getMessagesController().didAddedNewTask(minDate, messages); } catch (Exception e) { FileLog.e(e); } }); } public void createTaskForSecretChat(final int chatId, final int time, final int readTime, final int isOut, final ArrayList random_ids) { storageQueue.postRunnable(() -> { try { int minDate = Integer.MAX_VALUE; SparseArray> messages = new SparseArray<>(); final ArrayList midsArray = new ArrayList<>(); StringBuilder mids = new StringBuilder(); SQLiteCursor cursor; if (random_ids == null) { cursor = database.queryFinalized(String.format(Locale.US, "SELECT mid, ttl FROM messages WHERE uid = %d AND out = %d AND read_state > 0 AND ttl > 0 AND date <= %d AND send_state = 0 AND media != 1", ((long) chatId) << 32, isOut, time)); } else { String ids = TextUtils.join(",", random_ids); cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.mid, m.ttl FROM messages as m INNER JOIN randoms as r ON m.mid = r.mid WHERE r.random_id IN (%s)", ids)); } while (cursor.next()) { int ttl = cursor.intValue(1); long mid = cursor.intValue(0); if (random_ids != null) { midsArray.add(mid); } if (ttl <= 0) { continue; } int date = Math.max(time, readTime) + ttl; minDate = Math.min(minDate, date); ArrayList arr = messages.get(date); if (arr == null) { arr = new ArrayList<>(); messages.put(date, arr); } if (mids.length() != 0) { mids.append(","); } mids.append(mid); arr.add(mid); } cursor.dispose(); if (random_ids != null) { AndroidUtilities.runOnUIThread(() -> { markMessagesContentAsRead(midsArray, 0); getNotificationCenter().postNotificationName(NotificationCenter.messagesReadContent, midsArray); }); } if (messages.size() != 0) { database.beginTransaction(); SQLitePreparedStatement state = database.executeFast("REPLACE INTO enc_tasks_v2 VALUES(?, ?, ?)"); for (int a = 0; a < messages.size(); a++) { int key = messages.keyAt(a); ArrayList arr = messages.get(key); for (int b = 0; b < arr.size(); b++) { state.requery(); state.bindLong(1, arr.get(b)); state.bindInteger(2, key); state.bindInteger(3, 0); state.step(); } } state.dispose(); database.commitTransaction(); database.executeFast(String.format(Locale.US, "UPDATE messages SET ttl = 0 WHERE mid IN(%s)", mids.toString())).stepThis().dispose(); getMessagesController().didAddedNewTask(minDate, messages); } } catch (Exception e) { FileLog.e(e); } }); } private void updateFiltersReadCounter(LongSparseArray dialogsToUpdate, LongSparseArray dialogsToUpdateMentions, boolean read) throws Exception { if ((dialogsToUpdate == null || dialogsToUpdate.size() == 0) && (dialogsToUpdateMentions == null || dialogsToUpdateMentions.size() == 0)) { return; } for (int a = 0; a < 2; a++) { for (int b = 0; b < 2; b++) { contacts[a][b] = nonContacts[a][b] = bots[a][b] = channels[a][b] = groups[a][b] = 0; } mentionChannels[a] = mentionGroups[a] = 0; } final ArrayList users = new ArrayList<>(); final ArrayList encUsers = new ArrayList<>(); final ArrayList chats = new ArrayList<>(); ArrayList usersToLoad = new ArrayList<>(); ArrayList chatsToLoad = new ArrayList<>(); ArrayList encryptedToLoad = new ArrayList<>(); LongSparseArray dialogsByFolders = new LongSparseArray<>(); LongSparseArray newUnreadDialogs = new LongSparseArray<>(); for (int b = 0; b < 2; b++) { LongSparseArray array = b == 0 ? dialogsToUpdate : dialogsToUpdateMentions; if (array == null) { continue; } for (int a = 0; a < array.size(); a++) { Integer count = array.valueAt(a); if (read && count != 0 || !read && count == 0) { continue; } long did = array.keyAt(a); if (read) { if (b == 0) { dialogsWithUnread.remove(did); /*if (BuildVars.DEBUG_VERSION) { FileLog.d("read remove = " + did); }*/ } else { dialogsWithMentions.remove(did); /*if (BuildVars.DEBUG_VERSION) { FileLog.d("mention remove = " + did); }*/ } } else { if (dialogsWithMentions.indexOfKey(did) < 0 && dialogsWithUnread.indexOfKey(did) < 0) { newUnreadDialogs.put(did, count); } if (b == 0) { dialogsWithUnread.put(did, count); /*if (BuildVars.DEBUG_VERSION) { FileLog.d("read add = " + did); }*/ } else { dialogsWithMentions.put(did, count); /*if (BuildVars.DEBUG_VERSION) { FileLog.d("mention add = " + did); }*/ } } if (dialogsByFolders.indexOfKey(did) < 0) { SQLiteCursor cursor = database.queryFinalized("SELECT folder_id FROM dialogs WHERE did = " + did); int folderId = 0; if (cursor.next()) { folderId = cursor.intValue(0); } cursor.dispose(); dialogsByFolders.put(did, folderId); } int lowerId = (int) did; int highId = (int) (did >> 32); if (lowerId != 0) { if (lowerId < 0) { if (!chatsToLoad.contains(-lowerId)) { chatsToLoad.add(-lowerId); } } else { if (!usersToLoad.contains(lowerId)) { usersToLoad.add(lowerId); } } } else { if (!encryptedToLoad.contains(highId)) { encryptedToLoad.add(highId); } } } } SparseArray usersDict = new SparseArray<>(); SparseArray chatsDict = new SparseArray<>(); SparseArray encUsersDict = new SparseArray<>(); SparseArray encryptedChatsByUsersCount = new SparseArray<>(); SparseArray mutedDialogs = new SparseArray<>(); SparseArray archivedDialogs = new SparseArray<>(); if (!usersToLoad.isEmpty()) { getUsersInternal(TextUtils.join(",", usersToLoad), users); for (int a = 0, N = users.size(); a < N; a++) { TLRPC.User user = users.get(a); boolean muted = getMessagesController().isDialogMuted(user.id); int idx1 = dialogsByFolders.get(user.id); int idx2 = muted ? 1 : 0; if (muted) { mutedDialogs.put(user.id, true); } if (idx1 == 1) { archivedDialogs.put(user.id, true); } if (user.bot) { bots[idx1][idx2]++; } else if (user.self || user.contact) { contacts[idx1][idx2]++; } else { nonContacts[idx1][idx2]++; } usersDict.put(user.id, user); } } if (!encryptedToLoad.isEmpty()) { ArrayList encUsersToLoad = new ArrayList<>(); ArrayList encryptedChats = new ArrayList<>(); getEncryptedChatsInternal(TextUtils.join(",", encryptedToLoad), encryptedChats, encUsersToLoad); if (!encUsersToLoad.isEmpty()) { getUsersInternal(TextUtils.join(",", encUsersToLoad), encUsers); for (int a = 0, N = encUsers.size(); a < N; a++) { TLRPC.User user = encUsers.get(a); encUsersDict.put(user.id, user); } for (int a = 0, N = encryptedChats.size(); a < N; a++) { TLRPC.EncryptedChat encryptedChat = encryptedChats.get(a); TLRPC.User user = encUsersDict.get(encryptedChat.user_id); if (user == null) { continue; } long did = ((long) encryptedChat.id) << 32; boolean muted = getMessagesController().isDialogMuted(did); int idx1 = dialogsByFolders.get(did); int idx2 = muted ? 1 : 0; if (muted) { mutedDialogs.put(user.id, true); } if (idx1 == 1) { archivedDialogs.put(user.id, true); } if (user.self || user.contact) { contacts[idx1][idx2]++; } else { nonContacts[idx1][idx2]++; } int count = encryptedChatsByUsersCount.get(user.id, 0); encryptedChatsByUsersCount.put(user.id, count + 1); } } } if (!chatsToLoad.isEmpty()) { getChatsInternal(TextUtils.join(",", chatsToLoad), chats); for (int a = 0, N = chats.size(); a < N; a++) { TLRPC.Chat chat = chats.get(a); if (chat.migrated_to instanceof TLRPC.TL_inputChannel || ChatObject.isNotInChat(chat)) { continue; } boolean muted = getMessagesController().isDialogMuted(-chat.id, chat); boolean hasUnread = dialogsWithUnread.indexOfKey(-chat.id) >= 0; boolean hasMention = dialogsWithMentions.indexOfKey(-chat.id) >= 0; int idx1 = dialogsByFolders.get(-chat.id); int idx2 = muted ? 1 : 0; if (muted) { mutedDialogs.put(-chat.id, true); } if (idx1 == 1) { archivedDialogs.put(-chat.id, true); } if (muted && dialogsToUpdateMentions != null && dialogsToUpdateMentions.indexOfKey(-chat.id) >= 0) { if (ChatObject.isChannel(chat) && !chat.megagroup) { mentionChannels[idx1]++; } else { mentionGroups[idx1]++; } } if (read && !hasUnread && !hasMention || !read && newUnreadDialogs.indexOfKey(-chat.id) >= 0) { if (ChatObject.isChannel(chat) && !chat.megagroup) { channels[idx1][idx2]++; } else { groups[idx1][idx2]++; } } chatsDict.put(chat.id, chat); } } /*if (BuildVars.DEBUG_VERSION) { for (int b = 0; b < 2; b++) { FileLog.d("read = " + read + " contacts = " + contacts[b][0] + ", " + contacts[b][1]); FileLog.d("read = " + read + " nonContacts = " + nonContacts[b][0] + ", " + nonContacts[b][1]); FileLog.d("read = " + read + " groups = " + groups[b][0] + ", " + groups[b][1]); FileLog.d("read = " + read + " channels = " + channels[b][0] + ", " + channels[b][1]); FileLog.d("read = " + read + " bots = " + bots[b][0] + ", " + bots[b][1]); } }*/ for (int a = 0, N = dialogFilters.size(); a < N + 2; a++) { int unreadCount; MessagesController.DialogFilter filter; int flags; if (a < N) { filter = dialogFilters.get(a); if (filter.pendingUnreadCount < 0) { continue; } unreadCount = filter.pendingUnreadCount; flags = filter.flags; } else { filter = null; flags = MessagesController.DIALOG_FILTER_FLAG_ALL_CHATS; if (a == N) { unreadCount = pendingMainUnreadCount; flags |= MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_ARCHIVED; if (!getNotificationsController().showBadgeMuted) { flags |= MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED; } } else { unreadCount = pendingArchiveUnreadCount; flags |= MessagesController.DIALOG_FILTER_FLAG_ONLY_ARCHIVED; } } if (read) { if ((flags & MessagesController.DIALOG_FILTER_FLAG_CONTACTS) != 0) { if ((flags & MessagesController.DIALOG_FILTER_FLAG_ONLY_ARCHIVED) == 0) { unreadCount -= contacts[0][0]; if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) == 0) { unreadCount -= contacts[0][1]; } } if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_ARCHIVED) == 0) { unreadCount -= contacts[1][0]; if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) == 0) { unreadCount -= contacts[1][1]; } } } if ((flags & MessagesController.DIALOG_FILTER_FLAG_NON_CONTACTS) != 0) { if ((flags & MessagesController.DIALOG_FILTER_FLAG_ONLY_ARCHIVED) == 0) { unreadCount -= nonContacts[0][0]; if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) == 0) { unreadCount -= nonContacts[0][1]; } } if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_ARCHIVED) == 0) { unreadCount -= nonContacts[1][0]; if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) == 0) { unreadCount -= nonContacts[1][1]; } } } if ((flags & MessagesController.DIALOG_FILTER_FLAG_GROUPS) != 0) { if ((flags & MessagesController.DIALOG_FILTER_FLAG_ONLY_ARCHIVED) == 0) { unreadCount -= groups[0][0]; if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) == 0) { unreadCount -= groups[0][1]; } else { unreadCount -= mentionGroups[0]; } } if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_ARCHIVED) == 0) { unreadCount -= groups[1][0]; if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) == 0) { unreadCount -= groups[1][1]; } else { unreadCount -= mentionGroups[1]; } } } if ((flags & MessagesController.DIALOG_FILTER_FLAG_CHANNELS) != 0) { if ((flags & MessagesController.DIALOG_FILTER_FLAG_ONLY_ARCHIVED) == 0) { unreadCount -= channels[0][0]; if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) == 0) { unreadCount -= channels[0][1]; } else { unreadCount -= mentionChannels[0]; } } if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_ARCHIVED) == 0) { unreadCount -= channels[1][0]; if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) == 0) { unreadCount -= channels[1][1]; } else { unreadCount -= mentionChannels[1]; } } } if ((flags & MessagesController.DIALOG_FILTER_FLAG_BOTS) != 0) { if ((flags & MessagesController.DIALOG_FILTER_FLAG_ONLY_ARCHIVED) == 0) { unreadCount -= bots[0][0]; if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) == 0) { unreadCount -= bots[0][1]; } } if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_ARCHIVED) == 0) { unreadCount -= bots[1][0]; if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) == 0) { unreadCount -= bots[1][1]; } } } if (filter != null) { for (int b = 0, N2 = filter.alwaysShow.size(); b < N2; b++) { int did = filter.alwaysShow.get(b); if (did > 0) { for (int i = 0; i < 2; i++) { SparseArray dict = i == 0 ? usersDict : encUsersDict; TLRPC.User user = dict.get(did); if (user != null) { int count; if (i == 0) { count = 1; } else { count = encryptedChatsByUsersCount.get(did, 0); if (count == 0) { continue; } } int flag; if (user.bot) { flag = MessagesController.DIALOG_FILTER_FLAG_BOTS; } else if (user.self || user.contact) { flag = MessagesController.DIALOG_FILTER_FLAG_CONTACTS; } else { flag = MessagesController.DIALOG_FILTER_FLAG_NON_CONTACTS; } if ((flags & flag) == 0) { unreadCount -= count; } else if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) != 0 && mutedDialogs.indexOfKey(user.id) >= 0) { unreadCount -= count; } else if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_ARCHIVED) != 0 && archivedDialogs.indexOfKey(user.id) >= 0) { unreadCount -= count; } } } } else { TLRPC.Chat chat = chatsDict.get(-did); if (chat != null) { int flag; if (ChatObject.isChannel(chat) && !chat.megagroup) { flag = MessagesController.DIALOG_FILTER_FLAG_CHANNELS; } else { flag = MessagesController.DIALOG_FILTER_FLAG_GROUPS; } if ((flags & flag) == 0) { unreadCount--; } else if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) != 0 && mutedDialogs.indexOfKey(-chat.id) >= 0 && dialogsWithMentions.indexOfKey(-chat.id) < 0) { unreadCount--; } else if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_ARCHIVED) != 0 && archivedDialogs.indexOfKey(-chat.id) >= 0) { unreadCount--; } } } } for (int b = 0, N2 = filter.neverShow.size(); b < N2; b++) { int did = filter.neverShow.get(b); if (dialogsToUpdateMentions != null && dialogsToUpdateMentions.indexOfKey(did) >= 0 && mutedDialogs.indexOfKey(did) < 0) { continue; } if (did > 0) { for (int i = 0; i < 2; i++) { SparseArray dict = i == 0 ? usersDict : encUsersDict; TLRPC.User user = dict.get(did); if (user != null) { int count; if (i == 0) { count = 1; } else { count = encryptedChatsByUsersCount.get(did, 0); if (count == 0) { continue; } } int flag; if (user.bot) { flag = MessagesController.DIALOG_FILTER_FLAG_BOTS; } else if (user.self || user.contact) { flag = MessagesController.DIALOG_FILTER_FLAG_CONTACTS; } else { flag = MessagesController.DIALOG_FILTER_FLAG_NON_CONTACTS; } if ((flags & flag) != 0) { if (((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_ARCHIVED) == 0 || archivedDialogs.indexOfKey(user.id) < 0) && ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) == 0 || mutedDialogs.indexOfKey(user.id) < 0)) { unreadCount += count; } } } } } else { TLRPC.Chat chat = chatsDict.get(-did); if (chat != null) { int flag; if (ChatObject.isChannel(chat) && !chat.megagroup) { flag = MessagesController.DIALOG_FILTER_FLAG_CHANNELS; } else { flag = MessagesController.DIALOG_FILTER_FLAG_GROUPS; } if ((flags & flag) != 0) { if (((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_ARCHIVED) == 0 || archivedDialogs.indexOfKey(-chat.id) < 0) && ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) == 0 || mutedDialogs.indexOfKey(-chat.id) < 0 || dialogsWithMentions.indexOfKey(-chat.id) >= 0)) { unreadCount++; } } } } } } if (unreadCount < 0) { unreadCount = 0; } } else { if ((flags & MessagesController.DIALOG_FILTER_FLAG_CONTACTS) != 0) { if ((flags & MessagesController.DIALOG_FILTER_FLAG_ONLY_ARCHIVED) == 0) { unreadCount += contacts[0][0]; if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) == 0) { unreadCount += contacts[0][1]; } } if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_ARCHIVED) == 0) { unreadCount += contacts[1][0]; if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) == 0) { unreadCount += contacts[1][1]; } } } if ((flags & MessagesController.DIALOG_FILTER_FLAG_NON_CONTACTS) != 0) { if ((flags & MessagesController.DIALOG_FILTER_FLAG_ONLY_ARCHIVED) == 0) { unreadCount += nonContacts[0][0]; if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) == 0) { unreadCount += nonContacts[0][1]; } } if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_ARCHIVED) == 0) { unreadCount += nonContacts[1][0]; if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) == 0) { unreadCount += nonContacts[1][1]; } } } if ((flags & MessagesController.DIALOG_FILTER_FLAG_GROUPS) != 0) { if ((flags & MessagesController.DIALOG_FILTER_FLAG_ONLY_ARCHIVED) == 0) { unreadCount += groups[0][0]; if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) == 0) { unreadCount += groups[0][1]; } else { unreadCount += mentionGroups[0]; } } if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_ARCHIVED) == 0) { unreadCount += groups[1][0]; if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) == 0) { unreadCount += groups[1][1]; } else { unreadCount += mentionGroups[1]; } } } if ((flags & MessagesController.DIALOG_FILTER_FLAG_CHANNELS) != 0) { if ((flags & MessagesController.DIALOG_FILTER_FLAG_ONLY_ARCHIVED) == 0) { unreadCount += channels[0][0]; if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) == 0) { unreadCount += channels[0][1]; } else { unreadCount += mentionChannels[0]; } } if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_ARCHIVED) == 0) { unreadCount += channels[1][0]; if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) == 0) { unreadCount += channels[1][1]; } else { unreadCount += mentionChannels[1]; } } } if ((flags & MessagesController.DIALOG_FILTER_FLAG_BOTS) != 0) { if ((flags & MessagesController.DIALOG_FILTER_FLAG_ONLY_ARCHIVED) == 0) { unreadCount += bots[0][0]; if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) == 0) { unreadCount += bots[0][1]; } } if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_ARCHIVED) == 0) { unreadCount += bots[1][0]; if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) == 0) { unreadCount += bots[1][1]; } } } if (filter != null) { if (!filter.alwaysShow.isEmpty()) { if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) != 0) { for (int b = 0, N2 = dialogsToUpdateMentions.size(); b < N2; b++) { int did = (int) dialogsToUpdateMentions.keyAt(b); TLRPC.Chat chat = chatsDict.get(-did); if (ChatObject.isChannel(chat) && !chat.megagroup) { if ((flags & MessagesController.DIALOG_FILTER_FLAG_CHANNELS) == 0) { continue; } } else { if ((flags & MessagesController.DIALOG_FILTER_FLAG_GROUPS) == 0) { continue; } } if (mutedDialogs.indexOfKey(did) >= 0 && filter.alwaysShow.contains(did)) { unreadCount--; } } } for (int b = 0, N2 = filter.alwaysShow.size(); b < N2; b++) { int did = filter.alwaysShow.get(b); if (newUnreadDialogs.indexOfKey(did) < 0) { continue; } if (did > 0) { TLRPC.User user = usersDict.get(did); if (user != null) { if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) != 0 && mutedDialogs.indexOfKey(user.id) >= 0) { unreadCount++; } else { if (user.bot) { if ((flags & MessagesController.DIALOG_FILTER_FLAG_BOTS) == 0) { unreadCount++; } } else if (user.self || user.contact) { if ((flags & MessagesController.DIALOG_FILTER_FLAG_CONTACTS) == 0) { unreadCount++; } } else { if ((flags & MessagesController.DIALOG_FILTER_FLAG_NON_CONTACTS) == 0) { unreadCount++; } } } } user = encUsersDict.get(did); if (user != null) { int count = encryptedChatsByUsersCount.get(did, 0); if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) != 0 && mutedDialogs.indexOfKey(user.id) >= 0) { unreadCount += count; } else { if (user.bot) { if ((flags & MessagesController.DIALOG_FILTER_FLAG_BOTS) == 0) { unreadCount += count; } } else if (user.self || user.contact) { if ((flags & MessagesController.DIALOG_FILTER_FLAG_CONTACTS) == 0) { unreadCount += count; } } else { if ((flags & MessagesController.DIALOG_FILTER_FLAG_NON_CONTACTS) == 0) { unreadCount += count; } } } } } else { TLRPC.Chat chat = chatsDict.get(-did); if (chat != null) { if ((flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) != 0 && mutedDialogs.indexOfKey(-chat.id) >= 0) { unreadCount++; } else { if (ChatObject.isChannel(chat) && !chat.megagroup) { if ((flags & MessagesController.DIALOG_FILTER_FLAG_CHANNELS) == 0) { unreadCount++; } } else { if ((flags & MessagesController.DIALOG_FILTER_FLAG_GROUPS) == 0) { unreadCount++; } } } } } } } for (int b = 0, N2 = filter.neverShow.size(); b < N2; b++) { int did = filter.neverShow.get(b); if (did > 0) { TLRPC.User user = usersDict.get(did); if (user != null) { unreadCount--; } user = encUsersDict.get(did); if (user != null) { unreadCount -= encryptedChatsByUsersCount.get(did, 0); } } else { TLRPC.Chat chat = chatsDict.get(-did); if (chat != null) { unreadCount--; } } } } } if (filter != null) { filter.pendingUnreadCount = unreadCount; /*if (BuildVars.DEBUG_VERSION) { FileLog.d("filter " + filter.name + " flags = " + flags + " read = " + read + " unread count = " + filter.pendingUnreadCount); }*/ } else if (a == N) { pendingMainUnreadCount = unreadCount; } else if (a == N + 1) { pendingArchiveUnreadCount = unreadCount; } } AndroidUtilities.runOnUIThread(() -> { ArrayList filters = getMessagesController().dialogFilters; for (int a = 0, N = filters.size(); a < N; a++) { filters.get(a).unreadCount = filters.get(a).pendingUnreadCount; } mainUnreadCount = pendingMainUnreadCount; archiveUnreadCount = pendingArchiveUnreadCount; }); } private void updateDialogsWithReadMessagesInternal(final ArrayList messages, final SparseLongArray inbox, SparseLongArray outbox, final ArrayList mentions) { try { LongSparseArray dialogsToUpdate = new LongSparseArray<>(); LongSparseArray dialogsToUpdateMentions = new LongSparseArray<>(); ArrayList channelMentionsToReload = new ArrayList<>(); if (!isEmpty(messages)) { String ids = TextUtils.join(",", messages); SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT uid, read_state, out FROM messages WHERE mid IN(%s)", ids)); while (cursor.next()) { int out = cursor.intValue(2); if (out != 0) { continue; } int read_state = cursor.intValue(1); if (read_state != 0) { continue; } long uid = cursor.longValue(0); Integer currentCount = dialogsToUpdate.get(uid); if (currentCount == null) { dialogsToUpdate.put(uid, 1); } else { dialogsToUpdate.put(uid, currentCount + 1); } } cursor.dispose(); } else { if (!isEmpty(inbox)) { for (int b = 0; b < inbox.size(); b++) { int key = inbox.keyAt(b); long messageId = inbox.get(key); SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT COUNT(mid) FROM messages WHERE uid = %d AND mid > %d AND read_state IN(0,2) AND out = 0", key, messageId)); if (cursor.next()) { dialogsToUpdate.put(key, cursor.intValue(0)); } cursor.dispose(); SQLitePreparedStatement state = database.executeFast("UPDATE dialogs SET inbox_max = max((SELECT inbox_max FROM dialogs WHERE did = ?), ?) WHERE did = ?"); state.requery(); state.bindLong(1, key); state.bindInteger(2, (int) messageId); state.bindLong(3, key); state.step(); state.dispose(); } } if (!isEmpty(mentions)) { ArrayList notFoundMentions = new ArrayList<>(mentions); String ids = TextUtils.join(",", mentions); SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT uid, read_state, out, mention, mid FROM messages WHERE mid IN(%s)", ids)); while (cursor.next()) { long did = cursor.longValue(0); notFoundMentions.remove(cursor.longValue(4)); if (cursor.intValue(1) < 2 && cursor.intValue(2) == 0 && cursor.intValue(3) == 1) { Integer unread_count = dialogsToUpdateMentions.get(did); if (unread_count == null) { SQLiteCursor cursor2 = database.queryFinalized("SELECT unread_count_i FROM dialogs WHERE did = " + did); int old_mentions_count = 0; if (cursor2.next()) { old_mentions_count = cursor2.intValue(0); } cursor2.dispose(); dialogsToUpdateMentions.put(did, Math.max(0, old_mentions_count - 1)); } else { dialogsToUpdateMentions.put(did, Math.max(0, unread_count - 1)); } } } cursor.dispose(); for (int a = 0; a < notFoundMentions.size(); a++) { int channelId = (int) (notFoundMentions.get(a) >> 32); if (channelId > 0 && !channelMentionsToReload.contains(channelId)) { channelMentionsToReload.add(channelId); } } } if (!isEmpty(outbox)) { for (int b = 0; b < outbox.size(); b++) { int key = outbox.keyAt(b); long messageId = outbox.get(key); SQLitePreparedStatement state = database.executeFast("UPDATE dialogs SET outbox_max = max((SELECT outbox_max FROM dialogs WHERE did = ?), ?) WHERE did = ?"); state.requery(); state.bindLong(1, key); state.bindInteger(2, (int) messageId); state.bindLong(3, key); state.step(); state.dispose(); } } } if (dialogsToUpdate.size() > 0 || dialogsToUpdateMentions.size() > 0) { database.beginTransaction(); if (dialogsToUpdate.size() > 0) { ArrayList dids = new ArrayList<>(); SQLitePreparedStatement state = database.executeFast("UPDATE dialogs SET unread_count = ? WHERE did = ?"); for (int a = 0; a < dialogsToUpdate.size(); a++) { long did = dialogsToUpdate.keyAt(a); int prevUnreadCount = 0; int newCount = dialogsToUpdate.valueAt(a); SQLiteCursor cursor = database.queryFinalized("SELECT unread_count FROM dialogs WHERE did = " + did); if (cursor.next()) { prevUnreadCount = cursor.intValue(0); } cursor.dispose(); if (prevUnreadCount == newCount) { dialogsToUpdate.removeAt(a); a--; continue; } state.requery(); state.bindInteger(1, newCount); state.bindLong(2, did); state.step(); dids.add(did); } state.dispose(); updateWidgets(dids); } if (dialogsToUpdateMentions.size() > 0) { SQLitePreparedStatement state = database.executeFast("UPDATE dialogs SET unread_count_i = ? WHERE did = ?"); for (int a = 0; a < dialogsToUpdateMentions.size(); a++) { state.requery(); state.bindInteger(1, dialogsToUpdateMentions.valueAt(a)); state.bindLong(2, dialogsToUpdateMentions.keyAt(a)); state.step(); } state.dispose(); } database.commitTransaction(); } updateFiltersReadCounter(dialogsToUpdate, dialogsToUpdateMentions, true); getMessagesController().processDialogsUpdateRead(dialogsToUpdate, dialogsToUpdateMentions); if (!channelMentionsToReload.isEmpty()) { getMessagesController().reloadMentionsCountForChannels(channelMentionsToReload); } } catch (Exception e) { FileLog.e(e); } } private static boolean isEmpty(SparseArray array) { return array == null || array.size() == 0; } private static boolean isEmpty(SparseLongArray array) { return array == null || array.size() == 0; } private static boolean isEmpty(List array) { return array == null || array.isEmpty(); } private static boolean isEmpty(SparseIntArray array) { return array == null || array.size() == 0; } private static boolean isEmpty(LongSparseArray array) { return array == null || array.size() == 0; } public void updateDialogsWithReadMessages(final SparseLongArray inbox, final SparseLongArray outbox, final ArrayList mentions, boolean useQueue) { if (isEmpty(inbox) && isEmpty(outbox) && isEmpty(mentions)) { return; } if (useQueue) { storageQueue.postRunnable(() -> updateDialogsWithReadMessagesInternal(null, inbox, outbox, mentions)); } else { updateDialogsWithReadMessagesInternal(null, inbox, outbox, mentions); } } public void updateChatParticipants(final TLRPC.ChatParticipants participants) { if (participants == null) { return; } storageQueue.postRunnable(() -> { try { SQLiteCursor cursor = database.queryFinalized("SELECT info, pinned, online, inviter FROM chat_settings_v2 WHERE uid = " + participants.chat_id); TLRPC.ChatFull info = null; ArrayList loadedUsers = new ArrayList<>(); if (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { info = TLRPC.ChatFull.TLdeserialize(data, data.readInt32(false), false); data.reuse(); info.pinned_msg_id = cursor.intValue(1); info.online_count = cursor.intValue(2); info.inviterId = cursor.intValue(3); } } cursor.dispose(); if (info instanceof TLRPC.TL_chatFull) { info.participants = participants; final TLRPC.ChatFull finalInfo = info; AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.chatInfoDidLoad, finalInfo, 0, false, false)); SQLitePreparedStatement state = database.executeFast("REPLACE INTO chat_settings_v2 VALUES(?, ?, ?, ?, ?, ?)"); NativeByteBuffer data = new NativeByteBuffer(info.getObjectSize()); info.serializeToStream(data); state.bindInteger(1, info.id); state.bindByteBuffer(2, data); state.bindInteger(3, info.pinned_msg_id); state.bindInteger(4, info.online_count); state.bindInteger(5, info.inviterId); state.bindInteger(6, info.invitesCount); state.step(); state.dispose(); data.reuse(); } } catch (Exception e) { FileLog.e(e); } }); } public void loadChannelAdmins(final int chatId) { storageQueue.postRunnable(() -> { try { SQLiteCursor cursor = database.queryFinalized("SELECT uid, data FROM channel_admins_v3 WHERE did = " + chatId); SparseArray ids = new SparseArray<>(); while (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(1); if (data != null) { TLRPC.ChannelParticipant participant = TLRPC.ChannelParticipant.TLdeserialize(data, data.readInt32(false), false); data.reuse(); if (participant != null) { ids.put(cursor.intValue(0), participant); } } } cursor.dispose(); getMessagesController().processLoadedChannelAdmins(ids, chatId, true); } catch (Exception e) { FileLog.e(e); } }); } public void putChannelAdmins(final int chatId, final SparseArray ids) { storageQueue.postRunnable(() -> { try { database.executeFast("DELETE FROM channel_admins_v3 WHERE did = " + chatId).stepThis().dispose(); database.beginTransaction(); SQLitePreparedStatement state = database.executeFast("REPLACE INTO channel_admins_v3 VALUES(?, ?, ?)"); int date = (int) (System.currentTimeMillis() / 1000); NativeByteBuffer data; for (int a = 0; a < ids.size(); a++) { state.requery(); state.bindInteger(1, chatId); state.bindInteger(2, ids.keyAt(a)); TLRPC.ChannelParticipant participant = ids.valueAt(a); data = new NativeByteBuffer(participant.getObjectSize()); participant.serializeToStream(data); state.bindByteBuffer(3, data); state.step(); data.reuse(); } state.dispose(); database.commitTransaction(); } catch (Exception e) { FileLog.e(e); } }); } public void updateChannelUsers(final int channelId, final ArrayList participants) { storageQueue.postRunnable(() -> { try { long did = -channelId; database.executeFast("DELETE FROM channel_users_v2 WHERE did = " + did).stepThis().dispose(); database.beginTransaction(); SQLitePreparedStatement state = database.executeFast("REPLACE INTO channel_users_v2 VALUES(?, ?, ?, ?)"); NativeByteBuffer data; int date = (int) (System.currentTimeMillis() / 1000); for (int a = 0; a < participants.size(); a++) { TLRPC.ChannelParticipant participant = participants.get(a); state.requery(); state.bindLong(1, did); state.bindInteger(2, MessageObject.getPeerId(participant.peer)); state.bindInteger(3, date); data = new NativeByteBuffer(participant.getObjectSize()); participant.serializeToStream(data); state.bindByteBuffer(4, data); state.step(); data.reuse(); date--; } state.dispose(); database.commitTransaction(); loadChatInfo(channelId, true, null, false, true); } catch (Exception e) { FileLog.e(e); } }); } public void saveBotCache(final String key, final TLObject result) { if (result == null || TextUtils.isEmpty(key)) { return; } storageQueue.postRunnable(() -> { try { int currentDate = getConnectionsManager().getCurrentTime(); if (result instanceof TLRPC.TL_messages_botCallbackAnswer) { currentDate += ((TLRPC.TL_messages_botCallbackAnswer) result).cache_time; } else if (result instanceof TLRPC.TL_messages_botResults) { currentDate += ((TLRPC.TL_messages_botResults) result).cache_time; } SQLitePreparedStatement state = database.executeFast("REPLACE INTO botcache VALUES(?, ?, ?)"); NativeByteBuffer data = new NativeByteBuffer(result.getObjectSize()); result.serializeToStream(data); state.bindString(1, key); state.bindInteger(2, currentDate); state.bindByteBuffer(3, data); state.step(); state.dispose(); data.reuse(); } catch (Exception e) { FileLog.e(e); } }); } public void getBotCache(final String key, final RequestDelegate requestDelegate) { if (key == null || requestDelegate == null) { return; } final int currentDate = getConnectionsManager().getCurrentTime(); storageQueue.postRunnable(() -> { TLObject result = null; try { database.executeFast("DELETE FROM botcache WHERE date < " + currentDate).stepThis().dispose(); SQLiteCursor cursor = database.queryFinalized( "SELECT data FROM botcache WHERE id = ?", key); if (cursor.next()) { try { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { int constructor = data.readInt32(false); if (constructor == TLRPC.TL_messages_botCallbackAnswer.constructor) { result = TLRPC.TL_messages_botCallbackAnswer.TLdeserialize(data, constructor, false); } else { result = TLRPC.messages_BotResults.TLdeserialize(data, constructor, false); } data.reuse(); } } catch (Exception e) { FileLog.e(e); } } cursor.dispose(); } catch (Exception e) { FileLog.e(e); } finally { requestDelegate.run(result, null); } }); } public void loadUserInfo(TLRPC.User user, final boolean force, int classGuid, int fromMessageId) { if (user == null) { return; } storageQueue.postRunnable(() -> { HashMap pinnedMessagesMap = new HashMap<>(); ArrayList pinnedMessages = new ArrayList<>(); int totalPinnedCount = 0; boolean pinnedEndReached = false; TLRPC.UserFull info = null; try { SQLiteCursor cursor = database.queryFinalized("SELECT info, pinned FROM user_settings WHERE uid = " + user.id); if (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { info = TLRPC.UserFull.TLdeserialize(data, data.readInt32(false), false); info.pinned_msg_id = cursor.intValue(1); data.reuse(); } } cursor.dispose(); cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT mid FROM chat_pinned_v2 WHERE uid = %d ORDER BY mid DESC", user.id)); while (cursor.next()) { int id = cursor.intValue(0); pinnedMessages.add(id); pinnedMessagesMap.put(id, null); } cursor.dispose(); cursor = database.queryFinalized("SELECT count, end FROM chat_pinned_count WHERE uid = " + user.id); if (cursor.next()) { totalPinnedCount = cursor.intValue(0); pinnedEndReached = cursor.intValue(1) != 0; } cursor.dispose(); if (info != null && info.pinned_msg_id != 0) { if (pinnedMessages.isEmpty() || info.pinned_msg_id > pinnedMessages.get(0)) { pinnedMessages.clear(); pinnedMessages.add(info.pinned_msg_id); pinnedMessagesMap.put(info.pinned_msg_id, null); } } if (!pinnedMessages.isEmpty()) { ArrayList messageObjects = getMediaDataController().loadPinnedMessages(user.id, 0, pinnedMessages, false); if (messageObjects != null) { for (int a = 0, N = messageObjects.size(); a < N; a++) { MessageObject messageObject = messageObjects.get(a); pinnedMessagesMap.put(messageObject.getId(), messageObject); } } } } catch (Exception e) { FileLog.e(e); } finally { getMessagesController().processUserInfo(user, info, true, force, classGuid, pinnedMessages, pinnedMessagesMap, totalPinnedCount, pinnedEndReached); } }); } public void updateUserInfo(final TLRPC.UserFull info, final boolean ifExist) { storageQueue.postRunnable(() -> { try { if (ifExist) { SQLiteCursor cursor = database.queryFinalized("SELECT uid FROM user_settings WHERE uid = " + info.user.id); boolean exist = cursor.next(); cursor.dispose(); if (!exist) { return; } } SQLitePreparedStatement state = database.executeFast("REPLACE INTO user_settings VALUES(?, ?, ?)"); NativeByteBuffer data = new NativeByteBuffer(info.getObjectSize()); info.serializeToStream(data); state.bindInteger(1, info.user.id); state.bindByteBuffer(2, data); state.bindInteger(3, info.pinned_msg_id); state.step(); state.dispose(); data.reuse(); if ((info.flags & 2048) != 0) { state = database.executeFast("UPDATE dialogs SET folder_id = ? WHERE did = ?"); state.bindInteger(1, info.folder_id); state.bindLong(2, info.user.id); state.step(); state.dispose(); unknownDialogsIds.remove(info.user.id); } } catch (Exception e) { FileLog.e(e); } }); } public void saveChatInviter(int chatId, int inviterId) { storageQueue.postRunnable(() -> { try { SQLitePreparedStatement state = database.executeFast("UPDATE chat_settings_v2 SET inviter = ? WHERE uid = ?"); state.requery(); state.bindInteger(1, inviterId); state.bindInteger(2, chatId); state.step(); state.dispose(); } catch (Exception e) { FileLog.e(e); } }); } public void saveChatLinksCount(int chatId, int linksCount) { storageQueue.postRunnable(() -> { try { SQLitePreparedStatement state = database.executeFast("UPDATE chat_settings_v2 SET links = ? WHERE uid = ?"); state.requery(); state.bindInteger(1, linksCount); state.bindInteger(2, chatId); state.step(); state.dispose(); } catch (Exception e) { FileLog.e(e); } }); } public void updateChatInfo(final TLRPC.ChatFull info, final boolean ifExist) { storageQueue.postRunnable(() -> { try { int currentOnline = -1; int inviter = 0; int links = 0; SQLiteCursor cursor = database.queryFinalized("SELECT online, inviter, links FROM chat_settings_v2 WHERE uid = " + info.id); if (cursor.next()) { currentOnline = cursor.intValue(0); info.inviterId = cursor.intValue(1); links = cursor.intValue(2); } cursor.dispose(); if (ifExist && currentOnline == -1) { return; } if (currentOnline >= 0 && (info.flags & 8192) == 0) { info.online_count = currentOnline; } if (links >= 0) { info.invitesCount = links; } SQLitePreparedStatement state = database.executeFast("REPLACE INTO chat_settings_v2 VALUES(?, ?, ?, ?, ?, ?)"); NativeByteBuffer data = new NativeByteBuffer(info.getObjectSize()); info.serializeToStream(data); state.bindInteger(1, info.id); state.bindByteBuffer(2, data); state.bindInteger(3, info.pinned_msg_id); state.bindInteger(4, info.online_count); state.bindInteger(5, info.inviterId); state.bindInteger(6, info.invitesCount); state.step(); state.dispose(); data.reuse(); if (info instanceof TLRPC.TL_channelFull) { cursor = database.queryFinalized("SELECT inbox_max, outbox_max FROM dialogs WHERE did = " + (-info.id)); if (cursor.next()) { int inbox_max = cursor.intValue(0); if (inbox_max < info.read_inbox_max_id) { int outbox_max = cursor.intValue(1); state = database.executeFast("UPDATE dialogs SET unread_count = ?, inbox_max = ?, outbox_max = ? WHERE did = ?"); state.bindInteger(1, info.unread_count); state.bindInteger(2, info.read_inbox_max_id); state.bindInteger(3, Math.max(outbox_max, info.read_outbox_max_id)); state.bindLong(4, -info.id); state.step(); state.dispose(); } } cursor.dispose(); } if ((info.flags & 2048) != 0) { state = database.executeFast("UPDATE dialogs SET folder_id = ? WHERE did = ?"); state.bindInteger(1, info.folder_id); state.bindLong(2, -info.id); state.step(); state.dispose(); unknownDialogsIds.remove(-info.id); } } catch (Exception e) { FileLog.e(e); } }); } public void updateChatOnlineCount(final int channelId, final int onlineCount) { storageQueue.postRunnable(() -> { try { SQLitePreparedStatement state = database.executeFast("UPDATE chat_settings_v2 SET online = ? WHERE uid = ?"); state.requery(); state.bindInteger(1, onlineCount); state.bindInteger(2, channelId); state.step(); state.dispose(); } catch (Exception e) { FileLog.e(e); } }); } public void updatePinnedMessages(long dialogId, ArrayList ids, boolean pin, int totalCount, int maxId, boolean end, HashMap messages) { storageQueue.postRunnable(() -> { try { if (pin) { database.beginTransaction(); int alreadyAdded = 0; boolean endReached; if (messages != null) { if (maxId == 0) { database.executeFast("DELETE FROM chat_pinned_v2 WHERE uid = " + dialogId).stepThis().dispose(); } } else { SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT COUNT(mid) FROM chat_pinned_v2 WHERE uid = %d AND mid IN (%s)", dialogId, TextUtils.join(",", ids))); alreadyAdded = cursor.next() ? cursor.intValue(0) : 0; cursor.dispose(); } SQLitePreparedStatement state = database.executeFast("REPLACE INTO chat_pinned_v2 VALUES(?, ?, ?)"); for (int a = 0, N = ids.size(); a < N; a++) { Integer id = ids.get(a); state.requery(); state.bindLong(1, dialogId); state.bindInteger(2, id); MessageObject message = null; if (messages != null) { message = messages.get(id); } NativeByteBuffer data = null; if (message != null) { data = new NativeByteBuffer(message.messageOwner.getObjectSize()); message.messageOwner.serializeToStream(data); state.bindByteBuffer(3, data); } else { state.bindNull(3); } state.step(); if (data != null) { data.reuse(); } } state.dispose(); database.commitTransaction(); SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT COUNT(mid) FROM chat_pinned_v2 WHERE uid = %d", dialogId)); int newCount1 = cursor.next() ? cursor.intValue(0) : 0; cursor.dispose(); int newCount; if (messages != null) { newCount = Math.max(totalCount, newCount1); endReached = end; } else { SQLiteCursor cursor2 = database.queryFinalized(String.format(Locale.US, "SELECT count, end FROM chat_pinned_count WHERE uid = %d", dialogId)); int newCount2; if (cursor2.next()) { newCount2 = cursor2.intValue(0); endReached = cursor2.intValue(1) != 0; } else { newCount2 = 0; endReached = false; } cursor2.dispose(); newCount = Math.max(newCount2 + (ids.size() - alreadyAdded), newCount1); } state = database.executeFast("REPLACE INTO chat_pinned_count VALUES(?, ?, ?)"); state.requery(); state.bindLong(1, dialogId); state.bindInteger(2, newCount); state.bindInteger(3, endReached ? 1 : 0); state.step(); state.dispose(); AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.didLoadPinnedMessages, dialogId, ids, true, null, messages, maxId, newCount, endReached)); } else { int newCount; boolean endReached; if (ids == null) { database.executeFast("DELETE FROM chat_pinned_v2 WHERE uid = " + dialogId).stepThis().dispose(); if (dialogId < 0) { database.executeFast(String.format(Locale.US, "UPDATE chat_settings_v2 SET pinned = 0 WHERE uid = %d", -dialogId)).stepThis().dispose(); } else { database.executeFast(String.format(Locale.US, "UPDATE user_settings SET pinned = 0 WHERE uid = %d", dialogId)).stepThis().dispose(); } newCount = 0; endReached = true; } else { String idsStr = TextUtils.join(",", ids); if (dialogId < 0) { database.executeFast(String.format(Locale.US, "UPDATE chat_settings_v2 SET pinned = 0 WHERE uid = %d AND pinned IN (%s)", -dialogId, idsStr)).stepThis().dispose(); } else { database.executeFast(String.format(Locale.US, "UPDATE user_settings SET pinned = 0 WHERE uid = %d AND pinned IN (%s)", dialogId, idsStr)).stepThis().dispose(); } database.executeFast(String.format(Locale.US, "DELETE FROM chat_pinned_v2 WHERE uid = %d AND mid IN(%s)", dialogId, idsStr)).stepThis().dispose(); SQLiteCursor cursor = database.queryFinalized("SELECT changes()"); int updatedCount = cursor.next() ? cursor.intValue(0) : 0; cursor.dispose(); cursor = database.queryFinalized(String.format(Locale.US, "SELECT COUNT(mid) FROM chat_pinned_v2 WHERE uid = %d", dialogId)); int newCount1 = cursor.next() ? cursor.intValue(0) : 0; cursor.dispose(); cursor = database.queryFinalized(String.format(Locale.US, "SELECT count, end FROM chat_pinned_count WHERE uid = %d", dialogId)); int newCount2; if (cursor.next()) { newCount2 = Math.max(0, cursor.intValue(0) - updatedCount); endReached = cursor.intValue(1) != 0; } else { newCount2 = 0; endReached = false; } cursor.dispose(); newCount = Math.max(newCount1, newCount2); } SQLitePreparedStatement state = database.executeFast("REPLACE INTO chat_pinned_count VALUES(?, ?, ?)"); state.requery(); state.bindLong(1, dialogId); state.bindInteger(2, newCount); state.bindInteger(3, endReached ? 1 : 0); state.step(); state.dispose(); AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.didLoadPinnedMessages, dialogId, ids, false, null, messages, maxId, newCount, endReached)); } } catch (Exception e) { FileLog.e(e); } }); } public void updateChatInfo(final int chat_id, final int user_id, final int what, final int invited_id, final int version) { storageQueue.postRunnable(() -> { try { SQLiteCursor cursor = database.queryFinalized("SELECT info, pinned, online, inviter FROM chat_settings_v2 WHERE uid = " + chat_id); TLRPC.ChatFull info = null; ArrayList loadedUsers = new ArrayList<>(); if (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { info = TLRPC.ChatFull.TLdeserialize(data, data.readInt32(false), false); data.reuse(); info.pinned_msg_id = cursor.intValue(1); info.online_count = cursor.intValue(2); info.inviterId = cursor.intValue(3); } } cursor.dispose(); if (info instanceof TLRPC.TL_chatFull) { if (what == 1) { for (int a = 0; a < info.participants.participants.size(); a++) { TLRPC.ChatParticipant participant = info.participants.participants.get(a); if (participant.user_id == user_id) { info.participants.participants.remove(a); break; } } } else if (what == 0) { for (TLRPC.ChatParticipant part : info.participants.participants) { if (part.user_id == user_id) { return; } } TLRPC.TL_chatParticipant participant = new TLRPC.TL_chatParticipant(); participant.user_id = user_id; participant.inviter_id = invited_id; participant.date = getConnectionsManager().getCurrentTime(); info.participants.participants.add(participant); } else if (what == 2) { for (int a = 0; a < info.participants.participants.size(); a++) { TLRPC.ChatParticipant participant = info.participants.participants.get(a); if (participant.user_id == user_id) { TLRPC.ChatParticipant newParticipant; if (invited_id == 1) { newParticipant = new TLRPC.TL_chatParticipantAdmin(); } else { newParticipant = new TLRPC.TL_chatParticipant(); } newParticipant.user_id = participant.user_id; newParticipant.date = participant.date; newParticipant.inviter_id = participant.inviter_id; info.participants.participants.set(a, newParticipant); break; } } } info.participants.version = version; final TLRPC.ChatFull finalInfo = info; AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.chatInfoDidLoad, finalInfo, 0, false, false)); SQLitePreparedStatement state = database.executeFast("REPLACE INTO chat_settings_v2 VALUES(?, ?, ?, ?, ?, ?)"); NativeByteBuffer data = new NativeByteBuffer(info.getObjectSize()); info.serializeToStream(data); state.bindInteger(1, chat_id); state.bindByteBuffer(2, data); state.bindInteger(3, info.pinned_msg_id); state.bindInteger(4, info.online_count); state.bindInteger(5, info.inviterId); state.bindInteger(6, info.invitesCount); state.step(); state.dispose(); data.reuse(); } } catch (Exception e) { FileLog.e(e); } }); } public boolean isMigratedChat(final int chat_id) { final CountDownLatch countDownLatch = new CountDownLatch(1); final boolean[] result = new boolean[1]; storageQueue.postRunnable(() -> { try { SQLiteCursor cursor = database.queryFinalized("SELECT info FROM chat_settings_v2 WHERE uid = " + chat_id); TLRPC.ChatFull info = null; ArrayList loadedUsers = new ArrayList<>(); if (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { info = TLRPC.ChatFull.TLdeserialize(data, data.readInt32(false), false); data.reuse(); } } cursor.dispose(); result[0] = info instanceof TLRPC.TL_channelFull && info.migrated_from_chat_id != 0; countDownLatch.countDown(); } catch (Exception e) { FileLog.e(e); } finally { countDownLatch.countDown(); } }); try { countDownLatch.await(); } catch (Exception e) { FileLog.e(e); } return result[0]; } public boolean hasInviteMeMessage(final int chat_id) { final CountDownLatch countDownLatch = new CountDownLatch(1); final boolean[] result = new boolean[1]; storageQueue.postRunnable(() -> { try { int selfId = getUserConfig().getClientUserId(); SQLiteCursor cursor = database.queryFinalized("SELECT data FROM messages WHERE uid = " + -chat_id + " AND out = 0 ORDER BY mid DESC LIMIT 100"); while (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); data.reuse(); if (message.action instanceof TLRPC.TL_messageActionChatAddUser && message.action.users.contains(selfId)) { result[0] = true; break; } } } cursor.dispose(); } catch (Exception e) { FileLog.e(e); } finally { countDownLatch.countDown(); } }); try { countDownLatch.await(); } catch (Exception e) { FileLog.e(e); } return result[0]; } private TLRPC.ChatFull loadChatInfoInternal(final int chatId, boolean isChannel, final boolean force, final boolean byChannelUsers, int fromMessageId) { TLRPC.ChatFull info = null; ArrayList loadedUsers = new ArrayList<>(); HashMap pinnedMessagesMap = new HashMap<>(); ArrayList pinnedMessages = new ArrayList<>(); int totalPinnedCount = 0; boolean pinnedEndReached = false; try { SQLiteCursor cursor = database.queryFinalized("SELECT info, pinned, online, inviter, links FROM chat_settings_v2 WHERE uid = " + chatId); if (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { info = TLRPC.ChatFull.TLdeserialize(data, data.readInt32(false), false); data.reuse(); info.pinned_msg_id = cursor.intValue(1); info.online_count = cursor.intValue(2); info.inviterId = cursor.intValue(3); info.invitesCount = cursor.intValue(4); } } cursor.dispose(); if (info instanceof TLRPC.TL_chatFull) { StringBuilder usersToLoad = new StringBuilder(); for (int a = 0; a < info.participants.participants.size(); a++) { TLRPC.ChatParticipant c = info.participants.participants.get(a); if (usersToLoad.length() != 0) { usersToLoad.append(","); } usersToLoad.append(c.user_id); } if (usersToLoad.length() != 0) { getUsersInternal(usersToLoad.toString(), loadedUsers); } } else if (info instanceof TLRPC.TL_channelFull) { cursor = database.queryFinalized("SELECT us.data, us.status, cu.data, cu.date FROM channel_users_v2 as cu LEFT JOIN users as us ON us.uid = cu.uid WHERE cu.did = " + (-chatId) + " ORDER BY cu.date DESC"); info.participants = new TLRPC.TL_chatParticipants(); while (cursor.next()) { try { TLRPC.User user = null; TLRPC.ChannelParticipant participant = null; NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { user = TLRPC.User.TLdeserialize(data, data.readInt32(false), false); data.reuse(); } data = cursor.byteBufferValue(2); if (data != null) { participant = TLRPC.ChannelParticipant.TLdeserialize(data, data.readInt32(false), false); data.reuse(); } if (user != null && participant != null) { if (user.status != null) { user.status.expires = cursor.intValue(1); } loadedUsers.add(user); participant.date = cursor.intValue(3); TLRPC.TL_chatChannelParticipant chatChannelParticipant = new TLRPC.TL_chatChannelParticipant(); chatChannelParticipant.user_id = MessageObject.getPeerId(participant.peer); chatChannelParticipant.date = participant.date; chatChannelParticipant.inviter_id = participant.inviter_id; chatChannelParticipant.channelParticipant = participant; info.participants.participants.add(chatChannelParticipant); } } catch (Exception e) { FileLog.e(e); } } cursor.dispose(); StringBuilder usersToLoad = new StringBuilder(); for (int a = 0; a < info.bot_info.size(); a++) { TLRPC.BotInfo botInfo = info.bot_info.get(a); if (usersToLoad.length() != 0) { usersToLoad.append(","); } usersToLoad.append(botInfo.user_id); } if (usersToLoad.length() != 0) { getUsersInternal(usersToLoad.toString(), loadedUsers); } } if (info != null && info.inviterId != 0) { getUsersInternal("" + info.inviterId, loadedUsers); } cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT mid FROM chat_pinned_v2 WHERE uid = %d ORDER BY mid DESC", -chatId)); while (cursor.next()) { int id = cursor.intValue(0); pinnedMessages.add(id); pinnedMessagesMap.put(id, null); } cursor.dispose(); cursor = database.queryFinalized("SELECT count, end FROM chat_pinned_count WHERE uid = " + (-chatId)); if (cursor.next()) { totalPinnedCount = cursor.intValue(0); pinnedEndReached = cursor.intValue(1) != 0; } cursor.dispose(); if (info != null && info.pinned_msg_id != 0) { if (pinnedMessages.isEmpty() || info.pinned_msg_id > pinnedMessages.get(0)) { pinnedMessages.clear(); pinnedMessages.add(info.pinned_msg_id); pinnedMessagesMap.put(info.pinned_msg_id, null); } } if (!pinnedMessages.isEmpty()) { ArrayList messageObjects = getMediaDataController().loadPinnedMessages(-chatId, isChannel ? chatId : 0, pinnedMessages, false); if (messageObjects != null) { for (int a = 0, N = messageObjects.size(); a < N; a++) { MessageObject messageObject = messageObjects.get(a); pinnedMessagesMap.put(messageObject.getId(), messageObject); } } } } catch (Exception e) { FileLog.e(e); } finally { getMessagesController().processChatInfo(chatId, info, loadedUsers, true, force, byChannelUsers, pinnedMessages, pinnedMessagesMap, totalPinnedCount, pinnedEndReached); } return info; } public TLRPC.ChatFull loadChatInfo(int chatId, boolean isChannel, CountDownLatch countDownLatch, boolean force, boolean byChannelUsers) { return loadChatInfo(chatId, isChannel, countDownLatch, force, byChannelUsers, 0); } public TLRPC.ChatFull loadChatInfo(int chatId, boolean isChannel, CountDownLatch countDownLatch, boolean force, boolean byChannelUsers, int fromMessageId) { TLRPC.ChatFull[] result = new TLRPC.ChatFull[1]; storageQueue.postRunnable(() -> { result[0] = loadChatInfoInternal(chatId, isChannel, force, byChannelUsers, fromMessageId); if (countDownLatch != null) { countDownLatch.countDown(); } }); if (countDownLatch != null) { try { countDownLatch.await(); } catch (Throwable ignore) { } } return result[0]; } public void processPendingRead(final long dialog_id, final long maxPositiveId, final long maxNegativeId, final boolean isChannel, final int scheduledCount) { final int maxDate = lastSavedDate; storageQueue.postRunnable(() -> { try { long currentMaxId = 0; int unreadCount = 0; long last_mid = 0; int prevUnreadCount = 0; SQLiteCursor cursor = database.queryFinalized("SELECT unread_count, inbox_max, last_mid FROM dialogs WHERE did = " + dialog_id); if (cursor.next()) { prevUnreadCount = unreadCount = cursor.intValue(0); currentMaxId = cursor.intValue(1); last_mid = cursor.longValue(2); } cursor.dispose(); database.beginTransaction(); SQLitePreparedStatement state; int lower_id = (int) dialog_id; if (lower_id != 0) { currentMaxId = Math.max(currentMaxId, (int) maxPositiveId); if (isChannel) { currentMaxId |= ((long) -lower_id) << 32; } state = database.executeFast("UPDATE messages SET read_state = read_state | 1 WHERE uid = ? AND mid <= ? AND read_state IN(0,2) AND out = 0"); state.requery(); state.bindLong(1, dialog_id); state.bindLong(2, currentMaxId); state.step(); state.dispose(); if (currentMaxId >= last_mid) { unreadCount = 0; } else { int updatedCount = 0; cursor = database.queryFinalized("SELECT changes()"); if (cursor.next()) { updatedCount = cursor.intValue(0) + scheduledCount; } cursor.dispose(); unreadCount = Math.max(0, unreadCount - updatedCount); } state = database.executeFast("DELETE FROM unread_push_messages WHERE uid = ? AND mid <= ?"); state.requery(); state.bindLong(1, dialog_id); state.bindLong(2, currentMaxId); state.step(); state.dispose(); state = database.executeFast("DELETE FROM unread_push_messages WHERE uid = ? AND date <= ?"); state.requery(); state.bindLong(1, dialog_id); state.bindLong(2, maxDate); state.step(); state.dispose(); } else { currentMaxId = (int) maxNegativeId; state = database.executeFast("UPDATE messages SET read_state = read_state | 1 WHERE uid = ? AND mid >= ? AND read_state IN(0,2) AND out = 0"); state.requery(); state.bindLong(1, dialog_id); state.bindLong(2, currentMaxId); state.step(); state.dispose(); if (currentMaxId <= last_mid) { unreadCount = 0; } else { int updatedCount = 0; cursor = database.queryFinalized("SELECT changes()"); if (cursor.next()) { updatedCount = cursor.intValue(0) + scheduledCount; } cursor.dispose(); unreadCount = Math.max(0, unreadCount - updatedCount); } } state = database.executeFast("UPDATE dialogs SET unread_count = ?, inbox_max = ? WHERE did = ?"); state.requery(); state.bindInteger(1, unreadCount); state.bindInteger(2, (int) currentMaxId); state.bindLong(3, dialog_id); state.step(); state.dispose(); database.commitTransaction(); if (prevUnreadCount != 0 && unreadCount == 0) { LongSparseArray dialogsToUpdate = new LongSparseArray<>(); dialogsToUpdate.put(dialog_id, unreadCount); updateFiltersReadCounter(dialogsToUpdate, null, true); } updateWidgets(dialog_id); } catch (Exception e) { FileLog.e(e); } }); } public void putContacts(ArrayList contacts, final boolean deleteAll) { if (contacts.isEmpty() && !deleteAll) { return; } final ArrayList contactsCopy = new ArrayList<>(contacts); storageQueue.postRunnable(() -> { try { if (deleteAll) { database.executeFast("DELETE FROM contacts WHERE 1").stepThis().dispose(); } database.beginTransaction(); SQLitePreparedStatement state = database.executeFast("REPLACE INTO contacts VALUES(?, ?)"); for (int a = 0; a < contactsCopy.size(); a++) { TLRPC.TL_contact contact = contactsCopy.get(a); state.requery(); state.bindInteger(1, contact.user_id); state.bindInteger(2, contact.mutual ? 1 : 0); state.step(); } state.dispose(); database.commitTransaction(); } catch (Exception e) { FileLog.e(e); } }); } public void deleteContacts(final ArrayList uids) { if (uids == null || uids.isEmpty()) { return; } storageQueue.postRunnable(() -> { try { String ids = TextUtils.join(",", uids); database.executeFast("DELETE FROM contacts WHERE uid IN(" + ids + ")").stepThis().dispose(); } catch (Exception e) { FileLog.e(e); } }); } public void applyPhoneBookUpdates(final String adds, final String deletes) { if (TextUtils.isEmpty(adds)) { return; } storageQueue.postRunnable(() -> { try { if (adds.length() != 0) { database.executeFast(String.format(Locale.US, "UPDATE user_phones_v7 SET deleted = 0 WHERE sphone IN(%s)", adds)).stepThis().dispose(); } if (deletes.length() != 0) { database.executeFast(String.format(Locale.US, "UPDATE user_phones_v7 SET deleted = 1 WHERE sphone IN(%s)", deletes)).stepThis().dispose(); } } catch (Exception e) { FileLog.e(e); } }); } public void putCachedPhoneBook(final HashMap contactHashMap, final boolean migrate, final boolean delete) { if (contactHashMap == null || contactHashMap.isEmpty() && !migrate && !delete) { return; } storageQueue.postRunnable(() -> { try { if (BuildVars.LOGS_ENABLED) { FileLog.d(currentAccount + " save contacts to db " + contactHashMap.size()); } database.executeFast("DELETE FROM user_contacts_v7 WHERE 1").stepThis().dispose(); database.executeFast("DELETE FROM user_phones_v7 WHERE 1").stepThis().dispose(); database.beginTransaction(); SQLitePreparedStatement state = database.executeFast("REPLACE INTO user_contacts_v7 VALUES(?, ?, ?, ?, ?)"); SQLitePreparedStatement state2 = database.executeFast("REPLACE INTO user_phones_v7 VALUES(?, ?, ?, ?)"); for (HashMap.Entry entry : contactHashMap.entrySet()) { ContactsController.Contact contact = entry.getValue(); if (contact.phones.isEmpty() || contact.shortPhones.isEmpty()) { continue; } state.requery(); state.bindString(1, contact.key); state.bindInteger(2, contact.contact_id); state.bindString(3, contact.first_name); state.bindString(4, contact.last_name); state.bindInteger(5, contact.imported); state.step(); for (int a = 0; a < contact.phones.size(); a++) { state2.requery(); state2.bindString(1, contact.key); state2.bindString(2, contact.phones.get(a)); state2.bindString(3, contact.shortPhones.get(a)); state2.bindInteger(4, contact.phoneDeleted.get(a)); state2.step(); } } state.dispose(); state2.dispose(); database.commitTransaction(); if (migrate) { database.executeFast("DROP TABLE IF EXISTS user_contacts_v6;").stepThis().dispose(); database.executeFast("DROP TABLE IF EXISTS user_phones_v6;").stepThis().dispose(); getCachedPhoneBook(false); } } catch (Exception e) { FileLog.e(e); } }); } public void getCachedPhoneBook(final boolean byError) { storageQueue.postRunnable(() -> { SQLiteCursor cursor = null; try { cursor = database.queryFinalized("SELECT name FROM sqlite_master WHERE type='table' AND name='user_contacts_v6'"); boolean migrate = cursor.next(); cursor.dispose(); cursor = null; if (migrate) { int count = 16; cursor = database.queryFinalized("SELECT COUNT(uid) FROM user_contacts_v6 WHERE 1"); if (cursor.next()) { count = Math.min(5000, cursor.intValue(0)); } cursor.dispose(); SparseArray contactHashMap = new SparseArray<>(count); cursor = database.queryFinalized("SELECT us.uid, us.fname, us.sname, up.phone, up.sphone, up.deleted, us.imported FROM user_contacts_v6 as us LEFT JOIN user_phones_v6 as up ON us.uid = up.uid WHERE 1"); while (cursor.next()) { int uid = cursor.intValue(0); ContactsController.Contact contact = contactHashMap.get(uid); if (contact == null) { contact = new ContactsController.Contact(); contact.first_name = cursor.stringValue(1); contact.last_name = cursor.stringValue(2); contact.imported = cursor.intValue(6); if (contact.first_name == null) { contact.first_name = ""; } if (contact.last_name == null) { contact.last_name = ""; } contact.contact_id = uid; contactHashMap.put(uid, contact); } String phone = cursor.stringValue(3); if (phone == null) { continue; } contact.phones.add(phone); String sphone = cursor.stringValue(4); if (sphone == null) { continue; } if (sphone.length() == 8 && phone.length() != 8) { sphone = PhoneFormat.stripExceptNumbers(phone); } contact.shortPhones.add(sphone); contact.phoneDeleted.add(cursor.intValue(5)); contact.phoneTypes.add(""); if (contactHashMap.size() == 5000) { break; } } cursor.dispose(); cursor = null; getContactsController().migratePhoneBookToV7(contactHashMap); return; } } catch (Throwable e) { FileLog.e(e); } finally { if (cursor != null) { cursor.dispose(); } } int count = 16; int currentContactsCount = 0; int start = 0; try { cursor = database.queryFinalized("SELECT COUNT(key) FROM user_contacts_v7 WHERE 1"); if (cursor.next()) { currentContactsCount = cursor.intValue(0); count = Math.min(5000, currentContactsCount); if (currentContactsCount > 5000) { start = currentContactsCount - 5000; } if (BuildVars.LOGS_ENABLED) { FileLog.d(currentAccount + " current cached contacts count = " + currentContactsCount); } } } catch (Throwable e) { FileLog.e(e); } finally { if (cursor != null) { cursor.dispose(); } } HashMap contactHashMap = new HashMap<>(count); try { if (start != 0) { cursor = database.queryFinalized("SELECT us.key, us.uid, us.fname, us.sname, up.phone, up.sphone, up.deleted, us.imported FROM user_contacts_v7 as us LEFT JOIN user_phones_v7 as up ON us.key = up.key WHERE 1 LIMIT " + 0 + "," + currentContactsCount); } else { cursor = database.queryFinalized("SELECT us.key, us.uid, us.fname, us.sname, up.phone, up.sphone, up.deleted, us.imported FROM user_contacts_v7 as us LEFT JOIN user_phones_v7 as up ON us.key = up.key WHERE 1"); } while (cursor.next()) { String key = cursor.stringValue(0); ContactsController.Contact contact = contactHashMap.get(key); if (contact == null) { contact = new ContactsController.Contact(); contact.contact_id = cursor.intValue(1); contact.first_name = cursor.stringValue(2); contact.last_name = cursor.stringValue(3); contact.imported = cursor.intValue(7); if (contact.first_name == null) { contact.first_name = ""; } if (contact.last_name == null) { contact.last_name = ""; } contactHashMap.put(key, contact); } String phone = cursor.stringValue(4); if (phone == null) { continue; } contact.phones.add(phone); String sphone = cursor.stringValue(5); if (sphone == null) { continue; } if (sphone.length() == 8 && phone.length() != 8) { sphone = PhoneFormat.stripExceptNumbers(phone); } contact.shortPhones.add(sphone); contact.phoneDeleted.add(cursor.intValue(6)); contact.phoneTypes.add(""); if (contactHashMap.size() == 5000) { break; } } cursor.dispose(); cursor = null; } catch (Exception e) { contactHashMap.clear(); FileLog.e(e); } finally { if (cursor != null) { cursor.dispose(); } } getContactsController().performSyncPhoneBook(contactHashMap, true, true, false, false, !byError, false); }); } public void getContacts() { storageQueue.postRunnable(() -> { ArrayList contacts = new ArrayList<>(); ArrayList users = new ArrayList<>(); try { SQLiteCursor cursor = database.queryFinalized("SELECT * FROM contacts WHERE 1"); StringBuilder uids = new StringBuilder(); while (cursor.next()) { int user_id = cursor.intValue(0); TLRPC.TL_contact contact = new TLRPC.TL_contact(); contact.user_id = user_id; contact.mutual = cursor.intValue(1) == 1; if (uids.length() != 0) { uids.append(","); } contacts.add(contact); uids.append(contact.user_id); } cursor.dispose(); if (uids.length() != 0) { getUsersInternal(uids.toString(), users); } } catch (Exception e) { contacts.clear(); users.clear(); FileLog.e(e); } getContactsController().processLoadedContacts(contacts, users, 1); }); } public void getUnsentMessages(final int count) { storageQueue.postRunnable(() -> { try { SparseArray messageHashMap = new SparseArray<>(); ArrayList messages = new ArrayList<>(); ArrayList scheduledMessages = new ArrayList<>(); ArrayList users = new ArrayList<>(); ArrayList chats = new ArrayList<>(); ArrayList encryptedChats = new ArrayList<>(); ArrayList usersToLoad = new ArrayList<>(); ArrayList chatsToLoad = new ArrayList<>(); ArrayList encryptedChatIds = new ArrayList<>(); SQLiteCursor cursor = database.queryFinalized("SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.uid, s.seq_in, s.seq_out, m.ttl FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid LEFT JOIN messages_seq as s ON m.mid = s.mid WHERE (m.mid < 0 AND m.send_state = 1) OR (m.mid > 0 AND m.send_state = 3) ORDER BY m.mid DESC LIMIT " + count); while (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(1); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); message.send_state = cursor.intValue(2); message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); if (messageHashMap.indexOfKey(message.id) < 0) { MessageObject.setUnreadFlags(message, cursor.intValue(0)); message.id = cursor.intValue(3); message.date = cursor.intValue(4); if (!cursor.isNull(5)) { message.random_id = cursor.longValue(5); } message.dialog_id = cursor.longValue(6); message.seq_in = cursor.intValue(7); message.seq_out = cursor.intValue(8); message.ttl = cursor.intValue(9); messages.add(message); messageHashMap.put(message.id, message); int lower_id = (int) message.dialog_id; int high_id = (int) (message.dialog_id >> 32); if (lower_id != 0) { if (lower_id < 0) { if (!chatsToLoad.contains(-lower_id)) { chatsToLoad.add(-lower_id); } } else { if (!usersToLoad.contains(lower_id)) { usersToLoad.add(lower_id); } } } else { if (!encryptedChatIds.contains(high_id)) { encryptedChatIds.add(high_id); } } addUsersAndChatsFromMessage(message, usersToLoad, chatsToLoad); if (message.send_state != 3 && (message.peer_id.channel_id == 0 && !MessageObject.isUnread(message) && lower_id != 0 || message.id > 0)) { message.send_state = 0; } } } } cursor.dispose(); cursor = database.queryFinalized("SELECT m.data, m.send_state, m.mid, m.date, r.random_id, m.uid, m.ttl FROM scheduled_messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE (m.mid < 0 AND m.send_state = 1) OR (m.mid > 0 AND m.send_state = 3) ORDER BY date ASC"); while (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); message.send_state = cursor.intValue(1); message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); if (messageHashMap.indexOfKey(message.id) < 0) { message.id = cursor.intValue(2); message.date = cursor.intValue(3); if (!cursor.isNull(4)) { message.random_id = cursor.longValue(4); } message.dialog_id = cursor.longValue(5); message.ttl = cursor.intValue(6); scheduledMessages.add(message); messageHashMap.put(message.id, message); int lower_id = (int) message.dialog_id; int high_id = (int) (message.dialog_id >> 32); if (lower_id != 0) { if (lower_id < 0) { if (!chatsToLoad.contains(-lower_id)) { chatsToLoad.add(-lower_id); } } else { if (!usersToLoad.contains(lower_id)) { usersToLoad.add(lower_id); } } } else { if (!encryptedChatIds.contains(high_id)) { encryptedChatIds.add(high_id); } } addUsersAndChatsFromMessage(message, usersToLoad, chatsToLoad); if (message.send_state != 3 && (message.peer_id.channel_id == 0 && !MessageObject.isUnread(message) && lower_id != 0 || message.id > 0)) { message.send_state = 0; } } } } cursor.dispose(); if (!encryptedChatIds.isEmpty()) { getEncryptedChatsInternal(TextUtils.join(",", encryptedChatIds), encryptedChats, usersToLoad); } if (!usersToLoad.isEmpty()) { getUsersInternal(TextUtils.join(",", usersToLoad), users); } if (!chatsToLoad.isEmpty()) { StringBuilder stringToLoad = new StringBuilder(); for (int a = 0; a < chatsToLoad.size(); a++) { Integer cid = chatsToLoad.get(a); if (stringToLoad.length() != 0) { stringToLoad.append(","); } stringToLoad.append(cid); } getChatsInternal(stringToLoad.toString(), chats); } getSendMessagesHelper().processUnsentMessages(messages, scheduledMessages, users, chats, encryptedChats); } catch (Exception e) { FileLog.e(e); } }); } public boolean checkMessageByRandomId(final long random_id) { final boolean[] result = new boolean[1]; final CountDownLatch countDownLatch = new CountDownLatch(1); storageQueue.postRunnable(() -> { SQLiteCursor cursor = null; try { cursor = database.queryFinalized(String.format(Locale.US, "SELECT random_id FROM randoms WHERE random_id = %d", random_id)); if (cursor.next()) { result[0] = true; } } catch (Exception e) { FileLog.e(e); } finally { if (cursor != null) { cursor.dispose(); } } countDownLatch.countDown(); }); try { countDownLatch.await(); } catch (Exception e) { FileLog.e(e); } return result[0]; } public boolean checkMessageId(final long dialog_id, boolean isChannel, final int mid) { final boolean[] result = new boolean[1]; final CountDownLatch countDownLatch = new CountDownLatch(1); storageQueue.postRunnable(() -> { SQLiteCursor cursor = null; try { long messageId = mid; if (isChannel) { messageId |= ((-dialog_id) << 32); } cursor = database.queryFinalized(String.format(Locale.US, "SELECT mid FROM messages WHERE uid = %d AND mid = %d", dialog_id, messageId)); if (cursor.next()) { result[0] = true; } } catch (Exception e) { FileLog.e(e); } finally { if (cursor != null) { cursor.dispose(); } } countDownLatch.countDown(); }); try { countDownLatch.await(); } catch (Exception e) { FileLog.e(e); } return result[0]; } public void getUnreadMention(final long dialog_id, final IntCallback callback) { storageQueue.postRunnable(() -> { try { final int result; SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT MIN(mid) FROM messages WHERE uid = %d AND mention = 1 AND read_state IN(0, 1)", dialog_id)); if (cursor.next()) { result = cursor.intValue(0); } else { result = 0; } cursor.dispose(); AndroidUtilities.runOnUIThread(() -> callback.run(result)); } catch (Exception e) { FileLog.e(e); } }); } public void getMessagesCount(final long dialog_id, final IntCallback callback) { storageQueue.postRunnable(() -> { try { final int result; SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT COUNT(mid) FROM messages WHERE uid = %d", dialog_id)); if (cursor.next()) { result = cursor.intValue(0); } else { result = 0; } cursor.dispose(); AndroidUtilities.runOnUIThread(() -> callback.run(result)); } catch (Exception e) { FileLog.e(e); } }); } public Runnable getMessagesInternal(long dialogId, long mergeDialogId, int count, int max_id, int offset_date, int minDate, int classGuid, int load_type, boolean isChannel, boolean scheduled, int replyMessageId, int loadIndex, boolean processMessages) { TLRPC.TL_messages_messages res = new TLRPC.TL_messages_messages(); int currentUserId = getUserConfig().clientUserId; int count_unread = 0; int mentions_unread = 0; int count_query = count; int offset_query = 0; int min_unread_id = 0; int last_message_id = 0; boolean queryFromServer = false; int max_unread_date = 0; long messageMaxId = max_id; int max_id_query = max_id; boolean unreadCountIsLocal = false; int max_id_override = max_id; int channelId = 0; if (isChannel) { channelId = -(int) dialogId; } if (messageMaxId != 0 && channelId != 0) { messageMaxId |= ((long) channelId) << 32; } boolean isEnd = false; int num = dialogId == 777000 ? 10 : 1; int messagesCount = 0; long startLoadTime = SystemClock.elapsedRealtime(); try { ArrayList usersToLoad = new ArrayList<>(); ArrayList chatsToLoad = new ArrayList<>(); ArrayList replyMessages = new ArrayList<>(); SparseArray> replyMessageOwners = new SparseArray<>(); LongSparseArray> replyMessageRandomOwners = new LongSparseArray<>(); SQLiteCursor cursor; final String messageSelect = "SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl, m.mention, m.imp, m.forwards, m.replies_data FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid"; if (scheduled) { isEnd = true; cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.ttl FROM scheduled_messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d ORDER BY m.date DESC", dialogId)); while (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); message.send_state = cursor.intValue(1); message.id = cursor.intValue(2); if (message.id > 0 && message.send_state != 0 && message.send_state != 3) { message.send_state = 0; } if (dialogId == currentUserId) { message.out = true; message.unread = false; } else { message.unread = true; } message.readAttachPath(data, currentUserId); data.reuse(); message.date = cursor.intValue(3); message.dialog_id = dialogId; if (message.ttl == 0) { message.ttl = cursor.intValue(6); } res.messages.add(message); addUsersAndChatsFromMessage(message, usersToLoad, chatsToLoad); if (message.reply_to != null && (message.reply_to.reply_to_msg_id != 0 || message.reply_to.reply_to_random_id != 0)) { if (!cursor.isNull(5)) { data = cursor.byteBufferValue(5); if (data != null) { message.replyMessage = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); message.replyMessage.readAttachPath(data, currentUserId); data.reuse(); if (message.replyMessage != null) { addUsersAndChatsFromMessage(message.replyMessage, usersToLoad, chatsToLoad); } } } if (message.replyMessage == null) { if (message.reply_to.reply_to_msg_id != 0) { long messageId = message.reply_to.reply_to_msg_id; if (message.reply_to.reply_to_peer_id != null) { if (message.reply_to.reply_to_peer_id.channel_id != 0) { messageId |= ((long) message.reply_to.reply_to_peer_id.channel_id) << 32; } } else if (message.peer_id.channel_id != 0) { messageId |= ((long) message.peer_id.channel_id) << 32; } if (!replyMessages.contains(messageId)) { replyMessages.add(messageId); } ArrayList messages = replyMessageOwners.get(message.reply_to.reply_to_msg_id); if (messages == null) { messages = new ArrayList<>(); replyMessageOwners.put(message.reply_to.reply_to_msg_id, messages); } messages.add(message); } else { if (!replyMessages.contains(message.reply_to.reply_to_random_id)) { replyMessages.add(message.reply_to.reply_to_random_id); } ArrayList messages = replyMessageRandomOwners.get(message.reply_to.reply_to_random_id); if (messages == null) { messages = new ArrayList<>(); replyMessageRandomOwners.put(message.reply_to.reply_to_random_id, messages); } messages.add(message); } } } } } cursor.dispose(); } else { int lower_id = (int) dialogId; if (lower_id != 0) { if (load_type == 3 && minDate == 0) { cursor = database.queryFinalized("SELECT inbox_max, unread_count, date, unread_count_i FROM dialogs WHERE did = " + dialogId); if (cursor.next()) { min_unread_id = Math.max(1, cursor.intValue(0)) + 1; count_unread = cursor.intValue(1); max_unread_date = cursor.intValue(2); mentions_unread = cursor.intValue(3); } cursor.dispose(); } else if (load_type != 1 && load_type != 3 && load_type != 4 && minDate == 0) { if (load_type == 2) { cursor = database.queryFinalized("SELECT inbox_max, unread_count, date, unread_count_i FROM dialogs WHERE did = " + dialogId); if (cursor.next()) { messageMaxId = max_id_query = min_unread_id = Math.max(1, cursor.intValue(0)); count_unread = cursor.intValue(1); max_unread_date = cursor.intValue(2); mentions_unread = cursor.intValue(3); queryFromServer = true; if (messageMaxId != 0 && channelId != 0) { messageMaxId |= ((long) channelId) << 32; } if (dialogId == currentUserId) { count_unread = 0; } } cursor.dispose(); if (!queryFromServer) { cursor = database.queryFinalized(String.format(Locale.US, "SELECT min(mid), max(date) FROM messages WHERE uid = %d AND out = 0 AND read_state IN(0,2) AND mid > 0", dialogId)); if (cursor.next()) { min_unread_id = cursor.intValue(0); max_unread_date = cursor.intValue(1); } cursor.dispose(); if (min_unread_id != 0) { cursor = database.queryFinalized(String.format(Locale.US, "SELECT COUNT(*) FROM messages WHERE uid = %d AND mid >= %d AND out = 0 AND read_state IN(0,2)", dialogId, min_unread_id)); if (cursor.next()) { count_unread = cursor.intValue(0); } cursor.dispose(); } } else if (max_id_query == 0) { int existingUnreadCount = 0; cursor = database.queryFinalized(String.format(Locale.US, "SELECT COUNT(*) FROM messages WHERE uid = %d AND mid > 0 AND out = 0 AND read_state IN(0,2)", dialogId)); if (cursor.next()) { existingUnreadCount = cursor.intValue(0); } cursor.dispose(); if (existingUnreadCount == count_unread) { cursor = database.queryFinalized(String.format(Locale.US, "SELECT min(mid) FROM messages WHERE uid = %d AND out = 0 AND read_state IN(0,2) AND mid > 0", dialogId)); if (cursor.next()) { messageMaxId = max_id_query = min_unread_id = cursor.intValue(0); if (messageMaxId != 0 && channelId != 0) { messageMaxId |= ((long) channelId) << 32; } } cursor.dispose(); } } else { cursor = database.queryFinalized(String.format(Locale.US, "SELECT start, end FROM messages_holes WHERE uid = %d AND start < %d AND end > %d", dialogId, max_id_query, max_id_query)); boolean containMessage = !cursor.next(); cursor.dispose(); if (containMessage) { cursor = database.queryFinalized(String.format(Locale.US, "SELECT min(mid) FROM messages WHERE uid = %d AND out = 0 AND read_state IN(0,2) AND mid > %d", dialogId, max_id_query)); if (cursor.next()) { messageMaxId = max_id_query = cursor.intValue(0); if (messageMaxId != 0 && channelId != 0) { messageMaxId |= ((long) channelId) << 32; } } cursor.dispose(); } } } if (count_query > count_unread || count_unread < num) { count_query = Math.max(count_query, count_unread + 10); if (count_unread < num) { count_unread = 0; min_unread_id = 0; messageMaxId = 0; last_message_id = 0; queryFromServer = false; } } else { offset_query = count_unread - count_query; count_query += 10; } } cursor = database.queryFinalized(String.format(Locale.US, "SELECT start FROM messages_holes WHERE uid = %d AND start IN (0, 1)", dialogId)); if (cursor.next()) { isEnd = cursor.intValue(0) == 1; } else { cursor.dispose(); cursor = database.queryFinalized(String.format(Locale.US, "SELECT min(mid) FROM messages WHERE uid = %d AND mid > 0", dialogId)); if (cursor.next()) { int mid = cursor.intValue(0); if (mid != 0) { SQLitePreparedStatement state = database.executeFast("REPLACE INTO messages_holes VALUES(?, ?, ?)"); state.requery(); state.bindLong(1, dialogId); state.bindInteger(2, 0); state.bindInteger(3, mid); state.step(); state.dispose(); } } } cursor.dispose(); if (load_type == 3 || load_type == 4 || queryFromServer && load_type == 2) { cursor = database.queryFinalized(String.format(Locale.US, "SELECT max(mid) FROM messages WHERE uid = %d AND mid > 0", dialogId)); if (cursor.next()) { last_message_id = cursor.intValue(0); } cursor.dispose(); if (load_type == 4 && offset_date != 0) { int startMid; int endMid; cursor = database.queryFinalized(String.format(Locale.US, "SELECT max(mid) FROM messages WHERE uid = %d AND date <= %d AND mid > 0", dialogId, offset_date)); if (cursor.next()) { startMid = cursor.intValue(0); } else { startMid = -1; } cursor.dispose(); cursor = database.queryFinalized(String.format(Locale.US, "SELECT min(mid) FROM messages WHERE uid = %d AND date >= %d AND mid > 0", dialogId, offset_date)); if (cursor.next()) { endMid = cursor.intValue(0); } else { endMid = -1; } cursor.dispose(); if (startMid != -1 && endMid != -1) { if (startMid == endMid) { max_id_query = startMid; } else { cursor = database.queryFinalized(String.format(Locale.US, "SELECT start FROM messages_holes WHERE uid = %d AND start <= %d AND end > %d", dialogId, startMid, startMid)); if (cursor.next()) { startMid = -1; } cursor.dispose(); if (startMid != -1) { cursor = database.queryFinalized(String.format(Locale.US, "SELECT start FROM messages_holes WHERE uid = %d AND start <= %d AND end > %d", dialogId, endMid, endMid)); if (cursor.next()) { endMid = -1; } cursor.dispose(); if (endMid != -1) { max_id_override = endMid; messageMaxId = max_id_query = endMid; if (messageMaxId != 0 && channelId != 0) { messageMaxId |= ((long) channelId) << 32; } } } } } } boolean containMessage = max_id_query != 0; if (containMessage) { cursor = database.queryFinalized(String.format(Locale.US, "SELECT start FROM messages_holes WHERE uid = %d AND start < %d AND end > %d", dialogId, max_id_query, max_id_query)); if (cursor.next()) { containMessage = false; } cursor.dispose(); } if (containMessage) { long holeMessageMaxId = 0; long holeMessageMinId = 1; cursor = database.queryFinalized(String.format(Locale.US, "SELECT start FROM messages_holes WHERE uid = %d AND start >= %d ORDER BY start ASC LIMIT 1", dialogId, max_id_query)); if (cursor.next()) { holeMessageMaxId = cursor.intValue(0); if (channelId != 0) { holeMessageMaxId |= ((long) channelId) << 32; } } cursor.dispose(); cursor = database.queryFinalized(String.format(Locale.US, "SELECT end FROM messages_holes WHERE uid = %d AND end <= %d ORDER BY end DESC LIMIT 1", dialogId, max_id_query)); if (cursor.next()) { holeMessageMinId = cursor.intValue(0); if (channelId != 0) { holeMessageMinId |= ((long) channelId) << 32; } } cursor.dispose(); if (holeMessageMaxId != 0 || holeMessageMinId != 1) { if (holeMessageMaxId == 0) { holeMessageMaxId = 1000000000; if (channelId != 0) { holeMessageMaxId |= ((long) channelId) << 32; } } cursor = database.queryFinalized(String.format(Locale.US, "SELECT * FROM (" + messageSelect + " WHERE m.uid = %d AND m.mid <= %d AND (m.mid >= %d OR m.mid < 0) ORDER BY m.date DESC, m.mid DESC LIMIT %d) UNION " + "SELECT * FROM (" + messageSelect + " WHERE m.uid = %d AND m.mid > %d AND (m.mid <= %d OR m.mid < 0) ORDER BY m.date ASC, m.mid ASC LIMIT %d)", dialogId, messageMaxId, holeMessageMinId, count_query / 2, dialogId, messageMaxId, holeMessageMaxId, count_query / 2)); } else { cursor = database.queryFinalized(String.format(Locale.US, "SELECT * FROM (" + messageSelect + " WHERE m.uid = %d AND m.mid <= %d ORDER BY m.date DESC, m.mid DESC LIMIT %d) UNION " + "SELECT * FROM (" + messageSelect + " WHERE m.uid = %d AND m.mid > %d ORDER BY m.date ASC, m.mid ASC LIMIT %d)", dialogId, messageMaxId, count_query / 2, dialogId, messageMaxId, count_query / 2)); } } else { if (load_type == 2) { int existingUnreadCount = 0; cursor = database.queryFinalized(String.format(Locale.US, "SELECT COUNT(*) FROM messages WHERE uid = %d AND mid != 0 AND out = 0 AND read_state IN(0,2)", dialogId)); if (cursor.next()) { existingUnreadCount = cursor.intValue(0); } cursor.dispose(); if (existingUnreadCount == count_unread) { unreadCountIsLocal = true; cursor = database.queryFinalized(String.format(Locale.US, "SELECT * FROM (" + messageSelect + " WHERE m.uid = %d AND m.mid <= %d ORDER BY m.date DESC, m.mid DESC LIMIT %d) UNION " + "SELECT * FROM (" + messageSelect + " WHERE m.uid = %d AND m.mid > %d ORDER BY m.date ASC, m.mid ASC LIMIT %d)", dialogId, messageMaxId, count_query / 2, dialogId, messageMaxId, count_query / 2)); } else { cursor = null; } } else { cursor = null; } } } else if (load_type == 1) { long holeMessageId = 0; cursor = database.queryFinalized(String.format(Locale.US, "SELECT start, end FROM messages_holes WHERE uid = %d AND start >= %d AND start != 1 AND end != 1 ORDER BY start ASC LIMIT 1", dialogId, max_id)); if (cursor.next()) { holeMessageId = cursor.intValue(0); if (channelId != 0) { holeMessageId |= ((long) channelId) << 32; } } cursor.dispose(); if (holeMessageId != 0) { cursor = database.queryFinalized(String.format(Locale.US, "" + messageSelect + " WHERE m.uid = %d AND m.date >= %d AND m.mid > %d AND m.mid <= %d ORDER BY m.date ASC, m.mid ASC LIMIT %d", dialogId, minDate, messageMaxId, holeMessageId, count_query)); } else { cursor = database.queryFinalized(String.format(Locale.US, "" + messageSelect + " WHERE m.uid = %d AND m.date >= %d AND m.mid > %d ORDER BY m.date ASC, m.mid ASC LIMIT %d", dialogId, minDate, messageMaxId, count_query)); } } else if (minDate != 0) { if (messageMaxId != 0) { long holeMessageId = 0; cursor = database.queryFinalized(String.format(Locale.US, "SELECT end FROM messages_holes WHERE uid = %d AND end <= %d ORDER BY end DESC LIMIT 1", dialogId, max_id)); if (cursor.next()) { holeMessageId = cursor.intValue(0); if (channelId != 0) { holeMessageId |= ((long) channelId) << 32; } } cursor.dispose(); if (holeMessageId != 0) { cursor = database.queryFinalized(String.format(Locale.US, "" + messageSelect + " WHERE m.uid = %d AND m.date <= %d AND m.mid < %d AND (m.mid >= %d OR m.mid < 0) ORDER BY m.date DESC, m.mid DESC LIMIT %d", dialogId, minDate, messageMaxId, holeMessageId, count_query)); } else { cursor = database.queryFinalized(String.format(Locale.US, "" + messageSelect + " WHERE m.uid = %d AND m.date <= %d AND m.mid < %d ORDER BY m.date DESC, m.mid DESC LIMIT %d", dialogId, minDate, messageMaxId, count_query)); } } else { cursor = database.queryFinalized(String.format(Locale.US, "" + messageSelect + " WHERE m.uid = %d AND m.date <= %d ORDER BY m.date DESC, m.mid DESC LIMIT %d,%d", dialogId, minDate, offset_query, count_query)); } } else { cursor = database.queryFinalized(String.format(Locale.US, "SELECT max(mid) FROM messages WHERE uid = %d AND mid > 0", dialogId)); if (cursor.next()) { last_message_id = cursor.intValue(0); } cursor.dispose(); long holeMessageId = 0; cursor = database.queryFinalized(String.format(Locale.US, "SELECT max(end) FROM messages_holes WHERE uid = %d", dialogId)); if (cursor.next()) { holeMessageId = cursor.intValue(0); if (channelId != 0) { holeMessageId |= ((long) channelId) << 32; } } cursor.dispose(); if (holeMessageId != 0) { cursor = database.queryFinalized(String.format(Locale.US, "" + messageSelect + " WHERE m.uid = %d AND (m.mid >= %d OR m.mid < 0) ORDER BY m.date DESC, m.mid DESC LIMIT %d,%d", dialogId, holeMessageId, offset_query, count_query)); } else { cursor = database.queryFinalized(String.format(Locale.US, "" + messageSelect + " WHERE m.uid = %d ORDER BY m.date DESC, m.mid DESC LIMIT %d,%d", dialogId, offset_query, count_query)); } } } else { isEnd = true; if (load_type == 3 && minDate == 0) { cursor = database.queryFinalized(String.format(Locale.US, "SELECT min(mid) FROM messages WHERE uid = %d AND mid < 0", dialogId)); if (cursor.next()) { min_unread_id = cursor.intValue(0); } cursor.dispose(); int min_unread_id2 = 0; cursor = database.queryFinalized(String.format(Locale.US, "SELECT max(mid), max(date) FROM messages WHERE uid = %d AND out = 0 AND read_state IN(0,2) AND mid < 0", dialogId)); if (cursor.next()) { min_unread_id2 = cursor.intValue(0); max_unread_date = cursor.intValue(1); } cursor.dispose(); if (min_unread_id2 != 0) { min_unread_id = min_unread_id2; cursor = database.queryFinalized(String.format(Locale.US, "SELECT COUNT(*) FROM messages WHERE uid = %d AND mid <= %d AND out = 0 AND read_state IN(0,2)", dialogId, min_unread_id2)); if (cursor.next()) { count_unread = cursor.intValue(0); } cursor.dispose(); } } if (load_type == 3 || load_type == 4) { cursor = database.queryFinalized(String.format(Locale.US, "SELECT min(mid) FROM messages WHERE uid = %d AND mid < 0", dialogId)); if (cursor.next()) { last_message_id = cursor.intValue(0); } cursor.dispose(); cursor = database.queryFinalized(String.format(Locale.US, "SELECT * FROM (" + messageSelect + " WHERE m.uid = %d AND m.mid <= %d ORDER BY m.mid DESC LIMIT %d) UNION " + "SELECT * FROM (" + messageSelect + " WHERE m.uid = %d AND m.mid > %d ORDER BY m.mid ASC LIMIT %d)", dialogId, messageMaxId, count_query / 2, dialogId, messageMaxId, count_query / 2)); } else if (load_type == 1) { cursor = database.queryFinalized(String.format(Locale.US, "" + messageSelect + " WHERE m.uid = %d AND m.mid < %d ORDER BY m.mid DESC LIMIT %d", dialogId, max_id, count_query)); } else if (minDate != 0) { if (max_id != 0) { cursor = database.queryFinalized(String.format(Locale.US, "" + messageSelect + " WHERE m.uid = %d AND m.mid > %d ORDER BY m.mid ASC LIMIT %d", dialogId, max_id, count_query)); } else { cursor = database.queryFinalized(String.format(Locale.US, "" + messageSelect + " WHERE m.uid = %d AND m.date <= %d ORDER BY m.mid ASC LIMIT %d,%d", dialogId, minDate, offset_query, count_query)); } } else { if (load_type == 2) { cursor = database.queryFinalized(String.format(Locale.US, "SELECT min(mid) FROM messages WHERE uid = %d AND mid < 0", dialogId)); if (cursor.next()) { last_message_id = cursor.intValue(0); } cursor.dispose(); cursor = database.queryFinalized(String.format(Locale.US, "SELECT max(mid), max(date) FROM messages WHERE uid = %d AND out = 0 AND read_state IN(0,2) AND mid < 0", dialogId)); if (cursor.next()) { min_unread_id = cursor.intValue(0); max_unread_date = cursor.intValue(1); } cursor.dispose(); if (min_unread_id != 0) { cursor = database.queryFinalized(String.format(Locale.US, "SELECT COUNT(*) FROM messages WHERE uid = %d AND mid <= %d AND out = 0 AND read_state IN(0,2)", dialogId, min_unread_id)); if (cursor.next()) { count_unread = cursor.intValue(0); } cursor.dispose(); } } if (count_query > count_unread || count_unread < num) { count_query = Math.max(count_query, count_unread + 10); if (count_unread < num) { count_unread = 0; min_unread_id = 0; last_message_id = 0; } } else { offset_query = count_unread - count_query; count_query += 10; } cursor = database.queryFinalized(String.format(Locale.US, "" + messageSelect + " WHERE m.uid = %d ORDER BY m.mid ASC LIMIT %d,%d", dialogId, offset_query, count_query)); } } int minId = Integer.MAX_VALUE; int maxId = Integer.MIN_VALUE; if (cursor != null) { while (cursor.next()) { messagesCount++; if (!processMessages) { continue; } NativeByteBuffer data = cursor.byteBufferValue(1); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); message.send_state = cursor.intValue(2); message.id = cursor.intValue(3); if (message.id > 0 && message.send_state != 0 && message.send_state != 3) { message.send_state = 0; } if (dialogId == currentUserId) { message.out = true; } message.readAttachPath(data, currentUserId); data.reuse(); MessageObject.setUnreadFlags(message, cursor.intValue(0)); if (message.id > 0) { minId = Math.min(message.id, minId); maxId = Math.max(message.id, maxId); } message.date = cursor.intValue(4); message.dialog_id = dialogId; if ((message.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0) { message.views = cursor.intValue(7); message.forwards = cursor.intValue(11); } NativeByteBuffer repliesData = cursor.byteBufferValue(12); if (repliesData != null) { TLRPC.TL_messageReplies replies = TLRPC.TL_messageReplies.TLdeserialize(repliesData, repliesData.readInt32(false), false); if (replies != null) { message.replies = replies; } repliesData.reuse(); } if (lower_id != 0 && message.ttl == 0) { message.ttl = cursor.intValue(8); } if (cursor.intValue(9) != 0) { message.mentioned = true; } int flags = cursor.intValue(10); if ((flags & 1) != 0) { message.stickerVerified = 0; } else if ((flags & 2) != 0) { message.stickerVerified = 2; } res.messages.add(message); addUsersAndChatsFromMessage(message, usersToLoad, chatsToLoad); if (message.reply_to != null && (message.reply_to.reply_to_msg_id != 0 || message.reply_to.reply_to_random_id != 0)) { if (!cursor.isNull(6)) { data = cursor.byteBufferValue(6); if (data != null) { message.replyMessage = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); message.replyMessage.readAttachPath(data, currentUserId); data.reuse(); if (message.replyMessage != null) { addUsersAndChatsFromMessage(message.replyMessage, usersToLoad, chatsToLoad); } } } if (message.replyMessage == null) { if (message.reply_to.reply_to_msg_id != 0) { long messageId = message.reply_to.reply_to_msg_id; if (message.reply_to.reply_to_peer_id != null) { if (message.reply_to.reply_to_peer_id.channel_id != 0) { messageId |= ((long) message.reply_to.reply_to_peer_id.channel_id) << 32; } } else if (message.peer_id.channel_id != 0) { messageId |= ((long) message.peer_id.channel_id) << 32; } if (!replyMessages.contains(messageId)) { replyMessages.add(messageId); } ArrayList messages = replyMessageOwners.get(message.reply_to.reply_to_msg_id); if (messages == null) { messages = new ArrayList<>(); replyMessageOwners.put(message.reply_to.reply_to_msg_id, messages); } messages.add(message); } else { if (!replyMessages.contains(message.reply_to.reply_to_random_id)) { replyMessages.add(message.reply_to.reply_to_random_id); } ArrayList messages = replyMessageRandomOwners.get(message.reply_to.reply_to_random_id); if (messages == null) { messages = new ArrayList<>(); replyMessageRandomOwners.put(message.reply_to.reply_to_random_id, messages); } messages.add(message); } } } if (lower_id == 0 && !cursor.isNull(5)) { message.random_id = cursor.longValue(5); } if (MessageObject.isSecretMedia(message)) { try { SQLiteCursor cursor2 = database.queryFinalized(String.format(Locale.US, "SELECT date FROM enc_tasks_v2 WHERE mid = %d", message.id)); if (cursor2.next()) { message.destroyTime = cursor2.intValue(0); } cursor2.dispose(); } catch (Exception e) { FileLog.e(e); } } } } cursor.dispose(); } Collections.sort(res.messages, (lhs, rhs) -> { if (lhs.id > 0 && rhs.id > 0) { if (lhs.id > rhs.id) { return -1; } else if (lhs.id < rhs.id) { return 1; } } else if (lhs.id < 0 && rhs.id < 0) { if (lhs.id < rhs.id) { return -1; } else if (lhs.id > rhs.id) { return 1; } } else { if (lhs.date > rhs.date) { return -1; } else if (lhs.date < rhs.date) { return 1; } } return 0; }); if (lower_id != 0) { if ((load_type == 3 || load_type == 4 || load_type == 2 && queryFromServer && !unreadCountIsLocal) && !res.messages.isEmpty()) { if (!(minId <= max_id_query && maxId >= max_id_query)) { replyMessages.clear(); usersToLoad.clear(); chatsToLoad.clear(); res.messages.clear(); } } if ((load_type == 4 || load_type == 3) && res.messages.size() == 1) { res.messages.clear(); } } if (mentions_unread != 0) { cursor = database.queryFinalized(String.format(Locale.US, "SELECT COUNT(mid) FROM messages WHERE uid = %d AND mention = 1 AND read_state IN(0, 1)", dialogId)); if (cursor.next()) { if (mentions_unread != cursor.intValue(0)) { mentions_unread *= -1; } } else { mentions_unread *= -1; } cursor.dispose(); } } if (!replyMessages.isEmpty()) { if (replyMessageOwners.size() > 0) { cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid, date FROM messages WHERE mid IN(%s)", TextUtils.join(",", replyMessages))); } else { cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.data, m.mid, m.date, r.random_id FROM randoms as r INNER JOIN messages as m ON r.mid = m.mid WHERE r.random_id IN(%s)", TextUtils.join(",", replyMessages))); } while (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); message.readAttachPath(data, currentUserId); data.reuse(); message.id = cursor.intValue(1); message.date = cursor.intValue(2); message.dialog_id = dialogId; addUsersAndChatsFromMessage(message, usersToLoad, chatsToLoad); if (replyMessageOwners.size() > 0) { ArrayList arrayList = replyMessageOwners.get(message.id); if (arrayList != null) { for (int a = 0; a < arrayList.size(); a++) { TLRPC.Message object = arrayList.get(a); object.replyMessage = message; } } } else { long value = cursor.longValue(3); ArrayList arrayList = replyMessageRandomOwners.get(value); replyMessageRandomOwners.remove(value); if (arrayList != null) { for (int a = 0; a < arrayList.size(); a++) { TLRPC.Message object = arrayList.get(a); object.replyMessage = message; if (object.reply_to != null) { object.reply_to.reply_to_msg_id = message.id; } } } } } } cursor.dispose(); if (replyMessageRandomOwners.size() > 0) { for (int b = 0; b < replyMessageRandomOwners.size(); b++) { ArrayList arrayList = replyMessageRandomOwners.valueAt(b); for (int a = 0; a < arrayList.size(); a++) { TLRPC.Message message = arrayList.get(a); if (message.reply_to != null) { message.reply_to.reply_to_random_id = 0; } } } } } if (!usersToLoad.isEmpty()) { getUsersInternal(TextUtils.join(",", usersToLoad), res.users); } if (!chatsToLoad.isEmpty()) { getChatsInternal(TextUtils.join(",", chatsToLoad), res.chats); } } catch (Exception e) { res.messages.clear(); res.chats.clear(); res.users.clear(); FileLog.e(e); } if (BuildVars.LOGS_ENABLED) { FileLog.d("messages load time = " + (SystemClock.elapsedRealtime() - startLoadTime) + " for dialog = " + dialogId); } int countQueryFinal = count_query; int maxIdOverrideFinal = max_id_override; int minUnreadIdFinal = min_unread_id; int lastMessageIdFinal = last_message_id; boolean isEndFinal = isEnd; boolean queryFromServerFinal = queryFromServer; int mentionsUnreadFinal = mentions_unread; int countUnreadFinal = count_unread; int maxUnreadDateFinal = max_unread_date; /*if (!scheduled && mergeDialogId != 0 && res.messages.size() < count && isEnd && load_type == 2) { TODO fix if end not reached Runnable runnable = getMessagesInternal(mergeDialogId, 0, count, Integer.MAX_VALUE, 0, 0, classGuid, 0, false, false, loadIndex); return () -> { getMessagesController().processLoadedMessages(res, dialogId, mergeDialogId, countQueryFinal, maxIdOverrideFinal, offset_date, true, classGuid, minUnreadIdFinal, lastMessageIdFinal, countUnreadFinal, maxUnreadDateFinal, load_type, isChannel, isEndFinal, scheduled, -loadIndex, queryFromServerFinal, mentionsUnreadFinal); runnable.run(); }; } else {*/ int finalMessagesCount = scheduled ? res.messages.size() : messagesCount; return () -> getMessagesController().processLoadedMessages(res, finalMessagesCount, dialogId, mergeDialogId, countQueryFinal, maxIdOverrideFinal, offset_date, true, classGuid, minUnreadIdFinal, lastMessageIdFinal, countUnreadFinal, maxUnreadDateFinal, load_type, isChannel, isEndFinal, scheduled ? 1 : 0, replyMessageId, loadIndex, queryFromServerFinal, mentionsUnreadFinal, processMessages); //} } public void getMessages(long dialogId, long mergeDialogId, boolean loadInfo, int count, int max_id, int offset_date, int minDate, int classGuid, int load_type, boolean isChannel, boolean scheduled, int replyMessageId, int loadIndex, boolean processMessages) { storageQueue.postRunnable(() -> { long mergeDialogIdFinal = mergeDialogId; int lowerId = (int) dialogId; /*if (loadInfo) { if (lowerId < 0) { TLRPC.ChatFull info = loadChatInfoInternal(-lowerId, true, false, 0); if (info != null) { mergeDialogIdFinal = -info.migrated_from_chat_id; } } }*/ Utilities.stageQueue.postRunnable(getMessagesInternal(dialogId, mergeDialogIdFinal, count, max_id, offset_date, minDate, classGuid, load_type, isChannel, scheduled, replyMessageId, loadIndex, processMessages)); }); } public void clearSentMedia() { storageQueue.postRunnable(() -> { try { database.executeFast("DELETE FROM sent_files_v2 WHERE 1").stepThis().dispose(); } catch (Exception e) { FileLog.e(e); } }); } public Object[] getSentFile(final String path, final int type) { if (path == null || path.toLowerCase().endsWith("attheme")) { return null; } final CountDownLatch countDownLatch = new CountDownLatch(1); final Object[] result = new Object[2]; storageQueue.postRunnable(() -> { try { String id = Utilities.MD5(path); if (id != null) { SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, parent FROM sent_files_v2 WHERE uid = '%s' AND type = %d", id, type)); if (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLObject file = TLRPC.MessageMedia.TLdeserialize(data, data.readInt32(false), false); data.reuse(); if (file instanceof TLRPC.TL_messageMediaDocument) { result[0] = ((TLRPC.TL_messageMediaDocument) file).document; } else if (file instanceof TLRPC.TL_messageMediaPhoto) { result[0] = ((TLRPC.TL_messageMediaPhoto) file).photo; } if (result[0] != null) { result[1] = cursor.stringValue(1); } } } cursor.dispose(); } } catch (Exception e) { FileLog.e(e); } finally { countDownLatch.countDown(); } }); try { countDownLatch.await(); } catch (Exception e) { FileLog.e(e); } return result[0] != null ? result : null; } private void updateWidgets(long did) { ArrayList dids = new ArrayList<>(); dids.add(did); updateWidgets(dids); } private void updateWidgets(ArrayList dids) { if (dids.isEmpty()) { return; } try { AppWidgetManager appWidgetManager = null; String ids = TextUtils.join(",", dids); SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT DISTINCT id FROM shortcut_widget WHERE did IN(%s,-1)", TextUtils.join(",", dids))); while (cursor.next()) { if (appWidgetManager == null) { appWidgetManager = AppWidgetManager.getInstance(ApplicationLoader.applicationContext); } appWidgetManager.notifyAppWidgetViewDataChanged(cursor.intValue(0), R.id.list_view); } cursor.dispose(); } catch (Exception e) { FileLog.e(e); } } public void putWidgetDialogs(int widgetId, ArrayList dids) { storageQueue.postRunnable(() -> { try { database.beginTransaction(); database.executeFast("DELETE FROM shortcut_widget WHERE id = " + widgetId).stepThis().dispose(); SQLitePreparedStatement state = database.executeFast("REPLACE INTO shortcut_widget VALUES(?, ?, ?)"); if (dids.isEmpty()) { state.requery(); state.bindInteger(1, widgetId); state.bindLong(2, -1); state.bindInteger(3, 0); state.step(); } else { for (int a = 0, N = dids.size(); a < N; a++) { long did = dids.get(a); state.requery(); state.bindInteger(1, widgetId); state.bindLong(2, did); state.bindInteger(3, a); state.step(); } } state.dispose(); database.commitTransaction(); } catch (Exception e) { FileLog.e(e); } }); } public void clearWidgetDialogs(int widgetId) { storageQueue.postRunnable(() -> { try { database.executeFast("DELETE FROM shortcut_widget WHERE id = " + widgetId).stepThis().dispose(); } catch (Exception e) { FileLog.e(e); } }); } public void getWidgetDialogIds(int widgetId, int type, ArrayList dids, ArrayList users, ArrayList chats, boolean edit) { final CountDownLatch countDownLatch = new CountDownLatch(1); storageQueue.postRunnable(() -> { try { ArrayList usersToLoad = new ArrayList<>(); ArrayList chatsToLoad = new ArrayList<>(); SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT did FROM shortcut_widget WHERE id = %d ORDER BY ord ASC", widgetId)); while (cursor.next()) { long id = cursor.longValue(0); if (id == -1) { continue; } dids.add(id); if (users != null && chats != null) { int lowerId = (int) id; if (lowerId > 0) { usersToLoad.add(lowerId); } else { chatsToLoad.add(-lowerId); } } } cursor.dispose(); if (!edit && dids.isEmpty()) { if (type == EditWidgetActivity.TYPE_CHATS) { cursor = database.queryFinalized("SELECT did FROM dialogs WHERE folder_id = 0 ORDER BY pinned DESC, date DESC LIMIT 0,10"); while (cursor.next()) { long dialogId = cursor.longValue(0); if (DialogObject.isFolderDialogId(dialogId)) { continue; } dids.add(dialogId); if (users != null && chats != null) { int lowerId = (int) dialogId; if (lowerId > 0) { usersToLoad.add(lowerId); } else { chatsToLoad.add(-lowerId); } } } cursor.dispose(); } else { cursor = getMessagesStorage().getDatabase().queryFinalized("SELECT did FROM chat_hints WHERE type = 0 ORDER BY rating DESC LIMIT 4"); while (cursor.next()) { long dialogId = cursor.longValue(0); dids.add(dialogId); if (users != null && chats != null) { int lowerId = (int) dialogId; if (lowerId > 0) { usersToLoad.add(lowerId); } else { chatsToLoad.add(-lowerId); } } } cursor.dispose(); } } if (users != null && chats != null) { if (!chatsToLoad.isEmpty()) { getChatsInternal(TextUtils.join(",", chatsToLoad), chats); } if (!usersToLoad.isEmpty()) { getUsersInternal(TextUtils.join(",", usersToLoad), users); } } } catch (Exception e) { FileLog.e(e); } finally { countDownLatch.countDown(); } }); try { countDownLatch.await(); } catch (Exception e) { FileLog.e(e); } } public void getWidgetDialogs(int widgetId, int type, ArrayList dids, LongSparseArray dialogs, LongSparseArray messages, ArrayList users, ArrayList chats) { final CountDownLatch countDownLatch = new CountDownLatch(1); storageQueue.postRunnable(() -> { try { boolean add = false; ArrayList usersToLoad = new ArrayList<>(); ArrayList chatsToLoad = new ArrayList<>(); SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT did FROM shortcut_widget WHERE id = %d ORDER BY ord ASC", widgetId)); while (cursor.next()) { long id = cursor.longValue(0); if (id == -1) { continue; } dids.add(id); int lowerId = (int) id; if (lowerId > 0) { usersToLoad.add(lowerId); } else { chatsToLoad.add(-lowerId); } } cursor.dispose(); if (dids.isEmpty() && type == EditWidgetActivity.TYPE_CONTACTS) { cursor = getMessagesStorage().getDatabase().queryFinalized("SELECT did FROM chat_hints WHERE type = 0 ORDER BY rating DESC LIMIT 4"); while (cursor.next()) { long dialogId = cursor.longValue(0); dids.add(dialogId); int lowerId = (int) dialogId; if (lowerId > 0) { usersToLoad.add(lowerId); } else { chatsToLoad.add(-lowerId); } } cursor.dispose(); } if (dids.isEmpty()) { add = true; cursor = database.queryFinalized("SELECT d.did, d.last_mid, d.unread_count, d.date, m.data, m.read_state, m.mid, m.send_state, m.date FROM dialogs as d LEFT JOIN messages as m ON d.last_mid = m.mid WHERE d.folder_id = 0 ORDER BY d.pinned DESC, d.date DESC LIMIT 0,10"); } else { cursor = database.queryFinalized(String.format(Locale.US, "SELECT d.did, d.last_mid, d.unread_count, d.date, m.data, m.read_state, m.mid, m.send_state, m.date FROM dialogs as d LEFT JOIN messages as m ON d.last_mid = m.mid WHERE d.did IN(%s)", TextUtils.join(",", dids))); } while (cursor.next()) { long dialogId = cursor.longValue(0); if (DialogObject.isFolderDialogId(dialogId)) { continue; } if (add) { dids.add(dialogId); } TLRPC.Dialog dialog = new TLRPC.TL_dialog(); dialog.id = dialogId; dialog.top_message = cursor.intValue(1); dialog.unread_count = cursor.intValue(2); dialog.last_message_date = cursor.intValue(3); dialogs.put(dialog.id, dialog); NativeByteBuffer data = cursor.byteBufferValue(4); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); MessageObject.setUnreadFlags(message, cursor.intValue(5)); message.id = cursor.intValue(6); message.send_state = cursor.intValue(7); int date = cursor.intValue(8); if (date != 0) { dialog.last_message_date = date; } message.dialog_id = dialog.id; messages.put(dialog.id, message); addUsersAndChatsFromMessage(message, usersToLoad, chatsToLoad); } } cursor.dispose(); if (!add) { if (dids.size() > dialogs.size()) { for (int a = 0, N = dids.size(); a < N; a++) { long did = dids.get(a); if (dialogs.get(dids.get(a)) == null) { TLRPC.TL_dialog dialog = new TLRPC.TL_dialog(); dialog.id = did; dialogs.put(dialog.id, dialog); int lowerId = (int) did; if (lowerId < 0) { if (chatsToLoad.contains(-lowerId)) { chatsToLoad.add(-lowerId); } } else { if (usersToLoad.contains(lowerId)) { usersToLoad.add(lowerId); } } } } } } if (!chatsToLoad.isEmpty()) { getChatsInternal(TextUtils.join(",", chatsToLoad), chats); } if (!usersToLoad.isEmpty()) { getUsersInternal(TextUtils.join(",", usersToLoad), users); } } catch (Exception e) { FileLog.e(e); } finally { countDownLatch.countDown(); } }); try { countDownLatch.await(); } catch (Exception e) { FileLog.e(e); } } public void putSentFile(final String path, final TLObject file, final int type, String parent) { if (path == null || file == null || parent == null) { return; } storageQueue.postRunnable(() -> { SQLitePreparedStatement state = null; try { String id = Utilities.MD5(path); if (id != null) { TLRPC.MessageMedia messageMedia = null; if (file instanceof TLRPC.Photo) { messageMedia = new TLRPC.TL_messageMediaPhoto(); messageMedia.photo = (TLRPC.Photo) file; messageMedia.flags |= 1; } else if (file instanceof TLRPC.Document) { messageMedia = new TLRPC.TL_messageMediaDocument(); messageMedia.document = (TLRPC.Document) file; messageMedia.flags |= 1; } if (messageMedia == null) { return; } state = database.executeFast("REPLACE INTO sent_files_v2 VALUES(?, ?, ?, ?)"); state.requery(); NativeByteBuffer data = new NativeByteBuffer(messageMedia.getObjectSize()); messageMedia.serializeToStream(data); state.bindString(1, id); state.bindInteger(2, type); state.bindByteBuffer(3, data); state.bindString(4, parent); state.step(); data.reuse(); } } catch (Exception e) { FileLog.e(e); } finally { if (state != null) { state.dispose(); } } }); } public void updateEncryptedChatSeq(final TLRPC.EncryptedChat chat, final boolean cleanup) { if (chat == null) { return; } storageQueue.postRunnable(() -> { SQLitePreparedStatement state = null; try { state = database.executeFast("UPDATE enc_chats SET seq_in = ?, seq_out = ?, use_count = ?, in_seq_no = ?, mtproto_seq = ? WHERE uid = ?"); state.bindInteger(1, chat.seq_in); state.bindInteger(2, chat.seq_out); state.bindInteger(3, (int) chat.key_use_count_in << 16 | chat.key_use_count_out); state.bindInteger(4, chat.in_seq_no); state.bindInteger(5, chat.mtproto_seq); state.bindInteger(6, chat.id); state.step(); if (cleanup && chat.in_seq_no != 0) { long did = ((long) chat.id) << 32; database.executeFast(String.format(Locale.US, "DELETE FROM messages WHERE mid IN (SELECT m.mid FROM messages as m LEFT JOIN messages_seq as s ON m.mid = s.mid WHERE m.uid = %d AND m.date = 0 AND m.mid < 0 AND s.seq_out <= %d)", did, chat.in_seq_no)).stepThis().dispose(); } } catch (Exception e) { FileLog.e(e); } finally { if (state != null) { state.dispose(); } } }); } public void updateEncryptedChatTTL(final TLRPC.EncryptedChat chat) { if (chat == null) { return; } storageQueue.postRunnable(() -> { SQLitePreparedStatement state = null; try { state = database.executeFast("UPDATE enc_chats SET ttl = ? WHERE uid = ?"); state.bindInteger(1, chat.ttl); state.bindInteger(2, chat.id); state.step(); } catch (Exception e) { FileLog.e(e); } finally { if (state != null) { state.dispose(); } } }); } public void updateEncryptedChatLayer(final TLRPC.EncryptedChat chat) { if (chat == null) { return; } storageQueue.postRunnable(() -> { SQLitePreparedStatement state = null; try { state = database.executeFast("UPDATE enc_chats SET layer = ? WHERE uid = ?"); state.bindInteger(1, chat.layer); state.bindInteger(2, chat.id); state.step(); } catch (Exception e) { FileLog.e(e); } finally { if (state != null) { state.dispose(); } } }); } public void updateEncryptedChat(final TLRPC.EncryptedChat chat) { if (chat == null) { return; } storageQueue.postRunnable(() -> { SQLitePreparedStatement state = null; try { if ((chat.key_hash == null || chat.key_hash.length < 16) && chat.auth_key != null) { chat.key_hash = AndroidUtilities.calcAuthKeyHash(chat.auth_key); } state = database.executeFast("UPDATE enc_chats SET data = ?, g = ?, authkey = ?, ttl = ?, layer = ?, seq_in = ?, seq_out = ?, use_count = ?, exchange_id = ?, key_date = ?, fprint = ?, fauthkey = ?, khash = ?, in_seq_no = ?, admin_id = ?, mtproto_seq = ? WHERE uid = ?"); NativeByteBuffer data = new NativeByteBuffer(chat.getObjectSize()); NativeByteBuffer data2 = new NativeByteBuffer(chat.a_or_b != null ? chat.a_or_b.length : 1); NativeByteBuffer data3 = new NativeByteBuffer(chat.auth_key != null ? chat.auth_key.length : 1); NativeByteBuffer data4 = new NativeByteBuffer(chat.future_auth_key != null ? chat.future_auth_key.length : 1); NativeByteBuffer data5 = new NativeByteBuffer(chat.key_hash != null ? chat.key_hash.length : 1); chat.serializeToStream(data); state.bindByteBuffer(1, data); if (chat.a_or_b != null) { data2.writeBytes(chat.a_or_b); } if (chat.auth_key != null) { data3.writeBytes(chat.auth_key); } if (chat.future_auth_key != null) { data4.writeBytes(chat.future_auth_key); } if (chat.key_hash != null) { data5.writeBytes(chat.key_hash); } state.bindByteBuffer(2, data2); state.bindByteBuffer(3, data3); state.bindInteger(4, chat.ttl); state.bindInteger(5, chat.layer); state.bindInteger(6, chat.seq_in); state.bindInteger(7, chat.seq_out); state.bindInteger(8, (int) chat.key_use_count_in << 16 | chat.key_use_count_out); state.bindLong(9, chat.exchange_id); state.bindInteger(10, chat.key_create_date); state.bindLong(11, chat.future_key_fingerprint); state.bindByteBuffer(12, data4); state.bindByteBuffer(13, data5); state.bindInteger(14, chat.in_seq_no); state.bindInteger(15, chat.admin_id); state.bindInteger(16, chat.mtproto_seq); state.bindInteger(17, chat.id); state.step(); data.reuse(); data2.reuse(); data3.reuse(); data4.reuse(); data5.reuse(); } catch (Exception e) { FileLog.e(e); } finally { if (state != null) { state.dispose(); } } }); } public void isDialogHasTopMessage(long did, Runnable onDontExist) { storageQueue.postRunnable(() -> { boolean exists = false; try { SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT last_mid FROM dialogs WHERE did = %d", did)); if (cursor.next()) { exists = cursor.intValue(0) != 0; } cursor.dispose(); } catch (Exception e) { FileLog.e(e); } if (!exists) { AndroidUtilities.runOnUIThread(onDontExist); } }); } public boolean hasAuthMessage(final int date) { final CountDownLatch countDownLatch = new CountDownLatch(1); final boolean[] result = new boolean[1]; storageQueue.postRunnable(() -> { try { SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT mid FROM messages WHERE uid = 777000 AND date = %d AND mid < 0 LIMIT 1", date)); result[0] = cursor.next(); cursor.dispose(); } catch (Exception e) { FileLog.e(e); } finally { countDownLatch.countDown(); } }); try { countDownLatch.await(); } catch (Exception e) { FileLog.e(e); } return result[0]; } public void getEncryptedChat(final int chat_id, final CountDownLatch countDownLatch, final ArrayList result) { if (countDownLatch == null || result == null) { return; } storageQueue.postRunnable(() -> { try { ArrayList usersToLoad = new ArrayList<>(); ArrayList encryptedChats = new ArrayList<>(); getEncryptedChatsInternal("" + chat_id, encryptedChats, usersToLoad); if (!encryptedChats.isEmpty() && !usersToLoad.isEmpty()) { ArrayList users = new ArrayList<>(); getUsersInternal(TextUtils.join(",", usersToLoad), users); if (!users.isEmpty()) { result.add(encryptedChats.get(0)); result.add(users.get(0)); } } } catch (Exception e) { FileLog.e(e); } finally { countDownLatch.countDown(); } }); } public void putEncryptedChat(final TLRPC.EncryptedChat chat, final TLRPC.User user, final TLRPC.Dialog dialog) { if (chat == null) { return; } storageQueue.postRunnable(() -> { try { if ((chat.key_hash == null || chat.key_hash.length < 16) && chat.auth_key != null) { chat.key_hash = AndroidUtilities.calcAuthKeyHash(chat.auth_key); } SQLitePreparedStatement state = database.executeFast("REPLACE INTO enc_chats VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); NativeByteBuffer data = new NativeByteBuffer(chat.getObjectSize()); NativeByteBuffer data2 = new NativeByteBuffer(chat.a_or_b != null ? chat.a_or_b.length : 1); NativeByteBuffer data3 = new NativeByteBuffer(chat.auth_key != null ? chat.auth_key.length : 1); NativeByteBuffer data4 = new NativeByteBuffer(chat.future_auth_key != null ? chat.future_auth_key.length : 1); NativeByteBuffer data5 = new NativeByteBuffer(chat.key_hash != null ? chat.key_hash.length : 1); chat.serializeToStream(data); state.bindInteger(1, chat.id); state.bindInteger(2, user.id); state.bindString(3, formatUserSearchName(user)); state.bindByteBuffer(4, data); if (chat.a_or_b != null) { data2.writeBytes(chat.a_or_b); } if (chat.auth_key != null) { data3.writeBytes(chat.auth_key); } if (chat.future_auth_key != null) { data4.writeBytes(chat.future_auth_key); } if (chat.key_hash != null) { data5.writeBytes(chat.key_hash); } state.bindByteBuffer(5, data2); state.bindByteBuffer(6, data3); state.bindInteger(7, chat.ttl); state.bindInteger(8, chat.layer); state.bindInteger(9, chat.seq_in); state.bindInteger(10, chat.seq_out); state.bindInteger(11, (int) chat.key_use_count_in << 16 | chat.key_use_count_out); state.bindLong(12, chat.exchange_id); state.bindInteger(13, chat.key_create_date); state.bindLong(14, chat.future_key_fingerprint); state.bindByteBuffer(15, data4); state.bindByteBuffer(16, data5); state.bindInteger(17, chat.in_seq_no); state.bindInteger(18, chat.admin_id); state.bindInteger(19, chat.mtproto_seq); state.step(); state.dispose(); data.reuse(); data2.reuse(); data3.reuse(); data4.reuse(); data5.reuse(); if (dialog != null) { state = database.executeFast("REPLACE INTO dialogs VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); state.bindLong(1, dialog.id); state.bindInteger(2, dialog.last_message_date); state.bindInteger(3, dialog.unread_count); state.bindInteger(4, dialog.top_message); state.bindInteger(5, dialog.read_inbox_max_id); state.bindInteger(6, dialog.read_outbox_max_id); state.bindInteger(7, 0); state.bindInteger(8, dialog.unread_mentions_count); state.bindInteger(9, dialog.pts); state.bindInteger(10, 0); state.bindInteger(11, dialog.pinnedNum); state.bindInteger(12, dialog.flags); state.bindInteger(13, dialog.folder_id); state.bindNull(14); state.step(); state.dispose(); } } catch (Exception e) { FileLog.e(e); } }); } private String formatUserSearchName(TLRPC.User user) { StringBuilder str = new StringBuilder(); if (user.first_name != null && user.first_name.length() > 0) { str.append(user.first_name); } if (user.last_name != null && user.last_name.length() > 0) { if (str.length() > 0) { str.append(" "); } str.append(user.last_name); } str.append(";;;"); if (user.username != null && user.username.length() > 0) { str.append(user.username); } return str.toString().toLowerCase(); } private void putUsersInternal(ArrayList users) throws Exception { if (users == null || users.isEmpty()) { return; } SQLitePreparedStatement state = database.executeFast("REPLACE INTO users VALUES(?, ?, ?, ?)"); for (int a = 0; a < users.size(); a++) { TLRPC.User user = users.get(a); if (user.min) { SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT data FROM users WHERE uid = %d", user.id)); if (cursor.next()) { try { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.User oldUser = TLRPC.User.TLdeserialize(data, data.readInt32(false), false); data.reuse(); if (oldUser != null) { if (user.username != null) { oldUser.username = user.username; oldUser.flags |= 8; } else { oldUser.username = null; oldUser.flags = oldUser.flags & ~8; } if (user.apply_min_photo) { if (user.photo != null) { oldUser.photo = user.photo; oldUser.flags |= 32; } else { oldUser.photo = null; oldUser.flags = oldUser.flags & ~32; } } user = oldUser; } } } catch (Exception e) { FileLog.e(e); } } cursor.dispose(); } state.requery(); NativeByteBuffer data = new NativeByteBuffer(user.getObjectSize()); user.serializeToStream(data); state.bindInteger(1, user.id); state.bindString(2, formatUserSearchName(user)); if (user.status != null) { if (user.status instanceof TLRPC.TL_userStatusRecently) { user.status.expires = -100; } else if (user.status instanceof TLRPC.TL_userStatusLastWeek) { user.status.expires = -101; } else if (user.status instanceof TLRPC.TL_userStatusLastMonth) { user.status.expires = -102; } state.bindInteger(3, user.status.expires); } else { state.bindInteger(3, 0); } state.bindByteBuffer(4, data); state.step(); data.reuse(); } state.dispose(); } public void updateChatDefaultBannedRights(int chatId, TLRPC.TL_chatBannedRights rights, int version) { if (rights == null || chatId == 0) { return; } storageQueue.postRunnable(() -> { try { TLRPC.Chat chat = null; SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT data FROM chats WHERE uid = %d", chatId)); if (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { chat = TLRPC.Chat.TLdeserialize(data, data.readInt32(false), false); data.reuse(); } } cursor.dispose(); if (chat == null || chat.default_banned_rights != null && version < chat.version) { return; } chat.default_banned_rights = rights; chat.flags |= 262144; chat.version = version; SQLitePreparedStatement state = database.executeFast("UPDATE chats SET data = ? WHERE uid = ?"); NativeByteBuffer data = new NativeByteBuffer(chat.getObjectSize()); chat.serializeToStream(data); state.bindByteBuffer(1, data); state.bindInteger(2, chat.id); state.step(); data.reuse(); state.dispose(); } catch (Exception e) { FileLog.e(e); } }); } private void putChatsInternal(ArrayList chats) throws Exception { if (chats == null || chats.isEmpty()) { return; } SQLitePreparedStatement state = database.executeFast("REPLACE INTO chats VALUES(?, ?, ?)"); for (int a = 0; a < chats.size(); a++) { TLRPC.Chat chat = chats.get(a); if (chat.min) { SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT data FROM chats WHERE uid = %d", chat.id)); if (cursor.next()) { try { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.Chat oldChat = TLRPC.Chat.TLdeserialize(data, data.readInt32(false), false); data.reuse(); if (oldChat != null) { oldChat.title = chat.title; oldChat.photo = chat.photo; oldChat.broadcast = chat.broadcast; oldChat.verified = chat.verified; oldChat.megagroup = chat.megagroup; oldChat.call_not_empty = chat.call_not_empty; oldChat.call_active = chat.call_active; if (chat.default_banned_rights != null) { oldChat.default_banned_rights = chat.default_banned_rights; oldChat.flags |= 262144; } if (chat.admin_rights != null) { oldChat.admin_rights = chat.admin_rights; oldChat.flags |= 16384; } if (chat.banned_rights != null) { oldChat.banned_rights = chat.banned_rights; oldChat.flags |= 32768; } if (chat.username != null) { oldChat.username = chat.username; oldChat.flags |= 64; } else { oldChat.username = null; oldChat.flags = oldChat.flags & ~64; } chat = oldChat; } } } catch (Exception e) { FileLog.e(e); } } cursor.dispose(); } state.requery(); chat.flags |= 131072; NativeByteBuffer data = new NativeByteBuffer(chat.getObjectSize()); chat.serializeToStream(data); state.bindInteger(1, chat.id); if (chat.title != null) { String name = chat.title.toLowerCase(); state.bindString(2, name); } else { state.bindString(2, ""); } state.bindByteBuffer(3, data); state.step(); data.reuse(); } state.dispose(); } public void getUsersInternal(String usersToLoad, ArrayList result) throws Exception { if (usersToLoad == null || usersToLoad.length() == 0 || result == null) { return; } SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, status FROM users WHERE uid IN(%s)", usersToLoad)); while (cursor.next()) { try { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.User user = TLRPC.User.TLdeserialize(data, data.readInt32(false), false); data.reuse(); if (user != null) { if (user.status != null) { user.status.expires = cursor.intValue(1); } result.add(user); } } } catch (Exception e) { FileLog.e(e); } } cursor.dispose(); } public void getChatsInternal(String chatsToLoad, ArrayList result) throws Exception { if (chatsToLoad == null || chatsToLoad.length() == 0 || result == null) { return; } SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT data FROM chats WHERE uid IN(%s)", chatsToLoad)); while (cursor.next()) { try { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.Chat chat = TLRPC.Chat.TLdeserialize(data, data.readInt32(false), false); data.reuse(); if (chat != null) { result.add(chat); } } } catch (Exception e) { FileLog.e(e); } } cursor.dispose(); } public void getEncryptedChatsInternal(String chatsToLoad, ArrayList result, ArrayList usersToLoad) throws Exception { if (chatsToLoad == null || chatsToLoad.length() == 0 || result == null) { return; } SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, user, g, authkey, ttl, layer, seq_in, seq_out, use_count, exchange_id, key_date, fprint, fauthkey, khash, in_seq_no, admin_id, mtproto_seq FROM enc_chats WHERE uid IN(%s)", chatsToLoad)); while (cursor.next()) { try { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.EncryptedChat chat = TLRPC.EncryptedChat.TLdeserialize(data, data.readInt32(false), false); data.reuse(); if (chat != null) { chat.user_id = cursor.intValue(1); if (usersToLoad != null && !usersToLoad.contains(chat.user_id)) { usersToLoad.add(chat.user_id); } chat.a_or_b = cursor.byteArrayValue(2); chat.auth_key = cursor.byteArrayValue(3); chat.ttl = cursor.intValue(4); chat.layer = cursor.intValue(5); chat.seq_in = cursor.intValue(6); chat.seq_out = cursor.intValue(7); int use_count = cursor.intValue(8); chat.key_use_count_in = (short) (use_count >> 16); chat.key_use_count_out = (short) (use_count); chat.exchange_id = cursor.longValue(9); chat.key_create_date = cursor.intValue(10); chat.future_key_fingerprint = cursor.longValue(11); chat.future_auth_key = cursor.byteArrayValue(12); chat.key_hash = cursor.byteArrayValue(13); chat.in_seq_no = cursor.intValue(14); int admin_id = cursor.intValue(15); if (admin_id != 0) { chat.admin_id = admin_id; } chat.mtproto_seq = cursor.intValue(16); result.add(chat); } } } catch (Exception e) { FileLog.e(e); } } cursor.dispose(); } private void putUsersAndChatsInternal(final ArrayList users, final ArrayList chats, final boolean withTransaction) { try { if (withTransaction) { database.beginTransaction(); } putUsersInternal(users); putChatsInternal(chats); if (withTransaction) { database.commitTransaction(); } } catch (Exception e) { FileLog.e(e); } } public void putUsersAndChats(final ArrayList users, final ArrayList chats, final boolean withTransaction, boolean useQueue) { if (users != null && users.isEmpty() && chats != null && chats.isEmpty()) { return; } if (useQueue) { storageQueue.postRunnable(() -> putUsersAndChatsInternal(users, chats, withTransaction)); } else { putUsersAndChatsInternal(users, chats, withTransaction); } } public void removeFromDownloadQueue(final long id, final int type, final boolean move) { storageQueue.postRunnable(() -> { try { if (move) { int minDate = -1; SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT min(date) FROM download_queue WHERE type = %d", type)); if (cursor.next()) { minDate = cursor.intValue(0); } cursor.dispose(); if (minDate != -1) { database.executeFast(String.format(Locale.US, "UPDATE download_queue SET date = %d WHERE uid = %d AND type = %d", minDate - 1, id, type)).stepThis().dispose(); } } else { database.executeFast(String.format(Locale.US, "DELETE FROM download_queue WHERE uid = %d AND type = %d", id, type)).stepThis().dispose(); } } catch (Exception e) { FileLog.e(e); } }); } private void deleteFromDownloadQueue(ArrayList> ids, boolean transaction) { if (ids == null || ids.isEmpty()) { return; } try { if (transaction) { database.beginTransaction(); } SQLitePreparedStatement state = database.executeFast("DELETE FROM download_queue WHERE uid = ? AND type = ?"); for (int a = 0, N = ids.size(); a < N; a++) { Pair pair = ids.get(a); state.requery(); state.bindLong(1, pair.first); state.bindInteger(2, pair.second); state.step(); } state.dispose(); if (transaction) { database.commitTransaction(); } AndroidUtilities.runOnUIThread(() -> getDownloadController().cancelDownloading(ids)); } catch (Exception e) { FileLog.e(e); } } public void clearDownloadQueue(final int type) { storageQueue.postRunnable(() -> { try { if (type == 0) { database.executeFast("DELETE FROM download_queue WHERE 1").stepThis().dispose(); } else { database.executeFast(String.format(Locale.US, "DELETE FROM download_queue WHERE type = %d", type)).stepThis().dispose(); } } catch (Exception e) { FileLog.e(e); } }); } public void getDownloadQueue(final int type) { storageQueue.postRunnable(() -> { try { final ArrayList objects = new ArrayList<>(); SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT uid, type, data, parent FROM download_queue WHERE type = %d ORDER BY date DESC LIMIT 3", type)); while (cursor.next()) { DownloadObject downloadObject = new DownloadObject(); downloadObject.type = cursor.intValue(1); downloadObject.id = cursor.longValue(0); downloadObject.parent = cursor.stringValue(3); NativeByteBuffer data = cursor.byteBufferValue(2); if (data != null) { TLRPC.MessageMedia messageMedia = TLRPC.MessageMedia.TLdeserialize(data, data.readInt32(false), false); data.reuse(); if (messageMedia.document != null) { downloadObject.object = messageMedia.document; downloadObject.secret = MessageObject.isVideoDocument(messageMedia.document) && messageMedia.ttl_seconds > 0 && messageMedia.ttl_seconds <= 60; } else if (messageMedia.photo != null) { downloadObject.object = messageMedia.photo; downloadObject.secret = messageMedia.ttl_seconds > 0 && messageMedia.ttl_seconds <= 60; } downloadObject.forceCache = (messageMedia.flags & 0x80000000) != 0; } objects.add(downloadObject); } cursor.dispose(); AndroidUtilities.runOnUIThread(() -> getDownloadController().processDownloadObjects(type, objects)); } catch (Exception e) { FileLog.e(e); } }); } private int getMessageMediaType(TLRPC.Message message) { if (message instanceof TLRPC.TL_message_secret) { if ((message.media instanceof TLRPC.TL_messageMediaPhoto || MessageObject.isGifMessage(message)) && message.ttl > 0 && message.ttl <= 60 || MessageObject.isVoiceMessage(message) || MessageObject.isVideoMessage(message) || MessageObject.isRoundVideoMessage(message)) { return 1; } else if (message.media instanceof TLRPC.TL_messageMediaPhoto || MessageObject.isVideoMessage(message)) { return 0; } } else if (message instanceof TLRPC.TL_message && (message.media instanceof TLRPC.TL_messageMediaPhoto || message.media instanceof TLRPC.TL_messageMediaDocument) && message.media.ttl_seconds != 0) { return 1; } else if (message.media instanceof TLRPC.TL_messageMediaPhoto || MessageObject.isVideoMessage(message)) { return 0; } return -1; } public void putWebPages(final LongSparseArray webPages) { if (isEmpty(webPages)) { return; } storageQueue.postRunnable(() -> { try { final ArrayList messages = new ArrayList<>(); for (int a = 0; a < webPages.size(); a++) { SQLiteCursor cursor = database.queryFinalized("SELECT mid FROM webpage_pending WHERE id = " + webPages.keyAt(a)); ArrayList mids = new ArrayList<>(); while (cursor.next()) { mids.add(cursor.longValue(0)); } cursor.dispose(); if (mids.isEmpty()) { continue; } cursor = database.queryFinalized(String.format(Locale.US, "SELECT mid, data FROM messages WHERE mid IN (%s)", TextUtils.join(",", mids))); while (cursor.next()) { int mid = cursor.intValue(0); NativeByteBuffer data = cursor.byteBufferValue(1); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); if (message.media instanceof TLRPC.TL_messageMediaWebPage) { message.id = mid; message.media.webpage = webPages.valueAt(a); messages.add(message); } } } cursor.dispose(); } //database.executeFast(String.format(Locale.US, "DELETE FROM webpage_pending WHERE id IN (%s)", ids)).stepThis().dispose(); if (messages.isEmpty()) { return; } database.beginTransaction(); SQLitePreparedStatement state = database.executeFast("UPDATE messages SET data = ? WHERE mid = ?"); SQLitePreparedStatement state2 = database.executeFast("UPDATE media_v2 SET data = ? WHERE mid = ?"); for (int a = 0; a < messages.size(); a++) { TLRPC.Message message = messages.get(a); NativeByteBuffer data = new NativeByteBuffer(message.getObjectSize()); message.serializeToStream(data); long messageId = message.id; if (message.peer_id.channel_id != 0) { messageId |= ((long) message.peer_id.channel_id) << 32; } state.requery(); state.bindByteBuffer(1, data); state.bindLong(2, messageId); state.step(); state2.requery(); state2.bindByteBuffer(1, data); state2.bindLong(2, messageId); state2.step(); data.reuse(); } state.dispose(); state2.dispose(); database.commitTransaction(); AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.didReceivedWebpages, messages)); } catch (Exception e) { FileLog.e(e); } }); } public void overwriteChannel(final int channel_id, final TLRPC.TL_updates_channelDifferenceTooLong difference, final int newDialogType) { storageQueue.postRunnable(() -> { try { boolean checkInvite = false; final long did = -channel_id; int pinned = 0; SQLiteCursor cursor = database.queryFinalized("SELECT pinned FROM dialogs WHERE did = " + did); if (!cursor.next()) { if (newDialogType != 0) { checkInvite = true; } } else { pinned = cursor.intValue(0); } cursor.dispose(); database.executeFast("DELETE FROM chat_pinned_count WHERE uid = " + did).stepThis().dispose(); database.executeFast("DELETE FROM chat_pinned_v2 WHERE uid = " + did).stepThis().dispose(); database.executeFast("DELETE FROM messages WHERE uid = " + did).stepThis().dispose(); database.executeFast("DELETE FROM bot_keyboard WHERE uid = " + did).stepThis().dispose(); database.executeFast("UPDATE media_counts_v2 SET old = 1 WHERE uid = " + did).stepThis().dispose(); database.executeFast("DELETE FROM media_v2 WHERE uid = " + did).stepThis().dispose(); database.executeFast("DELETE FROM messages_holes WHERE uid = " + did).stepThis().dispose(); database.executeFast("DELETE FROM media_holes_v2 WHERE uid = " + did).stepThis().dispose(); getMediaDataController().clearBotKeyboard(did, null); TLRPC.TL_messages_dialogs dialogs = new TLRPC.TL_messages_dialogs(); dialogs.chats.addAll(difference.chats); dialogs.users.addAll(difference.users); dialogs.messages.addAll(difference.messages); TLRPC.Dialog dialog = difference.dialog; dialog.id = did; dialog.flags = 1; dialog.notify_settings = null; dialog.pinned = pinned != 0; dialog.pinnedNum = pinned; dialogs.dialogs.add(dialog); putDialogsInternal(dialogs, 0); updateDialogsWithDeletedMessages(new ArrayList<>(), null, false, channel_id); AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.removeAllMessagesFromDialog, did, true)); if (checkInvite) { if (newDialogType == 1) { getMessagesController().checkChatInviter(channel_id, true); } else { getMessagesController().generateJoinMessage(channel_id, false); } } } catch (Exception e) { FileLog.e(e); } }); } public void putChannelViews(final SparseArray channelViews, final SparseArray channelForwards, final SparseArray> channelReplies, boolean addReply, final boolean isChannel) { if (isEmpty(channelViews) && isEmpty(channelForwards) && isEmpty(channelReplies)) { return; } storageQueue.postRunnable(() -> { try { database.beginTransaction(); if (!isEmpty(channelViews)) { SQLitePreparedStatement state = database.executeFast("UPDATE messages SET media = max((SELECT media FROM messages WHERE mid = ?), ?) WHERE mid = ?"); for (int a = 0; a < channelViews.size(); a++) { int peer = channelViews.keyAt(a); SparseIntArray messages = channelViews.get(peer); for (int b = 0; b < messages.size(); b++) { int views = messages.get(messages.keyAt(b)); long messageId = messages.keyAt(b); if (isChannel) { messageId |= ((long) -peer) << 32; } state.requery(); state.bindLong(1, messageId); state.bindInteger(2, views); state.bindLong(3, messageId); state.step(); } } state.dispose(); } if (!isEmpty(channelForwards)) { SQLitePreparedStatement state = database.executeFast("UPDATE messages SET forwards = max((SELECT forwards FROM messages WHERE mid = ?), ?) WHERE mid = ?"); for (int a = 0; a < channelForwards.size(); a++) { int peer = channelForwards.keyAt(a); SparseIntArray messages = channelForwards.get(peer); for (int b = 0; b < messages.size(); b++) { int forwards = messages.get(messages.keyAt(b)); long messageId = messages.keyAt(b); if (isChannel) { messageId |= ((long) -peer) << 32; } state.requery(); state.bindLong(1, messageId); state.bindInteger(2, forwards); state.bindLong(3, messageId); state.step(); } } state.dispose(); } if (!isEmpty(channelReplies)) { SQLitePreparedStatement state = database.executeFast("UPDATE messages SET replies_data = ? WHERE mid = ?"); for (int a = 0; a < channelReplies.size(); a++) { int peer = channelReplies.keyAt(a); SparseArray messages = channelReplies.get(peer); for (int b = 0; b < messages.size(); b++) { long messageId = messages.keyAt(b); if (isChannel) { messageId |= ((long) -peer) << 32; } boolean messageExists; TLRPC.TL_messageReplies currentReplies = null; SQLiteCursor cursor = database.queryFinalized("SELECT replies_data FROM messages WHERE mid = " + messageId); if (messageExists = cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { currentReplies = TLRPC.TL_messageReplies.TLdeserialize(data, data.readInt32(false), false); data.reuse(); } } cursor.dispose(); if (!messageExists) { continue; } TLRPC.TL_messageReplies replies = messages.get(messages.keyAt(b)); if (!addReply && currentReplies != null && currentReplies.replies_pts != 0 && replies.replies_pts <= currentReplies.replies_pts && replies.read_max_id <= currentReplies.read_max_id && replies.max_id <= currentReplies.max_id) { continue; } if (addReply) { if (currentReplies == null) { currentReplies = new TLRPC.TL_messageReplies(); currentReplies.flags |= 2; } currentReplies.replies += replies.replies; for (int c = 0, N = replies.recent_repliers.size(); c < N; c++) { int id = MessageObject.getPeerId(replies.recent_repliers.get(c)); for (int d = 0, N2 = currentReplies.recent_repliers.size(); d < N2; d++) { int id2 = MessageObject.getPeerId(currentReplies.recent_repliers.get(d)); if (id == id2) { currentReplies.recent_repliers.remove(d); d--; N2--; } } } currentReplies.recent_repliers.addAll(0, replies.recent_repliers); while (currentReplies.recent_repliers.size() > 3) { currentReplies.recent_repliers.remove(0); } replies = currentReplies; } if (currentReplies != null && currentReplies.read_max_id > replies.read_max_id) { replies.read_max_id = currentReplies.read_max_id; } state.requery(); NativeByteBuffer data = new NativeByteBuffer(replies.getObjectSize()); replies.serializeToStream(data); state.bindByteBuffer(1, data); state.bindLong(2, messageId); state.step(); data.reuse(); } } state.dispose(); } database.commitTransaction(); } catch (Exception e) { FileLog.e(e); } }); } private void updateRepliesMaxReadIdInternal(int chatId, int mid, int readMaxId) { try { SQLitePreparedStatement state = database.executeFast("UPDATE messages SET replies_data = ? WHERE mid = ?"); long messageId = mid | ((long) chatId) << 32; TLRPC.TL_messageReplies currentReplies = null; SQLiteCursor cursor = database.queryFinalized("SELECT replies_data FROM messages WHERE mid = " + messageId); if (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { currentReplies = TLRPC.TL_messageReplies.TLdeserialize(data, data.readInt32(false), false); data.reuse(); } } cursor.dispose(); if (currentReplies != null) { currentReplies.read_max_id = readMaxId; state.requery(); NativeByteBuffer data = new NativeByteBuffer(currentReplies.getObjectSize()); currentReplies.serializeToStream(data); state.bindByteBuffer(1, data); state.bindLong(2, messageId); state.step(); data.reuse(); } state.dispose(); } catch (Exception e) { FileLog.e(e); } } public void updateRepliesMaxReadId(int chatId, int mid, int readMaxId, boolean useQueue) { if (useQueue) { storageQueue.postRunnable(() -> updateRepliesMaxReadIdInternal(chatId, mid, readMaxId)); } else { updateRepliesMaxReadIdInternal(chatId, mid, readMaxId); } } public void updateRepliesCount(int chatId, int mid, ArrayList repliers, int maxId, int count) { storageQueue.postRunnable(() -> { try { SQLitePreparedStatement state = database.executeFast("UPDATE messages SET replies_data = ? WHERE mid = ?"); long messageId = mid | ((long) chatId) << 32; TLRPC.TL_messageReplies currentReplies = null; SQLiteCursor cursor = database.queryFinalized("SELECT replies_data FROM messages WHERE mid = " + messageId); if (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { currentReplies = TLRPC.TL_messageReplies.TLdeserialize(data, data.readInt32(false), false); data.reuse(); } } cursor.dispose(); if (currentReplies != null) { currentReplies.replies += count; if (currentReplies.replies < 0) { currentReplies.replies = 0; } if (repliers != null) { currentReplies.recent_repliers = repliers; currentReplies.flags |= 2; } if (maxId != 0) { currentReplies.max_id = maxId; } state.requery(); NativeByteBuffer data = new NativeByteBuffer(currentReplies.getObjectSize()); currentReplies.serializeToStream(data); state.bindByteBuffer(1, data); state.bindLong(2, messageId); state.step(); data.reuse(); } state.dispose(); } catch (Exception e) { FileLog.e(e); } }); } private boolean isValidKeyboardToSave(TLRPC.Message message) { return message.reply_markup != null && !(message.reply_markup instanceof TLRPC.TL_replyInlineMarkup) && (!message.reply_markup.selective || message.mentioned); } public void updateMessageVerifyFlags(ArrayList messages) { Utilities.stageQueue.postRunnable(() -> { try { database.beginTransaction(); SQLitePreparedStatement state = database.executeFast("UPDATE messages SET imp = ? WHERE mid = ?"); for (int a = 0, N = messages.size(); a < N; a++) { TLRPC.Message message = messages.get(a); state.requery(); int flags = 0; if (message.stickerVerified == 0) { flags |= 1; } else if (message.stickerVerified == 2) { flags |= 2; } state.bindInteger(1, flags); state.bindLong(2, message.id); state.step(); } state.dispose(); database.commitTransaction(); } catch (Exception e) { FileLog.e(e); } }); } private void putMessagesInternal(final ArrayList messages, final boolean withTransaction, final boolean doNotUpdateDialogDate, final int downloadMask, boolean ifNoLastMessage, boolean scheduled) { try { if (scheduled) { if (withTransaction) { database.beginTransaction(); } SQLitePreparedStatement state_messages = database.executeFast("REPLACE INTO scheduled_messages VALUES(?, ?, ?, ?, ?, ?, NULL)"); SQLitePreparedStatement state_randoms = database.executeFast("REPLACE INTO randoms VALUES(?, ?)"); ArrayList dialogsToUpdate = new ArrayList<>(); for (int a = 0; a < messages.size(); a++) { TLRPC.Message message = messages.get(a); if (message instanceof TLRPC.TL_messageEmpty) { continue; } fixUnsupportedMedia(message); state_messages.requery(); long messageId = message.id; if (message.local_id != 0) { messageId = message.local_id; } if (message.peer_id.channel_id != 0) { messageId |= ((long) message.peer_id.channel_id) << 32; } NativeByteBuffer data = new NativeByteBuffer(message.getObjectSize()); message.serializeToStream(data); long did = MessageObject.getDialogId(message); state_messages.bindLong(1, messageId); state_messages.bindLong(2, did); state_messages.bindInteger(3, message.send_state); state_messages.bindInteger(4, message.date); state_messages.bindByteBuffer(5, data); state_messages.bindInteger(6, message.ttl); state_messages.step(); if (message.random_id != 0) { state_randoms.requery(); state_randoms.bindLong(1, message.random_id); state_randoms.bindLong(2, messageId); state_randoms.step(); } data.reuse(); if (!dialogsToUpdate.contains(did)) { dialogsToUpdate.add(did); } } state_messages.dispose(); state_randoms.dispose(); if (withTransaction) { database.commitTransaction(); } for (int a = 0, N = dialogsToUpdate.size(); a < N; a++) { broadcastScheduledMessagesChange(dialogsToUpdate.get(a)); } } else { if (ifNoLastMessage) { TLRPC.Message lastMessage = messages.get(0); if (lastMessage.dialog_id == 0) { MessageObject.getDialogId(lastMessage); } int lastMid = -1; SQLiteCursor cursor = database.queryFinalized("SELECT last_mid FROM dialogs WHERE did = " + lastMessage.dialog_id); if (cursor.next()) { lastMid = cursor.intValue(0); } cursor.dispose(); if (lastMid != 0) { return; } } if (withTransaction) { database.beginTransaction(); } LongSparseArray messagesMap = new LongSparseArray<>(); LongSparseArray messagesCounts = new LongSparseArray<>(); LongSparseArray newMessagesCounts = new LongSparseArray<>(); LongSparseArray newMentionsCounts = new LongSparseArray<>(); LongSparseArray mentionCounts = new LongSparseArray<>(); SparseArray> mediaCounts = null; LongSparseArray botKeyboards = new LongSparseArray<>(); LongSparseArray messagesMediaIdsMap = null; LongSparseArray mediaTypesChange = null; StringBuilder messageMediaIds = null; LongSparseArray mediaTypes = null; StringBuilder messageIds = new StringBuilder(); LongSparseArray dialogsReadMax = new LongSparseArray<>(); LongSparseArray messagesIdsMap = new LongSparseArray<>(); LongSparseArray mentionsIdsMap = new LongSparseArray<>(); SQLitePreparedStatement state_messages = database.executeFast("REPLACE INTO messages VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?)"); SQLitePreparedStatement state_media = null; SQLitePreparedStatement state_randoms = database.executeFast("REPLACE INTO randoms VALUES(?, ?)"); SQLitePreparedStatement state_download = database.executeFast("REPLACE INTO download_queue VALUES(?, ?, ?, ?, ?)"); SQLitePreparedStatement state_webpage = database.executeFast("REPLACE INTO webpage_pending VALUES(?, ?)"); SQLitePreparedStatement state_polls = null; SQLitePreparedStatement state_tasks = null; int minDeleteTime = Integer.MAX_VALUE; for (int a = 0; a < messages.size(); a++) { TLRPC.Message message = messages.get(a); long messageId = message.id; if (message.dialog_id == 0) { MessageObject.getDialogId(message); } if (message.peer_id.channel_id != 0) { messageId |= ((long) message.peer_id.channel_id) << 32; } if (message.mentioned && message.media_unread) { mentionsIdsMap.put(messageId, message.dialog_id); } if (!(message.action instanceof TLRPC.TL_messageActionHistoryClear) && (!MessageObject.isOut(message) || message.from_scheduled) && (message.id > 0 || MessageObject.isUnread(message))) { Integer currentMaxId = dialogsReadMax.get(message.dialog_id); if (currentMaxId == null) { SQLiteCursor cursor = database.queryFinalized("SELECT inbox_max FROM dialogs WHERE did = " + message.dialog_id); if (cursor.next()) { currentMaxId = cursor.intValue(0); } else { currentMaxId = 0; } cursor.dispose(); dialogsReadMax.put(message.dialog_id, currentMaxId); } if (message.id < 0 || currentMaxId < message.id) { if (messageIds.length() > 0) { messageIds.append(","); } messageIds.append(messageId); messagesIdsMap.put(messageId, message.dialog_id); } } if (MediaDataController.canAddMessageToMedia(message)) { if (messageMediaIds == null) { messageMediaIds = new StringBuilder(); messagesMediaIdsMap = new LongSparseArray<>(); mediaTypes = new LongSparseArray<>(); } if (messageMediaIds.length() > 0) { messageMediaIds.append(","); } messageMediaIds.append(messageId); messagesMediaIdsMap.put(messageId, message.dialog_id); mediaTypes.put(messageId, MediaDataController.getMediaType(message)); } if (isValidKeyboardToSave(message)) { TLRPC.Message oldMessage = botKeyboards.get(message.dialog_id); if (oldMessage == null || oldMessage.id < message.id) { botKeyboards.put(message.dialog_id, message); } } } for (int a = 0; a < botKeyboards.size(); a++) { getMediaDataController().putBotKeyboard(botKeyboards.keyAt(a), botKeyboards.valueAt(a)); } if (messageMediaIds != null) { SQLiteCursor cursor = database.queryFinalized("SELECT mid, type FROM media_v2 WHERE mid IN(" + messageMediaIds.toString() + ")"); while (cursor.next()) { long mid = cursor.longValue(0); int type = cursor.intValue(1); if (type == mediaTypes.get(mid)) { messagesMediaIdsMap.remove(mid); } else { if (mediaTypesChange == null) { mediaTypesChange = new LongSparseArray<>(); } mediaTypesChange.put(mid, type); } } cursor.dispose(); mediaCounts = new SparseArray<>(); for (int a = 0; a < messagesMediaIdsMap.size(); a++) { long key = messagesMediaIdsMap.keyAt(a); long value = messagesMediaIdsMap.valueAt(a); Integer type = mediaTypes.get(key); LongSparseArray counts = mediaCounts.get(type); Integer count; if (counts == null) { counts = new LongSparseArray<>(); count = 0; mediaCounts.put(type, counts); } else { count = counts.get(value); } if (count == null) { count = 0; } count++; counts.put(value, count); if (mediaTypesChange != null) { int previousType = mediaTypesChange.get(key, -1); if (previousType >= 0) { counts = mediaCounts.get(previousType); if (counts == null) { counts = new LongSparseArray<>(); count = 0; mediaCounts.put(previousType, counts); } else { count = counts.get(value); } if (count == null) { count = 0; } count--; counts.put(value, count); } } } } if (messageIds.length() > 0) { SQLiteCursor cursor = database.queryFinalized("SELECT mid FROM messages WHERE mid IN(" + messageIds.toString() + ")"); while (cursor.next()) { long mid = cursor.longValue(0); messagesIdsMap.remove(mid); mentionsIdsMap.remove(mid); } cursor.dispose(); for (int a = 0; a < messagesIdsMap.size(); a++) { long dialog_id = messagesIdsMap.valueAt(a); Integer count = messagesCounts.get(dialog_id); if (count == null) { count = 0; } count++; messagesCounts.put(dialog_id, count); } for (int a = 0; a < mentionsIdsMap.size(); a++) { long dialog_id = mentionsIdsMap.valueAt(a); Integer count = mentionCounts.get(dialog_id); if (count == null) { count = 0; } count++; mentionCounts.put(dialog_id, count); } } int downloadMediaMask = 0; for (int a = 0; a < messages.size(); a++) { TLRPC.Message message = messages.get(a); fixUnsupportedMedia(message); state_messages.requery(); long messageId = message.id; if (message.local_id != 0) { messageId = message.local_id; } if (message.peer_id.channel_id != 0) { messageId |= ((long) message.peer_id.channel_id) << 32; } NativeByteBuffer data = new NativeByteBuffer(message.getObjectSize()); message.serializeToStream(data); boolean updateDialog = true; if (message.action instanceof TLRPC.TL_messageEncryptedAction && !(message.action.encryptedAction instanceof TLRPC.TL_decryptedMessageActionSetMessageTTL || message.action.encryptedAction instanceof TLRPC.TL_decryptedMessageActionScreenshotMessages)) { updateDialog = false; } else if (message.out) { SQLiteCursor cursor = database.queryFinalized("SELECT mid FROM messages WHERE mid = " + messageId); if (cursor.next()) { updateDialog = false; } cursor.dispose(); } if (updateDialog) { TLRPC.Message lastMessage = messagesMap.get(message.dialog_id); if (lastMessage == null || message.date > lastMessage.date || lastMessage.id > 0 && message.id > lastMessage.id || lastMessage.id < 0 && message.id < lastMessage.id) { messagesMap.put(message.dialog_id, message); } } state_messages.bindLong(1, messageId); state_messages.bindLong(2, message.dialog_id); state_messages.bindInteger(3, MessageObject.getUnreadFlags(message)); state_messages.bindInteger(4, message.send_state); state_messages.bindInteger(5, message.date); state_messages.bindByteBuffer(6, data); state_messages.bindInteger(7, (MessageObject.isOut(message) || message.from_scheduled ? 1 : 0)); state_messages.bindInteger(8, message.ttl); if ((message.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0) { state_messages.bindInteger(9, message.views); } else { state_messages.bindInteger(9, getMessageMediaType(message)); } int flags = 0; if (message.stickerVerified == 0) { flags |= 1; } else if (message.stickerVerified == 2) { flags |= 2; } state_messages.bindInteger(10, flags); state_messages.bindInteger(11, message.mentioned ? 1 : 0); state_messages.bindInteger(12, message.forwards); NativeByteBuffer repliesData = null; if (message.replies != null) { repliesData = new NativeByteBuffer(message.replies.getObjectSize()); message.replies.serializeToStream(repliesData); state_messages.bindByteBuffer(13, repliesData); } else { state_messages.bindNull(13); } if (message.reply_to != null) { state_messages.bindInteger(14, message.reply_to.reply_to_top_id != 0 ? message.reply_to.reply_to_top_id : message.reply_to.reply_to_msg_id); } else { state_messages.bindInteger(14, 0); } state_messages.step(); if (message.random_id != 0) { state_randoms.requery(); state_randoms.bindLong(1, message.random_id); state_randoms.bindLong(2, messageId); state_randoms.step(); } if (MediaDataController.canAddMessageToMedia(message)) { if (state_media == null) { state_media = database.executeFast("REPLACE INTO media_v2 VALUES(?, ?, ?, ?, ?)"); } state_media.requery(); state_media.bindLong(1, messageId); state_media.bindLong(2, message.dialog_id); state_media.bindInteger(3, message.date); state_media.bindInteger(4, MediaDataController.getMediaType(message)); state_media.bindByteBuffer(5, data); state_media.step(); } if (message.ttl_period != 0 && message.id > 0) { if (state_tasks == null) { state_tasks = database.executeFast("REPLACE INTO enc_tasks_v2 VALUES(?, ?, ?)"); } state_tasks.requery(); state_tasks.bindLong(1, messageId); state_tasks.bindInteger(2, message.date + message.ttl_period); state_tasks.bindInteger(3, 0); state_tasks.step(); minDeleteTime = Math.min(minDeleteTime, message.date + message.ttl_period); } if (message.media instanceof TLRPC.TL_messageMediaPoll) { if (state_polls == null) { state_polls = database.executeFast("REPLACE INTO polls VALUES(?, ?)"); } TLRPC.TL_messageMediaPoll mediaPoll = (TLRPC.TL_messageMediaPoll) message.media; state_polls.requery(); state_polls.bindLong(1, messageId); state_polls.bindLong(2, mediaPoll.poll.id); state_polls.step(); } else if (message.media instanceof TLRPC.TL_messageMediaWebPage) { state_webpage.requery(); state_webpage.bindLong(1, message.media.webpage.id); state_webpage.bindLong(2, messageId); state_webpage.step(); } if (repliesData != null) { repliesData.reuse(); } data.reuse(); if (downloadMask != 0 && (message.peer_id.channel_id == 0 || message.post) && message.date >= getConnectionsManager().getCurrentTime() - 60 * 60 && getDownloadController().canDownloadMedia(message) == 1) { if (message.media instanceof TLRPC.TL_messageMediaPhoto || message.media instanceof TLRPC.TL_messageMediaDocument || message.media instanceof TLRPC.TL_messageMediaWebPage) { int type = 0; long id = 0; TLRPC.MessageMedia object = null; TLRPC.Document document = MessageObject.getDocument(message); TLRPC.Photo photo = MessageObject.getPhoto(message); if (MessageObject.isVoiceMessage(message)) { id = document.id; type = DownloadController.AUTODOWNLOAD_TYPE_AUDIO; object = new TLRPC.TL_messageMediaDocument(); object.document = document; object.flags |= 1; } else if (MessageObject.isStickerMessage(message) || MessageObject.isAnimatedStickerMessage(message)) { id = document.id; type = DownloadController.AUTODOWNLOAD_TYPE_PHOTO; object = new TLRPC.TL_messageMediaDocument(); object.document = document; object.flags |= 1; } else if (MessageObject.isVideoMessage(message) || MessageObject.isRoundVideoMessage(message) || MessageObject.isGifMessage(message)) { id = document.id; type = DownloadController.AUTODOWNLOAD_TYPE_VIDEO; object = new TLRPC.TL_messageMediaDocument(); object.document = document; object.flags |= 1; } else if (document != null) { id = document.id; type = DownloadController.AUTODOWNLOAD_TYPE_DOCUMENT; object = new TLRPC.TL_messageMediaDocument(); object.document = document; object.flags |= 1; } else if (photo != null) { TLRPC.PhotoSize photoSize = FileLoader.getClosestPhotoSizeWithSize(photo.sizes, AndroidUtilities.getPhotoSize()); if (photoSize != null) { id = photo.id; type = DownloadController.AUTODOWNLOAD_TYPE_PHOTO; object = new TLRPC.TL_messageMediaPhoto(); object.photo = photo; object.flags |= 1; if (message.media instanceof TLRPC.TL_messageMediaWebPage) { object.flags |= 0x80000000; } } } if (object != null) { if (message.media.ttl_seconds != 0) { object.ttl_seconds = message.media.ttl_seconds; object.flags |= 4; } downloadMediaMask |= type; state_download.requery(); data = new NativeByteBuffer(object.getObjectSize()); object.serializeToStream(data); state_download.bindLong(1, id); state_download.bindInteger(2, type); state_download.bindInteger(3, message.date); state_download.bindByteBuffer(4, data); state_download.bindString(5, "sent_" + (message.peer_id != null ? message.peer_id.channel_id : 0) + "_" + message.id); state_download.step(); data.reuse(); } } } } state_messages.dispose(); if (state_media != null) { state_media.dispose(); } if (state_tasks != null) { state_tasks.dispose(); getMessagesController().didAddedNewTask(minDeleteTime, null); } if (state_polls != null) { state_polls.dispose(); } state_randoms.dispose(); state_download.dispose(); state_webpage.dispose(); SQLitePreparedStatement state_dialogs_replace = database.executeFast("REPLACE INTO dialogs VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); SQLitePreparedStatement state_dialogs_update = database.executeFast("UPDATE dialogs SET date = ?, unread_count = ?, last_mid = ?, unread_count_i = ? WHERE did = ?"); ArrayList dids = new ArrayList<>(); for (int a = 0; a < messagesMap.size(); a++) { long key = messagesMap.keyAt(a); if (key == 0) { continue; } TLRPC.Message message = messagesMap.valueAt(a); int channelId = 0; if (message != null) { channelId = message.peer_id.channel_id; } SQLiteCursor cursor = database.queryFinalized("SELECT date, unread_count, last_mid, unread_count_i FROM dialogs WHERE did = " + key); int dialog_date = 0; int last_mid = 0; int old_unread_count = 0; int old_mentions_count = 0; boolean exists; if (exists = cursor.next()) { dialog_date = cursor.intValue(0); old_unread_count = Math.max(0, cursor.intValue(1)); last_mid = cursor.intValue(2); old_mentions_count = Math.max(0, cursor.intValue(3)); } else if (channelId != 0) { getMessagesController().checkChatInviter(channelId, true); } cursor.dispose(); Integer mentions_count = mentionCounts.get(key); Integer unread_count = messagesCounts.get(key); if (unread_count == null) { unread_count = 0; } else { messagesCounts.put(key, unread_count + old_unread_count); } if (mentions_count == null) { mentions_count = 0; } else { mentionCounts.put(key, mentions_count + old_mentions_count); } long messageId = message != null ? message.id : last_mid; if (message != null) { if (message.local_id != 0) { messageId = message.local_id; } } if (channelId != 0) { messageId |= ((long) channelId) << 32; } if (old_unread_count == 0 && unread_count != 0) { newMessagesCounts.put(key, unread_count); } if (old_mentions_count == 0 && mentions_count != 0) { newMentionsCounts.put(key, mentions_count); } dids.add(key); if (exists) { state_dialogs_update.requery(); state_dialogs_update.bindInteger(1, message != null && (!doNotUpdateDialogDate || dialog_date == 0) ? message.date : dialog_date); state_dialogs_update.bindInteger(2, old_unread_count + unread_count); state_dialogs_update.bindLong(3, messageId); state_dialogs_update.bindInteger(4, old_mentions_count + mentions_count); state_dialogs_update.bindLong(5, key); state_dialogs_update.step(); } else { state_dialogs_replace.requery(); state_dialogs_replace.bindLong(1, key); state_dialogs_replace.bindInteger(2, message != null && (!doNotUpdateDialogDate || dialog_date == 0) ? message.date : dialog_date); state_dialogs_replace.bindInteger(3, old_unread_count + unread_count); state_dialogs_replace.bindLong(4, messageId); state_dialogs_replace.bindInteger(5, 0); state_dialogs_replace.bindInteger(6, 0); state_dialogs_replace.bindLong(7, 0); state_dialogs_replace.bindInteger(8, old_mentions_count + mentions_count); state_dialogs_replace.bindInteger(9, channelId != 0 ? 1 : 0); state_dialogs_replace.bindInteger(10, 0); state_dialogs_replace.bindInteger(11, 0); state_dialogs_replace.bindInteger(12, 0); state_dialogs_replace.bindInteger(13, 0); state_dialogs_replace.bindNull(14); state_dialogs_replace.step(); unknownDialogsIds.put(key, true); } } state_dialogs_update.dispose(); state_dialogs_replace.dispose(); if (mediaCounts != null) { state_randoms = database.executeFast("REPLACE INTO media_counts_v2 VALUES(?, ?, ?, ?)"); for (int a = 0; a < mediaCounts.size(); a++) { int type = mediaCounts.keyAt(a); LongSparseArray value = mediaCounts.valueAt(a); for (int b = 0; b < value.size(); b++) { long uid = value.keyAt(b); int lower_part = (int) uid; int count = -1; int old = 0; SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT count, old FROM media_counts_v2 WHERE uid = %d AND type = %d LIMIT 1", uid, type)); if (cursor.next()) { count = cursor.intValue(0); old = cursor.intValue(1); } cursor.dispose(); if (count != -1) { state_randoms.requery(); count += value.valueAt(b); state_randoms.bindLong(1, uid); state_randoms.bindInteger(2, type); state_randoms.bindInteger(3, Math.max(0, count)); state_randoms.bindInteger(4, old); state_randoms.step(); } } } state_randoms.dispose(); } if (withTransaction) { database.commitTransaction(); } updateFiltersReadCounter(newMessagesCounts, newMentionsCounts, false); getMessagesController().processDialogsUpdateRead(messagesCounts, mentionCounts); if (downloadMediaMask != 0) { final int downloadMediaMaskFinal = downloadMediaMask; AndroidUtilities.runOnUIThread(() -> getDownloadController().newDownloadObjectsAvailable(downloadMediaMaskFinal)); } updateWidgets(dids); } } catch (Exception e) { FileLog.e(e); } } public void putMessages(final ArrayList messages, final boolean withTransaction, boolean useQueue, final boolean doNotUpdateDialogDate, final int downloadMask, boolean scheduled) { putMessages(messages, withTransaction, useQueue, doNotUpdateDialogDate, downloadMask, false, scheduled); } public void putMessages(final ArrayList messages, final boolean withTransaction, boolean useQueue, final boolean doNotUpdateDialogDate, final int downloadMask, final boolean ifNoLastMessage, boolean scheduled) { if (messages.size() == 0) { return; } if (useQueue) { storageQueue.postRunnable(() -> putMessagesInternal(messages, withTransaction, doNotUpdateDialogDate, downloadMask, ifNoLastMessage, scheduled)); } else { putMessagesInternal(messages, withTransaction, doNotUpdateDialogDate, downloadMask, ifNoLastMessage, scheduled); } } public void markMessageAsSendError(final TLRPC.Message message, boolean scheduled) { storageQueue.postRunnable(() -> { try { long messageId = message.id; if (message.peer_id.channel_id != 0) { messageId |= ((long) message.peer_id.channel_id) << 32; } if (scheduled) { database.executeFast("UPDATE scheduled_messages SET send_state = 2 WHERE mid = " + messageId).stepThis().dispose(); } else { database.executeFast("UPDATE messages SET send_state = 2 WHERE mid = " + messageId).stepThis().dispose(); } } catch (Exception e) { FileLog.e(e); } }); } public void setMessageSeq(final int mid, final int seq_in, final int seq_out) { storageQueue.postRunnable(() -> { try { SQLitePreparedStatement state = database.executeFast("REPLACE INTO messages_seq VALUES(?, ?, ?)"); state.requery(); state.bindInteger(1, mid); state.bindInteger(2, seq_in); state.bindInteger(3, seq_out); state.step(); state.dispose(); } catch (Exception e) { FileLog.e(e); } }); } private long[] updateMessageStateAndIdInternal(long random_id, Long _oldId, int newId, int date, int channelId, int scheduled) { SQLiteCursor cursor = null; long oldMessageId; long newMessageId = newId; if (_oldId == null) { try { cursor = database.queryFinalized(String.format(Locale.US, "SELECT mid FROM randoms WHERE random_id = %d LIMIT 1", random_id)); if (cursor.next()) { _oldId = cursor.longValue(0); } } catch (Exception e) { FileLog.e(e); } finally { if (cursor != null) { cursor.dispose(); } } if (_oldId == null) { return null; } if (channelId == 0) { channelId = (int) (_oldId >> 32); } } oldMessageId = _oldId; if (channelId != 0) { oldMessageId |= ((long) channelId) << 32; newMessageId |= ((long) channelId) << 32; } if (_oldId < 0 && scheduled == 1) { SQLitePreparedStatement state = null; try { state = database.executeFast("UPDATE randoms SET mid = ? WHERE random_id = ? and mid = ?"); state.bindLong(1, newMessageId); state.bindLong(2, random_id); state.bindLong(3, oldMessageId); state.step(); } catch (Exception e) { FileLog.e(e); } finally { if (state != null) { state.dispose(); } } } else if (_oldId > 0) { TLRPC.TL_updateDeleteScheduledMessages update = new TLRPC.TL_updateDeleteScheduledMessages(); update.messages.add((int) oldMessageId); if (channelId != 0) { update.peer = new TLRPC.TL_peerChannel(); update.peer.channel_id = channelId; } else { update.peer = new TLRPC.TL_peerUser(); } TLRPC.TL_updates updates = new TLRPC.TL_updates(); updates.updates.add(update); Utilities.stageQueue.postRunnable(() -> getMessagesController().processUpdates(updates, false)); try { database.executeFast(String.format(Locale.US, "DELETE FROM randoms WHERE random_id = %d AND mid = %d", random_id, _oldId)).stepThis().dispose(); } catch (Exception e) { FileLog.e(e); } return null; } long did = 0; if (scheduled == -1 || scheduled == 0) { try { cursor = database.queryFinalized(String.format(Locale.US, "SELECT uid FROM messages WHERE mid = %d LIMIT 1", oldMessageId)); if (cursor.next()) { did = cursor.longValue(0); scheduled = 0; } } catch (Exception e) { FileLog.e(e); } finally { if (cursor != null) { cursor.dispose(); } } } if (scheduled == -1 || scheduled == 1) { try { cursor = database.queryFinalized(String.format(Locale.US, "SELECT uid FROM scheduled_messages WHERE mid = %d LIMIT 1", oldMessageId)); if (cursor.next()) { did = cursor.longValue(0); scheduled = 1; } } catch (Exception e) { FileLog.e(e); } finally { if (cursor != null) { cursor.dispose(); } } } if (did == 0) { return null; } SQLitePreparedStatement state = null; if (oldMessageId == newMessageId && date != 0) { try { if (scheduled == 0) { state = database.executeFast("UPDATE messages SET send_state = 0, date = ? WHERE mid = ?"); } else { state = database.executeFast("UPDATE scheduled_messages SET send_state = 0, date = ? WHERE mid = ?"); } state.bindInteger(1, date); state.bindLong(2, newMessageId); state.step(); } catch (Exception e) { FileLog.e(e); } finally { if (state != null) { state.dispose(); } } return new long[]{did, newId}; } else { if (scheduled == 0) { try { state = database.executeFast("UPDATE messages SET mid = ?, send_state = 0 WHERE mid = ?"); state.bindLong(1, newMessageId); state.bindLong(2, oldMessageId); state.step(); } catch (Exception e) { try { database.executeFast(String.format(Locale.US, "DELETE FROM messages WHERE mid = %d", oldMessageId)).stepThis().dispose(); database.executeFast(String.format(Locale.US, "DELETE FROM messages_seq WHERE mid = %d", oldMessageId)).stepThis().dispose(); } catch (Exception e2) { FileLog.e(e2); } } finally { if (state != null) { state.dispose(); state = null; } } try { state = database.executeFast("UPDATE media_v2 SET mid = ? WHERE mid = ?"); state.bindLong(1, newMessageId); state.bindLong(2, oldMessageId); state.step(); } catch (Exception e) { try { database.executeFast(String.format(Locale.US, "DELETE FROM media_v2 WHERE mid = %d", oldMessageId)).stepThis().dispose(); } catch (Exception e2) { FileLog.e(e2); } } finally { if (state != null) { state.dispose(); state = null; } } try { state = database.executeFast("UPDATE dialogs SET last_mid = ? WHERE last_mid = ?"); state.bindLong(1, newMessageId); state.bindLong(2, oldMessageId); state.step(); } catch (Exception e) { FileLog.e(e); } finally { if (state != null) { state.dispose(); } } } else if (scheduled == 1) { try { state = database.executeFast("UPDATE scheduled_messages SET mid = ?, send_state = 0 WHERE mid = ?"); state.bindLong(1, newMessageId); state.bindLong(2, oldMessageId); state.step(); } catch (Exception e) { try { database.executeFast(String.format(Locale.US, "DELETE FROM scheduled_messages WHERE mid = %d", oldMessageId)).stepThis().dispose(); } catch (Exception e2) { FileLog.e(e2); } } finally { if (state != null) { state.dispose(); } } } return new long[]{did, _oldId}; } } public long[] updateMessageStateAndId(final long random_id, final Long _oldId, final int newId, final int date, boolean useQueue, final int channelId, int scheduled) { if (useQueue) { storageQueue.postRunnable(() -> updateMessageStateAndIdInternal(random_id, _oldId, newId, date, channelId, scheduled)); } else { return updateMessageStateAndIdInternal(random_id, _oldId, newId, date, channelId, scheduled); } return null; } private void updateUsersInternal(final ArrayList users, final boolean onlyStatus, final boolean withTransaction) { try { if (onlyStatus) { if (withTransaction) { database.beginTransaction(); } SQLitePreparedStatement state = database.executeFast("UPDATE users SET status = ? WHERE uid = ?"); for (int a = 0, N = users.size(); a < N; a++) { TLRPC.User user = users.get(a); state.requery(); if (user.status != null) { state.bindInteger(1, user.status.expires); } else { state.bindInteger(1, 0); } state.bindInteger(2, user.id); state.step(); } state.dispose(); if (withTransaction) { database.commitTransaction(); } } else { StringBuilder ids = new StringBuilder(); SparseArray usersDict = new SparseArray<>(); for (int a = 0, N = users.size(); a < N; a++) { TLRPC.User user = users.get(a); if (ids.length() != 0) { ids.append(","); } ids.append(user.id); usersDict.put(user.id, user); } ArrayList loadedUsers = new ArrayList<>(); getUsersInternal(ids.toString(), loadedUsers); for (int a = 0, N = loadedUsers.size(); a < N; a++) { TLRPC.User user = loadedUsers.get(a); TLRPC.User updateUser = usersDict.get(user.id); if (updateUser != null) { if (updateUser.first_name != null && updateUser.last_name != null) { if (!UserObject.isContact(user)) { user.first_name = updateUser.first_name; user.last_name = updateUser.last_name; } user.username = updateUser.username; } else if (updateUser.photo != null) { user.photo = updateUser.photo; } else if (updateUser.phone != null) { user.phone = updateUser.phone; } } } if (!loadedUsers.isEmpty()) { if (withTransaction) { database.beginTransaction(); } putUsersInternal(loadedUsers); if (withTransaction) { database.commitTransaction(); } } } } catch (Exception e) { FileLog.e(e); } } public void updateUsers(final ArrayList users, final boolean onlyStatus, final boolean withTransaction, boolean useQueue) { if (users == null || users.isEmpty()) { return; } if (useQueue) { storageQueue.postRunnable(() -> updateUsersInternal(users, onlyStatus, withTransaction)); } else { updateUsersInternal(users, onlyStatus, withTransaction); } } private void markMessagesAsReadInternal(SparseLongArray inbox, SparseLongArray outbox, SparseIntArray encryptedMessages) { try { if (!isEmpty(inbox)) { SQLitePreparedStatement state = database.executeFast("DELETE FROM unread_push_messages WHERE uid = ? AND mid <= ?"); for (int b = 0; b < inbox.size(); b++) { int key = inbox.keyAt(b); long messageId = inbox.get(key); database.executeFast(String.format(Locale.US, "UPDATE messages SET read_state = read_state | 1 WHERE uid = %d AND mid > 0 AND mid <= %d AND read_state IN(0,2) AND out = 0", key, messageId)).stepThis().dispose(); state.requery(); state.bindLong(1, key); state.bindLong(2, messageId); state.step(); } state.dispose(); } if (!isEmpty(outbox)) { for (int b = 0; b < outbox.size(); b++) { int key = outbox.keyAt(b); long messageId = outbox.get(key); database.executeFast(String.format(Locale.US, "UPDATE messages SET read_state = read_state | 1 WHERE uid = %d AND mid > 0 AND mid <= %d AND read_state IN(0,2) AND out = 1", key, messageId)).stepThis().dispose(); } } if (encryptedMessages != null && !isEmpty(encryptedMessages)) { for (int a = 0; a < encryptedMessages.size(); a++) { long dialog_id = ((long) encryptedMessages.keyAt(a)) << 32; int max_date = encryptedMessages.valueAt(a); SQLitePreparedStatement state = database.executeFast("UPDATE messages SET read_state = read_state | 1 WHERE uid = ? AND date <= ? AND read_state IN(0,2) AND out = 1"); state.requery(); state.bindLong(1, dialog_id); state.bindInteger(2, max_date); state.step(); state.dispose(); } } } catch (Exception e) { FileLog.e(e); } } public void markMessagesContentAsRead(final ArrayList mids, final int date) { if (isEmpty(mids)) { return; } storageQueue.postRunnable(() -> { try { String midsStr = TextUtils.join(",", mids); database.executeFast(String.format(Locale.US, "UPDATE messages SET read_state = read_state | 2 WHERE mid IN (%s)", midsStr)).stepThis().dispose(); if (date != 0) { SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT mid, ttl FROM messages WHERE mid IN (%s) AND ttl > 0", midsStr)); ArrayList arrayList = null; while (cursor.next()) { if (arrayList == null) { arrayList = new ArrayList<>(); } arrayList.add(cursor.intValue(0)); } if (arrayList != null) { emptyMessagesMedia(arrayList); } cursor.dispose(); } } catch (Exception e) { FileLog.e(e); } }); } public void markMessagesAsRead(final SparseLongArray inbox, final SparseLongArray outbox, final SparseIntArray encryptedMessages, boolean useQueue) { if (useQueue) { storageQueue.postRunnable(() -> markMessagesAsReadInternal(inbox, outbox, encryptedMessages)); } else { markMessagesAsReadInternal(inbox, outbox, encryptedMessages); } } public void markMessagesAsDeletedByRandoms(final ArrayList messages) { if (messages.isEmpty()) { return; } storageQueue.postRunnable(() -> { try { String ids = TextUtils.join(",", messages); SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT mid FROM randoms WHERE random_id IN(%s)", ids)); final ArrayList mids = new ArrayList<>(); while (cursor.next()) { mids.add(cursor.intValue(0)); } cursor.dispose(); if (!mids.isEmpty()) { AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.messagesDeleted, mids, 0, false)); updateDialogsWithReadMessagesInternal(mids, null, null, null); markMessagesAsDeletedInternal(mids, 0, true, false); updateDialogsWithDeletedMessagesInternal(mids, null, 0); } } catch (Exception e) { FileLog.e(e); } }); } protected void deletePushMessages(long dialogId, final ArrayList messages) { try { database.executeFast(String.format(Locale.US, "DELETE FROM unread_push_messages WHERE uid = %d AND mid IN(%s)", dialogId, TextUtils.join(",", messages))).stepThis().dispose(); } catch (Exception e) { FileLog.e(e); } } private void broadcastScheduledMessagesChange(Long did) { try { SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT COUNT(mid) FROM scheduled_messages WHERE uid = %d", did)); int count; if (cursor.next()) { count = cursor.intValue(0); } else { count = 0; } cursor.dispose(); AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.scheduledMessagesUpdated, did, count)); } catch (Exception e) { FileLog.e(e); } } private ArrayList markMessagesAsDeletedInternal(final ArrayList messages, int channelId, boolean deleteFiles, boolean scheduled) { try { ArrayList dialogsIds = new ArrayList<>(); if (scheduled) { String ids; if (channelId != 0) { StringBuilder builder = new StringBuilder(messages.size()); for (int a = 0; a < messages.size(); a++) { long messageId = messages.get(a); messageId |= ((long) channelId) << 32; if (builder.length() > 0) { builder.append(','); } builder.append(messageId); } ids = builder.toString(); } else { ids = TextUtils.join(",", messages); } ArrayList dialogsToUpdate = new ArrayList<>(); SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT uid FROM scheduled_messages WHERE mid IN(%s)", ids)); try { while (cursor.next()) { long did = cursor.longValue(0); if (!dialogsToUpdate.contains(did)) { dialogsToUpdate.add(did); } } } catch (Exception e) { FileLog.e(e); } cursor.dispose(); database.executeFast(String.format(Locale.US, "DELETE FROM scheduled_messages WHERE mid IN(%s)", ids)).stepThis().dispose(); for (int a = 0, N = dialogsToUpdate.size(); a < N; a++) { broadcastScheduledMessagesChange(dialogsToUpdate.get(a)); } } else { String ids; final ArrayList temp = new ArrayList<>(messages); LongSparseArray dialogsToUpdate = new LongSparseArray<>(); LongSparseArray> messagesByDialogs = new LongSparseArray<>(); if (channelId != 0) { StringBuilder builder = new StringBuilder(messages.size()); for (int a = 0; a < messages.size(); a++) { long messageId = messages.get(a); messageId |= ((long) channelId) << 32; if (builder.length() > 0) { builder.append(','); } builder.append(messageId); } ids = builder.toString(); } else { ids = TextUtils.join(",", messages); } ArrayList filesToDelete = new ArrayList<>(); ArrayList namesToDelete = new ArrayList<>(); ArrayList> idsToDelete = new ArrayList<>(); int currentUser = getUserConfig().getClientUserId(); SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT uid, data, read_state, out, mention, mid FROM messages WHERE mid IN(%s)", ids)); try { while (cursor.next()) { long did = cursor.longValue(0); int mid = cursor.intValue(5); temp.remove((Integer) mid); ArrayList mids = messagesByDialogs.get(did); if (mids == null) { mids = new ArrayList<>(); messagesByDialogs.put(did, mids); } mids.add(mid); if (did != currentUser) { int read_state = cursor.intValue(2); if (cursor.intValue(3) == 0) { Integer[] unread_count = dialogsToUpdate.get(did); if (unread_count == null) { unread_count = new Integer[]{0, 0}; dialogsToUpdate.put(did, unread_count); } if (read_state < 2) { unread_count[1]++; } if (read_state == 0 || read_state == 2) { unread_count[0]++; } } } if ((int) did != 0 && !deleteFiles) { continue; } NativeByteBuffer data = cursor.byteBufferValue(1); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); addFilesToDelete(message, filesToDelete, idsToDelete, namesToDelete, false); } } } catch (Exception e) { FileLog.e(e); } cursor.dispose(); deleteFromDownloadQueue(idsToDelete, true); AndroidUtilities.runOnUIThread(() -> getFileLoader().cancelLoadFiles(namesToDelete)); getFileLoader().deleteFiles(filesToDelete, 0); for (int a = 0; a < dialogsToUpdate.size(); a++) { long did = dialogsToUpdate.keyAt(a); Integer[] counts = dialogsToUpdate.valueAt(a); cursor = database.queryFinalized("SELECT unread_count, unread_count_i FROM dialogs WHERE did = " + did); int old_unread_count = 0; int old_mentions_count = 0; if (cursor.next()) { old_unread_count = cursor.intValue(0); old_mentions_count = cursor.intValue(1); } cursor.dispose(); dialogsIds.add(did); SQLitePreparedStatement state = database.executeFast("UPDATE dialogs SET unread_count = ?, unread_count_i = ? WHERE did = ?"); state.requery(); state.bindInteger(1, Math.max(0, old_unread_count - counts[0])); state.bindInteger(2, Math.max(0, old_mentions_count - counts[1])); state.bindLong(3, did); state.step(); state.dispose(); } for (int a = 0, N = messagesByDialogs.size(); a < N; a++) { long did = messagesByDialogs.keyAt(a); ArrayList mids = messagesByDialogs.valueAt(a); int lowerId = (int) did; String idsStr = TextUtils.join(",", mids); if (lowerId != 0) { if (lowerId < 0) { database.executeFast(String.format(Locale.US, "UPDATE chat_settings_v2 SET pinned = 0 WHERE uid = %d AND pinned IN (%s)", -lowerId, idsStr)).stepThis().dispose(); } else { database.executeFast(String.format(Locale.US, "UPDATE user_settings SET pinned = 0 WHERE uid = %d AND pinned IN (%s)", lowerId, idsStr)).stepThis().dispose(); } } database.executeFast(String.format(Locale.US, "DELETE FROM chat_pinned_v2 WHERE uid = %d AND mid IN(%s)", did, idsStr)).stepThis().dispose(); int updatedCount = 0; cursor = database.queryFinalized("SELECT changes()"); if (cursor.next()) { updatedCount = cursor.intValue(0); } cursor.dispose(); if (updatedCount > 0) { cursor = database.queryFinalized(String.format(Locale.US, "SELECT count FROM chat_pinned_count WHERE uid = %d", did)); if (cursor.next()) { int count = cursor.intValue(0); SQLitePreparedStatement state = database.executeFast("UPDATE chat_pinned_count SET count = ? WHERE uid = ?"); state.requery(); state.bindInteger(1, Math.max(0, count - updatedCount)); state.bindLong(2, did); state.step(); state.dispose(); } cursor.dispose(); } } database.executeFast(String.format(Locale.US, "DELETE FROM messages WHERE mid IN(%s)", ids)).stepThis().dispose(); database.executeFast(String.format(Locale.US, "DELETE FROM polls WHERE mid IN(%s)", ids)).stepThis().dispose(); database.executeFast(String.format(Locale.US, "DELETE FROM bot_keyboard WHERE mid IN(%s)", ids)).stepThis().dispose(); database.executeFast(String.format(Locale.US, "DELETE FROM messages_seq WHERE mid IN(%s)", ids)).stepThis().dispose(); if (temp.isEmpty()) { cursor = database.queryFinalized(String.format(Locale.US, "SELECT uid, type FROM media_v2 WHERE mid IN(%s)", ids)); SparseArray> mediaCounts = null; while (cursor.next()) { long uid = cursor.longValue(0); int type = cursor.intValue(1); if (mediaCounts == null) { mediaCounts = new SparseArray<>(); } LongSparseArray counts = mediaCounts.get(type); Integer count; if (counts == null) { counts = new LongSparseArray<>(); count = 0; mediaCounts.put(type, counts); } else { count = counts.get(uid); } if (count == null) { count = 0; } count++; counts.put(uid, count); } cursor.dispose(); if (mediaCounts != null) { SQLitePreparedStatement state = database.executeFast("REPLACE INTO media_counts_v2 VALUES(?, ?, ?, ?)"); for (int a = 0; a < mediaCounts.size(); a++) { int type = mediaCounts.keyAt(a); LongSparseArray value = mediaCounts.valueAt(a); for (int b = 0; b < value.size(); b++) { long uid = value.keyAt(b); int lower_part = (int) uid; int count = -1; int old = 0; cursor = database.queryFinalized(String.format(Locale.US, "SELECT count, old FROM media_counts_v2 WHERE uid = %d AND type = %d LIMIT 1", uid, type)); if (cursor.next()) { count = cursor.intValue(0); old = cursor.intValue(1); } cursor.dispose(); if (count != -1) { state.requery(); count = Math.max(0, count - value.valueAt(b)); state.bindLong(1, uid); state.bindInteger(2, type); state.bindInteger(3, count); state.bindInteger(4, old); state.step(); } } } state.dispose(); } } else { if (channelId == 0) { database.executeFast("UPDATE media_counts_v2 SET old = 1 WHERE 1").stepThis().dispose(); } else { database.executeFast(String.format(Locale.US, "UPDATE media_counts_v2 SET old = 1 WHERE uid = %d", -channelId)).stepThis().dispose(); } } database.executeFast(String.format(Locale.US, "DELETE FROM media_v2 WHERE mid IN(%s)", ids)).stepThis().dispose(); getMediaDataController().clearBotKeyboard(0, messages); if (dialogsToUpdate.size() != 0) { resetAllUnreadCounters(false); } updateWidgets(dialogsIds); } return dialogsIds; } catch (Exception e) { FileLog.e(e); } return null; } private void updateDialogsWithDeletedMessagesInternal(final ArrayList messages, ArrayList additionalDialogsToUpdate, int channelId) { try { String ids; ArrayList dialogsToUpdate = new ArrayList<>(); if (!messages.isEmpty()) { SQLitePreparedStatement state; if (channelId != 0) { dialogsToUpdate.add((long) -channelId); state = database.executeFast("UPDATE dialogs SET last_mid = (SELECT mid FROM messages WHERE uid = ? AND date = (SELECT MAX(date) FROM messages WHERE uid = ?)) WHERE did = ?"); } else { ids = TextUtils.join(",", messages); SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT did FROM dialogs WHERE last_mid IN(%s)", ids)); while (cursor.next()) { dialogsToUpdate.add(cursor.longValue(0)); } cursor.dispose(); state = database.executeFast("UPDATE dialogs SET last_mid = (SELECT mid FROM messages WHERE uid = ? AND date = (SELECT MAX(date) FROM messages WHERE uid = ? AND date != 0)) WHERE did = ?"); } database.beginTransaction(); for (int a = 0; a < dialogsToUpdate.size(); a++) { long did = dialogsToUpdate.get(a); state.requery(); state.bindLong(1, did); state.bindLong(2, did); state.bindLong(3, did); state.step(); } state.dispose(); database.commitTransaction(); } else { dialogsToUpdate.add((long) -channelId); } if (additionalDialogsToUpdate != null) { for (int a = 0; a < additionalDialogsToUpdate.size(); a++) { Long did = additionalDialogsToUpdate.get(a); if (!dialogsToUpdate.contains(did)) { dialogsToUpdate.add(did); } } } ids = TextUtils.join(",", dialogsToUpdate); TLRPC.messages_Dialogs dialogs = new TLRPC.TL_messages_dialogs(); ArrayList encryptedChats = new ArrayList<>(); ArrayList usersToLoad = new ArrayList<>(); ArrayList chatsToLoad = new ArrayList<>(); ArrayList encryptedToLoad = new ArrayList<>(); SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT d.did, d.last_mid, d.unread_count, d.date, m.data, m.read_state, m.mid, m.send_state, m.date, d.pts, d.inbox_max, d.outbox_max, d.pinned, d.unread_count_i, d.flags, d.folder_id, d.data FROM dialogs as d LEFT JOIN messages as m ON d.last_mid = m.mid WHERE d.did IN(%s)", ids)); while (cursor.next()) { long dialogId = cursor.longValue(0); TLRPC.Dialog dialog; if (DialogObject.isFolderDialogId(dialogId)) { TLRPC.TL_dialogFolder dialogFolder = new TLRPC.TL_dialogFolder(); if (!cursor.isNull(16)) { NativeByteBuffer data = cursor.byteBufferValue(16); if (data != null) { dialogFolder.folder = TLRPC.TL_folder.TLdeserialize(data, data.readInt32(false), false); data.reuse(); } else { dialogFolder.folder = new TLRPC.TL_folder(); dialogFolder.folder.id = cursor.intValue(15); } } dialog = dialogFolder; } else { dialog = new TLRPC.TL_dialog(); } dialog.id = dialogId; dialog.top_message = cursor.intValue(1); dialog.read_inbox_max_id = cursor.intValue(10); dialog.read_outbox_max_id = cursor.intValue(11); dialog.unread_count = cursor.intValue(2); dialog.unread_mentions_count = cursor.intValue(13); dialog.last_message_date = cursor.intValue(3); dialog.pts = cursor.intValue(9); dialog.flags = channelId == 0 ? 0 : 1; dialog.pinnedNum = cursor.intValue(12); dialog.pinned = dialog.pinnedNum != 0; int dialog_flags = cursor.intValue(14); dialog.unread_mark = (dialog_flags & 1) != 0; dialog.folder_id = cursor.intValue(15); dialogs.dialogs.add(dialog); NativeByteBuffer data = cursor.byteBufferValue(4); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); MessageObject.setUnreadFlags(message, cursor.intValue(5)); message.id = cursor.intValue(6); message.send_state = cursor.intValue(7); int date = cursor.intValue(8); if (date != 0) { dialog.last_message_date = date; } message.dialog_id = dialog.id; dialogs.messages.add(message); addUsersAndChatsFromMessage(message, usersToLoad, chatsToLoad); } int lower_id = (int) dialog.id; int high_id = (int) (dialog.id >> 32); if (lower_id != 0) { if (lower_id > 0) { if (!usersToLoad.contains(lower_id)) { usersToLoad.add(lower_id); } } else { if (!chatsToLoad.contains(-lower_id)) { chatsToLoad.add(-lower_id); } } } else { if (!encryptedToLoad.contains(high_id)) { encryptedToLoad.add(high_id); } } } cursor.dispose(); if (!encryptedToLoad.isEmpty()) { getEncryptedChatsInternal(TextUtils.join(",", encryptedToLoad), encryptedChats, usersToLoad); } if (!chatsToLoad.isEmpty()) { getChatsInternal(TextUtils.join(",", chatsToLoad), dialogs.chats); } if (!usersToLoad.isEmpty()) { getUsersInternal(TextUtils.join(",", usersToLoad), dialogs.users); } if (!dialogs.dialogs.isEmpty() || !encryptedChats.isEmpty()) { getMessagesController().processDialogsUpdate(dialogs, encryptedChats, true); } } catch (Exception e) { FileLog.e(e); } } public void updateDialogsWithDeletedMessages(final ArrayList messages, final ArrayList additionalDialogsToUpdate, boolean useQueue, final int channelId) { if (messages.isEmpty() && channelId == 0) { return; } if (useQueue) { storageQueue.postRunnable(() -> updateDialogsWithDeletedMessagesInternal(messages, additionalDialogsToUpdate, channelId)); } else { updateDialogsWithDeletedMessagesInternal(messages, additionalDialogsToUpdate, channelId); } } public ArrayList markMessagesAsDeleted(final ArrayList messages, boolean useQueue, final int channelId, boolean deleteFiles, boolean scheduled) { if (messages.isEmpty()) { return null; } if (useQueue) { storageQueue.postRunnable(() -> markMessagesAsDeletedInternal(messages, channelId, deleteFiles, scheduled)); } else { return markMessagesAsDeletedInternal(messages, channelId, deleteFiles, scheduled); } return null; } private ArrayList markMessagesAsDeletedInternal(final int channelId, final int mid, boolean deleteFiles) { try { String ids; ArrayList dialogsIds = new ArrayList<>(); LongSparseArray dialogsToUpdate = new LongSparseArray<>(); long maxMessageId = mid; maxMessageId |= ((long) channelId) << 32; ArrayList filesToDelete = new ArrayList<>(); ArrayList namesToDelete = new ArrayList<>(); ArrayList> idsToDelete = new ArrayList<>(); int currentUser = getUserConfig().getClientUserId(); SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT uid, data, read_state, out, mention FROM messages WHERE uid = %d AND mid <= %d", -channelId, maxMessageId)); try { while (cursor.next()) { long did = cursor.longValue(0); if (did != currentUser) { int read_state = cursor.intValue(2); if (cursor.intValue(3) == 0) { Integer[] unread_count = dialogsToUpdate.get(did); if (unread_count == null) { unread_count = new Integer[]{0, 0}; dialogsToUpdate.put(did, unread_count); } if (read_state < 2) { unread_count[1]++; } if (read_state == 0 || read_state == 2) { unread_count[0]++; } } } if ((int) did != 0 && !deleteFiles) { continue; } NativeByteBuffer data = cursor.byteBufferValue(1); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); addFilesToDelete(message, filesToDelete, idsToDelete, namesToDelete, false); } } } catch (Exception e) { FileLog.e(e); } cursor.dispose(); deleteFromDownloadQueue(idsToDelete, true); AndroidUtilities.runOnUIThread(() -> getFileLoader().cancelLoadFiles(namesToDelete)); getFileLoader().deleteFiles(filesToDelete, 0); for (int a = 0; a < dialogsToUpdate.size(); a++) { long did = dialogsToUpdate.keyAt(a); Integer[] counts = dialogsToUpdate.valueAt(a); cursor = database.queryFinalized("SELECT unread_count, unread_count_i FROM dialogs WHERE did = " + did); int old_unread_count = 0; int old_mentions_count = 0; if (cursor.next()) { old_unread_count = cursor.intValue(0); old_mentions_count = cursor.intValue(1); } cursor.dispose(); dialogsIds.add(did); SQLitePreparedStatement state = database.executeFast("UPDATE dialogs SET unread_count = ?, unread_count_i = ? WHERE did = ?"); state.requery(); state.bindInteger(1, Math.max(0, old_unread_count - counts[0])); state.bindInteger(2, Math.max(0, old_mentions_count - counts[1])); state.bindLong(3, did); state.step(); state.dispose(); } database.executeFast(String.format(Locale.US, "UPDATE chat_settings_v2 SET pinned = 0 WHERE uid = %d AND pinned <= %d", channelId, maxMessageId)).stepThis().dispose(); database.executeFast(String.format(Locale.US, "DELETE FROM chat_pinned_v2 WHERE uid = %d AND mid <= %d", channelId, maxMessageId)).stepThis().dispose(); int updatedCount = 0; cursor = database.queryFinalized("SELECT changes()"); if (cursor.next()) { updatedCount = cursor.intValue(0); } cursor.dispose(); if (updatedCount > 0) { cursor = database.queryFinalized(String.format(Locale.US, "SELECT count FROM chat_pinned_count WHERE uid = %d", -channelId)); if (cursor.next()) { int count = cursor.intValue(0); SQLitePreparedStatement state = database.executeFast("UPDATE chat_pinned_count SET count = ? WHERE uid = ?"); state.requery(); state.bindInteger(1, Math.max(0, count - updatedCount)); state.bindLong(2, -channelId); state.step(); state.dispose(); } cursor.dispose(); } database.executeFast(String.format(Locale.US, "DELETE FROM messages WHERE uid = %d AND mid <= %d", -channelId, maxMessageId)).stepThis().dispose(); database.executeFast(String.format(Locale.US, "DELETE FROM media_v2 WHERE uid = %d AND mid <= %d", -channelId, maxMessageId)).stepThis().dispose(); database.executeFast(String.format(Locale.US, "UPDATE media_counts_v2 SET old = 1 WHERE uid = %d", -channelId)).stepThis().dispose(); updateWidgets(dialogsIds); return dialogsIds; } catch (Exception e) { FileLog.e(e); } return null; } public ArrayList markMessagesAsDeleted(final int channelId, final int mid, boolean useQueue, boolean deleteFiles) { if (useQueue) { storageQueue.postRunnable(() -> markMessagesAsDeletedInternal(channelId, mid, deleteFiles)); } else { return markMessagesAsDeletedInternal(channelId, mid, deleteFiles); } return null; } private void fixUnsupportedMedia(TLRPC.Message message) { if (message == null) { return; } boolean ok = false; if (message.media instanceof TLRPC.TL_messageMediaUnsupported_old) { if (message.media.bytes.length == 0) { message.media.bytes = new byte[1]; message.media.bytes[0] = TLRPC.LAYER; } } else if (message.media instanceof TLRPC.TL_messageMediaUnsupported) { message.media = new TLRPC.TL_messageMediaUnsupported_old(); message.media.bytes = new byte[1]; message.media.bytes[0] = TLRPC.LAYER; message.flags |= TLRPC.MESSAGE_FLAG_HAS_MEDIA; } } private void doneHolesInTable(String table, long did, int max_id) throws Exception { if (max_id == 0) { database.executeFast(String.format(Locale.US, "DELETE FROM " + table + " WHERE uid = %d", did)).stepThis().dispose(); } else { database.executeFast(String.format(Locale.US, "DELETE FROM " + table + " WHERE uid = %d AND start = 0", did)).stepThis().dispose(); } SQLitePreparedStatement state = database.executeFast("REPLACE INTO " + table + " VALUES(?, ?, ?)"); state.requery(); state.bindLong(1, did); state.bindInteger(2, 1); state.bindInteger(3, 1); state.step(); state.dispose(); } public void doneHolesInMedia(long did, int max_id, int type) throws Exception { if (type == -1) { if (max_id == 0) { database.executeFast(String.format(Locale.US, "DELETE FROM media_holes_v2 WHERE uid = %d", did)).stepThis().dispose(); } else { database.executeFast(String.format(Locale.US, "DELETE FROM media_holes_v2 WHERE uid = %d AND start = 0", did)).stepThis().dispose(); } SQLitePreparedStatement state = database.executeFast("REPLACE INTO media_holes_v2 VALUES(?, ?, ?, ?)"); for (int a = 0; a < MediaDataController.MEDIA_TYPES_COUNT; a++) { state.requery(); state.bindLong(1, did); state.bindInteger(2, a); state.bindInteger(3, 1); state.bindInteger(4, 1); state.step(); } state.dispose(); } else { if (max_id == 0) { database.executeFast(String.format(Locale.US, "DELETE FROM media_holes_v2 WHERE uid = %d AND type = %d", did, type)).stepThis().dispose(); } else { database.executeFast(String.format(Locale.US, "DELETE FROM media_holes_v2 WHERE uid = %d AND type = %d AND start = 0", did, type)).stepThis().dispose(); } SQLitePreparedStatement state = database.executeFast("REPLACE INTO media_holes_v2 VALUES(?, ?, ?, ?)"); state.requery(); state.bindLong(1, did); state.bindInteger(2, type); state.bindInteger(3, 1); state.bindInteger(4, 1); state.step(); state.dispose(); } } private static class Hole { public Hole(int s, int e) { start = s; end = e; } public Hole(int t, int s, int e) { type = t; start = s; end = e; } public int start; public int end; public int type; } public void closeHolesInMedia(long did, int minId, int maxId, int type) { try { boolean ok = false; SQLiteCursor cursor; if (type < 0) { cursor = database.queryFinalized(String.format(Locale.US, "SELECT type, start, end FROM media_holes_v2 WHERE uid = %d AND type >= 0 AND ((end >= %d AND end <= %d) OR (start >= %d AND start <= %d) OR (start >= %d AND end <= %d) OR (start <= %d AND end >= %d))", did, minId, maxId, minId, maxId, minId, maxId, minId, maxId)); } else { cursor = database.queryFinalized(String.format(Locale.US, "SELECT type, start, end FROM media_holes_v2 WHERE uid = %d AND type = %d AND ((end >= %d AND end <= %d) OR (start >= %d AND start <= %d) OR (start >= %d AND end <= %d) OR (start <= %d AND end >= %d))", did, type, minId, maxId, minId, maxId, minId, maxId, minId, maxId)); } ArrayList holes = null; while (cursor.next()) { if (holes == null) { holes = new ArrayList<>(); } int holeType = cursor.intValue(0); int start = cursor.intValue(1); int end = cursor.intValue(2); if (start == end && start == 1) { continue; } holes.add(new Hole(holeType, start, end)); } cursor.dispose(); if (holes != null) { for (int a = 0; a < holes.size(); a++) { Hole hole = holes.get(a); if (maxId >= hole.end - 1 && minId <= hole.start + 1) { database.executeFast(String.format(Locale.US, "DELETE FROM media_holes_v2 WHERE uid = %d AND type = %d AND start = %d AND end = %d", did, hole.type, hole.start, hole.end)).stepThis().dispose(); } else if (maxId >= hole.end - 1) { if (hole.end != minId) { try { database.executeFast(String.format(Locale.US, "UPDATE media_holes_v2 SET end = %d WHERE uid = %d AND type = %d AND start = %d AND end = %d", minId, did, hole.type, hole.start, hole.end)).stepThis().dispose(); } catch (Exception e) { FileLog.e(e); } } } else if (minId <= hole.start + 1) { if (hole.start != maxId) { try { database.executeFast(String.format(Locale.US, "UPDATE media_holes_v2 SET start = %d WHERE uid = %d AND type = %d AND start = %d AND end = %d", maxId, did, hole.type, hole.start, hole.end)).stepThis().dispose(); } catch (Exception e) { FileLog.e(e); } } } else { database.executeFast(String.format(Locale.US, "DELETE FROM media_holes_v2 WHERE uid = %d AND type = %d AND start = %d AND end = %d", did, hole.type, hole.start, hole.end)).stepThis().dispose(); SQLitePreparedStatement state = database.executeFast("REPLACE INTO media_holes_v2 VALUES(?, ?, ?, ?)"); state.requery(); state.bindLong(1, did); state.bindInteger(2, hole.type); state.bindInteger(3, hole.start); state.bindInteger(4, minId); state.step(); state.requery(); state.bindLong(1, did); state.bindInteger(2, hole.type); state.bindInteger(3, maxId); state.bindInteger(4, hole.end); state.step(); state.dispose(); } } } } catch (Exception e) { FileLog.e(e); } } private void closeHolesInTable(String table, long did, int minId, int maxId) { try { boolean ok = false; SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT start, end FROM " + table + " WHERE uid = %d AND ((end >= %d AND end <= %d) OR (start >= %d AND start <= %d) OR (start >= %d AND end <= %d) OR (start <= %d AND end >= %d))", did, minId, maxId, minId, maxId, minId, maxId, minId, maxId)); ArrayList holes = null; while (cursor.next()) { if (holes == null) { holes = new ArrayList<>(); } int start = cursor.intValue(0); int end = cursor.intValue(1); if (start == end && start == 1) { continue; } holes.add(new Hole(start, end)); } cursor.dispose(); if (holes != null) { for (int a = 0; a < holes.size(); a++) { Hole hole = holes.get(a); if (maxId >= hole.end - 1 && minId <= hole.start + 1) { database.executeFast(String.format(Locale.US, "DELETE FROM " + table + " WHERE uid = %d AND start = %d AND end = %d", did, hole.start, hole.end)).stepThis().dispose(); } else if (maxId >= hole.end - 1) { if (hole.end != minId) { try { database.executeFast(String.format(Locale.US, "UPDATE " + table + " SET end = %d WHERE uid = %d AND start = %d AND end = %d", minId, did, hole.start, hole.end)).stepThis().dispose(); } catch (Exception e) { FileLog.e(e); } } } else if (minId <= hole.start + 1) { if (hole.start != maxId) { try { database.executeFast(String.format(Locale.US, "UPDATE " + table + " SET start = %d WHERE uid = %d AND start = %d AND end = %d", maxId, did, hole.start, hole.end)).stepThis().dispose(); } catch (Exception e) { FileLog.e(e); } } } else { database.executeFast(String.format(Locale.US, "DELETE FROM " + table + " WHERE uid = %d AND start = %d AND end = %d", did, hole.start, hole.end)).stepThis().dispose(); SQLitePreparedStatement state = database.executeFast("REPLACE INTO " + table + " VALUES(?, ?, ?)"); state.requery(); state.bindLong(1, did); state.bindInteger(2, hole.start); state.bindInteger(3, minId); state.step(); state.requery(); state.bindLong(1, did); state.bindInteger(2, maxId); state.bindInteger(3, hole.end); state.step(); state.dispose(); } } } } catch (Exception e) { FileLog.e(e); } } public void replaceMessageIfExists(final TLRPC.Message message, ArrayList users, ArrayList chats, boolean broadcast) { if (message == null) { return; } storageQueue.postRunnable(() -> { try { long messageId = message.id; if (message.peer_id.channel_id != 0) { messageId |= ((long) message.peer_id.channel_id) << 32; } SQLiteCursor cursor = null; try { cursor = database.queryFinalized(String.format(Locale.US, "SELECT uid FROM messages WHERE mid = %d LIMIT 1", messageId)); if (!cursor.next()) { return; } } catch (Exception e) { FileLog.e(e); } finally { if (cursor != null) { cursor.dispose(); } } database.beginTransaction(); SQLitePreparedStatement state = database.executeFast("REPLACE INTO messages VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?)"); SQLitePreparedStatement state2 = database.executeFast("REPLACE INTO media_v2 VALUES(?, ?, ?, ?, ?)"); if (message.dialog_id == 0) { MessageObject.getDialogId(message); } fixUnsupportedMedia(message); state.requery(); NativeByteBuffer data = new NativeByteBuffer(message.getObjectSize()); message.serializeToStream(data); state.bindLong(1, messageId); state.bindLong(2, message.dialog_id); state.bindInteger(3, MessageObject.getUnreadFlags(message)); state.bindInteger(4, message.send_state); state.bindInteger(5, message.date); state.bindByteBuffer(6, data); state.bindInteger(7, (MessageObject.isOut(message) || message.from_scheduled ? 1 : 0)); state.bindInteger(8, message.ttl); if ((message.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0) { state.bindInteger(9, message.views); } else { state.bindInteger(9, getMessageMediaType(message)); } int flags = 0; if (message.stickerVerified == 0) { flags |= 1; } else if (message.stickerVerified == 2) { flags |= 2; } state.bindInteger(10, flags); state.bindInteger(11, message.mentioned ? 1 : 0); state.bindInteger(12, message.forwards); NativeByteBuffer repliesData = null; if (message.replies != null) { repliesData = new NativeByteBuffer(message.replies.getObjectSize()); message.replies.serializeToStream(repliesData); state.bindByteBuffer(13, repliesData); } else { state.bindNull(13); } if (message.reply_to != null) { state.bindInteger(14, message.reply_to.reply_to_top_id != 0 ? message.reply_to.reply_to_top_id : message.reply_to.reply_to_msg_id); } else { state.bindInteger(14, 0); } state.step(); if (MediaDataController.canAddMessageToMedia(message)) { state2.requery(); state2.bindLong(1, messageId); state2.bindLong(2, message.dialog_id); state2.bindInteger(3, message.date); state2.bindInteger(4, MediaDataController.getMediaType(message)); state2.bindByteBuffer(5, data); state2.step(); } if (repliesData != null) { repliesData.reuse(); } data.reuse(); state.dispose(); state2.dispose(); database.commitTransaction(); if (broadcast) { HashMap userHashMap = new HashMap<>(); HashMap chatHashMap = new HashMap<>(); for (int a = 0; a < users.size(); a++) { TLRPC.User user = users.get(a); userHashMap.put(user.id, user); } for (int a = 0; a < chats.size(); a++) { TLRPC.Chat chat = chats.get(a); chatHashMap.put(chat.id, chat); } MessageObject messageObject = new MessageObject(currentAccount, message, userHashMap, chatHashMap, true, true); ArrayList arrayList = new ArrayList<>(); arrayList.add(messageObject); AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.replaceMessagesObjects, messageObject.getDialogId(), arrayList)); } } catch (Exception e) { FileLog.e(e); } }); } public void putMessages(final TLRPC.messages_Messages messages, final long dialog_id, final int load_type, final int max_id, final boolean createDialog, final boolean scheduled) { storageQueue.postRunnable(() -> { try { if (scheduled) { database.executeFast(String.format(Locale.US, "DELETE FROM scheduled_messages WHERE uid = %d AND mid > 0", dialog_id)).stepThis().dispose(); SQLitePreparedStatement state_messages = database.executeFast("REPLACE INTO scheduled_messages VALUES(?, ?, ?, ?, ?, ?, NULL)"); int channelId = 0; int count = messages.messages.size(); for (int a = 0; a < count; a++) { TLRPC.Message message = messages.messages.get(a); if (message instanceof TLRPC.TL_messageEmpty) { continue; } long messageId = message.id; if (channelId == 0) { channelId = message.peer_id.channel_id; } if (message.peer_id.channel_id != 0) { messageId |= ((long) channelId) << 32; } fixUnsupportedMedia(message); state_messages.requery(); NativeByteBuffer data = new NativeByteBuffer(message.getObjectSize()); message.serializeToStream(data); state_messages.bindLong(1, messageId); state_messages.bindLong(2, dialog_id); state_messages.bindInteger(3, message.send_state); state_messages.bindInteger(4, message.date); state_messages.bindByteBuffer(5, data); state_messages.bindInteger(6, message.ttl); state_messages.step(); data.reuse(); } state_messages.dispose(); putUsersInternal(messages.users); putChatsInternal(messages.chats); database.commitTransaction(); broadcastScheduledMessagesChange(dialog_id); } else { int mentionCountUpdate = Integer.MAX_VALUE; if (messages.messages.isEmpty()) { if (load_type == 0) { doneHolesInTable("messages_holes", dialog_id, max_id); doneHolesInMedia(dialog_id, max_id, -1); } return; } database.beginTransaction(); if (load_type == 0) { int minId = messages.messages.get(messages.messages.size() - 1).id; closeHolesInTable("messages_holes", dialog_id, minId, max_id); closeHolesInMedia(dialog_id, minId, max_id, -1); } else if (load_type == 1) { int maxId = messages.messages.get(0).id; closeHolesInTable("messages_holes", dialog_id, max_id, maxId); closeHolesInMedia(dialog_id, max_id, maxId, -1); } else if (load_type == 3 || load_type == 2 || load_type == 4) { int maxId = max_id == 0 && load_type != 4 ? Integer.MAX_VALUE : messages.messages.get(0).id; int minId = messages.messages.get(messages.messages.size() - 1).id; closeHolesInTable("messages_holes", dialog_id, minId, maxId); closeHolesInMedia(dialog_id, minId, maxId, -1); } int count = messages.messages.size(); //load_type == 0 ? backward loading //load_type == 1 ? forward loading //load_type == 2 ? load from first unread //load_type == 3 ? load around message //load_type == 4 ? load around date ArrayList filesToDelete = new ArrayList<>(); ArrayList namesToDelete = new ArrayList<>(); ArrayList> idsToDelete = new ArrayList<>(); SQLitePreparedStatement state_messages = database.executeFast("REPLACE INTO messages VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?)"); SQLitePreparedStatement state_media = database.executeFast("REPLACE INTO media_v2 VALUES(?, ?, ?, ?, ?)"); SQLitePreparedStatement state_polls = null; SQLitePreparedStatement state_webpage = null; SQLitePreparedStatement state_tasks = null; int minDeleteTime = Integer.MAX_VALUE; TLRPC.Message botKeyboard = null; int channelId = 0; for (int a = 0; a < count; a++) { TLRPC.Message message = messages.messages.get(a); long messageId = message.id; if (channelId == 0) { channelId = message.peer_id.channel_id; } if (message.peer_id.channel_id != 0) { messageId |= ((long) channelId) << 32; } if (load_type == -2) { SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT mid, data, ttl, mention, read_state, send_state FROM messages WHERE mid = %d", messageId)); boolean exist; if (exist = cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(1); if (data != null) { TLRPC.Message oldMessage = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); oldMessage.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); int send_state = cursor.intValue(5); if (send_state != 3) { message.attachPath = oldMessage.attachPath; message.ttl = cursor.intValue(2); } boolean sameMedia = false; if (oldMessage.media instanceof TLRPC.TL_messageMediaPhoto && message.media instanceof TLRPC.TL_messageMediaPhoto && oldMessage.media.photo != null && message.media.photo != null) { sameMedia = oldMessage.media.photo.id == message.media.photo.id; } else if (oldMessage.media instanceof TLRPC.TL_messageMediaDocument && message.media instanceof TLRPC.TL_messageMediaDocument && oldMessage.media.document != null && message.media.document != null) { sameMedia = oldMessage.media.document.id == message.media.document.id; } if (!sameMedia) { addFilesToDelete(oldMessage, filesToDelete, idsToDelete, namesToDelete, false); } } boolean oldMention = cursor.intValue(3) != 0; int readState = cursor.intValue(4); if (oldMention != message.mentioned) { if (mentionCountUpdate == Integer.MAX_VALUE) { SQLiteCursor cursor2 = database.queryFinalized("SELECT unread_count_i FROM dialogs WHERE did = " + dialog_id); if (cursor2.next()) { mentionCountUpdate = cursor2.intValue(0); } cursor2.dispose(); } if (oldMention) { if (readState <= 1) { mentionCountUpdate--; } } else { if (message.media_unread) { mentionCountUpdate++; } } } } cursor.dispose(); if (!exist) { continue; } } if (a == 0 && createDialog) { int pinned = 0; int mentions = 0; int flags = 0; SQLiteCursor cursor = database.queryFinalized("SELECT pinned, unread_count_i, flags FROM dialogs WHERE did = " + dialog_id); boolean exist; if (exist = cursor.next()) { pinned = cursor.intValue(0); mentions = cursor.intValue(1); flags = cursor.intValue(2); } cursor.dispose(); SQLitePreparedStatement state3; if (exist) { state3 = database.executeFast("UPDATE dialogs SET date = ?, last_mid = ?, inbox_max = ?, last_mid_i = ?, pts = ?, date_i = ? WHERE did = ?"); state3.bindInteger(1, message.date); state3.bindLong(2, messageId); state3.bindInteger(3, message.id); state3.bindLong(4, messageId); state3.bindInteger(5, messages.pts); state3.bindInteger(6, message.date); state3.bindLong(7, dialog_id); } else { state3 = database.executeFast("REPLACE INTO dialogs VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); state3.bindLong(1, dialog_id); state3.bindInteger(2, message.date); state3.bindInteger(3, 0); state3.bindLong(4, messageId); state3.bindInteger(5, message.id); state3.bindInteger(6, 0); state3.bindLong(7, messageId); state3.bindInteger(8, mentions); state3.bindInteger(9, messages.pts); state3.bindInteger(10, message.date); state3.bindInteger(11, pinned); state3.bindInteger(12, flags); state3.bindInteger(13, -1); state3.bindNull(14); unknownDialogsIds.put(dialog_id, true); } state3.step(); state3.dispose(); } fixUnsupportedMedia(message); state_messages.requery(); NativeByteBuffer data = new NativeByteBuffer(message.getObjectSize()); message.serializeToStream(data); state_messages.bindLong(1, messageId); state_messages.bindLong(2, dialog_id); state_messages.bindInteger(3, MessageObject.getUnreadFlags(message)); state_messages.bindInteger(4, message.send_state); state_messages.bindInteger(5, message.date); state_messages.bindByteBuffer(6, data); state_messages.bindInteger(7, (MessageObject.isOut(message) || message.from_scheduled ? 1 : 0)); state_messages.bindInteger(8, message.ttl); if ((message.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0) { state_messages.bindInteger(9, message.views); } else { state_messages.bindInteger(9, getMessageMediaType(message)); } int flags = 0; if (message.stickerVerified == 0) { flags |= 1; } else if (message.stickerVerified == 2) { flags |= 2; } state_messages.bindInteger(10, flags); state_messages.bindInteger(11, message.mentioned ? 1 : 0); state_messages.bindInteger(12, message.forwards); NativeByteBuffer repliesData = null; if (message.replies != null) { repliesData = new NativeByteBuffer(message.replies.getObjectSize()); message.replies.serializeToStream(repliesData); state_messages.bindByteBuffer(13, repliesData); } else { state_messages.bindNull(13); } if (message.reply_to != null) { state_messages.bindInteger(14, message.reply_to.reply_to_top_id != 0 ? message.reply_to.reply_to_top_id : message.reply_to.reply_to_msg_id); } else { state_messages.bindInteger(14, 0); } state_messages.step(); if (MediaDataController.canAddMessageToMedia(message)) { state_media.requery(); state_media.bindLong(1, messageId); state_media.bindLong(2, dialog_id); state_media.bindInteger(3, message.date); state_media.bindInteger(4, MediaDataController.getMediaType(message)); state_media.bindByteBuffer(5, data); state_media.step(); } else if (message instanceof TLRPC.TL_messageService && message.action instanceof TLRPC.TL_messageActionHistoryClear) { try { database.executeFast(String.format(Locale.US, "DELETE FROM media_v2 WHERE mid = %d", message.id)).stepThis().dispose(); database.executeFast("DELETE FROM media_counts_v2 WHERE uid = " + dialog_id).stepThis().dispose(); } catch (Exception e2) { FileLog.e(e2); } } if (repliesData != null) { repliesData.reuse(); } data.reuse(); if (message.ttl_period != 0 && message.id > 0) { if (state_tasks == null) { state_tasks = database.executeFast("REPLACE INTO enc_tasks_v2 VALUES(?, ?, ?)"); } state_tasks.requery(); state_tasks.bindLong(1, messageId); state_tasks.bindInteger(2, message.date + message.ttl_period); state_tasks.bindInteger(3, 0); state_tasks.step(); minDeleteTime = Math.min(minDeleteTime, message.date + message.ttl_period); } if (message.media instanceof TLRPC.TL_messageMediaPoll) { if (state_polls == null) { state_polls = database.executeFast("REPLACE INTO polls VALUES(?, ?)"); } TLRPC.TL_messageMediaPoll mediaPoll = (TLRPC.TL_messageMediaPoll) message.media; state_polls.requery(); state_polls.bindLong(1, messageId); state_polls.bindLong(2, mediaPoll.poll.id); state_polls.step(); } else if (message.media instanceof TLRPC.TL_messageMediaWebPage) { if (state_webpage == null) { state_webpage = database.executeFast("REPLACE INTO webpage_pending VALUES(?, ?)"); } state_webpage.requery(); state_webpage.bindLong(1, message.media.webpage.id); state_webpage.bindLong(2, messageId); state_webpage.step(); } if (load_type == 0 && isValidKeyboardToSave(message)) { if (botKeyboard == null || botKeyboard.id < message.id) { botKeyboard = message; } } } state_messages.dispose(); state_media.dispose(); if (state_webpage != null) { state_webpage.dispose(); } if (state_tasks != null) { state_tasks.dispose(); getMessagesController().didAddedNewTask(minDeleteTime, null); } if (state_polls != null) { state_polls.dispose(); } if (botKeyboard != null) { getMediaDataController().putBotKeyboard(dialog_id, botKeyboard); } deleteFromDownloadQueue(idsToDelete, false); AndroidUtilities.runOnUIThread(() -> getFileLoader().cancelLoadFiles(namesToDelete)); getFileLoader().deleteFiles(filesToDelete, 0); putUsersInternal(messages.users); putChatsInternal(messages.chats); if (mentionCountUpdate != Integer.MAX_VALUE) { database.executeFast(String.format(Locale.US, "UPDATE dialogs SET unread_count_i = %d WHERE did = %d", mentionCountUpdate, dialog_id)).stepThis().dispose(); LongSparseArray sparseArray = new LongSparseArray<>(1); sparseArray.put(dialog_id, mentionCountUpdate); getMessagesController().processDialogsUpdateRead(null, sparseArray); } database.commitTransaction(); if (createDialog) { updateDialogsWithDeletedMessages(new ArrayList<>(), null, false, channelId); } } } catch (Exception e) { FileLog.e(e); } }); } public static void addUsersAndChatsFromMessage(TLRPC.Message message, ArrayList usersToLoad, ArrayList chatsToLoad) { int fromId = MessageObject.getFromChatId(message); if (fromId > 0) { if (!usersToLoad.contains(fromId)) { usersToLoad.add(fromId); } } else if (fromId < 0) { if (!chatsToLoad.contains(-fromId)) { chatsToLoad.add(-fromId); } } if (message.via_bot_id != 0 && !usersToLoad.contains(message.via_bot_id)) { usersToLoad.add(message.via_bot_id); } if (message.action != null) { if (message.action.user_id != 0 && !usersToLoad.contains(message.action.user_id)) { usersToLoad.add(message.action.user_id); } if (message.action.channel_id != 0 && !chatsToLoad.contains(message.action.channel_id)) { chatsToLoad.add(message.action.channel_id); } if (message.action.chat_id != 0 && !chatsToLoad.contains(message.action.chat_id)) { chatsToLoad.add(message.action.chat_id); } if (message.action instanceof TLRPC.TL_messageActionGeoProximityReached) { TLRPC.TL_messageActionGeoProximityReached action = (TLRPC.TL_messageActionGeoProximityReached) message.action; Integer id = MessageObject.getPeerId(action.from_id); if (id > 0) { if (!usersToLoad.contains(id)) { usersToLoad.add(id); } } else if (!chatsToLoad.contains(-id)) { chatsToLoad.add(-id); } id = MessageObject.getPeerId(action.to_id); if (id > 0) { if (!usersToLoad.contains(id)) { usersToLoad.add(id); } } else if (!chatsToLoad.contains(-id)) { chatsToLoad.add(-id); } } if (!message.action.users.isEmpty()) { for (int a = 0; a < message.action.users.size(); a++) { Integer uid = message.action.users.get(a); if (!usersToLoad.contains(uid)) { usersToLoad.add(uid); } } } } if (!message.entities.isEmpty()) { for (int a = 0; a < message.entities.size(); a++) { TLRPC.MessageEntity entity = message.entities.get(a); if (entity instanceof TLRPC.TL_messageEntityMentionName) { usersToLoad.add(((TLRPC.TL_messageEntityMentionName) entity).user_id); } else if (entity instanceof TLRPC.TL_inputMessageEntityMentionName) { usersToLoad.add(((TLRPC.TL_inputMessageEntityMentionName) entity).user_id.user_id); } } } if (message.media != null) { if (message.media.user_id != 0 && !usersToLoad.contains(message.media.user_id)) { usersToLoad.add(message.media.user_id); } if (message.media instanceof TLRPC.TL_messageMediaPoll) { TLRPC.TL_messageMediaPoll messageMediaPoll = (TLRPC.TL_messageMediaPoll) message.media; if (!messageMediaPoll.results.recent_voters.isEmpty()) { usersToLoad.addAll(messageMediaPoll.results.recent_voters); } } } if (message.replies != null) { for (int a = 0, N = message.replies.recent_repliers.size(); a < N; a++) { int id = MessageObject.getPeerId(message.replies.recent_repliers.get(a)); if (id > 0) { if (!usersToLoad.contains(id)) { usersToLoad.add(id); } } else if (id < 0) { if (!chatsToLoad.contains(-id)) { chatsToLoad.add(-id); } } } } if (message.reply_to != null && message.reply_to.reply_to_peer_id != null) { int id = MessageObject.getPeerId(message.reply_to.reply_to_peer_id); if (id > 0) { if (!usersToLoad.contains(id)) { usersToLoad.add(id); } } else if (id < 0) { if (!chatsToLoad.contains(-id)) { chatsToLoad.add(-id); } } } if (message.fwd_from != null) { if (message.fwd_from.from_id instanceof TLRPC.TL_peerUser) { if (!usersToLoad.contains(message.fwd_from.from_id.user_id)) { usersToLoad.add(message.fwd_from.from_id.user_id); } } else if (message.fwd_from.from_id instanceof TLRPC.TL_peerChannel) { if (!chatsToLoad.contains(message.fwd_from.from_id.channel_id)) { chatsToLoad.add(message.fwd_from.from_id.channel_id); } } else if (message.fwd_from.from_id instanceof TLRPC.TL_peerChat) { if (!chatsToLoad.contains(message.fwd_from.from_id.chat_id)) { chatsToLoad.add(message.fwd_from.from_id.chat_id); } } if (message.fwd_from.saved_from_peer != null) { if (message.fwd_from.saved_from_peer.user_id != 0) { if (!chatsToLoad.contains(message.fwd_from.saved_from_peer.user_id)) { usersToLoad.add(message.fwd_from.saved_from_peer.user_id); } } else if (message.fwd_from.saved_from_peer.channel_id != 0) { if (!chatsToLoad.contains(message.fwd_from.saved_from_peer.channel_id)) { chatsToLoad.add(message.fwd_from.saved_from_peer.channel_id); } } else if (message.fwd_from.saved_from_peer.chat_id != 0) { if (!chatsToLoad.contains(message.fwd_from.saved_from_peer.chat_id)) { chatsToLoad.add(message.fwd_from.saved_from_peer.chat_id); } } } } if (message.ttl < 0) { if (!chatsToLoad.contains(-message.ttl)) { chatsToLoad.add(-message.ttl); } } } public void getDialogs(final int folderId, final int offset, final int count, final boolean loadDraftsPeersAndFolders) { final long[] draftsDialogIds; if (loadDraftsPeersAndFolders) { final LongSparseArray> drafts = getMediaDataController().getDrafts(); final int draftsCount = drafts.size(); if (draftsCount > 0) { draftsDialogIds = new long[draftsCount]; for (int i = 0; i < draftsCount; i++) { SparseArray threads = drafts.valueAt(i); if (threads.get(0) == null) { continue; } draftsDialogIds[i] = drafts.keyAt(i); } } else { draftsDialogIds = null; } } else { draftsDialogIds = null; } storageQueue.postRunnable(() -> { TLRPC.messages_Dialogs dialogs = new TLRPC.TL_messages_dialogs(); ArrayList encryptedChats = new ArrayList<>(); try { ArrayList usersToLoad = new ArrayList<>(); usersToLoad.add(getUserConfig().getClientUserId()); ArrayList chatsToLoad = new ArrayList<>(); ArrayList encryptedToLoad = new ArrayList<>(); ArrayList loadedDialogs = new ArrayList<>(); ArrayList replyMessages = new ArrayList<>(); LongSparseArray replyMessageOwners = new LongSparseArray<>(); ArrayList foldersToLoad = new ArrayList<>(2); foldersToLoad.add(folderId); for (int a = 0; a < foldersToLoad.size(); a++) { int fid = foldersToLoad.get(a); int off; int cnt; if (a == 0) { off = offset; cnt = count; } else { off = 0; cnt = 100; } SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT d.did, d.last_mid, d.unread_count, d.date, m.data, m.read_state, m.mid, m.send_state, s.flags, m.date, d.pts, d.inbox_max, d.outbox_max, m.replydata, d.pinned, d.unread_count_i, d.flags, d.folder_id, d.data FROM dialogs as d LEFT JOIN messages as m ON d.last_mid = m.mid LEFT JOIN dialog_settings as s ON d.did = s.did WHERE d.folder_id = %d ORDER BY d.pinned DESC, d.date DESC LIMIT %d,%d", fid, off, cnt)); while (cursor.next()) { long dialogId = cursor.longValue(0); TLRPC.Dialog dialog; if (DialogObject.isFolderDialogId(dialogId)) { TLRPC.TL_dialogFolder dialogFolder = new TLRPC.TL_dialogFolder(); if (!cursor.isNull(18)) { NativeByteBuffer data = cursor.byteBufferValue(18); if (data != null) { dialogFolder.folder = TLRPC.TL_folder.TLdeserialize(data, data.readInt32(false), false); data.reuse(); } else { dialogFolder.folder = new TLRPC.TL_folder(); dialogFolder.folder.id = (int) dialogId; } } dialog = dialogFolder; if (a == 0) { foldersToLoad.add(dialogFolder.folder.id); } } else { dialog = new TLRPC.TL_dialog(); } dialog.id = dialogId; dialog.top_message = cursor.intValue(1); dialog.unread_count = cursor.intValue(2); dialog.last_message_date = cursor.intValue(3); dialog.pts = cursor.intValue(10); dialog.flags = dialog.pts == 0 || (int) dialog.id > 0 ? 0 : 1; dialog.read_inbox_max_id = cursor.intValue(11); dialog.read_outbox_max_id = cursor.intValue(12); dialog.pinnedNum = cursor.intValue(14); dialog.pinned = dialog.pinnedNum != 0; dialog.unread_mentions_count = cursor.intValue(15); int dialog_flags = cursor.intValue(16); dialog.unread_mark = (dialog_flags & 1) != 0; long flags = cursor.longValue(8); int low_flags = (int) flags; dialog.notify_settings = new TLRPC.TL_peerNotifySettings(); if ((low_flags & 1) != 0) { dialog.notify_settings.mute_until = (int) (flags >> 32); if (dialog.notify_settings.mute_until == 0) { dialog.notify_settings.mute_until = Integer.MAX_VALUE; } } dialog.folder_id = cursor.intValue(17); dialogs.dialogs.add(dialog); if (draftsDialogIds != null) { loadedDialogs.add(dialogId); } NativeByteBuffer data = cursor.byteBufferValue(4); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); if (message != null) { message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); MessageObject.setUnreadFlags(message, cursor.intValue(5)); message.id = cursor.intValue(6); int date = cursor.intValue(9); if (date != 0) { dialog.last_message_date = date; } message.send_state = cursor.intValue(7); message.dialog_id = dialog.id; dialogs.messages.add(message); addUsersAndChatsFromMessage(message, usersToLoad, chatsToLoad); try { if (message.reply_to != null && message.reply_to.reply_to_msg_id != 0 && ( message.action instanceof TLRPC.TL_messageActionPinMessage || message.action instanceof TLRPC.TL_messageActionPaymentSent || message.action instanceof TLRPC.TL_messageActionGameScore)) { if (!cursor.isNull(13)) { NativeByteBuffer data2 = cursor.byteBufferValue(13); if (data2 != null) { message.replyMessage = TLRPC.Message.TLdeserialize(data2, data2.readInt32(false), false); message.replyMessage.readAttachPath(data2, getUserConfig().clientUserId); data2.reuse(); if (message.replyMessage != null) { addUsersAndChatsFromMessage(message.replyMessage, usersToLoad, chatsToLoad); } } } if (message.replyMessage == null) { long messageId = message.reply_to.reply_to_msg_id; if (message.reply_to.reply_to_peer_id != null) { if (message.reply_to.reply_to_peer_id.channel_id != 0) { messageId |= ((long) message.reply_to.reply_to_peer_id.channel_id) << 32; } } else if (message.peer_id.channel_id != 0) { messageId |= ((long) message.peer_id.channel_id) << 32; } if (!replyMessages.contains(messageId)) { replyMessages.add(messageId); } replyMessageOwners.put(dialog.id, message); } } } catch (Exception e) { FileLog.e(e); } } else { data.reuse(); } } int lower_id = (int) dialog.id; int high_id = (int) (dialog.id >> 32); if (lower_id != 0) { if (lower_id > 0) { if (!usersToLoad.contains(lower_id)) { usersToLoad.add(lower_id); } } else { if (!chatsToLoad.contains(-lower_id)) { chatsToLoad.add(-lower_id); } } } else { if (!encryptedToLoad.contains(high_id)) { encryptedToLoad.add(high_id); } } } cursor.dispose(); } if (!replyMessages.isEmpty()) { SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid, date, uid FROM messages WHERE mid IN(%s)", TextUtils.join(",", replyMessages))); while (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); message.id = cursor.intValue(1); message.date = cursor.intValue(2); message.dialog_id = cursor.longValue(3); addUsersAndChatsFromMessage(message, usersToLoad, chatsToLoad); TLRPC.Message owner = replyMessageOwners.get(message.dialog_id); if (owner != null) { owner.replyMessage = message; message.dialog_id = owner.dialog_id; } } } cursor.dispose(); } if (draftsDialogIds != null) { final ArrayList unloadedDialogs = new ArrayList<>(); for (int i = 0; i < draftsDialogIds.length; i++) { final int dialogId = (int) draftsDialogIds[i]; if (dialogId == 0) { continue; } if (dialogId > 0) { if (!usersToLoad.contains(dialogId)) { usersToLoad.add(dialogId); } } else { if (!chatsToLoad.contains(-dialogId)) { chatsToLoad.add(-dialogId); } } if (!loadedDialogs.contains(draftsDialogIds[i])) { unloadedDialogs.add(draftsDialogIds[i]); } } final LongSparseArray folderIds; if (!unloadedDialogs.isEmpty()) { folderIds = new LongSparseArray<>(unloadedDialogs.size()); final SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT did, folder_id FROM dialogs WHERE did IN(%s)", TextUtils.join(",", unloadedDialogs))); while (cursor.next()) { folderIds.put(cursor.longValue(0), cursor.intValue(1)); } cursor.dispose(); } else { folderIds = null; } AndroidUtilities.runOnUIThread(() -> { final MediaDataController mediaDataController = getMediaDataController(); mediaDataController.clearDraftsFolderIds(); if (folderIds != null) { for (int i = 0, size = folderIds.size(); i < size; i++) { mediaDataController.setDraftFolderId(folderIds.keyAt(i), folderIds.valueAt(i)); } } }); } if (!encryptedToLoad.isEmpty()) { getEncryptedChatsInternal(TextUtils.join(",", encryptedToLoad), encryptedChats, usersToLoad); } if (!chatsToLoad.isEmpty()) { getChatsInternal(TextUtils.join(",", chatsToLoad), dialogs.chats); } if (!usersToLoad.isEmpty()) { getUsersInternal(TextUtils.join(",", usersToLoad), dialogs.users); } getMessagesController().processLoadedDialogs(dialogs, encryptedChats, folderId, offset, count, 1, false, false, true); } catch (Exception e) { dialogs.dialogs.clear(); dialogs.users.clear(); dialogs.chats.clear(); encryptedChats.clear(); FileLog.e(e); getMessagesController().processLoadedDialogs(dialogs, encryptedChats, folderId, 0, 100, 1, true, false, true); } }); } public static void createFirstHoles(long did, SQLitePreparedStatement state5, SQLitePreparedStatement state6, int messageId) throws Exception { state5.requery(); state5.bindLong(1, did); state5.bindInteger(2, messageId == 1 ? 1 : 0); state5.bindInteger(3, messageId); state5.step(); for (int b = 0; b < MediaDataController.MEDIA_TYPES_COUNT; b++) { state6.requery(); state6.bindLong(1, did); state6.bindInteger(2, b); state6.bindInteger(3, messageId == 1 ? 1 : 0); state6.bindInteger(4, messageId); state6.step(); } } private void putDialogsInternal(final TLRPC.messages_Dialogs dialogs, int check) { try { database.beginTransaction(); final LongSparseArray new_dialogMessage = new LongSparseArray<>(dialogs.messages.size()); for (int a = 0; a < dialogs.messages.size(); a++) { TLRPC.Message message = dialogs.messages.get(a); new_dialogMessage.put(MessageObject.getDialogId(message), message); } if (!dialogs.dialogs.isEmpty()) { SQLitePreparedStatement state_messages = database.executeFast("REPLACE INTO messages VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?)"); SQLitePreparedStatement state_dialogs = database.executeFast("REPLACE INTO dialogs VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); SQLitePreparedStatement state_media = database.executeFast("REPLACE INTO media_v2 VALUES(?, ?, ?, ?, ?)"); SQLitePreparedStatement state_settings = database.executeFast("REPLACE INTO dialog_settings VALUES(?, ?)"); SQLitePreparedStatement state_holes = database.executeFast("REPLACE INTO messages_holes VALUES(?, ?, ?)"); SQLitePreparedStatement state_media_holes = database.executeFast("REPLACE INTO media_holes_v2 VALUES(?, ?, ?, ?)"); SQLitePreparedStatement state_polls = null; SQLitePreparedStatement state_tasks = null; int minDeleteTime = Integer.MAX_VALUE; for (int a = 0; a < dialogs.dialogs.size(); a++) { TLRPC.Dialog dialog = dialogs.dialogs.get(a); boolean exists = false; DialogObject.initDialog(dialog); unknownDialogsIds.remove(dialog.id); if (check == 1) { SQLiteCursor cursor = database.queryFinalized("SELECT did FROM dialogs WHERE did = " + dialog.id); exists = cursor.next(); cursor.dispose(); if (exists) { continue; } } else if (check == 2) { SQLiteCursor cursor = database.queryFinalized("SELECT pinned FROM dialogs WHERE did = " + dialog.id); if (cursor.next()) { exists = true; if (dialog.pinned) { dialog.pinnedNum = cursor.intValue(0); } } cursor.dispose(); } else if (check == 3) { int mid = 0; SQLiteCursor cursor = database.queryFinalized("SELECT last_mid FROM dialogs WHERE did = " + dialog.id); if (cursor.next()) { mid = cursor.intValue(0); } cursor.dispose(); if (mid < 0) { continue; } } int messageDate = 0; TLRPC.Message message = new_dialogMessage.get(dialog.id); if (message != null) { messageDate = Math.max(message.date, messageDate); if (isValidKeyboardToSave(message)) { getMediaDataController().putBotKeyboard(dialog.id, message); } fixUnsupportedMedia(message); NativeByteBuffer data = new NativeByteBuffer(message.getObjectSize()); message.serializeToStream(data); long messageId = message.id; if (message.peer_id.channel_id != 0) { messageId |= ((long) message.peer_id.channel_id) << 32; } state_messages.requery(); state_messages.bindLong(1, messageId); state_messages.bindLong(2, dialog.id); state_messages.bindInteger(3, MessageObject.getUnreadFlags(message)); state_messages.bindInteger(4, message.send_state); state_messages.bindInteger(5, message.date); state_messages.bindByteBuffer(6, data); state_messages.bindInteger(7, (MessageObject.isOut(message) || message.from_scheduled ? 1 : 0)); state_messages.bindInteger(8, 0); state_messages.bindInteger(9, (message.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0 ? message.views : 0); int flags = 0; if (message.stickerVerified == 0) { flags |= 1; } else if (message.stickerVerified == 2) { flags |= 2; } state_messages.bindInteger(10, flags); state_messages.bindInteger(11, message.mentioned ? 1 : 0); state_messages.bindInteger(12, message.forwards); NativeByteBuffer repliesData = null; if (message.replies != null) { repliesData = new NativeByteBuffer(message.replies.getObjectSize()); message.replies.serializeToStream(repliesData); state_messages.bindByteBuffer(13, repliesData); } else { state_messages.bindNull(13); } if (message.reply_to != null) { state_messages.bindInteger(14, message.reply_to.reply_to_top_id != 0 ? message.reply_to.reply_to_top_id : message.reply_to.reply_to_msg_id); } else { state_messages.bindInteger(14, 0); } state_messages.step(); if (MediaDataController.canAddMessageToMedia(message)) { state_media.requery(); state_media.bindLong(1, messageId); state_media.bindLong(2, dialog.id); state_media.bindInteger(3, message.date); state_media.bindInteger(4, MediaDataController.getMediaType(message)); state_media.bindByteBuffer(5, data); state_media.step(); } if (repliesData != null) { repliesData.reuse(); } data.reuse(); if (message.ttl_period != 0 && message.id > 0) { if (state_tasks == null) { state_tasks = database.executeFast("REPLACE INTO enc_tasks_v2 VALUES(?, ?, ?)"); } state_tasks.requery(); state_tasks.bindLong(1, messageId); state_tasks.bindInteger(2, message.date + message.ttl_period); state_tasks.bindInteger(3, 0); state_tasks.step(); minDeleteTime = Math.min(minDeleteTime, message.date + message.ttl_period); } if (message.media instanceof TLRPC.TL_messageMediaPoll) { if (state_polls == null) { state_polls = database.executeFast("REPLACE INTO polls VALUES(?, ?)"); } TLRPC.TL_messageMediaPoll mediaPoll = (TLRPC.TL_messageMediaPoll) message.media; state_polls.requery(); state_polls.bindLong(1, messageId); state_polls.bindLong(2, mediaPoll.poll.id); state_polls.step(); } if (exists) { closeHolesInTable("messages_holes", dialog.id, message.id, message.id); closeHolesInMedia(dialog.id, message.id, message.id, -1); } else { createFirstHoles(dialog.id, state_holes, state_media_holes, message.id); } } long topMessage = dialog.top_message; if (dialog.peer != null && dialog.peer.channel_id != 0) { topMessage |= ((long) dialog.peer.channel_id) << 32; } state_dialogs.requery(); state_dialogs.bindLong(1, dialog.id); state_dialogs.bindInteger(2, messageDate); state_dialogs.bindInteger(3, dialog.unread_count); state_dialogs.bindLong(4, topMessage); state_dialogs.bindInteger(5, dialog.read_inbox_max_id); state_dialogs.bindInteger(6, dialog.read_outbox_max_id); state_dialogs.bindLong(7, 0); state_dialogs.bindInteger(8, dialog.unread_mentions_count); state_dialogs.bindInteger(9, dialog.pts); state_dialogs.bindInteger(10, 0); state_dialogs.bindInteger(11, dialog.pinnedNum); int flags = 0; if (dialog.unread_mark) { flags |= 1; } state_dialogs.bindInteger(12, flags); state_dialogs.bindInteger(13, dialog.folder_id); NativeByteBuffer data; if (dialog instanceof TLRPC.TL_dialogFolder) { TLRPC.TL_dialogFolder dialogFolder = (TLRPC.TL_dialogFolder) dialog; data = new NativeByteBuffer(dialogFolder.folder.getObjectSize()); dialogFolder.folder.serializeToStream(data); state_dialogs.bindByteBuffer(14, data); } else { data = null; state_dialogs.bindNull(14); } state_dialogs.step(); if (data != null) { data.reuse(); } if (dialog.notify_settings != null) { state_settings.requery(); state_settings.bindLong(1, dialog.id); state_settings.bindInteger(2, dialog.notify_settings.mute_until != 0 ? 1 : 0); state_settings.step(); } } state_messages.dispose(); state_dialogs.dispose(); state_media.dispose(); state_settings.dispose(); state_holes.dispose(); state_media_holes.dispose(); if (state_tasks != null) { state_tasks.dispose(); getMessagesController().didAddedNewTask(minDeleteTime, null); } if (state_polls != null) { state_polls.dispose(); } } putUsersInternal(dialogs.users); putChatsInternal(dialogs.chats); database.commitTransaction(); resetAllUnreadCounters(false); } catch (Exception e) { FileLog.e(e); } } public void getDialogFolderId(long dialogId, IntCallback callback) { storageQueue.postRunnable(() -> { try { int folderId; if (unknownDialogsIds.get(dialogId) != null) { folderId = -1; } else { SQLiteCursor cursor = database.queryFinalized("SELECT folder_id FROM dialogs WHERE did = ?", dialogId); if (cursor.next()) { folderId = cursor.intValue(0); } else { folderId = -1; } cursor.dispose(); } AndroidUtilities.runOnUIThread(() -> callback.run(folderId)); } catch (Exception e) { FileLog.e(e); } }); } public void setDialogsFolderId(final ArrayList peers, ArrayList inputPeers, long dialogId, int folderId) { if (peers == null && inputPeers == null && dialogId == 0) { return; } storageQueue.postRunnable(() -> { try { database.beginTransaction(); SQLitePreparedStatement state = database.executeFast("UPDATE dialogs SET folder_id = ?, pinned = ? WHERE did = ?"); if (peers != null) { for (int a = 0, N = peers.size(); a < N; a++) { TLRPC.TL_folderPeer folderPeer = peers.get(a); long did = DialogObject.getPeerDialogId(folderPeer.peer); state.requery(); state.bindInteger(1, folderPeer.folder_id); state.bindInteger(2, 0); state.bindLong(3, did); state.step(); unknownDialogsIds.remove(did); } } else if (inputPeers != null) { for (int a = 0, N = inputPeers.size(); a < N; a++) { TLRPC.TL_inputFolderPeer folderPeer = inputPeers.get(a); long did = DialogObject.getPeerDialogId(folderPeer.peer); state.requery(); state.bindInteger(1, folderPeer.folder_id); state.bindInteger(2, 0); state.bindLong(3, did); state.step(); unknownDialogsIds.remove(did); } } else { state.requery(); state.bindInteger(1, folderId); state.bindInteger(2, 0); state.bindLong(3, dialogId); state.step(); } state.dispose(); database.commitTransaction(); checkIfFolderEmptyInternal(1); resetAllUnreadCounters(false); } catch (Exception e) { FileLog.e(e); } }); } private void checkIfFolderEmptyInternal(int folderId) { try { SQLiteCursor cursor = database.queryFinalized("SELECT did FROM dialogs WHERE folder_id = ?", folderId); boolean isEmpty = true; while (cursor.next()) { long did = cursor.longValue(0); int lowerId = (int) did; int highId = (int) (did >> 32); if (lowerId > 0 || highId != 0 && lowerId == 0) { isEmpty = false; break; } else { TLRPC.Chat chat = getChat(-lowerId); if (!ChatObject.isNotInChat(chat) && chat.migrated_to == null) { isEmpty = false; break; } } } cursor.dispose(); if (isEmpty) { AndroidUtilities.runOnUIThread(() -> getMessagesController().onFolderEmpty(folderId)); database.executeFast("DELETE FROM dialogs WHERE did = " + DialogObject.makeFolderDialogId(folderId)).stepThis().dispose(); } } catch (Exception e) { FileLog.e(e); } } public void checkIfFolderEmpty(int folderId) { storageQueue.postRunnable(() -> checkIfFolderEmptyInternal(folderId)); } public void unpinAllDialogsExceptNew(final ArrayList dids, int folderId) { storageQueue.postRunnable(() -> { try { ArrayList unpinnedDialogs = new ArrayList<>(); SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT did, folder_id FROM dialogs WHERE pinned > 0 AND did NOT IN (%s)", TextUtils.join(",", dids))); while (cursor.next()) { long did = cursor.longValue(0); int fid = cursor.intValue(1); if (fid == folderId && (int) did != 0 && !DialogObject.isFolderDialogId(did)) { unpinnedDialogs.add(cursor.longValue(0)); } } cursor.dispose(); if (!unpinnedDialogs.isEmpty()) { SQLitePreparedStatement state = database.executeFast("UPDATE dialogs SET pinned = ? WHERE did = ?"); for (int a = 0; a < unpinnedDialogs.size(); a++) { long did = unpinnedDialogs.get(a); state.requery(); state.bindInteger(1, 0); state.bindLong(2, did); state.step(); } state.dispose(); } } catch (Exception e) { FileLog.e(e); } }); } public void setDialogUnread(final long did, final boolean unread) { storageQueue.postRunnable(() -> { try { int flags = 0; SQLiteCursor cursor = null; try { cursor = database.queryFinalized("SELECT flags FROM dialogs WHERE did = " + did); if (cursor.next()) { flags = cursor.intValue(0); } } catch (Exception e) { FileLog.e(e); } finally { if (cursor != null) { cursor.dispose(); } } if (unread) { flags |= 1; } else { flags &= ~1; } SQLitePreparedStatement state = database.executeFast("UPDATE dialogs SET flags = ? WHERE did = ?"); state.bindInteger(1, flags); state.bindLong(2, did); state.step(); state.dispose(); resetAllUnreadCounters(false); } catch (Exception e) { FileLog.e(e); } }); } private void resetAllUnreadCounters(boolean muted) { for (int a = 0, N = dialogFilters.size(); a < N; a++) { MessagesController.DialogFilter filter = dialogFilters.get(a); if (muted) { if ((filter.flags & MessagesController.DIALOG_FILTER_FLAG_EXCLUDE_MUTED) != 0) { filter.pendingUnreadCount = -1; } } else { filter.pendingUnreadCount = -1; } } calcUnreadCounters(false); AndroidUtilities.runOnUIThread(() -> { ArrayList filters = getMessagesController().dialogFilters; for (int a = 0, N = filters.size(); a < N; a++) { filters.get(a).unreadCount = filters.get(a).pendingUnreadCount; } mainUnreadCount = pendingMainUnreadCount; archiveUnreadCount = pendingArchiveUnreadCount; getNotificationCenter().postNotificationName(NotificationCenter.updateInterfaces, MessagesController.UPDATE_MASK_READ_DIALOG_MESSAGE); }); } public void setDialogPinned(final long did, final int pinned) { storageQueue.postRunnable(() -> { try { SQLitePreparedStatement state = database.executeFast("UPDATE dialogs SET pinned = ? WHERE did = ?"); state.bindInteger(1, pinned); state.bindLong(2, did); state.step(); state.dispose(); } catch (Exception e) { FileLog.e(e); } }); } public void setDialogsPinned(ArrayList dids, ArrayList pinned) { storageQueue.postRunnable(() -> { try { SQLitePreparedStatement state = database.executeFast("UPDATE dialogs SET pinned = ? WHERE did = ?"); for (int a = 0, N = dids.size(); a < N; a++) { state.requery(); state.bindInteger(1, pinned.get(a)); state.bindLong(2, dids.get(a)); state.step(); } state.dispose(); } catch (Exception e) { FileLog.e(e); } }); } public void putDialogs(final TLRPC.messages_Dialogs dialogs, final int check) { if (dialogs.dialogs.isEmpty()) { return; } storageQueue.postRunnable(() -> { putDialogsInternal(dialogs, check); try { loadUnreadMessages(); } catch (Exception e) { FileLog.e(e); } }); } public void getDialogMaxMessageId(final long dialog_id, IntCallback callback) { storageQueue.postRunnable(() -> { SQLiteCursor cursor = null; int[] max = new int[1]; try { cursor = database.queryFinalized("SELECT MAX(mid) FROM messages WHERE uid = " + dialog_id); if (cursor.next()) { max[0] = cursor.intValue(0); } } catch (Exception e) { FileLog.e(e); } finally { if (cursor != null) { cursor.dispose(); } } AndroidUtilities.runOnUIThread(() -> callback.run(max[0])); }); } public int getDialogReadMax(final boolean outbox, final long dialog_id) { final CountDownLatch countDownLatch = new CountDownLatch(1); final Integer[] max = new Integer[]{0}; storageQueue.postRunnable(() -> { SQLiteCursor cursor = null; try { if (outbox) { cursor = database.queryFinalized("SELECT outbox_max FROM dialogs WHERE did = " + dialog_id); } else { cursor = database.queryFinalized("SELECT inbox_max FROM dialogs WHERE did = " + dialog_id); } if (cursor.next()) { max[0] = cursor.intValue(0); } } catch (Exception e) { FileLog.e(e); } finally { if (cursor != null) { cursor.dispose(); } } countDownLatch.countDown(); }); try { countDownLatch.await(); } catch (Exception e) { FileLog.e(e); } return max[0]; } public int getChannelPtsSync(final int channelId) { final CountDownLatch countDownLatch = new CountDownLatch(1); final Integer[] pts = new Integer[]{0}; storageQueue.postRunnable(() -> { SQLiteCursor cursor = null; try { cursor = database.queryFinalized("SELECT pts FROM dialogs WHERE did = " + (-channelId)); if (cursor.next()) { pts[0] = cursor.intValue(0); } } catch (Exception e) { FileLog.e(e); } finally { if (cursor != null) { cursor.dispose(); } } try { countDownLatch.countDown(); } catch (Exception e) { FileLog.e(e); } }); try { countDownLatch.await(); } catch (Exception e) { FileLog.e(e); } return pts[0]; } public TLRPC.User getUserSync(final int user_id) { final CountDownLatch countDownLatch = new CountDownLatch(1); final TLRPC.User[] user = new TLRPC.User[1]; storageQueue.postRunnable(() -> { user[0] = getUser(user_id); countDownLatch.countDown(); }); try { countDownLatch.await(); } catch (Exception e) { FileLog.e(e); } return user[0]; } public TLRPC.Chat getChatSync(final int chat_id) { final CountDownLatch countDownLatch = new CountDownLatch(1); final TLRPC.Chat[] chat = new TLRPC.Chat[1]; storageQueue.postRunnable(() -> { chat[0] = getChat(chat_id); countDownLatch.countDown(); }); try { countDownLatch.await(); } catch (Exception e) { FileLog.e(e); } return chat[0]; } public TLRPC.User getUser(final int user_id) { TLRPC.User user = null; try { ArrayList users = new ArrayList<>(); getUsersInternal("" + user_id, users); if (!users.isEmpty()) { user = users.get(0); } } catch (Exception e) { FileLog.e(e); } return user; } public ArrayList getUsers(final ArrayList uids) { ArrayList users = new ArrayList<>(); try { getUsersInternal(TextUtils.join(",", uids), users); } catch (Exception e) { users.clear(); FileLog.e(e); } return users; } public TLRPC.Chat getChat(final int chat_id) { TLRPC.Chat chat = null; try { ArrayList chats = new ArrayList<>(); getChatsInternal("" + chat_id, chats); if (!chats.isEmpty()) { chat = chats.get(0); } } catch (Exception e) { FileLog.e(e); } return chat; } public TLRPC.EncryptedChat getEncryptedChat(final int chat_id) { TLRPC.EncryptedChat chat = null; try { ArrayList encryptedChats = new ArrayList<>(); getEncryptedChatsInternal("" + chat_id, encryptedChats, null); if (!encryptedChats.isEmpty()) { chat = encryptedChats.get(0); } } catch (Exception e) { FileLog.e(e); } return chat; } public void localSearch(int dialogsType, String query, ArrayList resultArray, ArrayList resultArrayNames, ArrayList encUsers, int folderId) { int selfUserId = UserConfig.getInstance(currentAccount).getClientUserId(); try { String search1 = query.trim().toLowerCase(); if (TextUtils.isEmpty(search1)) { return; } String savedMessages = LocaleController.getString("SavedMessages", R.string.SavedMessages).toLowerCase(); String savedMessages2 = "saved messages"; String search2 = LocaleController.getInstance().getTranslitString(search1); if (search1.equals(search2) || search2.length() == 0) { search2 = null; } String[] search = new String[1 + (search2 != null ? 1 : 0)]; search[0] = search1; if (search2 != null) { search[1] = search2; } ArrayList usersToLoad = new ArrayList<>(); ArrayList chatsToLoad = new ArrayList<>(); ArrayList encryptedToLoad = new ArrayList<>(); int resultCount = 0; LongSparseArray dialogsResult = new LongSparseArray<>(); SQLiteCursor cursor; if (folderId >= 0) { cursor = getDatabase().queryFinalized("SELECT did, date FROM dialogs WHERE folder_id = ? ORDER BY date DESC LIMIT 600", folderId); } else { cursor = getDatabase().queryFinalized("SELECT did, date FROM dialogs ORDER BY date DESC LIMIT 600"); } while (cursor.next()) { long id = cursor.longValue(0); DialogsSearchAdapter.DialogSearchResult dialogSearchResult = new DialogsSearchAdapter.DialogSearchResult(); dialogSearchResult.date = cursor.intValue(1); dialogsResult.put(id, dialogSearchResult); int lower_id = (int) id; int high_id = (int) (id >> 32); if (lower_id != 0) { if (lower_id > 0) { if (dialogsType == 4 && lower_id == selfUserId) { continue; } if (dialogsType != 2 && !usersToLoad.contains(lower_id)) { usersToLoad.add(lower_id); } } else { if (dialogsType == 4) { continue; } if (!chatsToLoad.contains(-lower_id)) { chatsToLoad.add(-lower_id); } } } else if (dialogsType == 0 || dialogsType == 3) { if (!encryptedToLoad.contains(high_id)) { encryptedToLoad.add(high_id); } } } cursor.dispose(); if (dialogsType != 4 && (savedMessages).startsWith(search1) || savedMessages2.startsWith(search1)) { TLRPC.User user = UserConfig.getInstance(currentAccount).getCurrentUser(); DialogsSearchAdapter.DialogSearchResult dialogSearchResult = new DialogsSearchAdapter.DialogSearchResult(); dialogSearchResult.date = Integer.MAX_VALUE; dialogSearchResult.name = savedMessages; dialogSearchResult.object = user; dialogsResult.put(user.id, dialogSearchResult); resultCount++; } if (!usersToLoad.isEmpty()) { cursor = getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, status, name FROM users WHERE uid IN(%s)", TextUtils.join(",", usersToLoad))); while (cursor.next()) { String name = cursor.stringValue(2); String tName = LocaleController.getInstance().getTranslitString(name); if (name.equals(tName)) { tName = null; } String username = null; int usernamePos = name.lastIndexOf(";;;"); if (usernamePos != -1) { username = name.substring(usernamePos + 3); } int found = 0; for (String q : search) { if (name.startsWith(q) || name.contains(" " + q) || tName != null && (tName.startsWith(q) || tName.contains(" " + q))) { found = 1; } else if (username != null && username.startsWith(q)) { found = 2; } if (found != 0) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.User user = TLRPC.User.TLdeserialize(data, data.readInt32(false), false); data.reuse(); DialogsSearchAdapter.DialogSearchResult dialogSearchResult = dialogsResult.get(user.id); if (user.status != null) { user.status.expires = cursor.intValue(1); } if (found == 1) { dialogSearchResult.name = AndroidUtilities.generateSearchName(user.first_name, user.last_name, q); } else { dialogSearchResult.name = AndroidUtilities.generateSearchName("@" + user.username, null, "@" + q); } dialogSearchResult.object = user; resultCount++; } break; } } } cursor.dispose(); } if (!chatsToLoad.isEmpty()) { cursor = getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, name FROM chats WHERE uid IN(%s)", TextUtils.join(",", chatsToLoad))); while (cursor.next()) { String name = cursor.stringValue(1); String tName = LocaleController.getInstance().getTranslitString(name); if (name.equals(tName)) { tName = null; } for (String q : search) { if (name.startsWith(q) || name.contains(" " + q) || tName != null && (tName.startsWith(q) || tName.contains(" " + q))) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.Chat chat = TLRPC.Chat.TLdeserialize(data, data.readInt32(false), false); data.reuse(); if (!(chat == null || chat.deactivated || ChatObject.isChannel(chat) && ChatObject.isNotInChat(chat))) { long dialog_id = -chat.id; DialogsSearchAdapter.DialogSearchResult dialogSearchResult = dialogsResult.get(dialog_id); dialogSearchResult.name = AndroidUtilities.generateSearchName(chat.title, null, q); dialogSearchResult.object = chat; resultCount++; } } break; } } } cursor.dispose(); } if (!encryptedToLoad.isEmpty()) { cursor = getDatabase().queryFinalized(String.format(Locale.US, "SELECT q.data, u.name, q.user, q.g, q.authkey, q.ttl, u.data, u.status, q.layer, q.seq_in, q.seq_out, q.use_count, q.exchange_id, q.key_date, q.fprint, q.fauthkey, q.khash, q.in_seq_no, q.admin_id, q.mtproto_seq FROM enc_chats as q INNER JOIN users as u ON q.user = u.uid WHERE q.uid IN(%s)", TextUtils.join(",", encryptedToLoad))); while (cursor.next()) { String name = cursor.stringValue(1); String tName = LocaleController.getInstance().getTranslitString(name); if (name.equals(tName)) { tName = null; } String username = null; int usernamePos = name.lastIndexOf(";;;"); if (usernamePos != -1) { username = name.substring(usernamePos + 2); } int found = 0; for (int a = 0; a < search.length; a++) { String q = search[a]; if (name.startsWith(q) || name.contains(" " + q) || tName != null && (tName.startsWith(q) || tName.contains(" " + q))) { found = 1; } else if (username != null && username.startsWith(q)) { found = 2; } if (found != 0) { TLRPC.EncryptedChat chat = null; TLRPC.User user = null; NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { chat = TLRPC.EncryptedChat.TLdeserialize(data, data.readInt32(false), false); data.reuse(); } data = cursor.byteBufferValue(6); if (data != null) { user = TLRPC.User.TLdeserialize(data, data.readInt32(false), false); data.reuse(); } if (chat != null && user != null) { DialogsSearchAdapter.DialogSearchResult dialogSearchResult = dialogsResult.get((long) chat.id << 32); chat.user_id = cursor.intValue(2); chat.a_or_b = cursor.byteArrayValue(3); chat.auth_key = cursor.byteArrayValue(4); chat.ttl = cursor.intValue(5); chat.layer = cursor.intValue(8); chat.seq_in = cursor.intValue(9); chat.seq_out = cursor.intValue(10); int use_count = cursor.intValue(11); chat.key_use_count_in = (short) (use_count >> 16); chat.key_use_count_out = (short) (use_count); chat.exchange_id = cursor.longValue(12); chat.key_create_date = cursor.intValue(13); chat.future_key_fingerprint = cursor.longValue(14); chat.future_auth_key = cursor.byteArrayValue(15); chat.key_hash = cursor.byteArrayValue(16); chat.in_seq_no = cursor.intValue(17); int admin_id = cursor.intValue(18); if (admin_id != 0) { chat.admin_id = admin_id; } chat.mtproto_seq = cursor.intValue(19); if (user.status != null) { user.status.expires = cursor.intValue(7); } if (found == 1) { dialogSearchResult.name = new SpannableStringBuilder(ContactsController.formatName(user.first_name, user.last_name)); ((SpannableStringBuilder) dialogSearchResult.name).setSpan(new ForegroundColorSpan(Theme.getColor(Theme.key_chats_secretName)), 0, dialogSearchResult.name.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } else { dialogSearchResult.name = AndroidUtilities.generateSearchName("@" + user.username, null, "@" + q); } dialogSearchResult.object = chat; encUsers.add(user); resultCount++; } break; } } } cursor.dispose(); } ArrayList searchResults = new ArrayList<>(resultCount); for (int a = 0; a < dialogsResult.size(); a++) { DialogsSearchAdapter.DialogSearchResult dialogSearchResult = dialogsResult.valueAt(a); if (dialogSearchResult.object != null && dialogSearchResult.name != null) { searchResults.add(dialogSearchResult); } } Collections.sort(searchResults, (lhs, rhs) -> { if (lhs.date < rhs.date) { return 1; } else if (lhs.date > rhs.date) { return -1; } return 0; }); for (int a = 0; a < searchResults.size(); a++) { DialogsSearchAdapter.DialogSearchResult dialogSearchResult = searchResults.get(a); resultArray.add(dialogSearchResult.object); resultArrayNames.add(dialogSearchResult.name); } if (dialogsType != 2) { cursor = getDatabase().queryFinalized("SELECT u.data, u.status, u.name, u.uid FROM users as u INNER JOIN contacts as c ON u.uid = c.uid"); while (cursor.next()) { int uid = cursor.intValue(3); if (dialogsResult.indexOfKey(uid) >= 0) { continue; } String name = cursor.stringValue(2); String tName = LocaleController.getInstance().getTranslitString(name); if (name.equals(tName)) { tName = null; } String username = null; int usernamePos = name.lastIndexOf(";;;"); if (usernamePos != -1) { username = name.substring(usernamePos + 3); } int found = 0; for (String q : search) { if (name.startsWith(q) || name.contains(" " + q) || tName != null && (tName.startsWith(q) || tName.contains(" " + q))) { found = 1; } else if (username != null && username.startsWith(q)) { found = 2; } if (found != 0) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.User user = TLRPC.User.TLdeserialize(data, data.readInt32(false), false); data.reuse(); if (user.status != null) { user.status.expires = cursor.intValue(1); } if (found == 1) { resultArrayNames.add(AndroidUtilities.generateSearchName(user.first_name, user.last_name, q)); } else { resultArrayNames.add(AndroidUtilities.generateSearchName("@" + user.username, null, "@" + q)); } resultArray.add(user); } break; } } } cursor.dispose(); } } catch (Exception e) { FileLog.e(e); } } }