553 lines
14 KiB
C++
553 lines
14 KiB
C++
|
#include "private.h"
|
||
|
#include "offl_cpp.h"
|
||
|
#include "subsmgrp.h"
|
||
|
|
||
|
HRESULT _GetURLData(IDataObject *, int, TCHAR *, TCHAR *);
|
||
|
HRESULT _ConvertHDROPData(IDataObject *, BOOL);
|
||
|
HRESULT ScheduleDefault(LPCTSTR, LPCTSTR);
|
||
|
|
||
|
#define CITBDTYPE_HDROP 1
|
||
|
#define CITBDTYPE_URL 2
|
||
|
#define CITBDTYPE_TEXT 3
|
||
|
|
||
|
//
|
||
|
// Constructor
|
||
|
//
|
||
|
|
||
|
COfflineDropTarget::COfflineDropTarget(HWND hwndParent)
|
||
|
{
|
||
|
m_cRefs = 1;
|
||
|
m_hwndParent = hwndParent;
|
||
|
m_pDataObj = NULL;
|
||
|
m_grfKeyStateLast = 0;
|
||
|
m_fHasHDROP = FALSE;
|
||
|
m_fHasSHELLURL = FALSE;
|
||
|
m_fHasTEXT = FALSE;
|
||
|
m_dwEffectLastReturned = 0;
|
||
|
|
||
|
DllAddRef();
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Destructor
|
||
|
//
|
||
|
|
||
|
COfflineDropTarget::~COfflineDropTarget()
|
||
|
{
|
||
|
DllRelease();
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// QueryInterface
|
||
|
//
|
||
|
|
||
|
STDMETHODIMP COfflineDropTarget::QueryInterface(REFIID riid, LPVOID *ppv)
|
||
|
{
|
||
|
HRESULT hr = E_NOINTERFACE;
|
||
|
|
||
|
*ppv = NULL;
|
||
|
|
||
|
// Any interface on this object is the object pointer
|
||
|
|
||
|
if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IDropTarget))
|
||
|
{
|
||
|
*ppv = (LPDROPTARGET)this;
|
||
|
|
||
|
AddRef();
|
||
|
|
||
|
hr = NOERROR;
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// AddRef
|
||
|
//
|
||
|
|
||
|
STDMETHODIMP_(ULONG) COfflineDropTarget::AddRef()
|
||
|
{
|
||
|
return ++m_cRefs;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Release
|
||
|
//
|
||
|
|
||
|
STDMETHODIMP_(ULONG) COfflineDropTarget::Release()
|
||
|
{
|
||
|
if (0L != --m_cRefs)
|
||
|
{
|
||
|
return m_cRefs;
|
||
|
}
|
||
|
|
||
|
delete this;
|
||
|
|
||
|
return 0L;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// DragEnter
|
||
|
//
|
||
|
|
||
|
STDMETHODIMP COfflineDropTarget::DragEnter(LPDATAOBJECT pDataObj,
|
||
|
DWORD grfKeyState,
|
||
|
POINTL pt,
|
||
|
LPDWORD pdwEffect)
|
||
|
{
|
||
|
// Release any old data object we might have
|
||
|
|
||
|
// TraceMsg(TF_SUBSFOLDER, TEXT("odt - DragEnter"));
|
||
|
if (m_pDataObj)
|
||
|
{
|
||
|
m_pDataObj->Release();
|
||
|
}
|
||
|
|
||
|
m_grfKeyStateLast = grfKeyState;
|
||
|
m_pDataObj = pDataObj;
|
||
|
|
||
|
//
|
||
|
// See if we will be able to get CF_HDROP from this guy
|
||
|
//
|
||
|
|
||
|
if (pDataObj)
|
||
|
{
|
||
|
pDataObj->AddRef();
|
||
|
TCHAR url[INTERNET_MAX_URL_LENGTH], name[MAX_NAME_QUICKLINK];
|
||
|
FORMATETC fe = {(CLIPFORMAT) RegisterClipboardFormat(CFSTR_SHELLURL),
|
||
|
NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
||
|
|
||
|
m_fHasSHELLURL = m_fHasHDROP = m_fHasTEXT = FALSE;
|
||
|
if (NOERROR == pDataObj->QueryGetData(&fe))
|
||
|
{
|
||
|
TraceMsg(TF_SUBSFOLDER, "odt - DragEnter : SHELLURL!");
|
||
|
m_fHasSHELLURL =
|
||
|
(NOERROR == _GetURLData(pDataObj,CITBDTYPE_URL,url,name));
|
||
|
}
|
||
|
if (fe.cfFormat = CF_HDROP, NOERROR == pDataObj->QueryGetData(&fe))
|
||
|
{
|
||
|
TraceMsg(TF_SUBSFOLDER, "odt - DragEnter : HDROP!");
|
||
|
m_fHasHDROP = (NOERROR ==
|
||
|
_ConvertHDROPData(pDataObj, FALSE));
|
||
|
}
|
||
|
if (fe.cfFormat = CF_TEXT, NOERROR == pDataObj->QueryGetData(&fe))
|
||
|
{
|
||
|
TraceMsg(TF_SUBSFOLDER, "odt - DragEnter : TEXT!");
|
||
|
m_fHasTEXT =
|
||
|
(NOERROR == _GetURLData(pDataObj,CITBDTYPE_TEXT,url,name));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Save the drop effect
|
||
|
|
||
|
if (pdwEffect)
|
||
|
{
|
||
|
*pdwEffect = m_dwEffectLastReturned = GetDropEffect(pdwEffect);
|
||
|
}
|
||
|
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// GetDropEffect
|
||
|
//
|
||
|
|
||
|
DWORD COfflineDropTarget::GetDropEffect(LPDWORD pdwEffect)
|
||
|
{
|
||
|
ASSERT(pdwEffect);
|
||
|
|
||
|
if (m_fHasSHELLURL || m_fHasTEXT)
|
||
|
{
|
||
|
return *pdwEffect & (DROPEFFECT_COPY | DROPEFFECT_LINK);
|
||
|
}
|
||
|
else if (m_fHasHDROP) {
|
||
|
return *pdwEffect & (DROPEFFECT_COPY );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return DROPEFFECT_NONE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// DragOver
|
||
|
//
|
||
|
|
||
|
STDMETHODIMP COfflineDropTarget::DragOver(DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
|
||
|
{
|
||
|
// TraceMsg(TF_SUBSFOLDER, TEXT("odt - DragOver"));
|
||
|
if (m_grfKeyStateLast == grfKeyState)
|
||
|
{
|
||
|
// Return the effect we saved at dragenter time
|
||
|
|
||
|
if (*pdwEffect)
|
||
|
{
|
||
|
*pdwEffect = m_dwEffectLastReturned;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (*pdwEffect)
|
||
|
{
|
||
|
*pdwEffect = m_dwEffectLastReturned = GetDropEffect(pdwEffect);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
m_grfKeyStateLast = grfKeyState;
|
||
|
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// DragLeave
|
||
|
//
|
||
|
|
||
|
STDMETHODIMP COfflineDropTarget::DragLeave()
|
||
|
{
|
||
|
// TraceMsg(TF_SUBSFOLDER, TEXT("odt - DragLeave"));
|
||
|
if (m_pDataObj)
|
||
|
{
|
||
|
m_pDataObj->Release();
|
||
|
m_pDataObj = NULL;
|
||
|
}
|
||
|
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Drop
|
||
|
//
|
||
|
STDMETHODIMP COfflineDropTarget::Drop(LPDATAOBJECT pDataObj,
|
||
|
DWORD grfKeyState,
|
||
|
POINTL pt,
|
||
|
LPDWORD pdwEffect)
|
||
|
{
|
||
|
// UINT idCmd; // Choice from drop popup menu
|
||
|
HRESULT hr = S_OK;
|
||
|
|
||
|
//
|
||
|
// Take the new data object, since OLE can give us a different one than
|
||
|
// it did in DragEnter
|
||
|
//
|
||
|
|
||
|
// TraceMsg(TF_SUBSFOLDER, TEXT("odt - Drop"));
|
||
|
if (m_pDataObj)
|
||
|
{
|
||
|
m_pDataObj->Release();
|
||
|
}
|
||
|
m_pDataObj = pDataObj;
|
||
|
if (pDataObj)
|
||
|
{
|
||
|
pDataObj->AddRef();
|
||
|
}
|
||
|
|
||
|
// If the dataobject doesn't have an HDROP, its not much good to us
|
||
|
|
||
|
*pdwEffect &= DROPEFFECT_COPY|DROPEFFECT_LINK;
|
||
|
if (!(*pdwEffect)) {
|
||
|
DragLeave();
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
hr = E_NOINTERFACE;
|
||
|
if (m_fHasHDROP)
|
||
|
hr = _ConvertHDROPData(pDataObj, TRUE);
|
||
|
else {
|
||
|
TCHAR url[INTERNET_MAX_URL_LENGTH], name[MAX_NAME_QUICKLINK];
|
||
|
if (m_fHasSHELLURL)
|
||
|
hr = _GetURLData(pDataObj, CITBDTYPE_URL, url, name);
|
||
|
if (FAILED(hr) && m_fHasTEXT)
|
||
|
hr = _GetURLData(pDataObj, CITBDTYPE_TEXT, url, name);
|
||
|
if (SUCCEEDED(hr)) {
|
||
|
TraceMsg(TF_SUBSFOLDER, "URL: %s, Name: %s", url, name);
|
||
|
hr = ScheduleDefault(url, name);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (FAILED(hr))
|
||
|
{
|
||
|
TraceMsg(TF_SUBSFOLDER, "Couldn't DROP");
|
||
|
}
|
||
|
|
||
|
DragLeave();
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
HRESULT _CLSIDFromExtension(
|
||
|
LPCTSTR pszExt,
|
||
|
CLSID *pclsid)
|
||
|
{
|
||
|
TCHAR szProgID[80];
|
||
|
long cb = SIZEOF(szProgID);
|
||
|
if (RegQueryValue(HKEY_CLASSES_ROOT, pszExt, szProgID, &cb) == ERROR_SUCCESS)
|
||
|
{
|
||
|
TCHAR szCLSID[80];
|
||
|
|
||
|
StrCatN(szProgID, TEXT("\\CLSID"), ARRAYSIZE(szProgID));
|
||
|
cb = SIZEOF(szCLSID);
|
||
|
|
||
|
if (RegQueryValue(HKEY_CLASSES_ROOT, szProgID, szCLSID, &cb) == ERROR_SUCCESS)
|
||
|
{
|
||
|
// FEATURE (scotth): call shell32's SHCLSIDFromString once it
|
||
|
// exports A/W versions. This would clean this
|
||
|
// up.
|
||
|
#ifdef UNICODE
|
||
|
return CLSIDFromString(szCLSID, pclsid);
|
||
|
#else
|
||
|
WCHAR wszCLSID[80];
|
||
|
MultiByteToWideChar(CP_ACP, 0, szCLSID, -1, wszCLSID, ARRAYSIZE(wszCLSID));
|
||
|
return CLSIDFromString(wszCLSID, pclsid);
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
|
||
|
|
||
|
// get the target of a shortcut. this uses IShellLink which
|
||
|
// Internet Shortcuts (.URL) and Shell Shortcuts (.LNK) support so
|
||
|
// it should work generally
|
||
|
//
|
||
|
HRESULT _GetURLTarget(LPCTSTR pszPath, LPTSTR pszTarget, UINT cch)
|
||
|
{
|
||
|
IShellLink *psl;
|
||
|
HRESULT hr = E_FAIL;
|
||
|
CLSID clsid;
|
||
|
|
||
|
if (FAILED(_CLSIDFromExtension(PathFindExtension(pszPath), &clsid)))
|
||
|
clsid = CLSID_ShellLink; // assume it's a shell link
|
||
|
|
||
|
hr = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID *)&psl);
|
||
|
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
IPersistFile *ppf;
|
||
|
|
||
|
hr = psl->QueryInterface(IID_IPersistFile, (void **)&ppf);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
#ifdef UNICODE
|
||
|
hr = ppf->Load(pszPath, 0);
|
||
|
#else
|
||
|
WCHAR wszPath[MAX_PATH];
|
||
|
MultiByteToWideChar(CP_ACP, 0, pszPath, -1, wszPath, ARRAYSIZE(wszPath));
|
||
|
|
||
|
hr = ppf->Load (wszPath, 0);
|
||
|
#endif
|
||
|
ppf->Release();
|
||
|
}
|
||
|
if (SUCCEEDED(hr)) {
|
||
|
IUniformResourceLocator * purl;
|
||
|
|
||
|
hr = psl->QueryInterface(IID_IUniformResourceLocator,(void**)&purl);
|
||
|
if (SUCCEEDED(hr))
|
||
|
purl->Release();
|
||
|
}
|
||
|
if (SUCCEEDED(hr))
|
||
|
hr = psl->GetPath(pszTarget, cch, NULL, SLGP_UNCPRIORITY);
|
||
|
psl->Release();
|
||
|
}
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
HRESULT _ConvertHDROPData(IDataObject *pdtobj, BOOL bSubscribe)
|
||
|
{
|
||
|
HRESULT hRes = NOERROR;
|
||
|
STGMEDIUM stgmedium;
|
||
|
FORMATETC formatetc;
|
||
|
TCHAR url[INTERNET_MAX_URL_LENGTH];
|
||
|
TCHAR name[MAX_NAME_QUICKLINK];
|
||
|
|
||
|
name[0] = 0;
|
||
|
url[0] = 0;
|
||
|
|
||
|
formatetc.cfFormat = CF_HDROP;
|
||
|
formatetc.ptd = NULL;
|
||
|
formatetc.dwAspect = DVASPECT_CONTENT;
|
||
|
formatetc.lindex = -1;
|
||
|
formatetc.tymed = TYMED_HGLOBAL;
|
||
|
|
||
|
// Get the parse string
|
||
|
hRes = pdtobj->GetData(&formatetc, &stgmedium);
|
||
|
if (SUCCEEDED(hRes))
|
||
|
{
|
||
|
LPTSTR pszURLData = (LPTSTR)GlobalLock(stgmedium.hGlobal);
|
||
|
if (pszURLData) {
|
||
|
TCHAR szPath[MAX_PATH];
|
||
|
SHFILEINFO sfi;
|
||
|
int cFiles, i;
|
||
|
HDROP hDrop = (HDROP)stgmedium.hGlobal;
|
||
|
|
||
|
hRes = S_FALSE;
|
||
|
cFiles = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);
|
||
|
for (i = 0; i < cFiles; i ++) {
|
||
|
DragQueryFile(hDrop, i, szPath, ARRAYSIZE(szPath));
|
||
|
|
||
|
// defaults...
|
||
|
StrCpyN(name, szPath, MAX_NAME_QUICKLINK);
|
||
|
|
||
|
if (SHGetFileInfo(szPath, 0, &sfi, sizeof(sfi), SHGFI_DISPLAYNAME))
|
||
|
StrCpyN(name, sfi.szDisplayName, MAX_NAME_QUICKLINK);
|
||
|
|
||
|
if (SHGetFileInfo(szPath, 0, &sfi, sizeof(sfi),SHGFI_ATTRIBUTES)
|
||
|
&& (sfi.dwAttributes & SFGAO_LINK))
|
||
|
{
|
||
|
if (SUCCEEDED(_GetURLTarget(szPath, url, INTERNET_MAX_URL_LENGTH)))
|
||
|
{
|
||
|
TraceMsg(TF_SUBSFOLDER, "URL: %s, Name: %s", url, name);
|
||
|
// If we just want to see whether there is some urls
|
||
|
// here, we can break now.
|
||
|
if (!bSubscribe)
|
||
|
{
|
||
|
if ((IsHTTPPrefixed(url)) &&
|
||
|
(!SHRestricted2(REST_NoAddingSubscriptions, url, 0)))
|
||
|
{
|
||
|
hRes = S_OK;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
hRes = ScheduleDefault(url, name);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
GlobalUnlock(stgmedium.hGlobal);
|
||
|
if (bSubscribe)
|
||
|
hRes = S_OK;
|
||
|
} else
|
||
|
hRes = S_FALSE;
|
||
|
|
||
|
ReleaseStgMedium(&stgmedium);
|
||
|
}
|
||
|
return hRes;
|
||
|
}
|
||
|
|
||
|
// Takes a variety of inputs and returns a string for drop targets.
|
||
|
// szUrl: the URL
|
||
|
// szName: the name (for quicklinks and the confo dialog boxes)
|
||
|
// returns: NOERROR if succeeded
|
||
|
//
|
||
|
HRESULT _GetURLData(IDataObject *pdtobj, int iDropType, TCHAR *pszUrl, TCHAR *pszName)
|
||
|
{
|
||
|
HRESULT hRes = NOERROR;
|
||
|
STGMEDIUM stgmedium;
|
||
|
FORMATETC formatetc;
|
||
|
|
||
|
*pszName = 0;
|
||
|
*pszUrl = 0;
|
||
|
|
||
|
switch (iDropType)
|
||
|
{
|
||
|
case CITBDTYPE_URL:
|
||
|
formatetc.cfFormat = (CLIPFORMAT) RegisterClipboardFormat(CFSTR_SHELLURL);
|
||
|
break;
|
||
|
case CITBDTYPE_TEXT:
|
||
|
formatetc.cfFormat = CF_TEXT;
|
||
|
break;
|
||
|
default:
|
||
|
return E_UNEXPECTED;
|
||
|
}
|
||
|
formatetc.ptd = NULL;
|
||
|
formatetc.dwAspect = DVASPECT_CONTENT;
|
||
|
formatetc.lindex = -1;
|
||
|
formatetc.tymed = TYMED_HGLOBAL;
|
||
|
|
||
|
// Get the parse string
|
||
|
hRes = pdtobj->GetData(&formatetc, &stgmedium);
|
||
|
if (SUCCEEDED(hRes))
|
||
|
{
|
||
|
LPTSTR pszURLData = (LPTSTR)GlobalLock(stgmedium.hGlobal);
|
||
|
if (pszURLData)
|
||
|
{
|
||
|
if (iDropType == CITBDTYPE_URL)
|
||
|
{
|
||
|
STGMEDIUM stgmediumFGD;
|
||
|
|
||
|
// defaults
|
||
|
StrCpyN(pszUrl, pszURLData, INTERNET_MAX_URL_LENGTH);
|
||
|
StrCpyN(pszName, pszURLData, MAX_NAME_QUICKLINK);
|
||
|
|
||
|
formatetc.cfFormat = (CLIPFORMAT) RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR);
|
||
|
if (SUCCEEDED(pdtobj->GetData(&formatetc, &stgmediumFGD)))
|
||
|
{
|
||
|
FILEGROUPDESCRIPTOR *pfgd = (FILEGROUPDESCRIPTOR *)GlobalLock(stgmediumFGD.hGlobal);
|
||
|
if (pfgd)
|
||
|
{
|
||
|
TCHAR szPath[MAX_PATH];
|
||
|
StrCpyN(szPath, pfgd->fgd[0].cFileName, ARRAYSIZE(szPath));
|
||
|
PathRemoveExtension(szPath);
|
||
|
StrCpyN(pszName, szPath, MAX_NAME_QUICKLINK);
|
||
|
GlobalUnlock(stgmediumFGD.hGlobal);
|
||
|
}
|
||
|
ReleaseStgMedium(&stgmediumFGD);
|
||
|
}
|
||
|
}
|
||
|
else if (iDropType == CITBDTYPE_TEXT)
|
||
|
{
|
||
|
if (PathIsURL(pszURLData)) {
|
||
|
StrCpyN(pszUrl, pszURLData, INTERNET_MAX_URL_LENGTH);
|
||
|
StrCpyN(pszName, pszURLData, MAX_NAME_QUICKLINK);
|
||
|
} else
|
||
|
hRes = E_FAIL;
|
||
|
}
|
||
|
GlobalUnlock(stgmedium.hGlobal);
|
||
|
}
|
||
|
ReleaseStgMedium(&stgmedium);
|
||
|
}
|
||
|
|
||
|
if (SUCCEEDED(hRes))
|
||
|
{
|
||
|
if (!IsHTTPPrefixed(pszUrl) || SHRestricted2(REST_NoAddingSubscriptions, pszUrl, 0))
|
||
|
{
|
||
|
hRes = E_FAIL;
|
||
|
}
|
||
|
}
|
||
|
return hRes;
|
||
|
}
|
||
|
|
||
|
HRESULT ScheduleDefault(LPCTSTR url, LPCTSTR name)
|
||
|
{
|
||
|
if (!IsHTTPPrefixed(url))
|
||
|
return E_INVALIDARG;
|
||
|
|
||
|
ISubscriptionMgr * pSub= NULL;
|
||
|
HRESULT hr = CoInitialize(NULL);
|
||
|
RETURN_ON_FAILURE(hr);
|
||
|
|
||
|
hr = CoCreateInstance(CLSID_SubscriptionMgr, NULL, CLSCTX_INPROC_SERVER,
|
||
|
IID_ISubscriptionMgr, (void **)&pSub);
|
||
|
CoUninitialize();
|
||
|
RETURN_ON_FAILURE(hr);
|
||
|
ASSERT(pSub);
|
||
|
|
||
|
BSTR bstrURL = NULL, bstrName = NULL;
|
||
|
hr = CreateBSTRFromTSTR(&bstrURL, url);
|
||
|
if (S_OK == hr)
|
||
|
hr = CreateBSTRFromTSTR(&bstrName, name);
|
||
|
|
||
|
// We need a perfectly valid structure.
|
||
|
SUBSCRIPTIONINFO subInfo;
|
||
|
ZeroMemory((void *)&subInfo, sizeof (SUBSCRIPTIONINFO));
|
||
|
subInfo.cbSize = sizeof(SUBSCRIPTIONINFO);
|
||
|
|
||
|
if (S_OK == hr)
|
||
|
hr = pSub->CreateSubscription(NULL, bstrURL, bstrName,
|
||
|
CREATESUBS_NOUI, SUBSTYPE_URL, &subInfo);
|
||
|
|
||
|
SAFERELEASE(pSub);
|
||
|
SAFEFREEBSTR(bstrURL);
|
||
|
SAFEFREEBSTR(bstrName);
|
||
|
|
||
|
if (FAILED(hr)) {
|
||
|
TraceMsg(TF_ALWAYS, "Failed to add default object.");
|
||
|
TraceMsg(TF_ALWAYS, " hr = 0x%x\n", hr);
|
||
|
return (FAILED(hr))?hr:E_FAIL;
|
||
|
} else if (hr == S_FALSE) {
|
||
|
TraceMsg(TF_SUBSFOLDER, "%s(%s) is already there.", url, name);
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
return S_OK;
|
||
|
}
|
||
|
|