summaryrefslogtreecommitdiff
path: root/plugins/!NotAdopted/HistoryStats/statistic.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/!NotAdopted/HistoryStats/statistic.cpp')
-rw-r--r--plugins/!NotAdopted/HistoryStats/statistic.cpp1733
1 files changed, 1733 insertions, 0 deletions
diff --git a/plugins/!NotAdopted/HistoryStats/statistic.cpp b/plugins/!NotAdopted/HistoryStats/statistic.cpp
new file mode 100644
index 0000000000..1fae408667
--- /dev/null
+++ b/plugins/!NotAdopted/HistoryStats/statistic.cpp
@@ -0,0 +1,1733 @@
+#include "_globals.h"
+#include "statistic.h"
+
+#include <algorithm>
+
+#include "utils.h"
+#include "utf8buffer.h"
+#include "resource.h"
+#include "column.h"
+#include "main.h"
+#include "mirandahistory.h"
+#include "mirandacontact.h"
+
+/*
+ * Statistic
+ */
+
+bool Statistic::m_bRunning = false;
+
+void Statistic::prepareColumns()
+{
+ m_ActiveCols.clear();
+ m_AcquireCols.clear();
+ m_TransformCols.clear();
+
+ std::map<ext::string, int> dataGUIDToSlot;
+
+ bool bOutputPNG = m_Settings.isPNGOutputActiveAndAvailable();
+
+ upto_each_(i, m_Settings.countCol())
+ {
+ Column* pCol = m_Settings.getCol(i);
+
+ if (pCol->isEnabled())
+ {
+ int restrictions = pCol->configGetRestrictions(NULL);
+
+ // MEMO: checks for columns having no HTML-only support
+ if (!bOutputPNG && !(restrictions & Column::crHTMLMask))
+ {
+ continue;
+ }
+
+ m_ActiveCols.push_back(pCol);
+
+ pCol->setHelpers(this, &m_Settings, &m_CharMapper);
+
+ if (pCol->getFeatures() & Column::cfAcquiresData)
+ {
+ ext::string dataGUID = pCol->contactDataGetUID();
+
+ std::map<ext::string, int>::iterator g2s = dataGUIDToSlot.find(dataGUID);
+
+ if (g2s == dataGUIDToSlot.end())
+ {
+ dataGUIDToSlot[dataGUID] = m_nNextSlot;
+ m_nNextSlot++;
+
+ m_AcquireCols.push_back(pCol);
+ }
+
+ pCol->contactDataSlotAssign(dataGUIDToSlot[dataGUID]);
+
+ if (pCol->getFeatures() & Column::cfTransformsData)
+ {
+ m_TransformCols.push_back(pCol);
+
+ pCol->contactDataTransformSlotAssign(m_nNextSlot++);
+ }
+ }
+ }
+ }
+}
+
+void Statistic::prepareContactData(Contact& contact)
+{
+ iter_each_(std::vector<Column*>, i, m_AcquireCols)
+ {
+ (*i)->contactDataPrepare(contact);
+ }
+}
+
+void Statistic::freeContactData(Contact& contact)
+{
+ iter_each_(std::vector<Column*>, i, m_AcquireCols)
+ {
+ (*i)->contactDataFree(contact);
+ }
+
+ iter_each_(std::vector<Column*>, i, m_TransformCols)
+ {
+ (*i)->contactDataFree(contact);
+ }
+}
+
+void Statistic::mergeContactData(Contact& contact, const Contact& include)
+{
+ iter_each_(std::vector<Column*>, i, m_AcquireCols)
+ {
+ (*i)->contactDataMerge(contact, include);
+ }
+}
+
+void Statistic::transformContactData(Contact& contact)
+{
+ iter_each_(std::vector<Column*>, i, m_TransformCols)
+ {
+ (*i)->contactDataTransform(contact);
+ }
+}
+
+Contact& Statistic::addContact(const ext::string& nick, const ext::string& protoDisplayName, const ext::string& groupName, int nSources)
+{
+ Contact* pContact = new Contact(this, m_nNextSlot, nick, protoDisplayName, groupName, 1, nSources);
+ prepareContactData(*pContact);
+
+ m_Contacts.push_back(pContact);
+
+ return *pContact;
+}
+
+const Contact& Statistic::getContact(int index) const
+{
+ assert(index >= 0 && index < m_Contacts.size());
+
+ return *m_Contacts[index];
+}
+
+DWORD Statistic::getFirstTime()
+{
+ if (!m_bHistoryTimeAvailable)
+ {
+ // put _all_ available contacts (including omitted/total) in one list
+ ContactListC l;
+
+ upto_each_(i, countContacts())
+ {
+ l.push_back(&getContact(i));
+ }
+
+ if (hasOmitted())
+ {
+ l.push_back(&getOmitted());
+ }
+
+ if (hasTotals())
+ {
+ l.push_back(&getTotals());
+ }
+
+ if (l.size() > 0)
+ {
+ DWORD nFirstTime = con::MaxDateTime, nLastTime = con::MinDateTime;
+
+ citer_each_(Statistic::ContactListC, c, l)
+ {
+ if ((*c)->isFirstLastTimeValid())
+ {
+ nFirstTime = min(nFirstTime, (*c)->getFirstTime());
+ nLastTime = max(nLastTime, (*c)->getLastTime());
+ }
+ }
+
+ if (nFirstTime == con::MaxDateTime && nLastTime == con::MinDateTime)
+ {
+ m_nFirstTime = m_nLastTime = 0;
+ }
+ else
+ {
+ m_nFirstTime = nFirstTime;
+ m_nLastTime = nLastTime;
+ }
+ }
+ else
+ {
+ m_nFirstTime = m_nLastTime = 0;
+ }
+
+ // mark data as available
+ m_bHistoryTimeAvailable = true;
+ }
+
+ return m_nFirstTime;
+}
+
+DWORD Statistic::getLastTime()
+{
+ if (!m_bHistoryTimeAvailable)
+ {
+ // trigger calculation
+ getFirstTime();
+ }
+
+ return m_nLastTime;
+}
+
+DWORD Statistic::getHistoryTime()
+{
+ if (!m_bHistoryTimeAvailable)
+ {
+ // trigger calculation
+ getFirstTime();
+ }
+
+ return m_nLastTime - m_nFirstTime;
+}
+
+ext::string Statistic::createFile(const ext::string& desiredName)
+{
+ if (!m_Settings.m_OverwriteAlways && utils::fileExists(desiredName))
+ {
+ mu_text tempBuf[MAX_PATH];
+
+ UINT nUnique = GetTempFileName(m_TempPath.c_str(), muT("his"), 0, tempBuf);
+
+ if (nUnique == 0)
+ {
+ abort();
+ }
+
+ ext::string tempName = tempBuf;
+
+ m_ConflictingFiles.push_back(std::make_pair(desiredName, tempName));
+
+ return tempName;
+ }
+
+ ext::string desiredPath = utils::extractPath(desiredName);
+
+ if (!utils::pathExists(desiredPath))
+ {
+ if (!utils::createPath(desiredPath))
+ {
+ m_ErrorText = ext::str(ext::kformat(i18n(muT("HistoryStats couldn't create a required folder (#{folder}).\r\n\r\nPlease check the output filename and additional output folder you have chosen for correctness. Additionally, please check whether the file, folder, and/or disk is writable.")))
+ % muT("#{folder}") * desiredPath);
+ }
+ }
+
+ return desiredName;
+}
+
+bool Statistic::newFile(const mu_text* fileExt, ext::string& writeFile, ext::string& finalURL)
+{
+ ++m_nLastFileNr;
+
+ finalURL = m_OutputPrefix + utils::intToString(m_nLastFileNr) + fileExt;
+ writeFile = createFile(m_OutputPath + finalURL);
+
+ // convert relative filename to relative URL
+ utils::replaceAllInPlace(finalURL, muT("\\"), muT("/"));
+
+ return true;
+}
+
+bool Statistic::newFilePNG(Canvas& canvas, ext::string& finalURL)
+{
+ Canvas::Digest digest;
+
+ if (!canvas.getDigest(digest))
+ {
+ return false;
+ }
+
+ ImageMap::const_iterator i = m_Images.find(digest);
+
+ if (i == m_Images.end())
+ {
+ ext::string writeFile;
+
+ if (newFilePNG(writeFile, finalURL))
+ {
+ canvas.writePNG(writeFile.c_str());
+ m_Images.insert(std::make_pair(digest, finalURL));
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else
+ {
+ finalURL = i->second;
+
+ return true;
+ }
+}
+
+void Statistic::handleAddMessage(Contact& contact, Message& msg)
+{
+ contact.addMessage(msg);
+
+ iter_each_(std::vector<Column*>, i, m_AcquireCols)
+ {
+ (*i)->contactDataAcquireMessage(contact, msg);
+ }
+}
+
+void Statistic::handleAddChat(Contact& contact, bool bOutgoing, DWORD localTimestampStarted, DWORD duration)
+{
+ if (duration >= m_Settings.m_ChatSessionMinDur)
+ {
+ contact.addChat(bOutgoing, localTimestampStarted, duration);
+
+ iter_each_(std::vector<Column*>, i, m_AcquireCols)
+ {
+ (*i)->contactDataAcquireChat(contact, bOutgoing, localTimestampStarted, duration);
+ }
+ }
+}
+
+BOOL CALLBACK Statistic::staticProgressProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ Statistic* pStats = reinterpret_cast<Statistic*>(GetWindowLong(hDlg, GWL_USERDATA));
+
+ switch (msg)
+ {
+ case WM_INITDIALOG:
+ mu::langpack::translateDialog(hDlg);
+ SendMessage(hDlg, WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(LoadIcon(g_hInst, MAKEINTRESOURCE(IDI_HISTORYSTATS))));
+ return TRUE;
+
+ case WM_DESTROY:
+ PostQuitMessage(0);
+ return TRUE;
+
+ case WM_COMMAND:
+ if (LOWORD(wParam) == IDOK)
+ {
+ EnableWindow(GetDlgItem(hDlg, IDCANCEL), FALSE);
+ SetEvent(pStats->m_hCancelEvent);
+ }
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+void Statistic::setProgressMax(bool bSub, int max)
+{
+ SendDlgItemMessage(m_hWndProgress, bSub ? IDC_SUBBAR : IDC_MAINBAR, PBM_SETPOS, (WPARAM) 0, (LPARAM) 0);
+ SendDlgItemMessage(m_hWndProgress, bSub ? IDC_SUBBAR : IDC_MAINBAR, PBM_SETRANGE, (WPARAM) 0, (LPARAM) MAKELPARAM(0, max));
+
+ SetDlgItemText(m_hWndProgress, bSub ? IDC_SUBPERCENT : IDC_MAINPERCENT, (max > 0) ? muT("0%") : muT(""));
+
+ if (!bSub)
+ {
+ setProgressMax(true, 0);
+ setProgressLabel(true, muT(""));
+ }
+}
+
+void Statistic::setProgressLabel(bool bSub, const ext::string& label)
+{
+ SetDlgItemText(m_hWndProgress, bSub ? IDC_SUBTEXT : IDC_MAINTEXT, label.c_str());
+
+ if (!bSub)
+ {
+ setProgressMax(true, 0);
+ setProgressLabel(true, muT(""));
+ }
+}
+
+void Statistic::stepProgress(bool bSub, int step /* = 1 */)
+{
+ int pos = SendDlgItemMessage(m_hWndProgress, bSub ? IDC_SUBBAR : IDC_MAINBAR, PBM_GETPOS, (WPARAM) 0, (LPARAM) 0);
+ int max = SendDlgItemMessage(m_hWndProgress, bSub ? IDC_SUBBAR : IDC_MAINBAR, PBM_GETRANGE, (WPARAM) FALSE, (LPARAM) NULL);
+
+ pos += step;
+
+ SendDlgItemMessage(m_hWndProgress, bSub ? IDC_SUBBAR : IDC_MAINBAR, PBM_SETPOS, (WPARAM) pos, (LPARAM) 0);
+ SetDlgItemText(m_hWndProgress, bSub ? IDC_SUBPERCENT : IDC_MAINPERCENT, utils::ratioToPercent(pos, max).c_str());
+
+ if (!bSub)
+ {
+ setProgressMax(true, 0);
+ setProgressLabel(true, muT(""));
+ }
+}
+
+bool Statistic::stepInit()
+{
+ // file management
+ mu_text tempPath[MAX_PATH];
+ int nRes = GetTempPath(MAX_PATH, tempPath);
+
+ if (nRes > 0)
+ {
+ m_TempPath.assign(tempPath, nRes);
+ }
+
+ m_OutputFile = m_Settings.getOutputFile(getTimeStarted());
+ m_OutputPath = utils::extractPath(m_OutputFile);
+ m_OutputPrefix = m_Settings.getOutputPrefix(getTimeStarted());
+
+ // init column info
+ prepareColumns();
+
+ // figure out minimum/maximum date/time to include
+ m_TimeMin = 0;
+ m_TimeMax = 0xFFFFFFFF;
+
+ if (m_Settings.m_IgnoreOld != 0)
+ {
+ m_TimeMin = getTimeStarted() - 86400 * m_Settings.m_IgnoreOld;
+ }
+
+ if (m_Settings.getIgnoreBefore() != 0)
+ {
+ if (m_Settings.m_IgnoreOld != 0)
+ {
+ m_TimeMin = max(m_TimeMin, m_Settings.getIgnoreBefore());
+ }
+ else
+ {
+ m_TimeMin = m_Settings.getIgnoreBefore();
+ }
+ }
+
+ if (m_Settings.getIgnoreAfter() != 0)
+ {
+ m_TimeMax = m_Settings.getIgnoreAfter() + 86399;
+ }
+
+ return true;
+}
+
+bool Statistic::stepReadDB()
+{
+ if (shouldTerminate())
+ {
+ return false;
+ }
+
+ iter_each_(std::vector<Column*>, i, m_AcquireCols)
+ {
+ (*i)->contactDataBeginAcquire();
+ }
+
+ // prepare some data
+ MirandaHistory history(m_Settings);
+
+ setProgressMax(true, history.getContactCount());
+
+ upto_each_(contactIndex, history.getContactCount())
+ {
+ MirandaContact& hisContact = history.getContact(contactIndex);
+
+ setProgressLabel(true, hisContact.getNick());
+
+ Contact& curContact = addContact(hisContact.getNick(), hisContact.getProtocol(), hisContact.getGroup(), hisContact.getSources().size());
+
+ // signal begin of history for this contact
+ hisContact.beginRead();
+ curContact.beginMessages();
+
+ // init data for chat detection
+ DWORD lastAddedTime = 0;
+ DWORD chatStartTime = 0;
+ bool bChatOutgoing = false;
+ Message curMsg(m_Settings.m_FilterRawRTF && RTFFilter::available(), m_Settings.m_FilterBBCodes);
+
+ // iterate through all events
+ while (hisContact.hasNext())
+ {
+ const DBEVENTINFO& dbei = hisContact.getNext();
+
+ bool bOutgoing = bool_(dbei.flags & DBEF_SENT);
+
+ // only messages, no URLs, files or anything else
+ // filter logged status messages from tabSRMM
+ if (dbei.eventType == etMessage)
+ {
+ // convert to local time (everything in this plugin is done in local time)
+ DWORD localTimestamp = utils::toLocalTime(dbei.timestamp);
+
+ if (localTimestamp >= m_TimeMin && localTimestamp <= m_TimeMax)
+ {
+ if (dbei.flags & DBEF_UTF)
+ {
+ mu_ansi* pUTF8Text = reinterpret_cast<mu_ansi*>(dbei.pBlob);
+ int nUTF8Len = utils::getUTF8Len(pUTF8Text);
+
+ curMsg.assignTextFromUTF8(pUTF8Text, nUTF8Len);
+ }
+ else
+ {
+ mu_ansi* pAnsiText = reinterpret_cast<mu_ansi*>(dbei.pBlob);
+ int nAnsiLenP1 = ext::a::strfunc::len(pAnsiText) + 1;
+
+#if defined(MU_WIDE)
+ mu_wide* pWideText = reinterpret_cast<mu_wide*>(pAnsiText + nAnsiLenP1);
+ int nWideLen = 0;
+ int nWideMaxLen = (dbei.cbBlob - nAnsiLenP1) / sizeof(mu_wide);
+
+ if (dbei.cbBlob >= nAnsiLenP1 * 3)
+ {
+ for (int i = 0; i < nWideMaxLen; ++i)
+ {
+ if (!pWideText[i])
+ {
+ nWideLen = i;
+ break;
+ }
+ }
+ }
+
+ if (nWideLen > 0 && nWideLen < nAnsiLenP1)
+ {
+ curMsg.assignText(pWideText, nWideLen);
+ }
+ else
+ {
+ curMsg.assignText(pAnsiText, nAnsiLenP1 - 1);
+ }
+#else // MU_WIDE
+ curMsg.assignText(pAnsiText, nAnsiLenP1 - 1);
+#endif // MU_WIDE
+ }
+
+ curMsg.assignInfo(bOutgoing, localTimestamp);
+
+ // handle messages
+ handleAddMessage(curContact, curMsg);
+
+ // handle chats
+ if (localTimestamp - lastAddedTime >= (DWORD) m_Settings.m_ChatSessionTimeout || lastAddedTime == 0)
+ {
+ // new chat started
+ if (chatStartTime != 0)
+ {
+ handleAddChat(curContact, bChatOutgoing, chatStartTime, lastAddedTime - chatStartTime);
+ }
+
+ chatStartTime = localTimestamp;
+ bChatOutgoing = bOutgoing;
+ }
+
+ lastAddedTime = localTimestamp;
+ }
+ }
+
+ // non-message events
+ if (dbei.eventType != etMessage)
+ {
+ curContact.addEvent(dbei.eventType, bOutgoing);
+ }
+
+ hisContact.readNext();
+ }
+
+ // post processing for chat detection
+ if (chatStartTime != 0)
+ {
+ handleAddChat(curContact, bChatOutgoing, chatStartTime, lastAddedTime - chatStartTime);
+ }
+
+ // signal end of history for this contact
+ curContact.endMessages();
+ hisContact.endRead();
+
+ stepProgress(true);
+
+ if (shouldTerminate())
+ {
+ return false;
+ }
+ }
+
+ iter_each_(std::vector<Column*>, i, m_AcquireCols)
+ {
+ (*i)->contactDataEndAcquire();
+ }
+
+ return true;
+}
+
+bool Statistic::stepRemoveContacts()
+{
+ if (!m_Settings.m_RemoveEmptyContacts && !m_Settings.m_RemoveOutChatsZero && !m_Settings.m_RemoveInChatsZero)
+ {
+ return true;
+ }
+
+ if (shouldTerminate())
+ {
+ return false;
+ }
+
+ vector_each_(i, m_Contacts)
+ {
+ bool bRemove = false;
+ Contact* pCur = m_Contacts[i];
+
+ if (!bRemove && m_Settings.m_RemoveEmptyContacts)
+ {
+ bRemove = (pCur->getTotalMessages() == 0);
+ }
+
+ if (!bRemove && m_Settings.m_RemoveOutChatsZero)
+ {
+ bRemove = (pCur->getOutChats() == 0 && (!m_Settings.m_RemoveOutBytesZero || pCur->getOutBytes() == 0));
+ }
+
+ if (!bRemove && m_Settings.m_RemoveInChatsZero)
+ {
+ bRemove = (pCur->getInChats() == 0 && (!m_Settings.m_RemoveInBytesZero || pCur->getInBytes() == 0));
+ }
+
+ if (bRemove)
+ {
+ freeContactData(*pCur);
+ delete pCur;
+
+ m_Contacts.erase(m_Contacts.begin() + i);
+ --i;
+ }
+ }
+
+ return true;
+}
+
+bool Statistic::stepSortContacts()
+{
+ if (shouldTerminate())
+ {
+ return false;
+ }
+
+ ContactCompareBase cmpLast;
+ ContactCompareStr cmpName(&cmpLast, Contact::getNick);
+
+ int cmpDepth = 3;
+
+ upto_each_(i, Settings::cNumSortLevels)
+ {
+ if (m_Settings.m_Sort[i].by == Settings::skNothing)
+ {
+ cmpDepth = i;
+ break;
+ }
+ }
+
+ ContactCompareBase** ppCmps = new ContactCompareBase*[cmpDepth];
+
+ ContactCompareBase* pCmp = NULL;
+ ContactCompareBase* pPrev = &cmpName;
+
+ for (int i = cmpDepth - 1; i >= 0; --i)
+ {
+ switch (m_Settings.m_Sort[i].by)
+ {
+ case Settings::skNick:
+ pCmp = new ContactCompareStr(pPrev, Contact::getNick);
+ break;
+
+ case Settings::skProtocol:
+ pCmp = new ContactCompareStr(pPrev, Contact::getProtocol);
+ break;
+
+ case Settings::skGroup:
+ pCmp = new ContactCompareStr(pPrev, Contact::getGroup);
+ break;
+
+ case Settings::skBytesOut:
+ pCmp = new ContactCompare<int>(pPrev, Contact::getOutBytes);
+ break;
+
+ case Settings::skBytesIn:
+ pCmp = new ContactCompare<int>(pPrev, Contact::getInBytes);
+ break;
+
+ case Settings::skBytesTotal:
+ pCmp = new ContactCompare<int>(pPrev, Contact::getTotalBytes);
+ break;
+
+ case Settings::skMessagesOut:
+ pCmp = new ContactCompare<int>(pPrev, Contact::getOutMessages);
+ break;
+
+ case Settings::skMessagesIn:
+ pCmp = new ContactCompare<int>(pPrev, Contact::getOutMessages);
+ break;
+
+ case Settings::skMessagesTotal:
+ pCmp = new ContactCompare<int>(pPrev, Contact::getTotalMessages);
+ break;
+
+ case Settings::skChatsOut:
+ pCmp = new ContactCompare<int>(pPrev, Contact::getOutChats);
+ break;
+
+ case Settings::skChatsIn:
+ pCmp = new ContactCompare<int>(pPrev, Contact::getInChats);
+ break;
+
+ case Settings::skChatsTotal:
+ pCmp = new ContactCompare<int>(pPrev, Contact::getTotalChats);
+ break;
+
+ case Settings::skChatDurationTotal:
+ pCmp = new ContactCompare<DWORD>(pPrev, Contact::getChatDurSum);
+ break;
+
+ case Settings::skTimeOfFirstMessage:
+ pCmp = new ContactCompare<DWORD>(pPrev, Contact::getFirstTime);
+ break;
+
+ case Settings::skTimeOfLastMessage:
+ pCmp = new ContactCompare<DWORD>(pPrev, Contact::getLastTime);
+ break;
+
+ case Settings::skBytesOutAvg:
+ pCmp = new ContactCompare<double>(pPrev, Contact::getOutBytesAvg);
+ break;
+
+ case Settings::skBytesInAvg:
+ pCmp = new ContactCompare<double>(pPrev, Contact::getInBytesAvg);
+ break;
+
+ case Settings::skBytesTotalAvg:
+ pCmp = new ContactCompare<double>(pPrev, Contact::getTotalBytesAvg);
+ break;
+
+ case Settings::skMessagesOutAvg:
+ pCmp = new ContactCompare<double>(pPrev, Contact::getOutMessagesAvg);
+ break;
+
+ case Settings::skMessagesInAvg:
+ pCmp = new ContactCompare<double>(pPrev, Contact::getOutMessagesAvg);
+ break;
+
+ case Settings::skMessagesTotalAvg:
+ pCmp = new ContactCompare<double>(pPrev, Contact::getTotalMessagesAvg);
+ break;
+
+ case Settings::skChatsOutAvg:
+ pCmp = new ContactCompare<double>(pPrev, Contact::getOutChatsAvg);
+ break;
+
+ case Settings::skChatsInAvg:
+ pCmp = new ContactCompare<double>(pPrev, Contact::getInChatsAvg);
+ break;
+
+ case Settings::skChatsTotalAvg:
+ pCmp = new ContactCompare<double>(pPrev, Contact::getTotalChatsAvg);
+ break;
+
+ case Settings::skChatDurationMin:
+ pCmp = new ContactCompare<int>(pPrev, Contact::getChatDurMinForSort);
+ break;
+
+ case Settings::skChatDurationAvg:
+ pCmp = new ContactCompare<int>(pPrev, Contact::getChatDurAvgForSort);
+ break;
+
+ case Settings::skChatDurationMax:
+ pCmp = new ContactCompare<int>(pPrev, Contact::getChatDurMaxForSort);
+ break;
+ }
+
+ pCmp->setDir(m_Settings.m_Sort[i].asc);
+
+ ppCmps[i] = pPrev = pCmp;
+ pCmp = NULL;
+ }
+
+ std::sort(m_Contacts.begin(), m_Contacts.end(), ContactCompareOp(ppCmps[0]));
+
+ upto_each_(i, cmpDepth)
+ {
+ delete ppCmps[i];
+ }
+
+ delete[] ppCmps;
+
+ return true;
+}
+
+bool Statistic::stepPreOmitContacts()
+{
+ if (shouldTerminate())
+ {
+ return false;
+ }
+
+ iter_each_(std::vector<Column*>, i, m_ActiveCols)
+ {
+ (*i)->columnDataBeforeOmit();
+ }
+
+ return true;
+}
+
+bool Statistic::stepOmitContacts()
+{
+ if (!m_Settings.m_OmitContacts)
+ {
+ return true;
+ }
+
+ if (shouldTerminate())
+ {
+ return false;
+ }
+
+ m_pOmitted = new Contact(this, m_nNextSlot, muT(""), muT(""), muT(""), 0, 0);
+ prepareContactData(*m_pOmitted);
+
+ // omit depending on some value
+ if (m_Settings.m_OmitByValue)
+ {
+ static const struct {
+ int type; // 0 = int, 1 = double, 2 = DWORD
+ double factor; // factor to multiply function output with
+ int (Contact::*int_fn)() const;
+ double (Contact::*double_fn)() const;
+ DWORD (Contact::*DWORD_fn)() const;
+ } valueMap[] = {
+ { 0, 1.0, Contact::getInBytes , 0 , 0 },
+ { 0, 1.0, Contact::getOutBytes , 0 , 0 },
+ { 0, 1.0, Contact::getTotalBytes , 0 , 0 },
+ { 1, 604800.0, 0 , Contact::getInBytesAvg , 0 },
+ { 1, 604800.0, 0 , Contact::getOutBytesAvg , 0 },
+ { 1, 604800.0, 0 , Contact::getTotalBytesAvg , 0 },
+ { 0, 1.0, Contact::getInMessages , 0 , 0 },
+ { 0, 1.0, Contact::getOutMessages , 0 , 0 },
+ { 0, 1.0, Contact::getTotalMessages, 0 , 0 },
+ { 1, 604800.0, 0 , Contact::getInMessagesAvg , 0 },
+ { 1, 604800.0, 0 , Contact::getOutMessagesAvg , 0 },
+ { 1, 604800.0, 0 , Contact::getTotalMessagesAvg, 0 },
+ { 0, 1.0, Contact::getInChats , 0 , 0 },
+ { 0, 1.0, Contact::getOutChats , 0 , 0 },
+ { 0, 1.0, Contact::getTotalChats , 0 , 0 },
+ { 1, 604800.0, 0 , Contact::getInChatsAvg , 0 },
+ { 1, 604800.0, 0 , Contact::getOutChatsAvg , 0 },
+ { 1, 604800.0, 0 , Contact::getTotalChatsAvg , 0 },
+ { 2, 1/3600.0, 0 , 0 , Contact::getChatDurSum },
+ };
+
+ int valueKey = m_Settings.m_OmitByValueData;
+ double fLimit = static_cast<double>(m_Settings.m_OmitByValueLimit) / valueMap[valueKey].factor;
+
+ for (int i = m_Contacts.size() - 1; i >= 0; --i)
+ {
+ Contact& cur = *m_Contacts[i];
+
+ bool bDoOmit = false;
+
+ switch (valueMap[valueKey].type)
+ {
+ case 0:
+ bDoOmit = (static_cast<double>((cur.*valueMap[valueKey].int_fn)()) < fLimit);
+ break;
+
+ case 1:
+ bDoOmit = ((cur.*valueMap[valueKey].double_fn)() < fLimit);
+ break;
+
+ case 2:
+ bDoOmit = (static_cast<double>((cur.*valueMap[valueKey].DWORD_fn)()) < fLimit);
+ break;
+ }
+
+ if (bDoOmit)
+ {
+ if (m_Settings.m_OmittedInTotals && m_Settings.m_CalcTotals ||
+ m_Settings.m_OmittedInExtraRow)
+ {
+ m_pOmitted->merge(cur);
+ mergeContactData(*m_pOmitted, cur);
+
+ m_bActuallyOmitted = true;
+ }
+
+ freeContactData(cur);
+ delete m_Contacts[i];
+
+ m_Contacts.erase(m_Contacts.begin() + i);
+ }
+
+ if (shouldTerminate())
+ {
+ return false;
+ }
+ }
+ }
+
+ // omit depending on message time
+ if (m_Settings.m_OmitByTime)
+ {
+ for (int i = m_Contacts.size() - 1; i >= 0; --i)
+ {
+ Contact& cur = *m_Contacts[i];
+
+ if (!cur.isFirstLastTimeValid() || (getTimeStarted() > cur.getLastTime() && getTimeStarted() - cur.getLastTime() > m_Settings.m_OmitByTimeDays * 86400))
+ {
+ if (m_Settings.m_OmittedInTotals && m_Settings.m_CalcTotals ||
+ m_Settings.m_OmittedInExtraRow)
+ {
+ m_pOmitted->merge(cur);
+ mergeContactData(*m_pOmitted, cur);
+
+ m_bActuallyOmitted = true;
+ }
+
+ freeContactData(cur);
+ delete m_Contacts[i];
+
+ m_Contacts.erase(m_Contacts.begin() + i);
+ }
+
+ if (shouldTerminate())
+ {
+ return false;
+ }
+ }
+ }
+
+ // omit depending on rank
+ if (m_Settings.m_OmitByRank)
+ {
+ while (m_Contacts.size() > m_Settings.m_OmitNumOnTop)
+ {
+ Contact& cur = *m_Contacts.back();
+
+ if (m_Settings.m_OmittedInTotals && m_Settings.m_CalcTotals ||
+ m_Settings.m_OmittedInExtraRow)
+ {
+ m_pOmitted->merge(cur);
+ mergeContactData(*m_pOmitted, cur);
+
+ m_bActuallyOmitted = true;
+ }
+
+ freeContactData(cur);
+ delete m_Contacts.back();
+
+ m_Contacts.pop_back();
+
+ if (shouldTerminate())
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool Statistic::stepCalcTotals()
+{
+ if (!m_Settings.m_CalcTotals)
+ {
+ return true;
+ }
+
+ if (shouldTerminate())
+ {
+ return false;
+ }
+
+ m_pTotals = new Contact(this, m_nNextSlot, muT(""), muT(""), muT(""), 0, 0);
+ prepareContactData(*m_pTotals);
+
+ setProgressMax(true, m_Contacts.size() + 1);
+
+ // normal contacts
+ vector_each_(i, m_Contacts)
+ {
+ Contact& curContact = *m_Contacts[i];
+
+ setProgressLabel(true, curContact.getNick());
+
+ m_pTotals->merge(curContact);
+ mergeContactData(*m_pTotals, curContact);
+
+ stepProgress(true);
+
+ if (shouldTerminate())
+ {
+ return false;
+ }
+ }
+
+ // omitted contacts
+ setProgressLabel(true, i18n(muT("Omitted contacts")));
+
+ if (m_Settings.m_OmitContacts && m_Settings.m_OmittedInTotals && m_bActuallyOmitted)
+ {
+ m_pTotals->merge(*m_pOmitted);
+ mergeContactData(*m_pTotals, *m_pOmitted);
+ }
+
+ stepProgress(true);
+
+ return true;
+}
+
+bool Statistic::stepPostOmitContacts()
+{
+ if (shouldTerminate())
+ {
+ return false;
+ }
+
+ iter_each_(std::vector<Column*>, i, m_ActiveCols)
+ {
+ (*i)->columnDataAfterOmit();
+ }
+
+ return true;
+}
+
+bool Statistic::stepTransformData()
+{
+ if (shouldTerminate())
+ {
+ return false;
+ }
+
+ setProgressMax(true, m_Contacts.size() + 2);
+
+ // normal contacts
+ vector_each_(i, m_Contacts)
+ {
+ Contact& curContact = *m_Contacts[i];
+
+ setProgressLabel(true, curContact.getNick());
+ transformContactData(curContact);
+ stepProgress(true);
+
+ if (shouldTerminate())
+ {
+ return false;
+ }
+ }
+
+ // omitted contacts
+ setProgressLabel(true, i18n(muT("Omitted contacts")));
+
+ if (m_bActuallyOmitted)
+ {
+ transformContactData(*m_pOmitted);
+ }
+
+ stepProgress(true);
+
+ // totals
+ setProgressLabel(true, i18n(muT("Totals")));
+
+ if (m_Settings.m_CalcTotals)
+ {
+ transformContactData(*m_pTotals);
+ }
+
+ stepProgress(true);
+
+ return true;
+}
+
+bool Statistic::stepWriteHTML()
+{
+ if (shouldTerminate())
+ {
+ return false;
+ }
+
+ bool bInterrupted = false;
+
+ int j;
+
+ /*
+ * Init output.
+ */
+
+ setProgressMax(true, countContacts() + 2);
+
+ /*
+ * Create output stream.
+ */
+
+ ext::a::ofstream ofs(utils::toA(createFile(m_OutputFile)).c_str());
+
+ if (!ofs.good())
+ {
+ m_ErrorText = ext::str(ext::kformat(i18n(muT("HistoryStats couldn't open the output file (#{file}) for write access.\r\n\r\nPlease check the output filename you have chosen for correctness. Additionally, please check whether the file, folder, and/or disk is writable.")))
+ % muT("#{file}") * m_OutputFile);
+ return false;
+ }
+
+ UTF8Buffer utf8_buf(ofs);
+ ext::ostream tos(&utf8_buf);
+
+ /*
+ * Inform active columns about beginning output.
+ */
+
+ iter_each_(std::vector<Column*>, col, m_ActiveCols)
+ {
+ (*col)->outputBegin();
+ }
+
+ /*
+ * Output HTML init sequence.
+ */
+
+ std::set<ext::string> additionalCSSSelectors;
+ std::vector<ext::string> additionalCSS;
+ Column::IDProvider idProvider;
+
+ iter_each_(std::vector<Column*>, col, m_ActiveCols)
+ {
+ Column::StyleList cssList = (*col)->outputGetAdditionalStyles(idProvider);
+
+ iter_each_(Column::StyleList, css, cssList)
+ {
+ if (additionalCSSSelectors.find(css->first) == additionalCSSSelectors.end())
+ {
+ additionalCSS.push_back(css->first + muT(" { ") + css->second + muT(" }"));
+ additionalCSSSelectors.insert(css->first);
+ }
+ }
+
+ }
+
+ additionalCSSSelectors.clear();
+
+ tos << muT("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">") << ext::endl
+ << muT("<html>") << ext::endl
+ << muT("<head>") << ext::endl
+ << muT("<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />") << ext::endl
+ << muT("<meta name=\"generator\" content=\"HistoryStats " << utils::versionToDotted(m_Settings.m_VersionCurrent) << "\" />") << ext::endl
+ << muT("<title>")
+ << ext::kformat(i18n(muT("Statistics for #{nick} - HistoryStats"))) % muT("#{nick}") * utils::htmlEscape(m_Settings.m_OwnNick)
+ << muT("</title>") << ext::endl
+ << muT("<style type=\"text/css\">") << ext::endl;
+ tos << m_Settings.getDefaultStyleSheet();
+
+ iter_each_(std::vector<ext::string>, css, additionalCSS)
+ {
+ tos << *css << ext::endl;
+ }
+
+ tos << muT("</style>") << ext::endl
+ << muT("</head>") << ext::endl
+ << muT("<body><h1>")
+ << ext::kformat(i18n(muT("Statistics for #{nick}"))) % muT("#{nick}") * utils::htmlEscape(m_Settings.m_OwnNick)
+ << muT("</h1>") << ext::endl;
+ tos << muT("<table>") << ext::endl;
+
+ additionalCSS.clear();
+
+ /*
+ * Output header.
+ */
+
+ SIZE headerSize = { 0, 1 };
+
+ iter_each_(std::vector<Column*>, col, m_ActiveCols)
+ {
+ SIZE colSize = (*col)->outputMeasureHeader();
+
+ headerSize.cx += colSize.cx;
+ headerSize.cy = max(headerSize.cy, colSize.cy);
+ }
+
+ if (m_Settings.m_TableHeader)
+ {
+ for (j = 1; j <= headerSize.cy; j++)
+ {
+ tos << muT("<tr class=\"header\">") << ext::endl;
+
+ iter_each_(std::vector<Column*>, col, m_ActiveCols)
+ {
+ (*col)->outputRenderHeader(tos, j, headerSize.cy);
+ }
+
+ tos << muT("</tr>") << ext::endl;
+ }
+ }
+
+ // stop if problem creating files/folders
+ if (!m_ErrorText.empty())
+ {
+ bInterrupted = true;
+ }
+
+ if (shouldTerminate())
+ {
+ bInterrupted = true;
+ }
+
+ /*
+ * Output contacts.
+ */
+
+ if (!bInterrupted)
+ {
+ upto_each_(i, countContacts())
+ {
+ tos << muT("<tr>") << ext::endl;
+
+ const Contact& curContact = getContact(i);
+
+ setProgressLabel(true, curContact.getNick());
+
+ iter_each_(std::vector<Column*>, col, m_ActiveCols)
+ {
+ (*col)->outputRenderRow(tos, curContact, Column::asContact);
+ }
+
+ tos << muT("</tr>") << ext::endl;
+
+ if (shouldTerminate())
+ {
+ bInterrupted = true;
+ break;
+ }
+
+ // stop if problem creating files/folders
+ if (!m_ErrorText.empty())
+ {
+ bInterrupted = true;
+ break;
+ }
+
+ if (m_Settings.m_TableHeader && m_Settings.m_TableHeaderRepeat != 0 && ((i + 1) % m_Settings.m_TableHeaderRepeat == 0))
+ {
+ for (j = 1; j <= headerSize.cy; ++j)
+ {
+ tos << muT("<tr class=\"header\">") << ext::endl;
+
+ iter_each_(std::vector<Column*>, col, m_ActiveCols)
+ {
+ (*col)->outputRenderHeader(tos, j, headerSize.cy);
+ }
+
+ tos << muT("</tr>") << ext::endl;
+ }
+ }
+
+ stepProgress(true);
+ }
+ } // !bInterrupted
+
+ /*
+ * Output omitted contacts.
+ */
+
+ if (!bInterrupted && m_Settings.m_OmitContacts && m_Settings.m_OmittedInExtraRow && m_bActuallyOmitted)
+ {
+ setProgressLabel(true, i18n(muT("Writing omitted contacts")));
+
+ const Contact& omittedContact = getOmitted();
+
+ tos << muT("<tr class=\"omitted\">") << ext::endl;
+
+ iter_each_(std::vector<Column*>, col, m_ActiveCols)
+ {
+ (*col)->outputRenderRow(tos, omittedContact, Column::asOmitted);
+ }
+
+ tos << muT("</tr>") << ext::endl;
+
+ // stop if problem creating files/folders
+ if (!m_ErrorText.empty())
+ {
+ bInterrupted = true;
+ }
+
+ if (shouldTerminate())
+ {
+ bInterrupted = true;
+ }
+ }
+
+ stepProgress(true);
+
+ /*
+ * Output totals.
+ */
+
+ if (!bInterrupted && m_Settings.m_CalcTotals)
+ {
+ setProgressLabel(true, i18n(muT("Writing totals")));
+
+ const Contact& totalsContact = getTotals();
+
+ tos << muT("<tr class=\"totals\">") << ext::endl;
+
+ iter_each_(std::vector<Column*>, col, m_ActiveCols)
+ {
+ (*col)->outputRenderRow(tos, totalsContact, Column::asTotal);
+ }
+
+ tos << muT("</tr>") << ext::endl;
+
+ stepProgress(true);
+
+ /*
+ * Finish output.
+ */
+
+ tos << muT("</table>") << ext::endl;
+
+ tos << muT("<div class=\"footer\">")
+ << ext::kformat(i18n(muT("Created with #{plugin} #{version} on #{date} at #{time}")))
+ % muT("#{plugin}") * muT("<a href=\"http://miranda.dark-passage.de/\">HistoryStats</a>")
+ % muT("#{version}") * utils::versionToDotted(m_Settings.m_VersionCurrent)
+ % muT("#{date}") * utils::htmlEscape(utils::timestampToDate(getTimeStarted()))
+ % muT("#{time}") * utils::htmlEscape(utils::timestampToTime(getTimeStarted()))
+ << muT("</div>") << ext::endl;
+
+ tos << muT("</body></html>") << ext::endl;
+ } // !bInterrupted
+
+ /*
+ * Inform active columns about ending output.
+ */
+
+ iter_each_(std::vector<Column*>, col, m_ActiveCols)
+ {
+ (*col)->outputEnd();
+ }
+
+ /*
+ * Close output stream.
+ */
+
+ tos.flush();
+ ofs.close();
+
+ /*
+ * Handle conflicting files.
+ */
+
+ if (bInterrupted)
+ {
+ iter_each_(ConflictingFiles, fi, m_ConflictingFiles)
+ {
+ DeleteFile(fi->second.c_str());
+ }
+
+ m_ConflictingFiles.clear();
+ }
+
+ if (m_ConflictingFiles.size() > 0)
+ {
+ int nResult = DialogBoxParam(
+ m_hInst,
+ MAKEINTRESOURCE(IDD_CONFLICT),
+ m_hWndProgress,
+ staticConflictProc,
+ reinterpret_cast<LPARAM>(&m_ConflictingFiles));
+
+ if (nResult == IDOK)
+ {
+ iter_each_(ConflictingFiles, fi, m_ConflictingFiles)
+ {
+ if (!MoveFileEx(fi->second.c_str(), fi->first.c_str(), MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING))
+ {
+ if (!MoveFile(fi->second.c_str(), fi->first.c_str()))
+ {
+ CopyFile(fi->second.c_str(), fi->first.c_str(), FALSE);
+ DeleteFile(fi->second.c_str());
+ }
+ }
+ }
+ }
+ else
+ {
+ iter_each_(ConflictingFiles, fi, m_ConflictingFiles)
+ {
+ DeleteFile(fi->second.c_str());
+ }
+ }
+
+ m_ConflictingFiles.clear();
+ }
+
+ /* don't do this, we don't want to delete a file we possibly never touched
+ if (bInterrupted)
+ {
+ // remove partialy generated file, if interrupted
+ DeleteFile(m_OutputFile.c_str());
+ }
+ */
+
+ return !bInterrupted;
+}
+
+Statistic::Statistic(const Settings& settings, InvocationSource invokedFrom, HINSTANCE hInst)
+ : m_Settings(settings),
+ m_CharMapper(m_Settings),
+ m_hInst(hInst),
+ m_hWndProgress(NULL),
+ m_hThreadPushEvent(NULL),
+ m_hCancelEvent(NULL),
+ m_InvokedFrom(invokedFrom),
+ m_pTotals(NULL),
+ m_pOmitted(NULL),
+ m_bActuallyOmitted(false),
+ m_nNextSlot(0),
+ m_nLastFileNr(0),
+ m_TimeMin(0),
+ m_TimeMax(0xFFFFFFFF),
+ m_bHistoryTimeAvailable(false),
+ m_nFirstTime(0),
+ m_nLastTime(0)
+{
+ m_TimeStarted = utils::toLocalTime(time(NULL));
+ m_MSecStarted = GetTickCount();
+ m_AverageMinTime = settings.m_AverageMinTime * 24 * 60 * 60; // calculate seconds from days
+}
+
+bool Statistic::createStatistics()
+{
+ /*
+ * Prepare event for cancel.
+ */
+ m_hCancelEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
+
+ if (m_hCancelEvent == NULL)
+ {
+ return false;
+ }
+
+ m_hWndProgress = CreateDialog(m_hInst, MAKEINTRESOURCE(IDD_PROGRESS), 0, staticProgressProc);
+
+ if (m_hWndProgress == NULL)
+ {
+ CloseHandle(m_hCancelEvent);
+ m_hCancelEvent = NULL;
+ return false;
+ }
+
+ SetWindowLong(m_hWndProgress, GWL_USERDATA, reinterpret_cast<LONG>(this));
+
+ /*
+ * Init progress dialog.
+ */
+ utils::centerDialog(m_hWndProgress);
+ UpdateWindow(m_hWndProgress);
+
+ DWORD dwThreadID = 0;
+ HANDLE hThread = CreateThread(NULL, 0, threadProcSteps, this, 0, &dwThreadID);
+
+ bool bDone = false;
+ MSG msg;
+
+ while (!bDone)
+ {
+ while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
+ {
+ if (msg.message == WM_QUIT)
+ {
+ bDone = true;
+ break;
+ }
+
+ if (!IsDialogMessage(msg.hwnd, &msg))
+ {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+ }
+
+ if (bDone)
+ {
+ break;
+ }
+
+ DWORD result = MsgWaitForMultipleObjects(1, &hThread, FALSE, INFINITE, QS_ALLINPUT);
+
+ if (result == WAIT_OBJECT_0 + 1)
+ {
+ // there is a message in the queue
+ continue;
+ }
+ else
+ {
+ // thread is signaled, i.e. terminated
+ DestroyWindow(m_hWndProgress);
+ }
+ }
+
+ /*
+ * Get result from thread.
+ */
+ // bool bSuccess = createStatisticsSteps();
+ DWORD threadRes;
+ bool bSuccess = false;
+
+ if (GetExitCodeThread(hThread, &threadRes))
+ {
+ bSuccess = (threadRes == 0);
+ }
+
+ /*
+ * Cleanup.
+ */
+ CloseHandle(hThread);
+ CloseHandle(m_hCancelEvent);
+ m_hCancelEvent = NULL;
+ m_hWndProgress = NULL;
+
+ if (bSuccess)
+ {
+ /*
+ * Save last successfully created statistics
+ */
+ g_pSettings->setLastStatisticsFile(m_OutputFile.c_str());
+
+ /*
+ * Open afterwards, if requested.
+ */
+ bool bOpenAfterwards =
+ (m_InvokedFrom == fromOptions && m_Settings.m_AutoOpenOptions) ||
+ (m_InvokedFrom == fromStartup && m_Settings.m_AutoOpenStartup) ||
+ (m_InvokedFrom == fromMenu && m_Settings.m_AutoOpenMenu);
+
+ if (bOpenAfterwards)
+ {
+ m_Settings.openURL(m_OutputFile.c_str());
+ }
+ }
+
+ return bSuccess;
+}
+
+bool Statistic::createStatisticsSteps()
+{
+ static const struct {
+ bool (Statistic::*stepFn)();
+ mu_text* stepMsg;
+ } stepsInfo[] = {
+ { stepInit , I18N(muT("Initializing")) },
+ { stepReadDB , I18N(muT("Reading database")) },
+ { stepRemoveContacts , I18N(muT("Removing contacts")) },
+ { stepSortContacts , I18N(muT("Sorting contacts")) },
+ { stepPreOmitContacts , I18N(muT("Precollecting column data")) },
+ { stepOmitContacts , I18N(muT("Limiting number of contacts")) },
+ { stepCalcTotals , I18N(muT("Calculating totals")) },
+ { stepPostOmitContacts, I18N(muT("Postcollecting column data")) },
+ { stepTransformData , I18N(muT("Transforming data")) },
+ { stepWriteHTML , I18N(muT("Creating HTML")) }
+ };
+
+ setProgressMax(false, array_len(stepsInfo));
+
+ array_each_(i, stepsInfo)
+ {
+ setProgressLabel(false, i18n(stepsInfo[i].stepMsg));
+
+ if (!(this->*stepsInfo[i].stepFn)())
+ {
+ return false;
+ }
+
+ stepProgress(false);
+ }
+
+ /*
+ * Last step: We are done.
+ */
+ setProgressLabel(false, i18n(muT("Done")));
+
+ return true;
+}
+
+DWORD WINAPI Statistic::threadProc(LPVOID lpParameter)
+{
+ Statistic* pStats = reinterpret_cast<Statistic*>(lpParameter);
+
+ // push to thread unwind stack
+ mu::system::threadPush();
+ SetEvent(pStats->m_hThreadPushEvent);
+
+ // perform action
+ bool bSuccess = pStats->createStatistics();
+
+ // check for errors
+ if (!pStats->m_ErrorText.empty() && !mu::system::terminated())
+ {
+ MessageBox(
+ 0,
+ pStats->m_ErrorText.c_str(),
+ i18n(muT("HistoryStats - Error")),
+ MB_ICONERROR | MB_OK);
+ }
+
+ // free statistics
+ delete pStats;
+
+ m_bRunning = false;
+
+ // pop from thread unwind stack
+ mu::system::threadPop();
+
+ return 0;
+}
+
+DWORD WINAPI Statistic::threadProcSteps(LPVOID lpParameter)
+{
+ Statistic* pStats = reinterpret_cast<Statistic*>(lpParameter);
+
+ if (pStats->m_Settings.m_ThreadLowPriority)
+ {
+ SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
+ }
+
+ bool bSuccess = pStats->createStatisticsSteps();
+
+ return (bSuccess ? 0 : 1);
+}
+
+BOOL CALLBACK Statistic::staticConflictProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ if (uMsg == WM_INITDIALOG)
+ {
+ mu::langpack::translateDialog(hDlg);
+
+ SendMessage(hDlg, WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(LoadIcon(g_hInst, MAKEINTRESOURCE(IDI_HISTORYSTATS))));
+
+ utils::centerDialog(hDlg);
+
+ HWND hWndFiles = GetDlgItem(hDlg, IDC_FILES);
+ ConflictingFiles* pFiles = reinterpret_cast<ConflictingFiles*>(lParam);
+
+ LVCOLUMN lvc;
+
+ lvc.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_FMT;
+ lvc.fmt = LVCFMT_LEFT;
+ lvc.cx = 400;
+ lvc.pszText = const_cast<mu_text*>(i18n(muT("Already existing file")));
+
+ ListView_InsertColumn(hWndFiles, 0, &lvc);
+
+ int nIndex = 0;
+
+ iter_each_(ConflictingFiles, fi, *pFiles)
+ {
+ LVITEM lvi;
+
+ lvi.mask = LVIF_TEXT;
+ lvi.iItem = nIndex++;
+ lvi.iSubItem = 0;
+ lvi.pszText = const_cast<mu_text*>(fi->first.c_str());
+
+ ListView_InsertItem(hWndFiles, &lvi);
+ }
+ }
+ else if (uMsg == WM_COMMAND)
+ {
+ switch (LOWORD(wParam))
+ {
+ case IDOK:
+ case IDCANCEL:
+ EndDialog(hDlg, LOWORD(wParam));
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+Statistic::~Statistic()
+{
+ iter_each_(ContactList, i, m_Contacts)
+ {
+ freeContactData(**i);
+ delete *i;
+ }
+
+ m_Contacts.clear();
+
+ if (m_pOmitted)
+ {
+ freeContactData(*m_pOmitted);
+ delete m_pOmitted;
+ }
+
+ if (m_pTotals)
+ {
+ freeContactData(*m_pTotals);
+ delete m_pTotals;
+ }
+}
+
+void Statistic::run(const Settings& settings, InvocationSource invokedFrom, HINSTANCE hInst, HWND hWndParent /* = NULL */)
+{
+ // check if running and make running
+ if (m_bRunning)
+ {
+ MessageBox(
+ 0,
+ i18n(muT("HistoryStats is already generating statistics. Please wait for the already running process to be finished or cancel it and try again.")),
+ i18n(muT("HistoryStats")),
+ MB_ICONINFORMATION | MB_OK);
+ return;
+ }
+
+ m_bRunning = true;
+
+ // create object holding and performing the statistics
+ Statistic* pStats = new Statistic(settings, invokedFrom, hInst);
+
+ // create event for thread stack unwinding
+ if ((pStats->m_hThreadPushEvent = CreateEvent(NULL, FALSE, FALSE, NULL)) == NULL)
+ {
+ m_bRunning = false;
+ return;
+ }
+
+ // create worker thread
+ DWORD dwThreadID = 0;
+ HANDLE hThread = CreateThread(NULL, 0, threadProc, pStats, 0, &dwThreadID);
+
+ // wait for thread to place itself on thread unwind stack
+ if (hThread != NULL)
+ {
+ WaitForSingleObject(pStats->m_hThreadPushEvent, INFINITE);
+ }
+ else
+ {
+ m_bRunning = false;
+ }
+
+ CloseHandle(pStats->m_hThreadPushEvent);
+ pStats->m_hThreadPushEvent = NULL;
+}