summaryrefslogtreecommitdiff
path: root/plugins/StatusManager/src/keepstatus.cpp
diff options
context:
space:
mode:
authorGeorge Hazan <ghazan@miranda.im>2018-06-19 12:43:27 +0300
committerGeorge Hazan <ghazan@miranda.im>2018-06-19 12:43:27 +0300
commit0fb39531240a0a23e92c0456fe72e26027740696 (patch)
tree8b85028b680b6951f8afa369297adfc93baef79a /plugins/StatusManager/src/keepstatus.cpp
parentaef4307d04fb8d8bbc05921d45d97a63e627fb4d (diff)
StatusManager:
- project structure linearized; - AAA flag "Use same settings wasn't saved across Miranda starts; - perversion with declaring static arrays in commonstatus.h removed
Diffstat (limited to 'plugins/StatusManager/src/keepstatus.cpp')
-rw-r--r--plugins/StatusManager/src/keepstatus.cpp1102
1 files changed, 1102 insertions, 0 deletions
diff --git a/plugins/StatusManager/src/keepstatus.cpp b/plugins/StatusManager/src/keepstatus.cpp
new file mode 100644
index 0000000000..cbb52e8777
--- /dev/null
+++ b/plugins/StatusManager/src/keepstatus.cpp
@@ -0,0 +1,1102 @@
+/*
+ KeepStatus Plugin for Miranda-IM (www.miranda-im.org)
+ Copyright 2003-2006 P. Boon
+
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "stdafx.h"
+
+int KSLangPack;
+
+static HANDLE hConnectionEvent = nullptr;
+static HANDLE hServices[4], hEvents[2];
+
+static mir_cs GenTimerCS, GenStatusCS, CheckContinueslyCS;
+
+static HANDLE hProtoAckHook = nullptr;
+static HANDLE hStatusChangeHook = nullptr;
+static HANDLE hCSStatusChangeHook = nullptr;
+static HANDLE hCSStatusChangeExHook = nullptr;
+
+static HWND hMessageWindow = nullptr;
+
+static UINT_PTR checkConnectionTimerId = 0;
+static UINT_PTR afterCheckTimerId = 0;
+static UINT_PTR processAckTimerId = 0;
+static UINT_PTR checkContinTimerId = 0;
+static UINT_PTR checkConnectingTimerId = 0;
+static int retryCount = 0;
+static BOOL bLastPingResult = TRUE;
+// variables (options)
+static int maxRetries = 0;
+static int initDelay = 0;
+static int currentDelay = 0;
+static int maxDelay = 0;
+static int ackDelay = 500;
+static int increaseExponential = 0;
+static int showConnectionPopups = 0;
+// prototypes
+static int StartTimer(int timer, int timeout, BOOL restart);
+static int StopTimer(int timer);
+static int ProcessProtoAck(WPARAM wParam, LPARAM lParam);
+static VOID CALLBACK CheckConnectingTimer(HWND hwnd, UINT message, UINT_PTR idEvent, DWORD dwTime);
+static VOID CALLBACK CheckAckStatusTimer(HWND hwnd, UINT message, UINT_PTR idEvent, DWORD dwTime);
+static int StatusChange(WPARAM wParam, LPARAM lParam);
+static int CSStatusChange(WPARAM wParam, LPARAM lParam);
+static int CSStatusChangeEx(WPARAM wParam, LPARAM lParam);
+static VOID CALLBACK CheckConnectionTimer(HWND hwnd, UINT message, UINT_PTR idEvent, DWORD dwTime);
+static int StopChecking();
+static VOID CALLBACK AfterCheckTimer(HWND hwnd, UINT message, UINT_PTR idEvent, DWORD dwTime);
+static VOID CALLBACK CheckContinueslyTimer(HWND hwnd, UINT message, UINT_PTR idEvent, DWORD dwTime);
+INT_PTR IsProtocolEnabledService(WPARAM wParam, LPARAM lParam);
+
+static int ProcessPopup(int reason, LPARAM lParam);
+LRESULT CALLBACK KSPopupDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
+static DWORD CALLBACK MessageWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
+
+// options.c
+extern int KeepStatusOptionsInit(WPARAM wparam, LPARAM);
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void KSUnloadOptions()
+{
+ UnhookEvent(hProtoAckHook);
+ UnhookEvent(hStatusChangeHook);
+ UnhookEvent(hCSStatusChangeHook);
+ UnhookEvent(hCSStatusChangeExHook);
+ hProtoAckHook = hStatusChangeHook = hCSStatusChangeHook = hCSStatusChangeExHook = nullptr;
+
+ if (IsWindow(hMessageWindow))
+ DestroyWindow(hMessageWindow);
+
+ if (StartTimer(IDT_CHECKCONTIN, -1, FALSE))
+ WSACleanup();
+
+ StopTimer(IDT_CHECKCONN | IDT_PROCESSACK | IDT_AFTERCHECK | IDT_CHECKCONTIN | IDT_CHECKCONNECTING);
+}
+
+int KSLoadOptions()
+{
+ KSUnloadOptions();
+
+ if (db_get_b(0, KSMODULENAME, SETTING_CHECKCONNECTION, FALSE)) {
+ if (db_get_b(0, KSMODULENAME, SETTING_CONTCHECK, FALSE)) {
+ if (db_get_b(0, KSMODULENAME, SETTING_BYPING, FALSE)) {
+ WSADATA wsaData;
+ WSAStartup(MAKEWORD(2, 2), &wsaData);
+ }
+ StartTimer(IDT_CHECKCONTIN, 0, FALSE);
+ }
+ increaseExponential = db_get_b(0, KSMODULENAME, SETTING_INCREASEEXPONENTIAL, FALSE);
+ currentDelay = initDelay = 1000 * db_get_dw(0, KSMODULENAME, SETTING_INITDELAY, DEFAULT_INITDELAY);
+ maxDelay = 1000 * db_get_dw(0, KSMODULENAME, SETTING_MAXDELAY, DEFAULT_MAXDELAY);
+ maxRetries = db_get_b(0, KSMODULENAME, SETTING_MAXRETRIES, 0);
+ if (maxRetries == 0)
+ maxRetries = -1;
+ hProtoAckHook = HookEvent(ME_PROTO_ACK, ProcessProtoAck);
+ hStatusChangeHook = HookEvent(ME_CLIST_STATUSMODECHANGE, StatusChange);
+ if (ServiceExists(ME_CS_STATUSCHANGE))
+ hCSStatusChangeHook = HookEvent(ME_CS_STATUSCHANGE, CSStatusChange);
+ hCSStatusChangeExHook = HookEvent(ME_CS_STATUSCHANGEEX, CSStatusChangeEx);
+ if (db_get_b(0, KSMODULENAME, SETTING_CHECKAPMRESUME, 0)) {
+ if (!IsWindow(hMessageWindow)) {
+ hMessageWindow = CreateWindowEx(0, L"STATIC", nullptr, 0, 0, 0, 0, 0, nullptr, nullptr, nullptr, nullptr);
+ SetWindowLongPtr(hMessageWindow, GWLP_WNDPROC, (LONG_PTR)MessageWndProc);
+ }
+ }
+ retryCount = 0;
+ }
+
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+int SMProto::AssignStatus(int iStatus, int iLastStatus, wchar_t *pwszMsg)
+{
+ if (iStatus < MIN_STATUS || iStatus > MAX_STATUS)
+ return -1;
+ if (iStatus != ID_STATUS_OFFLINE && m_status == ID_STATUS_DISABLED)
+ return -2;
+ if (!db_get_b(0, KSMODULENAME, SETTING_NOLOCKED, 0) && Proto_GetAccount(m_szName)->IsLocked())
+ return -3;
+
+ mir_cslock lck(GenStatusCS);
+
+ char dbSetting[128];
+ mir_snprintf(dbSetting, "%s_enabled", m_szName);
+ m_lastStatus = (iLastStatus == 0) ? m_status : iLastStatus;
+ if (!db_get_b(0, KSMODULENAME, dbSetting, 1))
+ m_status = ID_STATUS_DISABLED;
+ else if (iStatus == ID_STATUS_LAST)
+ m_status = m_lastStatus;
+ else
+ m_status = iStatus;
+
+ log_infoA("KeepStatus: assigning status %d (%d, %d) to %s", m_status, m_lastStatus, iLastStatus, m_szName);
+
+ if (pwszMsg != nullptr && mir_wstrcmp(pwszMsg, m_szMsg)) {
+ if (m_szMsg != nullptr)
+ mir_free(m_szMsg);
+
+ m_szMsg = mir_wstrdup(pwszMsg);
+ }
+ else if (pwszMsg != m_szMsg) {
+ if (m_szMsg != nullptr)
+ mir_free(m_szMsg);
+
+ m_szMsg = nullptr;
+ }
+ return 0;
+}
+
+int SMProto::GetStatus() const
+{
+ switch (m_status) {
+ case ID_STATUS_CURRENT:
+ return Proto_GetStatus(m_szName);
+ case ID_STATUS_LAST:
+ return m_lastStatus;
+ default:
+ return m_status;
+ }
+}
+
+static int SetCurrentStatus()
+{
+ TProtoSettings ps(protoList);
+ for (auto &p : ps) {
+ int realStatus = Proto_GetStatus(p->m_szName);
+ int curStatus = p->GetStatus();
+ if (curStatus == ID_STATUS_DISABLED)
+ continue;
+ if (curStatus == realStatus) {
+ p->m_status = ID_STATUS_DISABLED;
+ continue;
+ }
+
+ log_infoA("KeepStatus: status for %s differs: stored = %d, real = %d", p->m_szName, curStatus, realStatus);
+
+ // force offline before reconnecting?
+ if (realStatus != ID_STATUS_OFFLINE && db_get_b(0, KSMODULENAME, SETTING_FIRSTOFFLINE, FALSE)) {
+ log_infoA("KeepStatus: Setting %s offline before making a new connection attempt", p->m_szName);
+ CallProtoService(p->m_szName, PS_SETSTATUS, (WPARAM)ID_STATUS_OFFLINE, 0);
+ }
+ }
+ ProcessPopup(KS_CONN_STATE_RETRY, (LPARAM)ps.getArray());
+ return SetStatusEx(ps);
+}
+
+static int StatusChange(WPARAM wParam, LPARAM lParam)
+{
+ char *szProto = (char *)lParam;
+ if (szProto == nullptr) { // global status change
+ for (auto &it : protoList)
+ it->AssignStatus(wParam, 0, it->m_szMsg);
+ }
+ else {
+ for (auto &it : protoList)
+ if (!mir_strcmp(it->m_szName, szProto))
+ it->AssignStatus(wParam, 0, it->m_szMsg);
+ }
+
+ return 0;
+}
+
+static int CSStatusChange(WPARAM wParam, LPARAM)
+{
+ // the status was changed by commonstatus (old)
+ if (wParam != 0) {
+ PROTOCOLSETTING **protoSettings = *(PROTOCOLSETTING***)wParam;
+ if (protoSettings == nullptr)
+ return -1;
+
+ for (int i = 0; i < protoList.getCount(); i++) {
+ auto psi = protoSettings[i];
+ if (psi->szName == nullptr)
+ continue;
+
+ for (auto &it : protoList) {
+ if (it->m_szName == nullptr)
+ continue;
+
+ if (!mir_strcmp(psi->szName, it->m_szName))
+ it->AssignStatus(psi->status, psi->lastStatus, it->m_szMsg);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int CSStatusChangeEx(WPARAM wParam, LPARAM pCount)
+{
+ // the status was changed by commonstatus (new)
+ if (wParam != 0) {
+ PROTOCOLSETTINGEX** protoSettings = *(PROTOCOLSETTINGEX***)wParam;
+ if (protoSettings == nullptr)
+ return -1;
+
+ for (int i = 0; i < pCount; i++) {
+ auto psi = protoSettings[i];
+ if (psi->m_szName == nullptr)
+ continue;
+
+ for (auto &it : protoList) {
+ if (it->m_szName == nullptr)
+ continue;
+
+ if (!mir_strcmp(psi->m_szName, it->m_szName))
+ it->AssignStatus(psi->m_status, psi->m_lastStatus, psi->m_szMsg);
+ }
+ }
+ }
+
+ return 0;
+}
+
+struct TimerInfo
+{
+ int timer;
+ int timeout;
+ BOOL restart;
+};
+
+static INT_PTR CALLBACK StartTimerApcProc(void *param)
+{
+ TimerInfo *ti = (TimerInfo *)param;
+ int res = 0;
+
+ mir_cslock lck(GenTimerCS);
+ log_debugA("StartTimer: %d, %d, %d", ti->timer, ti->timeout, ti->restart);
+ log_debugA("ack: %u, chk: %u, aft: %u, cnt: %u, con: %u", processAckTimerId, checkConnectionTimerId, afterCheckTimerId, checkContinTimerId, checkConnectingTimerId);
+ if (ti->timer & IDT_PROCESSACK) {
+ res = (processAckTimerId == 0) ? 0 : 1;
+ if (((processAckTimerId == 0) && (checkConnectionTimerId == 0)) || (ti->restart)) {
+ if (ti->timeout != -1) {
+ if (ti->restart)
+ KillTimer(nullptr, processAckTimerId);
+ if (ti->timeout == 0)
+ ti->timeout = ackDelay;
+ processAckTimerId = SetTimer(nullptr, 0, ti->timeout, CheckAckStatusTimer);
+ }
+ }
+ }
+
+ if (ti->timer & IDT_CHECKCONN) {
+ res = (checkConnectionTimerId == 0 ? 0 : 1) || res;
+ if ((checkConnectionTimerId == 0) || (ti->restart)) {
+ if (ti->timeout != -1) {
+ if (ti->restart)
+ KillTimer(nullptr, checkConnectionTimerId);
+
+ if (ti->timeout == 0)
+ ti->timeout = initDelay;
+ checkConnectionTimerId = SetTimer(nullptr, 0, ti->timeout, CheckConnectionTimer);
+ }
+ }
+ }
+
+ if (ti->timer & IDT_AFTERCHECK) {
+ res = (afterCheckTimerId == 0 ? 0 : 1) || res;
+ if ((afterCheckTimerId == 0) || (ti->restart)) {
+ if (ti->timeout != -1) {
+ if (ti->restart)
+ KillTimer(nullptr, afterCheckTimerId);
+
+ if (ti->timeout == 0)
+ ti->timeout = initDelay / 2;
+ afterCheckTimerId = SetTimer(nullptr, 0, ti->timeout, AfterCheckTimer);
+ }
+ }
+ }
+
+ if (ti->timer & IDT_CHECKCONTIN) {
+ res = (checkContinTimerId == 0 ? 0 : 1) || res;
+ if ((checkContinTimerId == 0) || (ti->restart)) {
+ if (ti->timeout != -1) {
+ if (ti->restart)
+ KillTimer(nullptr, checkContinTimerId);
+
+ if (ti->timeout == 0)
+ ti->timeout = 1000 * db_get_dw(0, KSMODULENAME, SETTING_CNTDELAY, CHECKCONTIN_DELAY);
+ checkContinTimerId = SetTimer(nullptr, 0, ti->timeout, CheckContinueslyTimer);
+ }
+ }
+ }
+
+ if (ti->timer & IDT_CHECKCONNECTING) {
+ res = (checkConnectingTimerId == 0 ? 0 : 1) || res;
+ if ((checkConnectingTimerId == 0) || (ti->restart)) {
+ if (ti->timeout != -1) {
+ if (ti->restart)
+ KillTimer(nullptr, checkConnectingTimerId);
+ if (ti->timeout == 0)
+ ti->timeout = initDelay / 2;
+ checkConnectingTimerId = SetTimer(nullptr, 0, ti->timeout, CheckConnectingTimer);
+ }
+ }
+ }
+
+ log_debugA("ack: %u, chk: %u, aft: %u, cnt: %u, con: %u", processAckTimerId, checkConnectionTimerId, afterCheckTimerId, checkContinTimerId, checkConnectingTimerId);
+ log_debugA("StartTimer done %d", res);
+ return res;
+}
+
+static int StartTimer(int timer, int timeout, BOOL restart)
+{
+ TimerInfo ti = { timer, timeout, restart };
+ return CallFunctionSync(&StartTimerApcProc, &ti);
+}
+
+static int StopTimer(int timer)
+{
+ int res = 0;
+
+ mir_cslock lck(GenTimerCS);
+ log_debugA("StopTimer %d", timer);
+ log_debugA("ack: %u, chk: %u, aft: %u, cnt: %u, con: %u", processAckTimerId, checkConnectionTimerId, afterCheckTimerId, checkContinTimerId, checkConnectingTimerId);
+
+ if (timer & IDT_PROCESSACK) {
+ if (processAckTimerId == 0)
+ res = 0;
+ else {
+ KillTimer(nullptr, processAckTimerId);
+ processAckTimerId = 0;
+ res = 1;
+ }
+ }
+
+ if (timer & IDT_CHECKCONN) {
+ if (checkConnectionTimerId == 0)
+ res = 0 || res;
+ else {
+ KillTimer(nullptr, checkConnectionTimerId);
+ checkConnectionTimerId = 0;
+ res = 1;
+ }
+ }
+
+ if (timer & IDT_AFTERCHECK) {
+ if (afterCheckTimerId == 0)
+ res = 0 || res;
+ else {
+ KillTimer(nullptr, afterCheckTimerId);
+ afterCheckTimerId = 0;
+ res = 1;
+ }
+ }
+
+ if (timer & IDT_CHECKCONTIN) {
+ if (checkContinTimerId == 0)
+ res = 0 || res;
+ else {
+ KillTimer(nullptr, checkContinTimerId);
+ checkContinTimerId = 0;
+ res = 1;
+ }
+ }
+
+ if (timer & IDT_CHECKCONNECTING) {
+ if (checkConnectingTimerId == 0)
+ res = 0 || res;
+ else {
+ KillTimer(nullptr, checkConnectingTimerId);
+ checkConnectingTimerId = 0;
+ res = 1;
+ }
+ }
+
+ log_debugA("ack: %u, chk: %u, aft: %u, cnt: %u, con: %u", processAckTimerId, checkConnectionTimerId, afterCheckTimerId, checkContinTimerId, checkConnectingTimerId);
+ log_debugA("StopTimer done %d", res);
+ return res;
+}
+
+static int ProcessProtoAck(WPARAM, LPARAM lParam)
+{
+ ACKDATA *ack = (ACKDATA*)lParam;
+ if (ack->type != ACKTYPE_STATUS && ack->type != ACKTYPE_LOGIN)
+ return 0;
+
+ char dbSetting[128];
+ mir_snprintf(dbSetting, "%s_enabled", ack->szModule);
+ if (!db_get_b(0, KSMODULENAME, dbSetting, 1))
+ return 0;
+
+ if (ack->type == ACKTYPE_STATUS && ack->result == ACKRESULT_SUCCESS) {
+ for (auto &it : protoList)
+ if (!mir_strcmp(it->m_szName, ack->szModule))
+ it->lastStatusAckTime = GetTickCount();
+
+ StartTimer(IDT_PROCESSACK, 0, FALSE);
+ return 0;
+ }
+
+ if (ack->type == ACKTYPE_LOGIN) {
+ if (ack->lParam == LOGINERR_OTHERLOCATION) {
+ for (auto &it : protoList) {
+ if (!mir_strcmp(ack->szModule, it->m_szName)) {
+ it->AssignStatus(ID_STATUS_OFFLINE);
+ if (db_get_b(0, KSMODULENAME, SETTING_CNCOTHERLOC, 0)) {
+ StopTimer(IDT_PROCESSACK);
+ for (auto &jt : protoList)
+ jt->AssignStatus(ID_STATUS_OFFLINE);
+ }
+
+ NotifyEventHooks(hConnectionEvent, (WPARAM)KS_CONN_STATE_OTHERLOCATION, (LPARAM)it->m_szName);
+ ProcessPopup(KS_CONN_STATE_OTHERLOCATION, (LPARAM)ack->szModule);
+ }
+ }
+ }
+ else if (ack->result == ACKRESULT_FAILED) {
+ // login failed
+ NotifyEventHooks(hConnectionEvent, (WPARAM)KS_CONN_STATE_LOGINERROR, (LPARAM)ack->szModule);
+
+ switch (db_get_b(0, KSMODULENAME, SETTING_LOGINERR, LOGINERR_NOTHING)) {
+ case LOGINERR_CANCEL:
+ log_infoA("KeepStatus: cancel on login error (%s)", ack->szModule);
+ for (auto &it : protoList)
+ if (!mir_strcmp(ack->szModule, it->m_szName))
+ it->AssignStatus(ID_STATUS_OFFLINE);
+
+ ProcessPopup(KS_CONN_STATE_LOGINERROR, (LPARAM)ack->szModule);
+ StopChecking();
+ break;
+
+ case LOGINERR_SETDELAY:
+ {
+ int newDelay = 1000 * db_get_dw(0, KSMODULENAME, SETTING_LOGINERR_DELAY, DEFAULT_MAXDELAY);
+ log_infoA("KeepStatus: set delay to %d ms on login error (%s)", newDelay, ack->szModule);
+ StartTimer(IDT_CHECKCONN, newDelay, TRUE);
+ }
+ ProcessPopup(KS_CONN_STATE_LOGINERROR, (LPARAM)ack->szModule);
+ break;
+
+ default:
+ case LOGINERR_NOTHING:
+ StartTimer(IDT_PROCESSACK, 0, FALSE);
+ break;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static VOID CALLBACK CheckConnectingTimer(HWND, UINT, UINT_PTR, DWORD)
+{
+ StopTimer(IDT_CHECKCONNECTING);
+
+ for (auto &it : protoList) {
+ int curStatus = it->GetStatus();
+ if (IsStatusConnecting(curStatus)) { // connecting
+ int maxConnectingTime = db_get_dw(0, KSMODULENAME, SETTING_MAXCONNECTINGTIME, 0);
+ if (maxConnectingTime > 0) {
+ if ((unsigned int)maxConnectingTime <= ((GetTickCount() - it->lastStatusAckTime) / 1000)) {
+ // set offline
+ log_infoA("KeepStatus: %s is too long connecting; setting offline", it->m_szName);
+ CallProtoService(it->m_szName, PS_SETSTATUS, (WPARAM)ID_STATUS_OFFLINE, 0);
+ }
+ }
+ }
+ }
+}
+
+static VOID CALLBACK CheckAckStatusTimer(HWND, UINT, UINT_PTR, DWORD)
+{
+ bool needChecking = false;
+
+ StopTimer(IDT_PROCESSACK);
+ for (auto &it : protoList) {
+ int curStatus = it->GetStatus();
+ int newStatus = Proto_GetStatus(it->m_szName);
+ // ok, np
+ if (curStatus == ID_STATUS_CURRENT || curStatus == ID_STATUS_DISABLED || curStatus == newStatus || newStatus > MAX_STATUS)
+ continue;
+
+ if (IsStatusConnecting(newStatus)) { // connecting
+ int maxConnectingTime = db_get_dw(0, KSMODULENAME, SETTING_MAXCONNECTINGTIME, 0);
+ if (maxConnectingTime > 0)
+ StartTimer(IDT_CHECKCONNECTING, (maxConnectingTime * 1000 - (GetTickCount() - it->lastStatusAckTime)), FALSE);
+ }
+ // keepstatus' administration was wrong!
+ else if (newStatus != ID_STATUS_OFFLINE)
+ it->AssignStatus(newStatus);
+
+ // connection lost
+ else if (newStatus == ID_STATUS_OFFLINE) {// start checking connection
+ if (!StartTimer(IDT_CHECKCONN, -1, FALSE)) { /* check if not already checking */
+ needChecking = true;
+ log_infoA("KeepStatus: connection lost! (%s)", it->m_szName);
+ NotifyEventHooks(hConnectionEvent, (WPARAM)KS_CONN_STATE_LOST, (LPARAM)it->m_szName);
+ ProcessPopup(KS_CONN_STATE_LOST, (LPARAM)it->m_szName);
+ }
+ }
+ }
+
+ if (needChecking)
+ StartTimer(IDT_CHECKCONN, initDelay, FALSE);
+}
+
+static VOID CALLBACK CheckConnectionTimer(HWND, UINT, UINT_PTR, DWORD)
+{
+ log_debugA("CheckConnectionTimer");
+ bool setStatus = false;
+
+ for (auto &it : protoList) {
+ int realStatus = Proto_GetStatus(it->m_szName);
+ int shouldBeStatus = it->GetStatus();
+ if (shouldBeStatus == ID_STATUS_LAST)
+ shouldBeStatus = it->m_lastStatus;
+ if (shouldBeStatus == ID_STATUS_DISABLED)
+ continue;
+ if ((shouldBeStatus != realStatus) && (realStatus == ID_STATUS_OFFLINE) || (realStatus < MIN_STATUS)) {
+ setStatus = true;
+ break;
+ }
+ }
+
+ // one of the status was wrong
+ if (setStatus && (maxRetries == -1 || retryCount < maxRetries)) {
+ if (increaseExponential)
+ currentDelay = min(2 * currentDelay, maxDelay);
+
+ if (((db_get_b(0, KSMODULENAME, SETTING_CHKINET, 0)) && (!InternetGetConnectedState(nullptr, 0))) || ((db_get_b(0, KSMODULENAME, SETTING_BYPING, FALSE)) && (!bLastPingResult))) {
+ // no network
+ NotifyEventHooks(hConnectionEvent, (WPARAM)KS_CONN_STATE_RETRYNOCONN, (LPARAM)retryCount + 1);
+ ProcessPopup(KS_CONN_STATE_RETRYNOCONN, 0);
+ }
+ else {
+ NotifyEventHooks(hConnectionEvent, (WPARAM)KS_CONN_STATE_RETRY, (LPARAM)retryCount + 1);
+ SetCurrentStatus(); // set the status
+ }
+ retryCount += 1;
+ StartTimer(IDT_AFTERCHECK, min(currentDelay, AFTERCHECK_DELAY) / 2, FALSE);
+ StartTimer(IDT_CHECKCONN, currentDelay, TRUE); // restart this timer
+ }
+ else // all status set ok already, or stop checking
+ StopChecking();
+
+ log_debugA("CheckConnectionTimer done");
+}
+
+static int StopChecking()
+{
+ StopTimer(IDT_CHECKCONN | IDT_PROCESSACK | IDT_AFTERCHECK | IDT_CHECKCONNECTING);
+
+ BOOL isOk = TRUE;
+ for (auto &it : protoList) {
+ int curStatus = it->GetStatus();
+ int newStatus = Proto_GetStatus(it->m_szName);
+ if (newStatus != curStatus) {
+ it->AssignStatus(newStatus);
+ isOk = FALSE;
+ break;
+ }
+ }
+
+ NotifyEventHooks(hConnectionEvent, (WPARAM)KS_CONN_STATE_STOPPEDCHECKING, (LPARAM)isOk);
+ ProcessPopup(KS_CONN_STATE_STOPPEDCHECKING, (LPARAM)isOk);
+ log_infoA("KeepStatus: stop checking (%s)", isOk ? "success" : "failure");
+ retryCount = 0;
+ currentDelay = initDelay;
+
+ return 0;
+}
+
+static VOID CALLBACK AfterCheckTimer(HWND, UINT, UINT_PTR, DWORD)
+{
+ // after each connection check, this function is called to see if connection was recovered
+ StopTimer(IDT_AFTERCHECK);
+
+ bool setStatus = false;
+
+ for (auto &it : protoList) {
+ int realStatus = Proto_GetStatus(it->m_szName);
+ int shouldBeStatus = it->GetStatus();
+ if (shouldBeStatus == ID_STATUS_LAST) // this should never happen
+ shouldBeStatus = it->m_lastStatus;
+ if (shouldBeStatus == ID_STATUS_DISABLED) // (on ignoring proto)
+ continue;
+ if ((shouldBeStatus != realStatus) && (realStatus == ID_STATUS_OFFLINE) || (realStatus < MIN_STATUS))
+ setStatus = true;
+ }
+
+ if (!setStatus || retryCount == maxRetries)
+ StopChecking();
+}
+
+static void CheckContinuouslyFunction(void *)
+{
+ Thread_SetName("KeepStatus: CheckContinuouslyFunction");
+
+ static int pingFailures = 0;
+
+ // one at the time is enough, do it the 'easy' way
+ mir_cslock lck(CheckContinueslyCS);
+
+ // do a ping, even if reconnecting
+ bool doPing = false;
+ for (auto &it : protoList) {
+ int shouldBeStatus = it->GetStatus();
+ if (shouldBeStatus == ID_STATUS_LAST)
+ shouldBeStatus = it->m_lastStatus;
+
+ if (shouldBeStatus == ID_STATUS_DISABLED)
+ continue;
+
+ if (shouldBeStatus != ID_STATUS_OFFLINE) {
+ log_debugA("CheckContinuouslyFunction: %s should be %d", it->m_szName, shouldBeStatus);
+ doPing = true;
+ }
+ }
+
+ if (!doPing) {
+ log_debugA("CheckContinuouslyFunction: All protocols should be offline, no need to check connection");
+ return;
+ }
+
+ BOOL ping = db_get_b(0, KSMODULENAME, SETTING_BYPING, FALSE);
+ if (ping) {
+ DBVARIANT dbv;
+ if (db_get(0, KSMODULENAME, SETTING_PINGHOST, &dbv))
+ ping = FALSE;
+ else {
+ char *start, *end;
+ char host[MAX_PATH];
+ DWORD *addr;
+ struct hostent *hostent;
+ char reply[sizeof(ICMP_ECHO_REPLY) + 8];
+
+ bLastPingResult = FALSE;
+ HANDLE hICMPFile = (HANDLE)IcmpCreateFile();
+ if (hICMPFile == INVALID_HANDLE_VALUE) {
+ bLastPingResult = TRUE;
+ log_infoA("KeepStatus: icmp.dll error (2)");
+ }
+ if (bLastPingResult == FALSE) {
+ start = dbv.pszVal;
+ while ((*start != '\0') && (!bLastPingResult)) {
+ end = start;
+ while ((*end != ' ') && (*end != '\0'))
+ end++;
+ memset(host, '\0', sizeof(host));
+ strncpy(host, start, end - start);
+ hostent = gethostbyname(host);
+ if (hostent != nullptr) {
+ addr = (DWORD *)(*hostent->h_addr_list);
+ bLastPingResult = (IcmpSendEcho(hICMPFile, *addr, nullptr, 0, nullptr, reply, sizeof(ICMP_ECHO_REPLY) + 8, 5000) != 0);
+
+ if (bLastPingResult)
+ pingFailures = 0;
+ else
+ pingFailures++;
+
+ log_debugA("CheckContinuouslyFunction: pinging %s (result: %d/%d)", host, bLastPingResult, pingFailures);
+ }
+ else log_debugA("CheckContinuouslyFunction: unable to resolve %s", host);
+
+ start = end;
+ while (*start == ' ')
+ start++;
+ }
+ }
+ IcmpCloseHandle(hICMPFile);
+ }
+ db_free(&dbv);
+ }
+
+ if (StartTimer(IDT_CHECKCONN, -1, FALSE)) // already connecting, leave
+ return;
+
+ if (((!ping) && (!InternetGetConnectedState(nullptr, 0))) || ((ping) && (!bLastPingResult) && (pingFailures >= db_get_w(0, KSMODULENAME, SETTING_PINGCOUNT, DEFAULT_PINGCOUNT)))) {
+ pingFailures = 0;
+
+ for (auto &pa : Accounts()) {
+ if (!IsSuitableProto(pa))
+ continue;
+
+ if (IsStatusConnecting(pa->iRealStatus)) {
+ log_debugA("CheckContinuouslyFunction: %s is connecting", pa->szModuleName);
+ continue; // connecting, leave alone
+ }
+ if (IsProtocolEnabledService(0, (LPARAM)pa->szModuleName)) {
+ log_debugA("CheckContinuouslyFunction: forcing %s offline", pa->szModuleName);
+ CallProtoService(pa->szModuleName, PS_SETSTATUS, (WPARAM)ID_STATUS_OFFLINE, 0);
+ }
+ }
+
+ if (StartTimer(IDT_CHECKCONN | IDT_PROCESSACK, -1, FALSE)) {// are our 'set offlines' noticed?
+ log_debugA("CheckContinuouslyFunction: currently checking");
+ return;
+ }
+
+ log_infoA("KeepStatus: connection lost! (continuesly check)");
+ NotifyEventHooks(hConnectionEvent, (WPARAM)KS_CONN_STATE_LOST, 0);
+ ProcessPopup(KS_CONN_STATE_LOST, 0);
+ maxRetries = db_get_b(0, KSMODULENAME, SETTING_MAXRETRIES, 0);
+ if (maxRetries == 0)
+ maxRetries = -1;
+ StartTimer(IDT_CHECKCONN, initDelay, FALSE);
+ }
+}
+
+static VOID CALLBACK CheckContinueslyTimer(HWND, UINT, UINT_PTR, DWORD)
+{
+ if (db_get_b(0, KSMODULENAME, SETTING_BYPING, FALSE))
+ mir_forkthread(CheckContinuouslyFunction);
+ else
+ CheckContinuouslyFunction(nullptr);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// popup
+
+static INT_PTR ShowPopup(const wchar_t *msg, HICON hIcon)
+{
+ POPUPDATAT ppd = {};
+ ppd.lchIcon = hIcon;
+ wcsncpy(ppd.lptzContactName, TranslateT("Keep status"), MAX_CONTACTNAME);
+ wcsncpy(ppd.lptzText, msg, MAX_SECONDLINE);
+ if (db_get_b(0, KSMODULENAME, SETTING_POPUP_USEWINCOLORS, 0)) {
+ ppd.colorBack = GetSysColor(COLOR_BTNFACE);
+ ppd.colorText = GetSysColor(COLOR_WINDOWTEXT);
+ }
+ else if (!db_get_b(0, KSMODULENAME, SETTING_POPUP_USEDEFCOLORS, 0)) {
+ ppd.colorBack = db_get_dw(0, KSMODULENAME, SETTING_POPUP_BACKCOLOR, 0xAAAAAA);
+ ppd.colorText = db_get_dw(0, KSMODULENAME, SETTING_POPUP_TEXTCOLOR, 0x0000CC);
+ }
+ ppd.PluginWindowProc = KSPopupDlgProc;
+
+ switch (db_get_b(0, KSMODULENAME, SETTING_POPUP_DELAYTYPE, POPUP_DELAYFROMPU)) {
+ case POPUP_DELAYCUSTOM:
+ ppd.iSeconds = (int)db_get_dw(0, KSMODULENAME, SETTING_POPUP_TIMEOUT, 0);
+ if (ppd.iSeconds == 0)
+ ppd.iSeconds = currentDelay / 1000 - 1;
+ break;
+
+ case POPUP_DELAYPERMANENT:
+ ppd.iSeconds = -1;
+ break;
+
+ case POPUP_DELAYFROMPU:
+ default:
+ ppd.iSeconds = 0;
+ break;
+ }
+ return PUAddPopupT(&ppd);
+}
+
+static wchar_t* GetHumanName(LPARAM lParam)
+{
+ PROTOACCOUNT *ProtoAccount = Proto_GetAccount((char*)lParam);
+ return (ProtoAccount != nullptr) ? ProtoAccount->tszAccountName : TranslateT("Protocol");
+}
+
+static int ProcessPopup(int reason, LPARAM lParam)
+{
+ if (!db_get_b(0, KSMODULENAME, SETTING_SHOWCONNECTIONPOPUPS, FALSE) || !ServiceExists(MS_POPUP_ADDPOPUPT))
+ return -1;
+
+ HICON hIcon = nullptr;
+ CMStringW wszText;
+
+ switch (reason) {
+ case KS_CONN_STATE_OTHERLOCATION: // lParam = 1 proto
+ if (!db_get_b(0, KSMODULENAME, SETTING_PUOTHER, TRUE))
+ return -1;
+
+ hIcon = Skin_LoadProtoIcon((char*)lParam, SKINICON_STATUS_OFFLINE);
+ wszText.Format(TranslateT("%s connected from another location"), GetHumanName(lParam));
+ break;
+
+ case KS_CONN_STATE_LOGINERROR: // lParam = 1 proto
+ if (!db_get_b(0, KSMODULENAME, SETTING_PUOTHER, TRUE))
+ return -1;
+
+ hIcon = Skin_LoadProtoIcon((char*)lParam, SKINICON_STATUS_OFFLINE);
+ if (db_get_b(0, KSMODULENAME, SETTING_LOGINERR, LOGINERR_NOTHING) == LOGINERR_CANCEL)
+ wszText.Format(TranslateT("%s login error, cancel reconnecting"), GetHumanName(lParam));
+ else if (db_get_b(0, KSMODULENAME, SETTING_LOGINERR, LOGINERR_NOTHING) == LOGINERR_SETDELAY)
+ wszText.Format(TranslateT("%s login error (next retry (%d) in %d s)"), GetHumanName(lParam), retryCount + 1, db_get_dw(0, KSMODULENAME, SETTING_LOGINERR_DELAY, DEFAULT_MAXDELAY));
+ else
+ return -1;
+ break;
+
+ case KS_CONN_STATE_LOST: // lParam = 1 proto, or nullptr
+ if (!db_get_b(0, KSMODULENAME, SETTING_PUCONNLOST, TRUE))
+ return -1;
+
+ if (lParam) { // указатель на имя модуля.
+ hIcon = Skin_LoadProtoIcon((char*)lParam, SKINICON_STATUS_OFFLINE);
+ wszText.Format(TranslateT("%s status error (next retry (%d) in %d s)"), GetHumanName(lParam), retryCount + 1, currentDelay / 1000);
+ }
+ else wszText.Format(TranslateT("Status error (next retry (%d) in %d s)"), retryCount + 1, currentDelay / 1000);
+ break;
+
+ case KS_CONN_STATE_RETRY: // lParam = PROTOCOLSETTINGEX**
+ if (!db_get_b(0, KSMODULENAME, SETTING_PUCONNRETRY, TRUE))
+ return -1;
+
+ if (lParam) {
+ PROTOCOLSETTINGEX **ps = (PROTOCOLSETTINGEX **)lParam;
+
+ if (retryCount == maxRetries - 1)
+ wszText.Format(TranslateT("Resetting status... (last try (%d))"), retryCount + 1);
+ else
+ wszText.Format(TranslateT("Resetting status... (next retry (%d) in %d s)"), retryCount + 2, currentDelay / 1000);
+ wszText.Append(L"\r\n");
+ for (int i = 0; i < protoList.getCount(); i++) {
+ PROTOCOLSETTINGEX *p = ps[i];
+ if (p->m_status == ID_STATUS_DISABLED)
+ continue;
+
+ if (mir_wstrlen(p->m_tszAccName) > 0)
+ if (db_get_b(0, KSMODULENAME, SETTING_PUSHOWEXTRA, TRUE))
+ wszText.AppendFormat(TranslateT("%s\t(will be set to %s)\r\n"), p->m_tszAccName, Clist_GetStatusModeDescription(p->m_status, 0));
+ }
+
+ hIcon = Skin_LoadProtoIcon(ps[0]->m_szName, SKINICON_STATUS_OFFLINE);
+ wszText.TrimRight();
+ }
+ break;
+
+ case KS_CONN_STATE_RETRYNOCONN: // lParam = nullptr
+ if (!db_get_b(0, KSMODULENAME, SETTING_PUOTHER, TRUE))
+ return -1;
+
+ if (retryCount == maxRetries - 1)
+ wszText.Format(TranslateT("No internet connection seems available... (last try (%d))"), retryCount + 1);
+ else
+ wszText.Format(TranslateT("No internet connection seems available... (next retry (%d) in %d s)"), retryCount + 2, currentDelay / 1000);
+ break;
+
+ case KS_CONN_STATE_STOPPEDCHECKING: // lParam == BOOL succes
+ if (!db_get_b(0, KSMODULENAME, SETTING_PURESULT, TRUE))
+ return -1;
+
+ if (lParam) {
+ hIcon = Skin_LoadIcon(SKINICON_STATUS_ONLINE);
+ wszText.Format(TranslateT("Status was set ok"));
+ }
+ else wszText.Format(TranslateT("Giving up"));
+ break;
+ }
+ if (hIcon == nullptr)
+ hIcon = Skin_LoadIcon(SKINICON_STATUS_OFFLINE);
+
+ log_info(L"KeepStatus: %s", wszText.c_str());
+ return ShowPopup(wszText, hIcon);
+}
+
+LRESULT CALLBACK KSPopupDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
+{
+ switch (message) {
+ case WM_CONTEXTMENU: // right
+ case WM_COMMAND: // left
+ switch (db_get_b(0, KSMODULENAME,
+ (message == WM_COMMAND) ? SETTING_POPUP_LEFTCLICK : SETTING_POPUP_RIGHTCLICK,
+ POPUP_ACT_CLOSEPOPUP)) {
+ case POPUP_ACT_CANCEL:
+ // cancel timer
+ StopChecking();
+ PUDeletePopup(hWnd);
+ break;
+
+ case POPUP_ACT_CLOSEPOPUP:
+ // close the popup
+ PUDeletePopup(hWnd);
+ break;
+ }
+ break;
+ }
+
+ return DefWindowProc(hWnd, message, wParam, lParam);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// services
+
+INT_PTR StopReconnectingService(WPARAM, LPARAM)
+{
+ int ret = StartTimer(IDT_CHECKCONN | IDT_AFTERCHECK, -1, FALSE);
+ StopChecking();
+ return ret;
+}
+
+INT_PTR EnableProtocolService(WPARAM wParam, LPARAM lParam)
+{
+ char *szProto = (char *)lParam;
+ if (szProto == nullptr)
+ return -1;
+
+ char dbSetting[128];
+ mir_snprintf(dbSetting, "%s_enabled", szProto);
+ if (!db_get_b(0, KSMODULENAME, dbSetting, 1)) // 'hard' disabled
+ return -1;
+
+ int ret = -2;
+ for (auto &it : protoList) {
+ if (!mir_strcmp(szProto, it->m_szName)) {
+ if (wParam)
+ it->AssignStatus(Proto_GetStatus(it->m_szName));
+ else
+ it->AssignStatus(ID_STATUS_DISABLED);
+
+ ret = 0;
+ break;
+ }
+ }
+ return ret;
+}
+
+INT_PTR IsProtocolEnabledService(WPARAM, LPARAM lParam)
+{
+ char *szProto = (char *)lParam;
+
+ char dbSetting[128];
+ mir_snprintf(dbSetting, "%s_enabled", szProto);
+ if (!db_get_b(0, KSMODULENAME, dbSetting, 1))
+ return FALSE;
+
+ for (auto &it : protoList)
+ if (!mir_strcmp(szProto, it->m_szName))
+ return it->GetStatus() != ID_STATUS_DISABLED;
+
+ return FALSE;
+}
+
+INT_PTR AnnounceStatusChangeService(WPARAM, LPARAM lParam)
+{
+ PROTOCOLSETTINGEX *newSituation = (PROTOCOLSETTINGEX *)lParam;
+ log_infoA("Another plugin announced a status change to %d for %s", newSituation->m_status, newSituation->m_szName == nullptr ? "all" : newSituation->m_szName);
+
+ for (auto &it : protoList)
+ if (!mir_strcmp(it->m_szName, newSituation->m_szName))
+ it->AssignStatus(newSituation->m_status, newSituation->m_lastStatus, newSituation->m_szMsg);
+
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// window for suspend
+
+static DWORD CALLBACK MessageWndProc(HWND, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ static TProtoSettings *ps = nullptr;
+
+ switch (msg) {
+ case WM_POWERBROADCAST:
+ switch (wParam) {
+ case PBT_APMSUSPEND:
+ log_infoA("KeepStatus: suspend state detected: %08X %08X", wParam, lParam);
+ if (ps == nullptr) {
+ ps = new TProtoSettings(protoList);
+ for (auto &it : *ps)
+ EnableProtocolService(0, (LPARAM)it->m_szName);
+
+ // set proto's offline, the clist will not try to reconnect in that case
+ Clist_SetStatusMode(ID_STATUS_OFFLINE);
+ }
+ break;
+
+ case PBT_APMRESUMESUSPEND:
+ case PBT_APMRESUMECRITICAL:
+ // case PBT_APMRESUMEAUTOMATIC: ?
+ log_infoA("KeepStatus: resume from suspend state");
+ if (ps != nullptr) {
+ for (auto &it : *ps) {
+ SMProto *p = protoList.find(it);
+ if (p)
+ p->AssignStatus(it->m_status, it->m_lastStatus, it->m_szMsg);
+ }
+ delete ps;
+ ps = nullptr;
+ }
+ StartTimer(IDT_PROCESSACK, 0, FALSE);
+ break;
+ }
+ break;
+
+ case WM_DESTROY:
+ if (ps != nullptr) {
+ delete ps;
+ ps = nullptr;
+ }
+ break;
+ }
+
+ return TRUE;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// init stuff
+
+static int onShutdown(WPARAM, LPARAM)
+{
+ KSUnloadOptions();
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+int KSModuleLoaded(WPARAM, LPARAM)
+{
+ hMessageWindow = nullptr;
+ KSLoadOptions();
+
+ hEvents[0] = HookEvent(ME_OPT_INITIALISE, KeepStatusOptionsInit);
+ hEvents[1] = HookEvent(ME_SYSTEM_PRESHUTDOWN, onShutdown);
+ return 0;
+}
+
+void KeepStatusLoad()
+{
+ KSLangPack = GetPluginLangId(MIID_LAST, 0);
+
+ if (g_bMirandaLoaded)
+ KSModuleLoaded(0, 0);
+ else
+ HookEvent(ME_SYSTEM_MODULESLOADED, KSModuleLoaded);
+
+ hConnectionEvent = CreateHookableEvent(ME_KS_CONNECTIONEVENT);
+
+ hServices[0] = CreateServiceFunction(MS_KS_STOPRECONNECTING, StopReconnectingService);
+ hServices[1] = CreateServiceFunction(MS_KS_ENABLEPROTOCOL, EnableProtocolService);
+ hServices[2] = CreateServiceFunction(MS_KS_ISPROTOCOLENABLED, IsProtocolEnabledService);
+ hServices[3] = CreateServiceFunction(MS_KS_ANNOUNCESTATUSCHANGE, AnnounceStatusChangeService);
+}
+
+void KeepStatusUnload()
+{
+ if (g_bMirandaLoaded)
+ onShutdown(0, 0);
+
+ KillModuleOptions(KSLangPack);
+
+ for (auto &it : hServices) {
+ DestroyServiceFunction(it);
+ it = nullptr;
+ }
+
+ for (auto &it : hEvents) {
+ UnhookEvent(it);
+ it = nullptr;
+ }
+
+ DestroyHookableEvent(hConnectionEvent); hConnectionEvent = nullptr;
+}