// Upload.cpp : Implementation of CUpload
#include "stdafx.h"
#include "resource.h"
#include "CompatUI.h"
#include "shlobj.h"
extern "C" {
    #include "shimdb.h"
}

#include "Upload.h"

TCHAR szKeyDataFiles[] = TEXT("DataFiles");


//
// lives in util.cpp
//
BOOL
GetExePathFromObject(
    LPCTSTR lpszPath,  // path to an arbitrary object
    CComBSTR& bstrExePath
    );


//
// lives in proglist.cpp
//

wstring
    LoadResourceString(UINT nID);

//
// lives in ntutil.c
//
extern "C"
BOOL
WINAPI
CheckFileLocation(
    LPCWSTR pwszDosPath,
    BOOL* pbRoot,
    BOOL* pbLeaf
    );


//
// conversion
//
BOOL VariantToBOOL(CComVariant& v)
{
    if (SUCCEEDED(v.ChangeType(VT_BOOL))) {
        return v.boolVal;
    }

    return FALSE;
}

wstring VariantToStr(CComVariant& v)
{
    wstring str;

    if (v.vt != VT_EMPTY && v.vt != VT_NULL) {
        if (SUCCEEDED(v.ChangeType(VT_BSTR))) {
            str = v.bstrVal;
        }
    }

    return str;
}

HRESULT StringToVariant(VARIANT* pv, const wstring& str)
{
    HRESULT hr = E_FAIL;

    pv->vt = VT_NULL;
    pv->bstrVal = ::SysAllocString(str.c_str());
    if (pv->bstrVal == NULL) {
        hr = E_OUTOFMEMORY;
    } else {
        pv->vt = VT_BSTR;
        hr = S_OK;
    }

    return hr;
}


BOOL
GetTempFile(
    LPCTSTR lpszPrefix,
    CComBSTR& bstrFile
    )
{
    DWORD dwLength;
    TCHAR szBuffer[MAX_PATH];
    TCHAR szTempFile[MAX_PATH];

    dwLength = GetTempPath(CHARCOUNT(szBuffer), szBuffer);
    if (!dwLength || dwLength > CHARCOUNT(szBuffer)) {
        return FALSE;
    }

    //
    // we got directory, generate the file now
    //

    dwLength = GetTempFileName(szBuffer, lpszPrefix, 0, szTempFile);
    if (!dwLength) {
        return FALSE;
    }

    bstrFile = szTempFile;
    return TRUE;

}

wstring StrUpCase(wstring& wstr)
{
    ctype<wchar_t> _ct;
    wstring::iterator iter;

    for (iter = wstr.begin(); iter != wstr.end(); ++iter) {
        (*iter) = _ct.toupper(*iter);
    }

    return wstr;
}

/////////////////////////////////////////////////////////////////////////////
// CUpload


BOOL CUpload::GetDataFilesKey(CComBSTR& bstrVal)
{
    // STRVEC::iterator iter;
    MAPSTR2MFI::iterator iter;

    bstrVal.Empty();

    for (iter = m_DataFiles.begin(); iter != m_DataFiles.end(); ++iter) {
        if (bstrVal.Length()) {
            bstrVal.Append(TEXT("|"));
        }
        bstrVal.Append((*iter).second.strFileName.c_str());
    }
    return bstrVal.Length() != 0;

}

STDMETHODIMP CUpload::SetKey(BSTR pszKey, VARIANT* pvValue)
{
    wstring strKey = pszKey;
    VARIANT vStr;
    HRESULT hr;
    HRESULT hrRet = S_OK;


    //
    // dwwin is case-sensitive
    //
    // StrUpCase(strKey);

    if (strKey == szKeyDataFiles) { // data files cannot be set directly
        return E_INVALIDARG;
    }

    VariantInit(&vStr);

    hr = VariantChangeType(&vStr, pvValue, 0, VT_BSTR);
    if (SUCCEEDED(hr)) {
        wstring strVal = vStr.bstrVal;

        m_mapManifest[strKey] = strVal;
    } else if (pvValue->vt == VT_NULL || pvValue->vt == VT_EMPTY) {
        m_mapManifest.erase(strKey);
    } else {
        hrRet = E_INVALIDARG;
    }
    VariantClear(&vStr);

    return hrRet;
}

STDMETHODIMP CUpload::GetKey(BSTR pszKey, VARIANT *pValue)
{
    CComBSTR bstrVal;
    wstring  strKey = pszKey;

    // StrUpCase(strKey);

    if (strKey == szKeyDataFiles) {
        //
        // data files -- handled separately
        //
        if (GetDataFilesKey(bstrVal)) {
            pValue->vt = VT_BSTR;
            pValue->bstrVal = bstrVal.Copy();
        } else {
            pValue->vt = VT_NULL;
        }

    } else {

        MAPSTR2STR::iterator iter = m_mapManifest.find(strKey);
        if (iter != m_mapManifest.end()) {
            bstrVal = (*iter).second.c_str();
            pValue->vt = VT_BSTR;
            pValue->bstrVal = bstrVal.Copy();
        } else {
            pValue->vt = VT_NULL;
        }
    }
    return S_OK;
}

#define DWWIN_HEADLESS_MODE 0x00000080

BOOL CUpload::IsHeadlessMode(void)
{
    CComVariant varFlags;
    HRESULT     hr;
    BOOL        bHeadless = FALSE;

    GetKey(TEXT("Flags"), &varFlags);

    hr = varFlags.ChangeType(VT_I4);
    if (SUCCEEDED(hr)) {
        bHeadless = !!(varFlags.lVal & DWWIN_HEADLESS_MODE);
    }

    return bHeadless;
}


/*
DWORD CUpload::CountFiles(DWORD nLevel, LPCWSTR pszPath)
{
    WIN32_FIND_DATA wfd;
    wstring strPath = pszPath;
    wstring::size_type nPos;
    HANDLE hFind = INVALID_HANDLE_VALUE;
    DWORD dwCount = 0;

    nPos = strPath.length();
    if (strPath[nPos-1] != TEXT('\\')) {
        strPath += TEXT('\\');
        ++nPos;
    }

    FindFirstFileExW(

    strPath += TEXT('*');

    hFind = FindFirstFile(strPath.c_str(), &wfd);
    if (hFind != INVALID_HANDLE_VALUE) {
        do {
            if (nLevel < 3 && wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
                if (wcscmp(wfd.cFileName, TEXT(".")) && wcscmp(wfd.cFileName, TEXT(".."))) {
                    strPath.replace(nPos, wstring::nPos, wfd.cFileName);
                    dwCount += CountFiles(nLevel + 1, strPath.c_str());
                }
            } else { // file
                ++dwCount;
            }
        } while (FindNextFile(hFind, &wfd));

        FindClose(hFind);
    }

    return dwCount;
}
*/

BOOL CALLBACK CUpload::_GrabmiCallback(
    LPVOID    lpvCallbackParam, // application-defined parameter
    LPCTSTR   lpszRoot,         // root directory path
    LPCTSTR   lpszRelative,     // relative path
    PATTRINFO pAttrInfo,        // attributes
    LPCWSTR   pwszXML           // resulting xml
    )
{
    GMEPARAMS* pParams = (GMEPARAMS*)lpvCallbackParam;

    CUpload* pT = pParams->first;
    IProgressDialog* ppd = pParams->second;

    if (ppd == NULL) {
        return TRUE;
    }

    ppd->SetLine(2, lpszRoot,     TRUE, NULL);

    // ppd->SetLine(2, lpszRelative, TRUE, NULL);

    return !ppd->HasUserCancelled();
}


STDMETHODIMP CUpload::AddMatchingInfo(
    BSTR pszCommand,
    VARIANT vFilter,
    VARIANT vKey,
    VARIANT vDescription,
    VARIANT vProgress,
    BOOL *pbSuccess)
{

/*  HANDLE hThread = NULL;
    DWORD  dwExitCode = 0;
    DWORD  dwWait;
*/

    CComVariant varFilter(vFilter);
    DWORD  dwFilter = GRABMI_FILTER_NORMAL;
    wstring strKey;
    wstring strDescription;

    if (SUCCEEDED(varFilter.ChangeType(VT_I4))) {
        dwFilter   = (DWORD)varFilter.lVal;
    }

    strKey         = VariantToStr(CComVariant(vKey));
    strDescription = VariantToStr(CComVariant(vDescription));


    *pbSuccess = AddMatchingInfoInternal(::GetActiveWindow(),
                                         pszCommand,
                                         dwFilter,
                                         VariantToBOOL(CComVariant(vProgress)),
                                         strKey.empty()         ? NULL : strKey.c_str(),
                                         strDescription.empty() ? NULL : strDescription.c_str());

/*

    MITHREADPARAMBLK* pParam = new MITHREADPARAMBLK;
    CComVariant varFilter(vFilter);

    if (!pParam) {
        goto cleanup;
    }

    pParam->pThis          = this;
    pParam->strCommand     = pszCommand;
    pParam->hwndParent     = ::GetActiveWindow();
    pParam->dwFilter       = GRABMI_FILTER_NORMAL;

    if (SUCCEEDED(varFilter.ChangeType(VT_I4))) {
        pParam->dwFilter   = (DWORD)varFilter.lVal;
    }

    pParam->bNoProgress    = VariantToBOOL(CComVariant(vProgress));
    pParam->strKey         = VariantToStr(CComVariant(vKey));
    pParam->strDescription = VariantToStr(CComVariant(vDescription));

    hThread = CreateThread(NULL,
                           0,
                           (LPTHREAD_START_ROUTINE)_AddMatchingInfoThreadProc,
                           (LPVOID)pParam,
                           0,
                           NULL);
    if (!hThread) {
        goto cleanup;
    }

    dwWait = WaitForSingleObject(hThread, INFINITE);
    if (dwWait != WAIT_OBJECT_0) {
        goto cleanup;
    }

    GetExitCodeThread(hThread, &dwExitCode);

cleanup:
    if (hThread) {
        CloseHandle(hThread);
    }

    *pbSuccess = !!dwExitCode;
*/

    return S_OK;
}


DWORD WINAPI
CUpload::_AddMatchingInfoThreadProc(LPVOID lpvThis)
{
    BOOL bSuccess;
    HRESULT hr;

    MITHREADPARAMBLK* pParam = (MITHREADPARAMBLK*)lpvThis;
    if (!pParam->bNoProgress) {
        hr = CoInitialize(NULL);
        if (!SUCCEEDED(hr)) {
            pParam->bNoProgress = TRUE;
        }
    }

    bSuccess = pParam->pThis->AddMatchingInfoInternal(::GetActiveWindow(),
                                                      pParam->strCommand.c_str(),
                                                      pParam->dwFilter,
                                                      pParam->bNoProgress,
                                                      pParam->strKey.empty()         ? NULL : pParam->strKey.c_str(),
                                                      pParam->strDescription.empty() ? NULL : pParam->strDescription.c_str());
    if (!pParam->bNoProgress) {
        CoUninitialize();
    }
    delete pParam;
    return bSuccess;
}


BOOL CUpload::AddMatchingInfoInternal(
    HWND hwndParent,
    LPCWSTR pszCommand,
    DWORD   dwFilter,
    BOOL    bNoProgress,
    LPCTSTR pszKey,
    LPCTSTR pszDescription)
{
    CComBSTR bstrPath;
    CComBSTR bstrGrabmiFile;
    BOOL bSuccess = FALSE;

    IProgressDialog * ppd = NULL;
    HRESULT hr;
    GMEPARAMS GrabmiParams;
    MFI     MatchingFileInfo;
    wstring strKey;

    UINT   DriveType;
    BOOL   bLeaf = NULL;
    BOOL   bRoot = NULL;
    DWORD   dwFilters[3];
    wstring Paths[3];
    int    nDrive;
    DWORD  nPasses = 1;
    wstring DriveRoot(TEXT("X:\\"));

    //
    // this is kinda dangerous, the way it works. We collect the info while yielding to the
    // creating process (due to the start dialog
    // so the calling window needs to be disabled -- or we need to trap doing something else
    // while we're collecting data
    //


    if (!::GetExePathFromObject(pszCommand, bstrPath)) {
        return FALSE;
    }

    //
    // bstrPath is exe path, create and grab matching info
    //

    if (!GetTempFile(TEXT("ACG"), bstrGrabmiFile)) {
        goto cleanup;
    }

    //
    // we are going to run grabmi!!!
    //

    //
    // prepare callback
    //

    if (!bNoProgress) {
        hr = CoCreateInstance(CLSID_ProgressDialog,
                              NULL,
                              CLSCTX_INPROC_SERVER,
                              IID_IProgressDialog,
                              (void **)&ppd);
        if (!SUCCEEDED(hr)) {
            ppd = NULL;
        }
    }


    //
    // check to see what happened to hr
    //
    if (ppd) {
        wstring strCaption;

        strCaption = LoadResourceString(IDS_COLLECTINGDATACAPTION);
        ppd->SetTitle(strCaption.c_str());                        // Set the title of the dialog.

        ppd->SetAnimation (_Module.GetModuleInstance(), IDA_FINDANIM); // Set the animation to play.

        strCaption = LoadResourceString(IDS_WAITCLEANUP);
        ppd->SetCancelMsg (strCaption.c_str(), NULL);   // Will only be displayed if Cancel

        strCaption = LoadResourceString(IDS_GRABMISTATUS_COLLECTING);
        ppd->SetLine(1, strCaption.c_str(), FALSE, NULL);

        ppd->StartProgressDialog(hwndParent,
                                 NULL,
                                 PROGDLG_NOPROGRESSBAR|
                                    PROGDLG_MODAL|
                                    PROGDLG_NOMINIMIZE|
                                    PROGDLG_NORMAL|
                                    PROGDLG_NOTIME,
                                 NULL); // Display and enable automatic estimated time remain
    }

    //
    // this is where we have to determine whether grabmi is a going to be running wild
    // Check the drive first to see whether it's removable media
    // cases : leaf node / root node
    //       : system directory
    //       : cd-rom
    //       : temp directory
    // there could be many combinations
    //

    if (ppd) {
        wstring strCaption = LoadResourceString(IDS_CHECKING_FILES);
        ppd->SetLine(2, strCaption.c_str(), FALSE, NULL);
    }

    //
    // this is the default filter we shall use
    //
    dwFilters[0] = GRABMI_FILTER_PRIVACY;
    Paths    [0] = bstrPath;
    nPasses      = 1;

    //
    // determine if it's root/leaf node (could be both)
    //
    if (!CheckFileLocation(bstrPath, &bRoot, &bLeaf)) {
        // we cannot check the file's location
        goto GrabInformation;
    }

    DriveType = GetDriveTypeW(bstrPath); // this will give us *some* clue

    // rules:
    // cdrom and not root -- three passes
    // root - add current file
    //

    if (bRoot || DRIVE_REMOTE == DriveType) {

        dwFilters[0] |= GRABMI_FILTER_LIMITFILES;

    } else if (DRIVE_CDROM == DriveType) {

        nDrive = PathGetDriveNumber(bstrPath);
        if (nDrive >= 0) {
            dwFilters[0] |= GRABMI_FILTER_NOCLOSE|GRABMI_FILTER_APPEND;

            dwFilters[1] = GRABMI_FILTER_NORECURSE|GRABMI_FILTER_APPEND;
            Paths    [1] = DriveRoot;
            Paths    [1].at(0) = (WCHAR)(TEXT('A') + nDrive);
            nPasses = 2;
        }

    }

    if (bLeaf) {
        // we may want to do more here -- future dev
        ;
    }


GrabInformation:


    //
    // set callback context
    //
    GrabmiParams.first  = this;
    GrabmiParams.second = ppd;

    while (nPasses-- > 0) {

        if (SdbGrabMatchingInfoEx(Paths[nPasses].c_str(),
                                  dwFilters[nPasses],
                                  bstrGrabmiFile,
                                  _GrabmiCallback,
                                  (LPVOID)&GrabmiParams) == GMI_FAILED) {
            goto cleanup;
        }
    }


    //
    // figure out the key/description
    //

    if (pszDescription) {
        MatchingFileInfo.strDescription = pszDescription;
    }

    MatchingFileInfo.strFileName    = bstrGrabmiFile;
    MatchingFileInfo.bOwn           = TRUE; // we have generated this file
    //
    // key
    //

    if (pszKey == NULL) {
        strKey = MatchingFileInfo.strFileName;
    } else {
        strKey = pszKey;
    }
    StrUpCase(strKey);

    m_DataFiles[strKey] = MatchingFileInfo;

    // m_DataFiles.push_back(StrUpCase(wstring(bstrGrabmiFile)));

    //
    //
    //
    bSuccess = TRUE;

cleanup:
    if (ppd) {
        ppd->StopProgressDialog();
        ppd->Release();
    }

    return bSuccess;
}

STDMETHODIMP CUpload::AddDataFile(
    BSTR pszDataFile,
    VARIANT vKey,
    VARIANT vDescription,
    VARIANT vOwn)
{
    MFI     MatchingFileInfo;
    wstring strKey = VariantToStr(CComVariant(vKey));
    BOOL    bKeyFromName = FALSE;

    if (strKey.empty()) {
        strKey = pszDataFile;
        bKeyFromName = TRUE;
    }
    StrUpCase(strKey);

    if (m_DataFiles.find(strKey) != m_DataFiles.end() && !bKeyFromName) {
        CComBSTR bstrKey = strKey.c_str();
        RemoveDataFile(bstrKey);
    }

    MatchingFileInfo.strDescription = VariantToStr(CComVariant(vDescription));
    MatchingFileInfo.strFileName    = pszDataFile;
    MatchingFileInfo.bOwn           = VariantToBOOL(CComVariant(vOwn));

    m_DataFiles[strKey] = MatchingFileInfo;

    // m_DataFiles.push_back(StrUpCase(wstring(pszDataFile)));
    return S_OK;
}


STDMETHODIMP CUpload::RemoveDataFile(BSTR pszDataFile)
{
    // STRVEC::iterator iter;
    MAPSTR2MFI::iterator iter;
    wstring strFileName;

    wstring strDataFile = pszDataFile;

    StrUpCase(strDataFile);

    iter = m_DataFiles.find(strDataFile);
    if (iter != m_DataFiles.end()) {
        if ((*iter).second.bOwn) {
            ::DeleteFile((*iter).second.strFileName.c_str());
        }

        m_DataFiles.erase(iter);
    }

/*
    for (iter = m_DataFiles.begin(); iter != m_DataFiles.end(); ++iter) {
        if (*iter == strDataFile) {
            //
            // found it
            //
            m_DataFiles.erase(iter);
            break;
        }
    }
*/
    return S_OK;
}

STDMETHODIMP CUpload::CreateManifestFile(BOOL *pbSuccess)
{
    //
    // manifest file creation code
    //
    HANDLE hFile = INVALID_HANDLE_VALUE;
    WCHAR  UNICODE_MARKER[] = { (WCHAR)0xFEFF, L'\r', L'\n' };
    MAPSTR2STR::iterator iter;
    DWORD  dwWritten;
    wstring strLine;
    CComBSTR bstrDataFiles;
    BOOL bResult;
    BOOL bSuccess = FALSE;

    if (!GetTempFile(TEXT("ACM"), m_bstrManifest)) {
        goto cleanup;
    }

    //
    // m_bstrManifest is our file
    //


    hFile = CreateFileW(m_bstrManifest,
                        GENERIC_READ|GENERIC_WRITE,
                        FILE_SHARE_READ,
                        NULL,
                        CREATE_ALWAYS,
                        FILE_ATTRIBUTE_NORMAL,
                        NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        goto cleanup;
    }

    bResult = WriteFile(hFile, UNICODE_MARKER, sizeof(UNICODE_MARKER), &dwWritten, NULL);
    if (!bResult) {
        goto cleanup;
    }

    //
    // done with the marker, now do the manifest strings
    //
    //
    for (iter = m_mapManifest.begin(); iter != m_mapManifest.end(); ++iter) {
        strLine = (*iter).first + TEXT('=') + (*iter).second + TEXT("\r\n");
        bResult = WriteFile(hFile, strLine.c_str(), strLine.length() * sizeof(WCHAR), &dwWritten, NULL);
        if (!bResult) {
            goto cleanup;
        }
    }

    //
    // done with the general stuff, do the data files
    //

    if (GetDataFilesKey(bstrDataFiles)) {
        strLine = wstring(szKeyDataFiles) + TEXT('=') + wstring(bstrDataFiles) + TEXT("\r\n");
        bResult = WriteFile(hFile, strLine.c_str(), strLine.length() * sizeof(WCHAR), &dwWritten, NULL);
        if (!bResult) {
            goto cleanup;
        }
    }
    bSuccess = TRUE;

cleanup:

    if (hFile != INVALID_HANDLE_VALUE) {
        CloseHandle(hFile);
    }

    if (!bSuccess && m_bstrManifest.Length()) {
        DeleteFile(m_bstrManifest);
        m_bstrManifest.Empty();
    }
    *pbSuccess = bSuccess;

    return S_OK;
}

STDMETHODIMP CUpload::SendReport(BOOL *pbSuccess)
{
    UINT uSize;
    TCHAR szSystemWindowsDirectory[MAX_PATH];
    wstring strDWCmd;
    wstring strDWPath;
    STARTUPINFO         StartupInfo;
    PROCESS_INFORMATION ProcessInfo;
    DWORD dwWait;
    BOOL  bSuccess = FALSE;
    BOOL  bResult;
    DWORD dwExitCode;
    BOOL  bTerminated = FALSE;
    DWORD dwTimeout = 10; // 10ms per ping

    //
    // Create Progress dialog
    //
    IProgressDialog * ppd = NULL;
    HRESULT hr;

    if (IsHeadlessMode()) {

        hr = CoCreateInstance(CLSID_ProgressDialog,
                              NULL,
                              CLSCTX_INPROC_SERVER,
                              IID_IProgressDialog,
                              (void **)&ppd);
        if (!SUCCEEDED(hr)) {
            ppd = NULL;
        }
    }

    //
    // check to see what happened to hr
    //
    if (ppd) {
        wstring strCaption;

        strCaption = LoadResourceString(IDS_SENDINGCAPTION);
        ppd->SetTitle(strCaption.c_str());                        // Set the title of the dialog.

        ppd->SetAnimation (_Module.GetModuleInstance(), IDA_WATSONANIM); // Set the animation to play.

        strCaption = LoadResourceString(IDS_WAITCLEANUP);
        ppd->SetCancelMsg (strCaption.c_str(), NULL);   // Will only be displayed if Cancel

        strCaption = LoadResourceString(IDS_LAUNCHINGDR);
        ppd->SetLine (1, strCaption.c_str(), FALSE, NULL);

        ppd->StartProgressDialog(::GetActiveWindow(),
                                 NULL,
                                 PROGDLG_NOPROGRESSBAR|
                                    PROGDLG_MODAL|
                                    PROGDLG_NOMINIMIZE|
                                    PROGDLG_NORMAL|
                                    PROGDLG_NOTIME,
                                 NULL); // Display and enable automatic estimated time remain
    }

    uSize = ::GetSystemWindowsDirectory(szSystemWindowsDirectory,
                                        CHARCOUNT(szSystemWindowsDirectory));
    if (uSize == 0 || uSize > CHARCOUNT(szSystemWindowsDirectory)) {
        goto cleanup;
    }

    strDWPath = szSystemWindowsDirectory;
    if (strDWPath.at(strDWPath.length() - 1) != TCHAR('\\')) {
        strDWPath.append(TEXT("\\"));
    }

    strDWPath += TEXT("system32\\dwwin.exe");
    strDWCmd = strDWPath + TEXT(" -d ") + (LPCWSTR)m_bstrManifest;

    ZeroMemory(&StartupInfo, sizeof(StartupInfo));
    StartupInfo.cb = sizeof(StartupInfo);
    ZeroMemory(&ProcessInfo, sizeof(ProcessInfo));

    bResult = CreateProcess(strDWPath.c_str(),
                            (LPWSTR)strDWCmd.c_str(),
                            NULL,
                            NULL,
                            FALSE,
                            0,
                            NULL,
                            NULL,
                            &StartupInfo,
                            &ProcessInfo);
    if (bResult) {
        //
        // recover an exit code please
        //
        if (ppd) {
            wstring strSending = LoadResourceString(IDS_SENDINGINFO);
            ppd->SetLine(1, strSending.c_str(), FALSE, NULL);
        }
        while(TRUE) {
            dwWait = WaitForSingleObject(ProcessInfo.hProcess, dwTimeout);
            if (dwWait == WAIT_OBJECT_0) {
                if (GetExitCodeProcess(ProcessInfo.hProcess, &dwExitCode)) {
                    bSuccess = (dwExitCode == 0);
                } else {
                    bSuccess = FALSE;
                }
                break;

            } else if (dwWait == WAIT_TIMEOUT) {

                //
                // check the cancel button
                //

                if (ppd && !bTerminated && ppd->HasUserCancelled()) {
                    TerminateProcess(ProcessInfo.hProcess, (UINT)-1);
                    bTerminated = TRUE;
                    bSuccess = FALSE;
                    dwTimeout = 1000; // wait a bit longer
                }

            } else { // object somehow became abandoned
                bSuccess = FALSE;
                break;
            }
        }

    }

    if (ppd) {
        wstring strCleaningUp = LoadResourceString(IDS_CLEANINGUP);
        ppd->SetLine(1, strCleaningUp.c_str(), FALSE, NULL);
    }


cleanup:


    if (ProcessInfo.hThread) {
        CloseHandle(ProcessInfo.hThread);
    }
    if (ProcessInfo.hProcess) {
        CloseHandle(ProcessInfo.hProcess);
    }

    if (ppd) {
        ppd->StopProgressDialog();
        ppd->Release();
    }

    *pbSuccess = bSuccess;

    return S_OK;
}

wstring MakeXMLAttr(LPCTSTR lpszName, LPCTSTR lpszValue)
{
    wstring str;
    wstring strVal;
    LPCTSTR pch;
    wstring::size_type nPos = 0;
    int     nlen;

    if (NULL != lpszValue) {
        strVal = lpszValue;
    }

    // find and replace: all the instances of &quot; &amp; &lt; &gt;
    //
    while (nPos != wstring::npos && nPos < strVal.length()) {

        nPos = strVal.find_first_of(TEXT("&\"<>"), nPos);
        if (nPos == wstring::npos) {
            break;
        }

        switch(strVal.at(nPos)) {
        case TEXT('&'):
            pch = TEXT("&amp;");
            break;

        case TEXT('>'):
            pch = TEXT("&gt;");
            break;

        case TEXT('<'):
            pch = TEXT("&lt;");
            break;

        case TEXT('\"'):
            pch = TEXT("&quot;");
            break;
        default:
            // lunacy, we saw it -- and now it's gone
            pch = NULL;
            break;
        }

        if (pch) {
            strVal.replace(nPos, 1, pch); // one character
            nPos += _tcslen(pch);
        }
    }

    // once we got the string, assign
    str = lpszName;
    str += TEXT("=\"");
    str += strVal;
    str += TEXT("\"");

    return str;
}

wstring MakeXMLAttr(LPCTSTR lpszName, LONG lValue)
{
    WCHAR szBuf[32];
    wstring str;

    swprintf(szBuf, TEXT("\"0x%lx\""), lValue);

    str = lpszName;
    str += TEXT("=");
    str += szBuf;
    return str;
}

wstring MakeXMLLayers(LPCTSTR lpszLayers)
{
    wstring str;
    wstring strLayer;
    LPCTSTR pch, pbrk;

    //
    // partition the string
    //
    pch = lpszLayers;

    while (pch && *pch != TEXT('\0')) {

        pch += _tcsspn(pch, TEXT(" \t"));

        // check if we're not at the end
        if (*pch == TEXT('\0')) {
            break;
        }

        pbrk = _tcspbrk(pch, TEXT(" \t"));
        if (pbrk == NULL) {
            // from pch to the end of the string
            strLayer.assign(pch);
        } else {
            strLayer.assign(pch, (int)(pbrk-pch));
        }

        if (!str.empty()) {
            str += TEXT("\r\n");
        }
        str += TEXT("    "); // lead-in
        str += TEXT("<LAYER NAME=\"");
        str += strLayer;
        str += TEXT("\"/>");

        pch = pbrk;
    }

    return str;
}



STDMETHODIMP CUpload::AddDescriptionFile(
    BSTR     pszApplicationName,
    BSTR     pszApplicationPath,
    LONG     lMediaType,
    BOOL     bCompatSuccess,
    VARIANT* pvFixesApplied,
    VARIANT  vKey,
    BOOL     *pbSuccess
    )
{

    //
    // manifest file creation code
    //
    HANDLE   hFile = INVALID_HANDLE_VALUE;
    WCHAR    UNICODE_MARKER[] = { (WCHAR)0xFEFF, L'\r', L'\n' };
    DWORD    dwWritten;
    wstring  strLine;
    CComBSTR bstrDescriptionFile;
    BOOL     bResult;
    BOOL     bSuccess = FALSE;
    WCHAR    szBuf[32];
    VARIANT  vFixes;
    MFI      MatchingFileInfo;
    wstring  strKey = VariantToStr(CComVariant(vKey));
    wstring  strLayers;
    static   TCHAR szTab[] = TEXT("    ");
    static   TCHAR szCRTab[] = TEXT("\r\n    ");
    VariantInit(&vFixes);

    if (!GetTempFile(TEXT("ACI"), bstrDescriptionFile)) {
        goto cleanup;
    }

    //
    // m_bstrManifest is our file
    //


    hFile = CreateFileW(bstrDescriptionFile,
                        GENERIC_READ|GENERIC_WRITE,
                        FILE_SHARE_READ,
                        NULL,
                        CREATE_ALWAYS,
                        FILE_ATTRIBUTE_NORMAL,
                        NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        goto cleanup;
    }

    bResult = WriteFile(hFile, UNICODE_MARKER, sizeof(UNICODE_MARKER), &dwWritten, NULL);
    if (!bResult) {
        goto cleanup;
    }


    // xml marker
    strLine = TEXT("<?xml version=\"1.0\" encoding=\"UTF-16\"?>\r\n");
    bResult = WriteFile(hFile, strLine.c_str(), strLine.length() * sizeof(WCHAR), &dwWritten, NULL);
    if (!bResult) {
        goto cleanup;
    }

    // compose compat wizard...
    strLine = TEXT("<CompatWizardResults");
    strLine += TEXT(' ');
    strLine += MakeXMLAttr(TEXT("ApplicationName"), pszApplicationName);
    strLine += szCRTab;
    strLine += MakeXMLAttr(TEXT("ApplicationPath"), pszApplicationPath);
    strLine += szCRTab;
    strLine += MakeXMLAttr(TEXT("MediaType"), lMediaType);
    strLine += szCRTab;
    strLine += MakeXMLAttr(TEXT("CompatibilityResult"), bCompatSuccess ? TEXT("Success") : TEXT("Failure"));
    strLine += TEXT(">\r\n");

    if (SUCCEEDED(VariantChangeType(&vFixes, pvFixesApplied, 0, VT_BSTR))) {
        strLayers = vFixes.bstrVal;
    }

    if (!strLayers.empty()) {
        //
        // parse the layers string and get all of them listed
        //
        strLine += MakeXMLLayers(strLayers.c_str());
        strLine += TEXT("\r\n");
    }

    strLine += TEXT("</CompatWizardResults>\r\n");

    // we are done generating data, write it all out
    bResult = WriteFile(hFile, strLine.c_str(), strLine.length() * sizeof(WCHAR), &dwWritten, NULL);
    if (!bResult) {
        goto cleanup;
    }


/*

    //
    // after we get through the descriptions
    // write out data
    strLine = TEXT("[CompatWizardResults]\r\n");
    bResult = WriteFile(hFile, strLine.c_str(), strLine.length() * sizeof(WCHAR), &dwWritten, NULL);
    if (!bResult) {
        goto cleanup;
    }

    //
    // write out all the info
    //
    strLine =  TEXT("ApplicationName=");
    strLine += pszApplicationName;
    strLine += TEXT("\r\n");
    bResult = WriteFile(hFile, strLine.c_str(), strLine.length() * sizeof(WCHAR), &dwWritten, NULL);
    if (!bResult) {
        goto cleanup;
    }

    strLine =  TEXT("ApplicationPath=");
    strLine += pszApplicationPath;
    strLine += TEXT("\r\n");
    bResult = WriteFile(hFile, strLine.c_str(), strLine.length() * sizeof(WCHAR), &dwWritten, NULL);
    if (!bResult) {
        goto cleanup;
    }

    strLine =  TEXT("MediaType=");
    _sntprintf(szBuf, CHARCOUNT(szBuf), TEXT("0x%lx"), lMediaType);
    strLine += szBuf;
    strLine += TEXT("\r\n");
    bResult = WriteFile(hFile, strLine.c_str(), strLine.length() * sizeof(WCHAR), &dwWritten, NULL);
    if (!bResult) {
        goto cleanup;
    }

    //
    // success
    //
    strLine = TEXT("CompatibilityResult=");
    strLine += bCompatSuccess ? TEXT("Success") : TEXT("Failure");
    strLine += TEXT("\r\n");
    bResult = WriteFile(hFile, strLine.c_str(), strLine.length() * sizeof(WCHAR), &dwWritten, NULL);
    if (!bResult) {
        goto cleanup;
    }

    //
    // fixes applied
    //
    strLine = TEXT("Layers=");
    if (!SUCCEEDED(VariantChangeType(&vFixes, pvFixesApplied, 0, VT_BSTR))) {
        strLine += TEXT("none");
    } else {
        strLine += vFixes.bstrVal;
    }
    strLine += TEXT("\r\n");
    bResult = WriteFile(hFile, strLine.c_str(), strLine.length() * sizeof(WCHAR), &dwWritten, NULL);
    if (!bResult) {
        goto cleanup;
    }
*/



    // standard file -- manifesto
    MatchingFileInfo.strDescription = TEXT("Application Compatibility Description File");
    MatchingFileInfo.strFileName    = bstrDescriptionFile;
    MatchingFileInfo.bOwn           = TRUE;

    //
    // key is the filename prefixed by ACI_c:\foo\bar.exe
    //
    if (strKey.empty()) {
        strKey = TEXT("ACI_");
        strKey += pszApplicationPath;
    }
    StrUpCase(strKey);

    m_DataFiles[strKey] = MatchingFileInfo;

    // m_DataFiles.push_back(StrUpCase(wstring(bstrDescriptionFile)));
    bSuccess = TRUE;

cleanup:

    if (hFile != INVALID_HANDLE_VALUE) {
        CloseHandle(hFile);
    }

    if (!bSuccess && bstrDescriptionFile.Length()) {
        DeleteFile(bstrDescriptionFile);
    }
    *pbSuccess = bSuccess;

    VariantClear(&vFixes);

    return S_OK;
}

STDMETHODIMP CUpload::DeleteTempFiles()
{
    // kill all the supplemental files

    // STRVEC::iterator iter;
    MAPSTR2MFI::iterator iter;

    for (iter = m_DataFiles.begin(); iter != m_DataFiles.end(); ++iter) {
        if ((*iter).second.bOwn) {
            ::DeleteFile((*iter).second.strFileName.c_str());
        }
    }

    m_DataFiles.clear();

    //
    // kill the manifest
    //
    if (m_bstrManifest.Length() > 0) {
        ::DeleteFile((LPCTSTR)m_bstrManifest);
    }
    m_bstrManifest.Empty();

    return S_OK;
}

VOID CUpload::ListTempFiles(wstring& str)
{
//    STRVEC::iterator iter;
    MAPSTR2MFI::iterator iter;

    str = TEXT("");

    for (iter = m_DataFiles.begin(); iter != m_DataFiles.end(); ++iter) {
        if (!str.empty()) {
            str += TEXT(";");
        }
        str += (*iter).second.strFileName.c_str();
    }

/*  // this will show the manifest file as well -- but I don't think we need to
    // do this as the manifest is irrelevant
    if (!str.empty()) {
        str += TEXT("\r\n");
    }
    str += (LPCTSTR)m_bstrManifest;
*/
}

STDMETHODIMP CUpload::ShowTempFiles()
{
    TCHAR szMshtml[] = TEXT("mshtml.dll");
    TCHAR szModuleFileName[MAX_PATH];
    LPMONIKER pmk = NULL;
    HRESULT hr;
    CComVariant vargIn;
    CComVariant vargOut;
    DWORD dwLength;
    TCHAR szUrl2[MAX_PATH];
    wstring strURL = TEXT("res://");
    wstring strArg;
    SHOWHTMLDIALOGFN* pfnShowDlg = NULL;

    HMODULE hMshtml = ::GetModuleHandle(szMshtml);
    if (NULL == hMshtml) {
        hMshtml = ::LoadLibrary(szMshtml);
        if (NULL == hMshtml) {
            goto cleanup;
        }
    }

    pfnShowDlg = (SHOWHTMLDIALOGFN*)GetProcAddress(hMshtml,
                                                   "ShowHTMLDialog");

    if (NULL == pfnShowDlg) {
        goto cleanup;
    }

    dwLength = ::GetModuleFileName(_Module.GetModuleInstance(),
                                   szModuleFileName,
                                   CHARCOUNT(szModuleFileName));

    if (dwLength == 0 || dwLength >= CHARCOUNT(szModuleFileName)) {
        goto cleanup;
    }


    _sntprintf(szUrl2, CHARCOUNT(szUrl2),
               TEXT("/#%d/%s"),
               (int)PtrToInt(RT_HTML),
               IDR_SHOWTEMPFILESDLG);

    strURL += szModuleFileName;
    strURL += szUrl2;

    hr = CreateURLMoniker(NULL, strURL.c_str(), &pmk);

    if (!SUCCEEDED(hr)) {
        goto cleanup;
    }

    ListTempFiles(strArg);
    // create argument In
    vargIn = strArg.c_str();

    pfnShowDlg(::GetActiveWindow(),
               pmk,
               &vargIn,
               TEXT("center:yes"),
               &vargOut);

cleanup:

    if (NULL != pmk) {
        pmk->Release();
    }

    return S_OK;
}

STDMETHODIMP CUpload::GetDataFile(VARIANT vKey, LONG InformationClass, VARIANT* pVal)
{
    CComVariant varKey(vKey);
    LONG lIndex;
    MAPSTR2MFI::iterator iter;
    wstring str;
    HRESULT hr = S_OK;

    pVal->vt = VT_NULL;

    switch(InformationClass) {
    case InfoClassCount:
        // requested: counter
        pVal->vt = VT_I4;
        pVal->lVal = m_DataFiles.size();
        break;

    case InfoClassKey:

        if (!SUCCEEDED(varKey.ChangeType(VT_I4))) {
            break;
        }
        lIndex = varKey.lVal;
        iter = m_DataFiles.begin();
        while (iter != m_DataFiles.end() && lIndex > 0) {
            ++iter;
            --lIndex;
        }

        if (iter != m_DataFiles.end()) {
            hr = StringToVariant(pVal, (*iter).first);
        }
        break;

    case InfoClassFileName:
    case InfoClassDescription:

        if (SUCCEEDED(varKey.ChangeType(VT_I4))) {
            lIndex = varKey.lVal;
            iter = m_DataFiles.begin();
            while (iter != m_DataFiles.end() && lIndex > 0) {
                ++iter;
                --lIndex;
            }

        } else if (SUCCEEDED(varKey.ChangeType(VT_BSTR))) {
            str = varKey.bstrVal;
            iter = m_DataFiles.find(str);
        }

        if (iter != m_DataFiles.end()) {
            switch(InformationClass) {
            case InfoClassFileName:
                str = (*iter).second.strFileName;
                break;

            case InfoClassDescription:
                str = (*iter).second.strDescription;
                break;
            }

            hr = StringToVariant(pVal, str);

        }
        break;
    default:
        break;
    }

    return hr;
}