/*----------------------------------------------------------------------------- * * File: wiaproto.cpp * Author: Samuel Clement (samclem) * Date: Fri Aug 27 15:16:44 1999 * * Copyright (c) 1999 Microsoft Corporation * * Description: * This contains the implementation of the "wia" internet protocol. This * is a pluggable protocol that handles downloading thumbnails from a wia * device. * * History: * 27 Aug 1999: Created. *----------------------------------------------------------------------------*/ #include "stdafx.h" // declare some debugging tags DeclareTag( tagWiaProto, "!WiaProto", "Wia Protocol debug information" ); const WCHAR* k_wszProtocolName = L"wia"; const WCHAR* k_wszColonSlash = L":///"; const WCHAR* k_wszSeperator = L"?"; const WCHAR* k_wszThumb = L"thumb"; const WCHAR* k_wszExtension = L".bmp"; const int k_cchProtocolName = 3; const int z_cchThumb = 5; const WCHAR k_wchSeperator = L'?'; const WCHAR k_wchColon = L':'; const WCHAR k_wchFrontSlash = L'/'; const WCHAR k_wchPeriod = L'.'; const WCHAR k_wchEOS = L'\0'; enum { k_dwTransferPending = 0, k_dwTransferComplete = 1, }; /*----------------------------------------------------------------------------- * CWiaProtocol * * Create a new CWiaProtocol. This simply initializes all the members to * a known state so that we can then test against them *--(samclem)-----------------------------------------------------------------*/ CWiaProtocol::CWiaProtocol() : m_pFileItem( NULL ), m_ulOffset( 0 ) { TRACK_OBJECT( "CWiaProtocol" ); m_pd.dwState = k_dwTransferPending; } /*----------------------------------------------------------------------------- * CWiaProtocol::FinalRelease * * Called when we are finally released to cleanup any resources that we * want to cleanup. *--(samclem)-----------------------------------------------------------------*/ STDMETHODIMP_(void) CWiaProtocol::FinalRelease() { if ( m_pFileItem ) m_pFileItem->Release(); m_pFileItem = NULL; } /*----------------------------------------------------------------------------- * *--(samclem)-----------------------------------------------------------------*/ STDMETHODIMP CWiaProtocol::Start( LPCWSTR szUrl, IInternetProtocolSink* pOIProtSink, IInternetBindInfo* pOIBindInfo, DWORD grfPI, HANDLE_PTR dwReserved ) { CWiaCacheManager* pCache= CWiaCacheManager::GetInstance(); CComPtr pDevice; CComBSTR bstrDeviceId ; CComBSTR bstrItem ; TTPARAMS* pParams = NULL; HANDLE hThread = NULL; DWORD dwThreadId = 0; LONG lItemType = 0; BYTE* pbThumb = NULL; DWORD cbThumb = 0; HRESULT hr; // the first thing that we want to do is to attempt to crack the URL, // this can be an involved process so we have a helper method that // handles doing this for us. hr = THR( CrackURL( szUrl, &bstrDeviceId, &bstrItem ) ); if ( FAILED( hr ) ) goto Cleanup; // do we already have a cached version of this item, if so we can avoid // having to do anything else if ( pCache->GetThumbnail( bstrItem, &pbThumb, &cbThumb ) ) { TraceTag((tagWiaProto, "Using cached thumbnail" )); m_pd.pData = pbThumb; m_pd.cbData = cbThumb; m_pd.dwState = k_dwTransferComplete; hr = THR( pOIProtSink->ReportData( BSCF_LASTDATANOTIFICATION, cbThumb, cbThumb ) ); if ( FAILED( hr ) ) goto Cleanup; hr = THR( pOIProtSink->ReportResult( hr, hr, NULL ) ); if ( FAILED( hr ) ) goto Cleanup; } else { if ( !pCache->GetDevice( bstrDeviceId, &pDevice ) ) { hr = THR( CreateDevice( bstrDeviceId, &pDevice ) ); if ( FAILED( hr ) ) goto Cleanup; pCache->AddDevice( bstrDeviceId, pDevice ); } else { TraceTag((tagWiaProto, "Using cached device pointer" )); } hr = THR( pDevice->FindItemByName( 0, bstrItem, &m_pFileItem ) ); if ( FAILED( hr ) || S_FALSE == hr ) { TraceTag((tagWiaProto, "unable to locate item: %S", bstrItem )); hr = INET_E_RESOURCE_NOT_FOUND; goto Cleanup; } // the last thing we want to verify is that the item is an image // and a file, otherwise we don't want anything to do with it hr = THR( m_pFileItem->GetItemType( &lItemType ) ); if ( !( lItemType & WiaItemTypeFile ) && !( lItemType & WiaItemTypeImage ) ) { TraceTag((tagWiaProto, "unsupported wia item type for download" )); hr = INET_E_INVALID_REQUEST; goto Cleanup; } // at this point everything is happy in our land. we have a valid // thing to download from. We now need to create the thread which // will do the main work pParams = reinterpret_cast(CoTaskMemAlloc( sizeof( TTPARAMS ) ) ); if ( !pParams ) { hr = E_OUTOFMEMORY; goto Cleanup; } hr = THR( CoMarshalInterThreadInterfaceInStream( IID_IWiaItem, m_pFileItem, &pParams->pStrm ) ); if ( FAILED( hr ) ) { TraceTag((tagWiaProto, "error marshalling interface" )); goto Cleanup; } pParams->pInetSink = pOIProtSink; pParams->pInetSink->AddRef(); hThread = CreateThread( NULL, 0, CWiaProtocol::TransferThumbnail, pParams, 0, &dwThreadId ); if ( NULL == hThread ) { pParams->pInetSink->Release(); pParams->pStrm->Release(); CoTaskMemFree( pParams ); hr = E_FAIL; goto Cleanup; } else { CloseHandle(hThread); } TraceTag((tagWiaProto, "Started transfer thread: id(%x)", dwThreadId )); } Cleanup: if ( FAILED( hr ) ) { if ( m_pFileItem ) m_pFileItem->Release(); m_pFileItem = NULL; } return hr; } /*----------------------------------------------------------------------------- * CWiaProtocol::Continue * * This is called to pass data back from the the other threads. It lets * the controlling thread know we have data. * * Note: Copy the data from the pointer, DON'T use thier pointer, they will * free it following the return of this call. *--(samclem)-----------------------------------------------------------------*/ STDMETHODIMP CWiaProtocol::Continue( PROTOCOLDATA* pProtocolData ) { if ( k_dwTransferComplete == m_pd.dwState ) return E_UNEXPECTED; memcpy( &m_pd, pProtocolData, sizeof( PROTOCOLDATA ) ); return S_OK; } /*----------------------------------------------------------------------------- * CWiaProtocl::Abort * * This is called to abort our transfer. this is NYI. However, it would * need to kill our thread if it is still running and free our data. However, * it is perfectly harmless if the thread keeps running. * * hrReason: the reason for the abort * dwOptions: the options for this abourt *--(samclem)-----------------------------------------------------------------*/ STDMETHODIMP CWiaProtocol::Abort( HRESULT hrReason, DWORD dwOptions ) { TraceTag((tagWiaProto, "NYI: Abort hrReason=%hr", hrReason )); return E_NOTIMPL; } /*----------------------------------------------------------------------------- * CWiaProtocol::Terminate * * This is called when the transfer is finished. This is responsible for * cleaning anything up that we might need to do. We currently don't have * anything to clean up. So this simply returns S_OK. *--(samclem)-----------------------------------------------------------------*/ STDMETHODIMP CWiaProtocol::Terminate( DWORD dwOptions ) { // Nothing to do. return S_OK; } /*----------------------------------------------------------------------------- * CWiaProtocol::Suspend * * This is called to suspend the transfer. This is currently not implemenet * inside of trident, so our methods just return E_NOTIMPL *--(samclem)-----------------------------------------------------------------*/ STDMETHODIMP CWiaProtocol::Suspend() { TraceTag((tagWiaProto, "NYI: Suspend" )); return E_NOTIMPL; } /*----------------------------------------------------------------------------- * CWiaProtocol::Resume * * This is called to resume a suspended transfer. This is not suppored * inside of URLMON, so we just return E_NOTIMPL *--(samclem)-----------------------------------------------------------------*/ STDMETHODIMP CWiaProtocol::Resume() { TraceTag((tagWiaProto, "NYI: Resume" )); return E_NOTIMPL; } /*----------------------------------------------------------------------------- * CWiaProtocol::Read * * This is called to read data from our protocol. this copies cb bytes to * the buffer passed in. Or it will copy what ever we have. * * pv: the buffer that we want to copy the data to * cb: the size fo buffer, max bytes to copy * pcbRead: Out, the number of bytes that we actually copied to the buffer *--(samclem)-----------------------------------------------------------------*/ STDMETHODIMP CWiaProtocol::Read( void* pv, ULONG cb, ULONG* pcbRead) { // validate our arguments if ( !pv || !pcbRead ) return E_POINTER; *pcbRead = 0; // is the transfer currently pending? if so then // we don't actually want to do anything here. if ( k_dwTransferPending == m_pd.dwState ) return E_PENDING; // do we actually have data to copy? if the offset is greater // or equal to the size of our data then we don't have an data to // copy so return S_FALSE if ( m_ulOffset >= m_pd.cbData ) return S_FALSE; // figure out how much we are going to copy DWORD dwCopy = m_pd.cbData - m_ulOffset; if ( dwCopy >= cb ) dwCopy = cb; // if we have negative memory to copy, or 0, then we are done and we don't // actually want to do anything besides return S_FALSE if ( dwCopy <= 0 ) return S_FALSE; // do the memcpy and setup our state and the return value memcpy( pv, reinterpret_cast(m_pd.pData) + m_ulOffset, dwCopy ); m_ulOffset += dwCopy; *pcbRead = dwCopy; return ( dwCopy == cb ? S_OK : S_FALSE ); } /*----------------------------------------------------------------------------- * CWiaProtocol::Seek * * Called to seek our data. However, we don't support seeking so this just * returns E_FAIL * * dlibMove: how far to move the offset * dwOrigin: indicates where the move shoudl begin * plibNewPosition: The new position of the offset *--(samclem)-----------------------------------------------------------------*/ STDMETHODIMP CWiaProtocol::Seek( LARGE_INTEGER dlibMove, DWORD dwOrigin, ULARGE_INTEGER* plibNewPosition ) { // Don't support return E_FAIL; } /*----------------------------------------------------------------------------- * CWiaProtocol::LockRequest * * Called to lock the data. we don't need to lock our data, so this just * returns S_OK * * dwOptions: reserved, will be 0. *--(samclem)-----------------------------------------------------------------*/ STDMETHODIMP CWiaProtocol::LockRequest( DWORD dwOptions ) { //Don't support locking return S_OK; } /*----------------------------------------------------------------------------- * CWiaProtocol::UnlockRequest * * Called to unlock our data. We don't need or support locking, so this * doesn't do anything besides return S_OK. *--(samclem)-----------------------------------------------------------------*/ STDMETHODIMP CWiaProtocol::UnlockRequest() { //Don't support locking return S_OK; } /*----------------------------------------------------------------------------- * CWiaProtocol::CrackURL * * This handles breaking appart a URL which is passed in to us. This will * return S_OK if it is a valid URL and we can work with it. otherwise this * will return INET_E_INVALID_URL * * bstrUrl: the full url to be cracked * pbstrDeviceId: Out, recieves the device id portion of the URL * pbstrItem: Out, recieves the item portion of the URL *--(samclem)-----------------------------------------------------------------*/ HRESULT CWiaProtocol::CrackURL( CComBSTR bstrUrl, BSTR* pbstrDeviceId, BSTR* pbstrItem ) { WCHAR* pwchUrl = reinterpret_cast((BSTR)bstrUrl); WCHAR* pwch = NULL; WCHAR awch[INTERNET_MAX_URL_LENGTH] = { 0 }; HRESULT hr = INET_E_INVALID_URL; Assert( pbstrDeviceId && pbstrItem ); *pbstrDeviceId = NULL; *pbstrItem = NULL; if (SysStringLen(bstrUrl) >= INTERNET_MAX_URL_LENGTH) goto Cleanup; /* * We are going to use the SHWAPI functions to parse this URL. Our format * is very simple. * * proto:///? */ if ( StrCmpNIW( k_wszProtocolName, pwchUrl, k_cchProtocolName ) ) goto Cleanup; pwchUrl += k_cchProtocolName; while ( *pwchUrl == k_wchColon || *pwchUrl == k_wchFrontSlash ) pwchUrl++; if ( !(*pwchUrl ) ) goto Cleanup; // get the device portion of the URL pwch = StrChrIW( pwchUrl, k_wchSeperator ); if ( !pwch ) goto Cleanup; StrCpyNW( awch, pwchUrl, ( pwch - pwchUrl + 1 ) ); *pbstrDeviceId = SysAllocString( awch ); if ( !*pbstrDeviceId ) { hr = E_OUTOFMEMORY; goto Cleanup; } // adjust our pointer past the '?' pwchUrl = pwch + 1; if ( !*pwchUrl ) goto Cleanup; if ( StrCmpNIW( k_wszThumb, pwchUrl, z_cchThumb ) ) goto Cleanup; // get the command portion of the URL pwch = StrChrIW( pwchUrl, k_wchSeperator ); if ( !pwch ) goto Cleanup; // adjust our pointer past the '?' pwchUrl = pwch + 1; if ( !*pwchUrl ) goto Cleanup; // attempt to get the item portion of the url pwch = StrRChrIW( pwchUrl, 0, k_wchPeriod ); awch[0] = k_wchEOS; if ( pwch ) StrCpyNW( awch, pwchUrl, ( pwch - pwchUrl + 1) ); else StrCpyW( awch, pwchUrl ); *pbstrItem = SysAllocString( awch ); if ( !*pbstrItem ) { hr = E_OUTOFMEMORY; goto Cleanup; } TraceTag((tagWiaProto, "URL: Device=%S, Item=%S", *pbstrDeviceId, *pbstrItem )); // everything was ok return S_OK; Cleanup: if ( FAILED( hr ) ) { SysFreeString( *pbstrDeviceId ); SysFreeString( *pbstrItem ); } return INET_E_INVALID_URL; } /*----------------------------------------------------------------------------- * CWiaProtocol::CreateDevice * * This is a helper method which handles creating a wia device with the * specified id. this instances a IWiaDevMgr object and then attempts * to create the device. * * bstrId: the id of the device to create * ppDevice: Out, recieves the pointer to the newly created device *--(samclem)-----------------------------------------------------------------*/ HRESULT CWiaProtocol::CreateDevice( BSTR bstrId, IWiaItem** ppDevice ) { CComPtr pDevice; CComPtr pDevMgr; HRESULT hr; Assert( ppDevice ); *ppDevice = 0; // first we need to create our device manager hr = THR( pDevMgr.CoCreateInstance( CLSID_WiaDevMgr ) ); if ( FAILED( hr ) ) return hr; // now we need the device manager to create a device hr = THR( pDevMgr->CreateDevice( bstrId, &pDevice ) ); if ( FAILED( hr ) ) return hr; // copy our device pointer over return THR( pDevice.CopyTo( ppDevice ) ); } /*----------------------------------------------------------------------------- * CWiaProtocol::CreateURL [static] * * This method creates a URL for the given item. This doesn't verifiy the * item. Other than making sure it has a root so that we can build the URL. * This may return an invalid URL. It is important to verify that the item * can actually have a thumbnail before calling this. * * Note: in order to create a thumbnail: * lItemType & ( WiaItemTypeFile | WiaItemTypeImage ) * * pItem: The wia item that we want to generate the URL for. * pbstrUrl: Out, recieves the finished URL *--(samclem)-----------------------------------------------------------------*/ HRESULT CWiaProtocol::CreateURL( IWiaItem* pItem, BSTR* pbstrUrl ) { HRESULT hr; CComBSTR bstrUrl; CComPtr pRootItem; CComQIPtr pWiaStg; CComQIPtr pRootWiaStg; PROPSPEC spec = { PRSPEC_PROPID, WIA_DIP_DEV_ID }; PROPVARIANT va; if ( !pbstrUrl || !pItem ) return E_POINTER; PropVariantInit( &va ); // get the interfaces that we need pWiaStg = pItem; if ( !pWiaStg ) { hr = E_NOINTERFACE; goto Cleanup; } hr = THR( pItem->GetRootItem( &pRootItem ) ); if ( FAILED( hr ) || !pRootItem ) goto Cleanup; pRootWiaStg = pRootItem; if ( !pRootWiaStg ) { hr = E_NOINTERFACE; goto Cleanup; } // We need the device ID of the root item, and if we can't // get it then we don't have anything else to do. hr = THR( pRootWiaStg->ReadMultiple( 1, &spec, &va ) ); if ( FAILED( hr ) || va.vt != VT_BSTR ) goto Cleanup; // start building our URL bstrUrl.Append( k_wszProtocolName ); bstrUrl.Append( k_wszColonSlash ); bstrUrl.AppendBSTR( va.bstrVal ); bstrUrl.Append( k_wszSeperator ); bstrUrl.Append( k_wszThumb ); bstrUrl.Append( k_wszSeperator ); // we need to get the full item name from the item, because // we need to tack that on to the end PropVariantClear( &va ); spec.propid = WIA_IPA_FULL_ITEM_NAME; hr = THR( pWiaStg->ReadMultiple( 1, &spec, &va ) ); if ( FAILED( hr ) || va.vt != VT_BSTR ) goto Cleanup; bstrUrl.AppendBSTR( va.bstrVal ); bstrUrl.Append( k_wszExtension ); TraceTag((tagWiaProto, "Created URL: %S", (BSTR)bstrUrl )); *pbstrUrl = bstrUrl.Copy(); if ( !*pbstrUrl ) hr = E_OUTOFMEMORY; Cleanup: PropVariantClear( &va ); return hr; } /*----------------------------------------------------------------------------- * CWiaProtocol::TransferThumbnail [static] * * This handles the actual transfer of the thumbnail. This is only called * however, if we don't already have a cached copy of the thumbnail. Otherwise * we can simply use that one. * * Note: we spawn a thread with this function, which is why its static * * pvParams: a pointer to a TTPARAMS structure, which contains a pointer * to the IInternetProtoclSink and the IStream where the * item is marshalled. *--(samclem)-----------------------------------------------------------------*/ DWORD WINAPI CWiaProtocol::TransferThumbnail( LPVOID pvParams ) { CComPtr pItem; CComPtr pProtSink; IStream* pStrm = NULL; DWORD cbData = 0; BYTE* pbData = NULL; TTPARAMS* pParams = reinterpret_cast(pvParams); PROTOCOLDATA* ppd = NULL; HRESULT hr; HRESULT hrCoInit; Assert( pParams ); pProtSink = pParams->pInetSink; pStrm = pParams->pStrm; hrCoInit = THR( CoInitialize( NULL ) ); // we no longer need our params, so we can free them now. we // will handle freeing the params here since its simpler pParams->pInetSink->Release(); CoTaskMemFree( pParams ); pParams = NULL; // get the IWiaItem from the stream hr = THR( CoGetInterfaceAndReleaseStream( pStrm, IID_IWiaItem, reinterpret_cast(&pItem) ) ); if ( FAILED( hr ) ) goto Cleanup; // allocate a protocol data structure so that we can give this back to // the other thread. We will allocate this here. it may be freed if // something fails ppd = reinterpret_cast(LocalAlloc( LPTR, sizeof( PROTOCOLDATA ) ) ); if ( !ppd ) { hr = E_OUTOFMEMORY; goto Cleanup; } // use the utility method on CWiaItem to do the transfer hr = THR( CWiaItem::TransferThumbnailToCache( pItem, &pbData, &cbData ) ); if ( FAILED( hr ) ) goto Cleanup; ppd->pData = pbData; ppd->cbData = cbData; ppd->dwState = k_dwTransferComplete; // we are all done now, we can tell trident that we are 100% done // and then call switch hr = THR( pProtSink->Switch( ppd ) ); if ( FAILED( hr ) ) goto Cleanup; hr = THR( pProtSink->ReportData(BSCF_LASTDATANOTIFICATION, cbData, cbData ) ); Cleanup: // post our result status back to the sink //TODO(Aug, 27) samclem: implement the error string param if ( pProtSink ) THR( pProtSink->ReportResult( hr, hr, NULL ) ); if ( ppd ) LocalFree( ppd ); if ( SUCCEEDED( hrCoInit ) ) CoUninitialize(); return hr; }