Index: src/CommitMonitor.cpp =================================================================== --- src/CommitMonitor.cpp (revision 605) +++ src/CommitMonitor.cpp (working copy) @@ -33,6 +33,9 @@ #define STRUCT_IOVEC_DEFINED #include "sasl.h" +#include "Http.h" +#include "ReviewBoardXml.h" + // Global Variables: HINSTANCE hInst; // current instance HANDLE g_mutex = 0; @@ -47,6 +50,8 @@ UNREFERENCED_PARAMETER(hPrevInstance); UNREFERENCED_PARAMETER(lpCmdLine); UNREFERENCED_PARAMETER(nCmdShow); + gHTTP.Init("reviews.reviewboard.org"); + gReviewBoard; SetDllDirectory(L""); ::OleInitialize(NULL); @@ -116,7 +121,7 @@ else { //only one instance of this application part allowed - g_mutex = ::CreateMutex(NULL, FALSE, APPNAME_MUTEX); + g_mutex = ::CreateMutexW(NULL, FALSE, APPNAME_MUTEX); if (g_mutex != NULL) { @@ -141,6 +146,8 @@ if (hiddenWindow.RegisterAndCreateWindow()) { + gReviewBoard.Load(); + if ((snarlIface.GetVersionEx() != Snarl::M_FAILED)&&(Snarl::SnarlInterface::GetSnarlWindow() != NULL)) { wstring imgPath = CAppUtils::GetAppDataDir()+L"\\CM.png"; @@ -189,7 +196,8 @@ // unregister with Snarl snarlIface.UnregisterApp(); } - + gReviewBoard_Cleanup; + gHTTP_Cleanup; return (int) msg.wParam; } Index: src/CommitMonitor.vcxproj =================================================================== --- src/CommitMonitor.vcxproj (revision 605) +++ src/CommitMonitor.vcxproj (working copy) @@ -51,7 +51,7 @@ Disabled - ..\ext\apr\include;..\ext\Subversion\Subversion\include;..\ext\apr-util\include;Resources;..\ext\scintilla\win32;..\ext\scintilla\src;..\ext\scintilla\lexers;..\ext\scintilla\lexlib;..\ext\scintilla\include;..\ext\cyrus-sasl\include;..\ext\snarl;..\ext\BlowFish;%(AdditionalIncludeDirectories) + J:\dev\poco\include;..\ext\rapidxml;..\ext\apr\include;..\ext\Subversion\Subversion\include;..\ext\apr-util\include;Resources;..\ext\scintilla\win32;..\ext\scintilla\src;..\ext\scintilla\lexers;..\ext\scintilla\lexlib;..\ext\scintilla\include;..\ext\cyrus-sasl\include;..\ext\snarl;..\ext\BlowFish;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_WINDOWS;SVN_DEBUG;APR_DECLARE_STATIC;APU_DECLARE_STATIC;STATIC_BUILD;SCI_LEXER;%(PreprocessorDefinitions) true EnableFastChecks @@ -65,7 +65,7 @@ $(IntDir)%(Filename).res - Ws2_32.lib;Mswsock.lib;Rpcrt4.lib;Imm32.lib;Secur32.lib;Crypt32.lib;..\ext\apr\debug_win32\libapr.lib;..\ext\apr-util\debug_win32\libaprutil.lib;..\ext\neon\debug_win32\libneon.lib;..\ext\Subversion\debug_win32\libsvn_client.lib;..\ext\Subversion\debug_win32\libsvn_delta.lib;..\ext\Subversion\debug_win32\libsvn_diff.lib;..\ext\Subversion\debug_win32\libsvn_ra.lib;..\ext\Subversion\debug_win32\libsvn_ra_neon.lib;..\ext\Subversion\debug_win32\libsvn_ra_serf.lib;..\ext\Subversion\debug_win32\libsvn_ra_svn.lib;..\ext\Subversion\debug_win32\libsvn_repos.lib;..\ext\Subversion\debug_win32\libsvn_subr.lib;..\ext\Subversion\debug_win32\libsvn_wc.lib;..\ext\cyrus-sasl\debug_win32\libsasls.lib;..\ext\serf\debug_win32\libserf.lib;..\ext\sqlite\debug_win32\sqlite.lib;%(AdditionalDependencies) + PocoFoundationmtd.lib;PocoNetmtd.lib;PocoUtilmtd.lib;Ws2_32.lib;Mswsock.lib;Rpcrt4.lib;Imm32.lib;Secur32.lib;Crypt32.lib;..\ext\apr\debug_win32\libapr.lib;..\ext\apr-util\debug_win32\libaprutil.lib;..\ext\neon\debug_win32\libneon.lib;..\ext\Subversion\debug_win32\libsvn_client.lib;..\ext\Subversion\debug_win32\libsvn_delta.lib;..\ext\Subversion\debug_win32\libsvn_diff.lib;..\ext\Subversion\debug_win32\libsvn_ra.lib;..\ext\Subversion\debug_win32\libsvn_ra_neon.lib;..\ext\Subversion\debug_win32\libsvn_ra_serf.lib;..\ext\Subversion\debug_win32\libsvn_ra_svn.lib;..\ext\Subversion\debug_win32\libsvn_repos.lib;..\ext\Subversion\debug_win32\libsvn_subr.lib;..\ext\Subversion\debug_win32\libsvn_wc.lib;..\ext\cyrus-sasl\debug_win32\libsasls.lib;..\ext\serf\debug_win32\libserf.lib;..\ext\sqlite\debug_win32\sqlite.lib;%(AdditionalDependencies) winspool.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(IgnoreSpecificDefaultLibraries) true Windows @@ -73,6 +73,7 @@ MachineX86 + J:\dev\poco\lib;%(AdditionalLibraryDirectories) @@ -94,7 +95,7 @@ ..;%(AdditionalIncludeDirectories) - Ws2_32.lib;Mswsock.lib;Rpcrt4.lib;Imm32.lib;Secur32.lib;Crypt32.lib;..\ext\apr\release_win32\libapr.lib;..\ext\apr-util\release_win32\libaprutil.lib;..\ext\neon\release_win32\libneon.lib;..\ext\Subversion\release_win32\libsvn_client.lib;..\ext\Subversion\release_win32\libsvn_delta.lib;..\ext\Subversion\release_win32\libsvn_diff.lib;..\ext\Subversion\release_win32\libsvn_ra.lib;..\ext\Subversion\release_win32\libsvn_ra_neon.lib;..\ext\Subversion\release_win32\libsvn_ra_serf.lib;..\ext\Subversion\release_win32\libsvn_ra_svn.lib;..\ext\Subversion\release_win32\libsvn_repos.lib;..\ext\Subversion\release_win32\libsvn_subr.lib;..\ext\Subversion\release_win32\libsvn_wc.lib;..\ext\cyrus-sasl\release_win32\libsasls.lib;..\ext\serf\release_win32\libserf.lib;..\ext\sqlite\debug_win32\sqlite.lib;%(AdditionalDependencies) + PocoFoundationmt.lib;PocoNetmt.lib;PocoUtilmt.lib;Ws2_32.lib;Mswsock.lib;Rpcrt4.lib;Imm32.lib;Secur32.lib;Crypt32.lib;..\ext\apr\release_win32\libapr.lib;..\ext\apr-util\release_win32\libaprutil.lib;..\ext\neon\release_win32\libneon.lib;..\ext\Subversion\release_win32\libsvn_client.lib;..\ext\Subversion\release_win32\libsvn_delta.lib;..\ext\Subversion\release_win32\libsvn_diff.lib;..\ext\Subversion\release_win32\libsvn_ra.lib;..\ext\Subversion\release_win32\libsvn_ra_neon.lib;..\ext\Subversion\release_win32\libsvn_ra_serf.lib;..\ext\Subversion\release_win32\libsvn_ra_svn.lib;..\ext\Subversion\release_win32\libsvn_repos.lib;..\ext\Subversion\release_win32\libsvn_subr.lib;..\ext\Subversion\release_win32\libsvn_wc.lib;..\ext\cyrus-sasl\release_win32\libsasls.lib;..\ext\serf\release_win32\libserf.lib;..\ext\sqlite\debug_win32\sqlite.lib;%(AdditionalDependencies) winspool.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(IgnoreSpecificDefaultLibraries) true Windows @@ -323,10 +324,12 @@ + + @@ -336,6 +339,7 @@ + @@ -410,20 +414,25 @@ + + + + + Index: src/CommitMonitor.vcxproj.filters =================================================================== --- src/CommitMonitor.vcxproj.filters (revision 605) +++ src/CommitMonitor.vcxproj.filters (working copy) @@ -243,6 +243,15 @@ Scintilla + + Source Files + + + Source Files + + + Source Files + @@ -479,6 +488,21 @@ Scintilla + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + Index: src/Convert.h =================================================================== --- src/Convert.h (revision 0) +++ src/Convert.h (revision 0) @@ -0,0 +1,164 @@ +#ifndef CONVERT_H +#define CONVERT_H + +#include "stdafx.h" +#include +#include + +template +class Converter +{ +public: + static out_type convert(const in_value &t) + { + std::wstringstream stream; + stream << t; + + out_type result; + stream >> result; + return result; + } +}; + +template <> +class Converter +{ +public: + static std::wstring convert(const std::wstring &t) + { + return t; + } +}; + +template +out_type convert(const in_value &t) +{ + return Converter::convert(t); +} + +#define BOM8A 0xEF +#define BOM8B 0xBB +#define BOM8C 0xBF + +/* +* Copyright (c) 2009, Helios (helios.vmg@gmail.com) +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* * Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* +* THIS SOFTWARE IS PROVIDED BY HELIOS "AS IS" AND ANY EXPRESS OR IMPLIED +* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +* EVENT SHALL HELIOS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +* OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +typedef unsigned char uchar; + +/* +string: a UTF-8-encoded C string (nul terminated) +Return value: a wchar_t C string. + +The function handles memory allocation on its own. + +Limitations: Only handles the range [U+0000;U+FFFF], higher code points are +changed to '?'. + +Assumptions: sizeof(wchar_t)>=2 +*/ +wchar_t *UTF8_to_WChar(const char *string){ + long b=0, + c=0; + if ((uchar)string[0]==BOM8A && (uchar)string[1]==BOM8B && (uchar)string[2]==BOM8C) + string+=3; + for (const char *a=string;*a;a++) + if (((uchar)*a)<128 || (*a&192)==192) + c++; + wchar_t *res=new wchar_t[c+1]; + res[c]=0; + for (uchar *a=(uchar*)string;*a;a++){ + if (!(*a&128)) + //Byte represents an ASCII character. Direct copy will do. + res[b]=*a; + else if ((*a&192)==128) + //Byte is the middle of an encoded character. Ignore. + continue; + else if ((*a&224)==192) + //Byte represents the start of an encoded character in the range + //U+0080 to U+07FF + res[b]=((*a&31)<<6)|a[1]&63; + else if ((*a&240)==224) + //Byte represents the start of an encoded character in the range + //U+07FF to U+FFFF + res[b]=((*a&15)<<12)|((a[1]&63)<<6)|a[2]&63; + else if ((*a&248)==240){ + //Byte represents the start of an encoded character beyond the + //U+FFFF limit of 16-bit integers + res[b]='?'; + } + b++; + } + return res; +} + +//Do not call me. +long getUTF8size(const wchar_t *string){ + if (!string) + return 0; + long res=0; + for (;*string;string++){ + if (*string<0x80) + res++; + else if (*string<0x800) + res+=2; + else + res+=3; + } + return res; +} + +/* +string: a wchar_t C string (nul terminated) +Return value: a UTF-8-encoded C string. + +The function handles memory allocation on its own. + +Limitations: Only handles the range [U+0000;U+FFFF], higher code points are +changed to '?'. + +Assumptions: sizeof(wchar_t)>=2 +*/ +char *WChar_to_UTF8(const wchar_t *string){ + long fSize=getUTF8size(string); + char *res=new char[fSize+1]; + res[fSize]=0; + if (!string) + return res; + long b=0; + for (;*string;string++,b++){ + if (*string<0x80) + res[b]=(char)*string; + else if (*string<0x800){ + res[b++]=(*string>>6)|192; + res[b]=*string&63|128; + }else{ + res[b++]=(*string>>12)|224; + res[b++]=((*string&4095)>>6)|128; + res[b]=*string&63|128; + } + } + return res; +} + +#endif Index: src/HiddenWindow.cpp =================================================================== --- src/HiddenWindow.cpp (revision 605) +++ src/HiddenWindow.cpp (working copy) @@ -34,7 +34,7 @@ #include "OptionsDlg.h" #include "version.h" #include "SnarlInterface.h" - +#include "ReviewBoardXml.h" #include #include using namespace std; @@ -345,6 +345,7 @@ { // find the number of unread items int nNewCommits = 0; + int nNewReviews = gReviewBoard.GetUnreadReviews(); const map * pRead = m_UrlInfos.GetReadOnlyData(); for (map::const_iterator it = pRead->begin(); it != pRead->end(); ++it) { @@ -360,12 +361,39 @@ m_SystemTray.hWnd = *this; m_SystemTray.uFlags = NIF_MESSAGE | NIF_TIP; m_SystemTray.uCallbackMessage = COMMITMONITOR_TASKBARCALLBACK; - if (nNewCommits) + if (nNewCommits || nNewReviews) { - if (nNewCommits == 1) - _stprintf_s(m_SystemTray.szTip, _countof(m_SystemTray.szTip), _T("CommitMonitor - %d new commit"), nNewCommits); + if(nNewReviews == 0) + { + if (nNewCommits == 1) + _stprintf_s(m_SystemTray.szTip, _countof(m_SystemTray.szTip), _T("CommitMonitor - %d new commit"), nNewCommits); + else + _stprintf_s(m_SystemTray.szTip, _countof(m_SystemTray.szTip), _T("CommitMonitor - %d new commits"), nNewCommits); + } else - _stprintf_s(m_SystemTray.szTip, _countof(m_SystemTray.szTip), _T("CommitMonitor - %d new commits"), nNewCommits); + { + if (nNewCommits == 0) + { + if (nNewReviews == 1) + _stprintf_s(m_SystemTray.szTip, _countof(m_SystemTray.szTip), _T("CommitMonitor - %d new review"), nNewReviews); + else + _stprintf_s(m_SystemTray.szTip, _countof(m_SystemTray.szTip), _T("CommitMonitor - %d new reviews"), nNewReviews); + } + else if(nNewCommits == 1) + { + if (nNewReviews == 1) + _stprintf_s(m_SystemTray.szTip, _countof(m_SystemTray.szTip), _T("CommitMonitor - %d new commit, %d new review"), nNewCommits, nNewReviews); + else + _stprintf_s(m_SystemTray.szTip, _countof(m_SystemTray.szTip), _T("CommitMonitor - %d new commit, %d new reviews"), nNewCommits, nNewReviews); + } + else + { + if (nNewReviews == 1) + _stprintf_s(m_SystemTray.szTip, _countof(m_SystemTray.szTip), _T("CommitMonitor - %d new commits, %d new review"), nNewCommits, nNewReviews); + else + _stprintf_s(m_SystemTray.szTip, _countof(m_SystemTray.szTip), _T("CommitMonitor - %d new commits, %d new reviews"), nNewCommits, nNewReviews); + } + } } else _tcscpy_s(m_SystemTray.szTip, _countof(m_SystemTray.szTip), _T("CommitMonitor")); @@ -679,7 +707,8 @@ // won't block anything for too long. map urlinfoReadOnly = *m_UrlInfos.GetReadOnlyData(); m_UrlInfos.ReleaseReadOnlyData(); - + bool needReviewUpdate = false; + int newreviews = 0; TCHAR infotextbuf[1024]; TRACE(_T("monitor thread started\n")); const map * pUrlInfoReadOnly = &urlinfoReadOnly; @@ -695,6 +724,13 @@ } if (((it->second.monitored)&&((it->second.lastchecked + (mit*60)) < currenttime))||(m_UrlToWorkOn.size())) { + if(!needReviewUpdate && gReviewBoard.IsUrlMonitored(it->second.url)) + { + needReviewUpdate = true; + // do update here, we're not currently blocking anything (update will ofc...) + newreviews = gReviewBoard.DoUpdate(); + } + m_UrlToWorkOn.clear(); if ((it->second.errNr == SVN_ERR_RA_NOT_AUTHORIZED)&&(!it->second.error.empty())) continue; // don't check if the last error was 'not authorized' @@ -740,9 +776,9 @@ } if (!m_bRun) continue; - if (headrev > it->second.lastcheckedrev) + if (headrev > it->second.lastcheckedrev || gReviewBoard.GetNewReviewsForUrl(it->second.url)) { - TRACE(_T("%s has updates! Last checked revision was %ld, HEAD revision is %ld\n"), it->first.c_str(), it->second.lastcheckedrev, headrev); + TRACE(_T("%s has updates! Last checked revision was %ld, HEAD revision is %ld or new reviews\n"), it->first.c_str(), it->second.lastcheckedrev, headrev); if (m_hMainDlg) { _stprintf_s(infotextbuf, 1024, _T("getting log for %s"), it->first.c_str()); @@ -750,7 +786,7 @@ } int nNewCommits = 0; // commits without ignored ones int nTotalNewCommits = 0; // all commits, including ignored ones - if (pSCCS->GetLog(it->second.accurevRepo, it->first, headrev, it->second.lastcheckedrev + 1)) + if ((headrev <= it->second.lastcheckedrev && gReviewBoard.GetNewReviewsForUrl(it->second.url)) || pSCCS->GetLog(it->second.accurevRepo, it->first, headrev, it->second.lastcheckedrev + 1)) { TRACE(_T("log fetched for %s\n"), it->first.c_str()); if (!m_bRun) @@ -772,112 +808,115 @@ wstring sPopupText; bool hadError = !it->second.error.empty(); - for (map::iterator logit = pSCCS->m_logs.begin(); logit != pSCCS->m_logs.end(); ++logit) + if(headrev > it->second.lastcheckedrev) { - // again, only block for a short time - map * pWrite = m_UrlInfos.GetWriteData(); - map::iterator writeIt = pWrite->find(it->first); - bool bIgnore = false; - bool bEntryExists = false; - if (writeIt != pWrite->end()) + for (map::iterator logit = pSCCS->m_logs.begin(); logit != pSCCS->m_logs.end(); ++logit) { - map::iterator existIt = writeIt->second.logentries.find(logit->first); - bEntryExists = existIt != writeIt->second.logentries.end(); - bool readState = false; - if (bEntryExists) - readState = existIt->second.read; - logit->second.read = readState; + // again, only block for a short time + map * pWrite = m_UrlInfos.GetWriteData(); + map::iterator writeIt = pWrite->find(it->first); + bool bIgnore = false; + bool bEntryExists = false; + if (writeIt != pWrite->end()) + { + map::iterator existIt = writeIt->second.logentries.find(logit->first); + bEntryExists = existIt != writeIt->second.logentries.end(); + bool readState = false; + if (bEntryExists) + readState = existIt->second.read; + logit->second.read = readState; - writeIt->second.logentries[logit->first] = logit->second; + writeIt->second.logentries[logit->first] = logit->second; - if (!bEntryExists) - { - wstring author1 = logit->second.author; - std::transform(author1.begin(), author1.end(), author1.begin(), std::tolower); + if (!bEntryExists) + { + wstring author1 = logit->second.author; + std::transform(author1.begin(), author1.end(), author1.begin(), std::tolower); - if (writeIt->second.includeUsers.size() > 0) - { - wstring s1 = writeIt->second.includeUsers; + if (writeIt->second.includeUsers.size() > 0) + { + wstring s1 = writeIt->second.includeUsers; + std::transform(s1.begin(), s1.end(), s1.begin(), std::tolower); + CAppUtils::SearchReplace(s1, _T("\r\n"), _T("\n")); + vector includeVector = CAppUtils::tokenize_str(s1, _T("\n")); + bool bInclude = false; + for (vector::iterator it = includeVector.begin(); it != includeVector.end(); ++it) + { + if (author1.compare(*it) == 0) + { + bInclude = true; + break; + } + } + bIgnore = !bInclude; + } + + wstring s1 = writeIt->second.ignoreUsers; std::transform(s1.begin(), s1.end(), s1.begin(), std::tolower); CAppUtils::SearchReplace(s1, _T("\r\n"), _T("\n")); - vector includeVector = CAppUtils::tokenize_str(s1, _T("\n")); - bool bInclude = false; - for (vector::iterator it = includeVector.begin(); it != includeVector.end(); ++it) + vector ignoreVector = CAppUtils::tokenize_str(s1, _T("\n")); + for (vector::iterator it = ignoreVector.begin(); it != ignoreVector.end(); ++it) { if (author1.compare(*it) == 0) { - bInclude = true; + bIgnore = true; break; } } - bIgnore = !bInclude; - } - - wstring s1 = writeIt->second.ignoreUsers; - std::transform(s1.begin(), s1.end(), s1.begin(), std::tolower); - CAppUtils::SearchReplace(s1, _T("\r\n"), _T("\n")); - vector ignoreVector = CAppUtils::tokenize_str(s1, _T("\n")); - for (vector::iterator it = ignoreVector.begin(); it != ignoreVector.end(); ++it) - { - if (author1.compare(*it) == 0) + nTotalNewCommits++; + if (!bIgnore) { - bIgnore = true; - break; + bNewEntries = true; + nNewCommits++; } + else + // set own commit as already read + writeIt->second.logentries[logit->first].read = true; } - nTotalNewCommits++; - if (!bIgnore) - { - bNewEntries = true; - nNewCommits++; - } - else - // set own commit as already read - writeIt->second.logentries[logit->first].read = true; + writeIt->second.error.clear(); } - writeIt->second.error.clear(); - } - bNeedsSaving = true; - m_UrlInfos.ReleaseWriteData(); - if (!m_bRun) - continue; - // popup info text - if ((!bIgnore)&&(!bEntryExists)) - { - if (!sPopupText.empty()) - sPopupText += _T(", "); - sPopupText += logit->second.author; - } - if ((!it->second.disallowdiffs)&&(it->second.fetchdiffs)) - { - TCHAR buf[4096]; - // first, find a name where to store the diff for that revision - _stprintf_s(buf, 4096, _T("%s_%ld.diff"), it->second.name.c_str(), logit->first); - wstring diffFileName = CAppUtils::GetAppDataDir(); - diffFileName += _T("/"); - diffFileName += wstring(buf); - // do we already have that diff? - if (!PathFileExists(diffFileName.c_str())) + bNeedsSaving = true; + m_UrlInfos.ReleaseWriteData(); + if (!m_bRun) + continue; + // popup info text + if ((!bIgnore)&&(!bEntryExists)) { - // get the diff - if (m_hMainDlg) + if (!sPopupText.empty()) + sPopupText += _T(", "); + sPopupText += logit->second.author; + } + if ((!it->second.disallowdiffs)&&(it->second.fetchdiffs)) + { + TCHAR buf[4096]; + // first, find a name where to store the diff for that revision + _stprintf_s(buf, 4096, _T("%s_%ld.diff"), it->second.name.c_str(), logit->first); + wstring diffFileName = CAppUtils::GetAppDataDir(); + diffFileName += _T("/"); + diffFileName += wstring(buf); + // do we already have that diff? + if (!PathFileExists(diffFileName.c_str())) { - _stprintf_s(infotextbuf, 1024, _T("getting diff for %s, revision %ld"), it->first.c_str(), logit->first); - SendMessage(*this, COMMITMONITOR_INFOTEXT, 0, (LPARAM)infotextbuf); + // get the diff + if (m_hMainDlg) + { + _stprintf_s(infotextbuf, 1024, _T("getting diff for %s, revision %ld"), it->first.c_str(), logit->first); + SendMessage(*this, COMMITMONITOR_INFOTEXT, 0, (LPARAM)infotextbuf); + } + if (!pSCCS->Diff(it->first, logit->first, logit->first-1, logit->first, true, true, false, wstring(), false, diffFileName, wstring())) + { + TRACE(_T("Diff not fetched for %s, revision %ld because of an error\n"), it->first.c_str(), logit->first); + DeleteFile(diffFileName.c_str()); + } + else + TRACE(_T("Diff fetched for %s, revision %ld\n"), it->first.c_str(), logit->first); + if (!m_bRun) + break; } - if (!pSCCS->Diff(it->first, logit->first, logit->first-1, logit->first, true, true, false, wstring(), false, diffFileName, wstring())) - { - TRACE(_T("Diff not fetched for %s, revision %ld because of an error\n"), it->first.c_str(), logit->first); - DeleteFile(diffFileName.c_str()); - } - else - TRACE(_T("Diff fetched for %s, revision %ld\n"), it->first.c_str(), logit->first); - if (!m_bRun) - break; } } } - if ((it->second.lastcheckedrobots + (60*60*24*2)) < currenttime) + if ((headrev > it->second.lastcheckedrev) && (it->second.lastcheckedrobots + (60*60*24*2)) < currenttime) { wstring sRobotsURL = it->first; sRobotsURL += _T("/svnrobots.txt"); @@ -984,7 +1023,8 @@ m_UrlInfos.ReleaseWriteData(); } // prepare notification strings - if ((bNewEntries)||(!hadError && !it->second.error.empty())) + int nNewReviews = gReviewBoard.GetNewReviewsForUrl(it->second.url); + if ((nNewReviews > 0)||(bNewEntries)||(!hadError && !it->second.error.empty())) { popupData data; TCHAR sTitle[1024] = {0}; @@ -996,10 +1036,31 @@ else { data.sProject = it->second.name; - if (nNewCommits == 1) - _stprintf_s(sTitle, 1024, _T("%s\nhas %d new commit"), it->second.name.c_str(), nNewCommits); - else - _stprintf_s(sTitle, 1024, _T("%s\nhas %d new commits"), it->second.name.c_str(), nNewCommits); + if (nNewCommits == 0) + { + if(nNewReviews == 1) + _stprintf_s(sTitle, 1024, _T("%s\nhas %d new review"), it->second.name.c_str(), nNewReviews); + else + _stprintf_s(sTitle, 1024, _T("%s\nhas %d new reviews"), it->second.name.c_str(), nNewReviews); + } + else if (nNewCommits == 1) + { + if(nNewReviews == 0) + _stprintf_s(sTitle, 1024, _T("%s\nhas %d new commit"), it->second.name.c_str(), nNewCommits); + else if(nNewReviews == 1) + _stprintf_s(sTitle, 1024, _T("%s\nhas %d new commit, %d new review"), it->second.name.c_str(), nNewCommits, nNewReviews); + else + _stprintf_s(sTitle, 1024, _T("%s\nhas %d new commit, %d new reviews"), it->second.name.c_str(), nNewCommits, nNewReviews); + } + else + { + if(nNewReviews == 0) + _stprintf_s(sTitle, 1024, _T("%s\nhas %d new commits"), it->second.name.c_str(), nNewCommits); + else if(nNewReviews == 1) + _stprintf_s(sTitle, 1024, _T("%s\nhas %d new commits, %d new review"), it->second.name.c_str(), nNewCommits, nNewReviews); + else + _stprintf_s(sTitle, 1024, _T("%s\nhas %d new commits, %d new reviews"), it->second.name.c_str(), nNewCommits, nNewReviews); + } } data.sText = sPopupText; data.sTitle = wstring(sTitle); Index: src/Http.cpp =================================================================== --- src/Http.cpp (revision 0) +++ src/Http.cpp (revision 0) @@ -0,0 +1,112 @@ +#include "stdafx.h" +#include "Http.h" + +#pragma warning( push ) +#pragma warning( disable: 4244 ) +#include "Poco/Net/HTTPClientSession.h" +#include "Poco/Net/HTTPRequest.h" +#include "Poco/Net/HTTPResponse.h" +#include "Poco/StreamCopier.h" +#include "Poco/Path.h" +#include "Poco/URI.h" +#include "Poco/Exception.h" +#pragma warning( pop ) +#include +#include + +using Poco::Net::HTTPClientSession; +using Poco::Net::HTTPRequest; +using Poco::Net::HTTPResponse; +using Poco::Net::HTTPMessage; +using Poco::StreamCopier; +using Poco::Path; +using Poco::URI; +using Poco::Exception; + +using namespace std; + + +Http::Http() : inited(false) +{ +} + +Http::~Http() +{ + if(inited) + { + } +} + +bool Http::Init(const string& host) +{ + string url = "http://"; + url += host; + url += "/"; + URI uri(url); + std::string path(uri.getPathAndQuery()); + if (path.empty()) path = "/"; + + + session.setHost(uri.getHost()); + session.setPort(uri.getPort()); + inited = true; + return true; +} + +void Http::GetPageText(const string& path, stringstream& sstrm) +{ + if(!inited) + return; + HTTPRequest req(HTTPRequest::HTTP_GET, path, HTTPMessage::HTTP_1_1); + req.set("Accept","text/x-patch"); + session.sendRequest(req); + HTTPResponse res; + std::istream& rs = session.receiveResponse(res); + StreamCopier::copyStream(rs, sstrm); +} + +void Http::GetPageXml(const string& path, stringstream& sstrm) +{ + if(!inited) + return; + HTTPRequest req(HTTPRequest::HTTP_GET, path, HTTPMessage::HTTP_1_1); + req.set("Accept","application/xml"); + session.sendRequest(req); + HTTPResponse res; + std::istream& rs = session.receiveResponse(res); + StreamCopier::copyStream(rs, sstrm); +} + +void Http::GetPageXml(const string& path, string& str) +{ + stringstream sstrm; + GetPageXml(path,sstrm); + str = sstrm.str(); +} + +void Http::GetPageText(const string& path, string& str) +{ + stringstream sstrm; + GetPageText(path,sstrm); + str = sstrm.str(); +} + +void Http::GetPageXml(const string& path, char *& buf, int& len) +{ + string str; + GetPageXml(path,str); + len = str.length(); + buf = new char[len+1]; + memcpy(buf,str.c_str(),len * sizeof(char)); + buf[len]=0; +} + +void Http::GetPageText(const std::string& path, char *& buf, int& len) +{ + string str; + GetPageText(path,str); + len = str.length(); + buf = new char[len+1]; + memcpy(buf,str.c_str(),len * sizeof(char)); + buf[len]=0; +} Index: src/Http.h =================================================================== --- src/Http.h (revision 0) +++ src/Http.h (revision 0) @@ -0,0 +1,32 @@ +#ifndef HTTP_H__ +#define HTTP_H__ + +#include "stdafx.h" + +#include "Poco/Net/HTTPClientSession.h" +#include +#include "Singleton.h" + +class Http +{ + friend class Singleton; + Poco::Net::HTTPClientSession session; + bool inited; +private: + Http(); +public: + ~Http(); + bool Init(const std::string& host); + void GetPageXml(const std::string& path, std::stringstream& sstrm); + void GetPageText(const std::string& path, std::stringstream& sstrm); + void GetPageXml(const std::string& path, std::string& str); + void GetPageText(const std::string& path, std::string& str); + void GetPageXml(const std::string& path, char *& buf_you_must_delete_this_when_done_with_processing, int& len); + void GetPageText(const std::string& path, char *& buf_you_must_delete_this_when_done_with_processing, int& len); +}; + + +#define gHTTP (*(Singleton::GetInstance())) +#define gHTTP_Cleanup Singleton::Cleanup() + +#endif Index: src/MainDlg.cpp =================================================================== --- src/MainDlg.cpp (revision 605) +++ src/MainDlg.cpp (working copy) @@ -32,6 +32,8 @@ #include #include +#include "ReviewBoardXml.h" + #pragma comment(lib, "uxtheme.lib") #define FILTERBOXHEIGHT 20 @@ -52,6 +54,7 @@ , m_hImgList(NULL) , m_bNewerVersionAvailable(false) , m_refreshNeeded(false) + , m_commitsradio(true) { m_hParent = hParent; // use the default GUI font, create a copy of it and @@ -219,6 +222,11 @@ m_hListControl = ::GetDlgItem(*this, IDC_MONITOREDURLS); m_hLogMsgControl = ::GetDlgItem(*this, IDC_LOGINFO); m_hFilterControl = ::GetDlgItem(*this, IDC_FILTERSTRING); + m_hCommitsRadio = ::GetDlgItem(*this, IDC_RADIO2); + m_hReviewsRadio = ::GetDlgItem(*this, IDC_RADIO3); + + ::CheckRadioButton(hwndDlg, IDC_RADIO2, IDC_RADIO3, m_commitsradio ? IDC_RADIO2 : IDC_RADIO3); + ::SendMessage(m_hTreeControl, TVM_SETUNICODEFORMAT, 1, 0); SetWindowTheme(m_hListControl, L"Explorer", NULL); @@ -278,6 +286,9 @@ GetClientRect(*this, &rect); m_bottommarg = rect.bottom - m_bottommarg; + m_radiossize = 160; + + // subclass the tree view control to intercept the WM_SETFOCUS messages m_oldTreeWndProc = (WNDPROC)SetWindowLongPtr(m_hTreeControl, GWLP_WNDPROC, (LONG)TreeProc); SetWindowLongPtr(m_hTreeControl, GWLP_USERDATA, (LONG)this); @@ -446,7 +457,7 @@ case WM_GETMINMAXINFO: { MINMAXINFO * mmi = (MINMAXINFO*)lParam; - mmi->ptMinTrackSize.x = m_xSliderPos + 100; + mmi->ptMinTrackSize.x = m_xSliderPos + 265; mmi->ptMinTrackSize.y = m_ySliderPos + 100; return 0; } @@ -503,7 +514,7 @@ tv.hParent = FindParentTreeNode(it->first); tv.hInsertAfter = TVI_SORT; tv.itemex.mask = TVIF_TEXT|TVIF_PARAM|TVIF_STATE|TVIF_IMAGE|TVIF_SELECTEDIMAGE; - WCHAR * str = new WCHAR[it->second.name.size()+10]; + WCHAR * str = new WCHAR[it->second.name.size()+30]; // find out if there are some unread entries int unread = 0; for (map::const_iterator logit = it->second.logentries.begin(); logit != it->second.logentries.end(); ++logit) @@ -520,13 +531,14 @@ tv.itemex.hItem = directItem; tv.itemex.stateMask = TVIS_SELECTED|TVIS_BOLD|TVIF_IMAGE|TVIF_SELECTEDIMAGE; tv.itemex.pszText = str; - tv.itemex.cchTextMax = it->second.name.size()+9; + tv.itemex.cchTextMax = it->second.name.size()+29; TreeView_GetItem(m_hTreeControl, &tv.itemex); wstring sTitle = wstring(str); bool bRequiresUpdate = false; - if (unread) + int unreadreviews = gReviewBoard.GetUnreadReviewsForUrl(it->second.url); + if (unread || unreadreviews) { - _stprintf_s(str, it->second.name.size()+10, _T("%s (%d)"), it->second.name.c_str(), unread); + _stprintf_s(str, it->second.name.size()+30, _T("%s (%d + %d)"), it->second.name.c_str(), unread, unreadreviews); tv.itemex.state |= TVIS_BOLD; tv.itemex.stateMask = TVIS_BOLD; } @@ -551,7 +563,7 @@ tv.itemex.iImage = 4; tv.itemex.iSelectedImage = 4; } - else if (unread) + else if (unread || unreadreviews) { bRequiresUpdate = tv.itemex.iImage != 3; tv.itemex.iImage = 3; @@ -594,9 +606,10 @@ } else { - if (unread) + int unreadreviews = gReviewBoard.GetUnreadReviewsForUrl(it->second.url); + if (unread || unreadreviews) { - _stprintf_s(str, it->second.name.size()+10, _T("%s (%d)"), it->second.name.c_str(), unread); + _stprintf_s(str, it->second.name.size()+30, _T("%s (%d + %d)"), it->second.name.c_str(), unread, unreadreviews); tv.itemex.state = TVIS_BOLD; tv.itemex.stateMask = TVIS_BOLD; } @@ -619,7 +632,7 @@ tv.itemex.iImage = 4; tv.itemex.iSelectedImage = 4; } - else if (unread) + else if (unread || unreadreviews) { tv.itemex.iImage = 3; tv.itemex.iSelectedImage = 3; @@ -820,7 +833,7 @@ { HMENU hMenu = NULL; wstring tsvninstalled = CAppUtils::GetTSVNPath(); - if (tsvninstalled.empty()) + if (tsvninstalled.empty() || !m_commitsradio) hMenu = ::LoadMenu(hResource, MAKEINTRESOURCE(IDR_LISTPOPUP)); else hMenu = ::LoadMenu(hResource, MAKEINTRESOURCE(IDR_LISTPOPUPTSVN)); @@ -882,48 +895,101 @@ ListView_GetItem(m_hListControl, &item); if (item.state & LVIS_SELECTED) { - SCCSLogEntry * pLogEntry = (SCCSLogEntry*)item.lParam; - if (pLogEntry) + if(m_commitsradio) { - // set the entry as unread - if (pLogEntry->read) + SCCSLogEntry * pLogEntry = (SCCSLogEntry*)item.lParam; + if (pLogEntry) { - pLogEntry->read = false; - // refresh the name of the tree item to indicate the new - // number of unread log messages - // e.g. instead of 'TortoiseSVN (2)', show now 'TortoiseSVN (3)' - if (pRead->find(*(wstring*)itemex.lParam) != pRead->end()) + // set the entry as unread + if (pLogEntry->read) { - const CUrlInfo * uinfo = &pRead->find(*(wstring*)itemex.lParam)->second; - // count the number of unread messages - int unread = 0; - for (map::const_iterator it = uinfo->logentries.begin(); it != uinfo->logentries.end(); ++it) + pLogEntry->read = false; + // refresh the name of the tree item to indicate the new + // number of unread log messages + // e.g. instead of 'TortoiseSVN (2)', show now 'TortoiseSVN (3)' + if (pRead->find(*(wstring*)itemex.lParam) != pRead->end()) { - if (!it->second.read) - unread++; + const CUrlInfo * uinfo = &pRead->find(*(wstring*)itemex.lParam)->second; + // count the number of unread messages + int unread = 0; + for (map::const_iterator it = uinfo->logentries.begin(); it != uinfo->logentries.end(); ++it) + { + if (!it->second.read) + unread++; + } + WCHAR * str = new WCHAR[uinfo->name.size()+30]; + int unreadreviews = gReviewBoard.GetUnreadReviewsForUrl(uinfo->url); + if (unread || unreadreviews) + { + _stprintf_s(str, uinfo->name.size()+30, _T("%s (%d + %d)"), uinfo->name.c_str(), unread, unreadreviews); + itemex.state = TVIS_BOLD; + itemex.stateMask = TVIS_BOLD; + itemex.iImage = 3; + itemex.iSelectedImage = 3; + } + else + { + _stprintf_s(str, uinfo->name.size()+10, _T("%s"), uinfo->name.c_str()); + itemex.state = 0; + itemex.stateMask = TVIS_BOLD; + itemex.iImage = 2; + itemex.iSelectedImage = 2; + } + + itemex.pszText = str; + itemex.mask = TVIF_TEXT|TVIF_STATE|TVIF_IMAGE|TVIF_SELECTEDIMAGE; + m_refreshNeeded = true; + TreeView_SetItem(m_hTreeControl, &itemex); } - WCHAR * str = new WCHAR[uinfo->name.size()+10]; - if (unread) + } + } + } + else + { + ReviewBoardReview * pLogEntry = (ReviewBoardReview*)item.lParam; + if (pLogEntry) + { + // set the entry as unread + if (pLogEntry->read) + { + gReviewBoard.ReviewSetRead(pLogEntry->reviewid,false); + // refresh the name of the tree item to indicate the new + // number of unread log messages + // e.g. instead of 'TortoiseSVN (2)', show now 'TortoiseSVN (3)' + if (pRead->find(*(wstring*)itemex.lParam) != pRead->end()) { - _stprintf_s(str, uinfo->name.size()+10, _T("%s (%d)"), uinfo->name.c_str(), unread); - itemex.state = TVIS_BOLD; - itemex.stateMask = TVIS_BOLD; - itemex.iImage = 3; - itemex.iSelectedImage = 3; + const CUrlInfo * uinfo = &pRead->find(*(wstring*)itemex.lParam)->second; + // count the number of unread messages + int unread = 0; + for (map::const_iterator it = uinfo->logentries.begin(); it != uinfo->logentries.end(); ++it) + { + if (!it->second.read) + unread++; + } + WCHAR * str = new WCHAR[uinfo->name.size()+30]; + int unreadreviews = gReviewBoard.GetUnreadReviewsForUrl(uinfo->url); + if (unread || unreadreviews) + { + _stprintf_s(str, uinfo->name.size()+30, _T("%s (%d + %d)"), uinfo->name.c_str(), unread, unreadreviews); + itemex.state = TVIS_BOLD; + itemex.stateMask = TVIS_BOLD; + itemex.iImage = 3; + itemex.iSelectedImage = 3; + } + else + { + _stprintf_s(str, uinfo->name.size()+10, _T("%s"), uinfo->name.c_str()); + itemex.state = 0; + itemex.stateMask = TVIS_BOLD; + itemex.iImage = 2; + itemex.iSelectedImage = 2; + } + + itemex.pszText = str; + itemex.mask = TVIF_TEXT|TVIF_STATE|TVIF_IMAGE|TVIF_SELECTEDIMAGE; + m_refreshNeeded = true; + TreeView_SetItem(m_hTreeControl, &itemex); } - else - { - _stprintf_s(str, uinfo->name.size()+10, _T("%s"), uinfo->name.c_str()); - itemex.state = 0; - itemex.stateMask = TVIS_BOLD; - itemex.iImage = 2; - itemex.iSelectedImage = 2; - } - - itemex.pszText = str; - itemex.mask = TVIF_TEXT|TVIF_STATE|TVIF_IMAGE|TVIF_SELECTEDIMAGE; - m_refreshNeeded = true; - TreeView_SetItem(m_hTreeControl, &itemex); } } } @@ -1094,13 +1160,36 @@ if (pRead->find(*(wstring*)itemex.lParam) != pRead->end()) { const CUrlInfo * info = &pRead->find(*(wstring*)itemex.lParam)->second; - if ((info)&&(!info->webviewer.empty())) + if (!m_commitsradio || ((info)&&(!info->webviewer.empty()))) { // replace "%revision" with the new HEAD revision wstring tag(_T("%revision")); wstring commandline = info->webviewer; + wstring reviewurl; + if(!m_commitsradio) + { + // find the revision + LVITEM item = {0}; + int nItemCount = ListView_GetItemCount(m_hListControl); + for (int i=0; iUrl(); + break; + } + } + } + } wstring::iterator it_begin = search(commandline.begin(), commandline.end(), tag.begin(), tag.end()); - if (it_begin != commandline.end()) + if (m_commitsradio && it_begin != commandline.end()) { // find the revision LVITEM item = {0}; @@ -1143,8 +1232,19 @@ wstring::iterator it_end= it_begin + tag.size(); commandline.replace(it_begin, it_end, info->name); } - if (!commandline.empty()) + if (!m_commitsradio && !reviewurl.empty()) { + tag = _T("api/review-requests"); + it_begin = search(reviewurl.begin(), reviewurl.end(), tag.begin(), tag.end()); + if (it_begin != reviewurl.end()) + { + wstring::iterator it_end= it_begin + tag.size(); + reviewurl.replace(it_begin, it_end, TEXT("r")); + } + ShellExecute(*this, _T("open"), reviewurl.c_str(), NULL, NULL, SW_SHOWNORMAL); + } + else if (!commandline.empty()) + { ShellExecute(*this, _T("open"), commandline.c_str(), NULL, NULL, SW_SHOWNORMAL); } } @@ -1168,12 +1268,15 @@ { dlg.SetInfo(&pRead->find(*(wstring*)itemex.lParam)->second); wstring origurl = dlg.GetInfo()->url; + dlg.SetReviewMonitored(gReviewBoard.IsUrlMonitored(origurl)); if (id == ID_POPUP_ADDPROJECTWITHTEMPLATE) dlg.ClearForTemplate(); m_pURLInfos->ReleaseReadOnlyData(); if (dlg.DoModal(hResource, IDD_URLCONFIG, *this) == IDOK) { CUrlInfo * inf = dlg.GetInfo(); + bool monitor = dlg.GetReviewMonitored(); + bool needsaverev = false; if ((inf)&&inf->name.size()) { inf->errNr = 0; @@ -1182,9 +1285,25 @@ if ((inf) && (inf->url.size()) && ((origurl.compare(inf->url)) || (id == ID_MAIN_EDIT))) { if (id == ID_MAIN_EDIT) + { pWrite->erase(*(wstring*)itemex.lParam); + gReviewBoard.RemoveBranchToMonitor(origurl); + needsaverev = true; + } (*pWrite)[inf->url] = *inf; } + if(monitor && !gReviewBoard.IsUrlMonitored(inf->url)) + { + gReviewBoard.AddBranchToMonitor(inf->url); + needsaverev = true; + } + else if(!monitor && gReviewBoard.IsUrlMonitored(inf->url)) + { + gReviewBoard.RemoveBranchToMonitor(inf->url); + needsaverev = true; + } + if(needsaverev) + gReviewBoard.Save(); m_pURLInfos->Save(); m_pURLInfos->ReleaseWriteData(); RefreshURLTree(false); @@ -1291,52 +1410,106 @@ ListView_GetItem(m_hListControl, &item); if (item.state & LVIS_SELECTED) { - SCCSLogEntry * pLogEntry = (SCCSLogEntry*)item.lParam; - if (pLogEntry) + if(m_commitsradio) { - // get the info to put on the clipboard - _stprintf_s(tempBuf, 1024, _T("Revision: %ld\nAuthor: %s\nDate: %s\nMessage:\n"), - pLogEntry->revision, - pLogEntry->author.c_str(), - CAppUtils::ConvertDate(pLogEntry->date).c_str()); - sClipboardData += tempBuf; - sClipboardData += pLogEntry->message; - sClipboardData += _T("\n-------------------------------\n"); - // now add all changed paths, one path per line - for (map::const_iterator it = pLogEntry->m_changedPaths.begin(); it != pLogEntry->m_changedPaths.end(); ++it) + SCCSLogEntry * pLogEntry = (SCCSLogEntry*)item.lParam; + if (pLogEntry) { - // action - sClipboardData += it->second.action; - bool mods = false; - if ((it->second.text_modified == svn_tristate_true)||(it->second.props_modified == svn_tristate_true)) + // get the info to put on the clipboard + _stprintf_s(tempBuf, 1024, _T("Revision: %ld\nAuthor: %s\nDate: %s\nMessage:\n"), + pLogEntry->revision, + pLogEntry->author.c_str(), + CAppUtils::ConvertDate(pLogEntry->date).c_str()); + sClipboardData += tempBuf; + sClipboardData += pLogEntry->message; + sClipboardData += _T("\n-------------------------------\n"); + // now add all changed paths, one path per line + for (map::const_iterator it = pLogEntry->m_changedPaths.begin(); it != pLogEntry->m_changedPaths.end(); ++it) { - mods = true; + // action + sClipboardData += it->second.action; + bool mods = false; + if ((it->second.text_modified == svn_tristate_true)||(it->second.props_modified == svn_tristate_true)) + { + mods = true; + } + if (mods) + sClipboardData += L"("; + if (it->second.text_modified == svn_tristate_true) + sClipboardData += L"T"; + else if (mods) + sClipboardData += L" "; + if (it->second.props_modified == svn_tristate_true) + sClipboardData += L"P"; + else if (mods) + sClipboardData += L" "; + if (mods) + sClipboardData += L")"; + sClipboardData += _T(" : "); + sClipboardData += it->first; + sClipboardData += _T(" "); + if (!it->second.copyfrom_path.empty()) + { + sClipboardData += _T("(copied from: "); + sClipboardData += it->second.copyfrom_path; + sClipboardData += _T(", revision "); + _stprintf_s(tempBuf, 1024, _T("%ld)\n"), it->second.copyfrom_revision); + sClipboardData += wstring(tempBuf); + } + else + sClipboardData += _T("\n\n"); } - if (mods) - sClipboardData += L"("; - if (it->second.text_modified == svn_tristate_true) - sClipboardData += L"T"; - else if (mods) - sClipboardData += L" "; - if (it->second.props_modified == svn_tristate_true) - sClipboardData += L"P"; - else if (mods) - sClipboardData += L" "; - if (mods) - sClipboardData += L")"; - sClipboardData += _T(" : "); - sClipboardData += it->first; - sClipboardData += _T(" "); - if (!it->second.copyfrom_path.empty()) + } + } + else + { + ReviewBoardReview * pLogEntry = (ReviewBoardReview*)item.lParam; + if (pLogEntry) + { + // get the info to put on the clipboard + _stprintf_s(tempBuf, 1024, _T("Review id: %s\nAuthor: %s\nDate: %s\nMessage:\n"), + pLogEntry->Reviewid().c_str(), + pLogEntry->Author().c_str(), + pLogEntry->Date().c_str()); + sClipboardData += tempBuf; + sClipboardData += pLogEntry->Message(); + sClipboardData += _T("\n-------------------------------\n"); +/* // now add all changed paths, one path per line + for (map::const_iterator it = pLogEntry->m_changedPaths.begin(); it != pLogEntry->m_changedPaths.end(); ++it) { - sClipboardData += _T("(copied from: "); - sClipboardData += it->second.copyfrom_path; - sClipboardData += _T(", revision "); - _stprintf_s(tempBuf, 1024, _T("%ld)\n"), it->second.copyfrom_revision); - sClipboardData += wstring(tempBuf); - } - else - sClipboardData += _T("\n\n"); + // action + sClipboardData += it->second.action; + bool mods = false; + if ((it->second.text_modified == svn_tristate_true)||(it->second.props_modified == svn_tristate_true)) + { + mods = true; + } + if (mods) + sClipboardData += L"("; + if (it->second.text_modified == svn_tristate_true) + sClipboardData += L"T"; + else if (mods) + sClipboardData += L" "; + if (it->second.props_modified == svn_tristate_true) + sClipboardData += L"P"; + else if (mods) + sClipboardData += L" "; + if (mods) + sClipboardData += L")"; + sClipboardData += _T(" : "); + sClipboardData += it->first; + sClipboardData += _T(" "); + if (!it->second.copyfrom_path.empty()) + { + sClipboardData += _T("(copied from: "); + sClipboardData += it->second.copyfrom_path; + sClipboardData += _T(", revision "); + _stprintf_s(tempBuf, 1024, _T("%ld)\n"), it->second.copyfrom_revision); + sClipboardData += wstring(tempBuf); + } + else + sClipboardData += _T("\n\n"); + }*/ } } } @@ -1391,6 +1564,13 @@ } } break; + case IDC_RADIO2: + case IDC_RADIO3: + { + m_commitsradio = !m_commitsradio; + TreeItemSelected(m_hTreeControl, TreeView_GetSelection(m_hTreeControl)); + } + break; default: return 0; } @@ -1447,222 +1627,298 @@ ListView_GetItem(m_hListControl, &item); if (item.state & LVIS_SELECTED) { - SCCSLogEntry * pLogEntry = (SCCSLogEntry*)item.lParam; + if(m_commitsradio) + { + SCCSLogEntry * pLogEntry = (SCCSLogEntry*)item.lParam; - // Switch how the diff is done in SVN / Accurev - switch(pUrlInfo->sccs) { - default: - case CUrlInfo::SCCS_SVN: - { - // find the diff name - const CUrlInfo * pInfo = &pRead->find(*(wstring*)itemex.lParam)->second; - // in case the project name has 'path' chars in it, we have to remove those first - _stprintf_s(buf, 4096, _T("%s_%ld.diff"), CAppUtils::ConvertName(pInfo->name).c_str(), pLogEntry->revision); - wstring diffFileName = CAppUtils::GetDataDir(); - diffFileName += _T("\\"); - diffFileName += wstring(buf); - // construct a title for the diff viewer - _stprintf_s(buf, 4096, _T("%s, revision %ld"), pInfo->name.c_str(), pLogEntry->revision); - wstring title = wstring(buf); - // start the diff viewer - wstring cmd; - wstring tsvninstalled = CAppUtils::GetTSVNPath(); - wstring sVer = CAppUtils::GetVersionStringFromExe(tsvninstalled.c_str()); - if ((bUseTSVN)&&(!tsvninstalled.empty())&&(_tstoi(sVer.substr(3, 4).c_str()) > 4)) - { - // yes, we have TSVN installed - // call TortoiseProc to do the diff for us - cmd = _T("\""); - cmd += wstring(tsvninstalled); - cmd += _T("\" /command:diff /path:\""); - cmd += pInfo->url; - cmd += _T("\" /startrev:"); + // Switch how the diff is done in SVN / Accurev + switch(pUrlInfo->sccs) { + default: + case CUrlInfo::SCCS_SVN: + { + // find the diff name + const CUrlInfo * pInfo = &pRead->find(*(wstring*)itemex.lParam)->second; + // in case the project name has 'path' chars in it, we have to remove those first + _stprintf_s(buf, 4096, _T("%s_%ld.diff"), CAppUtils::ConvertName(pInfo->name).c_str(), pLogEntry->revision); + wstring diffFileName = CAppUtils::GetDataDir(); + diffFileName += _T("\\"); + diffFileName += wstring(buf); + // construct a title for the diff viewer + _stprintf_s(buf, 4096, _T("%s, revision %ld"), pInfo->name.c_str(), pLogEntry->revision); + wstring title = wstring(buf); + // start the diff viewer + wstring cmd; + wstring tsvninstalled = CAppUtils::GetTSVNPath(); + wstring sVer = CAppUtils::GetVersionStringFromExe(tsvninstalled.c_str()); + if ((bUseTSVN)&&(!tsvninstalled.empty())&&(_tstoi(sVer.substr(3, 4).c_str()) > 4)) + { + // yes, we have TSVN installed + // call TortoiseProc to do the diff for us + cmd = _T("\""); + cmd += wstring(tsvninstalled); + cmd += _T("\" /command:diff /path:\""); + cmd += pInfo->url; + cmd += _T("\" /startrev:"); - TCHAR numBuf[100] = {0}; - _stprintf_s(numBuf, 100, _T("%ld"), pLogEntry->revision-1); - cmd += numBuf; - cmd += _T(" /endrev:"); - _stprintf_s(numBuf, 100, _T("%ld"), pLogEntry->revision); - cmd += numBuf; - CAppUtils::LaunchApplication(cmd); - } - else - { - TCHAR apppath[4096]; - GetModuleFileName(NULL, apppath, 4096); - CRegStdString diffViewer = CRegStdString(_T("Software\\CommitMonitor\\DiffViewer")); - if (wstring(diffViewer).empty()) - { - cmd = apppath; - cmd += _T(" /patchfile:\""); + TCHAR numBuf[100] = {0}; + _stprintf_s(numBuf, 100, _T("%ld"), pLogEntry->revision-1); + cmd += numBuf; + cmd += _T(" /endrev:"); + _stprintf_s(numBuf, 100, _T("%ld"), pLogEntry->revision); + cmd += numBuf; + CAppUtils::LaunchApplication(cmd); } else { - cmd = (wstring)diffViewer; - cmd += _T(" \""); - } - cmd += diffFileName; - cmd += _T("\""); - if (wstring(diffViewer).empty()) - { - cmd += _T(" /title:\""); - cmd += title; - cmd += _T("\""); - } - // Check if the diff file exists. If it doesn't, we have to fetch - // the diff first - if (!PathFileExists(diffFileName.c_str())) - { - // fetch the diff - SVN svn; - svn.SetAuthInfo(pInfo->username, pInfo->password); - CProgressDlg progDlg; - svn.SetAndClearProgressInfo(&progDlg); - progDlg.SetTitle(_T("Fetching Diff")); - TCHAR dispbuf[MAX_PATH] = {0}; - _stprintf_s(dispbuf, MAX_PATH, _T("fetching diff of revision %ld"), pLogEntry->revision); - progDlg.SetLine(1, dispbuf); - progDlg.SetShowProgressBar(false); - progDlg.ShowModeless(*this); - progDlg.SetLine(1, dispbuf); - progDlg.SetProgress(3, 100); // set some dummy progress - CRegStdString diffParams = CRegStdString(_T("Software\\CommitMonitor\\DiffParameters")); - if (!svn.Diff(pInfo->url, pLogEntry->revision, pLogEntry->revision-1, pLogEntry->revision, true, true, false, diffParams, false, diffFileName, wstring())) + TCHAR apppath[4096]; + GetModuleFileName(NULL, apppath, 4096); + CRegStdString diffViewer = CRegStdString(_T("Software\\CommitMonitor\\DiffViewer")); + if (wstring(diffViewer).empty()) { - progDlg.Stop(); - if (svn.Err->apr_err != SVN_ERR_CANCELLED) - ::MessageBox(*this, svn.GetLastErrorMsg().c_str(), _T("CommitMonitor"), MB_ICONERROR); - DeleteFile(diffFileName.c_str()); + cmd = apppath; + cmd += _T(" /patchfile:\""); } else { - TRACE(_T("Diff fetched for %s, revision %ld\n"), pInfo->url.c_str(), pLogEntry->revision); - progDlg.Stop(); + cmd = (wstring)diffViewer; + cmd += _T(" \""); } + cmd += diffFileName; + cmd += _T("\""); + if (wstring(diffViewer).empty()) + { + cmd += _T(" /title:\""); + cmd += title; + cmd += _T("\""); + } + // Check if the diff file exists. If it doesn't, we have to fetch + // the diff first + if (!PathFileExists(diffFileName.c_str())) + { + // fetch the diff + SVN svn; + svn.SetAuthInfo(pInfo->username, pInfo->password); + CProgressDlg progDlg; + svn.SetAndClearProgressInfo(&progDlg); + progDlg.SetTitle(_T("Fetching Diff")); + TCHAR dispbuf[MAX_PATH] = {0}; + _stprintf_s(dispbuf, MAX_PATH, _T("fetching diff of revision %ld"), pLogEntry->revision); + progDlg.SetLine(1, dispbuf); + progDlg.SetShowProgressBar(false); + progDlg.ShowModeless(*this); + progDlg.SetLine(1, dispbuf); + progDlg.SetProgress(3, 100); // set some dummy progress + CRegStdString diffParams = CRegStdString(_T("Software\\CommitMonitor\\DiffParameters")); + if (!svn.Diff(pInfo->url, pLogEntry->revision, pLogEntry->revision-1, pLogEntry->revision, true, true, false, diffParams, false, diffFileName, wstring())) + { + progDlg.Stop(); + if (svn.Err->apr_err != SVN_ERR_CANCELLED) + ::MessageBox(*this, svn.GetLastErrorMsg().c_str(), _T("CommitMonitor"), MB_ICONERROR); + DeleteFile(diffFileName.c_str()); + } + else + { + TRACE(_T("Diff fetched for %s, revision %ld\n"), pInfo->url.c_str(), pLogEntry->revision); + progDlg.Stop(); + } + } + if (PathFileExists(diffFileName.c_str())) + CAppUtils::LaunchApplication(cmd); } - if (PathFileExists(diffFileName.c_str())) - CAppUtils::LaunchApplication(cmd); - } - } - break; + } + break; - case CUrlInfo::SCCS_ACCUREV: - { - /* Accurev 'diff' cannot be used as it mutex locks itself to only allow diffing of one - * file at a time... how typical. Therefore we 'pop' (get copies) of the correct versions - * of each file and then diff the directories :) - * TODO: Somehow hold onto and delete the temporary dirs when commit monitor is closed */ - CRegStdString accurevExe = CRegStdString(_T("Software\\CommitMonitor\\AccurevExe")); + case CUrlInfo::SCCS_ACCUREV: + { + /* Accurev 'diff' cannot be used as it mutex locks itself to only allow diffing of one + * file at a time... how typical. Therefore we 'pop' (get copies) of the correct versions + * of each file and then diff the directories :) + * TODO: Somehow hold onto and delete the temporary dirs when commit monitor is closed */ + CRegStdString accurevExe = CRegStdString(_T("Software\\CommitMonitor\\AccurevExe")); - wchar_t transactionNo[64]; - _itow_s(pLogEntry->revision, transactionNo, 10); + wchar_t transactionNo[64]; + _itow_s(pLogEntry->revision, transactionNo, 10); - wstring uuid; - CAppUtils::CreateUUIDString(uuid); + wstring uuid; + CAppUtils::CreateUUIDString(uuid); - wstring newTempPath = wstring(origTempPath); - newTempPath.append(_T("\\")); - newTempPath.append(uuid); - wstring sLatestRev(transactionNo); - wstring sBasisRev = _T("BASIS"); - wstring latestDir(newTempPath + _T("\\") + sLatestRev); - wstring basisDir(newTempPath + _T("\\") + sBasisRev); - CreateDirectory(newTempPath.c_str(), NULL); - CreateDirectory(latestDir.c_str(), NULL); - CreateDirectory(basisDir.c_str(), NULL); + wstring newTempPath = wstring(origTempPath); + newTempPath.append(_T("\\")); + newTempPath.append(uuid); + wstring sLatestRev(transactionNo); + wstring sBasisRev = _T("BASIS"); + wstring latestDir(newTempPath + _T("\\") + sLatestRev); + wstring basisDir(newTempPath + _T("\\") + sBasisRev); + CreateDirectory(newTempPath.c_str(), NULL); + CreateDirectory(latestDir.c_str(), NULL); + CreateDirectory(basisDir.c_str(), NULL); - // For each file that should be diffed - for (map::const_iterator it = pLogEntry->m_changedPaths.begin(); it != pLogEntry->m_changedPaths.end(); ++it) - { - // Parse the file and file revision from the stored URL - wstring rawPath = it->first; + // For each file that should be diffed + for (map::const_iterator it = pLogEntry->m_changedPaths.begin(); it != pLogEntry->m_changedPaths.end(); ++it) + { + // Parse the file and file revision from the stored URL + wstring rawPath = it->first; - int lastBracket = rawPath.rfind(L" ("); - rawPath.erase(lastBracket, wstring::npos); - int lastForwardSlash = rawPath.rfind(L"/"); - wstring sLatestAccuRevision(rawPath); - sLatestAccuRevision.erase(0, lastForwardSlash+1); - int iAccuRevision = _wtoi(sLatestAccuRevision.c_str()); - wchar_t basisRevisionNo[64]; - _itow_s(iAccuRevision-1, basisRevisionNo, 10); - wstring sBasisAccuRevision(basisRevisionNo); + int lastBracket = rawPath.rfind(L" ("); + rawPath.erase(lastBracket, wstring::npos); + int lastForwardSlash = rawPath.rfind(L"/"); + wstring sLatestAccuRevision(rawPath); + sLatestAccuRevision.erase(0, lastForwardSlash+1); + int iAccuRevision = _wtoi(sLatestAccuRevision.c_str()); + wchar_t basisRevisionNo[64]; + _itow_s(iAccuRevision-1, basisRevisionNo, 10); + wstring sBasisAccuRevision(basisRevisionNo); - wstring finalPath(rawPath); - int lastSpace = rawPath.rfind(L" "); - finalPath.erase(lastSpace, wstring::npos); + wstring finalPath(rawPath); + int lastSpace = rawPath.rfind(L" "); + finalPath.erase(lastSpace, wstring::npos); - // Can't diff unless there is a version to diff against :) - if (iAccuRevision >= 1) { + // Can't diff unless there is a version to diff against :) + if (iAccuRevision >= 1) { - // Check out the latest file - // Build the accurev command line - for (int i=0; i<2; i++) { - wstring accurevPopCmd; - wstring rev; - wstring dir; + // Check out the latest file + // Build the accurev command line + for (int i=0; i<2; i++) { + wstring accurevPopCmd; + wstring rev; + wstring dir; - switch (i) { - default: - case 0: - rev = sLatestAccuRevision; - dir = latestDir; - break; - case 1: - rev = sBasisAccuRevision; - dir = basisDir; - break; - } + switch (i) { + default: + case 0: + rev = sLatestAccuRevision; + dir = latestDir; + break; + case 1: + rev = sBasisAccuRevision; + dir = basisDir; + break; + } - /* If this is the basis version, and there is none, since the file was added, then break - * so we only check out the new version. This will then be shown in the directory compare :) */ - if ((i == 1) && (iAccuRevision == 1)) break; + /* If this is the basis version, and there is none, since the file was added, then break + * so we only check out the new version. This will then be shown in the directory compare :) */ + if ((i == 1) && (iAccuRevision == 1)) break; - accurevPopCmd.append(_T("\"")); - accurevPopCmd.append(wstring(accurevExe)); - accurevPopCmd.append(_T("\" pop -O -R -v ")); - accurevPopCmd.append(pUrlInfo->url); - accurevPopCmd.append(_T("/")); - accurevPopCmd.append(rev); - accurevPopCmd.append(_T(" -L \"")); - accurevPopCmd.append(dir); - accurevPopCmd.append(_T("\" \"")); - accurevPopCmd.append(finalPath); - accurevPopCmd.append(_T("\"")); + accurevPopCmd.append(_T("\"")); + accurevPopCmd.append(wstring(accurevExe)); + accurevPopCmd.append(_T("\" pop -O -R -v ")); + accurevPopCmd.append(pUrlInfo->url); + accurevPopCmd.append(_T("/")); + accurevPopCmd.append(rev); + accurevPopCmd.append(_T(" -L \"")); + accurevPopCmd.append(dir); + accurevPopCmd.append(_T("\" \"")); + accurevPopCmd.append(finalPath); + accurevPopCmd.append(_T("\"")); - // Run accurev to perform the pop command - CAppUtils::LaunchApplication(accurevPopCmd, true, true, true); + // Run accurev to perform the pop command + CAppUtils::LaunchApplication(accurevPopCmd, true, true, true); + } + + } } - } - } + CRegStdString diffCmd = CRegStdString(_T("Software\\CommitMonitor\\AccurevDiffCmd")); + wstring finalDiffCmd; - CRegStdString diffCmd = CRegStdString(_T("Software\\CommitMonitor\\AccurevDiffCmd")); - wstring finalDiffCmd; + // Build the final diff command + finalDiffCmd.append(wstring(diffCmd)); - // Build the final diff command - finalDiffCmd.append(wstring(diffCmd)); + // Find and replace "%OLD" + int pos = finalDiffCmd.find(_T("%OLD")); + finalDiffCmd.replace(pos, 4, sBasisRev, 0, sBasisRev.size()); - // Find and replace "%OLD" - int pos = finalDiffCmd.find(_T("%OLD")); - finalDiffCmd.replace(pos, 4, sBasisRev, 0, sBasisRev.size()); + // Find and replace "%NEW" + pos = finalDiffCmd.find(_T("%NEW")); + finalDiffCmd.replace(pos, 4, sLatestRev, 0, sLatestRev.size()); - // Find and replace "%NEW" - pos = finalDiffCmd.find(_T("%NEW")); - finalDiffCmd.replace(pos, 4, sLatestRev, 0, sLatestRev.size()); + // Find and replace "%1" + pos = finalDiffCmd.find(_T("%1")); + finalDiffCmd.replace(pos, 2, basisDir, 0, basisDir.size()); - // Find and replace "%1" - pos = finalDiffCmd.find(_T("%1")); - finalDiffCmd.replace(pos, 2, basisDir, 0, basisDir.size()); + // Find and replace "%2" + pos = finalDiffCmd.find(_T("%2")); + finalDiffCmd.replace(pos, 2, latestDir, 0, latestDir.size()); - // Find and replace "%2" - pos = finalDiffCmd.find(_T("%2")); - finalDiffCmd.replace(pos, 2, latestDir, 0, latestDir.size()); + // Run accurev to perform the diff command + CAppUtils::LaunchApplication(finalDiffCmd, true, false, false); + } + break; + } + } + else + { + ReviewBoardReview * pLogEntry = (ReviewBoardReview*)item.lParam; + + // Switch how the diff is done in SVN / Accurev + switch(pUrlInfo->sccs) { + default: + case CUrlInfo::SCCS_SVN: + { + // find the diff name + const CUrlInfo * pInfo = &pRead->find(*(wstring*)itemex.lParam)->second; + // in case the project name has 'path' chars in it, we have to remove those first + _stprintf_s(buf, 4096, _T("%s_review_%s.diff"), CAppUtils::ConvertName(pInfo->name).c_str(), pLogEntry->Reviewid().c_str()); + wstring diffFileName = CAppUtils::GetDataDir(); + diffFileName += _T("\\"); + diffFileName += wstring(buf); + // construct a title for the diff viewer + _stprintf_s(buf, 4096, _T("%s, review id %s"), pInfo->name.c_str(), pLogEntry->Reviewid().c_str()); + wstring title = wstring(buf); + // start the diff viewer + wstring cmd; - // Run accurev to perform the diff command - CAppUtils::LaunchApplication(finalDiffCmd, true, false, false); + TCHAR apppath[4096]; + GetModuleFileName(NULL, apppath, 4096); + CRegStdString diffViewer = CRegStdString(_T("Software\\CommitMonitor\\DiffViewer")); + if (wstring(diffViewer).empty()) + { + cmd = apppath; + cmd += _T(" /patchfile:\""); + } + else + { + cmd = (wstring)diffViewer; + cmd += _T(" \""); + } + cmd += diffFileName; + cmd += _T("\""); + if (wstring(diffViewer).empty()) + { + cmd += _T(" /title:\""); + cmd += title; + cmd += _T("\""); + } + // Check if the diff file exists. If it doesn't, we have to fetch + // the diff first + if (!PathFileExists(diffFileName.c_str())) + { + // fetch the diff + SVN svn; + svn.SetAuthInfo(pInfo->username, pInfo->password); + CProgressDlg progDlg; + svn.SetAndClearProgressInfo(&progDlg); + progDlg.SetTitle(_T("Fetching Diff")); + TCHAR dispbuf[MAX_PATH] = {0}; + _stprintf_s(dispbuf, MAX_PATH, _T("fetching diff of review id %s"), pLogEntry->Reviewid().c_str()); + progDlg.SetLine(1, dispbuf); + progDlg.SetShowProgressBar(false); + progDlg.ShowModeless(*this); + progDlg.SetLine(1, dispbuf); + progDlg.SetProgress(3, 100); // set some dummy progress + CRegStdString diffParams = CRegStdString(_T("Software\\CommitMonitor\\DiffParameters")); + ReviewBoardReviewUnifiedDiff unidiff; + unidiff.Open(pLogEntry->reviewid.c_str()); + unidiff.Save(diffFileName); + TRACE(_T("Diff fetched for %s, review id %s\n"), pInfo->url.c_str(), pLogEntry->Reviewid().c_str()); + progDlg.Stop(); + } + if (PathFileExists(diffFileName.c_str())) + CAppUtils::LaunchApplication(cmd); + } + break; } - break; } } } @@ -1699,7 +1955,7 @@ tv.hParent = FindParentTreeNode(it->first); tv.hInsertAfter = TVI_SORT; tv.itemex.mask = TVIF_TEXT|TVIF_PARAM|TVIF_STATE|TVIF_IMAGE|TVIF_SELECTEDIMAGE; - WCHAR * str = new WCHAR[it->second.name.size()+10]; + WCHAR * str = new WCHAR[it->second.name.size()+30]; // find out if there are some unread entries int unread = 0; for (map::const_iterator logit = it->second.logentries.begin(); logit != it->second.logentries.end(); ++logit) @@ -1707,9 +1963,10 @@ if (!logit->second.read) unread++; } - if (unread) + int unreadreviews = gReviewBoard.GetUnreadReviewsForUrl(it->second.url); + if (unread || unreadreviews) { - _stprintf_s(str, it->second.name.size()+10, _T("%s (%d)"), it->second.name.c_str(), unread); + _stprintf_s(str, it->second.name.size()+30, _T("%s (%d + %d)"), it->second.name.c_str(), unread, unreadreviews); tv.itemex.state = TVIS_BOLD; tv.itemex.stateMask = TVIS_BOLD; } @@ -1747,7 +2004,7 @@ HTREEITEM hItem = TreeView_InsertItem(m_hTreeControl, &tv); if ((!bShowLastUnread)&&(m_lastSelectedProject.compare(it->second.name) == 0)) tvToSel = hItem; - if ((unread)&&(tvToSel == 0)) + if ((unread || unreadreviews)&&(tvToSel == 0)) tvToSel = hItem; TreeView_Expand(m_hTreeControl, tv.hParent, TVE_EXPAND); delete [] str; @@ -1955,14 +2212,28 @@ lvc.mask = LVCF_TEXT; lvc.fmt = LVCFMT_LEFT; lvc.cx = -1; - lvc.pszText = _T("revision"); - ListView_InsertColumn(m_hListControl, 0, &lvc); - lvc.pszText = _T("date"); - ListView_InsertColumn(m_hListControl, 1, &lvc); - lvc.pszText = _T("author"); - ListView_InsertColumn(m_hListControl, 2, &lvc); - lvc.pszText = _T("log message"); - ListView_InsertColumn(m_hListControl, 3, &lvc); + if(m_commitsradio) + { + lvc.pszText = _T("revision"); + ListView_InsertColumn(m_hListControl, 0, &lvc); + lvc.pszText = _T("date"); + ListView_InsertColumn(m_hListControl, 1, &lvc); + lvc.pszText = _T("author"); + ListView_InsertColumn(m_hListControl, 2, &lvc); + lvc.pszText = _T("log message"); + ListView_InsertColumn(m_hListControl, 3, &lvc); + } + else + { + lvc.pszText = _T("review id"); + ListView_InsertColumn(m_hListControl, 0, &lvc); + lvc.pszText = _T("date"); + ListView_InsertColumn(m_hListControl, 1, &lvc); + lvc.pszText = _T("author"); + ListView_InsertColumn(m_hListControl, 2, &lvc); + lvc.pszText = _T("log message"); + ListView_InsertColumn(m_hListControl, 3, &lvc); + } LVITEM item = {0}; TCHAR buf[1024]; @@ -1984,99 +2255,203 @@ delete [] buffer; - for (map::const_iterator it = info->logentries.begin(); it != info->logentries.end(); ++it) + if(m_commitsradio) { - // only add entries that match the filter string - bool addEntry = true; - bool useFilter = filterstringlower.size() != 0; - bool bUseRegex = (filterstring.size() > 1)&&(filterstring[0] == '\\'); + for (map::const_iterator it = info->logentries.begin(); it != info->logentries.end(); ++it) + { + // only add entries that match the filter string + bool addEntry = true; + bool useFilter = filterstringlower.size() != 0; + bool bUseRegex = (filterstring.size() > 1)&&(filterstring[0] == '\\'); - if (useFilter) - { - if (bUseRegex) - { - try + if (useFilter) { - const tr1::wregex regCheck(filterstring.substr(1), tr1::regex_constants::icase | tr1::regex_constants::ECMAScript); + if (bUseRegex) + { + try + { + const tr1::wregex regCheck(filterstring.substr(1), tr1::regex_constants::icase | tr1::regex_constants::ECMAScript); - addEntry = tr1::regex_search(it->second.author, regCheck); + addEntry = tr1::regex_search(it->second.author, regCheck); + if (!addEntry) + { + addEntry = tr1::regex_search(it->second.message, regCheck); + if (!addEntry) + { + _stprintf_s(buf, 1024, _T("%ld"), it->first); + wstring s = wstring(buf); + addEntry = tr1::regex_search(s, regCheck); + } + } + } + catch (exception) + { + bUseRegex = false; + } + if (bNegateFilter) + addEntry = !addEntry; + } + if (!bUseRegex) + { + // search plain text + // note: \Q...\E doesn't seem to work with tr1 - it still + // throws an exception if the regex in between is not a valid regex :( + + wstring s = it->second.author; + std::transform(s.begin(), s.end(), s.begin(), std::tolower); + addEntry = s.find(filterstringlower) != wstring::npos; + if (!addEntry) { - addEntry = tr1::regex_search(it->second.message, regCheck); + s = it->second.message; + std::transform(s.begin(), s.end(), s.begin(), std::tolower); + addEntry = s.find(filterstringlower) != wstring::npos; if (!addEntry) { _stprintf_s(buf, 1024, _T("%ld"), it->first); - wstring s = wstring(buf); - addEntry = tr1::regex_search(s, regCheck); + s = buf; + addEntry = s.find(filterstringlower) != wstring::npos; } } + if (bNegateFilter) + addEntry = !addEntry; } - catch (exception) + } + + if (!addEntry) + continue; + + item.mask = LVIF_TEXT|LVIF_PARAM; + item.iItem = 0; + item.lParam = (LPARAM)&it->second; + _stprintf_s(buf, 1024, _T("%ld"), it->first); + item.pszText = buf; + ListView_InsertItem(m_hListControl, &item); + if (it->second.date) + _tcscpy_s(buf, 1024, CAppUtils::ConvertDate(it->second.date).c_str()); + else + _tcscpy_s(buf, 1024, _T("(no date)")); + ListView_SetItemText(m_hListControl, 0, 1, buf); + if (it->second.author.size()) + _tcscpy_s(buf, 1024, it->second.author.c_str()); + else + _tcscpy_s(buf, 1024, _T("(no author)")); + ListView_SetItemText(m_hListControl, 0, 2, buf); + wstring msg = it->second.message; + std::remove(msg.begin(), msg.end(), '\r'); + std::replace(msg.begin(), msg.end(), '\n', ' '); + std::replace(msg.begin(), msg.end(), '\t', ' '); + _tcsncpy_s(buf, 1024, msg.c_str(), 1023); + ListView_SetItemText(m_hListControl, 0, 3, buf); + + if ((iLastUnread < 0)&&(!it->second.read)) { - bUseRegex = false; + iLastUnread = 0; } - if (bNegateFilter) - addEntry = !addEntry; + if (iLastUnread >= 0) + iLastUnread++; } - if (!bUseRegex) + } + else + { + ReviewBoard::WR2Rmap vec; + gReviewBoard.GetReviewsForUrl(info->url,vec); + gReviewBoard.GetReadLock(); + for (ReviewBoard::WR2Rmap::const_iterator it = vec.begin(); it != vec.end(); ++it) { - // search plain text - // note: \Q...\E doesn't seem to work with tr1 - it still - // throws an exception if the regex in between is not a valid regex :( + // only add entries that match the filter string + bool addEntry = true; + bool useFilter = filterstringlower.size() != 0; + bool bUseRegex = (filterstring.size() > 1)&&(filterstring[0] == '\\'); - wstring s = it->second.author; - std::transform(s.begin(), s.end(), s.begin(), std::tolower); - addEntry = s.find(filterstringlower) != wstring::npos; + if (useFilter) + { + if (bUseRegex) + { + try + { + const tr1::wregex regCheck(filterstring.substr(1), tr1::regex_constants::icase | tr1::regex_constants::ECMAScript); - if (!addEntry) + addEntry = tr1::regex_search(it->second->Author(), regCheck); + if (!addEntry) + { + addEntry = tr1::regex_search(it->second->Message(), regCheck); + if (!addEntry) + { + _stprintf_s(buf, 1024, _T("%ld"), it->first); + wstring s = wstring(buf); + addEntry = tr1::regex_search(s, regCheck); + } + } + } + catch (exception) + { + bUseRegex = false; + } + if (bNegateFilter) + addEntry = !addEntry; + } + if (!bUseRegex) { - s = it->second.message; + // search plain text + // note: \Q...\E doesn't seem to work with tr1 - it still + // throws an exception if the regex in between is not a valid regex :( + + wstring s = it->second->Author(); std::transform(s.begin(), s.end(), s.begin(), std::tolower); addEntry = s.find(filterstringlower) != wstring::npos; + if (!addEntry) { - _stprintf_s(buf, 1024, _T("%ld"), it->first); - s = buf; - addEntry = s.find(filterstringlower) != wstring::npos; + s = it->second->Message(); + std::transform(s.begin(), s.end(), s.begin(), std::tolower); + addEntry = s.find(filterstringlower) != wstring::npos; + if (!addEntry) + { + _stprintf_s(buf, 1024, _T("%ld"), it->first); + s = buf; + addEntry = s.find(filterstringlower) != wstring::npos; + } } + if (bNegateFilter) + addEntry = !addEntry; } - if (bNegateFilter) - addEntry = !addEntry; - } - } + } - if (!addEntry) - continue; + if (!addEntry) + continue; - item.mask = LVIF_TEXT|LVIF_PARAM; - item.iItem = 0; - item.lParam = (LPARAM)&it->second; - _stprintf_s(buf, 1024, _T("%ld"), it->first); - item.pszText = buf; - ListView_InsertItem(m_hListControl, &item); - if (it->second.date) - _tcscpy_s(buf, 1024, CAppUtils::ConvertDate(it->second.date).c_str()); - else - _tcscpy_s(buf, 1024, _T("(no date)")); - ListView_SetItemText(m_hListControl, 0, 1, buf); - if (it->second.author.size()) - _tcscpy_s(buf, 1024, it->second.author.c_str()); - else - _tcscpy_s(buf, 1024, _T("(no author)")); - ListView_SetItemText(m_hListControl, 0, 2, buf); - wstring msg = it->second.message; - std::remove(msg.begin(), msg.end(), '\r'); - std::replace(msg.begin(), msg.end(), '\n', ' '); - std::replace(msg.begin(), msg.end(), '\t', ' '); - _tcsncpy_s(buf, 1024, msg.c_str(), 1023); - ListView_SetItemText(m_hListControl, 0, 3, buf); + item.mask = LVIF_TEXT|LVIF_PARAM; + item.iItem = 0; + item.lParam = (LPARAM)it->second; + _stprintf_s(buf, 1024, _T("%s"), it->first.c_str()); + item.pszText = buf; + ListView_InsertItem(m_hListControl, &item); + if (it->second->Date().size()) + _tcscpy_s(buf, 1024, it->second->Date().c_str()); + else + _tcscpy_s(buf, 1024, _T("(no date)")); + ListView_SetItemText(m_hListControl, 0, 1, buf); + if (it->second->Author().size()) + _tcscpy_s(buf, 1024, it->second->Author().c_str()); + else + _tcscpy_s(buf, 1024, _T("(no author)")); + ListView_SetItemText(m_hListControl, 0, 2, buf); + wstring msg = it->second->Message(); + std::remove(msg.begin(), msg.end(), '\r'); + std::replace(msg.begin(), msg.end(), '\n', ' '); + std::replace(msg.begin(), msg.end(), '\t', ' '); + _tcsncpy_s(buf, 1024, msg.c_str(), 1023); + ListView_SetItemText(m_hListControl, 0, 3, buf); - if ((iLastUnread < 0)&&(!it->second.read)) - { - iLastUnread = 0; + if ((iLastUnread < 0)&&(!it->second->read)) + { + iLastUnread = 0; + } + if (iLastUnread >= 0) + iLastUnread++; } - if (iLastUnread >= 0) - iLastUnread++; + gReviewBoard.ReleaseReadOnlyData(); } ListView_SetSelectionMark(m_hListControl, selMark); m_bBlockListCtrlUI = false; @@ -2110,6 +2485,7 @@ bChanged = true; it->second.read = true; } + gReviewBoard.ReviewsSetRead(info->url); // refresh the name of the tree item to indicate the new // number of unread log messages WCHAR * str = new WCHAR[info->name.size()+10]; @@ -2208,124 +2584,246 @@ return; if ((lpNMListView->uOldState ^ lpNMListView->uNewState) & LVIS_SELECTED) { - const map * pRead = m_pURLInfos->GetReadOnlyData(); LVITEM item = {0}; item.mask = LVIF_PARAM; item.iItem = lpNMListView->iItem; ListView_GetItem(m_hListControl, &item); - SCCSLogEntry * pLogEntry = (SCCSLogEntry*)item.lParam; - if (pLogEntry) + if(m_commitsradio) { - HTREEITEM hSelectedItem = TreeView_GetSelection(m_hTreeControl); - // get the url this entry refers to - TVITEMEX itemex = {0}; - itemex.hItem = hSelectedItem; - itemex.mask = TVIF_PARAM; - TreeView_GetItem(m_hTreeControl, &itemex); - if (itemex.lParam == 0) + const map * pRead = m_pURLInfos->GetReadOnlyData(); + SCCSLogEntry * pLogEntry = (SCCSLogEntry*)item.lParam; + if (pLogEntry) { - m_pURLInfos->ReleaseReadOnlyData(); - return; - } - // set the entry as read - if ((!pLogEntry->read)&&(lpNMListView->uNewState & LVIS_SELECTED)) - { - pLogEntry->read = true; - // refresh the name of the tree item to indicate the new - // number of unread log messages - // e.g. instead of 'TortoiseSVN (3)', show now 'TortoiseSVN (2)' - if (pRead->find(*(wstring*)itemex.lParam) != pRead->end()) + HTREEITEM hSelectedItem = TreeView_GetSelection(m_hTreeControl); + // get the url this entry refers to + TVITEMEX itemex = {0}; + itemex.hItem = hSelectedItem; + itemex.mask = TVIF_PARAM; + TreeView_GetItem(m_hTreeControl, &itemex); + if (itemex.lParam == 0) { - const CUrlInfo * uinfo = &pRead->find(*(wstring*)itemex.lParam)->second; - // count the number of unread messages - int unread = 0; - for (map::const_iterator it = uinfo->logentries.begin(); it != uinfo->logentries.end(); ++it) + m_pURLInfos->ReleaseReadOnlyData(); + return; + } + // set the entry as read + if ((!pLogEntry->read)&&(lpNMListView->uNewState & LVIS_SELECTED)) + { + pLogEntry->read = true; + // refresh the name of the tree item to indicate the new + // number of unread log messages + // e.g. instead of 'TortoiseSVN (3)', show now 'TortoiseSVN (2)' + if (pRead->find(*(wstring*)itemex.lParam) != pRead->end()) { - if (!it->second.read) - unread++; + const CUrlInfo * uinfo = &pRead->find(*(wstring*)itemex.lParam)->second; + // count the number of unread messages + int unread = 0; + for (map::const_iterator it = uinfo->logentries.begin(); it != uinfo->logentries.end(); ++it) + { + if (!it->second.read) + unread++; + } + WCHAR * str = new WCHAR[uinfo->name.size()+30]; + int unreadreviews = gReviewBoard.GetUnreadReviewsForUrl(uinfo->url); + if (unread || unreadreviews) + { + _stprintf_s(str, uinfo->name.size()+30, _T("%s (%d + %d)"), uinfo->name.c_str(), unread, unreadreviews); + itemex.state = TVIS_BOLD; + itemex.stateMask = TVIS_BOLD; + itemex.iImage = 3; + itemex.iSelectedImage = 3; + } + else + { + _stprintf_s(str, uinfo->name.size()+30, _T("%s"), uinfo->name.c_str()); + itemex.state = 0; + itemex.stateMask = TVIS_BOLD; + itemex.iImage = 2; + itemex.iSelectedImage = 2; + } + + itemex.pszText = str; + itemex.mask = TVIF_TEXT|TVIF_STATE|TVIF_IMAGE|TVIF_SELECTEDIMAGE; + TreeView_SetItem(m_hTreeControl, &itemex); } - WCHAR * str = new WCHAR[uinfo->name.size()+10]; - if (unread) + // the icon in the system tray needs to be changed back + // to 'normal' + ::SendMessage(m_hParent, COMMITMONITOR_CHANGEDINFO, (WPARAM)false, (LPARAM)0); + } + TCHAR buf[1024]; + wstring msg; + if (ListView_GetSelectedCount(m_hListControl) > 1) + { + msg = _T("multiple log entries selected. Info for the last selected one:\n-------------------------------\n\n"); + } + msg += pLogEntry->message.c_str(); + msg += _T("\n\n-------------------------------\n"); + // now add all changed paths, one path per line + for (map::const_iterator it = pLogEntry->m_changedPaths.begin(); it != pLogEntry->m_changedPaths.end(); ++it) + { + // action + msg += it->second.action; + bool mods = false; + if ((it->second.text_modified == svn_tristate_true)||(it->second.props_modified == svn_tristate_true)) { - _stprintf_s(str, uinfo->name.size()+10, _T("%s (%d)"), uinfo->name.c_str(), unread); - itemex.state = TVIS_BOLD; - itemex.stateMask = TVIS_BOLD; - itemex.iImage = 3; - itemex.iSelectedImage = 3; + mods = true; } - else + if (mods) + msg += L"("; + if (it->second.text_modified == svn_tristate_true) + msg += L"T"; + else if (mods) + msg += L" "; + if (it->second.props_modified == svn_tristate_true) + msg += L"P"; + else if (mods) + msg += L" "; + if (mods) + msg += L")"; + msg += _T(" : "); + msg += it->first; + msg += _T(" "); + if (!it->second.copyfrom_path.empty()) { - _stprintf_s(str, uinfo->name.size()+10, _T("%s"), uinfo->name.c_str()); - itemex.state = 0; - itemex.stateMask = TVIS_BOLD; - itemex.iImage = 2; - itemex.iSelectedImage = 2; + msg += _T("(copied from: "); + msg += it->second.copyfrom_path; + msg += _T(", revision "); + _stprintf_s(buf, 1024, _T("%ld)\n"), it->second.copyfrom_revision); + msg += wstring(buf); } + else + msg += _T("\n"); + } - itemex.pszText = str; - itemex.mask = TVIF_TEXT|TVIF_STATE|TVIF_IMAGE|TVIF_SELECTEDIMAGE; - TreeView_SetItem(m_hTreeControl, &itemex); - } - // the icon in the system tray needs to be changed back - // to 'normal' - ::SendMessage(m_hParent, COMMITMONITOR_CHANGEDINFO, (WPARAM)false, (LPARAM)0); + CAppUtils::SearchReplace(msg, _T("\n"), _T("\r\n")); + SetWindowText(m_hLogMsgControl, msg.c_str()); + + // find the diff name + _stprintf_s(buf, 1024, _T("%s_%ld.diff"), pRead->find(*(wstring*)itemex.lParam)->second.name.c_str(), pLogEntry->revision); + wstring diffFileName = CAppUtils::GetDataDir(); + diffFileName += _T("\\"); + diffFileName += wstring(buf); + SendMessage(m_hwndToolbar, TB_ENABLEBUTTON, ID_MAIN_SHOWDIFFCHOOSE, MAKELONG(true, 0)); } - TCHAR buf[1024]; - wstring msg; - if (ListView_GetSelectedCount(m_hListControl) > 1) + m_pURLInfos->ReleaseReadOnlyData(); + } + else + { + ReviewBoardReview * pLogEntry = (ReviewBoardReview*)item.lParam; // conversion loses qualifiers, cast to non-const + const map * pRead = m_pURLInfos->GetReadOnlyData(); + if (pLogEntry) { - msg = _T("multiple log entries selected. Info for the last selected one:\n-------------------------------\n\n"); - } - msg += pLogEntry->message.c_str(); - msg += _T("\n\n-------------------------------\n"); - // now add all changed paths, one path per line - for (map::const_iterator it = pLogEntry->m_changedPaths.begin(); it != pLogEntry->m_changedPaths.end(); ++it) - { - // action - msg += it->second.action; - bool mods = false; - if ((it->second.text_modified == svn_tristate_true)||(it->second.props_modified == svn_tristate_true)) + HTREEITEM hSelectedItem = TreeView_GetSelection(m_hTreeControl); + // get the url this entry refers to + TVITEMEX itemex = {0}; + itemex.hItem = hSelectedItem; + itemex.mask = TVIF_PARAM; + TreeView_GetItem(m_hTreeControl, &itemex); + if (itemex.lParam == 0) { - mods = true; + m_pURLInfos->ReleaseReadOnlyData(); + return; } - if (mods) - msg += L"("; - if (it->second.text_modified == svn_tristate_true) - msg += L"T"; - else if (mods) - msg += L" "; - if (it->second.props_modified == svn_tristate_true) - msg += L"P"; - else if (mods) - msg += L" "; - if (mods) - msg += L")"; - msg += _T(" : "); - msg += it->first; - msg += _T(" "); - if (!it->second.copyfrom_path.empty()) + // set the entry as read + if ((!pLogEntry->read)&&(lpNMListView->uNewState & LVIS_SELECTED)) { - msg += _T("(copied from: "); - msg += it->second.copyfrom_path; - msg += _T(", revision "); - _stprintf_s(buf, 1024, _T("%ld)\n"), it->second.copyfrom_revision); - msg += wstring(buf); + gReviewBoard.ReviewSetRead(pLogEntry->reviewid); + // refresh the name of the tree item to indicate the new + // number of unread log messages + // e.g. instead of 'TortoiseSVN (3)', show now 'TortoiseSVN (2)' + if (pRead->find(*(wstring*)itemex.lParam) != pRead->end()) + { + const CUrlInfo * uinfo = &pRead->find(*(wstring*)itemex.lParam)->second; + // count the number of unread messages + int unread = 0; + for (map::const_iterator it = uinfo->logentries.begin(); it != uinfo->logentries.end(); ++it) + { + if (!it->second.read) + unread++; + } + WCHAR * str = new WCHAR[uinfo->name.size()+30]; + int unreadreviews = gReviewBoard.GetUnreadReviewsForUrl(uinfo->url); + if (unread || unreadreviews) + { + _stprintf_s(str, uinfo->name.size()+30, _T("%s (%d + %d)"), uinfo->name.c_str(), unread, unreadreviews); + itemex.state = TVIS_BOLD; + itemex.stateMask = TVIS_BOLD; + itemex.iImage = 3; + itemex.iSelectedImage = 3; + } + else + { + _stprintf_s(str, uinfo->name.size()+30, _T("%s"), uinfo->name.c_str()); + itemex.state = 0; + itemex.stateMask = TVIS_BOLD; + itemex.iImage = 2; + itemex.iSelectedImage = 2; + } + + itemex.pszText = str; + itemex.mask = TVIF_TEXT|TVIF_STATE|TVIF_IMAGE|TVIF_SELECTEDIMAGE; + TreeView_SetItem(m_hTreeControl, &itemex); + } + // the icon in the system tray needs to be changed back + // to 'normal' + ::SendMessage(m_hParent, COMMITMONITOR_CHANGEDINFO, (WPARAM)false, (LPARAM)0); } - else - msg += _T("\n"); + TCHAR buf[1024]; + wstring msg; + if (ListView_GetSelectedCount(m_hListControl) > 1) + { + msg = _T("multiple log entries selected. Info for the last selected one:\n-------------------------------\n\n"); + } + msg += pLogEntry->Message().c_str(); + msg += _T("\n\n-------------------------------\n"); +/* // now add all changed paths, one path per line + for (map::const_iterator it = pLogEntry->m_changedPaths.begin(); it != pLogEntry->m_changedPaths.end(); ++it) + { + // action + msg += it->second.action; + bool mods = false; + if ((it->second.text_modified == svn_tristate_true)||(it->second.props_modified == svn_tristate_true)) + { + mods = true; + } + if (mods) + msg += L"("; + if (it->second.text_modified == svn_tristate_true) + msg += L"T"; + else if (mods) + msg += L" "; + if (it->second.props_modified == svn_tristate_true) + msg += L"P"; + else if (mods) + msg += L" "; + if (mods) + msg += L")"; + msg += _T(" : "); + msg += it->first; + msg += _T(" "); + if (!it->second.copyfrom_path.empty()) + { + msg += _T("(copied from: "); + msg += it->second.copyfrom_path; + msg += _T(", revision "); + _stprintf_s(buf, 1024, _T("%ld)\n"), it->second.copyfrom_revision); + msg += wstring(buf); + } + else + msg += _T("\n"); + } + */ + CAppUtils::SearchReplace(msg, _T("\n"), _T("\r\n")); + SetWindowText(m_hLogMsgControl, msg.c_str()); + + // find the diff name + _stprintf_s(buf, 1024, _T("%s_review_%s.diff"), pRead->find(*(wstring*)itemex.lParam)->second.name.c_str(), pLogEntry->Reviewid().c_str()); + wstring diffFileName = CAppUtils::GetDataDir(); + diffFileName += _T("\\"); + diffFileName += wstring(buf); + SendMessage(m_hwndToolbar, TB_ENABLEBUTTON, ID_MAIN_SHOWDIFFCHOOSE, MAKELONG(true, 0)); } - - CAppUtils::SearchReplace(msg, _T("\n"), _T("\r\n")); - SetWindowText(m_hLogMsgControl, msg.c_str()); - - // find the diff name - _stprintf_s(buf, 1024, _T("%s_%ld.diff"), pRead->find(*(wstring*)itemex.lParam)->second.name.c_str(), pLogEntry->revision); - wstring diffFileName = CAppUtils::GetDataDir(); - diffFileName += _T("\\"); - diffFileName += wstring(buf); - SendMessage(m_hwndToolbar, TB_ENABLEBUTTON, ID_MAIN_SHOWDIFFCHOOSE, MAKELONG(true, 0)); + m_pURLInfos->ReleaseReadOnlyData(); } - m_pURLInfos->ReleaseReadOnlyData(); } } @@ -2371,14 +2869,29 @@ break; case CDDS_ITEMPREPAINT: { - SCCSLogEntry * pLogEntry = (SCCSLogEntry*)lpNMCustomDraw->nmcd.lItemlParam; + if(m_commitsradio) + { + SCCSLogEntry * pLogEntry = (SCCSLogEntry*)lpNMCustomDraw->nmcd.lItemlParam; - if (!pLogEntry->read) + if (!pLogEntry->read) + { + SelectObject(lpNMCustomDraw->nmcd.hdc, m_boldFont); + // We changed the font, so we're returning CDRF_NEWFONT. This + // tells the control to recalculate the extent of the text. + result = CDRF_NEWFONT; + } + } + else { - SelectObject(lpNMCustomDraw->nmcd.hdc, m_boldFont); - // We changed the font, so we're returning CDRF_NEWFONT. This - // tells the control to recalculate the extent of the text. - result = CDRF_NEWFONT; + ReviewBoardReview * pLogEntry = (ReviewBoardReview*)lpNMCustomDraw->nmcd.lItemlParam; + + if (!pLogEntry->read) + { + SelectObject(lpNMCustomDraw->nmcd.hdc, m_boldFont); + // We changed the font, so we're returning CDRF_NEWFONT. This + // tells the control to recalculate the extent of the text. + result = CDRF_NEWFONT; + } } } break; @@ -2504,18 +3017,40 @@ ListView_GetItem(m_hListControl, &item); if (item.state & LVIS_SELECTED) { - SCCSLogEntry * pLogEntry = (SCCSLogEntry*)item.lParam; - // find the diff name - _stprintf_s(buf, 4096, _T("%s_%ld.diff"), pWrite->find(*(wstring*)itemex.lParam)->second.name.c_str(), pLogEntry->revision); - wstring diffFileName = CAppUtils::GetDataDir(); - diffFileName += _T("\\"); - diffFileName += wstring(buf); - DeleteFile(diffFileName.c_str()); + if(m_commitsradio) + { + SCCSLogEntry * pLogEntry = (SCCSLogEntry*)item.lParam; + // find the diff name + _stprintf_s(buf, 4096, _T("%s_%ld.diff"), pWrite->find(*(wstring*)itemex.lParam)->second.name.c_str(), pLogEntry->revision); + wstring diffFileName = CAppUtils::GetDataDir(); + diffFileName += _T("\\"); + diffFileName += wstring(buf); + DeleteFile(diffFileName.c_str()); - pWrite->find((*(wstring*)itemex.lParam))->second.logentries.erase(pLogEntry->revision); - ListView_DeleteItem(m_hListControl, i); - if (nFirstDeleted < 0) - nFirstDeleted = i; + pWrite->find((*(wstring*)itemex.lParam))->second.logentries.erase(pLogEntry->revision); + ListView_DeleteItem(m_hListControl, i); + if (nFirstDeleted < 0) + nFirstDeleted = i; + } + else + { + ReviewBoardReview * pLogEntry = (ReviewBoardReview*)item.lParam; + if(pLogEntry) + { + // find the diff name + _stprintf_s(buf, 1024, _T("%s_review_%s.diff"), pWrite->find(*(wstring*)itemex.lParam)->second.name.c_str(), pLogEntry->Reviewid().c_str()); + wstring diffFileName = CAppUtils::GetDataDir(); + diffFileName += _T("\\"); + diffFileName += wstring(buf); + DeleteFile(diffFileName.c_str()); + + gReviewBoard.DeleteReview(pLogEntry->reviewid); + pLogEntry = 0; + ListView_DeleteItem(m_hListControl, i); + if (nFirstDeleted < 0) + nFirstDeleted = i; + } + } } else ++i; @@ -2561,7 +3096,9 @@ HDWP hdwp = BeginDeferWindowPos(9); hdwp = DeferWindowPos(hdwp, m_hwndToolbar, *this, 0, 0, width, m_topmarg, SWP_NOZORDER|SWP_NOACTIVATE); hdwp = DeferWindowPos(hdwp, hFilterLabel, *this, m_xSliderPos+4, m_topmarg+5, FILTERLABELWIDTH, 12, SWP_NOZORDER|SWP_NOACTIVATE|SWP_FRAMECHANGED); - hdwp = DeferWindowPos(hdwp, m_hFilterControl, *this, m_xSliderPos+4+FILTERLABELWIDTH, m_topmarg+1, width-m_xSliderPos-4-FILTERLABELWIDTH-4, FILTERBOXHEIGHT-1, SWP_NOZORDER|SWP_NOACTIVATE); + hdwp = DeferWindowPos(hdwp, m_hFilterControl, *this, m_xSliderPos+4+FILTERLABELWIDTH, m_topmarg+1, width-m_xSliderPos-4-FILTERLABELWIDTH-4 - m_radiossize, FILTERBOXHEIGHT-1, SWP_NOZORDER|SWP_NOACTIVATE); + hdwp = DeferWindowPos(hdwp, m_hCommitsRadio, *this, width - m_radiossize + 4, m_topmarg+1, 80, FILTERBOXHEIGHT-1, SWP_NOZORDER|SWP_NOACTIVATE); + hdwp = DeferWindowPos(hdwp, m_hReviewsRadio, *this, width - m_radiossize + 84, m_topmarg+1, 80, FILTERBOXHEIGHT-1, SWP_NOZORDER|SWP_NOACTIVATE); hdwp = DeferWindowPos(hdwp, m_hTreeControl, *this, 0, m_topmarg, m_xSliderPos, height-m_topmarg-m_bottommarg+FILTERBOXHEIGHT+4, SWP_NOZORDER|SWP_NOACTIVATE); hdwp = DeferWindowPos(hdwp, m_hListControl, *this, m_xSliderPos+4, m_topmarg+FILTERBOXHEIGHT, width-m_xSliderPos-4, m_ySliderPos-m_topmarg+4, SWP_NOZORDER|SWP_NOACTIVATE); hdwp = DeferWindowPos(hdwp, m_hLogMsgControl, *this, m_xSliderPos+4, m_ySliderPos+8+FILTERBOXHEIGHT, width-m_xSliderPos-4, height-m_bottommarg-m_ySliderPos-4, SWP_NOZORDER|SWP_NOACTIVATE); @@ -2872,9 +3409,13 @@ loglist.left, treelist.top+5, 0, 0, SWP_NOACTIVATE|SWP_NOOWNERZORDER|SWP_NOZORDER|SWP_NOSIZE); hdwp = DeferWindowPos(hdwp, m_hFilterControl, NULL, - loglist.left+FILTERLABELWIDTH, treelist.top, loglist.right-FILTERLABELWIDTH, FILTERBOXHEIGHT, + loglist.left+FILTERLABELWIDTH, treelist.top, loglist.right-loglist.left-FILTERLABELWIDTH - 160 - 4, FILTERBOXHEIGHT, SWP_NOACTIVATE|SWP_NOOWNERZORDER|SWP_NOZORDER); + hdwp = DeferWindowPos(hdwp, m_hCommitsRadio, NULL, loglist.right - m_radiossize + 4, treelist.top + 1, 80, FILTERBOXHEIGHT-1, SWP_NOACTIVATE|SWP_NOOWNERZORDER|SWP_NOZORDER); + hdwp = DeferWindowPos(hdwp, m_hReviewsRadio, NULL, loglist.right - m_radiossize + 84, treelist.top + 1, 80, FILTERBOXHEIGHT-1, SWP_NOACTIVATE|SWP_NOOWNERZORDER|SWP_NOZORDER); + + hdwp = DeferWindowPos(hdwp, m_hListControl, NULL, loglist.left, treelist.top+FILTERBOXHEIGHT, loglist.right-loglist.left, loglist.bottom-treelist.top-FILTERBOXHEIGHT, SWP_NOACTIVATE|SWP_NOOWNERZORDER|SWP_NOZORDER); Index: src/MainDlg.h =================================================================== --- src/MainDlg.h (revision 605) +++ src/MainDlg.h (working copy) @@ -97,6 +97,8 @@ HWND m_hListControl; HWND m_hLogMsgControl; HWND m_hFilterControl; + HWND m_hCommitsRadio; + HWND m_hReviewsRadio; HFONT m_font; CListCtrl m_ListCtrl; @@ -112,6 +114,8 @@ LONG m_xSliderPos; LONG m_ySliderPos; LONG m_bottommarg; + LONG m_radiossize; + bool m_commitsradio; HFONT m_boldFont; Index: src/Resources/CommitMonitor.rc =================================================================== --- src/Resources/CommitMonitor.rc (revision 605) +++ src/Resources/CommitMonitor.rc (working copy) @@ -21,7 +21,7 @@ #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_NEU) LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -#pragma code_page(1252) +#pragma code_page(1250) ///////////////////////////////////////////////////////////////////////////// // @@ -103,8 +103,10 @@ LTEXT "",IDC_INFOLABEL,0,222,303,16 PUSHBUTTON "&Exit",IDC_EXIT,359,222,50,14 DEFPUSHBUTTON "&Hide",IDOK,304,222,50,14 - EDITTEXT IDC_FILTERSTRING,186,32,225,14,ES_AUTOHSCROLL + EDITTEXT IDC_FILTERSTRING,186,32,141,14,ES_AUTOHSCROLL LTEXT "Filter:",IDC_FILTERLABEL,158,34,26,8 + CONTROL "Commits",IDC_RADIO2,"Button",BS_AUTORADIOBUTTON,331,35,40,10 + CONTROL "Reviews",IDC_RADIO3,"Button",BS_AUTORADIOBUTTON,371,35,40,10 END IDD_URLCONFIG DIALOGEX 0, 0, 336, 322 @@ -144,6 +146,7 @@ COMBOBOX IDC_SCCSCOMBO,89,23,110,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP LTEXT "Accurev Repository:",IDC_REPOLABEL,14,70,66,8 EDITTEXT IDC_ACCUREVREPO,89,67,233,14,ES_AUTOHSCROLL + CONTROL "Check review requests",IDC_CHECKREVIEWS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,223,91,89,10 END IDD_OPTIONS DIALOGEX 0, 0, 308, 293 @@ -270,6 +273,10 @@ BOTTOMMARGIN, 286 END + IDD_FINDBAR, DIALOG + BEGIN + END + IDD_NEWERNOTIFYDLG, DIALOG BEGIN LEFTMARGIN, 7 @@ -365,7 +372,7 @@ POPUP "Popup" BEGIN MENUITEM "&Show Unified Diff", ID_MAIN_SHOWDIFF - MENUITEM "&Open WebViewer", ID_POPUP_OPENWEBVIEWER, INACTIVE + MENUITEM "&Open WebViewer", ID_POPUP_OPENWEBVIEWER MENUITEM "&Remove", ID_MAIN_REMOVE MENUITEM "&Mark as unread", ID_POPUP_MARKASUNREAD MENUITEM "&Copy to clipboard", ID_MAIN_COPY Index: src/Resources/resource.h =================================================================== --- src/Resources/resource.h (revision 605) +++ src/Resources/resource.h (working copy) @@ -104,6 +104,7 @@ #define IDC_NUMLOGS 1040 #define IDC_CHECK2 1041 #define IDC_NOTIFYCONNECTERROR 1041 +#define IDC_CHECKREVIEWS 1041 #define IDC_FILTERSTRING 1042 #define IDC_FILTERLABEL 1043 #define IDC_MAXLOGENTRIES 1044 @@ -130,6 +131,8 @@ #define IDC_ACCUDIFFCMDLABEL 1064 #define IDC_SCCSCOMBO 1065 #define IDC_EDIT2 1069 +#define IDC_RADIO2 1072 +#define IDC_RADIO3 1074 #define ID_FILE_OPENCOMMITMONITOR 32771 #define ID_MAIN_OPENCOMMITMONITOR 32772 #define ID_POPUP_OPENCOMMITMONITOR 32773 @@ -181,7 +184,7 @@ #define _APS_NO_MFC 1 #define _APS_NEXT_RESOURCE_VALUE 165 #define _APS_NEXT_COMMAND_VALUE 32836 -#define _APS_NEXT_CONTROL_VALUE 1070 +#define _APS_NEXT_CONTROL_VALUE 1073 #define _APS_NEXT_SYMED_VALUE 110 #endif #endif Index: src/ReviewBoardXml.cpp =================================================================== --- src/ReviewBoardXml.cpp (revision 0) +++ src/ReviewBoardXml.cpp (revision 0) @@ -0,0 +1,737 @@ +#include "stdafx.h" + +#include "ReviewBoardXml.h" +#include "rapidxml.hpp" +#include +#include +#include "timesupport.h" +#include "Http.h" + +#include "UnicodeUtils.h" +#include "AppUtils.h" + +using namespace rapidxml; +using namespace std; + +#define GET_REVIEWS_BASE "/api/review-requests/" +#define GET_REVIEWS "/api/review-requests/?status=all&max-results=30" +#define GET_REVIEWS_FROM "&time-added-from=" +#define GET_REVIEW_ID "/api/review-requests/" +#define GET_UNIDIFF_1 "/api/review-requests/" +#define GET_UNIDIFF_2 "/diffs/1/" +#define GET_FILES_1 "/api/review-requests/" +#define GET_FILES_2 "/diffs/1/files/" + + + +time_t ReviewBoard::Now() +{ + return time(NULL); +} + +time_t ReviewBoard::FromISO8601(const std::string& timestr) +{ + if(timestr == "0000-00-00T00:00:00") + return (time_t)0; + struct tm tm; + if(NULL == strptime(timestr.c_str(),"%Y-%m-%dT%H:%M:%S",&tm)) + return (time_t)0; + return mktime(&tm); +} + +std::string ReviewBoard::ToISO8601(time_t tim) +{ + if(tim == 0) + return string("0000-00-00T00:00:00"); + char buf[128]; + memset(buf,0,128); + strftime(buf,128,"%Y-%m-%dT%H:%M:%S",localtime(&tim)); + return string(buf); +} + +ReviewBoardXmlNode::~ReviewBoardXmlNode() +{ + while(!children.empty()) + { + delete children.back(); + children.pop_back(); + } +} + +void ReviewBoardXmlNode::Load(void* rapidnode, ReviewBoardXmlNode * paren) +{ + parent = paren; + + xml_node<> *rnode = (xml_node<>*)rapidnode; + + name.insert(0,rnode->name(),rnode->name_size()); +/* + the xml output from the reviewboard doesn't contain attributes + for (xml_attribute<> *attr = rnode->first_attribute(); + attr; attr = attr->next_attribute()) + { + } +*/ + for(rnode = rnode->first_node();rnode;rnode = rnode->next_sibling()) + { + if(rnode->value_size() != 0) + { + string name, val; + name.insert(0,rnode->name(),rnode->name_size()); + val.insert(0,rnode->value(),rnode->value_size()); + attributes[name] = val; + } + else + { + ReviewBoardXmlNode * child = new ReviewBoardXmlNode(); + child->Load(rnode,this); + children.push_back(child); + } + } +} + +ReviewBoardXmlNode const * ReviewBoardXmlNode::GetNodeWithAttributeAndValue(const std::string& attr, const std::string& val) const +{ + map::const_iterator itr = attributes.find(attr); + if(itr != attributes.end() && itr->second == val) + return this; + + for(vector::const_iterator it = children.begin(); it != children.end(); ++it) + { + ReviewBoardXmlNode const * ret = (*it)->GetNodeWithAttributeAndValue(attr,val); + if(ret) + return ret; + } + + return 0; +} + +ReviewBoardXmlNode const * ReviewBoardXmlNode::GetFirstNodeWithAttribute(const std::string& nam, const std::string& attr) const +{ + map::const_iterator itr = attributes.find(attr); + if(name == nam && itr != attributes.end()) + return this; + + for(vector::const_iterator it = children.begin(); it != children.end(); ++it) + { + ReviewBoardXmlNode const * ret = (*it)->GetFirstNodeWithAttribute(nam, attr); + if(ret) + return ret; + } + + return 0; +} + +void ReviewBoardXmlNode::GetNodesVectorWithAttribute(const std::string& attr, std::vector& nodes) const +{ + if(attributes.find(attr) != attributes.end()) + nodes.push_back(this); + for(vector::const_iterator it = children.begin(); it != children.end(); ++it) + (*it)->GetNodesVectorWithAttribute(attr,nodes); +} + + +void ReviewBoardXmlDocument::Cleanup() +{ + if(root) + delete root; + root = 0; +} + + +ReviewBoardXmlDocument::~ReviewBoardXmlDocument() +{ + Cleanup(); +} + +void ReviewBoardXmlDocument::LoadBuf(const string& buffer) +{ + char * buf; + int len; + + len = buffer.length(); + buf = new char[len+1]; + memcpy(buf,buffer.c_str(),len); + buf[len] = 0; + + xml_document<> doc; // character type defaults to char + doc.parse<0>(buf); // 0 means default parse flags + + xml_node<> * rapidnode = doc.first_node(); + + root = new ReviewBoardXmlNode(); + + if(rapidnode) + root->Load(rapidnode,NULL); + + delete[] buf; + + value.write(buffer.c_str(),buffer.length()); +} + +void ReviewBoardXmlDocument::Load(const string& path) +{ + Cleanup(); + + stringstream sstrm; + gHTTP.GetPageXml(path,sstrm); + + LoadBuf(sstrm.str()); +} + +void ReviewBoardTextDocument::Load(const string& path) +{ + gHTTP.GetPageText(path,value); +} + +void ReviewBoardReviewUnifiedDiff::Open(const char * review) +{ + string path = GET_UNIDIFF_1; + path += review; + path += GET_UNIDIFF_2; + Load(path); +} + +void ReviewBoardAllReviews::Open(const char * from_time) +{ + string path = GET_REVIEWS; + if(from_time) + { + path += GET_REVIEWS_FROM; + path += from_time; + } + Load(path); +} + +void ReviewBoardReview::Open(const char * id) +{ + string path = GET_REVIEW_ID; + path += id; + path += "/"; + Load(path); +} + +void ReviewBoardReviewFiles::Open(const char * id) +{ + string path = GET_FILES_1; + path += id; + path += GET_FILES_2; + Load(path); +} + +ReviewBoardXmlNode const * ReviewBoardXmlDocument::GetNodeWithAttributeAndValue(const std::string& attr, const std::string& val) const +{ + if(root) + return root->GetNodeWithAttributeAndValue(attr,val); + return 0; +} + +void ReviewBoardXmlDocument::GetNodesVectorWithAttribute(const std::string& attr, std::vector& nodes) const +{ + if(root) + return root->GetNodesVectorWithAttribute(attr,nodes); +} + +ReviewBoardXmlNode const * ReviewBoardXmlDocument::GetFirstNodeWithAttribute(const std::string& name, const std::string& attr) const +{ + if(root) + return root->GetFirstNodeWithAttribute(name, attr); + return 0; +} + + +typedef vector rbxvec; + +void ReviewBoard::GetAllNeededData_FirstPass(const char * from_time) +{ + ReviewBoardAllReviews * rbd = new ReviewBoardAllReviews(); + rbd->Open(from_time); + all_docs.push_back(rbd); + + rbxvec vec; + rbd->GetNodesVectorWithAttribute("id",vec); + for(rbxvec::const_iterator itr = vec.begin(); itr != vec.end(); ++itr) + { + string id = (*itr)->attributes.at("id"); + // if not cached already + if(reviewid_to_files.find(id) == reviewid_to_files.end()) + { + ReviewBoardReviewFiles * rbf = new ReviewBoardReviewFiles(); + + rbf->Open(id.c_str()); + reviewid_to_files[id] = rbf; + + all_docs.push_back(rbf); + } + } +} + +void ReviewBoard::GetSpecificReviewData_SecondPass(const std::vector& branches) +{ + for(R2Fmap::const_iterator itr = reviewid_to_files.begin(); itr != reviewid_to_files.end(); ++itr) + { + // if not cached already + if(reviewid_to_review.find(itr->first) == reviewid_to_review.end()) + { + if(itr->second->HasMatch(branches)) + { + ReviewBoardReview * rbr = new ReviewBoardReview(); + rbr->Open(itr->first.c_str()); + rbr->reviewid = itr->first; + reviewid_to_review[itr->first] = rbr; + all_docs.push_back(rbr); + } + } + } +} + +bool ReviewBoardReviewFiles::HasMatch(const std::string& branch) +{ + rbxvec vec; + GetNodesVectorWithAttribute("source_file",vec); + for(rbxvec::const_iterator it = vec.begin(); it != vec.end(); ++it) + { + const std::string& str = (*it)->attributes.at("source_file"); + if(str.find(branch) != string::npos) + return true; + } + return false; +} + + +bool ReviewBoardReviewFiles::HasMatch(const std::vector& branches) +{ + rbxvec vec; + GetNodesVectorWithAttribute("source_file",vec); + for(rbxvec::const_iterator it = vec.begin(); it != vec.end(); ++it) + { + const std::string& str = (*it)->attributes.at("source_file"); + for(std::vector::const_iterator itr = branches.begin(); itr != branches.end(); ++itr) + { + if(str.find(*itr) != string::npos) + return true; + } + } + return false; +} + +ReviewBoard::~ReviewBoard() +{ + // do not destroy while other threads are accessing the object + guard.AcquireWriterLock(); + while(!all_docs.empty()) + { + delete all_docs.back(); + all_docs.pop_back(); + } +} + +vector const * ReviewBoard::GetReadOnlyData() +{ + guard.AcquireReaderLock(); + return &all_docs; +} + +void ReviewBoard::ReleaseReadOnlyData() +{ + guard.ReleaseReaderLock(); +} + +void ReviewBoard::Save(const std::wstring& filename) +{ + guard.AcquireReaderLock(); + + if(filename != TEXT("")) + savefilename = filename; + + if(savefilename == TEXT("")) + savefilename = CAppUtils::GetDataDir() + TEXT("\\reviews"); + + ofstream ofile(savefilename.c_str()); + + + ofile << ToISO8601(lastupdate) << endl; + ofile << url_to_branch.size() << endl; + for(U2Bmap::const_iterator itr = url_to_branch.begin(); itr != url_to_branch.end(); ++itr) + { + ofile << itr->first << endl; + ofile << itr->second << endl; + } + + ofile << reviewid_to_files.size() << endl; + + for(R2Fmap::const_iterator itr = reviewid_to_files.begin(); itr != reviewid_to_files.end(); ++itr) + { + ofile << itr->first << endl; + ofile << itr->second->value.str().length() << endl; + ofile.write(itr->second->value.str().c_str(),itr->second->value.str().length()); + } + + ofile << reviewid_to_review.size() << endl; + + for(R2Rmap::const_iterator itr = reviewid_to_review.begin(); itr != reviewid_to_review.end(); ++itr) + { + ofile << itr->first << endl; + ofile << (itr->second->read ? "1" : "0") << endl; + ofile << itr->second->value.str().length() << endl; + ofile.write(itr->second->value.str().c_str(),itr->second->value.str().length()); + } + + + ofile.close(); + + guard.ReleaseReaderLock(); +} + +void ReviewBoard::Load(const std::wstring& filename) +{ + guard.AcquireWriterLock(); + + if(filename != TEXT("")) + savefilename = filename; + + if(savefilename == TEXT("")) + savefilename = CAppUtils::GetDataDir() + TEXT("\\reviews"); + + ifstream ifile(savefilename.c_str()); + + + std::string timestr; + ifile >> timestr; + lastupdate = FromISO8601(timestr); + int numbranches; + ifile >> numbranches; + for(int i = 0; i < numbranches; ++i) + { + string url, branch; + ifile >> url; + ifile >> branch; + url_to_branch[url] = branch; + } + + int filesnum; + ifile >> filesnum; + + for(int i = 0; i < filesnum; ++i) + { + string review; + ifile >> review; + int len; + ifile >> len; + ifile.get();// eat eol + string buf(len,0); + int j = 0; + while(j < len) + { + buf[j] = ifile.get(); + ++j; + } + ReviewBoardReviewFiles * r = new ReviewBoardReviewFiles(); + r->LoadBuf(buf); + reviewid_to_files[review] = r; + all_docs.push_back(r); + } + + ifile >> filesnum; + + for(int i = 0; i < filesnum; ++i) + { + string review; + ifile >> review; + int read; + ifile >> read; + int len; + ifile >> len; + ifile.get();// eat eol + string buf(len,0); + int j = 0; + while(j < len) + { + buf[j] = ifile.get(); + ++j; + } + ReviewBoardReview * r = new ReviewBoardReview(); + r->read = (read == 1); + r->LoadBuf(buf); + r->reviewid = review; + reviewid_to_review[review] = r; + all_docs.push_back(r); + } + + + ifile.close(); + + guard.ReleaseWriterLock(); +} + +int ReviewBoard::DoUpdate() +{ + guard.AcquireWriterLock(); + + // set already dl-d reviews to old + for(R2Rmap::iterator itr = reviewid_to_review.begin();itr != reviewid_to_review.end(); ++itr) + itr->second->old = true; + + int filesnum = reviewid_to_files.size(); + if(lastupdate != 0) + GetAllNeededData_FirstPass(ToISO8601(lastupdate).c_str()); + else + GetAllNeededData_FirstPass(); + + lastupdate = time(NULL); + filesnum -= reviewid_to_files.size(); + + std::vector vec; + + for(U2Bmap::const_iterator itr = url_to_branch.begin(); itr != url_to_branch.end(); ++itr) + { + vec.push_back(itr->second); + } + + int revsnum = reviewid_to_review.size(); + GetSpecificReviewData_SecondPass(vec); + revsnum -= reviewid_to_review.size(); + + guard.ReleaseWriterLock(); + if(filesnum < 0) + Save(); + + newreviews = -revsnum; + + return newreviews; +} + +void ReviewBoard::AddBranchToMonitor(const string& url, const string& branch) +{ + guard.AcquireWriterLock(); + url_to_branch[url] = branch; + guard.ReleaseWriterLock(); +} + +void ReviewBoard::AddBranchToMonitor(const wstring& wurl) +{ + std::string url = CUnicodeUtils::StdGetUTF8(wurl); + std::string branch = url; + size_t pos = branch.find("/code/"); + if(pos != string::npos) + { + branch = branch.substr(pos+6); + } + AddBranchToMonitor(url,branch); +} + + +void ReviewBoard::RemoveBranchToMonitor(const wstring& wurl) +{ + RemoveBranchToMonitor(CUnicodeUtils::StdGetUTF8(wurl)); +} + + +void ReviewBoard::RemoveBranchToMonitor(const string& url) +{ + guard.AcquireWriterLock(); + U2Bmap::iterator itr = url_to_branch.find(url); + if(itr != url_to_branch.end()) + url_to_branch.erase(url); + guard.ReleaseWriterLock(); +} + +bool ReviewBoard::IsUrlMonitored(const std::string& url) +{ + guard.AcquireReaderLock(); + U2Bmap::const_iterator itr = url_to_branch.find(url); + if(itr != url_to_branch.end()) + { + guard.ReleaseReaderLock(); + return true; + } + guard.ReleaseReaderLock(); + return false; +} + +bool ReviewBoard::IsUrlMonitored(const std::wstring& wurl) +{ + std::string url = CUnicodeUtils::StdGetUTF8(wurl); + return IsUrlMonitored(url); +} + +typedef std::vector rbrvec; +void ReviewBoard::GetReviewsForUrl(const std::wstring& wurl, WR2Rmap& vec) +{ + std::string url = CUnicodeUtils::StdGetUTF8(wurl); + guard.AcquireReaderLock(); + U2Bmap::const_iterator itr = url_to_branch.find(url); + if(itr != url_to_branch.end()) + { + for(R2Fmap::const_iterator filesitr = reviewid_to_files.begin(); filesitr != reviewid_to_files.end(); ++filesitr) + { + if(filesitr->second->HasMatch(itr->second)) + { + R2Rmap::const_iterator ritr = reviewid_to_review.find(filesitr->first); + if(ritr != reviewid_to_review.end()) + vec[UTF8ToWide(ritr->first)] = (const_cast(ritr->second)); + } + } + } + guard.ReleaseReaderLock(); +} + +int ReviewBoard::GetUnreadReviews() +{ + guard.AcquireReaderLock(); + int ret = 0; + for(R2Rmap::const_iterator ritr = reviewid_to_review.begin(); ritr != reviewid_to_review.end(); ++ritr) + { + if(!ritr->second->read) + ++ret; + } + guard.ReleaseReaderLock(); + return ret; +} + +int ReviewBoard::GetNewReviewsForUrl(const std::wstring& url) +{ + WR2Rmap v; + GetReviewsForUrl(url,v); + int n = 0; + for(WR2Rmap::const_iterator it = v.begin(); it != v.end(); ++it) + { + if(!it->second->old) + n++; + } + return n; +} + +int ReviewBoard::GetUnreadReviewsForUrl(const std::wstring& url) +{ + WR2Rmap v; + GetReviewsForUrl(url,v); + int n = 0; + for(WR2Rmap::const_iterator it = v.begin(); it != v.end(); ++it) + { + if(!it->second->read) + n++; + } + return n; +} + +void ReviewBoard::ReviewSetRead(const std::string& rev, bool b) +{ + guard.AcquireWriterLock(); + R2Rmap::iterator ritr = reviewid_to_review.find(rev); + bool needsave = false; + if(ritr != reviewid_to_review.end()) + { + if(ritr->second->read != b) + needsave = true; + ritr->second->read = b; + } + guard.ReleaseWriterLock(); + if(needsave) + Save(); +} + +void ReviewBoard::DeleteReview(const std::string& rev) +{ + guard.AcquireWriterLock(); + R2Rmap::iterator ritr = reviewid_to_review.find(rev); + bool needsave = false; + if(ritr != reviewid_to_review.end()) + { + std::vector::iterator it2 = all_docs.begin(); + while(it2 != all_docs.end()) + { + if((*it2) == (ritr->second)) + break; + it2++; + } + + if(!ritr->second->old) + newreviews--; + + delete (*it2); + + if(it2 != all_docs.end()) + all_docs.erase(it2); + + reviewid_to_review.erase(ritr); + needsave = true; + } + guard.ReleaseWriterLock(); + if(needsave) + Save(); +} + + +void ReviewBoardTextDocument::Save(const wstring& filename) +{ + ofstream ofile(filename.c_str()); + ofile.write(value.str().c_str(),value.str().length()); + ofile.close(); +} + +void ReviewBoard::ReviewsSetRead(const std::wstring& wurl) +{ + std::string url = CUnicodeUtils::StdGetUTF8(wurl); + guard.AcquireWriterLock(); + U2Bmap::const_iterator itr = url_to_branch.find(url); + if(itr != url_to_branch.end()) + { + for(R2Fmap::const_iterator filesitr = reviewid_to_files.begin(); filesitr != reviewid_to_files.end(); ++filesitr) + { + if(filesitr->second->HasMatch(itr->second)) + { + R2Rmap::iterator ritr = reviewid_to_review.find(filesitr->first); + if(ritr != reviewid_to_review.end()) + ritr->second->read = true; + } + } + } + guard.ReleaseWriterLock(); + Save(); +} + +wstring ReviewBoardReview::Author() const +{ + ReviewBoardXmlNode const * n = GetFirstNodeWithAttribute("submitter","title"); + if(n) + { + return UTF8ToWide(n->attributes.at("title")); + } + return TEXT("no author"); +} + +wstring ReviewBoardReview::Date() const +{ + ReviewBoardXmlNode const * n = GetFirstNodeWithAttribute("review_request","time_added"); + if(n) + { + return UTF8ToWide(n->attributes.at("time_added")); + } + return TEXT("(no date)"); +} + +wstring ReviewBoardReview::Url() const +{ + ReviewBoardXmlNode const * n = GetFirstNodeWithAttribute("self","href"); + if(n) + { + return UTF8ToWide(n->attributes.at("href")); + } + return TEXT(""); +} + +wstring ReviewBoardReview::Message() const +{ + ReviewBoardXmlNode const * n = GetFirstNodeWithAttribute("review_request","description"); + if(n) + { + return UTF8ToWide(n->attributes.at("description")); + } + return TEXT("(no description)"); +} + +wstring ReviewBoardReview::Reviewid() const +{ + return UTF8ToWide(reviewid); +} Index: src/ReviewBoardXml.h =================================================================== --- src/ReviewBoardXml.h (revision 0) +++ src/ReviewBoardXml.h (revision 0) @@ -0,0 +1,167 @@ +#ifndef REVIEWBOARDXML_H +#define REVIEWBOARDXML_H + +#include "stdafx.h" + +#include "ReaderWriterLock.h" +#include "Singleton.h" + +#include +#include +#include +#include + +class ReviewBoardXmlNode +{ +public: + std::string name; + ReviewBoardXmlNode * parent; + std::map attributes; + std::vector children; +public: + ReviewBoardXmlNode() : parent(0) {} + ~ReviewBoardXmlNode(); + void Load(void* rapidnode, ReviewBoardXmlNode * parent); + ReviewBoardXmlNode const * GetNodeWithAttributeAndValue(const std::string& attr, const std::string& val) const; + void GetNodesVectorWithAttribute(const std::string& attr, std::vector& nodes) const; + ReviewBoardXmlNode const * GetFirstNodeWithAttribute(const std::string& name, const std::string& attr) const; +}; + +enum ReviewBoardDocumentType +{ + RBDT_BASE = 0, + RBDT_XML, + RBDT_TEXT, + RBDT_UNIDIFF, + RBDT_ALLREVIEWS, + RBDT_REVIEW, + RBDT_REVIEWFILES +}; + +class ReviewBoardDocument +{ +public: + std::stringstream value; +public: + virtual ReviewBoardDocumentType GetType() { return RBDT_BASE; } +}; + +class ReviewBoardXmlDocument : public ReviewBoardDocument +{ +public: + ReviewBoardXmlNode * root; +public: + ReviewBoardXmlDocument() : root(0) {} + ~ReviewBoardXmlDocument(); + void Cleanup(); + void LoadBuf(const std::string& buf); + void Load(const std::string& path); + ReviewBoardXmlNode const * GetNodeWithAttributeAndValue(const std::string& attr, const std::string& val) const; + void GetNodesVectorWithAttribute(const std::string& attr, std::vector& nodes) const; + ReviewBoardXmlNode const * GetFirstNodeWithAttribute(const std::string& name, const std::string& attr) const; + virtual ReviewBoardDocumentType GetType() { return RBDT_XML; } +}; + +class ReviewBoardTextDocument : public ReviewBoardDocument +{ +public: + void Load(const std::string& path); + virtual ReviewBoardDocumentType GetType() { return RBDT_TEXT; } + void Save(const std::wstring& filename); +}; + +class ReviewBoardReviewUnifiedDiff : public ReviewBoardTextDocument +{ +public: + std::string review; +public: + void Open(const char * reviewrequest); + virtual ReviewBoardDocumentType GetType() { return RBDT_UNIDIFF; } +}; + +class ReviewBoardAllReviews : public ReviewBoardXmlDocument +{ +public: + void Open(const char * from_time = 0); + virtual ReviewBoardDocumentType GetType() { return RBDT_ALLREVIEWS; } +}; + +class ReviewBoardReview : public ReviewBoardXmlDocument +{ +public: + std::string reviewid; + bool read; + bool old; + ReviewBoardReview() : read(false), old(false) {} +public: + void Open(const char * reviewid); + virtual ReviewBoardDocumentType GetType() { return RBDT_REVIEW; } + std::wstring Author() const; + std::wstring Message() const; + std::wstring Date() const; + std::wstring Reviewid() const; + std::wstring Url() const; +}; + +class ReviewBoardReviewFiles : public ReviewBoardXmlDocument +{ +public: + void Open(const char * reviewid); + virtual ReviewBoardDocumentType GetType() { return RBDT_REVIEWFILES; } + bool HasMatch(const std::vector& branches); + bool HasMatch(const std::string& branch); +}; + +class ReviewBoard +{ +private: + friend class Singleton; + ReviewBoard() : lastupdate(0), savefilename(TEXT("")), newreviews(0) {} + std::vector all_docs; + typedef std::map R2Fmap; + R2Fmap reviewid_to_files; + typedef std::map R2Rmap; + R2Rmap reviewid_to_review; + CReaderWriterLock guard; + typedef std::map U2Bmap; + U2Bmap url_to_branch; + time_t lastupdate; + time_t Now(); + time_t FromISO8601(const std::string& timestr); + std::string ToISO8601(time_t time); + int newreviews; +public: + std::wstring savefilename; + void GetAllNeededData_FirstPass(const char * from_time = 0); + void GetSpecificReviewData_SecondPass(const std::vector& branches); + std::stringstream GetReviewUnifiedDiff(const char * review); + ~ReviewBoard(); + + // need lock funcs + std::vector const * GetReadOnlyData(); + void GetReadLock() { guard.AcquireReaderLock(); } + void ReleaseReadOnlyData(); + bool IsUrlMonitored(const std::string& branch); + bool IsUrlMonitored(const std::wstring& url); + typedef std::map WR2Rmap; + void GetReviewsForUrl(const std::wstring& url, WR2Rmap& vec); + int GetNewReviewsForUrl(const std::wstring& url); + int GetUnreadReviewsForUrl(const std::wstring& url); + + void DeleteReview(const std::string& rev); + void ReviewSetRead(const std::string& rev, bool read = true); + void ReviewsSetRead(const std::wstring& url); + void Load(const std::wstring& filepath = TEXT("")); + void Save(const std::wstring& filepath = TEXT("")); + void AddBranchToMonitor(const std::wstring& url); + void AddBranchToMonitor(const std::string& url, const std::string& branch); + void RemoveBranchToMonitor(const std::string& url); + void RemoveBranchToMonitor(const std::wstring& url); + int DoUpdate(); + int GetUnreadReviews(); +}; + +#define gReviewBoard (*(Singleton::GetInstance())) +#define gReviewBoard_Cleanup Singleton::Cleanup() + +#endif Index: src/Singleton.h =================================================================== --- src/Singleton.h (revision 0) +++ src/Singleton.h (revision 0) @@ -0,0 +1,34 @@ +#ifndef SINGLETON_H__ +#define SINGLETON_H__ + + +template +class Singleton +{ +private: + static T * _instance; + Singleton() + { + } +public: + static T * GetInstance() + { + if(!_instance) + { + _instance = new T(); + } + return _instance; + } + static void Cleanup() + { + if(_instance) + { + delete _instance; + _instance = 0; + } + } +}; + +template T* Singleton::_instance = 0; + +#endif Index: src/timesupport.cc =================================================================== --- src/timesupport.cc (revision 0) +++ src/timesupport.cc (revision 0) @@ -0,0 +1,248 @@ +// Copyright 2009 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +#include "stdafx.h" + +#include "timesupport.h" + +#include +#include +#include + +#ifdef WIN32 +// Implement strptime under windows +static const char* kWeekFull[] = { + "Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday" +}; + +static const char* kWeekAbbr[] = { + "Sun", "Mon", "Tue", "Wed", + "Thu", "Fri", "Sat" +}; + +static const char* kMonthFull[] = { + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December" +}; + +static const char* kMonthAbbr[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +static const char* _parse_num(const char* s, int low, int high, int* value) { + const char* p = s; + for (*value = 0; *p != NULL && isdigit(*p); ++p) { + *value = (*value) * 10 + static_cast(*p) - static_cast('0'); + } + + if (p == s || *value < low || *value > high) return NULL; + return p; +} + +static char* _strptime(const char *s, const char *format, struct tm *tm) { + while (*format != NULL && *s != NULL) { + if (*format != '%') { + if (*s != *format) return NULL; + + ++format; + ++s; + continue; + } + + ++format; + int len = 0; + switch (*format) { + // weekday name. + case 'a': + case 'A': + tm->tm_wday = -1; + for (int i = 0; i < 7; ++i) { + len = static_cast(strlen(kWeekAbbr[i])); + if (strnicmp(kWeekAbbr[i], s, len) == 0) { + tm->tm_wday = i; + break; + } + + len = static_cast(strlen(kWeekFull[i])); + if (strnicmp(kWeekFull[i], s, len) == 0) { + tm->tm_wday = i; + break; + } + } + if (tm->tm_wday == -1) return NULL; + s += len; + break; + + // month name. + case 'b': + case 'B': + case 'h': + tm->tm_mon = -1; + for (int i = 0; i < 12; ++i) { + len = static_cast(strlen(kMonthAbbr[i])); + if (strnicmp(kMonthAbbr[i], s, len) == 0) { + tm->tm_mon = i; + break; + } + + len = static_cast(strlen(kMonthFull[i])); + if (strnicmp(kMonthFull[i], s, len) == 0) { + tm->tm_mon = i; + break; + } + } + if (tm->tm_mon == -1) return NULL; + s += len; + break; + + // month [1, 12]. + case 'm': + s = _parse_num(s, 1, 12, &tm->tm_mon); + if (s == NULL) return NULL; + --tm->tm_mon; + break; + + // day [1, 31]. + case 'd': + case 'e': + s = _parse_num(s, 1, 31, &tm->tm_mday); + if (s == NULL) return NULL; + break; + + // hour [0, 23]. + case 'H': + s = _parse_num(s, 0, 23, &tm->tm_hour); + if (s == NULL) return NULL; + break; + + // minute [0, 59] + case 'M': + s = _parse_num(s, 0, 59, &tm->tm_min); + if (s == NULL) return NULL; + break; + + // seconds [0, 60]. 60 is for leap year. + case 'S': + s = _parse_num(s, 0, 60, &tm->tm_sec); + if (s == NULL) return NULL; + break; + + // year [1900, 9999]. + case 'Y': + s = _parse_num(s, 1900, 9999, &tm->tm_year); + if (s == NULL) return NULL; + tm->tm_year -= 1900; + break; + + // year [0, 99]. + case 'y': + s = _parse_num(s, 0, 99, &tm->tm_year); + if (s == NULL) return NULL; + if (tm->tm_year <= 68) { + tm->tm_year += 100; + } + break; + + // arbitray whitespace. + case 't': + case 'n': + while (isspace(*s)) ++s; + break; + + // '%'. + case '%': + if (*s != '%') return NULL; + ++s; + break; + + // All the other format are not supported. + default: + return NULL; + } + ++format; + } + + if (*format != NULL) { + return NULL; + } else { + return const_cast(s); + } +} + +char* strptime(const char *buf, const char *fmt, struct tm *tm) { + return _strptime(buf, fmt, tm); +} +#endif // WIN32 + +#if defined(__linux__) || defined(__unix__) + +time_t _mkgmtime(const struct tm *tm) { + // Month-to-day offset for non-leap-years. + static const int month_day[12] = + {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; + + // Most of the calculation is easy; leap years are the main difficulty. + int month = tm->tm_mon % 12; + int year = tm->tm_year + tm->tm_mon / 12; + if (month < 0) { // Negative values % 12 are still negative. + month += 12; + --year; + } + + // This is the number of Februaries since 1900. + const int year_for_leap = (month > 1) ? year + 1 : year; + + time_t rt = tm->tm_sec // Seconds + + 60 * (tm->tm_min // Minute = 60 seconds + + 60 * (tm->tm_hour // Hour = 60 minutes + + 24 * (month_day[month] + tm->tm_mday - 1 // Day = 24 hours + + 365 * (year - 70) // Year = 365 days + + (year_for_leap - 69) / 4 // Every 4 years is leap... + - (year_for_leap - 1) / 100 // Except centuries... + + (year_for_leap + 299) / 400))); // Except 400s. + return rt < 0 ? -1 : rt; +} + +#endif + +bool ParseRfcTime(const char* buf, struct tm *tm) { + memset(tm, 0, sizeof(*tm)); + return + strptime(buf, "%a, %e %b %Y %H:%M:%S", tm) || // RFC 822/1123 + strptime(buf, "%a, %e-%b-%y %H:%M:%S", tm) || // RFC 850/1036 + strptime(buf, "%a %b %e %H:%M:%S %Y", tm); // asctime() +} + +// Covert the time to W3C Datetime formats. +// See http://www.w3.org/TR/NOTE-datetime +const std::string FormatW3CTime(const time_t &time) { + char buffer[80]; + strftime(buffer, 80, + "%Y-%m-%dT%H:%M:%SZ", // like 2001-12-31T23:59:59Z + gmtime(&time)); + return buffer; +} + +std::string FormatHttpDate(time_t t) { + // Convert 'time_t' to struct tm (GMT) + tm* gmt_p = gmtime(&t); + + // Convert struct tm to HTTP-date string + char buff[256]; + strftime(buff, sizeof(buff), "%a, %d %b %Y %H:%M:%S GMT", gmt_p); + return buff; +} Index: src/timesupport.h =================================================================== --- src/timesupport.h (revision 0) +++ src/timesupport.h (revision 0) @@ -0,0 +1,53 @@ +// Copyright 2009 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +// This files includes functions to support time related operations. +// It defines common functions, which are only available on platforms. +// It also provides functions to parse RFC times. + +#ifndef COMMON_TIMESUPPORT_H__ +#define COMMON_TIMESUPPORT_H__ + +#include +#include + +#ifdef WIN32 +// Convert a string representation time to a time tm structure. +// It is the conversion function of strftime(). +// Linux provides this function. +char *strptime(const char *buf, const char *fmt, struct tm *tm); +#endif + +#if defined(__linux__) || defined(__unix__) +// Convert GMT time to UTC time value. +// It is like mktime(), but interprets the fields as GMT rather than local. +// This is the inverse of gmtime(). +// Windows provides this function. +time_t _mkgmtime(const struct tm *tm); +#endif + +// Tries various rfc's to convert a buffer to a struct tm. +// Warning: this function only handles a few cases and completely ignores +// time zones! +bool ParseRfcTime(const char* buf, struct tm *tm); + +// Covert the time to W3C Datetime formats. +// See http://www.w3.org/TR/NOTE-datetime +const std::string FormatW3CTime(const time_t &time); + +// A helper method used to format HttpDate. +std::string FormatHttpDate(time_t t); + +#endif // COMMON_TIMESUPPORT_H__ Index: src/URLDlg.cpp =================================================================== --- src/URLDlg.cpp (revision 605) +++ src/URLDlg.cpp (working copy) @@ -31,6 +31,7 @@ { sSCCS[0] = _T("SVN"); sSCCS[1] = _T("Accurev"); + reviewmonitored = false; } CURLDlg::~CURLDlg(void) @@ -68,6 +69,7 @@ SetDlgItemText(*this, IDC_URLTOMONITORLABEL, _T("URL to monitor")); SetDlgItemText(*this, IDC_URLGROUP, _T("SVN repository settings")); ShowWindow(GetDlgItem(*this, IDC_ACCUREVREPO), SW_HIDE); + ShowWindow(GetDlgItem(*this, IDC_CHECKREVIEWS), SW_SHOW); SendMessage(GetDlgItem(*this, IDC_SCCSCOMBO), CB_SETCURSEL, (WPARAM)sccs, 0); break; @@ -77,6 +79,7 @@ SetDlgItemText(*this, IDC_URLTOMONITORLABEL, _T("Accurev stream")); SetDlgItemText(*this, IDC_URLGROUP, _T("Accurev repository settings")); ShowWindow(GetDlgItem(*this, IDC_ACCUREVREPO), SW_SHOW); + ShowWindow(GetDlgItem(*this, IDC_CHECKREVIEWS), SW_HIDE); SendMessage(GetDlgItem(*this, IDC_SCCSCOMBO), CB_SETCURSEL, (WPARAM)sccs, 1); break; } @@ -103,6 +106,7 @@ AddToolTip(IDC_URLTOMONITOR, _T("URL to the repository, or the SVNParentPath URL")); AddToolTip(IDC_SCCSCOMBO, _T("Source code control system to use")); AddToolTip(IDC_ACCUREVREPO, _T("Accurev repository name")); + AddToolTip(IDC_CHECKREVIEWS, _T("Check review requests for this repository")); AddToolTip(IDC_IGNORESELF, _T("If enabled, commits from you won't show a notification")); AddToolTip(IDC_SCRIPT, _T("enter here a command which gets called after new revisions were detected.\n\n%revision gets replaced with the new HEAD revision\n%url gets replaced with the url of the project\n%project gets replaced with the project name\n\nExample command line:\nTortoiseProc.exe /command:update /rev:%revision /path:\"path\\to\\working\\copy\"")); AddToolTip(IDC_WEBDIFF, _T("URL to a web viewer\n%revision gets replaced with the new HEAD revision\n%url gets replaced with the url of the project\n%project gets replaced with the project name")); @@ -119,6 +123,7 @@ AddToolTip(IDC_CHECKTIME, _T("Interval for repository update checks")); // initialize the controls + SendMessage(GetDlgItem(*this, IDC_CHECKREVIEWS), BM_SETCHECK, reviewmonitored ? BST_CHECKED : BST_UNCHECKED, NULL); SetDlgItemText(*this, IDC_ACCUREVREPO, info.accurevRepo.c_str()); SetDlgItemText(*this, IDC_URLTOMONITOR, info.url.c_str()); WCHAR buf[20]; @@ -294,6 +299,8 @@ info.noexecuteignored = !!SendMessage(GetDlgItem(*this, IDC_EXECUTEIGNORED), BM_GETCHECK, 0, NULL); + reviewmonitored = !!SendMessage(GetDlgItem(*this, IDC_CHECKREVIEWS), BM_GETCHECK, 0, NULL); + // make sure this entry gets checked again as soon as the next timer fires info.lastchecked = 0; } Index: src/URLDlg.h =================================================================== --- src/URLDlg.h (revision 605) +++ src/URLDlg.h (working copy) @@ -31,6 +31,8 @@ ~CURLDlg(void); void SetInfo(const CUrlInfo * pURLInfo = NULL); + void SetReviewMonitored(bool b) { reviewmonitored = b; } + bool GetReviewMonitored() { return reviewmonitored; } CUrlInfo * GetInfo() {return &info;} void ClearForTemplate(); @@ -49,4 +51,5 @@ CUrlInfo info; AeroControlBase m_aerocontrols; wstring sSCCS[CUrlInfo::SCCS_LEN]; + bool reviewmonitored; };