/******************************Module*Header*******************************\ * Module Name: getinfo.cpp * * Author: David Stewart [dstewart] * * Copyright (c) 1998 Microsoft Corporation. All rights reserved. \**************************************************************************/ #include #include #include //for mci commands #include #include //for IID_IBindStatusCallback #include "getinfo.h" #include "netres.h" #include "wininet.h" #include "condlg.h" #include "..\main\mmfw.h" #include "..\cdopt\cdopt.h" #include "mapi.h" #include extern HINSTANCE g_dllInst; #define MODE_OK 0 #define MODE_MULTIPLE 1 #define MODE_NOT_FOUND 2 #define FRAMES_PER_SECOND 75 #define FRAMES_PER_MINUTE (60*FRAMES_PER_SECOND) #define MAX_UPLOAD_URL_LENGTH 1500 #ifdef UNICODE #define URLFUNCTION "URLOpenStreamW" #define CANONFUNCTION "InternetCanonicalizeUrlW" #else #define URLFUNCTION "URLOpenStreamA" #define CANONFUNCTION "InternetCanonicalizeUrlA" #endif HWND g_hwndParent = NULL; extern HINSTANCE g_hURLMon; LPCDOPT g_pNetOpt = NULL; LPCDDATA g_pNetData = NULL; BOOL g_fCancelDownload = FALSE; //ANY ACCESS MUST BE SURROUNDED by Enter/Leave g_Critical IBinding* g_pBind = NULL; //ANY ACCESS MUST BE SURROUNDED by Enter/Leave g_Critical long g_lNumDownloadingThreads = 0; //MUST USE InterlockedIncrement/Decrement BOOL g_fDownloadDone = FALSE; BOOL g_fDBWriteFailure = FALSE; extern CRITICAL_SECTION g_Critical; extern CRITICAL_SECTION g_BatchCrit; DWORD WINAPI SpawnSingleDownload(LPVOID pParam); DWORD WINAPI SpawnBatchDownload(LPVOID pParam); DWORD WINAPI DoBatchDownload(LPCDBATCH pBatchList, HWND hwndParent); BOOL DoDownload(TCHAR* url, TCHAR* szFilename, HWND hwndParent); CCDNet::CCDNet() { m_dwRef = 0; } CCDNet::~CCDNet() { } STDMETHODIMP CCDNet::QueryInterface(REFIID riid, void** ppv) { *ppv = NULL; if (IID_IUnknown == riid || IID_ICDNet == riid) { *ppv = this; } if (NULL==*ppv) { return E_NOINTERFACE; } AddRef(); return S_OK; } STDMETHODIMP_(ULONG) CCDNet::AddRef(void) { return ++m_dwRef; } STDMETHODIMP_(ULONG) CCDNet::Release(void) { if (0!=--m_dwRef) return m_dwRef; delete this; return 0; } STDMETHODIMP CCDNet::SetOptionsAndData(void* pOpts, void* pData) { g_pNetOpt = (LPCDOPT)pOpts; g_pNetData = (LPCDDATA)pData; return S_OK; } //this is a start to implementing the "upload via http" case rather than the //upload via mail case BOOL UploadToProvider(LPCDPROVIDER pProvider, LPCDTITLE pTitle, HWND hwndParent) { TCHAR szURL[INTERNET_MAX_URL_LENGTH]; TCHAR szMainURL[INTERNET_MAX_URL_LENGTH]; TCHAR szFilename[MAX_PATH]; //get the InternetCanonicalizeURL function typedef BOOL (PASCAL *CANPROC)(LPCTSTR, LPTSTR, LPDWORD, DWORD); CANPROC canProc = NULL; HMODULE hNet = LoadLibrary(TEXT("WININET.DLL")); if (hNet!=NULL) { canProc = (CANPROC)GetProcAddress(hNet,CANONFUNCTION); } else { return FALSE; } if (pProvider && pTitle && canProc) { //check for provider URL if (_tcslen(pProvider->szProviderUpload)>0) { TCHAR szTempCan[MAX_PATH*2]; //create the URL to send wsprintf(szMainURL,TEXT("%s%s"),pProvider->szProviderUpload,pTitle->szTitleQuery); _tcscpy(szURL,szMainURL); //add title _tcscat(szURL,TEXT("&t=")); DWORD dwSize = sizeof(szTempCan); canProc(pTitle->szTitle,szTempCan,&dwSize,0); _tcscat(szURL,szTempCan); //add artist _tcscat(szURL,TEXT("&a=")); dwSize = sizeof(szTempCan); canProc(pTitle->szArtist,szTempCan,&dwSize,0); _tcscat(szURL,szTempCan); //add tracks TCHAR szTrack[MAX_PATH]; for (DWORD i = 0; i < pTitle->dwNumTracks; i++) { wsprintf(szTrack,TEXT("&%u="),i+1); dwSize = sizeof(szTempCan); canProc(pTitle->pTrackTable[i].szName,szTempCan,&dwSize,0); if ((_tcslen(szURL) + _tcslen(szTrack) + _tcslen(szTempCan)) > MAX_UPLOAD_URL_LENGTH-sizeof(TCHAR)) { //we're coming close to the limit. Send what we have and start rebuilding if (!g_fCancelDownload) { if (DoDownload(szURL,szFilename, hwndParent)) { DeleteFile(szFilename); } //end if "upload" successful else { //bad upload, don't bother sending the rest //probably a timeout return FALSE; } } //reset the URL to just the provider + toc _tcscpy(szURL,szMainURL); } //end if length _tcscat(szURL,szTrack); _tcscat(szURL,szTempCan); } //end for track //send it if (!g_fCancelDownload) { if (DoDownload(szURL,szFilename, hwndParent)) { DeleteFile(szFilename); } //end if "upload" successful else { return FALSE; } } } //end if url exists } //end if state OK if (hNet) { FreeLibrary(hNet); } return TRUE; } DWORD WINAPI UploadThread(LPVOID pParam) { InterlockedIncrement((LONG*)&g_lNumDownloadingThreads); //this will block us against the batch download happening, too EnterCriticalSection(&g_BatchCrit); LPCDTITLE pTitle = (LPCDTITLE)pParam; HWND hwndParent = g_hwndParent; int nTries = 0; int nSuccessful = 0; if (pTitle) { LPCDOPTIONS pOptions = g_pNetOpt->GetCDOpts(); // Get the options, needed for provider list if (pOptions && pOptions->pCurrentProvider) // Make sure we have providers { LPCDPROVIDER pProviderList = NULL; LPCDPROVIDER pProvider = NULL; g_pNetOpt->CreateProviderList(&pProviderList); // Get the sorted provider list pProvider = pProviderList; // Get the head of the list while ((pProvider) && (!g_fCancelDownload)) { nTries++; if (UploadToProvider(pProvider,pTitle,hwndParent)) { nSuccessful++; } pProvider = pProvider->pNext; } g_pNetOpt->DestroyProviderList(&pProviderList); } //end if providers } //addref'ed before thread was created g_pNetOpt->Release(); g_pNetData->Release(); long status = UPLOAD_STATUS_NO_PROVIDERS; if ((nSuccessful != nTries) && (nSuccessful > 0)) { status = UPLOAD_STATUS_SOME_PROVIDERS; } if ((nSuccessful == nTries) && (nSuccessful > 0)) { status = UPLOAD_STATUS_ALL_PROVIDERS; } if (g_fCancelDownload) { status = UPLOAD_STATUS_CANCELED; } LeaveCriticalSection(&g_BatchCrit); InterlockedDecrement((LONG*)&g_lNumDownloadingThreads); //post message saying we're done PostMessage(hwndParent,WM_NET_DONE,(WPARAM)g_dllInst,status); return 0; } STDMETHODIMP CCDNet::Upload(LPCDTITLE pTitle, HWND hwndParent) { HRESULT hr = E_FAIL; DWORD dwHow; BOOL fConnected; if (g_pNetOpt && g_pNetData && pTitle) // Make sure we are in a valid state { fConnected = _InternetGetConnectedState(&dwHow,0,TRUE); // Make sure we are connected to net if (fConnected) // Make sure we are in a valid state { EnterCriticalSection(&g_Critical); g_fCancelDownload = FALSE; LeaveCriticalSection(&g_Critical); DWORD dwThreadID; HANDLE hNetThread = NULL; g_hwndParent = hwndParent; g_pNetOpt->AddRef(); g_pNetData->AddRef(); hNetThread = CreateThread(NULL,0,UploadThread,(void*)pTitle,0,&dwThreadID); if (hNetThread) { CloseHandle(hNetThread); hr = S_OK; } } //end if connected } //end if options and data ok return (hr); } STDMETHODIMP_(BOOL) CCDNet::CanUpload() { BOOL retcode = FALSE; if (g_pNetOpt && g_pNetData) // Make sure we are in a valid state { //check all providers to be sure at least one has upload capability LPCDOPTIONS pOptions = g_pNetOpt->GetCDOpts(); // Get the options, needed for provider list if (pOptions && pOptions->pCurrentProvider) // Make sure we have providers { LPCDPROVIDER pProviderList = NULL; LPCDPROVIDER pProvider = NULL; g_pNetOpt->CreateProviderList(&pProviderList); // Get the sorted provider list pProvider = pProviderList; // Get the head of the list while (pProvider) { if (_tcslen(pProvider->szProviderUpload) > 0) { retcode = TRUE; } pProvider = pProvider->pNext; } //end while g_pNetOpt->DestroyProviderList(&pProviderList); } //end if providers } //end if set up properly return (retcode); } STDMETHODIMP CCDNet::Download(DWORD dwDeviceHandle, TCHAR chDrive, DWORD dwMSID, LPCDTITLE pTitle, BOOL fManual, HWND hwndParent) { if (g_pNetOpt==NULL) { return E_FAIL; } if (g_pNetData==NULL) { return E_FAIL; } if (FAILED(g_pNetData->CheckDatabase(hwndParent))) { return E_FAIL; } CGetInfoFromNet netinfo(dwDeviceHandle, dwMSID, hwndParent); EnterCriticalSection(&g_Critical); g_fCancelDownload = FALSE; LeaveCriticalSection(&g_Critical); BOOL fResult = netinfo.DoIt(fManual, pTitle, chDrive); return fResult ? S_OK : E_FAIL; } STDMETHODIMP_(BOOL) CCDNet::IsDownloading() { BOOL retcode = FALSE; if (g_lNumDownloadingThreads > 0) { retcode = TRUE; } return (retcode); } STDMETHODIMP CCDNet::CancelDownload() { EnterCriticalSection(&g_Critical); if (g_pBind) { g_pBind->Abort(); } g_fCancelDownload = TRUE; LeaveCriticalSection(&g_Critical); while (IsDownloading()) { Sleep(10); } return S_OK; } struct CBindStatusCallback : IBindStatusCallback { ///// object state ULONG m_cRef; // object reference count BOOL m_fAbort; // set to true if we want this to abort HWND m_hMessage; // callback window IStream* m_pStream; // holds downloaded data ///// construction and destruction CBindStatusCallback(IStream* pStream, HWND hwndParent); ~CBindStatusCallback(); ///// IUnknown methods STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppvObj); STDMETHODIMP_(ULONG) AddRef(); STDMETHODIMP_(ULONG) Release(); ///// IBindStatusCallback methods STDMETHODIMP OnStartBinding(DWORD dwReserved, IBinding *pib); STDMETHODIMP GetPriority(LONG *pnPriority); STDMETHODIMP OnLowResource(DWORD reserved); STDMETHODIMP OnProgress(ULONG ulProgress, ULONG ulProgressMax, ULONG ulStatusCode, LPCWSTR szStatusText); STDMETHODIMP OnStopBinding(HRESULT hresult, LPCWSTR szError); STDMETHODIMP GetBindInfo(DWORD *grfBINDF, BINDINFO *pbindinfo); STDMETHODIMP OnDataAvailable(DWORD grfBSCF, DWORD dwSize, FORMATETC *pformatetc, STGMEDIUM *pstgmed); STDMETHODIMP OnObjectAvailable(REFIID riid, IUnknown *punk); }; ///////////////////////////////////////////////////////////////////////////// // CBindStatusCallback Creation & Destruction // CBindStatusCallback::CBindStatusCallback(IStream* pStream, HWND hwndParent) { HRESULT hr = S_OK; m_cRef = 0; m_fAbort = FALSE; m_pStream = pStream; m_pStream->AddRef(); m_hMessage = hwndParent; PostMessage(m_hMessage,WM_NET_STATUS,(WPARAM)g_dllInst,IDS_STRING_CONNECTING); } CBindStatusCallback::~CBindStatusCallback() { EnterCriticalSection(&g_Critical); if (g_pBind) { g_pBind->Release(); g_pBind = NULL; } LeaveCriticalSection(&g_Critical); if( m_pStream ) { m_pStream->Release(); m_pStream = NULL; } } ///////////////////////////////////////////////////////////////////////////// // CBindStatusCallback IUnknown Methods // STDMETHODIMP CBindStatusCallback::QueryInterface(REFIID riid, LPVOID *ppvObj) { if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IBindStatusCallback)) { *ppvObj = (IBindStatusCallback *) this; AddRef(); return NOERROR; } else { *ppvObj = NULL; return E_NOINTERFACE; } } STDMETHODIMP_(ULONG) CBindStatusCallback::AddRef() { InterlockedIncrement((LONG*)&m_cRef); return m_cRef; } STDMETHODIMP_(ULONG) CBindStatusCallback::Release() { ULONG cRef = m_cRef; if (InterlockedDecrement((LONG*)&m_cRef) == 0) { delete this; return 0; } else return cRef-1; } ///////////////////////////////////////////////////////////////////////////// // CBindStatusCallback IBindStatusCallback Methods // STDMETHODIMP CBindStatusCallback::OnStartBinding(DWORD dwReserved, IBinding *pib) { EnterCriticalSection(&g_Critical); g_pBind = pib; g_pBind->AddRef(); LeaveCriticalSection(&g_Critical); return S_OK; } STDMETHODIMP CBindStatusCallback::GetPriority(LONG *pnPriority) { return S_OK; } STDMETHODIMP CBindStatusCallback::OnLowResource(DWORD reserved) { return S_OK; } STDMETHODIMP CBindStatusCallback::OnProgress(ULONG ulProgress, ULONG ulProgressMax, ULONG ulStatusCode, LPCWSTR szStatusText) { int nResID = 0; switch (ulStatusCode) { case (BINDSTATUS_FINDINGRESOURCE) : nResID = IDS_STRING_FINDINGRESOURCE; break; case (BINDSTATUS_CONNECTING) : nResID = IDS_STRING_CONNECTING; break; case (BINDSTATUS_REDIRECTING) : nResID = IDS_STRING_REDIRECTING; break; case (BINDSTATUS_BEGINDOWNLOADDATA) : nResID = IDS_STRING_BEGINDOWNLOAD; break; case (BINDSTATUS_DOWNLOADINGDATA) : nResID = IDS_STRING_DOWNLOAD; break; case (BINDSTATUS_ENDDOWNLOADDATA) : nResID = IDS_STRING_ENDDOWNLOAD; break; case (BINDSTATUS_SENDINGREQUEST) : nResID = IDS_STRING_SENDINGREQUEST; break; } //end switch if (nResID > 0) { PostMessage(m_hMessage,WM_NET_STATUS,(WPARAM)g_dllInst,nResID); } if (( m_fAbort ) || (g_fCancelDownload)) { EnterCriticalSection(&g_Critical); g_fCancelDownload = TRUE; g_fDownloadDone = TRUE; LeaveCriticalSection(&g_Critical); return E_ABORT; } return S_OK; } STDMETHODIMP CBindStatusCallback::OnStopBinding(HRESULT hresult, LPCWSTR szError) { EnterCriticalSection(&g_Critical); if (g_pBind) { g_pBind->Release(); g_pBind = NULL; } LeaveCriticalSection(&g_Critical); return S_OK; } STDMETHODIMP CBindStatusCallback::GetBindInfo(DWORD *pgrfBINDF, BINDINFO *pbindinfo) { *pgrfBINDF = 0; pbindinfo->cbSize = sizeof(BINDINFO); pbindinfo->szExtraInfo = NULL; ZeroMemory(&pbindinfo->stgmedData, sizeof(STGMEDIUM)); pbindinfo->grfBindInfoF = 0; pbindinfo->dwBindVerb = BINDVERB_GET; pbindinfo->szCustomVerb = NULL; return S_OK; } STDMETHODIMP CBindStatusCallback::OnDataAvailable(DWORD grfBSCF, DWORD dwSize, FORMATETC *pformatetc, STGMEDIUM *pstgmed) { // fill our stream with the data from the stream passed to us if( m_pStream ) { ULARGE_INTEGER cb; cb.LowPart = dwSize; cb.HighPart = 0; if( pstgmed && pstgmed->pstm ) { pstgmed->pstm->CopyTo( m_pStream, cb, NULL, NULL ); } } // Notify owner when download is complete if( grfBSCF & BSCF_LASTDATANOTIFICATION ) { g_fDownloadDone = TRUE; if( m_pStream ) { m_pStream->Release(); m_pStream = NULL; } } return S_OK; } STDMETHODIMP CBindStatusCallback::OnObjectAvailable(REFIID riid, IUnknown *punk) { return E_NOTIMPL; } ///////////////////////////////////////////////////////////////////////////// // CGetInfoFromNet CGetInfoFromNet::CGetInfoFromNet(DWORD cdrom, DWORD dwMSID, HWND hwndParent) { DevHandle = cdrom; m_MS = dwMSID; g_hwndParent = hwndParent; } CGetInfoFromNet::~CGetInfoFromNet() { } BOOL CGetInfoFromNet::DoIt(BOOL fManual, LPCDTITLE pTitle, TCHAR chDrive) { BOOL fRet = FALSE; int nMode = CONNECTION_GETITNOW; if (!fManual) { if (g_lNumDownloadingThreads == 0) { //if no threads are running already, //check the connection, possibly prompting the user nMode = ConnectionCheck(g_hwndParent,g_pNetOpt, chDrive); } } if (nMode == CONNECTION_DONOTHING) { return FALSE; } //if passed-in ID is not > 0, then we don't want to scan current disc if ((m_MS > 0) && (pTitle == NULL)) { m_Tracks = readtoc(); if (m_Tracks > 0) { BuildQuery(); } } //if msid is greater than 0 if (nMode == CONNECTION_BATCH) { if (m_MS > 0) { AddToBatch(m_Tracks,m_Query); } return FALSE; } //we need to determine now whether we spawn a batching thread or a single-item downloader g_fDBWriteFailure = FALSE; DWORD dwThreadID; HANDLE hNetThread = NULL; //addref the global pointers before entering the thread g_pNetOpt->AddRef(); g_pNetData->AddRef(); if (m_MS > 0) { //need to create a batch item for this thread to use LPCDBATCH pBatch = new CDBATCH; pBatch->fRemove = FALSE; pBatch->fFresh = TRUE; pBatch->pNext = NULL; if (!pTitle) { pBatch->dwTitleID = m_MS; pBatch->dwNumTracks = m_Tracks; pBatch->szTitleQuery = new TCHAR[_tcslen(m_Query)+1]; _tcscpy(pBatch->szTitleQuery,m_Query); } else { pBatch->dwTitleID = pTitle->dwTitleID; pBatch->dwNumTracks = pTitle->dwNumTracks; if (pTitle->szTitleQuery) { pBatch->szTitleQuery = new TCHAR[_tcslen(pTitle->szTitleQuery)+1]; _tcscpy(pBatch->szTitleQuery,pTitle->szTitleQuery); } else { pBatch->szTitleQuery = new TCHAR[_tcslen(m_Query)+1]; _tcscpy(pBatch->szTitleQuery,m_Query); } } hNetThread = CreateThread(NULL,0,SpawnSingleDownload,(void*)pBatch,0,&dwThreadID); } else { hNetThread = CreateThread(NULL,0,SpawnBatchDownload,(void*)NULL,0,&dwThreadID); } if (hNetThread) { CloseHandle(hNetThread); fRet = TRUE; } return (fRet); } int CGetInfoFromNet::readtoc() { DWORD dwRet; MCI_SET_PARMS mciSet; ZeroMemory( &mciSet, sizeof(mciSet) ); mciSet.dwTimeFormat = MCI_FORMAT_MSF; mciSendCommand( DevHandle, MCI_SET, MCI_SET_TIME_FORMAT, (DWORD_PTR)(LPVOID)&mciSet ); MCI_STATUS_PARMS mciStatus; long lAddress, lStartPos, lDiskLen; int i; ZeroMemory( &mciStatus, sizeof(mciStatus) ); mciStatus.dwItem = MCI_STATUS_NUMBER_OF_TRACKS; // // NOTE: none of the mciSendCommand calls below bother to check the // return code. This is asking for trouble... but if the // commands fail we cannot do much about it. // dwRet = mciSendCommand( DevHandle, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)(LPVOID)&mciStatus); int tracks = -1; tracks = (UCHAR)mciStatus.dwReturn; mciStatus.dwItem = MCI_STATUS_POSITION; for ( i = 0; i < tracks; i++ ) { mciStatus.dwTrack = i + 1; dwRet = mciSendCommand( DevHandle, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK, (DWORD_PTR)(LPVOID)&mciStatus); lAddress = (long)mciStatus.dwReturn; //converts "packed" time into pure frames lAddress = (MCI_MSF_MINUTE(lAddress) * FRAMES_PER_MINUTE) + (MCI_MSF_SECOND(lAddress) * FRAMES_PER_SECOND) + (MCI_MSF_FRAME( lAddress)); m_toc[i] = lAddress; if (i==0) { lStartPos = lAddress; } } mciStatus.dwItem = MCI_STATUS_LENGTH; dwRet = mciSendCommand( DevHandle, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)(LPVOID)&mciStatus); /* ** Convert the total disk length into frames */ lAddress = (long)mciStatus.dwReturn; lDiskLen = (MCI_MSF_MINUTE(lAddress) * FRAMES_PER_MINUTE) + (MCI_MSF_SECOND(lAddress) * FRAMES_PER_SECOND) + (MCI_MSF_FRAME( lAddress)); /* ** Now, determine the absolute start position of the sentinel ** track. That is, the special track that marks the end of the ** disk. */ lAddress = lStartPos + lDiskLen + 1; //dstewart: add one for true time m_toc[i] = lAddress; return (tracks); } void CGetInfoFromNet::BuildQuery() { wsprintf(m_Query,TEXT("cd=%X"),m_Tracks); //add each frame stattime to query, include end time of disc TCHAR tempstr[MAX_PATH]; for (int i = 0; i < m_Tracks+1; i++) { wsprintf(tempstr,TEXT("+%X"),m_toc[i]); _tcscat(m_Query,tempstr); } } void CGetInfoFromNet::AddToBatch(int nNumTracks, TCHAR* szQuery) { if ((g_pNetData) && (g_pNetOpt)) { g_pNetData->AddToBatch(m_MS, szQuery, nNumTracks); LPCDOPTIONS pOptions = g_pNetOpt->GetCDOpts(); if (pOptions) { pOptions->dwBatchedTitles = g_pNetData->GetNumBatched(); g_pNetOpt->DownLoadCompletion(0,NULL); } } } void CopyStreamToFile( IStream* pStream, HANDLE hFile ) { TCHAR achBuf[512]; ULONG cb = 1; DWORD dwWritten; LARGE_INTEGER dlib; dlib.LowPart = 0; dlib.HighPart = 0; pStream->Seek( dlib, STREAM_SEEK_SET, NULL ); pStream->Read( achBuf, 512, &cb ); while( cb ) { if( FALSE == WriteFile( hFile, achBuf, cb, &dwWritten, NULL )) { break; } pStream->Read( achBuf, 512, &cb ); } } BOOL DoDownload(TCHAR* url, TCHAR* szFilename, HWND hwndParent) { TCHAR szPath[_MAX_PATH]; TCHAR sz[_MAX_PATH]; BOOL fGotFileName = FALSE; // Get a file name if(GetTempPath(_MAX_PATH, szPath)) { if(GetTempFileName(szPath, TEXT("cdd"), 0, sz)) { fGotFileName = TRUE; } } if (!fGotFileName) { return FALSE; } IStream* pStream = NULL; g_fDownloadDone = FALSE; if (FAILED(CreateStreamOnHGlobal( NULL, TRUE, &pStream ))) { return FALSE; } //pStream was addref'ed by createstreamonhgobal CBindStatusCallback* pCDC = new CBindStatusCallback(pStream, hwndParent); if(!pCDC) { pStream->Release(); return FALSE; } pCDC->AddRef(); HRESULT hr = E_NOTIMPL; if (g_hURLMon == NULL) { g_hURLMon = LoadLibrary(TEXT("URLMON.DLL")); } if (g_hURLMon!=NULL) { typedef BOOL (PASCAL *URLDOWNLOADPROC)(LPUNKNOWN, LPCTSTR, DWORD, LPBINDSTATUSCALLBACK); URLDOWNLOADPROC URLDownload = (URLDOWNLOADPROC)GetProcAddress(g_hURLMon,URLFUNCTION); if (URLDownload!=NULL) { #ifdef DBG OutputDebugString(url); OutputDebugString(TEXT("\n")); #endif hr = URLDownload(NULL, url, 0, pCDC); } } if(FAILED(hr)) { pCDC->Release(); pStream->Release(); return FALSE; } pCDC->Release(); if (g_fCancelDownload) { return FALSE; } // Create the file for writing HANDLE hFileWrite = CreateFile(sz, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, NULL); if( hFileWrite != INVALID_HANDLE_VALUE ) { CopyStreamToFile( pStream, hFileWrite ); CloseHandle( hFileWrite ); } pStream->Release(); _tcscpy(szFilename,sz); return TRUE; } //dialog box handler for multiple hits INT_PTR CALLBACK MultiHitDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_INITDIALOG : { TCHAR* szFilename = (TCHAR*)lParam; TCHAR szTemp[MAX_PATH]; TCHAR szArtist[MAX_PATH]; TCHAR szTitle[MAX_PATH]; int i = 1; _tcscpy(szTitle,TEXT(".")); while (_tcslen(szTitle)>0) { wsprintf(szTemp,TEXT("Title%i"),i); GetPrivateProfileString(TEXT("CD"),szTemp,TEXT(""),szTitle,sizeof(szTitle)/sizeof(TCHAR),szFilename); wsprintf(szTemp,TEXT("Artist%i"),i); GetPrivateProfileString(TEXT("CD"),szTemp,TEXT(""),szArtist,sizeof(szArtist)/sizeof(TCHAR),szFilename); i++; if (_tcslen(szTitle)>0) { wsprintf(szTemp,TEXT("%s (%s)"),szTitle,szArtist); SendDlgItemMessage(hwnd,IDC_LIST_DISCS,LB_ADDSTRING,0,(LPARAM)szTemp); } } SendDlgItemMessage(hwnd,IDC_LIST_DISCS,LB_SETCURSEL,0,0); } break; case WM_COMMAND : { if (LOWORD(wParam)==IDCANCEL) { EndDialog(hwnd,-1); } if (LOWORD(wParam)==IDOK) { LRESULT nSel = SendDlgItemMessage(hwnd,IDC_LIST_DISCS,LB_GETCURSEL,0,0); EndDialog(hwnd,nSel+1); } } break; } return FALSE; } BOOL ResolveMultiples(TCHAR* szFilename, BOOL fCurrent, HWND hwndParent) { //special case ... sometimes, this comes back with <2 hits!!! //in this case, go ahead and ask for URL1 TCHAR sznewurl[INTERNET_MAX_URL_LENGTH]; GetPrivateProfileString(TEXT("CD"),TEXT("URL2"),TEXT(""),sznewurl,sizeof(sznewurl)/sizeof(TCHAR),szFilename); INT_PTR nSelection = 0; if (_tcslen(sznewurl)==0) { nSelection = 1; } else { if (fCurrent) { nSelection = DialogBoxParam(g_dllInst, MAKEINTRESOURCE(IDD_MULTIPLE_HITS), hwndParent, MultiHitDlgProc, (LPARAM)szFilename ); } } if (nSelection > 0) { TCHAR szSelected[MAX_PATH]; wsprintf(szSelected,TEXT("URL%i"),nSelection); GetPrivateProfileString(TEXT("CD"),szSelected,TEXT(""),sznewurl,sizeof(sznewurl)/sizeof(TCHAR),szFilename); DeleteFile(szFilename); if (DoDownload(sznewurl,szFilename, hwndParent)) { return TRUE; } } return FALSE; } //no more cover art in first version #if 0 void TranslateTempCoverToFinal(TCHAR* szCurrent, TCHAR* szFinal, long discid, TCHAR* extension) { //we want to put the cover art in a "coverart" subdir relative to whereever CD player is TCHAR szPath[MAX_PATH]; GetModuleFileName(NULL,szPath,sizeof(szPath)); TCHAR* szPathEnd; szPathEnd = _tcsrchr(szPath, TEXT('\\'))+sizeof(TCHAR); _tcscpy(szPathEnd,TEXT("coverart\\")); CreateDirectory(szPath,NULL); //create the coverart subdir wsprintf(szFinal,TEXT("%s%08X%s"),szPath,discid,extension); } #endif DWORD GetNextDisc(long lOriginal, LPCDBATCH* ppBatch) { DWORD discid = (DWORD)-1; //only do the batch if no discid was passed in originally to thread if (lOriginal < 1) { if (*ppBatch!=NULL) { *ppBatch = (*ppBatch)->pNext; if (*ppBatch != NULL) { discid = (*ppBatch)->dwTitleID; } } } return (discid); } LPCDPROVIDER GetNewProvider(LPCDPROVIDER pList, LPCDPROVIDER pCurrent, LPCDPROVIDER pDefault) { //find the next provider that isn't the current if (pCurrent == pDefault) { //we've just done the current provider, so go to the head of the list next pCurrent = pList; if (pCurrent == pDefault) { //if the default was also the head of the list, go to the next and return pCurrent = pCurrent->pNext; } return (pCurrent); } //get the next entry on the list pCurrent = pCurrent->pNext; //is the next entry the same as the default entry? if so, move on one more if (pCurrent == pDefault) { pCurrent = pCurrent->pNext; } return (pCurrent); } //if szProvider is NULL, szURL is filled in with "just the query" ... //if szProvider is not NULL, it is prepended to the query in szURL int GetTracksAndQuery(LPCDBATCH pBatch, TCHAR* szURL, TCHAR* szProvider) { if (pBatch == NULL) { return 0; } int nReturn = pBatch->dwNumTracks; if (szProvider != NULL) { wsprintf(szURL,TEXT("%s%s"),szProvider,pBatch->szTitleQuery); } else { _tcscpy(szURL,pBatch->szTitleQuery); } return nReturn; } void WINAPI AddTitleToDatabase(DWORD dwDiscID, DWORD dwTracks, TCHAR *szURL, TCHAR *szTempFile) { LPCDTITLE pCDTitle = NULL; TCHAR tempstr[CDSTR]; BOOL fContinue = TRUE; DWORD dwMenus = 0; while (fContinue) { TCHAR szMenuIndex[10]; TCHAR szMenuEntry[INTERNET_MAX_URL_LENGTH]; wsprintf(szMenuIndex,TEXT("MENU%i"),dwMenus+1); GetPrivateProfileString( TEXT("CD"), szMenuIndex, TEXT(""), szMenuEntry, sizeof(szMenuEntry)/sizeof(TCHAR), szTempFile ); if (_tcslen(szMenuEntry)>0) { dwMenus++; } else { fContinue = FALSE; } } if (SUCCEEDED(g_pNetData->CreateTitle(&pCDTitle, dwDiscID, dwTracks, dwMenus))) { GetPrivateProfileString(TEXT("CD"),TEXT("TITLE"),TEXT(""),tempstr,sizeof(tempstr)/sizeof(TCHAR),szTempFile); _tcscpy(pCDTitle->szTitle,tempstr); GetPrivateProfileString(TEXT("CD"),TEXT("ARTIST"),TEXT(""),tempstr,sizeof(tempstr)/sizeof(TCHAR),szTempFile); _tcscpy(pCDTitle->szArtist,tempstr); GetPrivateProfileString(TEXT("CD"),TEXT("LABEL"),TEXT(""),tempstr,sizeof(tempstr)/sizeof(TCHAR),szTempFile); _tcscpy(pCDTitle->szLabel,tempstr); GetPrivateProfileString(TEXT("CD"),TEXT("COPYRIGHT"),TEXT(""),tempstr,sizeof(tempstr)/sizeof(TCHAR),szTempFile); _tcscpy(pCDTitle->szCopyright,tempstr); GetPrivateProfileString(TEXT("CD"),TEXT("RELEASEDATE"),TEXT(""),tempstr,sizeof(tempstr)/sizeof(TCHAR),szTempFile); _tcscpy(pCDTitle->szDate,tempstr); g_pNetData->SetTitleQuery(pCDTitle, szURL); for (int i = 1; i < (int) dwTracks + 1; i++) { TCHAR tempstrtrack[10]; TCHAR tempstrtitle[CDSTR]; wsprintf(tempstrtrack,TEXT("TRACK%i"),i); GetPrivateProfileString(TEXT("CD"),tempstrtrack,TEXT(""),tempstrtitle,sizeof(tempstrtitle)/sizeof(TCHAR),szTempFile); if (_tcslen(tempstrtitle) == 0) { TCHAR strFormat[CDSTR]; LoadString(g_dllInst,IDS_STRING_DEFAULTTRACK,strFormat,sizeof(strFormat)/sizeof(TCHAR)); wsprintf(tempstrtitle,strFormat,i); } _tcscpy(pCDTitle->pTrackTable[i-1].szName,tempstrtitle); } for (i = 1; i < (int) (dwMenus + 1); i++) { TCHAR tempstrmenu[10]; TCHAR tempstrmenuvalue[CDSTR+INTERNET_MAX_URL_LENGTH+(3*sizeof(TCHAR))]; //3 = two colons and a terminating null wsprintf(tempstrmenu,TEXT("MENU%i"),i); GetPrivateProfileString(TEXT("CD"),tempstrmenu,TEXT(""),tempstrmenuvalue,sizeof(tempstrmenuvalue)/sizeof(TCHAR),szTempFile); //need to split menu into its component parts if (_tcslen(tempstrmenuvalue)!=0) { TCHAR* szNamePart; szNamePart = _tcsstr(tempstrmenuvalue,URL_SEPARATOR); TCHAR* szURLPart; szURLPart = _tcsstr(tempstrmenuvalue,URL_SEPARATOR); if (szURLPart!=NULL) { //need to move past two colons szURLPart = _tcsinc(szURLPart); szURLPart = _tcsinc(szURLPart); } if (szNamePart!=NULL) { *szNamePart = '\0'; } if (tempstrmenuvalue) { if (_tcslen(tempstrmenuvalue) >= sizeof(pCDTitle->pMenuTable[i-1].szMenuText)/sizeof(TCHAR)) { tempstrmenuvalue[sizeof(pCDTitle->pMenuTable[i-1].szMenuText)/sizeof(TCHAR) - 1] = TEXT('\0'); // Trunc string to max len } _tcscpy(pCDTitle->pMenuTable[i-1].szMenuText,tempstrmenuvalue); } if (szURLPart) { g_pNetData->SetMenuQuery(&(pCDTitle->pMenuTable[i-1]), szURLPart); } } } g_pNetData->UnlockTitle(pCDTitle,TRUE); //at this point, if the title is not in the database, we have a major problem if (!g_pNetData->QueryTitle(dwDiscID)) { g_fDBWriteFailure = TRUE; } else { g_fDBWriteFailure = FALSE; } } } BOOL IsCertifiedProvider(LPCDPROVIDER pProvider, TCHAR *szTempFile) { BOOL fCertified = TRUE; TCHAR szCert[MAX_PATH]; GetPrivateProfileString(TEXT("CD"),TEXT("CERTIFICATE"),TEXT(""),szCert,sizeof(szCert)/sizeof(TCHAR),szTempFile); fCertified = g_pNetOpt->VerifyProvider(pProvider,szCert); return(fCertified); } void UpdatePropertyPage(DWORD dwDiscID, BOOL fDownloading, HWND hwndParent) { if (g_pNetOpt) { LPCDUNIT pUnit = g_pNetOpt->GetCDOpts()->pCDUnitList; while (pUnit!=NULL) { if (pUnit->dwTitleID == dwDiscID) { pUnit->fDownLoading = fDownloading; PostMessage(hwndParent,WM_NET_DB_UPDATE_DISC,0,(LPARAM)pUnit); //Tell the UI we changed status of disc break; } pUnit = pUnit->pNext; } } } BOOL WINAPI DownloadBatch(LPCDBATCH pBatch, LPCDPROVIDER pProvider, LPDWORD pdwMultiHit, LPBOOL pfTimeout, HWND hwndParent) { BOOL fSuccess = FALSE; DWORD dwTracks; TCHAR szURL[INTERNET_MAX_URL_LENGTH]; TCHAR szTempFile[MAX_PATH]; DWORD dwDiscID; dwTracks = GetTracksAndQuery(pBatch, szURL, pProvider->szProviderURL); dwDiscID = pBatch->dwTitleID; *pfTimeout = FALSE; UpdatePropertyPage(pBatch->dwTitleID, TRUE, hwndParent); //tell prop page ui that disc is downloading if (dwTracks > 0 && dwDiscID != 0) { if (DoDownload(szURL,szTempFile,hwndParent)) { if (IsCertifiedProvider(pProvider, szTempFile)) { int nMode = GetPrivateProfileInt(TEXT("CD"),TEXT("MODE"),MODE_NOT_FOUND,szTempFile); if (nMode == MODE_NOT_FOUND) { DeleteFile(szTempFile); } else if (nMode == MODE_MULTIPLE) { if (pdwMultiHit) { (*pdwMultiHit)++; } if (!ResolveMultiples(szTempFile,TRUE,hwndParent)) { DeleteFile(szTempFile); } else { nMode = MODE_OK; } } if (nMode == MODE_OK) { GetTracksAndQuery(pBatch,szURL,NULL); //reset szURL to lose the provider AddTitleToDatabase(dwDiscID, dwTracks, szURL, szTempFile); DeleteFile(szTempFile); fSuccess = TRUE; } //end if mode ok } //end if certified provider } //end if download ok else { *pfTimeout = TRUE; } } //end if valid query UpdatePropertyPage(pBatch->dwTitleID, FALSE, hwndParent); //tell prop page ui that disc is no longer downloading return(fSuccess); } DWORD WINAPI SpawnSingleDownload(LPVOID pParam) { InterlockedIncrement((LONG*)&g_lNumDownloadingThreads); LPCDBATCH pBatch = (LPCDBATCH)pParam; HWND hwndParent = g_hwndParent; if (pBatch) { DoBatchDownload(pBatch,hwndParent); //if download failed, add to batch if not already in db //but only do this if batching is turned on LPCDOPTIONS pOptions = g_pNetOpt->GetCDOpts(); if (pOptions) { LPCDOPTDATA pOptionData = pOptions->pCDData; if (pOptionData) { if (pOptionData->fBatchEnabled) { if (!g_pNetData->QueryTitle(pBatch->dwTitleID)) { g_pNetData->AddToBatch(pBatch->dwTitleID, pBatch->szTitleQuery, pBatch->dwNumTracks); pOptions->dwBatchedTitles = g_pNetData->GetNumBatched(); PostMessage(hwndParent,WM_NET_DB_UPDATE_BATCH,0,0); //Tell the UI we changed number in batch } //end if not in db } //if batching is on } //end if option data } //end if poptions delete [] pBatch->szTitleQuery; delete pBatch; } //addref'ed before thread was created g_pNetOpt->Release(); g_pNetData->Release(); InterlockedDecrement((LONG*)&g_lNumDownloadingThreads); return 0; } DWORD WINAPI SpawnBatchDownload(LPVOID pParam) { InterlockedIncrement((LONG*)&g_lNumDownloadingThreads); LPCDBATCH pBatchList = NULL; HWND hwndParent = g_hwndParent; if (g_pNetData) { if (SUCCEEDED(g_pNetData->LoadBatch(NULL,&pBatchList))) { DoBatchDownload(pBatchList,hwndParent); g_pNetData->UnloadBatch(pBatchList); } } //addref'ed before thread was created g_pNetOpt->Release(); g_pNetData->Release(); InterlockedDecrement((LONG*)&g_lNumDownloadingThreads); return 0; } DWORD WINAPI DoBatchDownload(LPCDBATCH pBatchList, HWND hwndParent) { EnterCriticalSection(&g_BatchCrit); BOOL retcode = FALSE; DWORD dwHow; BOOL fConnected; DWORD dwCurrent = 0; DWORD dwOther = 0; DWORD dwMultiHit = 0; DWORD dwTimedOut = 0; fConnected = _InternetGetConnectedState(&dwHow,0,TRUE); // Make sure we are connected to net if (fConnected && g_pNetOpt && g_pNetData) // Make sure we are in a valid state { LPCDOPTIONS pOptions = g_pNetOpt->GetCDOpts(); // Get the options, needed for provider list if (pOptions && pOptions->pCurrentProvider) // Make sure we have providers { LPCDPROVIDER pProviderList = NULL; LPCDPROVIDER pProvider = NULL; g_pNetOpt->CreateProviderList(&pProviderList); // Get the sorted provider list pProvider = pProviderList; // Get the head of the list LPCDBATCH pBatch; if (pBatchList) { while (pProvider && !g_fCancelDownload) // loop thru providers, but check current first and only once. { BOOL fNotifiedUIProvider = FALSE; pBatch = pBatchList; while (pBatch && !g_fCancelDownload && !pProvider->fTimedOut) // We will loop thru each batched title { BOOL fAttemptDownload = TRUE; // Assume we are going to try to download all in batch if (pBatch->fRemove) { fAttemptDownload = FALSE; //we've already tried this disc on one provider and got it } if (fAttemptDownload) { if (!fNotifiedUIProvider) { PostMessage(hwndParent,WM_NET_CHANGEPROVIDER,0,(LPARAM)pProvider); //Tell the UI who the provider is fNotifiedUIProvider = TRUE; } BOOL fTimeout = FALSE; if (DownloadBatch(pBatch, pProvider, &dwMultiHit, &fTimeout, hwndParent)) // attempt to download this batch { pBatch->fRemove = TRUE; // This batch download succeeded, mark for termination from batch if (pProvider == pOptions->pCurrentProvider) { dwCurrent++; } else { dwOther++; } } else { pProvider->fTimedOut = fTimeout; } //check to see if db write failed if (g_fDBWriteFailure) { //let the UI know PostMessage(hwndParent,WM_NET_DB_FAILURE,0,0); //get out of the batch loop break; } //let ui know that something happened with this disc PostMessage(hwndParent,WM_NET_DONE,(WPARAM)g_dllInst,pBatch->dwTitleID); //increment the meter if we know this is the last time we're //visiting this particular disc ... either it was found, or //we are out of possible places to look if ((pBatch->fRemove) || (pProvider->pNext == NULL)) { PostMessage(hwndParent,WM_NET_INCMETER,(WPARAM)g_dllInst,pBatch->dwTitleID); } } //end attempt on disc pBatch = pBatch->pNext; } //end batch if (g_fDBWriteFailure) { //get out of the provider loop break; } pProvider = pProvider->pNext; //providers are "in order" } //end while cycling providers } //end if load batch OK //check to see if ALL providers timed out ... possible net problem BOOL fAllFailed = TRUE; pProvider = pProviderList; while (pProvider!=NULL) { if (!pProvider->fTimedOut) { fAllFailed = FALSE; break; } pProvider = pProvider->pNext; } if (fAllFailed) { //let the UI know PostMessage(hwndParent,WM_NET_NET_FAILURE,0,0); } g_pNetOpt->DestroyProviderList(&pProviderList); } //end if pointers ok #ifdef DBG // Ok, output some interesting stat's about what happened. { TCHAR str[255]; wsprintf(str, TEXT("current = %d, other = %d, multihits = %d\n"), dwCurrent, dwOther, dwMultiHit); OutputDebugString(str); } #endif } //end if connected to net and pointers ok if (!fConnected) { //may be a net problem if ((dwHow & (INTERNET_CONNECTION_MODEM|INTERNET_CONNECTION_LAN)) == 0) { PostMessage(hwndParent,WM_NET_NET_FAILURE,0,0); } } PostMessage(hwndParent,WM_NET_DONE,(WPARAM)g_dllInst,(LPARAM) 0); //fBadGuy ? -1 : 0); LeaveCriticalSection(&g_BatchCrit); return (retcode); }