// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A // PARTICULAR PURPOSE. // // Copyright (c) Microsoft Corporation. All rights reserved #include "dll.h" #include "RegisterExtension.h" #include "PropertyStoreHelpers.h" #include #include #include #include #include // DOM interfaces, consider using XMLLite instead class DECLSPEC_UUID("9DBD2C50-62AD-11D0-B806-00C04FD706EC") PropertyThumbnailHandler; // MSXML is a dispatch based OM and requires BSTRs as input // we will cheat and cast C strings to BSTRs as MSXML does not // utalize the BSTR features of its inputs // check with the MSXML guys to see if this is OK __inline BSTR MAKEBSTR(PCWSTR psz) { return (BSTR)psz; } // // // // // // // // Spring 2008 // {C34F5F53-E91F-4BAA-AF47-516A8F742E63} // base64_property_store // // struct PROPERTYMAP { const PROPERTYKEY *pkey; BOOL fOverrideMeta; // normally properties in are read only and // prefered over values found in // this flag lets them be written by letting the // a value in be prefered PCWSTR pszValueNodeName; PCWSTR pszMetaNameValue; }; // all elements are in smil/head const PROPERTYMAP c_rgPROPERTYMAP[] = { { &PKEY_Title, false, L"title", NULL }, { &PKEY_Media_ContentID, false, L"guid", NULL }, { &PKEY_ApplicationName, false, L"meta", L"generator" }, { &PKEY_FileCount, false, L"meta", L"itemCount" }, { &PKEY_Media_Duration, false, L"meta", L"totalDuration" }, { &PKEY_Rating, true, L"meta", L"averageRating" }, }; // TODO: // the target items display name and file name should be inlcuded in PKEY_Search_Contents to enable searches // for those names to return the playlist file class CPlaylistPropertyHandler : public IPropertyStore, public IInitializeWithStream, public IPropertyStoreCapabilities, public IObjectProvider { public: CPlaylistPropertyHandler() : _cRef(1), _pStream(NULL), _grfMode(0), _pDomDoc(NULL), _pCache(NULL) { DllAddRef(); } // IUnknown IFACEMETHODIMP QueryInterface(REFIID riid, void ** ppv) { static const QITAB qit[] = { QITABENT(CPlaylistPropertyHandler, IPropertyStore), // required QITABENT(CPlaylistPropertyHandler, IInitializeWithStream), // required QITABENT(CPlaylistPropertyHandler, IPropertyStoreCapabilities), // optional QITABENT(CPlaylistPropertyHandler, IObjectProvider), // optional {0, 0 }, }; return QISearch(this, qit, riid, ppv); } IFACEMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&_cRef); } IFACEMETHODIMP_(ULONG) Release() { long cRef = InterlockedDecrement(&_cRef); if (cRef == 0) { delete this; } return cRef; } // IPropertyStore IFACEMETHODIMP GetCount(DWORD *pcProps); IFACEMETHODIMP GetAt(DWORD iProp, PROPERTYKEY *pkey); IFACEMETHODIMP GetValue(REFPROPERTYKEY key, PROPVARIANT *pPropVar); IFACEMETHODIMP SetValue(REFPROPERTYKEY key, REFPROPVARIANT propVar); IFACEMETHODIMP Commit(); // IInitializeWithStream IFACEMETHODIMP Initialize(IStream *pStream, DWORD grfMode); // IPropertyStoreCapabilities IFACEMETHODIMP IsPropertyWritable(REFPROPERTYKEY key) { HRESULT hr = S_OK; // by default yes, we support openmetadata for (int i = 0; i < ARRAYSIZE(c_rgPROPERTYMAP); i++) { if (IsEqualPropertyKey(*c_rgPROPERTYMAP[i].pkey, key) && !c_rgPROPERTYMAP[i].fOverrideMeta) { hr = S_FALSE; // no, not writeable break; } } return hr; } // by implementing IObjectProvider clients of this property handler can // get to this object and access a handler specific programming model. // those clients use IObjectProvider::QueryObject(, ...) // this would be useful for clients that need access to special features of // a particular file format but want to mostly use the IPropertyStore abstraction IFACEMETHODIMP QueryObject(REFGUID guidObject, REFIID riid, void **ppv) { *ppv = NULL; HRESULT hr = E_NOTIMPL; // provide access to this object directly, but in this case only // on IUnknown. provide access to handler specific features // by creating a handler specific interface that can be retrieved here if (guidObject == __uuidof(CPlaylistPropertyHandler)) { hr = QueryInterface(riid, ppv); } return hr; } private: HRESULT _LoadCacheFromDom(); HRESULT _LoadMetaPropertyFromNode(IXMLDOMNode *pNodeParent, const PROPERTYMAP &map, PROPVARIANT *ppropvar); HRESULT _LoadProperty(const PROPERTYMAP &map); HRESULT _SaveCacheToDom(); HRESULT _SaveToStream(); HRESULT _SavePropertyValue(IXMLDOMNode *pNodeParent, const PROPERTYMAP &map, REFPROPVARIANT propvar); HRESULT _SaveProperty(REFPROPVARIANT propvar, const PROPERTYMAP &map); void _ReleaseResources() { SafeRelease(&_pStream); SafeRelease(&_pCache); SafeRelease(&_pDomDoc); } ~CPlaylistPropertyHandler() { _ReleaseResources(); DllRelease(); } long _cRef; IStream *_pStream; DWORD _grfMode; IXMLDOMDocument* _pDomDoc; // DOM object representing the file IPropertyStoreCache *_pCache; // internal value cache to abstract IPropertyStore operations from the DOM back-end }; HRESULT CPlaylistPropertyHandler_CreateInstance(REFIID riid, void **ppv) { *ppv = NULL; CPlaylistPropertyHandler *pirm = new (std::nothrow) CPlaylistPropertyHandler(); HRESULT hr = pirm ? S_OK : E_OUTOFMEMORY; if (SUCCEEDED(hr)) { hr = pirm->QueryInterface(riid, ppv); pirm->Release(); } return hr; } HRESULT CPlaylistPropertyHandler::GetCount(DWORD *pcProps) { *pcProps = 0; return _pCache ? _pCache->GetCount(pcProps) : E_UNEXPECTED; } HRESULT CPlaylistPropertyHandler::GetAt(DWORD iProp, PROPERTYKEY *pkey) { *pkey = PKEY_Null; return _pCache ? _pCache->GetAt(iProp, pkey) : E_UNEXPECTED; } HRESULT CPlaylistPropertyHandler::GetValue(REFPROPERTYKEY key, PROPVARIANT *pPropVar) { PropVariantInit(pPropVar); return _pCache ? _pCache->GetValue(key, pPropVar) : E_UNEXPECTED; } // SetValue just updates the internal value cache HRESULT CPlaylistPropertyHandler::SetValue(REFPROPERTYKEY key, REFPROPVARIANT propVar) { HRESULT hr = E_UNEXPECTED; if (_pCache) { // check grfMode to ensure writes are allowed hr = STG_E_ACCESSDENIED; if (_grfMode & STGM_READWRITE) { hr = _pCache->SetValueAndState(key, &propVar, PSC_DIRTY); } } return hr; } HRESULT CPlaylistPropertyHandler::_SavePropertyValue( IXMLDOMNode *pNodeParent, const PROPERTYMAP &map, REFPROPVARIANT propvar) { BSTR bstrValue; HRESULT hr = PropVariantToBSTR(propvar, &bstrValue); if (SUCCEEDED(hr)) { // create an element and set its text to the value IXMLDOMElement *pValue; hr = _pDomDoc->createElement(MAKEBSTR(map.pszValueNodeName), &pValue); if (SUCCEEDED(hr)) { hr = pValue->put_text(bstrValue); if (SUCCEEDED(hr)) { // append the value to its parent node hr = pNodeParent->appendChild(pValue, NULL); } pValue->Release(); } SysFreeString(bstrValue); } return hr; } HRESULT CPlaylistPropertyHandler::_SaveProperty(REFPROPVARIANT propvar, const PROPERTYMAP &map) { // obtain the parent node of the value IXMLDOMNode *pNodeParent; HRESULT hr = _pDomDoc->selectSingleNode(MAKEBSTR(L"smil/head"), &pNodeParent); if (hr == S_OK) { // remove existing value nodes IXMLDOMNodeList *pNodeListValues; hr = pNodeParent->selectNodes(MAKEBSTR(map.pszValueNodeName), &pNodeListValues); if (hr == S_OK) { IXMLDOMSelection *pSelectionValues; hr = pNodeListValues->QueryInterface(&pSelectionValues); if (SUCCEEDED(hr)) { hr = pSelectionValues->removeAll(); pSelectionValues->Release(); } pNodeListValues->Release(); } if (SUCCEEDED(hr)) { // save the new values to the parent node hr = _SavePropertyValue(pNodeParent, map, propvar); } pNodeParent->Release(); } return hr; } HRESULT CPlaylistPropertyHandler::_SaveCacheToDom() { HRESULT hr = S_OK; for (UINT i = 0; SUCCEEDED(hr) && (i < ARRAYSIZE(c_rgPROPERTYMAP)); i++) { PROPVARIANT propvar; if (SUCCEEDED(_pCache->GetValue(*c_rgPROPERTYMAP[i].pkey, &propvar)) && (propvar.vt != VT_EMPTY)) { if (!c_rgPROPERTYMAP[i].pszMetaNameValue) { // only non meta values can be saved hr = _SaveProperty(propvar, c_rgPROPERTYMAP[i]); } if (!c_rgPROPERTYMAP[i].fOverrideMeta) { // clear the value in the cache so we don't save this redundantly const PROPVARIANT vEmpty = {}; _pCache->SetValue(*c_rgPROPERTYMAP[i].pkey, vEmpty); } PropVariantClear(&propvar); } } if (SUCCEEDED(hr)) { IXMLDOMNode *pPropertiesNode = NULL; IXMLDOMNode *pNode; hr = _pDomDoc->selectSingleNode(MAKEBSTR(L"smil/head/properties"), &pNode); if (S_OK == hr) { hr = pNode->QueryInterface(&pPropertiesNode); pNode->Release(); } else { hr = _pDomDoc->selectSingleNode(MAKEBSTR(L"smil/head"), &pNode); if (hr == S_OK) { IXMLDOMElement *pChildElem; hr = _pDomDoc->createElement(MAKEBSTR(L"properties"), &pChildElem); if (SUCCEEDED(hr)) { hr = pNode->appendChild(pChildElem, &pPropertiesNode); pChildElem->Release(); } pNode->Release(); } } if (SUCCEEDED(hr)) { PWSTR pszBase64; hr = SavePropertyStoreToString(_pCache, &pszBase64); if (SUCCEEDED(hr)) { hr = pPropertiesNode->put_text(MAKEBSTR(pszBase64)); CoTaskMemFree(pszBase64); } pPropertiesNode->Release(); } } return hr; } HRESULT CPlaylistPropertyHandler::_SaveToStream() { IStream *pStreamSaveTo; HRESULT hr = GetSafeSaveStream(_pStream, &pStreamSaveTo); if (SUCCEEDED(hr)) { VARIANT varStream = {}; varStream.vt = VT_UNKNOWN; varStream.punkVal = pStreamSaveTo; hr = _pDomDoc->save(varStream); if (SUCCEEDED(hr)) { hr = _pStream->Commit(STGC_DEFAULT); } pStreamSaveTo->Release(); } return hr; } // Commit writes the internal value cache back out to the stream passed to Initialize HRESULT CPlaylistPropertyHandler::Commit() { HRESULT hr = E_UNEXPECTED; if (_pCache) { hr = STG_E_ACCESSDENIED; if (_grfMode & STGM_READWRITE) // must be opened for writing { hr = _SaveCacheToDom(); if (SUCCEEDED(hr)) { hr = _SaveToStream(); } } } return hr; } HRESULT LoadDomDocFromStream(IStream *pStream, IXMLDOMDocument **ppDomDoc) { *ppDomDoc = NULL; IXMLDOMDocument *pDomDoc; HRESULT hr = CoCreateInstance(CLSID_DOMDocument60, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pDomDoc)); if (SUCCEEDED(hr)) { pDomDoc->put_validateOnParse(VARIANT_FALSE); pDomDoc->put_async(VARIANT_FALSE); pDomDoc->put_resolveExternals(VARIANT_FALSE); pDomDoc->put_preserveWhiteSpace(VARIANT_TRUE); // load the DOM object's contents from the stream VARIANT varStream = {}; varStream.vt = VT_UNKNOWN; varStream.punkVal = pStream; VARIANT_BOOL vfSuccess = VARIANT_FALSE; hr = pDomDoc->load(varStream, &vfSuccess); if ((hr == S_OK) && (vfSuccess == VARIANT_TRUE)) { hr = pDomDoc->QueryInterface(ppDomDoc); } else { hr = E_FAIL; } pDomDoc->Release(); } return hr; } HRESULT CPlaylistPropertyHandler::_LoadMetaPropertyFromNode( IXMLDOMNode *pNodeParent, const PROPERTYMAP &map, PROPVARIANT *ppropvar) { // the elements need special attention IXMLDOMNodeList *pElementsToSearch; HRESULT hr = pNodeParent->selectNodes(MAKEBSTR(map.pszValueNodeName), &pElementsToSearch); if (hr == S_OK) { long cElements; hr = pElementsToSearch->get_length(&cElements); for (long iElements = 0; (ppropvar->vt == VT_EMPTY) && SUCCEEDED(hr) && (iElements < cElements); iElements++) { IXMLDOMNode *pElement; hr = pElementsToSearch->get_item(iElements, &pElement); if (S_OK == hr) { IXMLDOMNamedNodeMap *pAttributes; hr = pElement->get_attributes(&pAttributes); if (S_OK == hr) { IXMLDOMNode *pNameAttribute; hr = pAttributes->getNamedItem(MAKEBSTR(L"name"), &pNameAttribute); if (S_OK == hr) { BSTR bstrNameValue; hr = pNameAttribute->get_text(&bstrNameValue); if (S_OK == hr) { if (StrCmpIC(bstrNameValue, map.pszMetaNameValue) == 0) { IXMLDOMNode *pContentAttribute; hr = pAttributes->getNamedItem(MAKEBSTR(L"content"), &pContentAttribute); if (S_OK == hr) { hr = pContentAttribute->get_text(&ppropvar->bstrVal); if (S_OK == hr) { ppropvar->vt = VT_BSTR; hr = PSCoerceToCanonicalValue(*map.pkey, ppropvar); if (SUCCEEDED(hr)) { if (IsEqualPropertyKey(*map.pkey, PKEY_Media_Duration) && (ppropvar->vt == VT_UI8)) { ppropvar->uhVal.QuadPart *= 1000; } } } pContentAttribute->Release(); } } SysFreeString(bstrNameValue); } pNameAttribute->Release(); } pAttributes->Release(); } pElement->Release(); } } pElementsToSearch->Release(); } return hr; } HRESULT CPlaylistPropertyHandler::_LoadProperty(const PROPERTYMAP &map) { // select the property's parent node and load the property's value(s) IXMLDOMNode *pNodeParent = NULL; HRESULT hr = _pDomDoc->selectSingleNode(MAKEBSTR(L"smil/head"), &pNodeParent); if (hr == S_OK) { PROPVARIANT propvar = {}; if (map.pszMetaNameValue) { // enable some properties (System.Rating) to be writeable // even though it is found in a element if (map.fOverrideMeta && TestForPropertyKey(_pCache, *map.pkey)) { hr = S_FALSE; } else { hr = _LoadMetaPropertyFromNode(pNodeParent, map, &propvar); } } else { IXMLDOMNode *pNodeValue; hr = pNodeParent->selectSingleNode(MAKEBSTR(map.pszValueNodeName), &pNodeValue); if (hr == S_OK) { hr = pNodeValue->get_text(&propvar.bstrVal); if (S_OK == hr) { propvar.vt = VT_BSTR; // coerce the value(s) to the appropriate type for the property key hr = PSCoerceToCanonicalValue(*map.pkey, &propvar); } pNodeValue->Release(); } } if (hr == S_OK) { hr = _pCache->SetValueAndState(*map.pkey, &propvar, PSC_NORMAL); PropVariantClear(&propvar); } pNodeParent->Release(); } return hr; } HRESULT CPlaylistPropertyHandler::_LoadCacheFromDom() { HRESULT hr = PSCreateMemoryPropertyStore(IID_PPV_ARGS(&_pCache)); if (SUCCEEDED(hr)) { // first load the open open metadata properties (if there are any) IXMLDOMNode *pNode; hr = _pDomDoc->selectSingleNode(MAKEBSTR(L"smil/head/properties"), &pNode); if (S_OK == hr) { BSTR bstrProperties; hr = pNode->get_text(&bstrProperties); if (S_OK == hr) { IStream *pstm; hr = GetStreamFromBase64String(bstrProperties, &pstm); if (SUCCEEDED(hr)) { IPersistStream *pps; hr = _pCache->QueryInterface(&pps); if (SUCCEEDED(hr)) { hr = pps->Load(pstm); pps->Release(); } pstm->Release(); } SysFreeString(bstrProperties); } pNode->Release(); } // load the properties from the smil/head WPL schema // these are loaded last to override any values loaded above for (UINT i = 0; SUCCEEDED(hr) && (i < ARRAYSIZE(c_rgPROPERTYMAP)); ++i) { hr = _LoadProperty(c_rgPROPERTYMAP[i]); } } return hr; } HRESULT CPlaylistPropertyHandler::Initialize(IStream *pStream, DWORD grfMode) { HRESULT hr = E_UNEXPECTED; if (!_pStream) { hr = LoadDomDocFromStream(pStream, &_pDomDoc); if (SUCCEEDED(hr)) { hr = _LoadCacheFromDom(); if (SUCCEEDED(hr)) { // save a reference to the stream as well as the grfMode hr = pStream->QueryInterface(&_pStream); if (SUCCEEDED(hr)) { _grfMode = grfMode; } } if (FAILED(hr)) { _ReleaseResources(); } } } return hr; } HRESULT RegisterPropLists(CRegisterExtension &re, PCWSTR pszFileAssoc) { const WCHAR c_szPlaylistTileInfo[] = L"prop:System.ItemType;*System.FileCount;*System.Media.Diration;System.DateModified"; const WCHAR c_szPlaylistPreviewDetails[] = L"prop:*System.Media.Duration;System.Rating;*System.DateModified;System.Keywords;System.ParentalRating;*System.OfflineAvailability;*System.OfflineStatus;*System.DateCreated;*System.SharedWith"; const WCHAR c_szPlaylistInfoTip[] = L"prop:System.ItemType;*System.FileCount;*System.Media.Diration;System.DateModified;System.Size"; const WCHAR c_szPlaylistFullDetails[] = L"prop:System.PropGroup.Description;System.Title;System.Media.SubTitle;System.Rating;System.Keywords;System.Comment;System.PropGroup.Media;System.Music.Artist;System.Music.AlbumArtist;System.Music.AlbumTitle;System.Media.Year;System.Music.TrackNumber;System.Music.Genre;System.Media.Duration;System.PropGroup.Audio;System.Audio.EncodingBitrate;System.PropGroup.Origin;System.Media.Producer;System.Media.Publisher;System.Media.ContentDistributor;System.Media.DateEncoded;System.Media.EncodedBy;System.Media.AuthorUrl;System.Media.PromotionUrl;System.Copyright;System.PropGroup.Content;System.ParentalRating;System.ParentalRatingReason;System.Music.Composer;System.Music.Conductor;System.Music.ContentGroupDescription;System.Music.Period;System.Music.Mood;System.Music.PartOfSet;System.Music.InitialKey;System.Music.BeatsPerMinute;System.DRM.IsProtected;System.PropGroup.FileSystem;System.ItemNameDisplay;System.ItemType;System.ItemFolderPathDisplay;System.DateCreated;System.DateModified;System.Size;System.FileAttributes;System.OfflineAvailability;System.OfflineStatus;System.SharedWith;System.FileOwner;System.ComputerName"; const WCHAR c_szPlaylistExtendedTileInfo[] = L"prop:System.ItemType;System.Size;System.Music.Artist;System.Media.Duration;System.OfflineAvailability"; HRESULT hr = re.RegisterProgIDValue(pszFileAssoc, L"TileInfo", c_szPlaylistTileInfo); if (SUCCEEDED(hr)) { hr = re.RegisterProgIDValue(pszFileAssoc, L"PreviewDetails", c_szPlaylistPreviewDetails); if (SUCCEEDED(hr)) { hr = re.RegisterProgIDValue(pszFileAssoc, L"InfoTip", c_szPlaylistInfoTip); if (SUCCEEDED(hr)) { hr = re.RegisterProgIDValue(pszFileAssoc, L"FullDetails", c_szPlaylistFullDetails); if (SUCCEEDED(hr)) { hr = re.RegisterProgIDValue(pszFileAssoc, L"ExtendedTileInfo", c_szPlaylistExtendedTileInfo); } } } } return hr; } const WCHAR c_szWplFileExtension[] = L".wpl"; const WCHAR c_szWplProgID[] = L"Windows.WPL"; const WCHAR c_szZplFileExtension[] = L".zpl"; const WCHAR c_szZplProgID[] = L"Windows.ZPL"; HRESULT RegisterHandler() { // register the property handler COM object, and set the options it uses const WCHAR c_szPropertyHandlerDescription[] = L"Playlist (.wpl, .zpl) Property Handler"; CRegisterExtension re(__uuidof(CPlaylistPropertyHandler), HKEY_LOCAL_MACHINE); HRESULT hr = re.RegisterInProcServer(c_szPropertyHandlerDescription, L"Both"); if (SUCCEEDED(hr)) { hr = re.RegisterInProcServerAttribute(L"ManualSafeSave", TRUE); } // Property Handler registrations use a different mechanism than the rest of the filetype association system, and do not use ProgIDs if (SUCCEEDED(hr)) { hr = re.RegisterPropertyHandler(c_szWplFileExtension); if (SUCCEEDED(hr)) { hr = re.RegisterPropertyHandler(c_szZplFileExtension); } } // Associate our ProgIDs with the file extensions, and write the proplists to the ProgID to minimize conflicts with other applications and facilitate easy unregistration if (SUCCEEDED(hr)) { hr = re.RegisterExtensionWithProgID(c_szWplFileExtension, c_szWplProgID); if (SUCCEEDED(hr)) { hr = RegisterPropLists(re, c_szWplProgID); } } if (SUCCEEDED(hr)) { hr = re.RegisterExtensionWithProgID(c_szZplFileExtension, c_szZplProgID); if (SUCCEEDED(hr)) { hr = RegisterPropLists(re, c_szZplProgID); } } // also register the property-driven thumbnail handler on the ProgIDs if (SUCCEEDED(hr)) { re.SetHandlerCLSID(__uuidof(PropertyThumbnailHandler)); hr = re.RegisterThumbnailHandler(c_szWplProgID); if (SUCCEEDED(hr)) { hr = re.RegisterThumbnailHandler(c_szZplProgID); } } return hr; } HRESULT UnregisterHandler() { // Unregister the property handler COM object. CRegisterExtension re(__uuidof(CPlaylistPropertyHandler), HKEY_LOCAL_MACHINE); HRESULT hr = re.UnRegisterObject(); if (SUCCEEDED(hr)) { // Unregister the property handler the file extensions. hr = re.UnRegisterPropertyHandler(c_szWplFileExtension); if (SUCCEEDED(hr)) { hr = re.UnRegisterPropertyHandler(c_szZplFileExtension); } } if (SUCCEEDED(hr)) { // Remove the whole ProgIDs since we own all of those settings. // Don't try to remove the file extension association since some other application may have overridden it with their own ProgID in the meantime. // Leaving the association to a non-existing ProgID is handled gracefully by the Shell. // NOTE: If the file extension is unambiguously owned by this application, the association to the ProgID could be safely removed as well, // along with any other association data stored on the file extension itself. hr = re.UnRegisterProgID(c_szWplProgID, c_szWplFileExtension); if (SUCCEEDED(hr)) { hr = re.UnRegisterProgID(c_szZplProgID, c_szZplFileExtension); } } return hr; }