text: /**********************************************************************
   * LeechCraft - modular cross-platform feature rich internet client.
   * Copyright (C) 2006-2014  Georg Rudoy
   *
   * Distributed under the Boost Software License, Version 1.0.
   * (See accompanying file LICENSE or copy at https://www.boost.org/LICENSE_1_0.txt)
   **********************************************************************/
  
  #include "vkaccount.h"
  #include <stdexcept>
  #include <QUuid>
  #include <QIcon>
  #include <QtDebug>
  #include <util/svcauth/vkcaptchadialog.h>
  #include <util/sll/prelude.h>
  #include <util/sll/either.h>
  #include <util/sll/unreachable.h>
  #include "vkprotocol.h"
  #include "vkconnection.h"
  #include "vkentry.h"
  #include "vkmessage.h"
  #include "photofetcher.h"
  #include "georesolver.h"
  #include "groupsmanager.h"
  #include "xmlsettingsmanager.h"
  #include "vkchatentry.h"
  #include "logger.h"
  #include "accountconfigdialog.h"
  #include "serverhistorymanager.h"
  #include "vkconnectiontunesetter.h"
  #include "transfermanager.h"
  #include "appinfomanager.h"
  
  namespace LC
  {
  namespace Azoth
  {
  namespace Murm
  {
  	VkAccount::VkAccount (const QString& name, VkProtocol *proto,
  			ICoreProxy_ptr proxy, const QByteArray& id, const QByteArray& cookies)
  	: QObject (proto)
  	, CoreProxy_ (proxy)
  	, Proto_ (proto)
  	, ID_ (id.isEmpty () ? QUuid::createUuid ().toByteArray () : id)
  	, PhotoStorage_ (new PhotoFetcher (proxy->GetNetworkAccessManager (), this))
  	, Name_ (name)
  	, Logger_ (new Logger (ID_, this))
  	, Conn_ (new VkConnection (name, cookies, proxy, *Logger_))
  	, ConnTuneSetter_ (new VkConnectionTuneSetter (Conn_, proxy))
  	, GroupsMgr_ (new GroupsManager (Conn_))
  	, GeoResolver_ (new GeoResolver (Conn_, this))
  	, ServHistMgr_ (new ServerHistoryManager (this))
  	, XFerMgr_ (new TransferManager (this))
  	, AppInfoMgr_ (new AppInfoManager (proxy->GetNetworkAccessManager (), Conn_, this))
  	{
  		connect (Conn_,
  				SIGNAL (cookiesChanged ()),
  				this,
  				SLOT (emitUpdateAcc ()));
  		connect (Conn_,
  				SIGNAL (stoppedPolling ()),
  				this,
  				SLOT (finishOffline ()));
  
  		connect (Conn_,
  				SIGNAL (gotSelfInfo (UserInfo)),
  				this,
  				SLOT (handleSelfInfo (UserInfo)));
  		connect (Conn_,
  				SIGNAL (gotUsers (QList<UserInfo>)),
  				this,
  				SLOT (handleUsers (QList<UserInfo>)));
  		connect (Conn_,
  				SIGNAL (gotNRIList (QList<qulonglong>)),
  				this,
  				SLOT (handleNRIList (QList<qulonglong>)));
  		connect (Conn_,
  				SIGNAL (userStateChanged (qulonglong, bool)),
  				this,
  				SLOT (handleUserState (qulonglong, bool)));
  		connect (Conn_,
  				SIGNAL (gotUserAppInfoStub (qulonglong, AppInfo)),
  				this,
  				SLOT (handleUserAppInfoStub (qulonglong, AppInfo)));
  
  		connect (Conn_,
  				SIGNAL (gotMessage (MessageInfo)),
  				this,
  				SLOT (handleMessage (MessageInfo)));
  		connect (Conn_,
  				SIGNAL (gotMessage (FullMessageInfo, MessageInfo)),
  				this,
  				SLOT (handleMessage (FullMessageInfo, MessageInfo)));
  		connect (Conn_,
  				SIGNAL (gotTypingNotification (qulonglong)),
  				this,
  				SLOT (handleTypingNotification (qulonglong)));
  		connect (Conn_,
  				SIGNAL (statusChanged (EntryStatus)),
  				this,
  				SIGNAL (statusChanged (EntryStatus)));
  
  		connect (Conn_,
  				SIGNAL (mucChanged (qulonglong)),
  				this,
  				SLOT (handleMucChanged (qulonglong)));
  		connect (Conn_,
  				SIGNAL (gotChatInfo (ChatInfo)),
  				this,
  				SLOT (handleGotChatInfo (ChatInfo)));
  		connect (Conn_,
  				SIGNAL (chatUserRemoved (qulonglong, qulonglong)),
  				this,
  				SLOT (handleChatUserRemoved (qulonglong, qulonglong)));
  
  		connect (Conn_,
  				SIGNAL (captchaNeeded (QString, QUrl)),
  				this,
  				SLOT (handleCaptcha (QString, QUrl)));
  
  		connect (Logger_,
  				SIGNAL (gotConsolePacket (QByteArray, IHaveConsole::PacketDirection, QString)),
  				this,
  				SIGNAL (gotConsolePacket (QByteArray, IHaveConsole::PacketDirection, QString)));
  
  		connect (ServHistMgr_,
  				SIGNAL (serverHistoryFetched (QModelIndex, QByteArray, SrvHistMessages_t)),
  				this,
  				SIGNAL (serverHistoryFetched (QModelIndex, QByteArray, SrvHistMessages_t)));
  
  		connect (AppInfoMgr_,
  				SIGNAL (gotAppInfo (AppInfo)),
  				this,
  				SLOT (handleAppInfo (AppInfo)));
  	}
  
  	QByteArray VkAccount::Serialize () const
  	{
  		QByteArray result;
  		QDataStream out (&result, QIODevice::WriteOnly);
  
  		out << static_cast<quint8> (4)
  				<< ID_
  				<< Name_
  				<< Conn_->GetCookies ()
  				<< EnableFileLog_
  				<< PublishTune_
  				<< MarkAsOnline_
  				<< UpdateStatus_;
  
  		return result;
  	}
  
  	VkAccount* VkAccount::Deserialize (const QByteArray& data, VkProtocol *proto, ICoreProxy_ptr proxy)
  	{
  		QDataStream in (data);
  
  		quint8 version = 0;
  		in >> version;
  		if (version < 1 || version > 4)
  		{
  			qWarning () << Q_FUNC_INFO
  					<< "unknown version"
  					<< version;
  			return nullptr;
  		}
  
  		QByteArray id;
  		QString name;
  		QByteArray cookies;
  
  		in >> id
  				>> name
  				>> cookies;
  
  		auto acc = new VkAccount (name, proto, proxy, id, cookies);
  
  		if (version >= 2)
  			in >> acc->EnableFileLog_
  					>> acc->PublishTune_;
  		if (version >= 3)
  			in >> acc->MarkAsOnline_;
  		if (version >= 4)
  			in >> acc->UpdateStatus_;
  
  		acc->Init ();
  
  		return acc;
  	}
  
  	void VkAccount::Init ()
  	{
  		Logger_->SetFileEnabled (EnableFileLog_);
  		handleMarkOnline ();
  	}
  
  	void VkAccount::Send (qulonglong to, VkConnection::Type type,
  			VkMessage *msg, const QStringList& attachments)
  	{
  		QPointer<VkMessage> safeMsg { msg };
  		Conn_->SendMessage (to,
  				msg->GetRawBody (),
  				[safeMsg] (qulonglong id)
  				{
  					if (safeMsg)
  						safeMsg->SetID (id);
  				},
  				type,
  				attachments);
  	}
  
  	void VkAccount::CreateChat (const QString& name, const QList<VkEntry*>& entries)
  	{
  		QList<qulonglong> ids;
  		for (auto entry : entries)
  			ids << entry->GetInfo ().ID_;
  		Conn_->CreateChat (name, ids);
  	}
  
  	VkEntry* VkAccount::GetEntry (qulonglong id) const
  	{
  		return Entries_.value (id);
  	}
  
  	VkEntry* VkAccount::FindEntryById (const QString& entryId) const
  	{
  		const auto pos = std::find_if (Entries_.begin (), Entries_.end (),
  				[&entryId] (VkEntry *entry) { return entry->GetEntryID () == entryId; });
  		return pos != Entries_.end () ? *pos : nullptr;
  	}
  
  	VkEntry* VkAccount::GetSelf () const
  	{
  		return SelfEntry_;
  	}
  
  	VkEntry* VkAccount::GetEntryOrCreate (const UserInfo& info)
  	{
  		if (!Entries_.contains (info.ID_))
  			CreateUsers ({ info });
  
  		return Entries_.value (info.ID_);
  	}
  
  	ICoreProxy_ptr VkAccount::GetCoreProxy () const
  	{
  		return CoreProxy_;
  	}
  
  	VkConnection* VkAccount::GetConnection () const
  	{
  		return Conn_;
  	}
  
  	PhotoFetcher* VkAccount::GetPhotoStorage () const
  	{
  		return PhotoStorage_;
  	}
  
  	GeoResolver* VkAccount::GetGeoResolver () const
  	{
  		return GeoResolver_;
  	}
  
  	GroupsManager* VkAccount::GetGroupsManager () const
  	{
  		return GroupsMgr_;
  	}
  
  	Logger& VkAccount::GetLogger () const
  	{
  		return *Logger_;
  	}
  
  	QObject* VkAccount::GetQObject ()
  	{
  		return this;
  	}
  
  	VkProtocol* VkAccount::GetParentProtocol () const
  	{
  		return Proto_;
  	}
  
  	IAccount::AccountFeatures VkAccount::GetAccountFeatures () const
  	{
  		return AccountFeature::FRenamable;
  	}
  
  	QList<QObject*> VkAccount::GetCLEntries ()
  	{
  		QList<QObject*> result;
  		result.reserve (Entries_.size () + ChatEntries_.size ());
  		std::copy (Entries_.begin (), Entries_.end (), std::back_inserter (result));
  		std::copy (ChatEntries_.begin (), ChatEntries_.end (), std::back_inserter (result));
  		return result;
  	}
  
  	QString VkAccount::GetAccountName () const
  	{
  		return Name_;
  	}
  
  	QString VkAccount::GetOurNick () const
  	{
  		return tr ("me");
  	}
  
  	void VkAccount::RenameAccount (const QString& name)
  	{
  		Name_ = name;
  		emit accountRenamed (name);
  		emit accountChanged (this);
  	}
  
  	QByteArray VkAccount::GetAccountID () const
  	{
  		return ID_;
  	}
  
  	QList<QAction*> VkAccount::GetActions () const
  	{
  		return {};
  	}
  
  	void VkAccount::OpenConfigurationDialog ()
  	{
  		auto dia = new AccountConfigDialog;
  
  		AccConfigDia_ = dia;
  
  		dia->SetFileLogEnabled (EnableFileLog_);
  		dia->SetPublishTuneEnabled (PublishTune_);
  		dia->SetMarkAsOnline (MarkAsOnline_);
  		dia->SetUpdateStatusEnabled (UpdateStatus_);
  
  		connect (dia,
  				SIGNAL (reauthRequested ()),
  				Conn_,
  				SLOT (reauth ()));
  
  		connect (dia,
  				SIGNAL (rejected ()),
  				dia,
  				SLOT (deleteLater ()));
  		connect (dia,
  				SIGNAL (accepted ()),
  				this,
  				SLOT (handleConfigDialogAccepted ()));
  
  		dia->show ();
  	}
  
  	EntryStatus VkAccount::GetState () const
  	{
  		return Conn_->GetStatus ();
  	}
  
  	void VkAccount::ChangeState (const EntryStatus& status)
  	{
  		Conn_->SetStatus (status, UpdateStatus_);
  	}
  
  	void VkAccount::Authorize (QObject*)
  	{
  	}
  
  	void VkAccount::DenyAuth (QObject*)
  	{
  	}
  
  	void VkAccount::RequestAuth (const QString&, const QString&, const QString&, const QStringList&)
  	{
  	}
  
  	void VkAccount::RemoveEntry (QObject *entryObj)
  	{
  		auto entry = qobject_cast<VkEntry*> (entryObj);
  		if (!entry)
  		{
  			qWarning () << Q_FUNC_INFO
  					<< entry
  					<< "is not a VkEntry";
  			return;
  		}
  
  		if (entry->IsNonRoster ())
  		{
  			emit removedCLItems ({ entry });
  
  			const auto id = entry->GetInfo ().ID_;
  			Entries_.remove (id);
  			entry->deleteLater ();
  
  			NonRosterItems_.removeOne (id);
  			Conn_->SetNRIList (NonRosterItems_);
  
  			return;
  		}
  	}
  
  	QObject* VkAccount::GetTransferManager () const
  	{
  		return XFerMgr_;
  	}
  
  	void VkAccount::PublishTune (const QMap<QString, QVariant>& tuneData)
  	{
  		if (!PublishTune_)
  			return;
  
  		ConnTuneSetter_->SetTune (tuneData);
  	}
  
  	QObject* VkAccount::GetSelfContact () const
  	{
  		return SelfEntry_;
  	}
  
  	QIcon VkAccount::GetAccountIcon () const
  	{
  		return {};
  	}
  
  	IHaveConsole::PacketFormat VkAccount::GetPacketFormat () const
  	{
  		return PacketFormat::PlainText;
  	}
  
  	void VkAccount::SetConsoleEnabled (bool)
  	{
  	}
  
  	QObject* VkAccount::CreateNonRosterItem (const QString& idStr)
  	{
  		auto realId = idStr;
  		if (realId.startsWith ("id"))
  			realId = realId.remove (0, 2);
  
  		bool ok = false;
  		const auto id = realId.toULongLong (&ok);
  		if (!ok)
  			throw std::runtime_error (tr ("%1 is invalid VKontake ID")
  					.arg (idStr)
  					.toUtf8 ().constData ());
  
  		if (Entries_.contains (id))
  			return Entries_ [id];
  
  		const auto entry = CreateNonRosterItem (id);
  		emit gotCLItems ({ entry });
  
  		NonRosterItems_ << id;
  		Conn_->SetNRIList (NonRosterItems_);
  		Conn_->GetUserInfo ({ id });
  
  		return entry;
  	}
  
  	bool VkAccount::HasFeature (ServerHistoryFeature feature) const
  	{
  		switch (feature)
  		{
  		case ServerHistoryFeature::AccountSupportsHistory:
  		case ServerHistoryFeature::DatedFetching:
  			return true;
  		case ServerHistoryFeature::Configurable:
  			return false;
  		}
  
  		Util::Unreachable ();
  	}
  
  	void VkAccount::OpenServerHistoryConfiguration ()
  	{
  	}
  
  	QAbstractItemModel* VkAccount::GetServerContactsModel () const
  	{
  		return ServHistMgr_->GetModel ();
  	}
  
  	void VkAccount::FetchServerHistory (const QModelIndex& contact, const QByteArray& startId, int count)
  	{
  		ServHistMgr_->RequestHistory (contact, startId.toInt (), count);
  	}
  
  	DefaultSortParams VkAccount::GetSortParams () const
  	{
  		return { 0, ServerHistoryRole::LastMessageDate, Qt::DescendingOrder };
  	}
  
  	QFuture<IHaveServerHistory::DatedFetchResult_t> VkAccount::FetchServerHistory (const QDateTime& since)
  	{
  		return ServHistMgr_->FetchServerHistory (since);
  	}
  
  	void VkAccount::TryPendingMessages ()
  	{
  		decltype (PendingMessages_) pending;
  
  		using std::swap;
  		swap (pending, PendingMessages_);
  
  		for (const auto& pair : pending)
  			handleMessage (pair.second, pair.first);
  	}
  
  	VkEntry* VkAccount::CreateNonRosterItem (qulonglong id)
  	{
  		UserInfo info;
  		info.ID_ = id;
  
  		auto entry = new VkEntry (info, this);
  		entry->SetNonRoster ();
  		Entries_ [id] = entry;
  
  		return entry;
  	}
  
  	bool VkAccount::CreateUsers (const QList<UserInfo>& infos)
  	{
  		AppInfoMgr_->CacheAppInfo (Util::Map (infos, &UserInfo::AppInfo_));
  
  		QList<QObject*> newEntries;
  		QHash<int, QString> newCountries;
  		QHash<int, QString> newCities;
  		bool hadNew = false;
  		for (const auto& info : infos)
  		{
  			if (Entries_.contains (info.ID_))
  			{
  				const auto entry = Entries_ [info.ID_];
  				entry->UpdateInfo (info);
  
  				continue;
  			}
  
  			auto entry = new VkEntry (info, this);
  			Entries_ [info.ID_] = entry;
  			newEntries << entry;
  
  			newCountries [info.Country_] = info.CountryName_;
  			newCities [info.City_] = info.CityName_;
  
  			hadNew = true;
  		}
  
  		GeoResolver_->AddCountriesToCache (newCountries);
  		GeoResolver_->AddCitiesToCache (newCities);
  
  		if (!newEntries.isEmpty ())
  			emit gotCLItems (newEntries);
  
  		return hadNew;
  	}
  
  	void VkAccount::handleSelfInfo (const UserInfo& info)
  	{
  		CreateUsers ({ info });
  
  		SelfEntry_ = Entries_ [info.ID_];
  		SelfEntry_->SetSelf ();
  	}
  
  	void VkAccount::handleUsers (const QList<UserInfo>& infos)
  	{
  		if (CreateUsers (infos))
  		{
  			TryPendingMessages ();
  			ServHistMgr_->refresh ();
  		}
  	}
  
  	void VkAccount::handleNRIList (const QList<qulonglong>& ids)
  	{
  		QList<qulonglong> toRequest;
  		QList<QObject*> objs;
  		for (auto id : ids)
  		{
  			if (Entries_.contains (id))
  				continue;
  
  			toRequest << id;
  			objs << CreateNonRosterItem (id);
  		}
  
  		emit gotCLItems (objs);
  		Conn_->GetUserInfo (toRequest);
  
  		NonRosterItems_ = toRequest;
  	}
  
  	void VkAccount::handleUserState (qulonglong id, bool isOnline)
  	{
  		if (!Entries_.contains (id))
  		{
  			qWarning () << Q_FUNC_INFO
  					<< "unknown user"
  					<< id;
  			Conn_->RerequestFriends ();
  			return;
  		}
  
  		auto entry = Entries_.value (id);
  		auto info = entry->GetInfo ();
  		info.IsOnline_ = isOnline;
  		entry->UpdateInfo (info);
  
  		entry->UpdateAppInfo ({}, {});
  
  		if (isOnline)
  			Conn_->RequestUserAppId (id);
  	}
  
  	void VkAccount::handleUserAppInfoStub (qulonglong id, const AppInfo& appInfo)
  	{
  		if (!Entries_.contains (id))
  		{
  			qWarning () << Q_FUNC_INFO
  					<< "unknown user"
  					<< id;
  			return;
  		}
  
  		const auto entry = Entries_.value (id);
  
  		const auto appId = appInfo.AppId_;
  		AppInfoMgr_->PerformWithAppInfo (appId,
  				[this, entry] (const AppInfo& info)
  					{ entry->UpdateAppInfo (info, AppInfoMgr_->GetAppImage (info)); },
  				[entry, appInfo] { entry->UpdateAppInfo (appInfo, {}); });
  	}
  
  	void VkAccount::handleMessage (const MessageInfo& info)
  	{
  		handleMessage ({}, info);
  	}
  
  	void VkAccount::handleMessage (const FullMessageInfo& fullInfo, const MessageInfo& info)
  	{
  		if (info.Params_.value ("source_act") == "chat_kick_user" &&
  				info.Params_.value ("source_mid").toULongLong () == SelfEntry_->GetInfo ().ID_)
  			return;
  
  		const auto from = info.From_;
  		if (info.Flags_ & MessageFlag::Chat)
  		{
  			if (!ChatEntries_.contains (from))
  			{
  				qDebug () << Q_FUNC_INFO
  						<< "unknown chat entry"
  						<< from;
  
  				if (!std::any_of (PendingMessages_.begin (), PendingMessages_.end (),
  						[from] (const QPair<MessageInfo, FullMessageInfo>& info)
  						{
  							return from == info.first.From_;
  						}))
  				{
  					qDebug () << Q_FUNC_INFO
  							<< "requesting info for"
  							<< from;
  					Conn_->RequestChatInfo (from);
  				}
  
  				PendingMessages_.append ({ info, fullInfo });
  				return;
  			}
  
  			switch (ChatEntries_.value (from)->HandleMessage (info, fullInfo))
  			{
  			case VkChatEntry::HandleMessageResult::Accepted:
  			case VkChatEntry::HandleMessageResult::Rejected:
  				return;
  			case VkChatEntry::HandleMessageResult::UserInfoRequested:
  				PendingMessages_.append ({ info, fullInfo });
  				return;
  			}
  		}
  		else
  		{
  			if (!Entries_.contains (from))
  			{
  				qWarning () << Q_FUNC_INFO
  						<< "message from unknown user"
  						<< from;
  
  				PendingMessages_.append ({ info, fullInfo });
  
  				Conn_->GetUserInfo ({ from });
  				return;
  			}
  
  			Entries_.value (from)->HandleMessage (info, fullInfo);
  		}
  	}
  
  	void VkAccount::handleTypingNotification (qulonglong uid)
  	{
  		if (!Entries_.contains (uid))
  		{
  			qWarning () << Q_FUNC_INFO
  					<< "message from unknown user"
  					<< uid;
  			Conn_->RerequestFriends ();
  			return;
  		}
  
  		const auto entry = Entries_.value (uid);
  		entry->HandleTypingNotification ();
  	}
  
  	void VkAccount::handleMarkOnline ()
  	{
  		Conn_->SetMarkingOnlineEnabled (MarkAsOnline_);
  	}
  
  	void VkAccount::finishOffline ()
  	{
  		if (!ChatEntries_.isEmpty ())
  		{
  			QList<QObject*> toRemove;
  			for (auto item : ChatEntries_)
  				toRemove << item;
  			emit removedCLItems (toRemove);
  
  			qDeleteAll (ChatEntries_);
  			ChatEntries_.clear ();
  		}
  
  		for (auto entry : Entries_)
  		{
  			auto info = entry->GetInfo ();
  			info.IsOnline_ = false;
  			entry->UpdateInfo (info, false);
  		}
  	}
  
  	void VkAccount::handleCaptcha (const QString& cid, const QUrl& url)
  	{
  		if (IsRequestingCaptcha_)
  		{
  			Conn_->HandleCaptcha (cid, {});
  			return;
  		}
  
  		auto dia = new Util::SvcAuth::VkCaptchaDialog (url, cid, CoreProxy_->GetNetworkAccessManager ());
  		dia->SetContextName ("Azoth Murm");
  		connect (dia,
  				SIGNAL (gotCaptcha (QString, QString)),
  				this,
  				SLOT (handleCaptchaEntered (QString, QString)));
  		dia->show ();
  
  		IsRequestingCaptcha_ = true;
  	}
  
  	void VkAccount::handleCaptchaEntered (const QString& cid, const QString& value)
  	{
  		Conn_->HandleCaptcha (cid, value);
  		IsRequestingCaptcha_ = false;
  	}
  
  	void VkAccount::handleConfigDialogAccepted()
  	{
  		if (!AccConfigDia_)
  			return;
  
  		EnableFileLog_ = AccConfigDia_->GetFileLogEnabled ();
  		Logger_->SetFileEnabled (EnableFileLog_);
  
  		MarkAsOnline_ = AccConfigDia_->GetMarkAsOnline ();
  		handleMarkOnline ();
  
  		PublishTune_ = AccConfigDia_->GetPublishTuneEnabled ();
  
  		UpdateStatus_ = AccConfigDia_->GetUpdateStatusEnabled ();
  
  		emit accountChanged (this);
  
  		AccConfigDia_->deleteLater ();
  	}
  
  	void VkAccount::handleMucChanged (qulonglong chat)
  	{
  		if (!ChatEntries_.contains (chat))
  		{
  			qDebug () << Q_FUNC_INFO
  					<< "ignoring chat change for non-present chat"
  					<< chat;
  			return;
  		}
  
  		Conn_->RequestChatInfo (chat);
  	}
  
  	void VkAccount::handleGotChatInfo (const ChatInfo& info)
  	{
  		if (!ChatEntries_.contains (info.ChatID_))
  		{
  			auto entry = new VkChatEntry (info, this);
  			connect (entry,
  					&VkChatEntry::removeEntry,
  					this,
  					[entry, this]
  					{
  						ChatEntries_.remove (ChatEntries_.key (entry));
  						emit removedCLItems ({ entry });
  						entry->deleteLater ();
  					});
  			ChatEntries_ [info.ChatID_] = entry;
  			emit gotCLItems ({ entry });
  
  			TryPendingMessages ();
  		}
  		else
  			ChatEntries_ [info.ChatID_]->UpdateInfo (info);
  	}
  
  	void VkAccount::handleChatUserRemoved (qulonglong chat, qulonglong id)
  	{
  		if (!ChatEntries_.contains (chat))
  		{
  			qWarning () << Q_FUNC_INFO
  					<< "unknown chat"
  					<< chat;
  			return;
  		}
  
  		ChatEntries_ [chat]->HandleRemoved (id);
  	}
  
  	void VkAccount::handleAppInfo (const AppInfo& info)
  	{
  		for (const auto entry : Entries_)
  			if (entry->GetInfo ().AppInfo_.AppId_ == info.AppId_)
  				entry->UpdateAppInfo (info, AppInfoMgr_->GetAppImage (info));
  	}
  
  	void VkAccount::emitUpdateAcc ()
  	{
  		emit accountChanged (this);
  	}
  }
  }
  }
  