summaryrefslogtreecommitdiff
path: root/protocols/Discord/src
diff options
context:
space:
mode:
authorGeorge Hazan <ghazan@miranda.im>2016-12-28 16:57:55 +0300
committerGeorge Hazan <ghazan@miranda.im>2016-12-28 16:58:16 +0300
commite7a87b3c82204e048f23750a18b456e567a6f436 (patch)
treefaf10111046c42cd4fcffb23d2e749effa10e540 /protocols/Discord/src
parent4efdd531337b74d819a40c87c454a0fa1f6ca838 (diff)
http framework
Diffstat (limited to 'protocols/Discord/src')
-rw-r--r--protocols/Discord/src/connection.cpp142
-rw-r--r--protocols/Discord/src/http.cpp113
-rw-r--r--protocols/Discord/src/proto.cpp30
-rw-r--r--protocols/Discord/src/proto.h85
-rw-r--r--protocols/Discord/src/server.cpp33
5 files changed, 401 insertions, 2 deletions
diff --git a/protocols/Discord/src/connection.cpp b/protocols/Discord/src/connection.cpp
index a7e3babd41..13a12129e2 100644
--- a/protocols/Discord/src/connection.cpp
+++ b/protocols/Discord/src/connection.cpp
@@ -17,7 +17,149 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "stdafx.h"
+void CDiscordProto::ExecuteRequest(AsyncHttpRequest *pReq)
+{
+ CMStringA str;
+
+ pReq->szUrl = pReq->m_szUrl.GetBuffer();
+ if (!pReq->m_szParam.IsEmpty()) {
+ if (pReq->requestType == REQUEST_GET) {
+ str.Format("%s?%s", pReq->m_szUrl, pReq->m_szParam);
+ pReq->szUrl = str.GetBuffer();
+ }
+ else {
+ pReq->pData = mir_strdup(pReq->m_szParam);
+ pReq->dataLength = pReq->m_szParam.GetLength();
+ }
+ }
+
+ pReq->flags |= NLHRF_PERSISTENT;
+ pReq->nlc = m_hAPIConnection;
+
+ debugLogA("Executing request #%d:\n%s", pReq->m_iReqNum, pReq->szUrl);
+ NETLIBHTTPREQUEST *reply = (NETLIBHTTPREQUEST*)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)m_hNetlibUser, (LPARAM)pReq);
+ if (reply != NULL) {
+ if (pReq->m_pCallback != NULL)
+ (this->*(pReq->m_pCallback))(reply, pReq);
+
+ m_hAPIConnection = reply->nlc;
+
+ CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)reply);
+ }
+ else {
+ debugLogA("Request %d failed", pReq->m_iReqNum);
+
+ if (IsStatusConnecting(m_iStatus))
+ ConnectionFailed(LOGINERR_NONETWORK);
+ else
+ ShutdownSession();
+ m_hAPIConnection = NULL;
+ }
+ delete pReq;
+}
+
+void CDiscordProto::OnLoggedIn()
+{
+ debugLogA("CDiscordProto::OnLoggedIn");
+ m_bOnline = true;
+ SetServerStatus(m_iDesiredStatus);
+}
+
+void CDiscordProto::OnLoggedOut()
+{
+ debugLogA("CDiscordProto::OnLoggedOut");
+ m_bOnline = false;
+ m_hWorkerThread = NULL;
+
+ if (m_hAPIConnection)
+ Netlib_CloseHandle(m_hAPIConnection);
+
+ ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)m_iStatus, ID_STATUS_OFFLINE);
+ m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE;
+}
+
+void CDiscordProto::ShutdownSession()
+{
+ debugLogA("CDiscordProto::ShutdownSession");
+ m_bTerminated = true;
+ if (m_hWorkerThread)
+ SetEvent(m_evRequestsQueue);
+ OnLoggedOut();
+}
+
+void CDiscordProto::ConnectionFailed(int iReason)
+{
+ debugLogA("CDiscordProto::ConnectionFailed -> reason %d", iReason);
+ delSetting("AccessToken");
+
+ ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, iReason);
+ ShutdownSession();
+}
+
+bool CDiscordProto::TryToConnect(void)
+{
+ ptrW wszLogin(getWStringA(DB_KEY_EMAIL)), wszPassword(getWStringA(DB_KEY_PASSWORD));
+ if (wszLogin == NULL) {
+ ConnectionFailed(LOGINERR_BADUSERID);
+ return false;
+ }
+ if (wszPassword == NULL) {
+ ConnectionFailed(LOGINERR_WRONGPASSWORD);
+ return false;
+ }
+
+ Push(new AsyncHttpRequest(this, REQUEST_GET, "/user/login", &CDiscordProto::OnReceiveToken)
+ << WCHAR_PARAM("email", wszLogin)
+ << WCHAR_PARAM("password", wszPassword));
+ return true;
+}
+
void CDiscordProto::ServerThread(void*)
{
+ m_szAccessToken = getStringA("AccessToken");
+ m_hAPIConnection = NULL;
+ m_bTerminated = false;
+
+ debugLogA("CDiscordProto::WorkerThread: %s", "entering");
+
+ if (m_szAccessToken != NULL)
+ // try to receive a response from server
+ RetrieveMyInfo();
+ else if (!TryToConnect())
+ return;
+
+ while (true) {
+ WaitForSingleObject(m_evRequestsQueue, 1000);
+ if (m_bTerminated)
+ break;
+
+ AsyncHttpRequest *pReq;
+ bool need_sleep = false;
+ while (true) {
+ {
+ mir_cslock lck(m_csHttpQueue);
+ if (m_arHttpQueue.getCount() == 0)
+ break;
+
+ pReq = m_arHttpQueue[0];
+ m_arHttpQueue.remove(0);
+ need_sleep = (m_arHttpQueue.getCount() > 1);
+ }
+ if (m_bTerminated)
+ break;
+ ExecuteRequest(pReq);
+ if (need_sleep) {
+ Sleep(330);
+ debugLogA("CDiscordProto::WorkerThread: %s", "need to sleep");
+ }
+ }
+ }
+
+ m_hWorkerThread = NULL;
+ if (m_hAPIConnection) {
+ Netlib_CloseHandle(m_hAPIConnection);
+ m_hAPIConnection = NULL;
+ }
+ debugLogA("CDiscordProto::WorkerThread: %s", "leaving");
}
diff --git a/protocols/Discord/src/http.cpp b/protocols/Discord/src/http.cpp
new file mode 100644
index 0000000000..5333d87a65
--- /dev/null
+++ b/protocols/Discord/src/http.cpp
@@ -0,0 +1,113 @@
+/*
+Copyright © 2016 Miranda NG team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "stdafx.h"
+
+AsyncHttpRequest* CDiscordProto::Push(AsyncHttpRequest *pReq, int iTimeout)
+{
+ pReq->timeout = iTimeout;
+ {
+ mir_cslock lck(m_csHttpQueue);
+ m_arHttpQueue.insert(pReq);
+ }
+ SetEvent(m_evRequestsQueue);
+ return pReq;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static LONG g_reqNum = 0;
+
+AsyncHttpRequest::AsyncHttpRequest()
+{
+ cbSize = sizeof(NETLIBHTTPREQUEST);
+ AddHeader("Connection", "keep-alive");
+ pUserInfo = NULL;
+ m_iErrorCode = 0;
+ m_pCallback = NULL;
+ m_iReqNum = ::InterlockedIncrement(&g_reqNum);
+}
+
+AsyncHttpRequest::AsyncHttpRequest(CDiscordProto *ppro, int iRequestType, LPCSTR _url, HttpCallback pFunc)
+{
+ cbSize = sizeof(NETLIBHTTPREQUEST);
+ AddHeader("Connection", "keep-alive");
+
+ if (*_url == '/') { // relative url leads to a site
+ m_szUrl = "https://discardapp.com";
+ m_szUrl += _url;
+ }
+ else m_szUrl = _url;
+
+ flags = NLHRF_DUMPASTEXT | NLHRF_HTTP11 | NLHRF_REDIRECT | NLHRF_SSL;
+ if (ppro->m_szAccessToken != NULL)
+ this << CHAR_PARAM("access_token", ppro->m_szAccessToken);
+
+ requestType = iRequestType;
+ m_pCallback = pFunc;
+ pUserInfo = NULL;
+ m_iErrorCode = 0;
+ m_iReqNum = ::InterlockedIncrement(&g_reqNum);
+}
+
+AsyncHttpRequest::~AsyncHttpRequest()
+{
+ for (int i = 0; i < headersCount; i++) {
+ mir_free(headers[i].szName);
+ mir_free(headers[i].szValue);
+ }
+ mir_free(headers);
+ mir_free(pData);
+}
+
+void AsyncHttpRequest::AddHeader(LPCSTR szName, LPCSTR szValue)
+{
+ headers = (NETLIBHTTPHEADER*)mir_realloc(headers, sizeof(NETLIBHTTPHEADER)*(headersCount + 1));
+ headers[headersCount].szName = mir_strdup(szName);
+ headers[headersCount].szValue = mir_strdup(szValue);
+ headersCount++;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+AsyncHttpRequest* operator<<(AsyncHttpRequest *pReq, const INT_PARAM &param)
+{
+ CMStringA &s = pReq->m_szParam;
+ if (!s.IsEmpty())
+ s.AppendChar('&');
+ s.AppendFormat("%s=%i", param.szName, param.iValue);
+ return pReq;
+}
+
+AsyncHttpRequest* operator<<(AsyncHttpRequest *pReq, const CHAR_PARAM &param)
+{
+ CMStringA &s = pReq->m_szParam;
+ if (!s.IsEmpty())
+ s.AppendChar('&');
+ s.AppendFormat("%s=%s", param.szName, ptrA(mir_urlEncode(param.szValue)));
+ return pReq;
+}
+
+AsyncHttpRequest* operator<<(AsyncHttpRequest *pReq, const WCHAR_PARAM &param)
+{
+ T2Utf szValue(param.wszValue);
+ CMStringA &s = pReq->m_szParam;
+ if (!s.IsEmpty())
+ s.AppendChar('&');
+ s.AppendFormat("%s=%s", param.szName, ptrA(mir_urlEncode(szValue)));
+ return pReq;
+}
diff --git a/protocols/Discord/src/proto.cpp b/protocols/Discord/src/proto.cpp
index 1c557daaa7..c44cab39bc 100644
--- a/protocols/Discord/src/proto.cpp
+++ b/protocols/Discord/src/proto.cpp
@@ -17,8 +17,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "stdafx.h"
+static int compareRequests(const AsyncHttpRequest *p1, const AsyncHttpRequest *p2)
+{
+ return p1->m_iReqNum - p2->m_iReqNum;
+}
+
CDiscordProto::CDiscordProto(const char *proto_name, const wchar_t *username) :
- PROTO<CDiscordProto>(proto_name, username)
+ PROTO<CDiscordProto>(proto_name, username),
+ m_arHttpQueue(10, compareRequests),
+ m_evRequestsQueue(CreateEvent(NULL, FALSE, FALSE, NULL))
{
// Services
CreateProtoService(PS_GETNAME, &CDiscordProto::GetName);
@@ -26,10 +33,25 @@ CDiscordProto::CDiscordProto(const char *proto_name, const wchar_t *username) :
// Events
HookProtoEvent(ME_OPT_INITIALISE, &CDiscordProto::OnOptionsInit);
+
+ // Network initialization
+ CMStringW descr(FORMAT, TranslateT("%s server connection"), m_tszUserName);
+
+ NETLIBUSER nlu = { sizeof(nlu) };
+ nlu.flags = NUF_INCOMING | NUF_OUTGOING | NUF_HTTPCONNS | NUF_UNICODE;
+ nlu.szSettingsModule = m_szModuleName;
+ nlu.ptszDescriptiveName = descr.GetBuffer();
+ m_hNetlibUser = (HANDLE)CallService(MS_NETLIB_REGISTERUSER, 0, (LPARAM)&nlu);
}
CDiscordProto::~CDiscordProto()
{
+ debugLogA("CDiscordProto::~CDiscordProto");
+ Netlib_CloseHandle(m_hNetlibUser);
+ m_hNetlibUser = NULL;
+
+ m_arHttpQueue.destroy();
+ ::CloseHandle(m_evRequestsQueue);
}
DWORD_PTR CDiscordProto::GetCaps(int type, MCONTACT)
@@ -84,7 +106,7 @@ int CDiscordProto::SetStatus(int iNewStatus)
m_iStatus = ID_STATUS_CONNECTING;
ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)iOldStatus, m_iStatus);
- ForkThread(&CDiscordProto::ServerThread, this);
+ m_hWorkerThread = ForkThreadEx(&CDiscordProto::ServerThread, NULL, NULL);
}
else if (iNewStatus == ID_STATUS_OFFLINE) {
m_iStatus = m_iDesiredStatus;
@@ -105,6 +127,10 @@ int CDiscordProto::OnModulesLoaded(WPARAM, LPARAM)
int CDiscordProto::OnPreShutdown(WPARAM, LPARAM)
{
+ debugLogA("CDiscordProto::OnPreShutdown");
+
+ m_bTerminated = true;
+ SetEvent(m_evRequestsQueue);
return 0;
}
diff --git a/protocols/Discord/src/proto.h b/protocols/Discord/src/proto.h
index 26466bcfae..5a381e52f9 100644
--- a/protocols/Discord/src/proto.h
+++ b/protocols/Discord/src/proto.h
@@ -1,9 +1,85 @@
+class CDiscordProto;
+typedef void (CDiscordProto::*HttpCallback)(NETLIBHTTPREQUEST*, struct AsyncHttpRequest*);
+
+struct AsyncHttpRequest : public NETLIBHTTPREQUEST, public MZeroedObject
+{
+ AsyncHttpRequest();
+ AsyncHttpRequest(CDiscordProto*, int iRequestType, LPCSTR szUrl, HttpCallback pFunc);
+ ~AsyncHttpRequest();
+
+ void AddHeader(LPCSTR, LPCSTR);
+
+ CMStringA m_szUrl;
+ CMStringA m_szParam;
+ HttpCallback m_pCallback;
+ int m_iErrorCode, m_iReqNum;
+ void *pUserInfo;
+};
+
+struct PARAM
+{
+ LPCSTR szName;
+ __forceinline PARAM(LPCSTR _name) : szName(_name)
+ {}
+};
+
+struct INT_PARAM : public PARAM
+{
+ int iValue;
+ __forceinline INT_PARAM(LPCSTR _name, int _value) :
+ PARAM(_name), iValue(_value)
+ {}
+};
+AsyncHttpRequest* operator<<(AsyncHttpRequest*, const INT_PARAM&);
+
+struct CHAR_PARAM : public PARAM
+{
+ LPCSTR szValue;
+ __forceinline CHAR_PARAM(LPCSTR _name, LPCSTR _value) :
+ PARAM(_name), szValue(_value)
+ {}
+};
+AsyncHttpRequest* operator<<(AsyncHttpRequest*, const CHAR_PARAM&);
+
+struct WCHAR_PARAM : public PARAM
+{
+ LPCWSTR wszValue;
+ __forceinline WCHAR_PARAM(LPCSTR _name, LPCWSTR _value) :
+ PARAM(_name), wszValue(_value)
+ {}
+};
+AsyncHttpRequest* operator<<(AsyncHttpRequest*, const WCHAR_PARAM&);
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
class CDiscordProto : public PROTO<CDiscordProto>
{
+ friend struct AsyncHttpRequest;
+
void __cdecl ServerThread(void*);
void SetAllContactStatuses(int iStatus);
+ bool TryToConnect(void);
+ void ConnectionFailed(int iReason);
+ void ShutdownSession(void);
+
+ ptrA m_szAccessToken;
+
+ mir_cs m_csHttpQueue;
+ HANDLE m_evRequestsQueue;
+ LIST<AsyncHttpRequest> m_arHttpQueue;
+
+ void ExecuteRequest(AsyncHttpRequest *pReq);
+ AsyncHttpRequest* Push(AsyncHttpRequest *pReq, int iTimeout = 10000);
+
+ HANDLE
+ m_hAPIConnection, // working connection
+ m_hWorkerThread; // worker thread handle
+
+ bool
+ m_bOnline, // protocol is online
+ m_bTerminated; // Miranda's going down
public:
CDiscordProto(const char*,const wchar_t*);
@@ -24,4 +100,13 @@ public:
int __cdecl OnModulesLoaded(WPARAM, LPARAM);
int __cdecl OnPreShutdown(WPARAM, LPARAM);
int __cdecl OnOptionsInit(WPARAM, LPARAM);
+
+ void OnLoggedIn();
+ void OnLoggedOut();
+
+ void OnReceiveToken(NETLIBHTTPREQUEST*, AsyncHttpRequest*);
+
+ // Misc
+ void RetrieveMyInfo(void);
+ void SetServerStatus(int iStatus);
};
diff --git a/protocols/Discord/src/server.cpp b/protocols/Discord/src/server.cpp
new file mode 100644
index 0000000000..4b7c2afa13
--- /dev/null
+++ b/protocols/Discord/src/server.cpp
@@ -0,0 +1,33 @@
+/*
+Copyright © 2016 Miranda NG team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "stdafx.h"
+
+void CDiscordProto::RetrieveMyInfo()
+{
+
+}
+
+void CDiscordProto::SetServerStatus(int iStatus)
+{
+
+}
+
+void CDiscordProto::OnReceiveToken(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq)
+{
+
+}