#include <fusenetincludes.h>

#include <bits.h>
#include <assemblycache.h>
#include "dialog.h"
#include <assemblydownload.h>

#include "..\id\sxsid.h"
#include ".\patchapi.h"

// Update services
#include "server.h"

#define DOWNLOAD_FLAGS_INTERNAL_TRAVERSE_LINK (DOWNLOAD_FLAGS_NOTIFY_COMPLETION + 1)
#define PATCH_DIRECTORY L"__patch__\\"


IBackgroundCopyManager* CAssemblyDownload::g_pManager = NULL;

// ---------------------------------------------------------------------------
// CreateAssemblyDownload
// ---------------------------------------------------------------------------
STDAPI CreateAssemblyDownload(IAssemblyDownload** ppDownload)
{
    HRESULT hr = S_OK;

    CAssemblyDownload *pDownload = new(CAssemblyDownload);
    if (!pDownload)
    {
        hr = E_OUTOFMEMORY;
        goto exit;
    }

exit:

    *ppDownload = (IAssemblyDownload*) pDownload;

    return hr;
}



// ---------------------------------------------------------------------------
// ctor
// ---------------------------------------------------------------------------
CAssemblyDownload::CAssemblyDownload()
    :   _dwSig('DLND'), _cRef(1), _hr(S_OK), _pRootEmit(NULL), _hNamedEvent(NULL),
    _pDlg(NULL)
{}

// ---------------------------------------------------------------------------
// dtor
// ---------------------------------------------------------------------------
CAssemblyDownload::~CAssemblyDownload()
{
    SAFERELEASE(_pRootEmit);
    SAFEDELETE(_pDlg);

    // BUGBUG: Do proper ref counting and release here
    //SAFERELEASE(g_pManager);
}


// IAssemblyDownload methods


void MakeSequentialFileName(CString& sPath)
{
    int iSeqNum = 1;
    int iIndex = sPath._cc;

    // BUGBUG: hack up name generation code

    sPath.Append(L"[1]");
    while (GetFileAttributes(sPath._pwz) != (DWORD)-1)
    {
        // keep incrementing till unique...
        iSeqNum++;

        // BUGBUG: note string len limitation
        WCHAR buffer[20];

        _itow(iSeqNum, buffer, 10);

        sPath[iIndex] = L'/';
        sPath.RemoveLastElement();
        sPath.Append(buffer);
        sPath.Append(L"]");

        // BUGBUG: MAX_PATH restriction?
    }
}


// ---------------------------------------------------------------------------
// DownloadManifestAndDependencies
// ---------------------------------------------------------------------------
HRESULT CAssemblyDownload::DownloadManifestAndDependencies(
    LPWSTR pwzApplicationManifestUrl, HANDLE hNamedEvent, DWORD dwFlags)
{
    HRESULT hr = S_OK;
    LPWSTR pwz = NULL;
    IBackgroundCopyJob *pJob = NULL;
    GUID guid = {0};

    CString sTempManifestPath;
    CString sAppUrl;
    CString sManifestFileName;

    // Create temporary manifest path from url.
    sAppUrl.Assign(pwzApplicationManifestUrl);
    sAppUrl.LastElement(sManifestFileName);
    CAssemblyCache::GetCacheRootDir(sTempManifestPath, CAssemblyCache::Staging);
    sTempManifestPath.Append(sManifestFileName._pwz);
    MakeSequentialFileName(sTempManifestPath);

    // BUGBUG - do real check
    if (!g_pManager)
    {
        CoCreateInstance(CLSID_BackgroundCopyManager, NULL, CLSCTX_LOCAL_SERVER, 
                IID_IBackgroundCopyManager, (void**) &g_pManager);
    }
    
    // just give it a location name
    hr = g_pManager->CreateJob(pwzApplicationManifestUrl, 
        BG_JOB_TYPE_DOWNLOAD, &guid, &pJob);

    // Init dialog object with job.
    if (dwFlags == DOWNLOAD_FLAGS_PROGRESS_UI)
    {
        hr = CreateDialogObject(&_pDlg, pJob);

    //felixybc no need        _pDlg->_pDownload = this; // bugbug - if addref, circular refcount
    }
    else if ((dwFlags == DOWNLOAD_FLAGS_INTERNAL_TRAVERSE_LINK) && _pDlg)
        _pDlg->SetJobObject(pJob);
    else if (dwFlags == DOWNLOAD_FLAGS_NOTIFY_COMPLETION)
        _hNamedEvent = hNamedEvent;
        
    // Set job config info.
    hr = pJob->SetNotifyInterface(static_cast<IBackgroundCopyCallback*> (this));    
    hr = pJob->SetNotifyFlags(BG_NOTIFY_JOB_MODIFICATION | BG_NOTIFY_JOB_TRANSFERRED | BG_NOTIFY_JOB_ERROR);
    hr = pJob->SetPriority(BG_JOB_PRIORITY_FOREGROUND);

    // Ensure local dir path exists.
    CAssemblyCache::CreateDirectoryHierarchy(NULL, sTempManifestPath._pwz);

    // Do the download;
    pJob->AddFile(pwzApplicationManifestUrl, sTempManifestPath._pwz);
    pJob->Resume();

    // We're releasing the job but BITS addrefs it.
    SAFERELEASE(pJob);

    // Pump messages if progress ui specified.
    if (dwFlags == DOWNLOAD_FLAGS_PROGRESS_UI)
    {
        MSG msg;
        BOOL bRet;
        DWORD dwError;
        while((bRet = GetMessage( &msg, _pDlg->_hwndDlg, 0, 0 )))
        {
            DWORD dwLow = LOWORD(msg.message);
            if (dwLow == WM_FINISH_DOWNLOAD || dwLow == WM_CANCEL_DOWNLOAD)
            {
                DestroyWindow(_pDlg->_hwndDlg);

                // BUGBUG: delete all committed files and app dir if canceling!
                if (dwLow == WM_CANCEL_DOWNLOAD)
                    hr = E_ABORT;
                
                break;
            }

            if (bRet == -1)
            {
                dwError = GetLastError();
                DebugBreak();
            }
            if (!IsDialogMessage(_pDlg->_hwndDlg, &msg))
            {
                TranslateMessage( &msg );
                DispatchMessage( &msg );
            }
        }

    }
    return hr;
}

// ---------------------------------------------------------------------------
// DoCacheUpdate
// ---------------------------------------------------------------------------
HRESULT CAssemblyDownload::DoCacheUpdate(IBackgroundCopyJob *pJob)
{
    LPWSTR pwz             = NULL;
    DWORD nCount          = 0, cc = 0;
    SERIALIZED_LIST          ManifestList = {0};
    IEnumBackgroundCopyFiles *pEnumFiles = NULL;
    IBackgroundCopyFile       *pFile = NULL;
    IAssemblyCacheEmit       *pEmit = NULL;
    IAssemblyCacheImport     *pCacheImport = NULL;
    IBackgroundCopyJob       *pChildJob = NULL;
    CString sDisplayName;
    CString sManifestStagingDir;
    CString sManifestFilePath, sManifestPatchFilePath;
    LPASSEMBLY_IDENTITY pPatchAssemblyId = NULL;
    BOOL fAdditionalDependencies = FALSE;

    CAssemblyCache::GetCacheRootDir(sManifestStagingDir, CAssemblyCache::Staging);

    // Commit files to disk
    pJob->Complete();    

    // Get the file enumerator.
    pJob->EnumFiles(&pEnumFiles);
    pEnumFiles->GetCount(&nCount);

    for (DWORD i = 0; i < nCount; i++)            
    {
        CString sLocalName(CString::COM_Allocator);
        
        pEnumFiles->Next(1, &pFile, NULL);        
        pFile->GetLocalName(&pwz);
        sLocalName.TakeOwnership(pwz);

        // This is somewhat hacky - we rely on the local target path
        // returned from BITS to figure out if a manifest file.
        if (sLocalName.PathPrefixMatch(sManifestStagingDir._pwz) == S_OK)
        {
            // First thing we need to do is figure out if this
            // is a subscription manifest which we have to indirect 
            // through            
            LPASSEMBLY_MANIFEST_IMPORT pManifestImport = NULL;
            LPDEPENDENT_ASSEMBLY_INFO pDependAsmInfo = NULL;
            CString sManifestFileName;
            CString sDependantASMCodebase;
            DWORD dwManifestType = MANIFEST_TYPE_UNKNOWN;

            CreateAssemblyManifestImport(&pManifestImport, sLocalName._pwz);

            pManifestImport->ReportManifestType(&dwManifestType);
            if (dwManifestType == MANIFEST_TYPE_SUBSCRIPTION)
            {
                // BUGBUG: the hardcoded index '0'
                pManifestImport->GetNextAssembly(0, &pDependAsmInfo);
            }

            if (pDependAsmInfo)
            {
                // We know its a subscription
                // just transit to the referenced codebase.
                // BUGBUG - need to clean up the old manifest in staging dir.

                _hr = pDependAsmInfo->Get(DEPENDENT_ASM_CODEBASE, &pwz, &cc);
                sDependantASMCodebase.TakeOwnership(pwz, cc);

                if (sDependantASMCodebase._pwz)
                {
                    LPASSEMBLY_IDENTITY pAsmId = NULL;

                    // subscription's dependent asm's id == app's asm id (fully qualified)                    
                    if ((_hr = pDependAsmInfo->GetAssemblyIdentity(&pAsmId)) == S_OK)
                    {
                        CString sName;
                        
                        _hr = pAsmId->GetAttribute(SXS_ASSEMBLY_IDENTITY_STD_ATTRIBUTE_NAME_NAME, &pwz, &cc);
                        sName.TakeOwnership(pwz, cc);

                        // _hr from above GetAttribute
                        if (_hr == S_OK)
                        {
                            IAssemblyUpdate *pAssemblyUpdate = NULL;

                            // register for updates
                            if (SUCCEEDED(_hr = CoCreateInstance(CLSID_CAssemblyUpdate, NULL, CLSCTX_LOCAL_SERVER, 
                                                IID_IAssemblyUpdate, (void**)&pAssemblyUpdate)))
                            {
                                CString sRemoteName(CString::COM_Allocator);
                                LPWSTR pwzSubscriptionManifestCodebase = NULL;
                                DWORD pollingInterval;

                                pFile->GetRemoteName(&pwzSubscriptionManifestCodebase);
                                sRemoteName.TakeOwnership(pwzSubscriptionManifestCodebase);

                                // Get subscription polling interval from manifest
                                _hr = pManifestImport->GetPollingInterval (&pollingInterval);

                                _hr = pAssemblyUpdate->RegisterAssemblySubscription(sName._pwz, 
                                            sRemoteName._pwz, pollingInterval);

                                SAFERELEASE(pAssemblyUpdate);
                            }
                            // else
                                // Error in update services. Cannot register subscription for updates - fail gracefully
                                // BUGBUG: need a way to recover from this and register later
                                
                        }
                        // else
                            // Error in retrieving assembly name. Cannot register subscription for updates - fail gracefully
                            // BUGBUG: This should not be allowed!
    
                        // check if this download is necessary
                        // download only if not in cache
                        if ((_hr=CreateAssemblyCacheImport(&pCacheImport, pAsmId, CACHEIMP_CREATE_RETRIEVE_EXIST)) == S_FALSE)
                        {
                            if (_pDlg)
                            {
                                _pDlg->InitDialog(_pDlg->_hwndDlg);
                                _pDlg->SetDlgState(DOWNLOADDLG_STATE_GETTING_APP_MANIFEST);
                            }

                            _hr = DownloadManifestAndDependencies(sDependantASMCodebase._pwz, NULL, DOWNLOAD_FLAGS_INTERNAL_TRAVERSE_LINK);
                        }
                             
                        // else
                            // assume it's being handled or it's done

                        SAFERELEASE(pCacheImport);
                    }
                        
                    // BUGBUG: should check file integrity

                    SAFERELEASE(pAsmId);
                }                                          

                else
                    // redirected to a manifest which has no dependentassembly/codebase
                   _hr = HRESULT_FROM_WIN32(ERROR_BAD_FORMAT);


                SAFERELEASE(pDependAsmInfo);

                // We're done with the subscription manifest now
                // and can release the interface and delete it from the manifest staging dir.
                SAFERELEASE(pManifestImport);
                ::DeleteFile(sLocalName._pwz);
                
                goto exit;
            }
            SAFERELEASE(pManifestImport);
            
            // Not a subscription manifest - pull down dependencies.
            fAdditionalDependencies = TRUE;

            // Generate the cache entry (assemblydir/manifest/<dirs>)
            // First callbac, _pRootEmit = NULL;
            CreateAssemblyCacheEmit(&pEmit, _pRootEmit, 0);

            // Generate manifest file codebase directory
            // used for enqueuing parsed dependencies.
            CString sCodebase(CString::COM_Allocator);
            pFile->GetRemoteName(&pwz);
            sCodebase.TakeOwnership(pwz);
            sCodebase.LastElement(sManifestFileName);
            sCodebase.RemoveLastElement();
            sCodebase.Append(L"/");

            // Create the cache entry.
            // (x86_foo_1.0.0.0_en-us/foo.manifest/<+extra dirs>)
            pEmit->CopyFile(sLocalName._pwz, sManifestFileName._pwz, MANIFEST);

            // If this is first cache entry created, save as root.
            if (!_pRootEmit)
            {
                _pRootEmit = pEmit;
                _pRootEmit->AddRef();
            }        

            // QI for the import interface.
            pEmit->QueryInterface(IID_IAssemblyCacheImport, (LPVOID*) &pCacheImport);

            // First time through loop get the display name
            if (!i)
            {
                pCacheImport->GetDisplayName(&pwz, &cc);
                sDisplayName.TakeOwnership(pwz, cc);                
            }

            // Line up it's dependencies for download and fire them off.
            // We pass the cache import interface which provides the
            // manifest enumeration. We could just as easily passed a manifest
            // interface but already have one in the pCacheImport
            EnqueueDependencies(pCacheImport, sCodebase, sDisplayName, &pChildJob);

            SAFERELEASE(pEmit);
            SAFERELEASE(pCacheImport);
        }


        // if file was a patch file, find the source and target, apply patch to source and move result to target
        // or if file was compressed, uncompress file
        else
        {
            // Grab mainfest file directory and append on PATCH_DIRECTORY
            // "C:\Program Files\Application Store\x86_foo_X.X.X.X\PATCH_DIRECTORY\"
            _pRootEmit->GetManifestFileDir(&pwz, &cc);
            sManifestFilePath.TakeOwnership(pwz, cc);
            sManifestPatchFilePath.Assign(sManifestFilePath);
            sManifestPatchFilePath.Append(PATCH_DIRECTORY);

            // if local file begins with the manifests patch direcotry, file is a patch file
            if (sLocalName.PathPrefixMatch(sManifestPatchFilePath._pwz) == S_OK)
            {
                CString sPatchDisplayName;
                
                // init pPatchAssemblyId only once                    
                if (!pPatchAssemblyId)
                {
                    _hr = GetPatchDisplayNameFromFilePath (sLocalName, sPatchDisplayName);
                    CreateAssemblyIdentityEx(&pPatchAssemblyId, 0, sPatchDisplayName._pwz);
                }

                // Check to see if file is a cab file (have to revamp IsCABbed to handle this by passing in the AssemblyId)
                // If it is CABbed, then have to call the FDI functions and pass in base directory (should have relative paths in 
                // the cab .ddf file (done by tool).


                // using the patch file path, step through the manifest to to find
                // the source and target files associated with the patch file and
                // apply the patch file to the the source file to create the target file.                
                _hr = ApplyPatchFile (pPatchAssemblyId, sLocalName._pwz);
            }
        }

        SAFERELEASE(pFile);
    }

    // if patched, delete patch directory
    if (pPatchAssemblyId)
        _hr = RemoveDirectoryAndChildren(sManifestPatchFilePath._pwz);

    SAFERELEASE(pEnumFiles);

    // Submit the job.
    if (pChildJob)
    {
        if (_pDlg)
        {
            _pDlg->InitDialog(_pDlg->_hwndDlg);
            _pDlg->SetDlgState(DOWNLOADDLG_STATE_GETTING_OTHER_FILES);
            _pDlg->SetJob(pChildJob);
        }
        pChildJob->Resume();            
        SAFERELEASE(pChildJob);
    }

    // If no additional jobs 
    if (!fAdditionalDependencies)
    {
        // If we have a dialog then DoFinish will commit bits.
        // BUGBUG - formalize done semantics

        _pRootEmit->Commit(0);

        if (_hNamedEvent)
            SetEvent(_hNamedEvent);

        _pDlg->SetDlgState(DOWNLOADDLG_STATE_ALL_DONE);
    }
    
exit:
    SAFERELEASE(pPatchAssemblyId);

    return S_OK;
}


// ---------------------------------------------------------------------------
//GetPatchDisplayNameFromFilePath
// ---------------------------------------------------------------------------
HRESULT CAssemblyDownload::GetPatchDisplayNameFromFilePath ( CString &sPatchFilePath, CString &sDisplayName)
{
    CString pwzFilePath;
    LPWSTR pwzStart, pwzEnd;

    pwzFilePath.Assign(sPatchFilePath);

    // Search file path for the PATCH_DIRECTORY
    pwzStart = StrStr(pwzFilePath._pwz, PATCH_DIRECTORY);

    // Set start pointer the one directory below the PATCH_DIRECTORY
    // This is the Beginning of the Patch DisplayNameb
    pwzStart = StrChr(pwzStart, L'\\');
    pwzStart++;
    
    // Set end pointer to the end of the Patch DisplayName and null out the character
    pwzEnd = StrChr(pwzStart, L'\\');
    (*pwzEnd) = L'\0';

    sDisplayName.Assign(pwzStart);

    return S_OK;
}


// ---------------------------------------------------------------------------
//ApplyPatchFile
// ---------------------------------------------------------------------------
HRESULT CAssemblyDownload::ApplyPatchFile ( LPASSEMBLY_IDENTITY pPatchAssemblyId, LPWSTR pwzPatchFilePath)
{
    int i = 0;
    LPWSTR pwzSource = NULL, pwzTarget = NULL;
    LPWSTR pwzBuf;
    DWORD ccBuf;
    CString sPatchLocalName;
    CString sPatchDisplayName;
    CString sManifestDir, sPatchManifestDir;
    CString sSourcePath, sTargetPath, sPatchPath;
    IAssemblyManifestImport *pManifestImport = NULL;
    LPASSEMBLY_IDENTITY pTempPatchAssemblyId = NULL;
    LPASSEMBLY_CACHE_IMPORT pPatchImport = NULL;


    // Get DisplayName of the "Patch From" Assembly
    _hr = pPatchAssemblyId->GetDisplayName(ASMID_DISPLAYNAME_NOMANGLING, &pwzBuf, &ccBuf);
    sPatchDisplayName.TakeOwnership(pwzBuf, ccBuf);
    
    //Parse out the local file path from the full file path of the patch file
    pwzBuf= StrStr (pwzPatchFilePath, sPatchDisplayName._pwz);
    pwzBuf = StrChr(pwzBuf, L'\\');

    //Following commented out code needed for NULL patching
    /*
    // Take care of patching from "itself"
     if (StrStr (pwzPatchFilePath, sPatchDisplayName._pwz) != NULL)
     {
          pwzBuf= StrStr (pwzBuf, sPatchDisplayName._pwz);
          pwzBuf = StrChr(pwzBuf, L'\\');
     }
    */
    pwzBuf++;
    sPatchLocalName.Assign(pwzBuf);
   
    _pRootEmit->GetManifestImport(&pManifestImport);
    _pRootEmit->GetManifestFileDir(&pwzBuf, &ccBuf);
    sManifestDir.TakeOwnership (pwzBuf, ccBuf);

    // set up the patchAssemblyNode in the manifestimport
     while ((_hr = pManifestImport->GetNextPatchAssemblyId (i,  &pTempPatchAssemblyId)) == S_OK)
    {
        if(FAILED(_hr =pPatchAssemblyId->IsEqual(pTempPatchAssemblyId)))
            goto exit;
        else if (_hr == S_OK)
        {
            _hr = pManifestImport->SetPatchAssemblyNode(i);

            // get the cacheImport for the "patch from" assembly
             if ((_hr = CreateAssemblyCacheImport(&pPatchImport, pPatchAssemblyId, CACHEIMP_CREATE_RETRIEVE_EXIST_COMPLETED))!= S_OK)
                goto exit;
                            
            break;        
        }

        i++;
        SAFERELEASE(pTempPatchAssemblyId);
    }

    // there has to be a matching patchassembly node.
    if (_hr != S_OK)
        goto exit;
    
    pPatchImport->GetManifestFileDir(&pwzBuf, &ccBuf);
    sPatchManifestDir.TakeOwnership (pwzBuf, ccBuf);

    if((_hr = pManifestImport->GetPatchFilePatchMapping(sPatchLocalName._pwz, &pwzSource, &pwzTarget)) != S_OK)
        goto exit;

    // Set up paths of source, target and patch files
    // set up Source path
    // If NULL patching, must add code to call sSourcePath.FreeBuffer is pwzSource is NULL
/*        
    sSourcePath.Assign(sManifestDir);
    sSourcePath.Append(PATCH_DIRECTORY);
    sSourcePath.Append(sPatchDisplayName);
    sSourcePath.Append(L"\\");
*/

    sSourcePath.Append(sPatchManifestDir);
    sSourcePath.Append(pwzSource);
    
    // set up Target path
    sTargetPath.Assign(sManifestDir);
    sTargetPath.Append(pwzTarget);
           
    // set up Patch path
    sPatchPath.Assign(pwzPatchFilePath);

    //Apply patchfile to sSource (grab from patch directory) and copy to path specified by sTarget
    if (!(ApplyPatchToFile((LPCWSTR)sPatchPath._pwz, (LPCWSTR)sSourcePath._pwz, (LPCWSTR)sTargetPath._pwz, 0)))
        _hr = E_FAIL;

    goto exit;       
          
   
exit:
    SAFEDELETEARRAY(pwzSource);
    SAFEDELETEARRAY(pwzTarget);
    SAFERELEASE(pTempPatchAssemblyId);    
    SAFERELEASE(pManifestImport);
    SAFERELEASE(pPatchImport);
    
    return _hr;
}


// ---------------------------------------------------------------------------
// EnqueueDependencies
// ---------------------------------------------------------------------------
HRESULT CAssemblyDownload::EnqueueDependencies(LPASSEMBLY_CACHE_IMPORT 
    pCacheImport, CString &sCodebase, CString &sDisplayName, IBackgroundCopyJob **ppJob)
{

    LPWSTR pwzBuf = NULL;
    LPWSTR pwzPatchFile, pwzSource;
    
    DWORD ccBuf   = 0;
    DWORD n = 0, i=0;

    BOOL patchAvailable = FALSE;
    BOOL CABbed = FALSE;

    CString sLocalPatchDirectoryPath, sPatchAssemblyDisplayName, sPatchManifestDirectory;
    CString sManifestDirectory;
    
    GUID guid = {0};
    IBackgroundCopyJob *pJob = NULL;
    IAssemblyManifestImport *pManifestImport = NULL;
    IAssemblyIdentity *pIdentity = NULL;
    IAssemblyFileInfo *pAssemblyFile = NULL;
    LPDEPENDENT_ASSEMBLY_INFO pDependAsm = NULL;
    LPASSEMBLY_CACHE_EMIT pCacheEmit = NULL;
    LPASSEMBLY_CACHE_IMPORT pMaxCachedImport = NULL;
    LPASSEMBLY_CACHE_IMPORT pMaxPatchImport = NULL;
    LPASSEMBLY_IDENTITY pPatchAssemblyId = NULL;


    // Create a new job
    if (*ppJob)
        pJob = *ppJob;
    else
    {
        g_pManager->CreateJob(sDisplayName._pwz,  BG_JOB_TYPE_DOWNLOAD, &guid, &pJob);
        pJob->SetNotifyInterface(static_cast<IBackgroundCopyCallback*> (this));    
        _hr = pJob->SetNotifyFlags(BG_NOTIFY_JOB_MODIFICATION | BG_NOTIFY_JOB_TRANSFERRED | BG_NOTIFY_JOB_ERROR);
        _hr = pJob->SetPriority(BG_JOB_PRIORITY_FOREGROUND);
   }
    
    // Get the cache import's manifest interface
    pCacheImport->GetManifestImport(&pManifestImport);

    // Get the asm Id
    _hr = pManifestImport->GetAssemblyIdentity(&pIdentity);

    // Find max completed version, if any
    // Init newly created cache import with the highest completed version
    // else S_FALSE or E_* and pMaxCachedImport == NULL - no completed version
    _hr = CreateAssemblyCacheImport(&pMaxCachedImport, pIdentity, CACHEIMP_CREATE_RETRIEVE_MAX_COMPLETED);
    
    // Check to see if there is a suitable upgradable version to patch from already in cache
    while (pManifestImport->GetNextPatchAssemblyId (i,  &pPatchAssemblyId) == S_OK)
    { 

        if (FAILED(_hr = CreateAssemblyCacheImport(&pMaxPatchImport, pPatchAssemblyId, CACHEIMP_CREATE_RETRIEVE_EXIST_COMPLETED)))
            goto exit;

        if (_hr == S_FALSE)
        {
                if(FAILED(_hr =pPatchAssemblyId->IsEqual(pIdentity)))
                    goto exit;
                else if (_hr == S_OK)
                {
                    pMaxPatchImport = pCacheImport;
                    pMaxPatchImport->AddRef();
                }
        }
                
        if (_hr == S_OK)          
        {
            // Set the patch assembly node
            pManifestImport->SetPatchAssemblyNode(i);
            
            // grab the manifest directory
            pCacheImport->GetManifestFileDir(&pwzBuf, &ccBuf);
            sManifestDirectory.TakeOwnership(pwzBuf, ccBuf);

            // get the manifest directory of the "patch from" directory
            pMaxPatchImport->GetManifestFileDir(&pwzBuf, &ccBuf);
            sPatchManifestDirectory.TakeOwnership(pwzBuf, ccBuf);
            
            //get display name of patch assembly identity
            _hr = (pPatchAssemblyId->GetDisplayName(ASMID_DISPLAYNAME_NOMANGLING, &pwzBuf, &ccBuf));
            sPatchAssemblyDisplayName.TakeOwnership (pwzBuf, ccBuf);

            //get the local path of the "patch to" directory
            sLocalPatchDirectoryPath.Assign(PATCH_DIRECTORY);
            sLocalPatchDirectoryPath.PathCombine(sPatchAssemblyDisplayName);
            sLocalPatchDirectoryPath.Append(L"\\");
            sLocalPatchDirectoryPath.PathNormalize();

            //create the patch directory
            CAssemblyCache::CreateDirectoryHierarchy(sManifestDirectory._pwz, sLocalPatchDirectoryPath._pwz);

            patchAvailable = TRUE;
            break;
        }

        i++;
        
        // Release pPatchAssemblyId every time through the loop
        // If the break is executed, the last pPatchAssemblyId is Released at end of the function
        SAFERELEASE (pPatchAssemblyId);
    }

    SAFERELEASE(pIdentity);

    // Lazy init. QI for the emit interface.
    if (!pCacheEmit)
        _hr = pCacheImport->QueryInterface(IID_IAssemblyCacheEmit, (LPVOID*) &pCacheEmit);

    if (!pCacheEmit)
    {
        _hr = E_FAIL;
        goto exit;                    
    }

    //Check to see if files are contained in a CAB. If so, entire pjob is the on cab file.
    if (patchAvailable)
    {
        LPWSTR pwzCabName;
        if ((_hr =pManifestImport->IsCABbed(&pwzCabName)) == S_OK)
        {
            n=0;
            CString sFileName;
            CString sLocalFilePath;
            CString sRemoteUrl;

            sFileName.TakeOwnership(pwzCabName);

             // Form local file path
            pCacheImport->GetManifestFileDir(&pwzBuf, &ccBuf);
            sLocalFilePath.TakeOwnership(pwzBuf, ccBuf);

            // Combine and ensure backslashes.
            sLocalFilePath.PathCombine(sFileName);
            sLocalFilePath.PathNormalize();
                 
            // Form remote name
            sRemoteUrl.Assign(sCodebase);
            sRemoteUrl.UrlCombine(sFileName);
             
            // add the file to the job.
            pJob->AddFile(sRemoteUrl._pwz, sLocalFilePath._pwz);

            CABbed = TRUE;
        }
    }

    
    // Submit files directly into their target dirs.
    while ((!CABbed) && (pManifestImport->GetNextFile(n++, &pAssemblyFile) == S_OK))
    {     
        CString sFileName;
        CString sLocalFilePath;
        CString sRemoteUrl;
        BOOL bSkipFile = FALSE;

        // File name parsed from manifest.
        pAssemblyFile->Get(ASM_FILE_NAME, &pwzBuf, &ccBuf);
        sFileName.TakeOwnership(pwzBuf, ccBuf);

        // Check against the max committed version
        if (pMaxCachedImport)
        {
            LPWSTR pwzPath = NULL;

            if ((_hr = pMaxCachedImport->FindExistMatching(pAssemblyFile, &pwzPath)) == S_OK)
            {               
                // Copy from existing cached copy to the new location
                // (Non-manifest files)
                if (SUCCEEDED(_hr = pCacheEmit->CopyFile(pwzPath, sFileName._pwz, OTHERFILES)))
                    bSkipFile = TRUE;

                SAFEDELETEARRAY(pwzPath);                
            }
        }

        if (!bSkipFile)
        {
            // Form local file path...
            // Manifest cache directory
            pCacheImport->GetManifestFileDir(&pwzBuf, &ccBuf);
            sLocalFilePath.TakeOwnership(pwzBuf, ccBuf);            

            if (patchAvailable)
            {
                if(FAILED(_hr = pManifestImport->GetTargetPatchMapping(sFileName._pwz, &pwzSource, &pwzPatchFile)))
                    goto exit;
                else if (_hr == S_OK)
                {
                    CString sLocalPatchDirFileName, sOrigFileName;
    
                    // Set up path of source file to be copied
                    sOrigFileName.Assign(sPatchManifestDirectory);
                    sOrigFileName.PathCombine(pwzSource);

                    // Set up local path of where the source file will be copied to.
                    sLocalPatchDirFileName.Assign(sLocalPatchDirectoryPath);
                    sLocalPatchDirFileName.PathCombine(pwzSource);

                    // Copy Source File into path directory
                    // _hr = pCacheEmit->CopyFile(sOrigFileName._pwz, sLocalPatchDirFileName._pwz, OTHERFILES);
                    sFileName.Assign (pwzPatchFile);

                    // Append the patch directory to local file path
                    sLocalFilePath.Append(sLocalPatchDirectoryPath);
                    CAssemblyCache::CreateDirectoryHierarchy(sLocalFilePath._pwz, sFileName._pwz);

                    SAFEDELETEARRAY(pwzSource);
                    SAFEDELETEARRAY(pwzPatchFile);

                }
            }

            // Form local file path continued from above if statement
            // Combine and ensure backslashes.
            sLocalFilePath.PathCombine(sFileName);
            sLocalFilePath.PathNormalize();
             
            // Form remote name
            sRemoteUrl.Assign(sCodebase);
            sRemoteUrl.UrlCombine(sFileName);
                
            // add the file to the job.
            pJob->AddFile(sRemoteUrl._pwz, sLocalFilePath._pwz);
        }

        SAFERELEASE(pAssemblyFile);        
    }
    
    // Submit assembly manifests into staging area
    // Note - we should also get assembly codebase and
    // use this instead or adjunctly to display name.
    // As is, there is a problem if the ref is partial.

    n = 0;
    while (pManifestImport->GetNextAssembly(n, &pDependAsm) == S_OK)
    {
        CString sAssemblyName;
        CString sLocalFilePath;
        CString sRemoteUrl;
        
        // Form local name (in staging area)....
        pDependAsm->GetAssemblyIdentity(&pIdentity);
        
        // Get the identity name
        pIdentity->GetAttribute(SXS_ASSEMBLY_IDENTITY_STD_ATTRIBUTE_NAME_NAME,
            &pwzBuf, &ccBuf);
        sAssemblyName.TakeOwnership(pwzBuf, ccBuf);
        
        // Form local cache path from identity name.
        // BUG?
        CAssemblyCache::GetCacheRootDir(sLocalFilePath, CAssemblyCache::Staging);
        sLocalFilePath.Append(sAssemblyName);
        sLocalFilePath.Append(L".manifest");
        MakeSequentialFileName(sLocalFilePath);

        // Get remote name, if any specified
        pDependAsm->Get(DEPENDENT_ASM_CODEBASE, &pwzBuf, &ccBuf);
        if (pwzBuf != NULL)
            sRemoteUrl.TakeOwnership(pwzBuf, ccBuf);
        else
        {
            // Form remote name - probing, in effect.
            sRemoteUrl.Assign(sCodebase);
            sRemoteUrl.UrlCombine(sAssemblyName);
            sRemoteUrl.Append(L"/");
            sRemoteUrl.Append(sAssemblyName);
            sRemoteUrl.Append(L".manifest");
        }
        
        pJob->AddFile(sRemoteUrl._pwz, sLocalFilePath._pwz);
        SAFERELEASE(pIdentity);
        SAFERELEASE(pDependAsm);
        n++;        
    }        

    *ppJob = pJob;    
        
    _hr = S_OK;

exit:

    SAFERELEASE(pManifestImport);
    SAFERELEASE(pCacheEmit);
    SAFERELEASE(pMaxCachedImport);
    SAFERELEASE(pPatchAssemblyId);
    SAFERELEASE(pMaxPatchImport );

    return _hr;

}


// IBackgroundCopyCallback methods

// ---------------------------------------------------------------------------
// JobTransferred
// ---------------------------------------------------------------------------
HRESULT CAssemblyDownload::JobTransferred(IBackgroundCopyJob *pJob)
{
    if (_pDlg)
        _pDlg->HandleCOMCallback(pJob, TRUE);

    _hr = DoCacheUpdate(pJob);
    return _hr;
}


// ---------------------------------------------------------------------------
// JobError
// ---------------------------------------------------------------------------
HRESULT CAssemblyDownload::JobError(IBackgroundCopyJob *pJob, IBackgroundCopyError *pError)
{
    LPWSTR pwstr = NULL;
    pError->GetErrorDescription(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), &pwstr);
    // BUGBUG - need to do CoTaskMemFree on pwstr (or use a cstring)
//    _pDlg->HandleCOMCallback(pJob, TRUE);
    return S_OK;
}

// ---------------------------------------------------------------------------
// JobModification
// ---------------------------------------------------------------------------
HRESULT CAssemblyDownload::JobModification(IBackgroundCopyJob *pJob, DWORD dwReserved)
{
    if (_pDlg)
        _pDlg->HandleCOMCallback(pJob, TRUE);
    return S_OK;
}

// Privates

// IUnknown methods

// ---------------------------------------------------------------------------
// CAssemblyDownload::QI
// ---------------------------------------------------------------------------
STDMETHODIMP
CAssemblyDownload::QueryInterface(REFIID riid, void** ppvObj)
{
    if (   IsEqualIID(riid, IID_IUnknown)
        || IsEqualIID(riid, IID_IAssemblyDownload)
       )
    {
        *ppvObj = static_cast<IAssemblyDownload*> (this);
        AddRef();
        return S_OK;
    }
    else if (IsEqualIID(riid, IID_IBackgroundCopyCallback))
    {
        *ppvObj = static_cast<IBackgroundCopyCallback*> (this);
        AddRef();
        return S_OK;
    }
    else
    {
        *ppvObj = NULL;
        return E_NOINTERFACE;
    }
}

// ---------------------------------------------------------------------------
// CAssemblyDownload::AddRef
// ---------------------------------------------------------------------------
STDMETHODIMP_(ULONG)
CAssemblyDownload::AddRef()
{
    return InterlockedIncrement ((LONG*) &_cRef);
}

// ---------------------------------------------------------------------------
// CAssemblyDownload::Release
// ---------------------------------------------------------------------------
STDMETHODIMP_(ULONG)
CAssemblyDownload::Release()
{
    ULONG lRet = InterlockedDecrement ((LONG*) &_cRef);
    if (!lRet)
        delete this;
    return lRet;
}