994 lines
34 KiB
C++
994 lines
34 KiB
C++
#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;
|
|
}
|
|
|