|
|
//=======================================================================
//
// Copyright (c) 1998-1999 Microsoft Corporation. All Rights Reserved.
//
// File: download.cpp
//
// Owner: YanL
//
// Description:
//
// Internet download implementation
//
//=======================================================================
#include <windows.h>
#include <wininet.h>
#include <shlwapi.h>
#include <tchar.h>
#include <wustl.h>
#define LOGGING_LEVEL 2
#include <log.h>
#include <download.h>
static void InfiniteWaitForSingleObject(HANDLE hHandle);
CDownload::CDownload( IN OPTIONAL int cnMaxThreads /*= 1*/ ) : m_cnMaxThreads(min(cnMaxThreads, MAX_THREADS_LIMIT)), m_cnThreads(0) { LOG_block("CDownload::CDownload"); // syncronization
m_hEventExit = CreateEvent(NULL, /*bManualReset*/ TRUE, /*bInitialState*/ FALSE, NULL); m_hEventJob = CreateEvent(NULL, /*bManualReset*/ TRUE, /*bInitialState*/ FALSE, NULL); m_hEventDone = CreateEvent(NULL, /*bManualReset*/ FALSE, /*bInitialState*/ FALSE, NULL); m_hSession = InternetOpen(_T("Windows Update Catalog"), INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0); } CDownload::~CDownload( void ) { LOG_block("CDownload::~CDownload"); if (m_cnThreads) { // Wait for all threads to exit:
HANDLE ahThreads[MAX_THREADS_LIMIT]; for (int i = 0; i < m_cnThreads; i ++) ahThreads[i] = m_ahThreads[i]; SetEvent(m_hEventExit); WaitForMultipleObjects(m_cnThreads, ahThreads, /*bWaitAll*/TRUE, INFINITE); } // to insure that m_hConnection is released before m_hSession
m_hConnection.release(); m_hSession.release(); }
bool CDownload::Connect( IN LPCTSTR szURL ) { LOG_block("CDownload::Connect");
// check that constructor created everything we need
if ( !m_hEventExit.valid() || !m_hEventJob.valid() || !m_hEventDone.valid() || !m_hSession.valid() ) { return false; }
// check parameters
if (NULL == szURL) return false;
// Prepare to crack URL
URL_COMPONENTS url; ZeroMemory(&url, sizeof(url)); url.dwStructSize = sizeof(url);
TCHAR szServerName[INTERNET_MAX_HOST_NAME_LENGTH]; url.lpszHostName = szServerName; url.dwHostNameLength = sizeOfArray(szServerName);
m_szRootPath[0] = 0; url.lpszUrlPath = m_szRootPath; url.dwUrlPathLength = sizeOfArray(m_szRootPath);
TCHAR szUserName[INTERNET_MAX_USER_NAME_LENGTH]; url.lpszUserName = szUserName; url.dwUserNameLength = sizeOfArray(szUserName);
TCHAR szPassword[INTERNET_MAX_PASSWORD_LENGTH]; url.lpszPassword = szPassword; url.dwPasswordLength = sizeOfArray(szPassword);
if (!InternetCrackUrl(szURL, 0, 0, &url)) { LOG_error("InternetCrackUrl(%s) failed, last error %d", szURL, GetLastError()); return false; } if (url.nScheme != INTERNET_SCHEME_HTTP && url.nScheme != INTERNET_SCHEME_HTTPS) { LOG_error("http or https only"); return false; } if (_T('/') == m_szRootPath[lstrlen(m_szRootPath) - 1]) m_szRootPath[lstrlen(m_szRootPath) - 1] = 0; LOG_out("connecting to %s at %s", szServerName, m_szRootPath); m_hConnection = InternetConnect(m_hSession, szServerName, url.nPort, szUserName, szPassword, INTERNET_SERVICE_HTTP, 0, 1); return m_hConnection.valid(); }
bool CDownload::Copy(IN LPCTSTR szSourceFile, IN LPCTSTR szDestFile, IN OPTIONAL IDownloadAdviceSink* pSink /*= 0*/) { LOG_block("CDownload::Copy"); JOB job = { szSourceFile, szDestFile, 0, NO_ERROR, 0 }; CopyEx(&job, 1, pSink); SetLastError(job.dwResult); return NO_ERROR == job.dwResult || ERROR_ALREADY_EXISTS == job.dwResult ? true : false; }
bool CDownload::Copy(IN LPCTSTR szSourceFile, IN byte_buffer& bufDest, IN OPTIONAL IDownloadAdviceSink* pSink /*= 0*/) { LOG_block("CDownload::Copy"); JOB job = { szSourceFile, 0, &bufDest, NO_ERROR, 0 }; CopyEx(&job, 1, pSink); SetLastError(job.dwResult); return NO_ERROR == job.dwResult || ERROR_ALREADY_EXISTS == job.dwResult ? true : false; }
void CDownload::CopyEx(IN PJOB pJobs, IN int cnJobs, IN OPTIONAL IDownloadAdviceSink* pSink /*= 0*/, IN bool fWaitComplete /*= true*/) { LOG_block("CDownload::CopyEx");
// Init jobs queue
m_pJobsQueue = pJobs; m_cnJobsTotal = cnJobs; m_cnJobsToComplete = cnJobs; m_nJobTop = 0; // init status
m_pSink = pSink;
// Create more threads if we need to
int cnThreadsNeed = min(m_cnMaxThreads, cnJobs); while (m_cnThreads < cnThreadsNeed) { DWORD dwThreadId; m_ahThreads[m_cnThreads] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)StaticThreadProc, this, 0, &dwThreadId); if (!m_ahThreads[m_cnThreads].valid()) break; m_cnThreads ++; }
if (0 == m_cnThreads) // No threads to work on
{ // This case is only if we are in new device wizard on 98
DWORD dwError = GetLastError(); LOG_out("No thread to work on (last error = %d), will work on the main one", dwError);
// turn proxy autodetection off
INTERNET_PER_CONN_OPTION option = { INTERNET_PER_CONN_FLAGS, 0 }; INTERNET_PER_CONN_OPTION_LIST list = { sizeof(INTERNET_PER_CONN_OPTION_LIST), 0, 1, 0, &option }; DWORD dwBufLen = sizeof(list); BOOL fQueryOK = InternetQueryOption(NULL, INTERNET_OPTION_PER_CONNECTION_OPTION, &list, &dwBufLen); DWORD dwConnFlags = option.dwOption; if (fQueryOK) { option.dwOption &= ~PROXY_TYPE_AUTO_DETECT; InternetSetOption(NULL, INTERNET_OPTION_PER_CONNECTION_OPTION, &list, sizeof(list)); }
while (m_nJobTop < m_cnJobsTotal) { DoJob(m_nJobTop); m_nJobTop ++; }
// restore proxy autodetection
if (fQueryOK) { option.dwOption = dwConnFlags; InternetSetOption(NULL, INTERNET_OPTION_PER_CONNECTION_OPTION, &list, sizeof(list)); } } else { // Normal case
// Start
ResetEvent(m_hEventDone); SetEvent(m_hEventJob);
// Wait for all jobs to complete
if (fWaitComplete) { while(0 < m_cnJobsToComplete) InfiniteWaitForSingleObject(m_hEventDone); } } }
DWORD WINAPI CDownload::StaticThreadProc(IN LPVOID lpParameter) { // just call member function so we can access members
CDownload* pThis = static_cast<CDownload*>(lpParameter); pThis->ThreadProc(); return 0; }
void CDownload::ThreadProc() { LOG_block("CDownload::ThreadProc"); HANDLE ahevents[2] = { (HANDLE)m_hEventJob, (HANDLE)m_hEventExit }; while(true) { // wait for job or exit
DWORD dwRet = WaitForMultipleObjects(2, ahevents, FALSE, INFINITE); if (WAIT_OBJECT_0+1 == dwRet) { return; // called to exit
} else if (WAIT_OBJECT_0 == dwRet) { int nJob = m_lock.Increment(&m_nJobTop) - 1; if (nJob < m_cnJobsTotal) // Job is waiting
{ DoJob(nJob);
// Increment complete counter
m_lock.Decrement(&m_cnJobsToComplete); SetEvent(m_hEventDone); } else { ResetEvent(m_hEventJob); // queue is empty
} } } }
void CDownload::DoJob(int nJob) { LOG_block("CDownload::DoJob");
DWORD dwStartTime; if (NULL != m_pSink) { m_pSink->JobStarted(nJob);// Notify start
if (NO_ERROR != m_pJobsQueue[nJob].dwResult) // this will inforce abort on all jobs
{ LOG_error("Job result is set to %d", m_pJobsQueue[nJob].dwResult); m_pSink->JobDone(nJob); return; } if (m_pSink->WantToAbortJob(nJob)) // this will inforce abort on all jobs
{ LOG_error("Job %d aborted", nJob); m_pJobsQueue[nJob].dwResult = ERROR_OPERATION_ABORTED; m_pSink->JobDone(nJob); return; } dwStartTime = GetTickCount(); // for progress
} //Make Full path
TCHAR szFullSourcePath[MAX_PATH]; lstrcpy(szFullSourcePath, m_szRootPath); lstrcat(szFullSourcePath, _T("/")); lstrcat(szFullSourcePath, m_pJobsQueue[nJob].pszSrvFile); // Do one of two copy functions
if (NULL != m_pJobsQueue[nJob].pszDstFile) { m_pJobsQueue[nJob].dwResult = DoCopyToFile(nJob, szFullSourcePath, m_pJobsQueue[nJob].pszDstFile); // to file
} else { m_pJobsQueue[nJob].dwResult = DoCopyToBuf(nJob, szFullSourcePath, *(m_pJobsQueue[nJob].pDstBuf)); // to buffer
}
if (NULL != m_pSink) { if (NO_ERROR == m_pJobsQueue[nJob].dwResult) m_pSink->JobDownloadTime(nJob, GetTickCount() - dwStartTime); // only if success
m_pSink->JobDone(nJob); } }
DWORD CDownload::DoCopyToFile( IN int nJob, // for notifications
IN LPCTSTR szSourceFile, IN LPCTSTR szDestFile ) { LOG_block("CDownload::DoCopyToFile"); LOG_out("source %s, destination %s", szSourceFile, szDestFile);
// If destination file exist let see if we even need to download - compare it's date and size with server
FILETIME ftLocal = {0}; SYSTEMTIME stLocal = {0}; auto_hfile hFileOut = CreateFile(szDestFile, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFileOut.valid()) { GetFileTime(hFileOut, NULL, NULL, &ftLocal); FileTimeToSystemTime(&ftLocal, &stLocal); }
auto_hinet hInetFile; DWORD dwServerFileSize; SYSTEMTIME stServer; DWORD dwError = OpenInetFile(szSourceFile, hFileOut.valid() ? &stLocal: 0, hInetFile, &dwServerFileSize, &stServer); if (NO_ERROR != dwError) { if (ERROR_ALREADY_EXISTS != dwError) LOG_error("OpenInetFile failed %d", dwError); return dwError; }
if (NULL != m_pSink) m_pSink->JobDownloadSize(nJob, dwServerFileSize);// Notify start
// get server file time to compare with existing local and timestamp it if replaced
FILETIME ftServer = {0}; SystemTimeToFileTime(&stServer, &ftServer);
// If destination file exist let see if we even need to download - compare it's date and size with server
if (hFileOut.valid()) { //#ifdef _WUV3TEST
DWORD dwLocalFileSize = GetFileSize(hFileOut, NULL); LOG_out("'%s' \t server (%d bytes) %2d/%02d %2d:%02d:%02d:%03d \t local (%d bytes) %2d/%02d %2d:%02d:%02d:%03d", szSourceFile, dwServerFileSize, (int)stServer.wMonth, (int)stServer.wDay, (int)stServer.wHour, (int)stServer.wMinute, (int)stServer.wSecond, (int)stServer.wMilliseconds, dwLocalFileSize, (int)stLocal.wMonth, (int)stLocal.wDay, (int)stLocal.wHour, (int)stLocal.wMinute, (int)stLocal.wSecond, (int)stLocal.wMilliseconds ); //#endif
hFileOut.release(); }
// coping through 8K buffer
byte_buffer bufTmp; bufTmp.resize(min(dwServerFileSize, 8 * 1024)); // use 8K buffer
if (!bufTmp.valid()) { LOG_error("Out of memory"); return ERROR_NOT_ENOUGH_MEMORY; }
hFileOut = CreateFile(szDestFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); return_error_if_false(hFileOut.valid());
dwError = NO_ERROR; DWORD dwFileWritten = 0; while(dwFileWritten < dwServerFileSize) { DWORD dwReadStartTime; if (NULL != m_pSink) { if (m_pSink->WantToAbortJob(nJob)) { LOG_error("Job %d aborted", nJob); dwError = ERROR_OPERATION_ABORTED; break; } dwReadStartTime = GetTickCount(); // for progress
}
DWORD dwNeedToRead = dwServerFileSize - dwFileWritten; dwNeedToRead = min(dwNeedToRead, bufTmp.size());
DWORD dwReadSize; if (!InternetReadFile(hInetFile, bufTmp, dwNeedToRead, &dwReadSize)) { dwError = GetLastError(); LOG_error("InternetReadFile(hInetFile... failed %d", dwError); break; }
DWORD dwWritten; if (!WriteFile(hFileOut, bufTmp, dwReadSize, &dwWritten, NULL)) { dwError = GetLastError(); LOG_error("WriteFile(hFileOut... failed %d", dwError); break; } dwFileWritten += dwReadSize; if (NULL != m_pSink) m_pSink->BlockDownloaded(nJob, dwReadSize, GetTickCount() - dwReadStartTime); }
if(dwError) { hFileOut.release(); DeleteFile(szDestFile); // file is not complete
return dwError; }
// timestamp destination file
SetFileTime(hFileOut, NULL, NULL, &ftServer); return NO_ERROR; }
DWORD CDownload::DoCopyToBuf( IN int nJob, // for notifications
IN LPCTSTR szSourceFile, IN byte_buffer& bufDest ) { LOG_block("CDownload::DoCopyToBuf"); LOG_out("source %s", szSourceFile);
auto_hinet hInetFile; DWORD dwServerFileSize; DWORD dwError = OpenInetFile(szSourceFile, NULL, hInetFile, &dwServerFileSize, NULL); if (NO_ERROR != dwError) { if (ERROR_ALREADY_EXISTS != dwError) LOG_error("OpenInetFile failed %d", dwError); return dwError; }
if (NULL != m_pSink) m_pSink->JobDownloadSize(nJob, dwServerFileSize);// Notify start
bufDest.resize(dwServerFileSize); if (!bufDest.valid()) { LOG_error("Out of memory"); return ERROR_NOT_ENOUGH_MEMORY; } DWORD dwReadSize; return_error_if_false(InternetReadFile(hInetFile, bufDest, dwServerFileSize, &dwReadSize)); return NO_ERROR; }
// commont part for both downloads
DWORD CDownload::OpenInetFile( LPCTSTR szFullSourcePath, SYSTEMTIME* pstIfModifiedSince, auto_hinet& hInetFile, DWORD* pdwServerFileSize, SYSTEMTIME* pstServer ) { LOG_block("CDownload::OpenInetFile"); hInetFile = HttpOpenRequest(m_hConnection, NULL, szFullSourcePath, _T("HTTP/1.0"), NULL, NULL, INTERNET_FLAG_DONT_CACHE|INTERNET_FLAG_KEEP_CONNECTION, 1); return_error_if_false(hInetFile.valid());
if (pstIfModifiedSince) // local file exist co send If-Modified-Since: header
{ TCHAR szHttpTime[INTERNET_RFC1123_BUFSIZE + 1]; DWORD dwSize = sizeof(szHttpTime); return_error_if_false(InternetTimeFromSystemTime(pstIfModifiedSince, INTERNET_RFC1123_FORMAT, szHttpTime, dwSize));
//prepare and set the header
static const TCHAR szFormat[] = _T("If-Modified-Since: %s\r\n"); TCHAR szHeader[INTERNET_RFC1123_BUFSIZE + sizeOfArray(szFormat)]; wsprintf(szHeader, szFormat, szHttpTime); return_error_if_false(HttpAddRequestHeaders(hInetFile, szHeader, -1L, HTTP_ADDREQ_FLAG_ADD)); }
return_error_if_false(HttpSendRequest(hInetFile, NULL, 0, NULL, 0));
DWORD dwStatus; DWORD dwLength = sizeof(dwStatus); return_error_if_false(HttpQueryInfo(hInetFile, HTTP_QUERY_FLAG_NUMBER | HTTP_QUERY_STATUS_CODE, &dwStatus, &dwLength, NULL));
if (HTTP_STATUS_NOT_MODIFIED == dwStatus) { LOG_out("file '%s' is the same as on server", szFullSourcePath); return ERROR_ALREADY_EXISTS; } else if (HTTP_STATUS_OK != dwStatus) { LOG_error("file '%s' is not found", szFullSourcePath); return ERROR_FILE_NOT_FOUND; } dwLength = sizeof(DWORD); return_error_if_false(HttpQueryInfo(hInetFile, HTTP_QUERY_FLAG_NUMBER | HTTP_QUERY_CONTENT_LENGTH, pdwServerFileSize, &dwLength, NULL));
if (0 == *pdwServerFileSize) { // 0 length files are not supported
LOG_error("file '%s' is 0 length", szFullSourcePath); return ERROR_FILE_NOT_FOUND; }
if (pstServer) { dwLength = sizeof(SYSTEMTIME); return_error_if_false(HttpQueryInfo(hInetFile, HTTP_QUERY_FLAG_SYSTEMTIME | HTTP_QUERY_LAST_MODIFIED, pstServer, (ULONG *)&dwLength, NULL)); } return NO_ERROR; }
// wait forever until hHandle signals
static void InfiniteWaitForSingleObject( HANDLE hHandle ) { while (WAIT_OBJECT_0 + 1 == MsgWaitForMultipleObjects(1, &hHandle, FALSE, INFINITE, QS_ALLINPUT)) { // Process messasges
MSG msg; while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } } }
|