diff options
author | George Hazan <ghazan@miranda.im> | 2018-06-19 12:43:27 +0300 |
---|---|---|
committer | George Hazan <ghazan@miranda.im> | 2018-06-19 12:43:27 +0300 |
commit | 0fb39531240a0a23e92c0456fe72e26027740696 (patch) | |
tree | 8b85028b680b6951f8afa369297adfc93baef79a /plugins/StatusManager/src/keepstatus.cpp | |
parent | aef4307d04fb8d8bbc05921d45d97a63e627fb4d (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.cpp | 1102 |
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; +} |