/*
Copyright (c) 2013-18 Miranda NG team (https://miranda-ng.org)
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation version 2
of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#include "stdafx.h"
void CVkProto::RetrievePollingInfo()
{
debugLogA("CVkProto::RetrievePollingInfo");
if (!IsOnline())
return;
Push(new AsyncHttpRequest(this, REQUEST_GET, "/method/messages.getLongPollServer.json", true, &CVkProto::OnReceivePollingInfo, AsyncHttpRequest::rpHigh)
<< INT_PARAM("use_ssl", 1)
<< INT_PARAM("lp_version", 2)
);
}
void CVkProto::OnReceivePollingInfo(NETLIBHTTPREQUEST *reply, AsyncHttpRequest *pReq)
{
debugLogA("CVkProto::OnReceivePollingInfo %d", reply->resultCode);
if (reply->resultCode != 200)
return;
JSONNode jnRoot;
JSONNode jnResponse = CheckJsonResponse(pReq, reply, jnRoot);
if (!jnResponse) {
if (!pReq->bNeedsRestart) {
debugLogA("CVkProto::OnReceivePollingInfo PollingThread not start (getLongPollServer error)");
m_pollingConn = nullptr;
ShutdownSession();
}
return;
}
char ts[32];
itoa(jnResponse["ts"].as_int(), ts, 10);
m_pollingTs = mir_strdup(ts);
m_pollingKey = mir_u2a(jnResponse["key"].as_mstring());
m_pollingServer = mir_u2a(jnResponse["server"].as_mstring());
if (!m_hPollingThread) {
debugLogA("CVkProto::OnReceivePollingInfo m_hPollingThread is nullptr");
debugLogA("CVkProto::OnReceivePollingInfo m_pollingTs = '%s' m_pollingKey = '%s' m_pollingServer = '%s'",
m_pollingTs ? m_pollingTs : "",
m_pollingKey ? m_pollingKey : "",
m_pollingServer ? m_pollingServer : "");
if (m_pollingTs != nullptr && m_pollingKey != nullptr && m_pollingServer != nullptr) {
debugLogA("CVkProto::OnReceivePollingInfo PollingThread starting...");
m_hPollingThread = ForkThreadEx(&CVkProto::PollingThread, nullptr, nullptr);
}
else {
debugLogA("CVkProto::OnReceivePollingInfo PollingThread not start");
m_pollingConn = nullptr;
ShutdownSession();
return;
}
}
else
debugLogA("CVkProto::OnReceivePollingInfo m_hPollingThread is not nullptr");
}
void CVkProto::PollUpdates(const JSONNode &jnUpdates)
{
debugLogA("CVkProto::PollUpdates");
CMStringA mids;
int msgid, uid, flags, platform;
bool bNonEdited = true;
MCONTACT hContact;
for (auto &it : jnUpdates) {
const JSONNode &jnChild = it.as_array();
switch (jnChild[json_index_t(0)].as_int()) {
case VKPOLL_MSG_DELFLAGS:
if (jnChild.size() < 4)
break;
msgid = jnChild[1].as_int();
flags = jnChild[2].as_int();
uid = jnChild[3].as_int();
hContact = FindUser(uid);
if (hContact != 0 && (flags & VKFLAG_MSGUNREAD) && !CheckMid(m_incIds, msgid)) {
setDword(hContact, "LastMsgReadTime", time(0));
if (ServiceExists(MS_MESSAGESTATE_UPDATE)) {
MessageReadData data(time(0), MRD_TYPE_READTIME);
CallService(MS_MESSAGESTATE_UPDATE, hContact, (LPARAM)&data);
}
else
SetSrmmReadStatus(hContact);
if (m_vkOptions.bUserForceInvisibleOnActivity)
SetInvisible(hContact);
if (m_vkOptions.bSyncReadMessageStatusFromServer)
MarkDialogAsRead(hContact);
}
break;
case VKPOLL_MSG_EDITED:
bNonEdited = false;
case VKPOLL_MSG_ADDED: // new message
msgid = jnChild[1].as_int();
// skip outgoing messages sent from a client
flags = jnChild[2].as_int();
if (bNonEdited && (flags & VKFLAG_MSGOUTBOX && !(flags & VKFLAG_MSGCHAT) && !m_vkOptions.bSendVKLinksAsAttachments && CheckMid(m_sendIds, msgid)))
break;
if (!mids.IsEmpty())
mids.AppendChar(',');
mids.AppendFormat("%d", msgid);
if(!bNonEdited)
m_editedIds.insert((HANDLE)msgid);
bNonEdited = true;
break;
case VKPOLL_READ_ALL_OUT:
uid = jnChild[1].as_int();
hContact = FindUser(uid);
if (hContact != 0) {
setDword(hContact, "LastMsgReadTime", time(0));
if (ServiceExists(MS_MESSAGESTATE_UPDATE)) {
MessageReadData data(time(0), MRD_TYPE_READTIME);
CallService(MS_MESSAGESTATE_UPDATE, hContact, (LPARAM)&data);
}
else
SetSrmmReadStatus(hContact);
if (m_vkOptions.bUserForceInvisibleOnActivity)
SetInvisible(hContact);
}
break;
case VKPOLL_READ_ALL_IN:
uid = jnChild[1].as_int();
hContact = FindUser(uid);
if (hContact != 0 && m_vkOptions.bSyncReadMessageStatusFromServer)
MarkDialogAsRead(hContact);
break;
case VKPOLL_USR_ONLINE:
uid = -jnChild[1].as_int();
if ((hContact = FindUser(uid)) != 0) {
setWord(hContact, "Status", ID_STATUS_ONLINE);
platform = jnChild[2].as_int();
SetMirVer(hContact, platform);
}
break;
case VKPOLL_USR_OFFLINE:
uid = -jnChild[1].as_int();
if ((hContact = FindUser(uid)) != 0) {
setWord(hContact, "Status", ID_STATUS_OFFLINE);
db_unset(hContact, m_szModuleName, "ListeningTo");
SetMirVer(hContact, -1);
}
break;
case VKPOLL_USR_UTN:
uid = jnChild[1].as_int();
hContact = FindUser(uid);
if (hContact != 0) {
ForkThread(&CVkProto::ContactTypingThread, (void *)hContact);
if (m_vkOptions.bUserForceInvisibleOnActivity)
SetInvisible(hContact);
}
break;
case VKPOLL_CHAT_UTN:
ForkThread(&CVkProto::ChatContactTypingThread, new CVKChatContactTypingParam(jnChild[2].as_int(), jnChild[1].as_int()));
break;
case VKPOLL_CHAT_CHANGED:
int chat_id = jnChild[1].as_int();
CVkChatInfo *cc = m_chats.find((CVkChatInfo*)&chat_id);
if (cc)
RetrieveChatInfo(cc);
break;
}
}
RetrieveMessagesByIds(mids);
}
int CVkProto::PollServer()
{
debugLogA("CVkProto::PollServer");
if (!IsOnline()) {
debugLogA("CVkProto::PollServer is dead (not online)");
m_pollingConn = nullptr;
ShutdownSession();
return 0;
}
debugLogA("CVkProto::PollServer (online)");
int iPollConnRetry = MAX_RETRIES;
NETLIBHTTPREQUEST *reply;
CMStringA szReqUrl(FORMAT, "https://%s?act=a_check&key=%s&ts=%s&wait=25&access_token=%s&mode=%d&version=%d", m_pollingServer, m_pollingKey, m_pollingTs, m_szAccessToken, 106, 2);
// see mode parametr description on https://vk.com/dev/using_longpoll (Russian version)
NETLIBHTTPREQUEST req = {};
req.cbSize = sizeof(req);
req.requestType = REQUEST_GET;
req.szUrl = szReqUrl.GetBuffer();
req.flags = VK_NODUMPHEADERS | NLHRF_PERSISTENT | NLHRF_HTTP11 | NLHRF_SSL;
req.timeout = 30000;
req.nlc = m_pollingConn;
while ((reply = Netlib_HttpTransaction(m_hNetlibUser, &req)) == nullptr) {
debugLogA("CVkProto::PollServer is dead");
m_pollingConn = nullptr;
if (iPollConnRetry && !m_bTerminated) {
iPollConnRetry--;
debugLogA("CVkProto::PollServer restarting %d", MAX_RETRIES - iPollConnRetry);
Sleep(1000);
}
else {
debugLogA("CVkProto::PollServer => ShutdownSession");
ShutdownSession();
return 0;
}
}
int retVal = 0;
if (reply->resultCode == 200) {
JSONNode jnRoot = JSONNode::parse(reply->pData);
const JSONNode &jnFailed = jnRoot["failed"];
if (jnFailed && jnFailed.as_int() > 1) {
RetrievePollingInfo();
retVal = -1;
debugLogA("Polling key expired, restarting polling thread");
}
else if (CheckJsonResult(nullptr, jnRoot)) {
char ts[32];
itoa(jnRoot["ts"].as_int(), ts, 10);
m_pollingTs = mir_strdup(ts);
const JSONNode &jnUpdates = jnRoot["updates"];
if (jnUpdates)
PollUpdates(jnUpdates);
retVal = 1;
}
}
else if ((reply->resultCode >= 400 && reply->resultCode <= 417)
|| (reply->resultCode >= 500 && reply->resultCode <= 509)) {
debugLogA("CVkProto::PollServer is dead. Error code - %d", reply->resultCode);
m_pollingConn = nullptr;
Netlib_FreeHttpRequest(reply);
ShutdownSession();
return 0;
}
m_pollingConn = reply->nlc;
Netlib_FreeHttpRequest(reply);
debugLogA("CVkProto::PollServer return %d", retVal);
return retVal;
}
void CVkProto::PollingThread(void*)
{
debugLogA("CVkProto::PollingThread: entering");
while (!m_bTerminated)
if (PollServer() == -1 || !m_hPollingThread)
break;
m_pollingConn = nullptr;
debugLogA("CVkProto::PollingThread: leaving");
if (m_hPollingThread) {
CloseHandle(m_hPollingThread);
m_hPollingThread = nullptr;
}
}