#undef UNICODE
#include "urlmon.h"
#include "wininet.h"
#include "commctrl.h"
#include "windows.h"
#include <stdio.h>
#include "initguid.h"
#include "hlink.h"
///#include "hlguids.h"

#ifdef PRODUCT_PROF
extern "C" void _stdcall StartCAP(void);
extern "C" void _stdcall StopCAP(void);
extern "C" void _stdcall SuspendCAP(void);
extern "C" void _stdcall ResumeCAP(void);
extern "C" void _stdcall StartCAPAll(void);
extern "C" void _stdcall StopCAPAll(void);
#else
#define StartCAP()
#define StopCAP()
#define SuspendCAP()
#define ResumeCAP()
#define StartCAPAll()
#define StopCAPAll()
#endif

typedef BOOL (WINAPI *PFNSPA)(HANDLE, DWORD);
typedef HRESULT (WINAPI * pfnCreateURLMoniker)(IMoniker *, LPCWSTR, IMoniker **);
typedef HRESULT (WINAPI * pfnRegisterBindStatusCallback)(LPBC, IBindStatusCallback *, IBindStatusCallback **, DWORD);

typedef struct
{
    TCHAR*   pBuf;      //Actual buffer to hold data
    DWORD    lNumRead;  //number of bytes read in buffer
    void*    pNext;     //Pointer to next buffer
} buffer;


HINSTANCE g_hUrlMon = NULL;
pfnCreateURLMoniker g_pfnCreateURLMoniker = NULL;
pfnRegisterBindStatusCallback g_pfnRegisterBindStatusCallback = NULL;

#define _HRESULT_TYPEDEF_(_sc) ((HRESULT)_sc)

#define DO_DOWNLOAD   WM_USER + 10
#define DOWNLOAD_DONE WM_USER + 11

#pragma warning(disable:4100)

// ---------------------------------------------------------------------------
#define DBG_ERROR            0x80000000

// verbose flags
#define DBG_RESULTS          0x01
#define DBG_DEBUG            0x02
#define DBG_INFO             0x04
#define DBG_STARTBINDING     0x08
#define DBG_STOPBINDING      0x10
#define DBG_ONPROGRESS       0x20
#define DBG_ONAVAIL          0x40
#define DBG_BREAKONERROR     0x80

#define DBG_ALLVALID         DBG_RESULTS | DBG_DEBUG | DBG_STARTBINDING | DBG_STOPBINDING | DBG_ONPROGRESS | DBG_ONAVAIL

DWORD g_dwDbgFlags = DBG_RESULTS;
// ---------------------------------------------------------------------------

const INT MAX_BUF_SIZE = 1024 * 16;
const INT BUF_SIZE = 2 * 1024;
const INT URL_MAX = 4;
const INT BUF_NUM = 16*4;
const DWORD TIMEOUT = 10000000;
const INT LDG_DONE = 1;
const INT LDG_STARTED = 0;
const INT PRI_LOW = 1;
const INT PRI_MED = 2;
const INT PRI_HI  = 3;

DWORD dwBegin_Time = 0;
DWORD dwEnd_Time;
DWORD dwTot_Time;
BOOL bDelim = FALSE;
DWORD dwNum_Opens = 1;
DWORD dwBuf_Size = BUF_SIZE;
DWORD dwBytes_Read = 0;
DWORD dwMax_Simul_Downloads = URL_MAX;
DWORD g_dwCacheFlag = BINDF_NOWRITECACHE | BINDF_GETNEWESTVERSION;
char *pFilename = NULL;
char *pInFile = NULL;
char *g_pRunStr = NULL;
char *g_pTestName = NULL;
char g_CmdLine[1024];
TCHAR sUrl[(INTERNET_MAX_URL_LENGTH+1)];
TCHAR* g_pBuf = NULL;

// %%Classes: ----------------------------------------------------------------

class CInfo 
{
public:
    CInfo();
    ~CInfo();
    INT      incDownloads(void) { return m_iDownloads++; }
    INT      decDownloads(void) { return m_iDownloads--; }
    INT      getDownloads(void) { return m_iDownloads; }

    HANDLE           m_hCompleteEvent;
    CRITICAL_SECTION m_csInfo;        //for critical section
    HANDLE           m_hMaxDownloadSem;
    buffer*          m_pPool;         //Pointer to current available buffer in pool
    void*            m_pdFirst;       //pointer to the first element
private:
    INT              m_iDownloads;    //number of current downloads
};

class CDownload 
{
  public:
    CDownload(LPSTR sName, CInfo* pcInfo);
    ~CDownload();
    HRESULT      doDownload(void);
    INT          getStatus(void) { return m_iStatus; }
    INT          getPriority(void) { return m_iPriority; }
#ifdef USE_POOL
    INT          releasePool(void);
#endif

    WCHAR                m_pUrl[(INTERNET_MAX_URL_LENGTH+1)];
#ifdef USE_POOL    
    buffer*              m_pbStartBuffer; //first buffer to hold data
    buffer*              m_pbCurBuffer;   //Current Buffer
#endif    
    CInfo*               m_pcInfo;  
    void*                m_pdNext;        //pointer to next element
    INT                  m_iStatus;       //the url's status
    INT                  m_iPriority;     //the url's priority
    DWORD                lNumRead;  //number of bytes read in buffer for this download
    
  private:
    IMoniker*            m_pMoniker;
    IBindCtx*            m_pBindCtx;
    IBindStatusCallback* m_pBindCallback;
};


class CBindStatusCallback : public IBindStatusCallback 
{
  public:
    // IUnknown methods
    STDMETHODIMP    QueryInterface(REFIID riid,void ** ppv);
    STDMETHODIMP_(ULONG)    AddRef()    { return m_cRef++; }
    STDMETHODIMP_(ULONG)    Release()   { if (--m_cRef == 0) { delete this; return 0; } return m_cRef; }

    // IBindStatusCallback methods
    STDMETHODIMP    OnStartBinding(DWORD dwReserved, IBinding* pbinding);
    STDMETHODIMP    GetPriority(LONG* pnPriority);
    STDMETHODIMP    OnLowResource(DWORD dwReserved);
    STDMETHODIMP    OnProgress(ULONG ulProgress, ULONG ulProgressMax, ULONG ulStatusCode,
                        LPCWSTR pwzStatusText);
    STDMETHODIMP    OnStopBinding(HRESULT hrResult, LPCWSTR szError);
    STDMETHODIMP    GetBindInfo(DWORD* pgrfBINDF, BINDINFO* pbindinfo);
    STDMETHODIMP    OnDataAvailable(DWORD grfBSCF, DWORD dwSize, FORMATETC *pfmtetc,
                        STGMEDIUM* pstgmed);
    STDMETHODIMP    OnObjectAvailable(REFIID riid, IUnknown* punk);

    // constructors/destructors
    CBindStatusCallback(CDownload* pcDownload);
    ~CBindStatusCallback();

    
    // data members
    DWORD           m_cRef;
    IBinding*       m_pBinding;
    IStream*        m_pStream;
    DWORD           m_cbOld;
    CDownload*      m_pcDownload;
};


// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
INT dprintf(DWORD dwFlags, TCHAR *fmt, ... ) 
{
    INT      ret = 0;
    va_list  marker;
    TCHAR     szBuffer[256];

    if(dwFlags & (g_dwDbgFlags | DBG_ERROR))
    {
        va_start( marker, fmt );
        ret = vsprintf( szBuffer, fmt, marker );
        OutputDebugString( szBuffer );
        printf(szBuffer);

        if(g_dwDbgFlags & DBG_BREAKONERROR)
            DebugBreak();
    }
    return ret; 
}

// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
void SetSingleProcessorAffinity()
{
    PFNSPA pfn;

    pfn = (PFNSPA)GetProcAddress(GetModuleHandleA("KERNEL32.DLL"),
            "SetProcessAffinityMask");

    if (pfn)
    {
        pfn(GetCurrentProcess(), 1);
    }
}


// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
HRESULT LoadUrlMon()
{
    g_hUrlMon = (HINSTANCE)LoadLibraryA("URLMON.DLL");

    if (g_hUrlMon == NULL)
    {
        dprintf(DBG_ERROR, "LoadLibraryA of URLMON.DLL failed\n");
        return(E_FAIL);
    }

    g_pfnCreateURLMoniker = (pfnCreateURLMoniker)GetProcAddress(g_hUrlMon, "CreateURLMoniker");
    
    if (g_pfnCreateURLMoniker == NULL)
    {
        dprintf(DBG_ERROR, "GetProcAddress CreateURLMoniker failed\n");
        return(E_FAIL);
    }

    g_pfnRegisterBindStatusCallback = (pfnRegisterBindStatusCallback)GetProcAddress(g_hUrlMon, "RegisterBindStatusCallback");

    if (g_pfnRegisterBindStatusCallback == NULL)
    {
        dprintf(DBG_ERROR, "GetProcAddress RegisterBindStatusCallback failed\n");
        return(E_FAIL);
    }

    return(S_OK);
}

// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
void UnloadUrlMon()
{
    if (g_hUrlMon)
    {
        FreeLibrary(g_hUrlMon);
    }
}

// ===========================================================================
//                     CBindStatusCallback Implementation
// ===========================================================================

// ---------------------------------------------------------------------------
// %%Function: CBindStatusCallback::CBindStatusCallback
// ---------------------------------------------------------------------------
CBindStatusCallback::CBindStatusCallback(CDownload* pcDownload)
{
    m_pBinding = NULL;
    m_pStream = NULL;
    m_cRef = 1;
    m_cbOld = 0;
    m_pcDownload = pcDownload;
}  // CBindStatusCallback

// ---------------------------------------------------------------------------
// %%Function: CBindStatusCallback::~CBindStatusCallback
// ---------------------------------------------------------------------------
CBindStatusCallback::~CBindStatusCallback()
{
}  // ~CBindStatusCallback

// ---------------------------------------------------------------------------
// %%Function: CBindStatusCallback::QueryInterface
// ---------------------------------------------------------------------------
 STDMETHODIMP
CBindStatusCallback::QueryInterface(REFIID riid, void** ppv)
{
    *ppv = NULL;

    if (riid==IID_IUnknown || riid==IID_IBindStatusCallback) 
    {
        *ppv = this;
        AddRef();
        return S_OK;    
    }
    return E_NOINTERFACE;
 }  // CBindStatusCallback::QueryInterface

 // ---------------------------------------------------------------------------
 // %%Function: CBindStatusCallback::OnStartBinding
 // ---------------------------------------------------------------------------
 STDMETHODIMP CBindStatusCallback::OnStartBinding(DWORD dwReserved, IBinding* pBinding)
 {
    if (m_pBinding != NULL)
        m_pBinding->Release();

    m_pBinding = pBinding;

    if (m_pBinding != NULL) 
        m_pBinding->AddRef();

    m_pcDownload->m_pcInfo->incDownloads();

    if(g_dwDbgFlags)    
        dprintf(DBG_STOPBINDING, "OnStartBinding getDownloads()=%d\n", m_pcDownload->m_pcInfo->getDownloads());
    return S_OK;

 }  // CBindStatusCallback::OnStartBinding

 // ---------------------------------------------------------------------------
 // %%Function: CBindStatusCallback::GetPriority
 // ---------------------------------------------------------------------------
 STDMETHODIMP CBindStatusCallback::GetPriority(LONG* pnPriority)
 {
     return E_NOTIMPL;
 }  // CBindStatusCallback::GetPriority

 // ---------------------------------------------------------------------------
 // %%Function: CBindStatusCallback::OnLowResource
 // ---------------------------------------------------------------------------
 STDMETHODIMP CBindStatusCallback::OnLowResource(DWORD dwReserved)
 {
     return E_NOTIMPL;
 }  // CBindStatusCallback::OnLowResource

 // ---------------------------------------------------------------------------
 // %%Function: CBindStatusCallback::OnProgress
 // ---------------------------------------------------------------------------
 
STDMETHODIMP CBindStatusCallback::OnProgress(ULONG ulProgress, ULONG ulProgressMax, ULONG ulStatusCode, LPCWSTR szStatusText)
{
    TCHAR sz[255];
    if(szStatusText != NULL) {
         WideCharToMultiByte(CP_ACP, 0, szStatusText, -1, sz, 255,0,0);
    }
    if(g_dwDbgFlags)    
        dprintf(DBG_ONPROGRESS, "OnProgress: %d(%s) %d of %d\n", ulStatusCode, sz, ulProgress, (ulProgress>ulProgressMax)?ulProgress:ulProgressMax);
    return(NOERROR);
}  // CBindStatusCallback::OnProgress

 // ---------------------------------------------------------------------------
 // %%Function: CBindStatusCallback::OnStopBinding
 // ---------------------------------------------------------------------------
 STDMETHODIMP CBindStatusCallback::OnStopBinding(HRESULT hrStatus, LPCWSTR pszError)
 {
     if (hrStatus != S_OK) 
     {
        if(g_dwDbgFlags & DBG_DEBUG)
        {
             TCHAR sUrl[(INTERNET_MAX_URL_LENGTH+1)];
             TCHAR sErr[1024];
             WideCharToMultiByte(CP_ACP, 0, m_pcDownload->m_pUrl, -1, 
                 sUrl, INTERNET_MAX_URL_LENGTH, 0, 0);
             WideCharToMultiByte(CP_ACP, 0, pszError, -1, 
                sErr, 1024, 0, 0);
             dprintf(DBG_ERROR, "*** ERROR *** %s OnStopBinding download failed. Status=%x Err=%s\n", sUrl, hrStatus, sErr);
         }
     }
     if (m_pBinding)	
     {
         m_pBinding->Release();
         m_pBinding = NULL;
     }
     
     m_pcDownload->m_pcInfo->decDownloads();
     if(g_dwDbgFlags)    
        dprintf(DBG_STOPBINDING, "OnStopBinding hrStatus=%d getDownloads()=%d\n", hrStatus, m_pcDownload->m_pcInfo->getDownloads());

     if(m_pcDownload->m_pcInfo->getDownloads() == 0) 
     {
         SetEvent(m_pcDownload->m_pcInfo->m_hCompleteEvent);
     }         

     return S_OK;
 }  // CBindStatusCallback::OnStopBinding


 // ---------------------------------------------------------------------------
 // %%Function: CBindStatusCallback::GetBindInfo
 // ---------------------------------------------------------------------------
 STDMETHODIMP CBindStatusCallback::GetBindInfo(DWORD* pgrfBINDF, BINDINFO* pBindInfo)
 {
     *pgrfBINDF = BINDF_ASYNCHRONOUS | BINDF_ASYNCSTORAGE | BINDF_PULLDATA;
     *pgrfBINDF |= g_dwCacheFlag;
     pBindInfo->cbSize = sizeof(BINDINFO);
     pBindInfo->szExtraInfo = NULL;
     memset(&pBindInfo->stgmedData, 0, sizeof(STGMEDIUM));
     pBindInfo->grfBindInfoF = 0;
     pBindInfo->dwBindVerb = BINDVERB_GET;
     pBindInfo->szCustomVerb = NULL;
     return S_OK;
 }  // CBindStatusCallback::GetBindInfo

 // ---------------------------------------------------------------------------
 // %%Function: CBindStatusCallback::OnDataAvailable
 // ---------------------------------------------------------------------------
 
 
STDMETHODIMP CBindStatusCallback::OnDataAvailable(DWORD grfBSCF, DWORD dwSize, FORMATETC* pfmtetc, STGMEDIUM* pstgmed)
{
    DWORD dwRead = dwSize - m_cbOld; // Amount to be read
    HRESULT hr = S_OK;

     // Get the Stream passed

     if(g_dwDbgFlags)
        dprintf(DBG_ONAVAIL, "OnDataAvailable(grfBSCF=%d pStream=0x%x dwRead=%d dwSize=%d pfmtetc=0x%x, pstgmed=0x%x\n",
            grfBSCF, m_pStream, dwRead, dwSize, pfmtetc, pstgmed);
    
     if (!m_pStream && pstgmed->tymed == TYMED_ISTREAM)
     {
         m_pStream = pstgmed->pstm;
     }

     // If there is some data to be read then go ahead and read
     if (m_pStream && dwRead) 
     {
         while(hr!=E_PENDING) 
         {
#ifdef USE_POOL    
             if(m_pcDownload->m_pcInfo->m_pPool) 
             {
                 //if pool ready
                 EnterCriticalSection(&(m_pcDownload->m_pcInfo->m_csInfo));    
                 if(!m_pcDownload->m_pbStartBuffer) 
                 {    
                     // if the first time
                     m_pcDownload->m_pbStartBuffer = 
                         m_pcDownload->m_pbCurBuffer = 
                         m_pcDownload->m_pcInfo->m_pPool;
                     m_pcDownload->m_pcInfo->m_pPool = 
                         (buffer *)m_pcDownload->m_pcInfo->m_pPool->pNext;
                     m_pcDownload->m_pbStartBuffer->pNext = NULL;    
                 }
                 else 
                 {
                     m_pcDownload->m_pbCurBuffer->pNext = 
                         m_pcDownload->m_pcInfo->m_pPool;
                     m_pcDownload->m_pcInfo->m_pPool =
                     (buffer *)m_pcDownload->m_pcInfo->m_pPool->pNext;
                     m_pcDownload->m_pbCurBuffer = (buffer *) m_pcDownload->m_pbCurBuffer->pNext;
                     m_pcDownload->m_pbCurBuffer->pNext = NULL;
                 }   
                 LeaveCriticalSection(&(m_pcDownload->m_pcInfo->m_csInfo));   
             }    
             else 
             {
                 //allocate buffers on the fly
                 if(!m_pcDownload->m_pbStartBuffer) 
                 {    
                     // if the first time
                     m_pcDownload->m_pbStartBuffer = m_pcDownload->m_pbCurBuffer =  new buffer;
                     if(!m_pcDownload->m_pbCurBuffer)
                     {
                         dprintf(DBG_ERROR, "*** ERROR *** on buff alloc\n");
                         return S_FALSE;
                     }
                     m_pcDownload->m_pbCurBuffer->pBuf = new TCHAR[dwBuf_Size];

                     if(!m_pcDownload->m_pbCurBuffer->pBuf)
                     {
                         dprintf(DBG_ERROR, "*** ERROR *** on buf alloc\n");
                         return S_FALSE;
                     }
                 
                     m_pcDownload->m_pbStartBuffer->pNext = NULL;        
                 }
             
                 else 
                 {    
                     m_pcDownload->m_pbCurBuffer->pNext = new buffer;
                     if(!m_pcDownload->m_pbCurBuffer->pNext)
                     {
                         dprintf(DBG_ERROR, "*** ERROR *** on buff alloc\n");
                         return S_FALSE;
                     }
                     m_pcDownload->m_pbCurBuffer = (buffer *) m_pcDownload->m_pbCurBuffer->pNext;
                     m_pcDownload->m_pbCurBuffer->pBuf = new TCHAR[dwBuf_Size];
                     if(!m_pcDownload->m_pbCurBuffer->pBuf)
                     {
                         dprintf(DBG_ERROR, "*** ERROR *** on buf alloc\n");
                         return S_FALSE;
                     }
                 
                     m_pcDownload->m_pbCurBuffer->pNext = NULL;
                 }
             }
#endif
             if(dwBegin_Time == 0)
                 dwBegin_Time = GetTickCount();

#ifdef USE_POOL    
             hr = m_pStream->Read(m_pcDownload->m_pbCurBuffer->pBuf,             
                 dwBuf_Size, &(m_pcDownload->m_pbCurBuffer->lNumRead));
             if(g_dwDbgFlags)
             {
                  dprintf(DBG_INFO & DBG_DEBUG, "Stream->Read Size=%d Read=%d hr=0x%x\n", dwBuf_Size, m_pcDownload->m_pbCurBuffer->lNumRead, hr);
                  if(hr != S_OK && hr != E_PENDING && hr != S_FALSE)
                    dprintf(DBG_ERROR, "************ Stream->Read hr=0x%x\n", hr);
             }
#else                 
             hr = m_pStream->Read(g_pBuf, dwBuf_Size, &(m_pcDownload->lNumRead));
             if(g_dwDbgFlags)
             {
                  dprintf(DBG_INFO & DBG_DEBUG, "Stream->Read Size=%d Read=%d hr=0x%x\n", dwBuf_Size, m_pcDownload->lNumRead, hr);
                  if(hr != S_OK && hr != E_PENDING && hr != S_FALSE)
                    dprintf(DBG_ERROR, "************ Stream->Read hr=0x%x\n", hr);
             }
#endif                 
             
             //need to check for error if read reaches end of stream
             if(hr == S_FALSE) 
             {
                 break;            
             }
#ifdef USE_POOL    
             if (m_pcDownload->m_pbCurBuffer->lNumRead > 0) 
             {
                 m_cbOld += m_pcDownload->m_pbCurBuffer->lNumRead;
             }
#else
             if (m_pcDownload->lNumRead > 0) 
             {
                 m_cbOld += m_pcDownload->lNumRead;
             }
#endif
         }
     }//     if(m_pstm && dwRead)

     if (BSCF_LASTDATANOTIFICATION & grfBSCF) 
     {
         WideCharToMultiByte(CP_ACP, 0, m_pcDownload->m_pUrl, -1, 
             sUrl, INTERNET_MAX_URL_LENGTH, 0, 0);
         if(g_dwDbgFlags && !bDelim)
            dprintf(DBG_INFO, "Status: %s downloaded.\n", sUrl);
//         m_pcDownload->m_pcInfo->decDownloads();
    
         m_pcDownload->m_iStatus = LDG_DONE;

         if(!ReleaseSemaphore(m_pcDownload->m_pcInfo->m_hMaxDownloadSem,1,NULL)) 
         {
             dprintf(DBG_ERROR, "*** ERROR *** ReleaseSemaphore failed!\n");
             return S_FALSE; 
         }

         dwBytes_Read += m_cbOld;  // accum buf size that was downloaded
     }
     return S_OK;
}  // CBindStatusCallback::OnDataAvailable

// ---------------------------------------------------------------------------
// %%Function: CBindStatusCallback::OnObjectAvailable
// ---------------------------------------------------------------------------
 STDMETHODIMP
CBindStatusCallback::OnObjectAvailable(REFIID riid, IUnknown* punk)
{
    return E_NOTIMPL;
}  // CBindStatusCallback::OnObjectAvailable

// ===========================================================================
//                           CDownload Implementation
// ===========================================================================

// ---------------------------------------------------------------------------
// %%Function: CDownload::CDownload
// ---------------------------------------------------------------------------
CDownload::CDownload(LPSTR sName, CInfo* pcInfo)
{
    MultiByteToWideChar(CP_ACP, 0, sName, -1, m_pUrl, INTERNET_MAX_URL_LENGTH);  
    m_pMoniker = 0;
    m_pBindCtx = 0;
    m_pBindCallback = 0;
    m_pdNext = NULL;

    m_iStatus = LDG_STARTED;
    m_iPriority = PRI_MED;
    m_pcInfo = pcInfo;
#ifdef USE_POOL    
    m_pbStartBuffer = m_pbCurBuffer = NULL;
#endif    

}  // CDownload

// ---------------------------------------------------------------------------
// %%Function: CDownload::~CDownload
// ---------------------------------------------------------------------------
CDownload::~CDownload()
{
    buffer* pbLastBuf = NULL;

    if (m_pMoniker)
        m_pMoniker->Release();
    if (m_pBindCtx)
        m_pBindCtx->Release();
    if (m_pBindCallback)
        m_pBindCallback->Release();
    delete m_pcInfo;

#ifdef USE_POOL    
    if(m_pbStartBuffer)
    {
        while(m_pbStartBuffer->lNumRead != 0 && 
            m_pbStartBuffer->lNumRead <= dwBuf_Size) 
        {
            delete m_pbStartBuffer->pBuf;
            pbLastBuf = m_pbStartBuffer;
            m_pbStartBuffer = (buffer *)m_pbStartBuffer->pNext;   
            delete pbLastBuf;   
        }   
    }
#endif
    GlobalFree(m_pUrl);
}  // ~CDownload

// ---------------------------------------------------------------------------
// %%Function: CDownload::DoDownload
// ---------------------------------------------------------------------------
 

HRESULT CDownload::doDownload(void) 
{
    IStream*        pstm;
    HRESULT         hr;

    hr = g_pfnCreateURLMoniker(NULL, m_pUrl, &m_pMoniker);
    if (FAILED(hr)) 
    {
        dprintf(DBG_ERROR, "*** ERROR *** doDownload CreateURLMoniker failed hr=0x%x\n", hr);
        goto LErrExit;    
    }
            
    m_pBindCallback = new CBindStatusCallback(this);

    if (m_pBindCallback == NULL) 
    {
        dprintf(DBG_ERROR, "*** ERROR *** doDownload CBindStatusCallback failed hr=0x%x\n", hr);
        hr = E_OUTOFMEMORY;
        goto LErrExit;        
    }

    hr = CreateBindCtx(0, &m_pBindCtx);
    if (FAILED(hr)) 
    {
        dprintf(DBG_ERROR, "*** ERROR *** doDownload CreateBindCtx failed hr=0x%x\n", hr);
        goto LErrExit;      
    }

    hr = g_pfnRegisterBindStatusCallback(
        m_pBindCtx, 
        m_pBindCallback, 
        0, 0L);
        
    if (FAILED(hr)) 
    {
        dprintf(DBG_ERROR, "*** ERROR *** doDownload RegisterBindStatusCallback failed hr=0x%x\n", hr);
        goto LErrExit;    
    }
    
    hr = m_pMoniker->BindToStorage(
        m_pBindCtx, 
        0, 
        IID_IStream, 
        (void**)&pstm);
    if (FAILED(hr)) 
    {
        dprintf(DBG_ERROR, "*** ERROR *** doDownload BindToStorage failed hr=0x%x\n", hr);
        goto LErrExit;    
    }
    
    return(hr);

LErrExit:
    if (m_pBindCtx != NULL) 
    {
        m_pBindCtx->Release();
        m_pBindCtx = NULL;
        }
    if (m_pBindCallback != NULL) 
    {
        m_pBindCallback->Release();
        m_pBindCallback = NULL;
        }
    if (m_pMoniker != NULL) 
    {
        m_pMoniker->Release();
        m_pMoniker = NULL;
        }
    return hr;
}

// ---------------------------------------------------------------------------
// %%Function: CDownload::releasePool
// ---------------------------------------------------------------------------
#ifdef USE_POOL
INT CDownload::releasePool()
{
    buffer *pbStart;

    EnterCriticalSection(&(m_pcInfo->m_csInfo));

    while(m_pbStartBuffer) 
    {
        // remember the start buf
        pbStart = (buffer *) m_pbStartBuffer->pNext;
        // adjust the start
        m_pbStartBuffer = (buffer *) m_pbStartBuffer->pNext;

        //insert the buffer at the beginning of the pool
        pbStart->pNext = m_pcInfo->m_pPool;

        // update the pool
        m_pcInfo->m_pPool = pbStart;
    }

    LeaveCriticalSection(&(m_pcInfo->m_csInfo));
    return TRUE;
}
#endif

// ===========================================================================
//                           CInfo Implementation
// ===========================================================================

// ---------------------------------------------------------------------------
// %%Function: CInfo::CInfo
// ---------------------------------------------------------------------------
CInfo::CInfo()
{
#ifdef USE_POOL    
    INT i;

    buffer* pStartBuffer = NULL;
#endif

    m_hCompleteEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    if (!m_hCompleteEvent) 
    {
        dprintf(DBG_ERROR, "*** ERROR *** on create Event!\n");
    }

    InitializeCriticalSection(&(m_csInfo));

    m_hMaxDownloadSem = CreateSemaphore(NULL,dwMax_Simul_Downloads,dwMax_Simul_Downloads, NULL);
    if(!m_hMaxDownloadSem) 
    {
        dprintf(DBG_ERROR, "*** ERROR *** CreateSem failed!\n");
    }

#ifdef USE_POOL    
    pStartBuffer = m_pPool = new buffer; 
    if(!m_pPool)
        return;

    m_pPool->pBuf = new TCHAR[dwBuf_Size]; 
    if (!m_pPool->pBuf)
        return;

    m_pPool->lNumRead = 0;
#endif    

    m_iDownloads = 0;

#ifdef USE_POOL    
    m_pPool->pNext = NULL;
    for(i=1; i<BUF_NUM; i++) 
    {
        m_pPool->pNext = new buffer;
        if (!m_pPool->pNext)
            return;

        m_pPool = (buffer *)m_pPool->pNext;
        m_pPool->pBuf = new TCHAR[dwBuf_Size];
        
        if (!m_pPool->pBuf)
            return;

        m_pPool->lNumRead = 0;
        m_pPool->pNext = NULL;
    }

    m_pPool = pStartBuffer;
#endif    
    return;
}  // CInfo

// ---------------------------------------------------------------------------
// %%Function: CInfo::~CInfo
// ---------------------------------------------------------------------------
CInfo::~CInfo()
{
    buffer *pLastBuf;

    while(m_pPool) 
    {
        delete m_pPool->pBuf;
        pLastBuf = m_pPool;
        m_pPool = (buffer *)m_pPool->pNext;   
        delete pLastBuf;
    }
    delete this;
}  // ~CInfo



// ===========================================================================
//                  User Interface and Initialization Routines
// ===========================================================================

//----------------------------------------------------------------------------
//  Procedure:   DownloadThread
//  Purpose:     Opens internet connection and downloads URL.  Saves
//               URL to pOutQ (one chunk per buffer).
//  Arguments:   outQ
//  Return Val:  TRUE or FALSE based on error
//----------------------------------------------------------------------------

DWORD DownloadThread(LPDWORD lpdwParam) 
{

    INT retVal;
    MSG msg;
    CDownload *pcDownload = (CDownload *) lpdwParam;

    SetEvent(pcDownload->m_pcInfo->m_hCompleteEvent);
    if(g_dwDbgFlags)    
        dprintf(DBG_INFO, "DownloadThread: m_hCompleteEvent set.\n");

    StartCAP();

    for (;;)
    {
        SuspendCAP();

        retVal = GetMessage(&msg, NULL, 0, 0);

        ResumeCAP();

        if(retVal == -1) 
        {
            dprintf(DBG_ERROR, "*** ERROR *** on GetMessage\n");
            break;
        }
        if(retVal == FALSE) 
        {
            msg.message = DOWNLOAD_DONE;
        }
        pcDownload = (CDownload *) msg.wParam;
        switch(msg.message) 
        {
        case DOWNLOAD_DONE:
            delete pcDownload;
            if(g_dwDbgFlags)    
                dprintf(DBG_INFO, "DownloadThread: exit\n");
            return TRUE;
            break;
        
        case DO_DOWNLOAD:
            if(FAILED(pcDownload->doDownload()))
            {
                return FALSE;
            }
            break;
        default:
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }    
    return TRUE;
}

//==================================================================
void Display_Usage(char **argv)
{
    printf("\nUsage: %s -fURLname [options]\n", argv[0]);
    printf("\n          -iInputFileName [options]\n");
    printf("\n\t options:\n");
    printf("\t\t -l   - read buffer length\n");
    printf("\t\t -m   - maximum number of simultaneous downloads\n");
    printf("\t\t -n## - number of times to download\n");
    printf("\t\t -z   - comma delimited format\n");
    printf("\t\t -c   - write to cache (default is NOWRITECACHE)\n");
    printf("\t\t -g   - read from cache (default is GETNEWESTVERSION)\n");
    printf("\t\t -d   - direct read (default uses QueryDataAvailable)\n");
    printf("\t\t -1   - single processor affinity (default multiprocessor)\n");
    printf("\t\t -x#  - verbose flags (default=0x%x)\n", g_dwDbgFlags);
    printf("\t\t\t Results          0x%02x\n",DBG_RESULTS);
    printf("\t\t\t Debug            0x%02x\n",DBG_DEBUG);
    printf("\t\t\t Info             0x%02x\n",DBG_INFO);
    printf("\t\t\t StartBinding     0x%02x\n",DBG_STARTBINDING);
    printf("\t\t\t StopBinding      0x%02x\n",DBG_STOPBINDING);
    printf("\t\t\t OnProgress       0x%02x\n",DBG_ONPROGRESS);
    printf("\t\t\t OnDataAvailable  0x%02x\n",DBG_ONAVAIL);
    printf("\t\t\t Break on Errors  0x%02x\n",DBG_BREAKONERROR);
}

//==================================================================
BOOL Process_Command_Line(int argcIn, char **argvIn)
{
    BOOL bRC = TRUE;
    int argc = argcIn;
    char **argv = argvIn;
    DWORD dwLen = 0;

    *g_CmdLine = '\0';
    
    argv++; argc--;
    while( argc > 0 && argv[0][0] == '-' )  
    {
        switch (argv[0][1]) 
        {
            case 'c':
                g_dwCacheFlag &= ~BINDF_NOWRITECACHE;
                break;
            case 'g':
                g_dwCacheFlag &= ~BINDF_GETNEWESTVERSION;
                break;
            case 'd':
                g_dwCacheFlag |= BINDF_DIRECT_READ;
                break;
            case 'f':
                pFilename = &argv[0][2];
                break;
            case 'i':
                pInFile = &argv[0][2];
                break;
            case 'n':
                dwNum_Opens = atoi(&argv[0][2]);
                break;
            case 'l':
                dwBuf_Size =  atoi(&argv[0][2]);
                if(dwBuf_Size > MAX_BUF_SIZE)
                    dwBuf_Size = MAX_BUF_SIZE;
                break;
            case 'm':
                dwMax_Simul_Downloads = atoi(&argv[0][2]);
                break;
            case 'r':
                g_pRunStr = &argv[0][2];
                break;
            case 't':
                g_pTestName = &argv[0][2];
                break;
            case 'z':
                bDelim = TRUE;
                break;
            case '1':
                SetSingleProcessorAffinity();
                break;
            case 'x':
                sscanf(&argv[0][2], "%x", &g_dwDbgFlags);
                if(!(g_dwDbgFlags & (DBG_ALLVALID)))
                {
                    printf("Invalid verbose flags %x\n", g_dwDbgFlags);
                    Display_Usage(argvIn);
                    bRC = FALSE;
                }
                break;
            default:
                Display_Usage(argvIn);
                bRC = FALSE;
        }
        if(bRC)
        {
            dwLen += lstrlen(argv[0]) + 1;   // length of arg and space
            if(dwLen < ((sizeof(g_CmdLine)/sizeof(g_CmdLine[0]))-1))
            {
                lstrcat(g_CmdLine, ",");
                lstrcat(g_CmdLine, argv[0]);
            }
        }
        
        argv++; argc--;
    }

    if(!pFilename && !pInFile)
    {
        Display_Usage(argvIn);
        bRC = FALSE;
    }

    return(bRC);
}

//----------------------------------------------------------------------------
// Function:  WinMain
// Purpose:   main entry procedure
// Args:      none
// RetVal:    TRUE or FALSE based on error
//----------------------------------------------------------------------------
//int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR szCmdLine, int nCmdShow)
int __cdecl main(INT argc, TCHAR *argv[]) //for console
{
    CDownload*      pcDownload = NULL;
    CDownload*      pcdFirst = NULL;
    CInfo*          pcInfo = NULL;
    DWORD           dwThreadID;
    DWORD           dwCnt;
    HANDLE          hDownloadThread;
    INT             iError;
    char            szName[MAX_PATH];
    __int64         ibeg, iend, ifrq;
    float fKB;
    float fSec;
    float fKBSec;

    if(!Process_Command_Line(argc, argv))
        exit(0);

    pcInfo = new CInfo();
    g_pBuf = new TCHAR[dwBuf_Size];

    if(!pcInfo) 
    {
        dprintf(DBG_ERROR, "*** ERROR *** generating pool!\n");
        return(0);
    }

    dwCnt = 0;
    if(pFilename)
    {
        while(dwCnt++ < dwNum_Opens)
        {    

            if(g_dwCacheFlag & BINDF_NOWRITECACHE)
                lstrcpy(szName, pFilename);
            else
                wsprintf(szName, "%s.%d", pFilename, dwCnt);

            if(!pcDownload) 
            {
                pcdFirst = pcDownload = new CDownload(szName, pcInfo);
                pcDownload->m_pcInfo->m_pdFirst = pcDownload;
            }
            else 
            {
                pcDownload->m_pdNext = new CDownload(szName, pcInfo);
                pcDownload = (CDownload *) pcDownload->m_pdNext;
            }

            if(!pcDownload)
            {
                dprintf(DBG_ERROR, "*** ERROR *** initializing pcDownload!\n");
                return(0);
            }
        }
    }
    else if(pInFile)    // Process input file
    {
        FILE *fp;

        while(dwCnt++ < dwNum_Opens) 
        {
            if((fp = fopen(pInFile, "r")) == NULL) 
            {
                dprintf(DBG_ERROR, "*** ERROR *** opening file\n");
                return(0);
            }

            while(fgets(szName, INTERNET_MAX_URL_LENGTH, fp) != NULL) 
            {
                if(szName[0] != '#') 
                {
                    szName[strlen(szName) - sizeof(char)] = '\0';

                    if(!pcDownload) 
                    {
                        pcdFirst = pcDownload = new CDownload(szName, pcInfo);
                        pcDownload->m_pcInfo->m_pdFirst = pcDownload;
                    }
                    else 
                    {
                        pcDownload->m_pdNext = new CDownload(szName, pcInfo);
                        pcDownload = (CDownload *) pcDownload->m_pdNext;
                    }

                    if(!pcDownload)
                    {
                        dprintf(DBG_ERROR, "*** ERROR *** initializing pcDownload!\n");
                        return(0);
                    }
                }
            }

            fclose(fp);
        }
    }

    pcDownload = (CDownload *) pcDownload->m_pcInfo->m_pdFirst;

    if (LoadUrlMon() != S_OK)
    {
        dprintf(DBG_ERROR, "*** ERROR *** LoadUrlMon() failed\n");
        return(0);
    }

    if (CoInitialize(NULL) != S_OK) 
    {
        dprintf(DBG_ERROR, "*** ERROR *** CoInitialize() failed\n");
        return(0);
    }

    pcDownload->m_pcInfo->m_hCompleteEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

    if (!pcDownload->m_pcInfo->m_hCompleteEvent) 
    {
        dprintf(DBG_ERROR, "*** ERROR *** on create Event!\n");
    }

    hDownloadThread = CreateThread(NULL,
        0,
        (LPTHREAD_START_ROUTINE)DownloadThread,
        (LPVOID)pcDownload,
        0,
        &dwThreadID );

    if (!hDownloadThread) 
    {
        dprintf(DBG_ERROR, "*** ERROR *** Could not create Thread\n");
        return(0);
    }

    if(WaitForSingleObject(pcDownload->m_pcInfo->m_hCompleteEvent, TIMEOUT)
        == WAIT_TIMEOUT) 
    {
        dprintf(DBG_ERROR, "*** ERROR *** timeout on init\n");
    }
    Sleep(100);

    QueryPerformanceCounter((LARGE_INTEGER *)&ibeg);

    while(pcDownload) 
    {
        if(WaitForSingleObject(pcDownload->m_pcInfo->m_hMaxDownloadSem, TIMEOUT) 
            == WAIT_TIMEOUT) 
        {
            dprintf(DBG_ERROR, "*** ERROR *** timeout on Sem\n");
        }

        if(g_dwDbgFlags)
        {
            TCHAR sz[255];
            WideCharToMultiByte(CP_ACP, 0, pcDownload->m_pUrl, -1, sz, 255,0,0);
            dprintf(DBG_INFO, "main: PostThreadMessage DO_DOWNLOAD %s\n", sz);
        }
        if(!PostThreadMessage(dwThreadID, DO_DOWNLOAD, (WPARAM) pcDownload, 0))
        {
            iError = GetLastError();
            dprintf(DBG_ERROR, "*** Error *** on PostThreadMessage(0x%X, %ld, 0x%lX, 0) [GLE=%d]\n", 
                dwThreadID, DO_DOWNLOAD, pcDownload, iError);
            return(0);
        }
        pcDownload = (CDownload *) pcDownload->m_pdNext;    
    }  
    //wait for completion downloads at one time
    if(WaitForSingleObject(pcdFirst->m_pcInfo->m_hCompleteEvent, TIMEOUT) == WAIT_TIMEOUT) 
    {
        dprintf(DBG_ERROR, "*** ERROR *** timeout on Sem\n");
    }

    QueryPerformanceCounter((LARGE_INTEGER *) &iend);
    QueryPerformanceFrequency((LARGE_INTEGER *) &ifrq);

    dwTot_Time = (DWORD)((iend - ibeg) * 1000 / ifrq);
    if(dwTot_Time == 0)
     dwTot_Time = 1;
    fKB = ((float)dwBytes_Read)/1024;
    fSec = ((float)dwTot_Time)/1000;
    fKBSec = fKB / fSec;
    if(!bDelim)
    {
        dprintf(DBG_RESULTS, "Downloaded: %s\r\n", sUrl);
        dprintf(DBG_RESULTS, "%ld Bytes in %ld Milliseconds = %2.0f KB/Sec\r\n", dwBytes_Read, dwTot_Time, fKBSec );
        dprintf(DBG_RESULTS, "%ld Reads, %ld Downloads, %ld Byte Read Buffer\r\n",
            dwNum_Opens, dwMax_Simul_Downloads, dwBuf_Size);
    }
    else
        dprintf(DBG_RESULTS, "%s, %s, %ld, %ld, %2.0f %s\n", 
            g_pTestName ?g_pTestName :"urlmon",
            g_pRunStr ?g_pRunStr :"1",
            dwTot_Time, dwBytes_Read, fKBSec, g_CmdLine );

    if(g_dwDbgFlags)    
        dprintf(DBG_INFO, "realized finished on data ready\n");

    if(!PostThreadMessage(dwThreadID, DOWNLOAD_DONE, (WPARAM) pcDownload, 0))
    {
        iError = GetLastError();
        dprintf(DBG_ERROR, "*** Error *** on PostThreadMessage(0x%X, %ld, 0x%lX, 0) [GLE=%d]\n", 
            dwThreadID, DOWNLOAD_DONE, pcDownload, iError);
        return(0);
    }
    if(WaitForSingleObject(hDownloadThread, TIMEOUT) == WAIT_TIMEOUT) 
    {
        dprintf(DBG_ERROR, "*** ERROR *** timeout on DownloadThread exit\n");
    }
   
    CloseHandle(hDownloadThread);
    CoUninitialize();
    UnloadUrlMon();

    if(g_dwDbgFlags)    
        dprintf(DBG_INFO, "main: exit\n");

    return(1);
}