#include "Worker.h"
#include <assert.h>
#include <windowsx.h>
#include "Helpers.h"
#define COL_OFFSET 6 // space between text and col border
#define CHUNK_SIZE 20 // increments of children list
// Thread count limiting
//LPCTSTR semName = TEXT("Worker Thread Limit");
HANDLE semThreadLimit;
CRITICAL_SECTION secLogin;
LONG ThreadCount = 0;
LONG ActiveThreads = 0;
COLUMNS coldef;
const DWORD MSG_NETNOTIFY = RegisterWindowMessage(TEXT("Worker Notify"));
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CWorker::CWorker(HWND control):
hwndNotify(control), tvItem(NULL),
parent(NULL), expanded(false),
lpList(NULL),count(0),allocated(0), cancelWork(false), AutoStart(false),
m_Name(NULL), hEnum(NULL), err(NULL),
hThread(NULL), idThread(0),
state(STOPPED), type(NRT_WORKER)
{
InitializeCriticalSection(&exclusive1);
lock.event = CreateEvent(NULL, false, true, NULL);
lock.ThreadID = 0;
}
CWorker::CWorker(HANDLE file, CWorker* parentnode, HWND control):
hwndNotify(control), tvItem(NULL),
parent(parentnode), expanded(false),
lpList(NULL),count(0),allocated(0), children_to_load(0),
cancelWork(false), AutoStart(false),
m_Name(NULL), hEnum(NULL), err(NULL),
hThread(NULL), idThread(0),
state(STOPPED), type(NRT_WORKER)
{
InitializeCriticalSection(&exclusive1);
lock.event = CreateEvent(NULL, false, true, NULL);
lock.ThreadID = 0;
NETPROPBAG npb;
DWORD read = 0;
if (ReadFile(file, &npb, sizeof(npb), &read, NULL) && sizeof(npb)==read) {
(NetResType)type = npb.type;
children_to_load = npb.children;
if (npb.children>0) state = COMPLETED;
m_Name = new TCHAR[npb.NameLen+1];
if (m_Name) {
ReadFile(file, m_Name, (npb.NameLen+1)* sizeof(TCHAR), &read, NULL);
err.SetIdentifier(m_Name);
} else {
DebugBreak();
}
AllocList(npb.children);
if (parent && NULL==parent->AddNoCheck(this)) delete this;
}
}
CWorker::~CWorker()
{
ExclusiveLock();
if (hwndNotify && tvItem) {
//if (parent && parent->tvItem) {
TreeView_DeleteItem(hwndNotify, tvItem);
//}
tvItem = NULL;
}
Stop(INFINITE);
ClearList();
ExclusiveRelease();
DeleteCriticalSection(&exclusive1);
CloseHandle(lock.event);
if (m_Name) delete m_Name;
}
void CWorker::Stop(unsigned timeout, bool recursive)
{
SignalRecursive(true);
//ReleaseSemaphore(semThreadLimit,1,NULL);
UpdateStatus();
if (hThread) {
if (timeout) {
if (WaitForSingleObjectEx(hThread, timeout, TRUE)==WAIT_TIMEOUT) {
DebugWrite(TEXT("Stop timed-out -> terminate"));
TerminateThread(hThread, -1);
}
CloseHandle(hThread);
hThread=NULL;
idThread=0;
}
}
if (hEnum) {
WNetCloseEnum(hEnum);
hEnum = NULL;
}
if (recursive) {
for(unsigned i=0; i<count; i++) {
lpList[i]->Stop(timeout, true);
}
}
}
void CWorker::SignalRecursive(bool value)
{
unsigned idx;
cancelWork=true;
//if (state == RUNNING) {
// idx = WNetCloseEnum(hEnum);//ERROR_EXTENDED_ERROR ERROR_INVALID_HANDLE
//}
for (idx=0; idx < count; idx++) {
lpList[idx]->SignalRecursive(value);
}
}
void CWorker::Start(bool AutoRecurse)
{
cancelWork = false;
if (hThread == NULL) {
//if (state != COMPLETED) {
SetState(WAITING);
hThread = CreateThread( NULL, 0, AsyncStartThread, this, CREATE_SUSPENDED, &idThread);
if (hThread) {
LONG threads = InterlockedIncrement( &ThreadCount);
if (1 == threads) PostNotify(NNM_ENUMSTARTED, 0);
SetThreadPriority( hThread, THREAD_PRIORITY_LOWEST );
UpdateStatus();
AutoStart = AutoRecurse;
ResumeThread( hThread );
}
}
}
DWORD WINAPI CWorker::AsyncFilterThread( void * pvoid )
{
CWorker * This = (CWorker *)pvoid;
if (This->ExclusiveLock()) {
SetWindowRedraw(This->hwndNotify, false);
This->Filter(This->filter.token, This->filter.col);
DeletePtr(This->filter.token);
SetWindowRedraw(This->hwndNotify, true);
//CloseHandle(This->hThread);
//This->hThread = NULL;
This->ExclusiveRelease();
}
return 0;
}
DWORD WINAPI CWorker::AsyncStartThread( void * This )
{
LONG tActive, tTotal;
CWorker * obj = (CWorker *)This;
obj->PostNotify(NNM_THREADCNTCHANGED, MAKELPARAM(ActiveThreads,ThreadCount));
if (WaitForSingleObject(semThreadLimit, INFINITE)==WAIT_OBJECT_0) {
InterlockedIncrement( &ActiveThreads );
if (!obj->cancelWork) {
LPEXCEPTION_POINTERS info;
obj->SetState(RUNNING);
obj->PostNotify(NNM_THREADCNTCHANGED, MAKELPARAM(ActiveThreads,ThreadCount));
//obj->DebugWrite(TEXT("Thread begins."));
if (obj->lpList && obj->AutoStart) {
for (unsigned i=0; i<obj->count; i++) {
obj->lpList[i]->Start(true);
}
}
__try {
obj->Work();
}
__except(info= GetExceptionInformation()){
TCHAR buf[100];
wsprintf(buf,TEXT("Exception: %X at 0x%X."), info->ExceptionRecord->ExceptionCode, info->ExceptionRecord->ExceptionAddress);
OutputDebugString(buf);
if (info->ExceptionRecord->ExceptionFlags==0) {
}
}
if (obj->hEnum) {
WNetCloseEnum(obj->hEnum);
obj->hEnum = NULL;
}
//obj->DebugWrite(TEXT("Thread finished."));
}
tActive = InterlockedDecrement( &ActiveThreads );
ReleaseSemaphore(semThreadLimit, 1, NULL);
} else {
obj->DebugWrite(TEXT("Start timed-out or semaphore error -> exiting."));
}
if (obj->state==FAIL_RUN) {
obj->SetState(FAIL_FINISHED);
} else {
obj->SetState(obj->cancelWork?STOPPED:COMPLETED);
}
CloseHandle(obj->hThread);
obj->hThread=NULL;
tTotal = InterlockedDecrement( &ThreadCount );
obj->PostNotify(NNM_THREADCNTCHANGED, MAKELPARAM(tActive,tTotal));
if ( tTotal == 0 ) {
// We're done enumerating
obj->DebugWrite(TEXT("All threads finished.\n"));
obj->PostNotify(NNM_ENUMFINISHED, 0);
}
if (obj->count==0) obj->PostNotify(NNM_EMPTYDETECTED, (LPARAM)obj);
return 0;
}
void CWorker::Work()
{
//UpdateStatus();
}
CWorker* CWorker::AddNoCheck(CWorker *item, bool TreeOnly)
{
if (!TreeOnly) {
if (!AllocList(count)) return NULL;
item->parent = this;
lpList[count++]=item;
}
TVINSERTSTRUCT ins;
if (tvItem==NULL && parent) parent->AddNoCheck(this, true);
ins.hParent = tvItem;
ins.hInsertAfter = TVI_SORT;
ins.item.mask = TVIF_TEXT | TVIF_CHILDREN | TVIF_PARAM | TVIF_STATE |TVIF_IMAGE | TVIF_SELECTEDIMAGE;
ins.item.cChildren = (item->type==NRT_SHARE)? 0: I_CHILDRENCALLBACK;
ins.item.pszText = item->m_Name; // LPSTR_TEXTCALLBACK;
ins.item.state = TVIS_EXPANDEDONCE;
ins.item.iImage = item->type;
ins.item.iSelectedImage = item->type;
ins.item.lParam = (LPARAM) item;
item->tvItem = TreeView_InsertItem(hwndNotify, &ins);
if (item->tvItem==NULL) err.DebugWriteString(TEXT("TreeView_InsertItem failed"));
return item;
}
// return NULL on error;
CWorker* CWorker::AddItem(CWorker *item)
{
if (item==NULL) return NULL;
unsigned i;
CWorker *ret=NULL;
if (ExclusiveLock()) {
for (i=0; i<count; i++) {
if (StrCmp(lpList[i]->m_Name, item->m_Name)==0) {
ret = lpList[i];
goto AddItemFail; // found - don't add
}
}
if (NULL == (ret = AddNoCheck(item))) goto AddItemFail;
PostNotify(NNM_ITEMADDED, (LPARAM) item);
if (expanded) {
TreeView_Expand(hwndNotify, tvItem, TVE_EXPAND);
expanded = false;
}
AddItemFail:
ExclusiveRelease();
}
return ret;
}
bool CWorker::AllocList(unsigned size)
{
if (size>=allocated) {
if (size<allocated+CHUNK_SIZE) size=allocated+CHUNK_SIZE;
CWorker** tmp=new CWorker*[size];
if (tmp==NULL) return false;
allocated = size;
memmove(tmp, lpList, sizeof(CWorker*)*count);
delete lpList;
lpList=tmp;
}
return true;
}
unsigned CWorker::GetItemIndex(CWorker *ptr)
{
for (unsigned i=0; i<count; i++) {
if (ptr == lpList[i]) return i;
}
return (-1);
}
// false on error
bool CWorker::RemoveItem(unsigned index)
{
bool ret = false;
if (ExclusiveLock()) {
if (index < count) {
delete lpList[index];
for (unsigned i=index; i<count-1; i++) {
lpList[i] = lpList[i+1];
}
lpList[i] = NULL;
count--;
ret = true;
}
ExclusiveRelease();
}
return ret;
}
bool CWorker::RemoveItem(CWorker *item)
{
bool ret = false;
if (ExclusiveLock(1000)) {
ret = RemoveItem(GetItemIndex(item));
ExclusiveRelease();
} else {
DebugWrite(TEXT("RemoveItem failed to gain exclusive access to internal list"));
}
return ret;
}
void CWorker::ClearList()
{
unsigned i;
if (ExclusiveLock()) {
for (i=0; i< count; i++) {
lpList[i]->tvItem = NULL;
delete lpList[i];
//lpList[i]=NULL;
}
delete lpList;
lpList=NULL;
allocated=count=0;
ExclusiveRelease();
}
}
inline unsigned CWorker::GetCount()
{
return count;
}
inline CWorker* CWorker::operator[](unsigned index)
{
if (index<count) return lpList[index];
return NULL;
}
void CWorker::UpdateStatus()
{
//RECT rc;
//if (TreeView_GetItemRect(hwndNotify, tvItem, &rc, FALSE)) {
InvalidateRect(hwndNotify, &rcItem, FALSE);
//}
//RedrawWindow(
}
void CWorker::DebugWrite(LPCTSTR str)
{
TCHAR msg[ 1000 ];
wsprintf( msg, TEXT("[0x%X:%d/%d] %s: %s\n"), idThread,ActiveThreads, ThreadCount, m_Name, str);
OutputDebugString(msg);
}
LRESULT CWorker::OnNotifyMsg(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, OnItemSelect selproc)
{
LPNMTVDISPINFO lpTvInfo = (LPNMTVDISPINFO) lParam;
LPNMTVCUSTOMDRAW lpTvDraw = (LPNMTVCUSTOMDRAW) lParam;
LPNMTREEVIEW lpTvNotify = (LPNMTREEVIEW) lParam;
LPNMHEADER lpHdNotify = (LPNMHEADER)lParam;
// LPNMHDFILTERBTNCLICK lpHdFilter = (LPNMHDFILTERBTNCLICK) lParam;
CWorker* obj;
unsigned i;
HDITEM hi={0};
TVHITTESTINFO hit;
RECT rc;
switch (((LPNMHDR) lParam)->code) {
case NM_CUSTOMDRAW:
switch (lpTvDraw->nmcd.dwDrawStage) {
case CDDS_PREPAINT:
//lpTvDraw->nmcd.rc.top = 20;
if (lpTvDraw->nmcd.hdr.hwndFrom == hwndNotify) return CDRF_NOTIFYITEMDRAW;
case CDDS_ITEMPREPAINT:
if ((obj = (CWorker*) lpTvDraw->nmcd.lItemlParam)!=NULL &&
(lpTvDraw->nmcd.uItemState & (CDIS_MARKED | CDIS_SELECTED))==0) {
if (obj->HasAnything()) {
lpTvDraw->clrText = RGB(0, 0, 255);
} else if (obj->count==0) {
lpTvDraw->clrText = GetSysColor(COLOR_3DSHADOW);
}
//if (lpTvDraw->nmcd.uItemState & CDIS_SELECTED) lpTvDraw->clrText ^=0x0FFFFFF;
}
//lpTvDraw->nmcd.rc.top += 20;
//lpTvDraw->nmcd.rc.bottom += 20;
return CDRF_NOTIFYPOSTPAINT;
case CDDS_ITEMPOSTPAINT:
if (obj = (CWorker*) lpTvDraw->nmcd.lItemlParam) {
obj->Draw(lpTvDraw, &coldef);
//return CDRF_SKIPDEFAULT;
}
return CDRF_DODEFAULT;
}
return CDRF_DODEFAULT;//DefWindowProc(hwnd, uMsg, wParam, lParam);
case TVN_GETDISPINFO:
obj = (CWorker*) lpTvInfo->item.lParam;
if (lpTvInfo->item.mask & TVIF_TEXT) StrNCopy(lpTvInfo->item.pszText, obj->m_Name, lpTvInfo->item.cchTextMax);
if (lpTvInfo->item.mask & TVIF_CHILDREN) lpTvInfo->item.cChildren = (obj->state>=COMPLETED)? obj->count: 1;
return TRUE;
case TVN_ITEMEXPANDING:
obj = (CWorker*) lpTvNotify->itemNew.lParam;
lpTvNotify->itemNew.mask |= TVIF_STATE;
lpTvNotify->itemNew.state |= TVIS_EXPANDED;
if (obj->state == STOPPED) {
obj->Start(false);
obj->expanded = true;
//TreeView_Expand(obj->hwndNotify, obj->tvItem, TVE_EXPAND);
}
//lpTvNotify->itemNew. TVIS_EXPANDEDONCE
break;
case TVN_SELCHANGED:
obj = (CWorker*)lpTvNotify->itemNew.lParam;
if (obj) selproc(obj);
break;
case NM_CLICK:
hit.flags=0;
hit.hItem=0;
GetCursorPos(&hit.pt);
ScreenToClient(lpTvNotify->hdr.hwndFrom, &hit.pt);
TreeView_HitTest(lpTvNotify->hdr.hwndFrom,&hit);
if (hit.hItem && (hit.flags & TVHT_ONITEMRIGHT)) {
TreeView_SelectItem(lpTvNotify->hdr.hwndFrom, hit.hItem);
}
break;
case TVN_GETINFOTIP:
//POINT pt;
//GetCurso
//((LPNMTVGETINFOTIP)lParam)->pszText
return TRUE;
case HDN_FILTERBTNCLICK:
return FALSE;
case HDN_FILTERCHANGE: {
TCHAR strFilter[50];
HDTEXTFILTER tf={strFilter, COUNT(strFilter)};
DWORD thrdID;
hi.mask = HDI_FILTER;
hi.type = HDFT_ISSTRING;
hi.pvFilter = &tf;
filter.col = lpHdNotify->iItem;
if (Header_GetItem(lpHdNotify->hdr.hwndFrom, lpHdNotify->iItem, &hi)) {
filter.token = new CToken(strFilter,true);
} else {
filter.token = NULL;
}
HANDLE thrd = CreateRunningThread(AsyncFilterThread, this, &thrdID);
CloseHandle(thrd);
/*
if (Header_GetItem(lpHdNotify->hdr.hwndFrom, lpHdNotify->iItem, &hi)) {
CToken tkn(filter,true);
Filter(&tkn, lpHdNotify->iItem);
} else {
Filter(NULL, lpHdNotify->iItem);
}
*/
break;
}
case HDN_BEGINDRAG:
if (lpHdNotify->iItem==0) return TRUE;
break;
case HDN_ENDDRAG:
Header_SetItem(lpHdNotify->hdr.hwndFrom, lpHdNotify->iItem, lpHdNotify->pitem);
//return TRUE;
case HDN_ITEMCHANGED:
coldef.count = Header_GetItemCount(lpHdNotify->hdr.hwndFrom);
for(i=0; i<coldef.count; i++) {
if (Header_GetItemRect(lpHdNotify->hdr.hwndFrom, i, &rc)) {
coldef.item[i].left = rc.left;
coldef.item[i].width = rc.right-rc.left;
}
}
InvalidateRect(hwndNotify, NULL, false);
break;
}
return FALSE;
}
void CWorker::Draw(LPNMTVCUSTOMDRAW tv, PCOLUMNS cols)
{
RECT rc = rcItem = tv->nmcd.rc;
TCHAR buf[10];
LPTSTR stText[]={
TEXT("Stopped"),
TEXT("Waiting"),
TEXT("Running"),
TEXT("Completed"),
TEXT("Run with error"),
TEXT("Error"),
TEXT("Canceling")
};
DWORD stCode = state;
//SetTextColor(tv->nmcd.hdc, (GetCount()>0)?RGB(0,0,200));
SetTextColor(tv->nmcd.hdc, tv->clrText);
SetBkColor(tv->nmcd.hdc, tv->clrTextBk);
HBRUSH br = CreateSolidBrush(tv->clrTextBk);
rc.left = cols->item[1].left;
for (int i = 1; i<3; i++) {
if (rc.left > cols->item[i].left) rc.left = cols->item[i].left;
}
FillRect(tv->nmcd.hdc, &rc, br);
DeleteObject(br);
//SetBkMode(tv->nmcd.hdc, TRANSPARENT);
rc.left = cols->item[1].left + 3;
rc.right = rc.left + cols->item[1].width - 6;
wsprintf(buf, TEXT("%d"), GetCount());
DrawText(tv->nmcd.hdc, buf, StrLen(buf), &rc, DT_SINGLELINE | DT_VCENTER | DT_RIGHT);
rc.left = cols->item[2].left + 3;
rc.right = rc.left + cols->item[2].width - 3;
if (cancelWork && state == RUNNING) {
stCode = 6; // CANCELING
}
DrawText(tv->nmcd.hdc, stText[stCode] , StrLen(stText[stCode]), &rc, DT_SINGLELINE | DT_VCENTER);
if (state>=FAIL_RUN) {
rc.left = cols->item[3].left + cols->item[3].width + 3;
rc.right = tv->nmcd.rc.right;
DrawText(tv->nmcd.hdc, *err , StrLen(*err), &rc, DT_SINGLELINE | DT_VCENTER);
}
}
void CWorker::SetState(ThreadState newstate)
{
state = newstate;
if (state==FAIL_RUN) {
err.Refresh(0);
}
UpdateStatus();
}
inline bool CWorker::HasAnything()
{
unsigned i;
for (i=0; i<count; i++) {
if (lpList[i]->HasAnything()) return true;
}
return false;
}
bool CWorker::Search(LPCTSTR mask, DWORD minSizeMB, DWORD maxSizeMB)
{
unsigned i;
for (i=0; i< count; i++) {
lpList[i]->Search(mask, minSizeMB, maxSizeMB);
}
return true;
}
NetResType CWorker::GetType()
{
return type;
}
void CWorker::Sort(bool recurse)
{
TreeView_SortChildren(hwndNotify, tvItem, recurse);
}
int CWorker::Filter(CToken *fltr, int col)
{
int chFilter=0;//, cnt;
switch (col) {
case 0: // resource name
if (fltr && parent && fltr->IsMatch(m_Name)) {
fltr = NULL;
chFilter++;
}
break;
case 1: // count
if (fltr && fltr->IntCompare(count)>=0) {
chFilter++;
//fltr = NULL;
}
break;
case 3: // IP
break;
default:
return 0;
}
//if (!(type == NRT_COMPUTER && chFilter)) {
for (unsigned i=0; i< count; i++) {
chFilter += lpList[i]->Filter(fltr, col);
}
//}
//assert(type != NRT_NETWORK);
if (chFilter>0 || fltr==NULL) {
if (tvItem==NULL) parent->AddNoCheck(this, true);
} else if (tvItem && parent) {
TreeView_DeleteItem(hwndNotify, tvItem);
tvItem = NULL;
}
return chFilter;
}
bool CWorker::Save(HANDLE file)
{
NETPROPBAG npb;
DWORD written = 0;
npb.type = type;
npb.children = count;
npb.NameLen = StrLen(m_Name);
if (WriteFile(file, &npb, sizeof(npb), &written, NULL)) {
if (WriteFile(file, m_Name, (npb.NameLen+1)* sizeof(TCHAR), &written, NULL)) {
for (written=0; written< count; written++) {
if (!lpList[written]->Save(file)) return false;
}
return true;
}
}
return false;
}
void CWorker::Unfold()
{
unsigned i;
for (i=0; i<count; i++) {
if (lpList[i]->tvItem==NULL) {
AddNoCheck(lpList[i], true);
}
lpList[i]->Unfold();
}
}
void CWorker::Expand()
{
TreeView_Expand(hwndNotify, tvItem, TVE_TOGGLE);
}
void CWorker::PostNotify(NetNotifyMessages code, LPARAM lParam)
{
PostMessage(GetParent(hwndNotify), MSG_NETNOTIFY, code, lParam);
}
CWorker* CWorker::GetParentNode()
{
return parent;
}
bool CWorker::ExclusiveLock(DWORD timeout)
{
if (GetCurrentThreadId() == lock.ThreadID) {
lock.locks++;
return true;
}
if (WaitForSingleObject(lock.event, timeout)== WAIT_OBJECT_0) {
lock.ThreadID = GetCurrentThreadId();
lock.locks = 1;
return true;
}
DebugWrite(TEXT("ExclusiveLock timed out"));
//EnterCriticalSection(&exclusive1);
return false;
}
void CWorker::ExclusiveRelease()
{
if (GetCurrentThreadId() == lock.ThreadID && ((--lock.locks) == 0)) {
lock.ThreadID = 0;
//lock.locks = 0;
SetEvent(lock.event);
}
//LeaveCriticalSection(&exclusive1);
}