/*<<<***************************************************************************
The MIT License (MIT)

Copyright(C) 2013 - 2015 Marc Mirti Mancinelli, Christophe Lardau and Philippe Pamart (xdcube.org)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
////////////////////////////////////////////////////////////////////////////////
#Title: xbDnCenter
#Project: xdcBase
////////////////////////////////////////////////////////////////////////////////
#Author: Marc MIRTI MANCINELLI
#History:
<15/09/2013: creation.
12/02/2014: this notice inserted.
***************************************************************************>>>*/
#include "stdafx.h"

//#define _WIN32_DCOM
#include "xbDnCenter.h"
//#include "xbSocketItf.h"
#include "xbHotPlug.h"

#pragma warning(push)
#pragma warning(disable: 4355) //'this' : used in base member initializer list
#pragma warning(disable: 4311) //'<function-style-cast>' : pointer truncation from 'sIxbThread *' to 'xbNptr'
//////////////////////////////////////////////////////////////////////////////////////////
// xbDnCenter
//////////////////////////////////////////////////////////////////////////////////////////
xbDnCenter		g_xbDnCenter("Base dnCenter",xbDNCENTER_THREADMAX,&g_xbDnPlugType);

sIxbDnPtr		*g_pxbDnWaitingPtr = NULL;

bool xbDnAsk(sIxbDnPtr *pPtr,xbDnOp op,xbDnPriority priority,bool bWaitProcessMsg,bool bCacheableOnly)
{
	if (op == xbDnLoad && g_xbEngineCenter.IsReady() && !g_xbEngineCenter.isInUse())
	{
		xbSectLock lck(&g_xbEngineCenter.m_sectStartStop);
		if (!g_xbEngineCenter.isInUse())
		{
			pPtr->_setNextWaiting(g_pxbDnWaitingPtr);
			g_pxbDnWaitingPtr = pPtr;
			return false;
		}
	}
	return g_xbDnCenter.AskOp(pPtr,op,priority,bWaitProcessMsg,bCacheableOnly);
}

bool xbDnAsk(IxbDnObject *pObj,xbDnOp op,LPCSTR pszUrl,xbDnPriority priority,bool bWaitProcessMsg)
{
	return g_xbDnCenter.AskOp(pObj,op,pszUrl,priority,bWaitProcessMsg);
}

bool xbDnCancel(IxbDnObject *pObj,LPCSTR pReason,xbDnOp op,bool bWaitProcessMsg)
{
	return g_xbDnCenter.CancelOp(pObj,pReason,op,bWaitProcessMsg);
}

bool xbDnWait(IxbDnObject *pObj,xbDnOp op,int timeOut,bool bWaitProcessMsg)
{
	return g_xbDnCenter.WaitOp(pObj,op,timeOut,bWaitProcessMsg);
}

bool xbDnCacheUpdate(IxbDnObject *pObj,bool bAddIfNotInCache)
{
	return g_xbDnCenter.UpdateCacheInfo(pObj,bAddIfNotInCache);
}

bool xbDnCacheRetrieve(const CLSID &clsid,const xbString &url,int64 urlParseType,xbRefPtr<IxbDnObject> *ppObj,bool bCacheableOnly)
{
	return g_xbDnCenter.RetrieveFromCache(clsid,url,urlParseType,ppObj,bCacheableOnly);
}

int xbDnCacheRetrieve(const CLSID &clsid,LPCSTR pszUrl,
		int64 urlParseType,xbArray< xbRefPtr<IxbDnObject> > *pArrObj,
		bool bCacheableOnly)
{
	return g_xbDnCenter.RetrieveFromCache(clsid,pszUrl,urlParseType,pArrObj,
		bCacheableOnly);
}

bool xbDnCacheRemove(IxbDnObject *pObj)
{
	return g_xbDnCenter.RemoveFromCache(pObj);
}

int xbDnCacheRemove(const CLSID &clsid,LPCSTR pszUrl,
		int64 urlParseType,bool bCacheableOnly)
{
	return g_xbDnCenter.RemoveFromCache(clsid,pszUrl,urlParseType,
		bCacheableOnly);
}

xbDnCenter::xbDnCenter(const char *pLabel,int cThreadMax,const xbEngines &engUsed)
	:xbEngine(xbEngineStart_OnUse,pLabel,xbEngines(&g_xbThreadCenter,engUsed)),
	m_evTaskNew(xbCtor_NoOp),m_evTaskDone(xbCtor_NoOp),m_hotPlugCb(this)
{
	m_cThreadMax = cThreadMax;
	m_cThreadMin = 1;
	m_ThreadTimeOut = xbDNCENTER_THREADTIMEOUT;
	m_SessionTimeOut = xbDNCENTER_SESSIONTIMEOUT;
	m_cThreadMaxWithTimeOut = xbDNCENTER_THREADMAXWITHTIMEOUT;
	m_cCacheByteMax = xbDNCENTER_CBYTECACHEMAX;
	m_CacheCleanTimeOut = xbDNCENTER_CACHECLEANTIMEOUT; // protected by m_sectInfo
	m_ObjNotUsedTimeOut = xbDNCENTER_OBJNOTUSEDTIMEOUT;

	m_cThreadLongTask = m_cThread = m_cThreadWaiting = m_cThreadPeak = 0;
	m_cThreadDone = 0;
	m_cTaskDone = 0;
	m_bThreadCntValid = true;
	m_bCacheSorted = false;
	m_pCacheFirst = m_pCacheLast = NULL;
	m_cCacheByte = 0;
	m_cCacheBytePeak = 0;
	m_cCacheByteDone = 0;
	m_tiCacheClean = -1;
	m_cCacheObj = 0;
	m_cCacheObjPeak = 0;
	m_cCacheObjDone = 0;
	m_retryTaskChgCnt = m_retryTaskDoneCnt = 0;
	m_retryTaskDoneTi = 0;

	m_cThreadClosed = -1;
}

void xbDnCenter::SetThreadMax(int cThreadMax)
{
	xbAssert(IsReady());
	xbSectLock lck(&m_sect);
	if (cThreadMax < 0) m_cThreadMax = xbDNCENTER_THREADMAX;
	else m_cThreadMax = cThreadMax;
}

int	xbDnCenter::GetThreadMax() const
{
	xbAssert(IsReady());
	xbSectLock lck(&m_sect);
	return m_cThreadMax;
}

void xbDnCenter::SetThreadMin(int cThreadMin)
{
	xbAssert(IsReady());
	xbSectLock lck(&m_sect);
	if (cThreadMin < 1) m_cThreadMin = 1;
	else m_cThreadMin = cThreadMin;
}

int	xbDnCenter::GetThreadMin() const
{
	xbAssert(IsReady());
	xbSectLock lck(&m_sect);
	return m_cThreadMin;
}

void xbDnCenter::SetThreadMaxWithTimeOut(int cThreadMax)
{
	xbAssert(IsReady());
	xbSectLock lck(&m_sect);
	if (cThreadMax < 0) m_cThreadMaxWithTimeOut = xbDNCENTER_THREADMAXWITHTIMEOUT;
	else m_cThreadMaxWithTimeOut = cThreadMax;
}

int	xbDnCenter::GetThreadMaxWithTimeOut() const
{
	xbAssert(IsReady());
	xbSectLock lck(&m_sect);
	return m_cThreadMaxWithTimeOut;
}

void xbDnCenter::SetThreadTimeOut(int val)
{
	xbAssert(IsReady());
	xbSectLock lck(&m_sect);
	if (val < 1) m_ThreadTimeOut = xbDNCENTER_THREADTIMEOUT;
	else m_ThreadTimeOut = val;
}

int	xbDnCenter::GetThreadTimeOut() const
{
	xbAssert(IsReady());
	xbSectLock lck(&m_sect);
	return m_ThreadTimeOut;
}

void xbDnCenter::SetSessionTimeOut(int val)
{
	xbAssert(IsReady());
	xbSectLock lck(&m_sect);
	if (val < 1) m_SessionTimeOut = xbDNCENTER_SESSIONTIMEOUT;
	else m_SessionTimeOut = val;
}

int	xbDnCenter::GetSessionTimeOut() const
{
	xbAssert(IsReady());
	xbSectLock lck(&m_sect);
	return m_SessionTimeOut;
}

void xbDnCenter::SetCacheCleanTimeOut(int val)
{
	xbAssert(IsReady());
	xbSectLock lck(&m_sectInfo);
	if (val < 1) m_CacheCleanTimeOut = xbDNCENTER_CACHECLEANTIMEOUT;
	else m_CacheCleanTimeOut = val;
}

int	xbDnCenter::GetCacheCleanTimeOut() const
{
	xbAssert(IsReady());
	xbSectLock lck(&m_sectInfo);
	return m_CacheCleanTimeOut;
}

void xbDnCenter::SetObjNotUsedTimeOut(int val)
{
	xbAssert(IsReady());
	xbSectLock lck(&m_sect);
	if (val < 1) m_CacheCleanTimeOut = xbDNCENTER_OBJNOTUSEDTIMEOUT;
	else m_CacheCleanTimeOut = val;
}

int	xbDnCenter::GetObjNotUsedTimeOut() const
{
	xbAssert(IsReady());
	xbSectLock lck(&m_sect);
	return m_ObjNotUsedTimeOut;
}

void xbDnCenter::SetCacheSizeMax(int val)
{
	xbAssert(IsReady());
	xbSectLock lck(&m_sect);
	if (val < 8192) m_cCacheByteMax = xbDNCENTER_CBYTECACHEMAX;
	else m_cCacheByteMax = val;
}

int	xbDnCenter::GetCacheSizeMax() const
{
	xbAssert(IsReady());
	xbSectLock lck(&m_sect);
	return m_cCacheByteMax;
}

void xbDnCenter::onStart() 
{
	if (m_evTaskNew.IsEmpty())
		m_evTaskNew.Create();
	if (m_evTaskDone.IsEmpty())
		m_evTaskDone.Create(NULL,NULL); //,xbEventManual);
	
	g_xbCfgEngine.AddPlugTypeCb(m_hotPlugCb);
	m_bThreadCntValid = true;

	sIxbDnPtr *ptr = g_pxbDnWaitingPtr;
	g_pxbDnWaitingPtr = NULL;
	while(ptr)
	{
		sIxbDnPtr *pNext = ptr->_getNextWaiting();
		askOp(ptr,xbDnLoad,xbDnHigh);
		ptr = pNext;
	}
	xbSectLock lck(&m_sect);
	m_bCacheSorted = false;
}

bool xbDnCenter::onStop(bool bForce) 
{
	taskClear();
	threadClear("xbDnCenter stopping");
	m_hotPlugCb.Destroy();
	xbSectLock lck(&m_sect);
	if (!bForce && m_cThread > 0) 
		return false;
	m_evTaskNew.Destroy();
	m_evTaskDone.Destroy();
	cacheClear();
	xbAssert(m_cThreadWaiting==0);
	m_cThread = m_cThreadWaiting = 0;
	m_arrTh.Clear();
	return true;
}

int	xbDnCenter::GetOpWaitingCount() const
{
	xbLock lckStart;
	if (!IsNowStarted(&lckStart)) return 0;

	xbSectLock lck(&m_sect);
	return m_arrHigh.Length + m_arrLow.Length + m_arrNotReady.Length 
		+ m_arrHighLocked.Length + m_arrLowLocked.Length + m_arrNotReadyLocked.Length;
}

int	xbDnCenter::GetOpWaitingLockedCount() const
{
	xbLock lckStart;
	if (!IsNowStarted(&lckStart)) return 0;

	xbSectLock lck(&m_sect);
	return m_arrHighLocked.Length + m_arrLowLocked.Length + m_arrNotReadyLocked.Length;
}

int	xbDnCenter::GetOpWaitingNotReadyCount() const
{
	xbLock lckStart;
	if (!IsNowStarted(&lckStart)) return 0;

	xbSectLock lck(&m_sect);
	return m_arrNotReady.Length + m_arrNotReadyLocked.Length;
}

bool	xbDnCenter::AskOp(sIxbDnPtr *pPtr,xbDnOp op,xbDnPriority priority,bool bWaitProcessMsg,bool bCacheableOnly)
{
	xbLock lckStart(this);
	return askOp(pPtr,op,priority,bWaitProcessMsg,bCacheableOnly);
}

bool	xbDnCenter::askOp(sIxbDnPtr *pPtr,xbDnOp op,xbDnPriority priority,bool bWaitProcessMsg,bool bCacheableOnly)
{
	if (op == xbDnNoop)
		xbThrow xbEsys("xbDnCenter::AskOp() op is noop !");

	xbSectLock	lck(&m_sect);
	xbRefPtr<IxbDnObject> spCacheObj;
	for(;;)
	{
		if (!bCacheableOnly || pPtr->IsCacheable())
		{
			if (cacheFind(pPtr->GetClsId(),pPtr->GetURL(),pPtr->GetURLparseType(),
				&spCacheObj,bCacheableOnly))
			{
				lck.Unlock();
				spCacheObj->AddPtr(pPtr,xbOp_Required);
				return AskOp(spCacheObj,op,NULL,priority,bWaitProcessMsg);
			}
		}
		try
		{
			spCacheObj = pPtr->CreateObject();
			xbString url(spCacheObj->GetURL());
			if (url.IsEmpty())
				xbThrow xbEdn("xbDnCenter::AskOp(), object with empty url !");
			if (op == xbDnLoad)
			{
				if (!spCacheObj->IsLoadable())
					xbThrow xbEdn("xbDnCenter::AskOp(op=xbDnLoad,url="+url+"), object is not loadable !");
			}
			else
			{
				if (!spCacheObj->IsWritable())
					xbThrow xbEdn("xbDnCenter::AskOp(op=xbDnWrite,url="+url+"), object is not writable !");
			}
			cacheAdd(spCacheObj,NULL);
			break;
		}
		catch(xbEmem *pE)
		{
			lck.Unlock();
			xbDbgTraceException(pE,false,__FILE__,__LINE__);
			if (!cacheClean(true))
				throw;
			pE->Delete();
			continue;
		}
		catch(xbEbase *pE)
		{
			lck.Unlock();
			if (xbIsHRmemory(xbGetHR(pE)))
			{
				xbDbgTraceException(pE,false,__FILE__,__LINE__);
				if (!cacheClean(true))
					throw;
				pE->Delete();
				continue;
			}
			throw;
		}
	}
	spCacheObj->setWaitingDnOp(op);
	try
	{
		taskAdd(spCacheObj,priority,lck);
	}
	catch(...)
	{
		eProtectTrace(spCacheObj->setWaitingDnOp(xbDnNoop));
		throw;
	}
	taskNew(priority,lck);
	return false;
}

bool	xbDnCenter::AskOp(IxbDnObject *pObj,xbDnOp op,LPCSTR pszUrl,xbDnPriority priority,bool bWaitProcessMsg)
{
	if (op == xbDnNoop)
		xbThrow xbEsys("xbDnCenter::AskOp() op is noop !");
	bool bUrl = (pszUrl && *pszUrl != 0);
	xbLock lckStart(this);

	xbSectLock	lck(&m_sect);
	xbString url(bUrl?xbString(pszUrl):pObj->GetURL());
	if (op == xbDnLoad)
	{
		if (!pObj->IsLoadable())
			xbThrow xbEdn("xbDnCenter::AskOp(op=xbDnLoad,url="+url+"), object is not loadable !");
	}
	else
	{
		if (!pObj->IsWritable())
			xbThrow xbEdn("xbDnCenter::AskOp(op=xbDnWrite,url="+url+"), object is not writable !");
	}
	if (!bUrl)
	{
		if (url.IsEmpty())
			xbThrow xbEdn("xbDnCenter::AskOp(), object with empty url !");
		if (op == xbDnLoad && pObj->IsLoaded()) 
		{
			cacheAdd(pObj,&lck);
			return true;
		}
		if (op == xbDnWrite && pObj->IsWrited())
		{
			cacheAdd(pObj,&lck);
			return true;
		}
	}
	if (pObj->IsInDnOp())
	{
		if (!pszUrl || *pszUrl == 0) 
			return false;

		pObj->stopDnOp("dnCenter reloading this object ("+pObj->GetURL()+")");

		int64 startTime = -1;
		while(pObj->IsInDnOp())
		{
			lck.Unlock();
			if (startTime < 0) startTime = ::GetTickCount();
			else if (xbDiffTime(::GetTickCount(),ulong(startTime)) > xbDNCENTER_CANCELTIMEOUT)
				xbThrow xbEdn(xbHR_TIMEOUT,"xbDnCenter::AskOp() : abort time out");
			::xbWait(2000,m_evTaskDone,bWaitProcessMsg);
			lck.Lock(&m_sect);
		}
		
	}
	bool bAddTask = true;
	if (pObj->IsWaitingDnOp()) 
		taskRemove(pObj,&bAddTask,priority);
	if (bUrl) 
		pObj->changeUrl(pszUrl);
	cacheAdd(pObj,&lck,bUrl);
	pObj->setWaitingDnOp(op);
	try
	{
		if (bAddTask)
			taskAdd(pObj,priority,lck);
	}
	catch(...)
	{
		eProtectTrace(pObj->setWaitingDnOp(xbDnNoop));
		throw;
	}
	taskNew(priority,lck);
	return false;
}

bool	xbDnCenter::WaitOp(IxbDnObject *pObj,xbDnOp op,int timeOut,bool bWaitProcessMsg)
{
	if (timeOut == 0) 
		timeOut = 60000;

	xbLock lckStart(this);

	if (op == xbDnLoad)
		AskOp(pObj,xbDnLoad,NULL,xbDnFast);

	int64 startTime = -1;
	for(;;)
	{
		{
			xbSectLock	lck(&m_sect);
			if (pObj->ShouldStopDnOp())
				return false;
			if (op == xbDnLoad)
			{
				if (pObj->IsLoaded()) 
					return true;
			}
			else if (op == xbDnWrite)
			{
				if (pObj->IsWrited())
					return true;
			}
			if (!pObj->IsWaitingDnOp() && !pObj->IsInDnOp())
				return false;
		}

		if (startTime < 0) startTime = ::GetTickCount();
		else if (xbDiffTime(::GetTickCount(),ulong(startTime)) > uint(timeOut))
			xbThrow xbEdn(xbHR_TIMEOUT,"xbDnCenter::WaitOp() : time out");
		::xbWait(2000,m_evTaskDone,bWaitProcessMsg);
	}
	return false;
}

bool	xbDnCenter::CancelOp(IxbDnObject *pObj,LPCSTR pReason,xbDnOp op,bool bWaitProcessMsg)
{
	xbLock lckStart;
	if (!IsNowStarted(&lckStart)) 
	{
		xbAssert(!pObj->IsInDnOp() && !pObj->IsWaitingDnOp());
		return false;
	}

	{
		bool bStopped = false;
		xbSectLock lck(&m_sect);
		if (pObj->IsInDnOp())
		{
			if (op != xbDnNoop
				&& pObj->GetWorkingDnOp() != op)
			{
				lck.Unlock();
				//m_evTaskDone.Pulse();
				return false;
			}

			bStopped = true;
			if (pReason && *pReason)
				pObj->stopDnOp("dnCenter cancelling this object ("+pObj->GetURL()+"), "+xbString(pReason));
			else
				pObj->stopDnOp("dnCenter cancelling this object ("+pObj->GetURL()+")");
			lck.Unlock();
			int64 startTime = -1;
			while(pObj->IsInDnOp())
			{
				if (startTime < 0) startTime = ::GetTickCount();
				else if (xbDiffTime(::GetTickCount(),ulong(startTime)) > xbDNCENTER_CANCELTIMEOUT)
					xbThrow xbEdn(xbHR_TIMEOUT,"xbDnCenter::CancelOp() : abort time out");
				::xbWait(2000,m_evTaskDone,bWaitProcessMsg);	
			}
			lck.Lock(&m_sect);		
		}
		if (!pObj->IsWaitingDnOp()
			|| (op != xbDnNoop && pObj->GetWaitingDnOp() != op))
		{
			lck.Unlock();
			//m_evTaskDone.Pulse();
			return false;
		}
		taskRemove(pObj);
		pObj->setWaitingDnOp(xbDnNoop);
		if (!bStopped)
		{
			if (pReason && *pReason)
				pObj->stopDnOp("dnCenter cancelling this object[2] ("+pObj->GetURL()+"), "+xbString(pReason));
			else
				pObj->stopDnOp("dnCenter cancelling this object[2] ("+pObj->GetURL()+")");
		}
	}
	//m_evTaskDone.Pulse();
	return true;
}

bool xbDnCenter::UpdateCacheInfo(IxbDnObject *pObj,bool bAddIfNotInCache)
{
	xbLock lckStart;
	if (bAddIfNotInCache)
		lckStart.Lock(this);
	else
	{
		if (!IsNowStarted(&lckStart)) 
			return false;
	}
	xbSectLock lck(&m_sect);
	if (!bAddIfNotInCache)
	{
		int id = pObj->dnCenter_getCacheId(this);
		if (!id)
			return false;
	}
	cacheAdd(pObj,&lck,true);
	return true;
}

bool xbDnCenter::RemoveFromCache(IxbDnObject *pObj)
{
	xbLock lckStart;
	if (!IsNowStarted(&lckStart)) 
		return false;

	xbSectLock lck(&m_sect);
	int id = pObj->dnCenter_getCacheId(this);
	if (!id)
		return false;

	CacheInfo *pInfo = m_arrCache.GetAt(id-1);
	m_bCacheSorted = false;
	m_arrCache.SetAt(id-1,NULL);
	delete pInfo;
	return true;
}

int xbDnCenter::RetrieveFromCache(const CLSID &clsid,LPCSTR pszUrl,
	int64 urlParseType,xbArray< xbRefPtr<IxbDnObject> > *pArrObj,
	bool bCacheableOnly)
{
	xbLock lckStart;
	if (!IsNowStarted(&lckStart)) 
		return 0;

	xbSectLock lck(&m_sect);
	int cnt = cacheFind(clsid,pszUrl,urlParseType,pArrObj,true);

	if (!bCacheableOnly)
		cnt += cacheFind(clsid,pszUrl,urlParseType,pArrObj,false);

	return cnt;
}

int xbDnCenter::RemoveFromCache(const CLSID &clsid,LPCSTR pszUrl,
		int64 urlParseType,bool bCacheableOnly)
{
//	xbLock lckStart;
//	if (!IsNowStarted(&lckStart)) 
//		return 0;

	xbArray< xbRefPtr<IxbDnObject> >	arrObj;
	int cnt = 0;
	{
		xbSectLock lck(&m_sect);
		cacheFind(clsid,pszUrl,urlParseType,&arrObj,true);

		if (!bCacheableOnly)
			cacheFind(clsid,pszUrl,urlParseType,&arrObj,false);

		xbRefPtr<IxbDnObject> *pprObj = arrObj.Begin();
		xbRefPtr<IxbDnObject> *pprEnd = arrObj.End();

		while(pprObj < pprEnd)
		{
			xbRefPtr<IxbDnObject> &prObj = *(pprObj++);

			if (prObj->IsInDnOp())
			{
				lck.Unlock();

				eProtectTrace(CancelOp(prObj,"removed from cache",xbDnNoop,true););

				lck.Lock(&m_sect);
//				eProtectTrace(prObj->stopDnOp());
			}

			int id = prObj->dnCenter_getCacheId(this);
			if (!id)
			{
				xbRelOutput("ERROR: xbDnCenter::RemoveFromCache("+xbString(clsid)+", url="+xbString(pszUrl?pszUrl:"All")+") : object (url="+prObj->GetURL()+") in cache with no id !");
				continue;
			}
			xbDbgOutput("xbDnCenter::RemoveFromCache("+xbString(clsid)+", url="+xbString(pszUrl?pszUrl:"All")+") : object (url="+prObj->GetURL()+") remove from cache");

			CacheInfo *pInfo = m_arrCache.GetAt(id-1);
			m_bCacheSorted = false;
			m_arrCache.SetAt(id-1,NULL);
			delete pInfo;

			cnt++;
		}
	}

	return cnt;
}

bool xbDnCenter::RetrieveFromCache(const CLSID &clsid,const xbString &url,int64 urlParseType,xbRefPtr<IxbDnObject> *ppObj,bool bCacheableOnly)
{
	xbLock lckStart;
	if (!IsNowStarted(&lckStart)) 
		return false;

	return cacheFind(clsid,url,urlParseType,ppObj,bCacheableOnly);
}

void	xbDnCenter::taskAdd(IxbDnObject *pObj,xbDnPriority priority,xbSectLock &lck)
{
	for(;;)
	{
		xbAssert(lck.IsLocked());
		try
		{
			xbObsPtr<IxbDnObject>		*ptr = NULL;
			xbObsRefPtr<IxbDnObject>	*ptrLocked = NULL;
			if (xbDnIsLockedPriority(priority))
			{
				ptrLocked = xbDnew xbObsRefPtr<IxbDnObject>(pObj);
			}
			else
			{
				ptr = xbDnew xbObsPtr<IxbDnObject>(pObj);
			}
			try
			{
				switch(priority)
				{
				case xbDnFast :
					if (m_arrHigh.IsEmpty()) m_arrHigh.Add(ptr);
					else m_arrHigh.Insert(0,ptr);
					break;
				case xbDnFastLocked :
					if (m_arrHighLocked.IsEmpty()) m_arrHighLocked.Add(ptrLocked);
					else m_arrHighLocked.Insert(0,ptrLocked);
					break;
				case xbDnNotReady :
					m_arrNotReady.Add(ptr);
					break;
				case xbDnNotReadyLocked :
					m_arrNotReadyLocked.Add(ptrLocked);
					break;
				case xbDnHigh :
					m_arrHigh.Add(ptr);
					break;
				case xbDnHighLocked :
					m_arrHighLocked.Add(ptrLocked);
					break;
				case xbDnLow :
					m_arrLow.Add(ptr);
					break;
				case xbDnLowLocked :
					m_arrLowLocked.Add(ptrLocked);
					break;
				default:
					xbThrow xbEdn("xbDnCenter::taskAdd() : bad priority");
				}
			}
			catch(...)
			{
				if (ptrLocked)
					delete ptrLocked;
				if (ptr)
					delete ptr;
				throw;
			}
		}
		catch(xbEmem *pE)
		{
			lck.Unlock();
			xbDbgTraceException(pE,false,__FILE__,__LINE__);
			if (!cacheClean(true))
				throw;
			pE->Delete();
			lck.Lock(&m_sect);
			continue;
		}
		catch(xbEbase *pE)
		{
			lck.Unlock();
			if (xbIsHRmemory(xbGetHR(pE)))
			{
				xbDbgTraceException(pE,false,__FILE__,__LINE__);
				if (!cacheClean(true))
					throw;
				pE->Delete();
				lck.Lock(&m_sect);
				continue;
			}
			throw;
		}
		break;
	}
}

void	xbDnCenter::taskRemove(IxbDnObject *pObj,bool *pbToAdd,xbDnPriority priority)
{
	xbSectLock	lck(&m_sect);
	if (m_arrNotReady.IsValid())
	{
		xbObsPtr<IxbDnObject> **ptr = m_arrNotReady.Begin();
		xbObsPtr<IxbDnObject> **pend = m_arrNotReady.End();
		int							no = 0;
		while(ptr < pend)
		{
			if ((**ptr) == pObj)
			{
				if (pbToAdd
					 && priority == xbDnNotReady)
				{
					*pbToAdd = false;
					return;
				}
				m_arrNotReady.Delete(no);
				if (pbToAdd)
					*pbToAdd = true;
				return;
			}
			no++;
			ptr++;
		}
	}
	if (m_arrNotReadyLocked.IsValid())
	{
		xbObsRefPtr<IxbDnObject> **ptr = m_arrNotReadyLocked.Begin();
		xbObsRefPtr<IxbDnObject> **pend = m_arrNotReadyLocked.End();
		int							no = 0;
		while(ptr < pend)
		{
			if ((**ptr) == pObj)
			{
				if (pbToAdd
					 && priority == xbDnNotReadyLocked)
				{
					*pbToAdd = false;
					return;
				}
				m_arrNotReadyLocked.Delete(no);
				if (pbToAdd)
					*pbToAdd = true;
				return;
			}
			no++;
			ptr++;
		}
	}
	if (m_arrHigh.IsValid())
	{
		xbObsPtr<IxbDnObject> **ptr = m_arrHigh.Begin();
		xbObsPtr<IxbDnObject> **pend = m_arrHigh.End();
		int							no = 0;
		while(ptr < pend)
		{
			if ((**ptr) == pObj)
			{
				if (pbToAdd
					 && !xbDnIsLockedPriority(priority))
				{
					*pbToAdd = false;
					return;
				}
				m_arrHigh.Delete(no);
				if (pbToAdd)
					*pbToAdd = true;
				return;
			}
			no++;
			ptr++;
		}
	}

	if (m_arrHighLocked.IsValid())
	{
		xbObsRefPtr<IxbDnObject> **ptr = m_arrHighLocked.Begin();
		xbObsRefPtr<IxbDnObject> **pend = m_arrHighLocked.End();
		int							no = 0;
		while(ptr < pend)
		{
			if ((**ptr) == (IUnknown*)pObj)
			{
				if (pbToAdd
					 && xbDnIsLockedPriority(priority))
				{
					*pbToAdd = false;
					return;
				}
				m_arrHighLocked.Delete(no);
				if (pbToAdd)
					*pbToAdd = true;
				return;
			}
			no++;
			ptr++;
		}
	}

	if (m_arrLow.IsValid())
	{
		xbObsPtr<IxbDnObject> **ptr = m_arrLow.Begin();
		xbObsPtr<IxbDnObject> **pend = m_arrLow.End();
		int							no = 0;
		while(ptr < pend)
		{
			if ((**ptr) == pObj)
			{
				if (pbToAdd && priority == xbDnLow)
				{
					*pbToAdd = false;
					return;
				}
				m_arrLow.Delete(no);
				if (pbToAdd)
					*pbToAdd = true;
				return;
			}
			no++;
			ptr++;
		}
	}

	if (m_arrLowLocked.IsValid())
	{
		xbObsRefPtr<IxbDnObject> **ptr = m_arrLowLocked.Begin();
		xbObsRefPtr<IxbDnObject> **pend = m_arrLowLocked.End();
		int							no = 0;
		while(ptr < pend)
		{
			if ((**ptr) == (IUnknown*)pObj)
			{
				if (pbToAdd && priority == xbDnLowLocked)
				{
					*pbToAdd = false;
					return;
				}
				m_arrLowLocked.Delete(no);
				if (pbToAdd)
					*pbToAdd = true;
				return;
			}
			no++;
			ptr++;
		}
	}
	if (pbToAdd)
		*pbToAdd = true;
	xbDbgOutput("xbDnCenter::taskRemove() : object "+xbString(pObj->GetURL())+" not found in tasks");
}

int	xbDnCenter::taskRetryNotReady()
{
	xbSectLock lck(&m_sect);
	if (m_retryTaskChgCnt == m_retryTaskDoneCnt)
	{
		if (m_arrNotReady.IsEmpty() && m_arrNotReadyLocked.IsEmpty())
			return 0;
		ulong tick = ::GetTickCount();
		if (!m_retryTaskDoneTi)
		{
			m_retryTaskDoneTi = tick;
			return 0;
		}
		//if (g_xbHotPlugCenter.GetInUpdateCnt() <= 0)
		{
			if (::xbDiffTime(tick,m_retryTaskDoneTi) < 30000)
				return 0;
		}
		/*else
		{
			if (::xbDiffTime(tick,m_retryTaskDoneTi) < 300000)
				return 0;
		}*/
		::xbRelOutput("xbDnCenter::taskRetryNotReady: "+xbString(m_arrNotReady.Length)+" tasks and "+xbString(m_arrNotReadyLocked.Length)+" locked to retry after "+xbString(::xbDiffTime(tick,m_retryTaskDoneTi))+"ms of inactivity"); 
	}
	m_retryTaskDoneTi = 0;
	m_retryTaskDoneCnt = m_retryTaskChgCnt;

	int	cTask = 0;

	xbDbgOutput("  !! xbDnCenter::taskRetryNotReady() called, clear cache and retry plugin failures");
//	cacheClear();
	while(m_arrNotReady.IsValid())
	{
		xbRefPtr<IxbDnObject> prObj;
		bool bOk = m_arrNotReady.First()->GetPtr(&prObj);
		m_arrNotReady.Delete(0);
		if (!bOk) 
			continue;
		xbDbgOutput("  !! xbDnCenter::taskRetryNotReady() -> retry download task (notready / url"+prObj->GetURL()+")");
		taskAdd(prObj,xbDnHigh,lck);
		cTask++;
	}
	while(m_arrNotReadyLocked.IsValid())
	{
		xbRefPtr<IxbDnObject> prObj;
		bool bOk = m_arrNotReadyLocked.First()->GetPtr(&prObj);
		m_arrNotReadyLocked.Delete(0);
		if (!bOk) 
			continue;
		xbDbgOutput("  !! xbDnCenter::taskRetryNotReady() -> retry download task (notreadyLocked / url"+prObj->GetURL()+")");
		taskAdd(prObj,xbDnHighLocked,lck);
		cTask++;
	}
	
	for (int i = 0; i < cTask && i < xbDNCENTER_THREADMAX 
		&& (m_cThreadMax <= 0 || i < m_cThreadMax); i++)
	{
		if (!lck.IsLocked())
			lck.Lock(&m_sect);
		taskNew(xbDnHigh,lck);
	}

	return cTask;
}

void	xbDnCenter::taskClear()
{
	xbSectLock lck(&m_sect);
	{
		xbObsPtr<IxbDnObject> **ptr = m_arrNotReady.Begin();
		xbObsPtr<IxbDnObject> **pend = m_arrNotReady.End();
		while(ptr < pend)
		{
			xbRefPtr<IxbDnObject> prObj;
			if ((*ptr)->GetPtr(&prObj))
			{
				xbAssert(prObj->IsWaitingDnOp());
				prObj->setWaitingDnOp(xbDnNoop);
				xbDbgOutput("  !! xbDnCenter::taskClear() -> download task cancel (notready / url"+prObj->GetURL()+")");
			}
			(*ptr)->Empty();
			ptr++;
		}
	}

	{
		xbObsRefPtr<IxbDnObject> **ptr = m_arrNotReadyLocked.Begin();
		xbObsRefPtr<IxbDnObject> **pend = m_arrNotReadyLocked.End();
		while(ptr < pend)
		{
			xbRefPtr<IxbDnObject> prObj;
			if ((*ptr)->GetPtr(&prObj))
			{
				xbAssert(prObj->IsWaitingDnOp());
				prObj->setWaitingDnOp(xbDnNoop);
				xbDbgOutput("  !! xbDnCenter::taskClear() -> download task cancel (notreadyLocked / url"+prObj->GetURL()+")");
			}
			(*ptr)->Empty();
			ptr++;
		}
	}

	{
		xbObsPtr<IxbDnObject> **ptr = m_arrHigh.Begin();
		xbObsPtr<IxbDnObject> **pend = m_arrHigh.End();
		while(ptr < pend)
		{
			xbRefPtr<IxbDnObject> prObj;
			if ((*ptr)->GetPtr(&prObj))
			{
				xbAssert(prObj->IsWaitingDnOp());
				prObj->setWaitingDnOp(xbDnNoop);
				xbDbgOutput("  !! xbDnCenter::taskClear() -> download task cancel (high / url"+prObj->GetURL()+")");
			}
			(*ptr)->Empty();
			ptr++;
		}
	}

	{
		xbObsRefPtr<IxbDnObject> **ptr = m_arrHighLocked.Begin();
		xbObsRefPtr<IxbDnObject> **pend = m_arrHighLocked.End();
		while(ptr < pend)
		{
			xbRefPtr<IxbDnObject> prObj;
			if ((*ptr)->GetPtr(&prObj))
			{
				xbAssert(prObj->IsWaitingDnOp());
				prObj->setWaitingDnOp(xbDnNoop);
				xbDbgOutput("  !! xbDnCenter::taskClear() -> download task cancel (highLocked / url"+prObj->GetURL()+")");
			}
			(*ptr)->Empty();
			ptr++;
		}
	}

	{
		xbObsPtr<IxbDnObject> **ptr = m_arrLow.Begin();
		xbObsPtr<IxbDnObject> **pend = m_arrLow.End();
		while(ptr < pend)
		{
			xbRefPtr<IxbDnObject> prObj;
			if ((*ptr)->GetPtr(&prObj))
			{
				xbAssert(prObj->IsWaitingDnOp());
				prObj->setWaitingDnOp(xbDnNoop);
				xbDbgOutput("  !! xbDnCenter::taskClear() -> download task cancel (low / url"+prObj->GetURL()+")");
			}
			(*ptr)->Empty();
			ptr++;
		}
	}

	{
		xbObsRefPtr<IxbDnObject> **ptr = m_arrLowLocked.Begin();
		xbObsRefPtr<IxbDnObject> **pend = m_arrLowLocked.End();
		int							no = 1;
		while(ptr < pend)
		{
			xbRefPtr<IxbDnObject> prObj;
			if ((*ptr)->GetPtr(&prObj))
			{
				xbAssert(prObj->IsWaitingDnOp());
				prObj->setWaitingDnOp(xbDnNoop);
				xbDbgOutput("  !! xbDnCenter::taskClear() -> download task cancel (lowLocked / url"+prObj->GetURL()+")");
			}
			(*ptr)->Empty();
			ptr++;
		}
	}
	m_arrNotReady.Clear();
	m_arrNotReadyLocked.Clear();
	m_arrHigh.Clear();
	m_arrHighLocked.Clear();
	m_arrLow.Clear();
	m_arrLowLocked.Clear();
}

void	xbDnCenter::taskNew(xbDnPriority priority,xbSectLock &lck)
{
	xbAssert(lck.IsLocked());
	if (m_cThreadWaiting <= 0 
		&& (priority == xbDnFast || priority == xbDnFastLocked 
		|| m_cThreadMax <= 0 || (m_cThread-m_cThreadLongTask) < m_cThreadMax))
	{
		if ((m_cThread-m_cThreadLongTask) > 0 && m_cThreadClosed >= 0
			&& m_cThreadClosed == g_xbThreadCenter.GetThreadClosedCount())
		{
			lck.Unlock();
			m_evTaskNew.Set();
			xbDbgOutput("  !! xbDownloadCenter::taskNew() :");
			return;
		}
		m_cThreadClosed = -1;

/*		if (g_xbExceptionEngine.IsToLog("xbEdn"))
		{
			if (priority == xbDnFast || priority == xbDnFastLocked) 
				xbRelOutput("  !! xbDnCenter::taskNew() called for fastest priority,  creating new thread (th="+xbString(m_cThread)+" / max="+xbString(m_cThreadMax)+" / waiting="+xbString(m_cThreadWaiting)+")");
			else
				xbRelOutput("  !! xbDnCenter::taskNew() called, creating new thread (th="+xbString(m_cThread)+" / max="+xbString(m_cThreadMax)+" / waiting="+xbString(m_cThreadWaiting)+")");
		}*/

		try
		{
			m_cThread++;
			try
			{
				int			thNo;
				Thread		*pTh;
				{
					ThreadInfo *pThInfo = NULL;
					{
						int no = 1;
						ThreadInfo *ptr = m_arrTh.Begin();
						ThreadInfo *pend = m_arrTh.Begin();
						while(ptr < pend)
						{
							if (ptr->m_bDestroyed)
							{
								pThInfo = ptr;
								thNo = no;
								break;
							}
							ptr++;
							no++;
						}
					}
					if (!pThInfo)
					{
						thNo = m_arrTh.IncLength();
						pThInfo = &m_arrTh.GetAt(thNo);
					}
					pTh = pThInfo->m_pTh;
					pThInfo->m_bDestroyed = false;
					pThInfo->m_bInCreate = true;
				}
				lck.Unlock();

				try
				{
					if (!pTh)
					{
						pTh = xbDnew Thread(this,thNo);
						try
						{
							lck.Lock(&m_sect);
							m_arrTh.GetAt(thNo-1).m_pTh = pTh;
							lck.Unlock();
						}
						catch(...)
						{
							eProtectTrace(delete pTh;);
							throw;
						}
					}

					pTh->Create();
					
					lck.Lock(&m_sect);
					if (m_cThread > m_cThreadPeak)
						m_cThreadPeak = m_cThread;
				}
				catch(...)
				{
					lck.Lock(&m_sect);
					ThreadInfo *pThInfo = &m_arrTh.GetAt(thNo-1);
					pThInfo->m_bDestroyed = true;
					pThInfo->m_bInCreate = false;
					throw;
				}
			}
			catch(...)
			{
				lck.Lock(&m_sect);
				m_cThread--;
				throw;
			}
		}
		catch(xbEbase *pE)
		{
			lck.Lock(&m_sect);
			if (m_cThread <= 0) throw;
			m_cThreadClosed = g_xbThreadCenter.GetThreadClosedCount();
			xbDbgOutput("  !! xbDownloadCenter::taskNew(), creating thread : exception : "+xbGetMsg(NULL,pE));
			pE->Delete();
		}
	}
#ifdef _DEBUG
	else if (priority == xbDnFast || priority == xbDnFastLocked)
			xbDbgOutput("  !! xbDnCenter::taskNew() called for fastest priority, no thread created (th="+xbString(m_cThread)+" / max="+xbString(m_cThreadMax)+" / waiting="+xbString(m_cThreadWaiting)+")");
#endif
	lck.Unlock();
	m_evTaskNew.Set();
}

void	xbDnCenter::cacheAdd(IxbDnObject *pObj,xbSectLock *pLck,bool bUrlChg)
{
	xbSectLock lck;
	if (!pLck) lck.Lock(&m_sect);

	for(;;)
	{
		xbAssert(!pLck || pLck->IsLocked());
		try
		{
			CacheInfo	*pInfo = NULL;
			int id = pObj->dnCenter_getCacheId(this);
			if (id) 
			{
				pInfo = m_arrCache.unsafeGetAt(id-1);
				xbAssert(pInfo->m_id == id && xbRefPtr<IxbDnObject>(pObj) == (IUnknown*)pInfo->m_pObj);
			}
			else
			{
				pInfo = xbDnew CacheInfo(this);
				try
				{
					CacheInfo	**pptr = m_arrCache.Begin();
					CacheInfo	**pend = m_arrCache.End();
					int no = 1;
					while(pptr < pend)
					{
						if (!*pptr)
						{
							id = no;
							*pptr = pInfo;
							break;
						}
						pptr++;
						no++;
					}

					if (!id)
					{
						id = m_arrCache.Add(pInfo)+1;
					}
					pInfo->m_id = id;
					pObj->dnCenter_cache(this,id);
					pInfo->m_pObj = pObj;
				}
				catch(...)
				{
					if (id)
					{
						m_arrCache.SetAt(id-1,NULL);
					}
					delete pInfo;
					throw;
				}
				bUrlChg = true;
			}
			if (bUrlChg)
			{
				pInfo->m_bCacheable = pObj->IsCacheable();
//				if (pInfo->m_bCacheable)
//				{
					pInfo->m_url = pObj->GetURL();
					pInfo->m_pClsId = &pObj->GetClsId();
					pInfo->m_urlParseType = pObj->GetURLparseType();
/*				}
				else
				{
					pInfo->m_url.Empty();
					pInfo->m_pClsId = NULL;
					pInfo->m_urlParseType = 0;
				}*/
				m_bCacheSorted = false;
			}
			pInfo->Access();
		}
		catch(xbEmem *pE)
		{
			if (!pLck) throw;
			pLck->Unlock();
			xbDbgTraceException(pE,false,__FILE__,__LINE__);
			if (!cacheClean(true))
				throw;
			pE->Delete();
			pLck->Lock(&m_sect);
			continue;
		}
		catch(xbEbase *pE)
		{
			if (!pLck) throw;
			pLck->Unlock();
			if (xbIsHRmemory(xbGetHR(pE)))
			{
				xbDbgTraceException(pE,false,__FILE__,__LINE__);
				if (!cacheClean(true))
					throw;
				pE->Delete();
				pLck->Lock(&m_sect);
				continue;
			}
			throw;
		}
		break;
	}
}

bool	xbDnCenter::cacheClean(bool bForce)
{
	ulong ti = ::GetTickCount();
	{
		xbSectLock lck(&m_sectInfo);
		if (!bForce && m_tiCacheClean >= 0)
		{
			if (xbDiffTime(ti,ulong(m_tiCacheClean)) < m_CacheCleanTimeOut)
				return false;

		}
		m_tiCacheClean = ti;
	}

	bool bRet = false;
	eProtectBegin();

	{
		xbSectLock lck(&m_sect);
		xbDbgOutput("  !! xbDnCenter::cacheClean(force="+xbString(bForce)+") called (bytes="+xbString(m_cCacheByte)+" / max="+xbString(m_cCacheByteMax)+") (th="+xbString(m_cThread)+" / max="+xbString(m_cThreadMax)+" / waiting="+xbString(m_cThreadWaiting)+")");
		CacheInfo *pCache = m_pCacheLast;
		if (!pCache)
		{
			xbAssert(m_pCacheFirst == NULL);
			return false;
		}

		bool ret = false;
		int	cntOk = 0;
		int	cntOkLim = m_cCacheObj;
		if (cntOkLim > 5)
			cntOkLim = 5;
		CacheInfo *pCur;
		while(pCache)
		{
			pCur = pCache;
			pCache = pCache->m_pPrev;
			if (!pCur->m_pObj)
			{
				xbDbgOutput("  !! xbDnCenter::cacheClean(force="+xbString(bForce)+") -> empty cache struct id="+xbString(pCur->m_id)+", deleted");
				m_bCacheSorted = false;
				m_arrCache.SetAt(pCur->m_id-1,NULL);
				delete pCur;
				bRet = true;
				continue;
			}
			if (!bForce 
				&& m_cCacheByte <= m_cCacheByteMax
				&& pCur->m_bCacheable
				&& xbDiffTime(ti,ulong(pCur->m_tiAccess)) < m_ObjNotUsedTimeOut)
				continue;

			if (!pCur->m_pObj->dnCenter_tryDel(this,pCur->m_id))
			{
				xbDbgOutput("  !! xbDnCenter::cacheClean(force="+xbString(bForce)+") -> object with cache (id="+xbString(pCur->m_id)+" / url"+pCur->m_url+") is used, kept");
				pCur->Access();
				if (++cntOk >= cntOkLim)
					break;
				continue;
			}
			xbDbgOutput("  !! xbDnCenter::cacheClean(force="+xbString(bForce)+") -> object with cache (id="+xbString(pCur->m_id)+" / url"+pCur->m_url+") not used, deleted");
			pCur->m_pObj = NULL;
			m_bCacheSorted = false;
			m_arrCache.SetAt(pCur->m_id-1,NULL);
			delete pCur;
			bRet = true;
		}
	}
	eProtectEndTrace();
	return bRet;
}

void	xbDnCenter::cacheClear()
{
	xbSectLock lck(&m_sect);
	xbDbgOutput("  !! xbDnCenter::cacheClear() called (bytes="+xbString(m_cCacheByte)+" / max="+xbString(m_cCacheByteMax)+") (th="+xbString(m_cThread)+" / max="+xbString(m_cThreadMax)+" / waiting="+xbString(m_cThreadWaiting)+")");
	m_arrCache.Clear();
	m_arrCacheSort.Clear();
	m_bCacheSorted = false;
	xbAssert(m_cCacheByte == 0);
	xbAssert(m_cCacheObj == 0);
}

struct xbDnFindInfo
{
	const CLSID *pId;
	xbString	url;
	int64		urlParseType;
};

int xbDnCenter::cacheFindCb(CacheInfo* const &pInfo,const void *_pFind)
{
	const xbDnFindInfo	*pFind = (const xbDnFindInfo *)_pFind;

	if (!pInfo) return 1;
	if (!pInfo->m_bCacheable) return 1;
	int ret = memcmp(pInfo->m_pClsId,pFind->pId,sizeof(*pFind->pId));
	if (ret != 0) return ret;

	ret = pInfo->m_url.Compare(pFind->url);
	if (ret != 0) return ret;

	if (pInfo->m_urlParseType > pFind->urlParseType) return 1;
	if (pInfo->m_urlParseType < pFind->urlParseType) return -1;
	return 0;
}

int xbDnCenter::cacheFindNotCacheableCb(CacheInfo* const &pInfo,const void *_pFind)
{
	const xbDnFindInfo	*pFind = (const xbDnFindInfo *)_pFind;

	if (!pInfo) return 1;
	if (pInfo->m_bCacheable) return -1;
	int ret = memcmp(pInfo->m_pClsId,pFind->pId,sizeof(*pFind->pId));
	if (ret != 0) return ret;

	ret = pInfo->m_url.Compare(pFind->url);
	if (ret != 0) return ret;

	if (pInfo->m_urlParseType > pFind->urlParseType) return 1;
	if (pInfo->m_urlParseType < pFind->urlParseType) return -1;
	return 0;
}

int xbDnCenter::cacheSortCb(CacheInfo* const &pInfo1,CacheInfo* const &pInfo2)
{
	if (!pInfo1 || !pInfo2)
	{
		if (!pInfo1 && !pInfo2) return 0;
		if (!pInfo1) return 1;
		return -1;
	}
	if (!pInfo1->m_bCacheable || !pInfo2->m_bCacheable)
	{
		if (pInfo1->m_bCacheable || pInfo2->m_bCacheable)
		{
			if (!pInfo1->m_bCacheable) return 1;
			return -1;
		}
	}
	int ret = memcmp(pInfo1->m_pClsId,pInfo2->m_pClsId,sizeof(*pInfo2->m_pClsId));
	if (ret != 0) return ret;

	ret = pInfo1->m_url.Compare(pInfo2->m_url);
	if (ret != 0) return ret;

	if (pInfo1->m_urlParseType > pInfo2->m_urlParseType) return 1;
	if (pInfo1->m_urlParseType < pInfo2->m_urlParseType) return -1;
	return 0;
}

int xbDnCenter::cacheFindExtCb(CacheInfo* const &pInfo,const void *_pFind)
{
	const xbDnFindInfo	*pFind = (const xbDnFindInfo *)_pFind;

	if (!pInfo) return 1;
	if (!pInfo->m_bCacheable) return 1;
	int ret = memcmp(pInfo->m_pClsId,pFind->pId,sizeof(*pFind->pId));
	if (ret != 0) return ret;

	if (pFind->url.IsEmpty())
		return 0;

	if (pFind->urlParseType<0)
	{
		return strncmp(pInfo->m_url,pFind->url,pFind->url.Length);
	}
	ret = strcmp(pInfo->m_url,pFind->url);
	if (ret != 0) return ret;

	if (pInfo->m_urlParseType > pFind->urlParseType) return 1;
	if (pInfo->m_urlParseType < pFind->urlParseType) return -1;
	return 0;
}

int xbDnCenter::cacheFindNotCacheableExtCb(CacheInfo* const &pInfo,const void *_pFind)
{
	const xbDnFindInfo	*pFind = (const xbDnFindInfo *)_pFind;

	if (!pInfo) return 1;
	if (pInfo->m_bCacheable) return -1;
	int ret = memcmp(pInfo->m_pClsId,pFind->pId,sizeof(*pFind->pId));
	if (ret != 0) return ret;

	if (pFind->url.IsEmpty())
		return 0;

	if (pFind->urlParseType<0)
	{
		return strncmp(pInfo->m_url,pFind->url,pFind->url.Length);
	}
	ret = strcmp(pInfo->m_url,pFind->url);
	if (ret != 0) return ret;

	if (pInfo->m_urlParseType > pFind->urlParseType) return 1;
	if (pInfo->m_urlParseType < pFind->urlParseType) return -1;
	return 0;
}

int xbDnCenter::cacheFind(const CLSID &clsid,LPCSTR pszUrl,
	int64 urlParseType,xbArray< xbRefPtr<IxbDnObject> > *pArrObj,
	bool bCacheable)
{
	xbDnFindInfo	info;
	info.pId = &clsid;
	if (pszUrl)
		info.url = pszUrl;
	info.urlParseType = urlParseType;
	xbSectLock lck(&m_sect);
	if (!m_bCacheSorted)
	{
		xbDbgOutput("xbDnCenter::cacheFind(clsid="+xbString(clsid)+", url="+xbString(pszUrl?pszUrl:"All")+") -> sort cache");
		m_arrCacheSort.CopyPointersFrom(m_arrCache);
		m_arrCacheSort.Sort(cacheSortCb);
		m_bCacheSorted = true;
	}
	int pos;
	CacheInfo **pInfo;
	if (bCacheable)
		pInfo = m_arrCacheSort.SortedFind(&info,cacheFindExtCb,&pos);
	else
		pInfo = m_arrCacheSort.SortedFind(&info,cacheFindNotCacheableExtCb,&pos);
	if (!pInfo) 
		return 0;

	int cnt = 0;

	while(pos > 0)
	{
		if (bCacheable)
		{
			if (cacheFindExtCb(*(pInfo-1),&info) < 0)
				break;
		}
		else
		{
			if (cacheFindNotCacheableExtCb(*(pInfo-1),&info) < 0)
				break;
		}
		pInfo--;
		pos--;
	}

	CacheInfo **pEnd = m_arrCacheSort.End();
	while(pInfo < pEnd)
	{
		if (bCacheable)
		{
			if (cacheFindExtCb(*pInfo,&info) > 0)
				break;
		}
		else
		{
			if (cacheFindNotCacheableExtCb(*pInfo,&info) > 0)
				break;
		}

		if (pArrObj)
		{
			pArrObj->Add((*pInfo)->m_pObj);
			(*pInfo)->m_pObj->dnCenter_cache(this,(*pInfo)->m_id);
		}
		xbDbgOutput("xbDnCenter::cacheFind(clsid="+xbString(clsid)+", url="+xbString(pszUrl?pszUrl:"All")+" -> object "+(*pInfo)->m_pObj->GetURL()+" retrieve from cache");
		
		cnt++;
		pInfo++;
	}


	return cnt;
}

bool	xbDnCenter::cacheFind(const CLSID &id,const xbString &url,int64 urlParseType,xbRefPtr<IxbDnObject> *ppObj,bool bCacheableOnly)
{
	xbDnFindInfo	info;
	info.pId = &id;
	info.url = url;
	info.urlParseType = urlParseType;
	xbSectLock lck(&m_sect);
	if (!m_bCacheSorted)
	{
		xbDbgOutput("xbDnCenter::cacheFind(url="+xbString(url)+") -> sort cache");
		m_arrCacheSort.CopyPointersFrom(m_arrCache);
		m_arrCacheSort.Sort(cacheSortCb);
		m_bCacheSorted = true;
	}
	CacheInfo **pInfo = m_arrCacheSort.SortedFind(&info,cacheFindCb);
	if (!pInfo) 
	{
		if (bCacheableOnly)
			return false;
		pInfo = m_arrCacheSort.SortedFind(&info,cacheFindNotCacheableCb);
		if (!pInfo)
			return false;
	}
	*ppObj = (*pInfo)->m_pObj;
	(*pInfo)->m_pObj->dnCenter_cache(this,(*pInfo)->m_id);
	xbDbgOutput("xbDnCenter::cacheFind(url="+xbString(url)+") -> object retrieve from cache");
	return true;
}

void	xbDnCenter::CacheInfo::Link()
{
	if (m_bLinked) return;
	if (m_pParent->m_pCacheFirst) 
	{
		xbAssert(m_pParent->m_pCacheFirst->m_pPrev == NULL);
		m_pParent->m_pCacheFirst->m_pPrev = this;
	}
	if (!m_pParent->m_pCacheLast) 
	{
		xbAssert(m_pParent->m_pCacheFirst == NULL);
		m_pParent->m_pCacheLast = this;
	}
	else
	{
		xbAssert(m_pParent->m_pCacheLast->m_pNext == NULL);
	}
	m_pPrev = NULL;
	m_pNext = m_pParent->m_pCacheFirst;
	m_pParent->m_pCacheFirst = this;
	m_bLinked = true;
}

void	xbDnCenter::CacheInfo::Unlink()
{
	if (!m_bLinked) return;
	if (m_pNext) 
		m_pNext->m_pPrev = m_pPrev;
	else 
	{
		xbAssert(m_pParent->m_pCacheLast == this);
		m_pParent->m_pCacheLast = m_pPrev;
	}

	if (m_pPrev) 
		m_pPrev->m_pNext = m_pNext;
	else 
	{
		xbAssert(m_pParent->m_pCacheFirst == this);
		m_pParent->m_pCacheFirst = m_pNext;
	}
	m_pPrev = m_pNext = NULL;
	m_bLinked = false;
}

void	xbDnCenter::CacheInfo::Access()
{
	if (!m_bLinked || m_pParent->m_pCacheFirst != this)
	{
		if (m_bLinked) Unlink();
		Link();
	}

	m_tiAccess = ::GetTickCount();
	int newSize = m_pObj->GetMemSizeForCache();
	if (!xbMustBe(newSize>0))
		newSize = 0;
	if (newSize != m_cByteObj)
	{
		m_pParent->m_cCacheByte -= m_cByteObj;
		xbAssert(m_pParent->m_cCacheByte >= 0);
		m_pParent->m_cCacheByte += newSize;
		m_cByteObj = newSize;
		if (m_pParent->m_cCacheByte > m_pParent->m_cCacheBytePeak)
			m_pParent->m_cCacheBytePeak = m_pParent->m_cCacheByte;
	}
}

bool	xbDnCenter::threadWaitForNewTask(Thread *pTh,xbRefPtr<IxbDnObject> *pptr,xbDnPriority *ppriority,xbDnOp *pop)
{
	bool	bThreadWaitingInc = false;
	bool	ret = true;
	try
	{
		for(;;)
		{
			if (!pTh->setBusy(xbOp_Optional)) 
			{
				ret = false;
				xbSectLock	lck(&m_sect);
				m_bThreadCntValid = false;
				break;
			}
			taskRetryNotReady();
			xbSectLock	lck(&m_sect);
			if (!m_arrHigh.IsEmpty())
			{
				bool ok = m_arrHigh.First()->GetPtr(pptr);
				m_arrHigh.Delete(0);
				if (!ok) continue;
				xbDnOp op = (*pptr)->GetWaitingDnOp();
				if (op == xbDnNoop)
				{
					*pptr = NULL;
					continue;
				}
				*pop = op;
				*ppriority = xbDnHigh;
				(*pptr)->setInDnOp(true);
				break;
			}
			if (!m_arrHighLocked.IsEmpty())
			{
				bool ok = m_arrHighLocked.First()->GetPtr(pptr);
				m_arrHighLocked.Delete(0);
				if (!ok) continue;
				xbDnOp op = (*pptr)->GetWaitingDnOp();
				if (op == xbDnNoop)
				{
					*pptr = NULL;
					continue;
				}
				*pop = op;
				*ppriority = xbDnHighLocked;
				(*pptr)->setInDnOp(true);
				break;
			}

			if (m_bThreadCntValid 
				&& m_cThreadMax > 0 && (m_cThread-m_cThreadLongTask) > m_cThreadMax)
			{
				xbDbgOutput("!! -> xbDnCenter::threadWaitForNewTask(), too many threads("+xbString(m_cThread)+"), closing this one");
				m_bThreadCntValid = false;
				ret = false;
				break;
			}

			if (!m_arrLow.IsEmpty())
			{
				bool ok = m_arrLow.First()->GetPtr(pptr);
				m_arrLow.Delete(0);
				if (!ok) continue;
				xbDnOp op = (*pptr)->GetWaitingDnOp();
				if (op == xbDnNoop)
				{
					*pptr = NULL;
					continue;
				}
				*pop = op;
				*ppriority = xbDnLow;
				(*pptr)->setInDnOp(true);
				break;
			}
			if (!m_arrLowLocked.IsEmpty())
			{
				bool ok = m_arrLowLocked.First()->GetPtr(pptr);
				m_arrLowLocked.Delete(0);
				if (!ok) continue;
				xbDnOp op = (*pptr)->GetWaitingDnOp();
				if (op == xbDnNoop)
				{
					*pptr = NULL;
					continue;
				}
				*pop = op;
				*ppriority = xbDnLowLocked;
				(*pptr)->setInDnOp(true);
				break;
			}

			if (!bThreadWaitingInc)
			{
/*				if (m_bThreadCntValid 
					&& m_cThreadMax <= 0 && m_cThreadWaiting > xbDNCENTER_THREADMAX)
				{
					xbDbgOutput("!! -> xbDnCenter::threadWaitForNewTask(), too many threads("+xbString(m_cThread)+"), closing this one");
					m_bThreadCntValid = false;
					ret = false;
					break;
				}*/
				m_cThreadWaiting++;
				bThreadWaitingInc = true;
			}
			lck.Unlock();
			pTh->onWait();

			pTh->unsetBusy();
			HANDLE ev[2] = {m_evTaskNew.GetHandle(),pTh->GetEventHandle()};
			DWORD ret;
			for(;;)
			{
/*inutile				if (pTh->IsCloseAsked())
				{
					ret = WAIT_OBJECT_0 + 1;
					break;
				}*/
				ret = ::MsgWaitForMultipleObjectsEx(2,ev,xbWAIT_TIMEOUTDFLT,xbWAITMSG_FILTER,xbWAITMSG_MODE);
				if (ret > WAIT_OBJECT_0 + 1 /*&& ret < WAIT_OBJECT_0 + MAXIMUM_WAIT_OBJECTS*/)
				{
					xbDoEvents(NULL);
					continue;
				}
				break;
			}
			if (ret == WAIT_OBJECT_0 + 1)
			{
				//Close
				*pptr = NULL;
				ret = !pTh->shouldStop();
				if (!ret)
				{
//Deadlock??					xbDbgOutput("!! -> xbDnCenter::threadWaitForNewTask(), close asked");
					xbSectLock	lck(&m_sect);
					m_bThreadCntValid = false;
				}
				else
					xbDbgOutput("!! -> xbDnCenter::threadWaitForNewTask(), evTh signaled");
				break;
			}
			if (ret == WAIT_OBJECT_0)
			{
				xbDbgOutput("!! -> xbDnCenter::threadWaitForNewTask(), taskNew signaled");
				continue;
			}
			if (ret == WAIT_IO_COMPLETION)
			{
				xbRdbgOutput("!! -> xbDnCenter::threadWaitForNewTask(), io completion signaled");
				continue;
			}
			if (ret == WAIT_TIMEOUT)
			{
				xbDbgOutput("!! -> xbDnCenter::threadWaitForNewTask(), timeout");
				*pptr = NULL;
				ret = true;
				break;
			}
			xbThrow xbEdn(xbHR_LAST,"xbDnCenter::threadWaitForNewTask() : wait failure");
		}
	}
	catch(...)
	{
		if (bThreadWaitingInc)
		{
			xbSectLock	lck(&m_sect);
			m_cThreadWaiting--;
			xbAssert(m_cThreadWaiting>=0);
		}
		throw;
	}
	if (bThreadWaitingInc)
	{
		xbSectLock	lck(&m_sect);
		m_cThreadWaiting--;
		xbAssert(m_cThreadWaiting>=0);
	}
	return ret;
}

void xbDnCenter::threadClear(LPCSTR pReason)
{
	xbSectLock lck(&m_sect);
	for (int i = 0; i < m_arrTh.Length; i++)
	{
		Thread *pTh;
		{
			ThreadInfo *pInfo = &m_arrTh.GetAt(i);
			if (!pInfo->m_pTh || pInfo->m_bDestroyed)
				continue;
			pTh = pInfo->m_pTh;
			lck.Unlock();
		}

		pTh->AskDestroy(pReason);
		lck.Lock(&m_sect);
	}
	m_cThreadClosed = -1;
}

//////////////////////////////////////////////////////////////////////////////////////////
// xbDnCenter::Thread
//////////////////////////////////////////////////////////////////////////////////////////
void xbDnCenter::Thread::onWait()
{
	m_pDnCenter->cacheClean(false);
}

DWORD	xbDnCenter::Thread::onThreadProc(sIxbThread *pTh)
{
	xbDbgOutput("  !! xbDnCenter::Thread::onThreadProc() started...");
	eProtectBegin();
	bool	bSessionOpened = false;
	for(;;)
	{
		xbDnPriority	priority;
		xbDnOp			op;
		xbRefPtr<IxbDnObject>	prObj;
		if (!m_pDnCenter->threadWaitForNewTask(this,&prObj,&priority,&op))
			break;

		ulong ti = ::GetTickCount();
		if (m_tiWork < 0)
			m_tiWork = ti;
		if (!prObj)
		{
			//timeout
			{
				xbSectLock lck(&m_pDnCenter->m_sect);
				if (m_pDnCenter->m_bThreadCntValid 
					&& (m_pDnCenter->m_cThread-m_pDnCenter->m_cThreadLongTask) > m_pDnCenter->m_cThreadMin
					&& ((m_pDnCenter->m_cThreadMaxWithTimeOut > 0 && (m_pDnCenter->m_cThread-m_pDnCenter->m_cThreadLongTask) > m_pDnCenter->m_cThreadMaxWithTimeOut)
						|| xbDiffTime(ti,ulong(m_tiWork)) > m_pDnCenter->m_ThreadTimeOut))
				{
					xbDbgOutput("!! -> xbDnCenter::onThreadProc(), timeout, thread ended (th="+xbString(m_pDnCenter->m_cThread)+" / max="+xbString(m_pDnCenter->m_cThreadMax)+" / waiting="+xbString(m_pDnCenter->m_cThreadWaiting)+")");
					m_pDnCenter->m_bThreadCntValid = false;
					break;
				}
			}
			if (bSessionOpened
				&& xbDiffTime(ti,ulong(m_tiWork)) > m_pDnCenter->m_SessionTimeOut)
			{
				xbDbgOutput("!! -> xbDnCenter::onThreadProc(), timeout, session ended (th="+xbString(m_pDnCenter->m_cThread)+" / max="+xbString(m_pDnCenter->m_cThreadMax)+" / waiting="+xbString(m_pDnCenter->m_cThreadWaiting)+")");

				m_session.Close("dnCenter session timeout");
			}
			continue;
		}

		ulong cfgChgCnt; //To verify if config changes during download
		{
			xbSectLock lck(&m_pDnCenter->m_sect);
			cfgChgCnt = m_pDnCenter->m_retryTaskChgCnt;
		}

		m_tiWork = ti;
		bSessionOpened = true;
		

		xbString url(prObj->GetRequestURL());
		try
		{
			xbDnData	data(m_pDnCenter,&m_session);

			bool bOk = m_session.DoDnOp(&data,op,url,prObj);

			if (op == xbDnLoad)
				prObj->setLoaded();
			else
				prObj->setWrited();
			prObj->setInDnOp(false);
			prObj->SignalPtr(bOk?xbPTRSIG_LOADEDOK:xbPTRSIG_LOADEDERROR);

			{
				xbSectLock lck(&m_pDnCenter->m_sect);
				m_pDnCenter->m_cTaskDone++;
			}
		}
		catch(xbEhotPlug *pE)
		{
			pE->AddCtx("url='"+url+"'");
			xbRdbgTraceException(pE,false,__FILE__,__LINE__);
			for(;;)
			{
				bool bRetryNow = false;
				//if (g_xbHotPlugCenter.GetInUpdateCnt() <= 0)
				{
					bRetryNow = true;
					xbSleep(300);
				}
				bool bInDlg = m_pDnCenter->IsDlgUseLocked();

				xbSectLock lck(&m_pDnCenter->m_sect);
				bool bRetry = false;
				eProtectTrace(bRetry = prObj->setDnOpRetry(bInDlg,pE));
				xbString msg;
				if (!bRetry)
					msg = xbGetMsgFull("raw",pE);
				pE->Delete();
				pE = NULL;
				if (!bRetry)
				{
					lck.Unlock();
					eProtectBegin();
					if (!prObj->HasError())
						prObj->SetError(xbHR_TIMEOUT,"Can't download '"+url+"' : Too many tries",msg);
					eProtectEndTrace();

					eProtectBegin();
					if (op == xbDnLoad)
						prObj->setLoaded();
					else
						prObj->setWrited();
					prObj->SignalPtr(xbPTRSIG_LOADEDERROR);
					m_pDnCenter->m_evTaskDone.Pulse();
					eProtectEndTrace();
					break;
				}
				if (m_pDnCenter->m_arrHigh.IsEmpty()
					&& m_pDnCenter->m_arrHighLocked.IsEmpty())
				{
					int timeout = prObj->getDnOpRetryTimeOut(bInDlg);
					if (timeout <= 0)
					{
						xbDbgOutput("!! -> xbDnCenter::onThreadProc(), xbEhotPlug, nothing in high to download, no sleep (th="+xbString(m_pDnCenter->m_cThread)+" / max="+xbString(m_pDnCenter->m_cThreadMax)+" / waiting="+xbString(m_pDnCenter->m_cThreadWaiting)+")");
					}
					else
					{
						xbDbgOutput("!! -> xbDnCenter::onThreadProc(), xbEhotPlug, nothing in high to download, sleep("+xbString(timeout)+") (th="+xbString(m_pDnCenter->m_cThread)+" / max="+xbString(m_pDnCenter->m_cThreadMax)+" / waiting="+xbString(m_pDnCenter->m_cThreadWaiting)+")");
						lck.Unlock();
						::xbMsgSleep(timeout);
						lck.Lock(&m_pDnCenter->m_sect);
					}
				}
				eProtectBegin();
				try
				{
					if (!bRetryNow && m_pDnCenter->m_retryTaskChgCnt == cfgChgCnt)
					{
						xbDbgOutput("!! -> xbDnCenter::onThreadProc(), xbEhotPlug, task added in not ready list (th="+xbString(m_pDnCenter->m_cThread)+" / max="+xbString(m_pDnCenter->m_cThreadMax)+" / waiting="+xbString(m_pDnCenter->m_cThreadWaiting)+")");
						m_pDnCenter->taskAdd(prObj,
							xbDnIsLockedPriority(priority)?xbDnNotReadyLocked:xbDnNotReady,lck);
					}
					else
					{
						xbDbgOutput("!! -> xbDnCenter::onThreadProc(), xbEhotPlug, task added in low priority list (th="+xbString(m_pDnCenter->m_cThread)+" / max="+xbString(m_pDnCenter->m_cThreadMax)+" / waiting="+xbString(m_pDnCenter->m_cThreadWaiting)+")");
						m_pDnCenter->taskAdd(prObj,
							xbDnIsLockedPriority(priority)?xbDnLowLocked:xbDnLow,lck);
					}
				}
				catch(...)
				{
					prObj->setWaitingDnOp(xbDnNoop);
					throw;
				}
				eProtectEndTrace();
				break;
			}
		}
		catch(xbEdnRetryLater *pE)
		{
			int eTimeOut = pE->GetTimeOut();
			pE->AddCtx("url='"+url+"'");
			xbRdbgTraceException(pE,false,__FILE__,__LINE__);
			
			bool bInDlg = m_pDnCenter->IsDlgUseLocked();

			xbSectLock lck(&m_pDnCenter->m_sect);
			bool bRetry = false;
			eProtectTrace(bRetry = prObj->setDnOpRetry(bInDlg,pE));
			xbString msg;
			if (!bRetry)
				msg = xbGetMsgFull("raw",pE);
			pE->Delete();
			pE = NULL;
			if (!bRetry)
			{
				eProtectBegin();
				if (!prObj->HasError())
					prObj->SetError(xbHR_TIMEOUT,"Can't download '"+url+"' : Too many tries",msg);
				eProtectEndTrace();

				eProtectBegin();
				if (op == xbDnLoad)
					prObj->setLoaded();
				else
					prObj->setWrited();
				prObj->SignalPtr(xbPTRSIG_LOADEDERROR);
				m_pDnCenter->m_evTaskDone.Pulse();
				eProtectEndTrace();
			}
			else
			{
				eProtectBegin();
				try
				{
					m_pDnCenter->taskAdd(prObj,
						xbDnIsLockedPriority(priority)?xbDnLowLocked:xbDnLow,lck);
					xbAssert(lck.IsLocked());
					m_pDnCenter->m_evTaskNew.Set();
				}
				catch(...)
				{
					prObj->setWaitingDnOp(xbDnNoop);
					throw;
				}
				eProtectEndTrace();
			}
			if (m_pDnCenter->m_arrHigh.IsEmpty()
				&& m_pDnCenter->m_arrHighLocked.IsEmpty())
			{
				lck.Unlock();
				int timeout = prObj->getDnOpRetryTimeOut(bInDlg);
				if (eTimeOut > 0 && timeout < eTimeOut)
					timeout = eTimeOut;
				if (timeout <= 0)
				{
					xbDbgOutput("!! -> xbDnCenter::onThreadProc(), xbEdnRetryLater, nothing in high to download, no sleep (th="+xbString(m_pDnCenter->m_cThread)+" / max="+xbString(m_pDnCenter->m_cThreadMax)+" / waiting="+xbString(m_pDnCenter->m_cThreadWaiting)+")");
				}
				else
				{
					xbDbgOutput("!! -> xbDnCenter::onThreadProc(), xbEdnRetryLater, nothing in high to download, sleep("+xbString(timeout)+") (th="+xbString(m_pDnCenter->m_cThread)+" / max="+xbString(m_pDnCenter->m_cThreadMax)+" / waiting="+xbString(m_pDnCenter->m_cThreadWaiting)+")");
					::xbMsgSleep(timeout);
				}
			}
		}
		catch(xbEdnRetrySession *pE)
		{
			int eTimeOut = pE->GetTimeOut();
			pE->AddCtx("url='"+url+"'");
			xbRdbgTraceException(pE,false,__FILE__,__LINE__);

			xbSectLock lck(&m_pDnCenter->m_sect);
			bool bRetry = false;
			eProtectTrace(bRetry = prObj->setDnOpRetry(false,pE));
			xbString msg;
			if (!bRetry)
				msg = xbGetMsgFull("raw",pE);
			pE->Delete();
			pE = NULL;
			if (!bRetry)
			{
				eProtectBegin();
				if (!prObj->HasError())
					prObj->SetError(xbHR_TIMEOUT,"Can't download '"+url+"' : Too many tries",msg);
				eProtectEndTrace();

				eProtectBegin();
				if (op == xbDnLoad)
					prObj->setLoaded();
				else
					prObj->setWrited();
				prObj->SignalPtr(xbPTRSIG_LOADEDERROR);
				m_pDnCenter->m_evTaskDone.Pulse();
				eProtectEndTrace();
			}
			else
			{
				eProtectBegin();
				try
				{
					m_pDnCenter->taskAdd(prObj,
						xbDnIsLockedPriority(priority)?xbDnLowLocked:xbDnLow,lck);
					xbAssert(lck.IsLocked());
					m_pDnCenter->m_evTaskNew.Set();
				}
				catch(...)
				{
					prObj->setWaitingDnOp(xbDnNoop);
					throw;
				}
				eProtectEndTrace();
			}
			lck.Unlock();
			if (eTimeOut <= 0)
				eTimeOut = 200;
			else if (eTimeOut >= 30000)
				eTimeOut = 30000;
			xbDbgOutput("!! -> xbDnCenter::onThreadProc(), xbEdnRetrySession, nothing in high to download, sleep("+xbString(eTimeOut)+") (th="+xbString(m_pDnCenter->m_cThread)+" / max="+xbString(m_pDnCenter->m_cThreadMax)+" / waiting="+xbString(m_pDnCenter->m_cThreadWaiting)+")");
			::xbMsgSleep(eTimeOut);
		}
		catch(xbEbase *pE)
		{
			eProtectBegin();
			if (!prObj->HasError())
				prObj->SetError(pE);
			eProtectEndTrace();

			xbRdbgTraceException(pE,true,__FILE__,__LINE__);
			eProtectBegin();
			if (op == xbDnLoad)
				prObj->setLoaded();
			else
				prObj->setWrited();
			prObj->setInDnOp(false);
			prObj->SignalPtr(xbPTRSIG_LOADEDERROR);
			m_pDnCenter->m_evTaskDone.Pulse();
			eProtectEndTrace();
		}
		catch(...)
		{
			xbTraceException(NULL,false,__FILE__,__LINE__);
			eProtectBegin();
			if (!prObj->HasError())
				prObj->SetError(NULL);
			if (op == xbDnLoad)
				prObj->setLoaded();
			else
				prObj->setWrited();
			prObj->setInDnOp(false);
			prObj->SignalPtr(xbPTRSIG_LOADEDERROR);
			m_pDnCenter->m_evTaskDone.Pulse();
			eProtectEndTrace();
		}

		m_pDnCenter->m_evTaskDone.Pulse();
	}
	eProtectEndTrace();
	xbRdbgOutput("!! xbDnCenter::Thread::onThreadProc() ended...");
	eProtectTrace(m_session.Destroy("dnCenter thread ending"));
	return 0;
}

void	xbDnCenter::Thread::onStart(sIxbThread *pTh)
{
	xbThread::onStart(pTh);

//	HRtoE(::CoInitializeEx(NULL,COINIT_MULTITHREADED));
//	HRtoE(::CoInitialize(NULL));
//	pTh->StartCoInit();

	xbSectLock lck(&m_pDnCenter->m_sect);
	ThreadInfo &info = m_pDnCenter->m_arrTh.GetAt(m_thNo-1);
	xbAssert(info.m_pTh == this);
	info.m_bDestroyed = false;
	info.m_bInCreate = false;
}

DWORD	xbDnCenter::Thread::onStop(sIxbThread *pTh,DWORD ret)
{
	{
		xbSectLock lck(&m_pDnCenter->m_sect);
		if (!xbMustBe(pTh == m_pTh))
		{
			xbRelOutput("xbDnCenter::Thread::onStop("+xbString(ret)+") : thread cb attached to another thread (stopped="+xbString(xbNptr(pTh),xbStringNum_hexa)+", actual="+xbString(xbNptr(m_pTh),xbStringNum_hexa)+")");
			return ret;
		}
		ThreadInfo &info = m_pDnCenter->m_arrTh.GetAt(m_thNo-1);
		xbAssert(info.m_pTh == this);
		if (!info.m_bDestroyed && !info.m_bInCreate)
		{
			m_pDnCenter->m_cThread--;
			m_pDnCenter->m_cThreadDone++;
			xbAssert(m_pDnCenter->m_cThread>=0);
		}
		info.m_bDestroyed = true;
		info.m_bInCreate = false;
		m_pDnCenter->m_bThreadCntValid = true;
	}
	return xbThread::onStop(pTh,ret);
}

void	xbDnCenter::Thread::onCloseAsked(sIxbThread *pTh,LPCSTR pReason)
{
	xbThread::onCloseAsked(pTh,pReason);
	m_session.Destroy(pReason);
}

bool xbDnCenter::IsDlgDispLocked() const
{
	sIxbExceptionCtx *pEctx = g_xbExceptionEngine.GetContext();
	if (!pEctx || pEctx->IsDlgDispLocked())
		return true;
	return false;
}
bool xbDnCenter::IsDlgUseLocked() const
{
	sIxbExceptionCtx *pEctx = g_xbExceptionEngine.GetContext();
	if (pEctx && pEctx->IsDlgUseLocked())
		return true;
	return false;
}
#pragma warning(pop)
