package ru.denull.wire.model;
import static ru.denull.mtproto.CryptoUtils.SHA1;
import java.io.*;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import javax.swing.*;
import ru.denull.mtproto.DataService;
import ru.denull.mtproto.Server;
import ru.denull.mtproto.Server.RPCCallback;
import ru.denull.wire.Utils;
import tl.*;
import tl.TMessage;
import tl.messages.*;
public class DialogManager extends AbstractListModel {
private static final long serialVersionUID = 5885327904689475365L;
private DataService service;
boolean loading = false, loadingFromCache = false;
boolean initializing = false;
boolean cachedData = true;
int total = -1;
int loaded = 0;
int first_id = 0;
public LinkedList<Dialog> all = new LinkedList<Dialog>();
public LinkedList<EncryptedDialog> encrypted = new LinkedList<EncryptedDialog>();
public LinkedList<Dialog> filtered = all;
public HashMap<Integer, EncryptedDialog> chats = new HashMap<Integer, EncryptedDialog>();
String filterQuery = null;
public int totalUnread = 0;
static final Comparator<Dialog> topMessageComparator = new Comparator<Dialog>() {
public int compare(Dialog a, Dialog b) {
return Integer.valueOf(Math.abs(b.top_message)).compareTo(Math.abs(a.top_message));
}
};
public class EncryptedDialog extends Dialog {
public TEncryptedChat chat;
public byte[] a_or_b;
public byte[] dh_prime;
public byte[] key;
public EncryptedDialog(TPeer peer, int top_message, int unread_count, TEncryptedChat chat, byte[] a_or_b, byte[] dh_prime, byte[] key) {
super(peer, top_message, unread_count);
this.chat = chat;
this.a_or_b = a_or_b;
this.dh_prime = dh_prime;
this.key = key;
}
public EncryptedDialog(ByteBuffer buffer) throws Exception {
super(buffer);
chat = (TEncryptedChat) TL.read(buffer);
a_or_b = new byte[256];
buffer.get(a_or_b, 0, 256);
dh_prime = new byte[256];
buffer.get(dh_prime, 0, 256);
key = new byte[256];
buffer.get(key, 0, 256);
}
public ByteBuffer writeTo(ByteBuffer buffer) throws Exception {
super.writeTo(buffer, false);
chat.writeTo(buffer, true);
buffer.put(a_or_b);
buffer.put(dh_prime);
buffer.put(key);
return buffer;
}
public int getSize() throws Exception {
return length() + chat.length(true) + 256 + 256 + 256;
}
}
public DialogManager(DataService service) {
this.service = service;
//peer_id = Utils.getPeerID(peer, service.me);
//reloadDialogs();
reloadEncrypted();
}
public Object getElementAt(int index) {
return (index < filtered.size() ? filtered.get(index) : (filterQuery == null ? "У вас ещё нет диалогов" : "Ничего не найдено"));
}
public int getSize() {
return Math.max(1, filtered.size());
}
public boolean isEmpty() {
return filtered.isEmpty();
}
public void filter(String query) {
filter(query, false);
}
public void filter(String query, boolean force) {
if (query != null) {
query = query.trim().toLowerCase();
}
if (!force && filterQuery != null && filterQuery.equals(query)) {
return;
}
filtered = new LinkedList<Dialog>();
if (query == null || query.length() == 0) {
filtered.addAll(all);
filtered.addAll(encrypted);
Collections.sort(filtered, topMessageComparator);
filterQuery = null;
fireContentsChanged(this, 0, getSize() - 1);
return;
}
filterList(all, query);
filterList(encrypted, query);
Collections.sort(filtered, topMessageComparator);
filterQuery = query;
fireContentsChanged(this, 0, getSize() - 1);
}
public void filterList(LinkedList<? extends Dialog> list, String query) {
for (Dialog dialog : list) {
boolean matches = false;
if (dialog.peer instanceof PeerChat) {
matches = service.chatManager.get(dialog.peer.chat_id).title.toLowerCase().indexOf(query) >= 0;
} else {
TUser user = service.userManager.get(dialog.peer.user_id);
matches = (user.first_name + " " + user.last_name).toLowerCase().indexOf(query) >= 0 || (user.last_name + " " + user.first_name).toLowerCase().indexOf(query) >= 0;
}
if (matches) {
filtered.add(dialog);
}
}
}
public void updateContents() {
fireContentsChanged(this, 0, getSize() - 1);
}
public void updateContents(int index) {
fireContentsChanged(this, index, index);
}
public void reloadDialogs() {
boolean force = true;
final boolean cachedData = false;
if (service.mainServer == null/* || !service.mainServer.transport.isConnected()*/){
//Log.i(TAG, "Not yet connected, stop");
return;
}
//Log.i(TAG, "loading = " + service.dialogManager.loading);
if (service.dialogManager.loading) return;
if (!force && service.dialogManager.total > -1 && all.size() >= service.dialogManager.total) return;
service.dialogManager.loading = true;
service.mainServer.call(new GetDialogs(cachedData ? 0 : all.size(), 0, 100), new RPCCallback<TDialogs>() {
public void done(TDialogs result) {
if (result instanceof Dialogs) {
Dialogs dialogs = (Dialogs) result;
service.dialogManager.total = dialogs.dialogs.length;
store(dialogs.dialogs, true);
service.chatManager.store(dialogs.chats);
service.userManager.store(dialogs.users);
service.messageManager.store(dialogs.messages);
} else {
DialogsSlice dialogs = (DialogsSlice) result;
service.dialogManager.total = dialogs.count;
store(dialogs.dialogs, cachedData);
service.chatManager.store(dialogs.chats);
service.userManager.store(dialogs.users);
service.messageManager.store(dialogs.messages);
}
service.dialogManager.loading = false;
//cachedData = false;
/*dialogList.setModel(new AbstractListModel() {
String[] values = new String[service.dialogManager.total];
public int getSize() {
return values.length;
}
public Object getElementAt(int index) {
return values[index];
}
});*/
//fireContentsChanged(list, 0, service.dialogManager.loaded.size() - 1);
filter(filterQuery, true);
}
public void error(int code, String message) {
//Log.e(TAG, "Error while loading dialogs");
}
});
}
public void reloadEncrypted() {
encrypted.clear();
try {
if (new File(Utils.getHomeDir("encrypted_chats.dat")).exists()) {
FileInputStream f = new FileInputStream(Utils.getHomeDir("encrypted_chats.dat"));
FileChannel ch = f.getChannel( );
MappedByteBuffer mb = ch.map( MapMode.READ_ONLY, 0L, ch.size());
mb.order(ByteOrder.LITTLE_ENDIAN);
while (mb.hasRemaining()) {
EncryptedDialog d = new EncryptedDialog(mb);
encrypted.push(d);
chats.put(d.chat.id, d);
}
f.close();
}
filter(filterQuery, true);
} catch (Exception e) {
e.printStackTrace();
}
}
public void saveEncrypted() {
try {
FileOutputStream f = new FileOutputStream(Utils.getHomeDir("encrypted_chats.dat"), false);
FileChannel ch = f.getChannel();
int capacity = 0;
for (EncryptedDialog dialog : encrypted) {
capacity += dialog.getSize();
}
ByteBuffer buf = ByteBuffer.allocate(capacity);
buf.order(ByteOrder.LITTLE_ENDIAN);
for (EncryptedDialog dialog : encrypted) {
dialog.writeTo(buf);
}
buf.flip();
ch.write(buf);
f.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public void addMessage(TMessage message, int chat_id) {
int index = 0;
Dialog enc = chats.get(chat_id);
for (Dialog dialog : all) {
if (dialog == enc) {
break;
}
if ((dialog.peer instanceof PeerUser && message.to_id instanceof PeerUser && (dialog.peer.user_id == message.from_id || dialog.peer.user_id == message.to_id.user_id)) ||
(dialog.peer instanceof PeerChat && message.to_id instanceof PeerChat && dialog.peer.chat_id == message.to_id.chat_id)) {
break;
}
index++;
}
Dialog dialog;
if (index < all.size()) {
if (message.id > 0 && all.get(index).top_message > message.id) return;
dialog = all.remove(index);
dialog.top_message = message.id;
if (!message.out) {
dialog.unread_count++;
updateUnreadCount(totalUnread + 1);
}
} else {
dialog = new Dialog(message.to_id instanceof PeerChat ? message.to_id : new PeerUser(message.out ? message.to_id.user_id : message.from_id), message.id, 1);
}
all.addFirst(dialog);
// TODO: store at DB
}
public void addMessage(TMessage message) {
addMessage(message, -1);
}
public void store(TDialog[] dialogs, boolean reset) {
if (reset) {
all.clear();
totalUnread = 0;
}
for (TDialog dialog : dialogs) {
all.add((Dialog) dialog);
totalUnread += dialog.unread_count;
}
}
public void resetUnread(TInputPeer peer) {
int peer_id = Utils.getPeerID(peer, service.me);
for (Dialog dialog : all) {
if ((dialog.peer instanceof PeerUser && ((PeerUser) dialog.peer).user_id == peer_id) ||
(dialog.peer instanceof PeerChat && ((PeerChat) dialog.peer).chat_id == -peer_id)) {
updateUnreadCount(Math.max(0, totalUnread - dialog.unread_count));
dialog.unread_count = 0;
break;
}
}
}
public void updateUnreadCount(int unread) {
totalUnread = unread;
//Application app = Application.getApplication();
//app.setDockIconBadge();
if (System.getProperty("os.name").contains("Mac")) {
try {
Object app =
Class.forName("com.apple.eawt.Application")
.getMethod("getApplication", (Class[]) null)
.invoke(null, (Object[]) null);
app.getClass()
.getMethod("setDockIconBadge", new Class[] { String.class })
.invoke(app, new Object[] { totalUnread > 0 ? Utils.toCount(totalUnread) : "" });
} catch (Exception e) {
//fail quietly
}
}
}
public void addEncryptedChat(int user_id, TEncryptedChat chat, byte[] a_or_b, byte[] dh_prime) {
EncryptedDialog d = new EncryptedDialog(new PeerUser(user_id), -service.messageManager.nextMessageID, 0, chat, a_or_b, dh_prime, new byte[256]);
encrypted.push(d);
chats.put(d.chat.id, d);
saveEncrypted();
filter(filterQuery, true);
}
public void updateEncryptedChat(TEncryptedChat chat) {
updateEncryptedChat(chat, null);
}
public void updateEncryptedChat(TEncryptedChat chat, byte[] key) {
int user_id = (chat.admin_id == service.me.id) ? chat.participant_id : chat.admin_id;
EncryptedDialog d = chats.get(chat.id);
if (d == null) {
addEncryptedChat(user_id, chat, new byte[256], new byte[256]);
return;
}
d.chat = chat;
if (key != null) {
d.key = key;
} else
if (chat instanceof EncryptedChat && chat.admin_id == service.me.id) {
BigInteger g_b = new BigInteger(1, chat.g_a_or_b);
BigInteger a = new BigInteger(1, d.a_or_b);
BigInteger dh_prime = new BigInteger(1, d.dh_prime);
d.key = g_b.modPow(a, dh_prime).toByteArray();
for (int i = 0; i < d.key.length; i++) {
d.key[i] ^= chat.nonce[i];
}
ByteBuffer tmp;
try {
tmp = ByteBuffer.wrap(SHA1(d.key));
tmp.order(ByteOrder.LITTLE_ENDIAN);
long key_fingerprint = tmp.getLong(12);
if (key_fingerprint != chat.key_fingerprint) {
encrypted.remove(d);
chats.remove(d.chat.id);
JOptionPane.showMessageDialog(null, "Ошибка создания секретного чата: отпечаток ключа некорректен.");
return;
}
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
saveEncrypted();
filter(filterQuery, true);
}
public void addEncryptedMessage(int chat_id, int user_id, TEncryptedMessage encrypted, TDecryptedMessage message) {
try {
FileOutputStream f = new FileOutputStream(Utils.getHomeDir("encrypted_chat" + chat_id + ".dat"), true);
FileChannel ch = f.getChannel();
long size = ch.size();
ByteBuffer buf = ByteBuffer.allocate(4 + encrypted.length(true) + message.length(true) + 8);
buf.order(ByteOrder.LITTLE_ENDIAN);
buf.putInt(user_id);
encrypted.writeTo(buf, true);
message.writeTo(buf, true);
buf.putLong(size);
buf.flip();
ch.write(buf);
f.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}