You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1763 lines
45 KiB
1763 lines
45 KiB
/************************************************************************
|
|
|
|
Copyright (c) Microsoft Corporation
|
|
|
|
Module Name :
|
|
|
|
bits_ie.cpp
|
|
|
|
Abstract :
|
|
|
|
Sample background downloader which uses BITS.
|
|
|
|
Revision History :
|
|
|
|
Notes:
|
|
|
|
This program is a very simple background downloader which demonstrates
|
|
the use of BITS. The program hooks into the IE context menu, to
|
|
allow the user to schedule the download instead of using the default
|
|
IE downloader.
|
|
|
|
Concepts Covered:
|
|
|
|
1. Basic connection with manager and job submission.
|
|
2. Example presentation to user of job state.
|
|
3. Job control such as suspend/resume/cancel/complete.
|
|
4. Interface based callbacks for updating progress/state.
|
|
5. How to get the text message for a BITS error code.
|
|
|
|
***********************************************************************/
|
|
|
|
#ifndef UNICODE
|
|
#define UNICODE
|
|
#endif
|
|
|
|
#ifndef _UNICODE
|
|
#define _UNICODE
|
|
#endif
|
|
|
|
#pragma warning( disable : 4786 )
|
|
|
|
#include <windows.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <malloc.h>
|
|
#include <math.h>
|
|
#include <float.h>
|
|
#include <commctrl.h>
|
|
#include <commdlg.h>
|
|
#include <shellapi.h>
|
|
#include <shlwapi.h>
|
|
#include <wininet.h>
|
|
#include <shlobj.h>
|
|
#include "resource.h"
|
|
#include <bits.h>
|
|
#include <comdef.h>
|
|
|
|
template<class T> class SmartRefPointer
|
|
{
|
|
private:
|
|
T * m_Interface;
|
|
|
|
void ReleaseIt()
|
|
{
|
|
if ( m_Interface )
|
|
m_Interface->Release();
|
|
m_Interface = NULL;
|
|
}
|
|
|
|
void RefIt()
|
|
{
|
|
if ( m_Interface )
|
|
m_Interface->AddRef();
|
|
}
|
|
|
|
public:
|
|
|
|
SmartRefPointer()
|
|
{
|
|
m_Interface = NULL;
|
|
}
|
|
|
|
SmartRefPointer( T * RawInterface )
|
|
{
|
|
m_Interface = RawInterface;
|
|
RefIt();
|
|
}
|
|
|
|
SmartRefPointer( SmartRefPointer & Other )
|
|
{
|
|
m_Interface = Other.m_Interface;
|
|
RefIt();
|
|
}
|
|
|
|
~SmartRefPointer()
|
|
{
|
|
ReleaseIt();
|
|
}
|
|
|
|
T * Get() const
|
|
{
|
|
return m_Interface;
|
|
}
|
|
|
|
T * Release()
|
|
{
|
|
T * temp = m_Interface;
|
|
m_Interface = NULL;
|
|
return temp;
|
|
}
|
|
|
|
void Clear()
|
|
{
|
|
ReleaseIt();
|
|
}
|
|
|
|
T** GetRecvPointer()
|
|
{
|
|
ReleaseIt();
|
|
return &m_Interface;
|
|
}
|
|
|
|
SmartRefPointer & operator=( SmartRefPointer & Other )
|
|
{
|
|
ReleaseIt();
|
|
m_Interface = Other.m_Interface;
|
|
RefIt();
|
|
return *this;
|
|
}
|
|
|
|
T* operator->() const
|
|
{
|
|
return m_Interface;
|
|
}
|
|
|
|
operator const T*() const
|
|
{
|
|
return m_Interface;
|
|
}
|
|
};
|
|
|
|
typedef SmartRefPointer<IUnknown> SmartIUnknownPointer;
|
|
typedef SmartRefPointer<IBackgroundCopyManager> SmartManagerPointer;
|
|
typedef SmartRefPointer<IBackgroundCopyJob> SmartJobPointer;
|
|
typedef SmartRefPointer<IBackgroundCopyJob2> SmartJob2Pointer;
|
|
typedef SmartRefPointer<IBackgroundCopyError> SmartJobErrorPointer;
|
|
typedef SmartRefPointer<IBackgroundCopyFile> SmartFilePointer;
|
|
typedef SmartRefPointer<IEnumBackgroundCopyFiles> SmartEnumFilesPointer;
|
|
typedef SmartRefPointer<IEnumBackgroundCopyJobs> SmartEnumJobsPointer;
|
|
typedef SmartRefPointer<IShellLink> SmartShellLinkPointer;
|
|
typedef SmartRefPointer<IPersistFile> SmartPersistFilePointer;
|
|
|
|
// Maxstring size, bump up on problems
|
|
#define MAX_STRING 0x800 // 2K
|
|
|
|
GUID g_JobId;
|
|
WCHAR g_szFileName[MAX_PATH];
|
|
HWND g_hwndDlg = NULL;
|
|
|
|
// These two global variables throttle updates.
|
|
// The algorithm is to set a timer on the first update request,
|
|
// and delay additional updates until after the timer expires.
|
|
|
|
// The timer is set
|
|
bool g_UpdateTimerSet = FALSE;
|
|
// Received on update request while timer is active
|
|
bool g_RefreshOnTimer = FALSE;
|
|
|
|
SmartJobPointer g_pJob;
|
|
SmartManagerPointer g_pManager;
|
|
|
|
void HandleUpdate( );
|
|
void CheckHR( HWND hwnd, HRESULT Hr, bool bThrow );
|
|
|
|
void SafeCopy( WCHAR * Dest, const WCHAR * Source, size_t Count )
|
|
{
|
|
if ( !Count )
|
|
return;
|
|
|
|
while (Count && (*Source != L'\0'))
|
|
{
|
|
*Dest++ = *Source++;
|
|
Count--;
|
|
}
|
|
|
|
if (Count == 0)
|
|
{
|
|
// we are going to truncate Dest
|
|
Dest--;
|
|
}
|
|
|
|
*Dest= L'\0';
|
|
}
|
|
|
|
void SafeCat( WCHAR * Dest, const WCHAR * Source, size_t Count )
|
|
{
|
|
size_t DestSize = wcslen( Dest );
|
|
|
|
if ( DestSize > Count )
|
|
return;
|
|
|
|
SafeCopy( Dest + DestSize,
|
|
Source,
|
|
Count - DestSize );
|
|
}
|
|
|
|
void SafeStringPrintf( WCHAR *Dest, DWORD Count, const WCHAR * Format, ... )
|
|
{
|
|
va_list arglist;
|
|
va_start( arglist, Format );
|
|
|
|
if ( !Count )
|
|
return;
|
|
|
|
int Ret;
|
|
size_t Max;
|
|
|
|
// leave the last space for the null terminator
|
|
Max = Count - 1;
|
|
|
|
Ret = _vsnwprintf( Dest, Max, Format, arglist);
|
|
|
|
if ((Ret < 0) || (((size_t)Ret) > Max))
|
|
{
|
|
// need to null terminate the string
|
|
Dest += Max;
|
|
*Dest = '\0';
|
|
}
|
|
else if (((size_t)Ret) == Max)
|
|
{
|
|
// need to null terminate the string
|
|
Dest += Max;
|
|
*Dest = '\0';
|
|
}
|
|
}
|
|
|
|
const WCHAR * GetString( UINT id )
|
|
{
|
|
|
|
//
|
|
// Retrieves the localized string for the resource id
|
|
// caching the string when loaded.
|
|
|
|
static const WCHAR* pStringArray[ IDS_MAX ];
|
|
static WCHAR TempStringBuffer[ MAX_STRING ];
|
|
const WCHAR * & pStringPointer = pStringArray[ id - 1 ];
|
|
|
|
// Cache resource strings
|
|
if ( pStringPointer )
|
|
return pStringPointer;
|
|
|
|
// Load string from resource
|
|
|
|
int CharsLoaded =
|
|
LoadStringW(
|
|
(HINSTANCE)GetModuleHandle(NULL),
|
|
id,
|
|
TempStringBuffer,
|
|
MAX_STRING );
|
|
|
|
if ( !CharsLoaded )
|
|
{
|
|
CheckHR( NULL, HRESULT_FROM_WIN32( GetLastError() ), false );
|
|
return L"";
|
|
}
|
|
|
|
WCHAR *pNewString = new WCHAR[ CharsLoaded + 1];
|
|
if ( !pNewString )
|
|
{
|
|
CheckHR( NULL, E_OUTOFMEMORY, false );
|
|
return L"";
|
|
}
|
|
|
|
SafeCopy( pNewString, TempStringBuffer, CharsLoaded + 1 );
|
|
return ( pStringPointer = pNewString );
|
|
|
|
}
|
|
|
|
void
|
|
DeleteStartupLink(
|
|
GUID JobID
|
|
)
|
|
{
|
|
|
|
//
|
|
// Delete the link in the Startup folder for the job
|
|
//
|
|
|
|
WCHAR szLinkFileName[MAX_PATH] = {0};
|
|
WCHAR szGUIDString[MAX_PATH] = {0};
|
|
|
|
BOOL bResult =
|
|
SHGetSpecialFolderPath(
|
|
NULL,
|
|
szLinkFileName,
|
|
CSIDL_STARTUP,
|
|
FALSE );
|
|
|
|
if ( !bResult )
|
|
return;
|
|
|
|
SafeCat( szLinkFileName, L"\\", MAX_PATH );
|
|
SafeCat( szLinkFileName, GetString( IDS_STARTUPLINK ), MAX_PATH );
|
|
SafeCat( szLinkFileName, L" ", MAX_PATH );
|
|
|
|
StringFromGUID2( JobID, szGUIDString, MAX_PATH );
|
|
SafeCat( szLinkFileName, szGUIDString, MAX_PATH );
|
|
SafeCat( szLinkFileName, L".lnk", MAX_PATH );
|
|
|
|
if (!DeleteFile( szLinkFileName ))
|
|
{
|
|
DWORD dwError = GetLastError();
|
|
if ( ERROR_PATH_NOT_FOUND != dwError &&
|
|
ERROR_FILE_NOT_FOUND != dwError )
|
|
{
|
|
CheckHR( NULL, HRESULT_FROM_WIN32( dwError ), false );
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void
|
|
CreateStartupLink(
|
|
GUID JobID,
|
|
WCHAR *pszFileName
|
|
)
|
|
{
|
|
//
|
|
// Create a link in the Startup folder for this job.
|
|
//
|
|
|
|
SmartShellLinkPointer ShellLink;
|
|
SmartPersistFilePointer PersistFile;
|
|
|
|
WCHAR szLinkFileName[MAX_PATH] = {0};
|
|
|
|
BOOL bResult =
|
|
SHGetSpecialFolderPath(
|
|
NULL,
|
|
szLinkFileName,
|
|
CSIDL_STARTUP,
|
|
FALSE );
|
|
|
|
if ( !bResult )
|
|
CheckHR( NULL, E_FAIL, true );
|
|
|
|
WCHAR szLinkDescription[MAX_PATH] = {0};
|
|
SafeCopy( szLinkDescription, GetString( IDS_STARTUPLINK ), MAX_PATH );
|
|
SafeCat( szLinkDescription, L" ", MAX_PATH );
|
|
|
|
WCHAR szGUIDString[MAX_PATH] = {0};
|
|
StringFromGUID2( JobID, szGUIDString, MAX_PATH );
|
|
SafeCat( szLinkDescription, szGUIDString, MAX_PATH );
|
|
|
|
WCHAR szArguments[MAX_PATH] = {0};
|
|
SafeCopy( szArguments, L"/RESUMEJOB ", MAX_PATH);
|
|
SafeCat( szArguments, szGUIDString, MAX_PATH );
|
|
SafeCat( szArguments, L" ", MAX_PATH );
|
|
SafeCat( szArguments, pszFileName, MAX_PATH );
|
|
|
|
SafeCat( szLinkFileName, L"\\", MAX_PATH );
|
|
SafeCat( szLinkFileName, szLinkDescription, MAX_PATH );
|
|
SafeCat( szLinkFileName, L".lnk", MAX_PATH );
|
|
|
|
CheckHR( NULL,
|
|
CoCreateInstance( CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
|
|
IID_IShellLink, (LPVOID*)ShellLink.GetRecvPointer() ),
|
|
true );
|
|
|
|
CheckHR( NULL, ShellLink->SetShowCmd( SW_SHOWMINIMIZED ), true );
|
|
CheckHR( NULL, ShellLink->QueryInterface( IID_IPersistFile, (LPVOID*)PersistFile.GetRecvPointer() ), true );
|
|
CheckHR( NULL, ShellLink->SetPath( L"%windir%\\system32\\bits_ie.exe" ), true );
|
|
CheckHR( NULL, ShellLink->SetArguments( szArguments ), true );
|
|
CheckHR( NULL, ShellLink->SetDescription( szLinkDescription ), true );
|
|
CheckHR( NULL, PersistFile->Save( szLinkFileName, TRUE ), true );
|
|
|
|
}
|
|
|
|
|
|
void SetWindowTime(
|
|
HWND hwnd,
|
|
FILETIME filetime
|
|
)
|
|
{
|
|
// Set the window text to be the text representation
|
|
// of the file time.
|
|
// If an error occurs, set the window text to be error
|
|
|
|
FILETIME localtime;
|
|
FileTimeToLocalFileTime( &filetime, &localtime );
|
|
|
|
SYSTEMTIME systemtime;
|
|
FileTimeToSystemTime( &localtime, &systemtime );
|
|
|
|
WCHAR DateBuffer[ MAX_STRING ];
|
|
|
|
int DateSize =
|
|
GetDateFormatW(
|
|
LOCALE_USER_DEFAULT,
|
|
0,
|
|
&systemtime,
|
|
NULL,
|
|
DateBuffer,
|
|
MAX_STRING );
|
|
|
|
if (!DateSize)
|
|
{
|
|
SetWindowText( hwnd, GetString( IDS_ERROR ) );
|
|
return;
|
|
}
|
|
|
|
WCHAR TimeBuffer[ MAX_STRING ];
|
|
|
|
int TimeSize =
|
|
GetTimeFormatW(
|
|
LOCALE_USER_DEFAULT,
|
|
0,
|
|
&systemtime,
|
|
NULL,
|
|
TimeBuffer,
|
|
MAX_STRING );
|
|
|
|
if (!TimeSize)
|
|
{
|
|
SetWindowText( hwnd, GetString( IDS_ERROR ) );
|
|
return;
|
|
}
|
|
|
|
WCHAR FullTime[ MAX_STRING ];
|
|
SafeStringPrintf( FullTime, MAX_STRING,
|
|
L"%s %s", DateBuffer, TimeBuffer );
|
|
|
|
SetWindowText( hwnd, FullTime );
|
|
}
|
|
|
|
UINT64
|
|
GetSystemTimeAsUINT64()
|
|
{
|
|
|
|
//
|
|
// Returns the system time as an UINT instead of a FILETIME.
|
|
//
|
|
|
|
FILETIME filetime;
|
|
GetSystemTimeAsFileTime( &filetime );
|
|
|
|
ULARGE_INTEGER large;
|
|
memcpy( &large, &filetime, sizeof(FILETIME) );
|
|
|
|
return large.QuadPart;
|
|
}
|
|
|
|
void SignalAlert(
|
|
HWND hwndDlg,
|
|
UINT Type
|
|
)
|
|
{
|
|
|
|
//
|
|
// Alert the user that an important event has occurred
|
|
//
|
|
|
|
FLASHWINFO FlashInfo;
|
|
FlashInfo.cbSize = sizeof(FlashInfo);
|
|
FlashInfo.hwnd = hwndDlg;
|
|
FlashInfo.dwFlags = FLASHW_ALL | FLASHW_TIMERNOFG;
|
|
FlashInfo.uCount = 0;
|
|
FlashInfo.dwTimeout = 0;
|
|
|
|
FlashWindowEx( &FlashInfo );
|
|
MessageBeep( Type );
|
|
|
|
}
|
|
|
|
const WCHAR *
|
|
MapStateToString(
|
|
BG_JOB_STATE state
|
|
)
|
|
{
|
|
|
|
//
|
|
// Maps a BITS job state to a human readable string
|
|
//
|
|
|
|
switch( state )
|
|
{
|
|
|
|
case BG_JOB_STATE_QUEUED:
|
|
return GetString( IDS_QUEUED );
|
|
|
|
case BG_JOB_STATE_CONNECTING:
|
|
return GetString( IDS_CONNECTING );
|
|
|
|
case BG_JOB_STATE_TRANSFERRING:
|
|
return GetString( IDS_TRANSFERRING );
|
|
|
|
case BG_JOB_STATE_SUSPENDED:
|
|
return GetString( IDS_SUSPENDED );
|
|
|
|
case BG_JOB_STATE_ERROR:
|
|
return GetString( IDS_FATALERROR );
|
|
|
|
case BG_JOB_STATE_TRANSIENT_ERROR:
|
|
return GetString( IDS_TRANSIENTERROR );
|
|
|
|
case BG_JOB_STATE_TRANSFERRED:
|
|
return GetString( IDS_TRANSFERRED );
|
|
|
|
case BG_JOB_STATE_ACKNOWLEDGED:
|
|
return GetString( IDS_ACKNOWLEDGED );
|
|
|
|
case BG_JOB_STATE_CANCELLED:
|
|
return GetString( IDS_CANCELLED );
|
|
|
|
default:
|
|
|
|
// NOTE: Always provide a default case
|
|
// since new states may be added in future versions.
|
|
return GetString( IDS_UNKNOWN );
|
|
|
|
}
|
|
}
|
|
|
|
double
|
|
ScaleDownloadRate(
|
|
double Rate, // rate in seconds
|
|
const WCHAR **pFormat )
|
|
{
|
|
|
|
//
|
|
// Scales a download rate and selects the correct
|
|
// format to pass to wprintf for printing.
|
|
//
|
|
|
|
double RateBounds[] =
|
|
{
|
|
1073741824.0, // Gigabyte
|
|
1048576.0, // Megabyte
|
|
1024.0, // Kilobyte
|
|
0 // Byte
|
|
};
|
|
|
|
UINT RateFormat[] =
|
|
{
|
|
IDS_GIGAFORMAT,
|
|
IDS_MEGAFORMAT,
|
|
IDS_KILOFORMAT,
|
|
IDS_BYTEFORMAT
|
|
};
|
|
|
|
for( unsigned int c = 0;; c++ )
|
|
{
|
|
if ( Rate >= RateBounds[c] )
|
|
{
|
|
*pFormat = GetString( RateFormat[c] );
|
|
double scale = (RateBounds[c] >= 1.0) ? RateBounds[c] : 1.0;
|
|
return Rate / scale;
|
|
}
|
|
}
|
|
}
|
|
|
|
UINT64
|
|
ScaleDownloadEstimate(
|
|
double Time, // time in seconds
|
|
const WCHAR **pFormat )
|
|
{
|
|
|
|
//
|
|
// Scales a download time estimate and selects the correct
|
|
// format to pass to wprintf for printing.
|
|
//
|
|
|
|
|
|
double TimeBounds[] =
|
|
{
|
|
60.0 * 60.0 * 24.0, // Days
|
|
60.0 * 60.0, // Hours
|
|
60.0, // Minutes
|
|
0.0 // Seconds
|
|
};
|
|
|
|
UINT TimeFormat[] =
|
|
{
|
|
IDS_DAYSFORMAT,
|
|
IDS_HOURSFORMAT,
|
|
IDS_MINUTESFORMAT,
|
|
IDS_SECONDSFORMAT
|
|
};
|
|
|
|
for( unsigned int c = 0;; c++ )
|
|
{
|
|
if ( Time >= TimeBounds[c] )
|
|
{
|
|
*pFormat = GetString( TimeFormat[c] );
|
|
double scale = (TimeBounds[c] >= 1.0) ? TimeBounds[c] : 1.0;
|
|
return (UINT64)floor( ( Time / scale ) + 0.5);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void
|
|
UpdateDialog(
|
|
HWND hwndDlg
|
|
)
|
|
{
|
|
|
|
//
|
|
// Main update routine for the dialog box.
|
|
// Retries the job state/properties from
|
|
// BITS and updates the dialog box.
|
|
//
|
|
|
|
{
|
|
// update the display name
|
|
|
|
HWND hwndDisplayName = GetDlgItem( hwndDlg, IDC_DISPLAYNAME );
|
|
WCHAR * pszDisplayName = NULL;
|
|
if (FAILED( g_pJob->GetDisplayName( &pszDisplayName ) ) )
|
|
return; // stop updating on an error
|
|
SetWindowText( hwndDisplayName, pszDisplayName );
|
|
ShowWindow( hwndDisplayName, SW_SHOW );
|
|
CoTaskMemFree( pszDisplayName );
|
|
|
|
}
|
|
|
|
static BG_JOB_STATE prevstate = BG_JOB_STATE_SUSPENDED;
|
|
BG_JOB_STATE state;
|
|
|
|
if (FAILED(g_pJob->GetState( &state )))
|
|
return; // stop updating on an error
|
|
|
|
if ( BG_JOB_STATE_ACKNOWLEDGED == state ||
|
|
BG_JOB_STATE_CANCELLED == state )
|
|
{
|
|
// someone else cancelled or completed the job on us,
|
|
// just exist the exit.
|
|
// May happen if job is canceled with bitsadmin
|
|
|
|
DeleteStartupLink( g_JobId );
|
|
PostQuitMessage( 0 );
|
|
return;
|
|
}
|
|
|
|
BG_JOB_PROGRESS progress;
|
|
if (FAILED(g_pJob->GetProgress( &progress )))
|
|
return; // stop updating on an error
|
|
|
|
{
|
|
// update the title, progress bar, and progress description
|
|
WCHAR szProgress[MAX_STRING];
|
|
WCHAR szTitle[MAX_STRING];
|
|
WPARAM newpos = 0;
|
|
|
|
if ( progress.BytesTotal &&
|
|
( progress.BytesTotal != BG_SIZE_UNKNOWN ) )
|
|
{
|
|
SafeStringPrintf(
|
|
szProgress, MAX_STRING, GetString( IDS_LONGPROGRESS ),
|
|
progress.BytesTransferred, progress.BytesTotal );
|
|
|
|
double Percent = (double)(__int64)progress.BytesTransferred /
|
|
(double)(__int64)progress.BytesTotal;
|
|
Percent *= 100.0;
|
|
SafeStringPrintf(
|
|
szTitle, MAX_STRING, L"%u%% %s",
|
|
(unsigned int)Percent, g_szFileName );
|
|
newpos = (WPARAM)Percent;
|
|
|
|
}
|
|
else
|
|
{
|
|
SafeStringPrintf(
|
|
szProgress, MAX_STRING, GetString( IDS_SHORTPROGRESS ),
|
|
progress.BytesTransferred );
|
|
SafeCopy( szTitle, g_szFileName, MAX_STRING );
|
|
newpos = 0;
|
|
}
|
|
|
|
SendDlgItemMessage( hwndDlg, IDC_PROGRESSBAR, PBM_SETPOS, newpos, 0 );
|
|
SetWindowText( GetDlgItem( hwndDlg, IDC_PROGRESSINFO ), szProgress );
|
|
ShowWindow( GetDlgItem( hwndDlg, IDC_PROGRESSINFO ), SW_SHOW );
|
|
EnableWindow( GetDlgItem( hwndDlg, IDC_PROGRESSINFOTXT ), TRUE );
|
|
SetWindowText( hwndDlg, szTitle );
|
|
|
|
}
|
|
|
|
{
|
|
// update the status
|
|
HWND hwndStatus = GetDlgItem( hwndDlg, IDC_STATUS );
|
|
|
|
SetWindowText( hwndStatus, MapStateToString( state ) );
|
|
ShowWindow( hwndStatus, SW_SHOW );
|
|
|
|
// Only enable the finish button if the job is finished.
|
|
EnableWindow( GetDlgItem( hwndDlg, IDC_FINISH ), ( state == BG_JOB_STATE_TRANSFERRED ) );
|
|
|
|
// Only enable the suspend button if the job is not finished or transferred
|
|
BOOL EnableSuspend =
|
|
( state != BG_JOB_STATE_SUSPENDED ) && ( state != BG_JOB_STATE_TRANSFERRED );
|
|
EnableWindow( GetDlgItem( hwndDlg, IDC_SUSPEND ), EnableSuspend );
|
|
|
|
// Only enable the resume button if the job is suspended
|
|
BOOL EnableResume = ( BG_JOB_STATE_SUSPENDED == state );
|
|
EnableWindow( GetDlgItem( hwndDlg, IDC_RESUME ), EnableResume );
|
|
|
|
// Alert the user when something important happens
|
|
// such as the job completes or a unrecoverable error occurs
|
|
if ( BG_JOB_STATE_TRANSFERRED == state &&
|
|
BG_JOB_STATE_TRANSFERRED != prevstate )
|
|
SignalAlert( hwndDlg, MB_OK );
|
|
|
|
else if ( BG_JOB_STATE_ERROR == state &&
|
|
BG_JOB_STATE_ERROR != prevstate )
|
|
SignalAlert( hwndDlg, MB_ICONEXCLAMATION );
|
|
|
|
}
|
|
|
|
{
|
|
// update times
|
|
BG_JOB_TIMES times;
|
|
if (FAILED(g_pJob->GetTimes( × )))
|
|
return;
|
|
|
|
HWND hwndCreationTime = GetDlgItem( hwndDlg, IDC_STARTTIME );
|
|
SetWindowTime( hwndCreationTime, times.CreationTime );
|
|
ShowWindow( hwndCreationTime, SW_SHOW );
|
|
|
|
HWND hwndModificationTime = GetDlgItem( hwndDlg, IDC_MODIFICATIONTIME );
|
|
SetWindowTime( hwndModificationTime, times.ModificationTime );
|
|
ShowWindow( hwndModificationTime, SW_SHOW );
|
|
|
|
HWND hwndCompletionTime = GetDlgItem( hwndDlg, IDC_COMPLETIONTIME );
|
|
if ( !times.TransferCompletionTime.dwLowDateTime && !times.TransferCompletionTime.dwHighDateTime )
|
|
{
|
|
|
|
// BITS sets the CompletionTime to all zeros
|
|
// if the job is incomplete
|
|
|
|
ShowWindow( hwndCompletionTime, SW_HIDE );
|
|
EnableWindow( GetDlgItem( hwndDlg, IDC_COMPLETIONTIMETXT ), FALSE );
|
|
}
|
|
else
|
|
{
|
|
SetWindowTime( hwndCompletionTime, times.TransferCompletionTime );
|
|
ShowWindow( hwndCompletionTime, SW_SHOW );
|
|
EnableWindow( GetDlgItem( hwndDlg, IDC_COMPLETIONTIMETXT ), TRUE );
|
|
}
|
|
}
|
|
|
|
{
|
|
// update the error message
|
|
IBackgroundCopyError *pError;
|
|
HRESULT Hr = g_pJob->GetError( &pError );
|
|
|
|
if ( FAILED(Hr) )
|
|
{
|
|
ShowWindow( GetDlgItem( hwndDlg, IDC_ERRORMSG ), SW_HIDE );
|
|
EnableWindow( GetDlgItem( hwndDlg, IDC_ERRORMSGTXT ), FALSE );
|
|
}
|
|
else
|
|
{
|
|
|
|
WCHAR* pszDescription = NULL;
|
|
WCHAR* pszContext = NULL;
|
|
|
|
// If these APIs fail, we should get back
|
|
// a NULL string. So everything should be harmless.
|
|
|
|
pError->GetErrorDescription(
|
|
LANGIDFROMLCID( GetThreadLocale() ),
|
|
&pszDescription );
|
|
pError->GetErrorContextDescription(
|
|
LANGIDFROMLCID( GetThreadLocale() ),
|
|
&pszContext );
|
|
|
|
WCHAR FullText[ MAX_STRING ];
|
|
FullText[0] = L'\0';
|
|
|
|
if ( pszDescription )
|
|
SafeCopy( FullText, pszDescription, MAX_STRING );
|
|
if ( pszContext )
|
|
SafeCat( FullText, pszContext, MAX_STRING );
|
|
CoTaskMemFree( pszDescription );
|
|
CoTaskMemFree( pszContext );
|
|
|
|
HWND hwndErrorText = GetDlgItem( hwndDlg, IDC_ERRORMSG );
|
|
SetWindowText( hwndErrorText, FullText );
|
|
ShowWindow( hwndErrorText, SW_SHOW );
|
|
EnableWindow( GetDlgItem( hwndDlg, IDC_ERRORMSGTXT ), TRUE );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!SendDlgItemMessage( hwndDlg, IDC_PRIORITY, CB_GETDROPPEDSTATE, 0, 0) )
|
|
{
|
|
// set the priority, but only do it if user isn't trying to
|
|
// set the priority.
|
|
BG_JOB_PRIORITY priority;
|
|
g_pJob->GetPriority( &priority );
|
|
SendDlgItemMessage( hwndDlg, IDC_PRIORITY, CB_SETCURSEL, (WPARAM)priority, 0 );
|
|
}
|
|
|
|
{
|
|
|
|
//
|
|
// This large block of text computes the average transfer rate
|
|
// and estimated completion time. This code has much
|
|
// room for improvement.
|
|
//
|
|
|
|
static BOOL HasRates = FALSE;
|
|
static UINT64 LastMeasurementTime;
|
|
static UINT64 LastMeasurementBytes;
|
|
static double LastMeasurementRate;
|
|
|
|
WCHAR szRateText[MAX_STRING];
|
|
BOOL EnableRate = FALSE;
|
|
|
|
if ( !( BG_JOB_STATE_QUEUED == state ) &&
|
|
!( BG_JOB_STATE_CONNECTING == state ) &&
|
|
!( BG_JOB_STATE_TRANSFERRING == state ) )
|
|
{
|
|
// If the job isn't running, then rate values won't
|
|
// make any sense. Don't display them.
|
|
HasRates = FALSE;
|
|
}
|
|
else
|
|
{
|
|
|
|
if ( !HasRates )
|
|
{
|
|
LastMeasurementTime = GetSystemTimeAsUINT64();
|
|
LastMeasurementBytes = progress.BytesTransferred;
|
|
LastMeasurementRate = 0;
|
|
HasRates = TRUE;
|
|
}
|
|
else
|
|
{
|
|
|
|
UINT64 CurrentTime = GetSystemTimeAsUINT64();
|
|
UINT64 NewTotalBytes = progress.BytesTransferred;
|
|
|
|
UINT64 NewTimeDiff = CurrentTime - LastMeasurementTime;
|
|
UINT64 NewBytesDiff = NewTotalBytes - LastMeasurementBytes;
|
|
double NewInstantRate = (double)(__int64)NewBytesDiff /
|
|
(double)(__int64)NewTimeDiff;
|
|
double NewAvgRate = (0.3 * LastMeasurementRate) +
|
|
(0.7 * NewInstantRate );
|
|
|
|
if ( !_finite(NewInstantRate) || !_finite(NewAvgRate) )
|
|
{
|
|
NewInstantRate = 0;
|
|
NewAvgRate = LastMeasurementRate;
|
|
}
|
|
|
|
LastMeasurementTime = CurrentTime;
|
|
LastMeasurementBytes = NewTotalBytes;
|
|
LastMeasurementRate = NewAvgRate;
|
|
|
|
// convert from FILETIME units to seconds
|
|
double NewDisplayRate = NewAvgRate * 10000000;
|
|
|
|
const WCHAR *pRateFormat = NULL;
|
|
double ScaledRate = ScaleDownloadRate( NewDisplayRate, &pRateFormat );
|
|
SafeStringPrintf( szRateText, MAX_STRING, pRateFormat, ScaledRate );
|
|
|
|
EnableRate = TRUE;
|
|
}
|
|
|
|
}
|
|
|
|
if (!EnableRate)
|
|
{
|
|
ShowWindow( GetDlgItem( hwndDlg, IDC_TRANSFERRATE ), SW_HIDE );
|
|
EnableWindow( GetDlgItem( hwndDlg, IDC_TRANSFERRATETXT ), FALSE );
|
|
}
|
|
else
|
|
{
|
|
SetWindowText( GetDlgItem( hwndDlg, IDC_TRANSFERRATE ), szRateText );
|
|
ShowWindow( GetDlgItem( hwndDlg, IDC_TRANSFERRATE ), SW_SHOW );
|
|
EnableWindow( GetDlgItem( hwndDlg, IDC_TRANSFERRATETXT ), TRUE );
|
|
}
|
|
|
|
BOOL EnableEstimate = FALSE;
|
|
WCHAR szEstimateText[MAX_STRING];
|
|
|
|
if ( EnableRate )
|
|
{
|
|
|
|
if ( progress.BytesTotal != 0 &&
|
|
progress.BytesTotal != BG_SIZE_UNKNOWN )
|
|
{
|
|
|
|
double TimeRemaining =
|
|
( (__int64)progress.BytesTotal - (__int64)LastMeasurementBytes ) / LastMeasurementRate;
|
|
|
|
// convert from FILETIME units to seconds
|
|
TimeRemaining = TimeRemaining / 10000000.0;
|
|
|
|
static const double SecsPer30Days = 60.0 * 60.0 * 24.0 * 30.0;
|
|
|
|
// Don't estimate if estimate is larger then 30 days.
|
|
if ( TimeRemaining < SecsPer30Days )
|
|
{
|
|
|
|
const WCHAR *pFormat = NULL;
|
|
UINT64 Time = ScaleDownloadEstimate( TimeRemaining, &pFormat );
|
|
SafeStringPrintf( szEstimateText, MAX_STRING, pFormat, Time );
|
|
EnableEstimate = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!EnableEstimate)
|
|
{
|
|
ShowWindow( GetDlgItem( hwndDlg, IDC_ESTIMATEDTIME ), SW_HIDE );
|
|
EnableWindow( GetDlgItem( hwndDlg, IDC_ESTIMATEDTIMETXT ), FALSE );
|
|
}
|
|
else
|
|
{
|
|
SetWindowText( GetDlgItem( hwndDlg, IDC_ESTIMATEDTIME ), szEstimateText );
|
|
ShowWindow( GetDlgItem( hwndDlg, IDC_ESTIMATEDTIME ), SW_SHOW );
|
|
EnableWindow( GetDlgItem( hwndDlg, IDC_ESTIMATEDTIMETXT ), TRUE );
|
|
}
|
|
|
|
}
|
|
|
|
prevstate = state;
|
|
}
|
|
|
|
void
|
|
InitDialog(
|
|
HWND hwndDlg
|
|
)
|
|
{
|
|
|
|
//
|
|
// Populate the priority list with priority descriptions
|
|
//
|
|
|
|
const WCHAR *Foreground = GetString( IDS_FOREGROUND );
|
|
const WCHAR *High = GetString( IDS_HIGH );
|
|
const WCHAR *Normal = GetString( IDS_NORMAL );
|
|
const WCHAR *Low = GetString( IDS_LOW );
|
|
|
|
SendDlgItemMessage( hwndDlg, IDC_PROGRESSBAR, PBM_SETRANGE, 0, MAKELPARAM(0, 100) );
|
|
SendDlgItemMessage( hwndDlg, IDC_PRIORITY, CB_ADDSTRING, 0, (LPARAM)Foreground );
|
|
SendDlgItemMessage( hwndDlg, IDC_PRIORITY, CB_ADDSTRING, 0, (LPARAM)High );
|
|
SendDlgItemMessage( hwndDlg, IDC_PRIORITY, CB_ADDSTRING, 0, (LPARAM)Normal );
|
|
SendDlgItemMessage( hwndDlg, IDC_PRIORITY, CB_ADDSTRING, 0, (LPARAM)Low );
|
|
|
|
}
|
|
|
|
void CheckHR( HWND hwnd, HRESULT Hr, bool bThrow )
|
|
{
|
|
//
|
|
// Provides automatic error code checking and dialog
|
|
// for generic system errors
|
|
//
|
|
|
|
if (SUCCEEDED(Hr))
|
|
return;
|
|
|
|
WCHAR * pszError = NULL;
|
|
|
|
DWORD dwFormatError =
|
|
FormatMessage(
|
|
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
|
|
FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
NULL,
|
|
(DWORD)Hr,
|
|
LANGIDFROMLCID( GetThreadLocale() ),
|
|
(WCHAR*)&pszError,
|
|
0,
|
|
NULL );
|
|
|
|
if ( !dwFormatError )
|
|
{
|
|
WCHAR ErrorMsg[ MAX_STRING ];
|
|
SafeStringPrintf( ErrorMsg, MAX_STRING, GetString( IDS_DISPLAYERRORCODE ), Hr );
|
|
|
|
MessageBox( hwnd, ErrorMsg, GetString( IDS_ERRORBOXTITLE ),
|
|
MB_OK | MB_ICONSTOP | MB_APPLMODAL );
|
|
}
|
|
else
|
|
{
|
|
MessageBox( hwnd, pszError, GetString( IDS_ERRORBOXTITLE ),
|
|
MB_OK | MB_ICONSTOP | MB_APPLMODAL );
|
|
LocalFree( pszError );
|
|
}
|
|
|
|
if ( bThrow )
|
|
throw _com_error( Hr );
|
|
|
|
}
|
|
|
|
void BITSCheckHR( HWND hwnd, HRESULT Hr, bool bThrow )
|
|
{
|
|
|
|
//
|
|
// Provides automatic error code checking and dialog
|
|
// for BITS specific errors
|
|
//
|
|
|
|
|
|
if (SUCCEEDED(Hr))
|
|
return;
|
|
|
|
WCHAR * pszError = NULL;
|
|
HRESULT hErrorHr =
|
|
g_pManager->GetErrorDescription(
|
|
Hr,
|
|
LANGIDFROMLCID( GetThreadLocale() ),
|
|
&pszError );
|
|
|
|
if ( FAILED(hErrorHr) || !pszError )
|
|
{
|
|
|
|
WCHAR ErrorMsg[ MAX_STRING ];
|
|
SafeStringPrintf( ErrorMsg, MAX_STRING, GetString( IDS_DISPLAYERRORCODE ), Hr );
|
|
|
|
MessageBox( hwnd, ErrorMsg, GetString( IDS_ERRORBOXTITLE ),
|
|
MB_OK | MB_ICONSTOP | MB_APPLMODAL );
|
|
|
|
}
|
|
|
|
else
|
|
{
|
|
|
|
MessageBox( hwnd, pszError, GetString( IDS_ERRORBOXTITLE ),
|
|
MB_OK | MB_ICONSTOP | MB_APPLMODAL );
|
|
CoTaskMemFree( pszError );
|
|
|
|
}
|
|
|
|
|
|
if ( bThrow )
|
|
throw _com_error( Hr );
|
|
}
|
|
|
|
void
|
|
DoCancel(
|
|
HWND hwndDlg,
|
|
bool PromptUser
|
|
)
|
|
{
|
|
|
|
//
|
|
// Handle all the operations required to cancel the job.
|
|
// This includes asking the user for confirmation.
|
|
//
|
|
|
|
if ( PromptUser )
|
|
{
|
|
|
|
int Result =
|
|
MessageBox(
|
|
hwndDlg,
|
|
GetString( IDS_CANCELTEXT ),
|
|
GetString( IDS_CANCELCAPTION ),
|
|
MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON2 | MB_APPLMODAL |
|
|
MB_SETFOREGROUND | MB_TOPMOST );
|
|
|
|
|
|
if ( IDYES != Result )
|
|
return;
|
|
|
|
}
|
|
|
|
try
|
|
{
|
|
BITSCheckHR( hwndDlg, g_pJob->Cancel(), true );
|
|
}
|
|
catch( _com_error Error )
|
|
{
|
|
// If we can't cancel for some unknown reason,
|
|
// don't exit
|
|
return;
|
|
}
|
|
|
|
DeleteStartupLink( g_JobId );
|
|
PostQuitMessage( 0 );
|
|
}
|
|
|
|
void
|
|
DoFinish(
|
|
HWND hwndDlg
|
|
)
|
|
{
|
|
|
|
//
|
|
// Handles all the necessary work to complete
|
|
// the download.
|
|
//
|
|
|
|
try
|
|
{
|
|
BITSCheckHR( hwndDlg, g_pJob->Complete(), true );
|
|
}
|
|
catch( _com_error Error )
|
|
{
|
|
// If we can't finish/complete for some unknown reason,
|
|
// don't exit
|
|
return;
|
|
}
|
|
|
|
DeleteStartupLink( g_JobId );
|
|
PostQuitMessage( 0 );
|
|
|
|
}
|
|
|
|
void
|
|
DoClose(
|
|
HWND hwndDlg
|
|
)
|
|
{
|
|
//
|
|
// Handles an attempt by the user to close the sample.
|
|
//
|
|
|
|
// Check to see if the download has finished,
|
|
// if so don't let the user exit.
|
|
|
|
BG_JOB_STATE state;
|
|
HRESULT hResult = g_pJob->GetState( &state );
|
|
|
|
if (FAILED( hResult ))
|
|
{
|
|
BITSCheckHR( hwndDlg, hResult, false );
|
|
return;
|
|
}
|
|
|
|
if ( BG_JOB_STATE_ERROR == state ||
|
|
BG_JOB_STATE_TRANSFERRED == state )
|
|
{
|
|
|
|
MessageBox(
|
|
hwndDlg,
|
|
GetString( IDS_ALREADYFINISHED ),
|
|
GetString( IDS_ALREADYFINISHEDCAPTION ),
|
|
MB_OK | MB_ICONERROR | MB_DEFBUTTON1 | MB_APPLMODAL |
|
|
MB_SETFOREGROUND | MB_TOPMOST );
|
|
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Inform the user that he selected close and ask
|
|
// confirm the intention to exit. Explain that the job
|
|
// will be canceled.
|
|
|
|
int Result =
|
|
MessageBox(
|
|
hwndDlg,
|
|
GetString( IDS_CLOSETEXT ),
|
|
GetString( IDS_CLOSECAPTION ),
|
|
MB_OKCANCEL | MB_ICONWARNING | MB_DEFBUTTON2 | MB_APPLMODAL |
|
|
MB_SETFOREGROUND | MB_TOPMOST );
|
|
|
|
if ( IDOK == Result )
|
|
{
|
|
|
|
// User confirmed the cancel, just do it.
|
|
|
|
DoCancel( hwndDlg, false );
|
|
return;
|
|
}
|
|
|
|
// The user didn't really want to exit, so ignore him
|
|
else
|
|
return;
|
|
|
|
}
|
|
|
|
void
|
|
HandleTimerTick( HWND hwndDlg )
|
|
{
|
|
|
|
//
|
|
// Handle the throttling timer event
|
|
// and update the dialog if needed
|
|
//
|
|
|
|
if ( g_RefreshOnTimer )
|
|
{
|
|
// The timer fired, handle all updates at once.
|
|
UpdateDialog( hwndDlg );
|
|
g_RefreshOnTimer = FALSE;
|
|
}
|
|
else
|
|
{
|
|
// The timer expired with an additional modification
|
|
// notification. Just kill the timer.
|
|
KillTimer( hwndDlg, 0 );
|
|
g_RefreshOnTimer = g_UpdateTimerSet = FALSE;
|
|
}
|
|
|
|
}
|
|
|
|
void
|
|
HandleUpdate()
|
|
{
|
|
|
|
//
|
|
// Handle a update request, batching the update if needed
|
|
//
|
|
|
|
if ( !g_UpdateTimerSet )
|
|
{
|
|
// We aren't currently batching updates,
|
|
// so do this one update but prevent
|
|
// further updates until the timer expires.
|
|
SetTimer( g_hwndDlg, 0, 1000, NULL );
|
|
g_UpdateTimerSet = TRUE;
|
|
UpdateDialog( g_hwndDlg );
|
|
}
|
|
else
|
|
{
|
|
// We've started batching and yet received
|
|
// another update request. Delay this
|
|
// update until the timer fires.
|
|
g_RefreshOnTimer = TRUE;
|
|
}
|
|
|
|
}
|
|
|
|
INT_PTR CALLBACK
|
|
DialogProc(
|
|
HWND hwndDlg, // handle to dialog box
|
|
UINT uMsg, // message
|
|
WPARAM wParam, // first message parameter
|
|
LPARAM lParam // second message parameter
|
|
)
|
|
{
|
|
|
|
//
|
|
// Dialog proc for main dialog window
|
|
//
|
|
|
|
switch( uMsg )
|
|
{
|
|
|
|
case WM_INITDIALOG:
|
|
g_hwndDlg = hwndDlg;
|
|
InitDialog( hwndDlg );
|
|
return TRUE;
|
|
|
|
case WM_TIMER:
|
|
HandleTimerTick( hwndDlg );
|
|
return TRUE;
|
|
|
|
case WM_CLOSE:
|
|
DoClose( hwndDlg );
|
|
return TRUE;
|
|
|
|
case WM_COMMAND:
|
|
|
|
switch( LOWORD( wParam ) )
|
|
{
|
|
|
|
case IDC_RESUME:
|
|
BITSCheckHR( hwndDlg, g_pJob->Resume(), false );
|
|
return TRUE;
|
|
|
|
case IDC_SUSPEND:
|
|
BITSCheckHR( hwndDlg, g_pJob->Suspend(), false );
|
|
return TRUE;
|
|
|
|
case IDC_CANCEL:
|
|
DoCancel( hwndDlg, true );
|
|
return TRUE;
|
|
|
|
case IDC_FINISH:
|
|
DoFinish( hwndDlg );
|
|
return TRUE;
|
|
|
|
case IDC_PRIORITY:
|
|
switch( HIWORD( wParam ) )
|
|
{
|
|
|
|
case CBN_SELENDOK:
|
|
|
|
// User clicked on priority,
|
|
// update it.
|
|
|
|
BITSCheckHR( hwndDlg,
|
|
g_pJob->SetPriority( (BG_JOB_PRIORITY)
|
|
SendDlgItemMessage( hwndDlg, IDC_PRIORITY, CB_GETCURSEL, 0, 0 ) ), false );
|
|
return TRUE;
|
|
|
|
case CBN_SELENDCANCEL:
|
|
return TRUE;
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
default:
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
HRESULT
|
|
HandleCOMCallback(
|
|
IBackgroundCopyJob* pJob,
|
|
bool CriticalEvent
|
|
);
|
|
|
|
class CBackgroundCopyCallback : public IBackgroundCopyCallback
|
|
{
|
|
|
|
//
|
|
// Callback class. Used for change notifications.
|
|
//
|
|
|
|
public:
|
|
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject)
|
|
{
|
|
|
|
if ( riid == _uuidof(IUnknown) )
|
|
{
|
|
*ppvObject = (IUnknown*)(IBackgroundCopyCallback*)this;
|
|
return S_OK;
|
|
}
|
|
|
|
else if ( riid == _uuidof(IBackgroundCopyCallback) )
|
|
{
|
|
*ppvObject = (IBackgroundCopyCallback*)this;
|
|
return S_OK;
|
|
}
|
|
|
|
else
|
|
return E_NOINTERFACE;
|
|
|
|
}
|
|
|
|
virtual HRESULT STDMETHODCALLTYPE CreateInstance(
|
|
IUnknown *pUnkOuter,
|
|
REFIID riid,
|
|
void **ppvObject )
|
|
{
|
|
|
|
if ( pUnkOuter )
|
|
return CLASS_E_NOAGGREGATION;
|
|
|
|
return QueryInterface( riid, ppvObject );
|
|
|
|
}
|
|
|
|
// We are cheating on COM here, but we
|
|
// are forcing the lifetime of the callback object
|
|
// to be the same of the lifetime of the exe.
|
|
|
|
virtual ULONG STDMETHODCALLTYPE AddRef(void)
|
|
{
|
|
return 0;
|
|
}
|
|
virtual ULONG STDMETHODCALLTYPE Release(void)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
virtual HRESULT STDMETHODCALLTYPE JobTransferred(IBackgroundCopyJob *pJob)
|
|
{
|
|
return HandleCOMCallback( pJob, true );
|
|
}
|
|
|
|
virtual HRESULT STDMETHODCALLTYPE JobError(IBackgroundCopyJob *pJob, IBackgroundCopyError *pError)
|
|
{
|
|
return HandleCOMCallback( pJob, true );
|
|
}
|
|
|
|
virtual HRESULT STDMETHODCALLTYPE JobModification( IBackgroundCopyJob *pJob, DWORD dwReserved )
|
|
{
|
|
return HandleCOMCallback( pJob, true );
|
|
}
|
|
} g_Callback;
|
|
|
|
HRESULT
|
|
HandleCOMCallback(
|
|
IBackgroundCopyJob* pJob,
|
|
bool CriticalEvent
|
|
)
|
|
{
|
|
|
|
// In addition to the work of HandleUpdate,
|
|
// this function checks to see if we've
|
|
// already initialized the manager. If not,
|
|
// do it now.
|
|
|
|
if ( !g_pManager )
|
|
{
|
|
|
|
try
|
|
{
|
|
CheckHR( NULL,
|
|
CoCreateInstance( CLSID_BackgroundCopyManager,
|
|
NULL,
|
|
CLSCTX_LOCAL_SERVER,
|
|
IID_IBackgroundCopyManager,
|
|
(void**)g_pManager.GetRecvPointer() ), true );
|
|
|
|
*g_pJob.GetRecvPointer() = pJob;
|
|
|
|
BITSCheckHR( NULL, g_pJob->SetNotifyFlags( BG_NOTIFY_JOB_MODIFICATION ), true );
|
|
|
|
// As an optimization, set the notification interface to be the callback
|
|
// It shouldn't matter if this fails
|
|
g_pJob->SetNotifyInterface( (IBackgroundCopyCallback*)&g_Callback );
|
|
|
|
HandleUpdate();
|
|
|
|
ShowWindow( g_hwndDlg, CriticalEvent ? SW_NORMAL : SW_MINIMIZE );
|
|
|
|
}
|
|
catch(_com_error error )
|
|
{
|
|
g_pManager.Release();
|
|
g_pJob.Release();
|
|
|
|
return error.Error();
|
|
}
|
|
|
|
}
|
|
|
|
HandleUpdate();
|
|
return S_OK;
|
|
}
|
|
|
|
void
|
|
CreateUI( int nShowCmd )
|
|
{
|
|
|
|
//
|
|
// Creates the dialog box for the sample.
|
|
//
|
|
|
|
g_hwndDlg =
|
|
CreateDialog(
|
|
(HINSTANCE)GetModuleHandle(NULL),
|
|
MAKEINTRESOURCE( IDD_DIALOG ),
|
|
GetDesktopWindow(),
|
|
DialogProc );
|
|
|
|
if ( !g_hwndDlg )
|
|
CheckHR( NULL, HRESULT_FROM_WIN32(GetLastError()), true );
|
|
|
|
ShowWindow( g_hwndDlg, nShowCmd );
|
|
}
|
|
|
|
void CreateJob(
|
|
WCHAR* szJobURL
|
|
)
|
|
{
|
|
//
|
|
// Request a destination file name from the user
|
|
// and submit a new job.
|
|
//
|
|
|
|
try
|
|
{
|
|
|
|
// crack the URL and get the filename
|
|
WCHAR szURLFilePath[MAX_PATH] = {L'\0'};
|
|
URL_COMPONENTS UrlComponents;
|
|
|
|
memset( &UrlComponents, 0, sizeof(UrlComponents) );
|
|
UrlComponents.dwStructSize = sizeof(URL_COMPONENTS);
|
|
UrlComponents.lpszUrlPath = szURLFilePath;
|
|
UrlComponents.dwUrlPathLength =
|
|
sizeof(szURLFilePath)/sizeof(*szURLFilePath);
|
|
|
|
BOOL CrackResult =
|
|
InternetCrackUrl(
|
|
szJobURL,
|
|
0,
|
|
0,
|
|
&UrlComponents );
|
|
|
|
if (!CrackResult)
|
|
CheckHR( NULL, HRESULT_FROM_WIN32( GetLastError() ), false );
|
|
|
|
if ( UrlComponents.nScheme != INTERNET_SCHEME_HTTP &&
|
|
UrlComponents.nScheme != INTERNET_SCHEME_HTTPS
|
|
)
|
|
{
|
|
|
|
MessageBox(
|
|
NULL,
|
|
GetString( IDS_NOHTTPORHTTPS ),
|
|
GetString( IDS_ERRORBOXTITLE ),
|
|
MB_OK | MB_ICONERROR | MB_APPLMODAL |
|
|
MB_SETFOREGROUND | MB_TOPMOST );
|
|
|
|
|
|
throw _com_error( E_INVALIDARG );
|
|
|
|
}
|
|
|
|
const WCHAR *szURLFileName =
|
|
szURLFilePath + wcslen( szURLFilePath );
|
|
|
|
// parse out the filename part of the URL
|
|
while( szURLFileName != szURLFilePath )
|
|
{
|
|
|
|
if ( L'/' == *szURLFileName ||
|
|
L'\\' == *szURLFileName )
|
|
{
|
|
szURLFileName++;
|
|
break;
|
|
}
|
|
|
|
szURLFileName--;
|
|
}
|
|
|
|
// This is needed in case the first
|
|
// character is a slash.
|
|
if ( L'/' == *szURLFileName ||
|
|
L'\\' == *szURLFileName )
|
|
szURLFileName++;
|
|
|
|
// parse out the extension from the name
|
|
const WCHAR *szURLFileExtension =
|
|
szURLFileName + wcslen( szURLFileName );
|
|
|
|
while( szURLFileName != szURLFileExtension )
|
|
{
|
|
if ( L'.' == *szURLFileExtension )
|
|
break;
|
|
szURLFileExtension--;
|
|
}
|
|
|
|
// build the extension list
|
|
|
|
WCHAR *szExtensionList = NULL;
|
|
const WCHAR *szAllFiles = GetString( IDS_ALLFILES );
|
|
const size_t AllFilesSize = wcslen( szAllFiles ) + 1;
|
|
const WCHAR *szAllFilesPattern = L"*";
|
|
const size_t AllFilesPatternSize = sizeof(L"*")/sizeof(WCHAR);
|
|
|
|
WCHAR *p;
|
|
|
|
if ( szURLFileExtension == szURLFileName &&
|
|
*szURLFileExtension != L'.' )
|
|
{
|
|
size_t StringSize = sizeof(WCHAR) * ( AllFilesSize + AllFilesPatternSize + 2 );
|
|
szExtensionList = (WCHAR*)_alloca( StringSize );
|
|
p = szExtensionList;
|
|
}
|
|
else
|
|
{
|
|
size_t ExtensionSize = wcslen( szURLFileExtension ) + 1;
|
|
size_t StringSize =
|
|
sizeof(WCHAR) * ( ExtensionSize + ExtensionSize + 1 + AllFilesSize
|
|
+ AllFilesPatternSize + 2 );
|
|
szExtensionList = (WCHAR*)_alloca( StringSize );
|
|
p = szExtensionList;
|
|
|
|
memcpy( p, szURLFileExtension, ExtensionSize * sizeof(WCHAR) );
|
|
p += ExtensionSize;
|
|
*p++ = L'*';
|
|
memcpy( p, szURLFileExtension, ExtensionSize * sizeof(WCHAR) );
|
|
p += ExtensionSize;
|
|
}
|
|
|
|
memcpy( p, szAllFiles, AllFilesSize * sizeof(WCHAR) );
|
|
p += AllFilesSize;
|
|
memcpy( p, szAllFilesPattern, AllFilesPatternSize * sizeof(WCHAR) );
|
|
p += AllFilesPatternSize;
|
|
memset( p, 0, sizeof(WCHAR) * 2 );
|
|
|
|
|
|
OPENFILENAME ofn;
|
|
memset( &ofn, 0, sizeof( ofn ) );
|
|
|
|
WCHAR szFileName[MAX_PATH];
|
|
WCHAR szFileTitle[MAX_PATH];
|
|
|
|
SafeCopy( szFileName, szURLFileName, MAX_PATH );
|
|
SafeCopy( szFileTitle, szURLFileName, MAX_PATH );
|
|
|
|
/* fill in non-variant fields of OPENFILENAME struct. */
|
|
ofn.lStructSize = sizeof(OPENFILENAME);
|
|
ofn.hwndOwner = g_hwndDlg;
|
|
ofn.lpstrFilter = szExtensionList;
|
|
ofn.lpstrCustomFilter = NULL;
|
|
ofn.nMaxCustFilter = 0;
|
|
ofn.nFilterIndex = 0;
|
|
ofn.lpstrFile = szFileName;
|
|
ofn.nMaxFile = MAX_PATH;
|
|
ofn.lpstrInitialDir = NULL;
|
|
ofn.lpstrFileTitle = szFileTitle;
|
|
ofn.nMaxFileTitle = MAX_PATH;
|
|
ofn.lpstrTitle = GetString( IDS_FILEDLGTITLE );
|
|
ofn.lpstrDefExt = NULL;
|
|
ofn.Flags = 0;
|
|
|
|
/* Use standard open dialog */
|
|
BOOL bResult = GetSaveFileName ((LPOPENFILENAME)&ofn);
|
|
|
|
if ( !bResult )
|
|
{
|
|
if ( !CommDlgExtendedError() )
|
|
{
|
|
// user canceled the box
|
|
PostQuitMessage( 0 );
|
|
return;
|
|
}
|
|
else
|
|
CheckHR( NULL, HRESULT_FROM_WIN32( GetLastError() ), true );
|
|
}
|
|
|
|
|
|
SafeCopy( g_szFileName, szFileTitle, MAX_STRING );
|
|
|
|
CheckHR( NULL,
|
|
CoCreateInstance( CLSID_BackgroundCopyManager,
|
|
NULL,
|
|
CLSCTX_LOCAL_SERVER,
|
|
IID_IBackgroundCopyManager,
|
|
(void**)g_pManager.GetRecvPointer() ), true );
|
|
|
|
GUID guid;
|
|
BITSCheckHR( NULL,
|
|
g_pManager->CreateJob( szJobURL,
|
|
BG_JOB_TYPE_DOWNLOAD,
|
|
&guid,
|
|
g_pJob.GetRecvPointer() ),
|
|
true );
|
|
|
|
memset( &g_JobId, 0, sizeof(GUID) );
|
|
BITSCheckHR( NULL, g_pJob->GetId( &g_JobId ), true );
|
|
BITSCheckHR( NULL, g_pJob->AddFile( szJobURL, szFileName ), true );
|
|
|
|
CreateStartupLink( g_JobId, g_szFileName );
|
|
|
|
BITSCheckHR( NULL, g_pJob->SetNotifyFlags( BG_NOTIFY_JOB_MODIFICATION ), true );
|
|
|
|
BITSCheckHR( NULL,
|
|
g_pJob->SetNotifyInterface( (IBackgroundCopyCallback*)&g_Callback ),
|
|
true );
|
|
BITSCheckHR( NULL, g_pJob->Resume(), true );
|
|
|
|
HandleUpdate();
|
|
|
|
}
|
|
catch( const _com_error &error )
|
|
{
|
|
if ( g_pJob )
|
|
{
|
|
g_pJob->Cancel();
|
|
DeleteStartupLink( g_JobId );
|
|
}
|
|
|
|
throw error;
|
|
}
|
|
|
|
}
|
|
|
|
void ResumeJob(
|
|
WCHAR* szJobGUID,
|
|
WCHAR* szJobFileName
|
|
)
|
|
{
|
|
|
|
//
|
|
// Resume the display on an existing job
|
|
//
|
|
|
|
SafeCopy( g_szFileName, szJobFileName, MAX_PATH );
|
|
CheckHR( NULL, IIDFromString( szJobGUID, &g_JobId ), true );
|
|
|
|
CheckHR( NULL,
|
|
CoCreateInstance( CLSID_BackgroundCopyManager,
|
|
NULL,
|
|
CLSCTX_LOCAL_SERVER,
|
|
IID_IBackgroundCopyManager,
|
|
(void**)g_pManager.GetRecvPointer() ), true );
|
|
|
|
BITSCheckHR( NULL, g_pManager->GetJob( g_JobId, g_pJob.GetRecvPointer() ), true );
|
|
BITSCheckHR( NULL,
|
|
g_pJob->SetNotifyInterface( (IBackgroundCopyCallback*)&g_Callback ),
|
|
true );
|
|
BITSCheckHR( NULL, g_pJob->SetNotifyFlags( BG_NOTIFY_JOB_MODIFICATION ), true );
|
|
|
|
ShowWindow( g_hwndDlg, SW_MINIMIZE );
|
|
HandleUpdate();
|
|
|
|
}
|
|
|
|
int WINAPI WinMain(
|
|
HINSTANCE hInstance, // handle to current instance
|
|
HINSTANCE hPrevInstance, // handle to previous instance
|
|
LPSTR lpCmdLine, // command line
|
|
int nCmdShow) // show state
|
|
{
|
|
|
|
//
|
|
// Expected syntax:
|
|
// bits_ie /CREATEJOB URL
|
|
// bits_ie /RESUMEJOB JobGUID DestinationFile
|
|
|
|
// /CREATEJOB - Called from the script which is run when
|
|
// "Background Download As" is selected.
|
|
// /RESUMEJOB - Called from the link in the startup directory
|
|
// to resume a job when it is restarted.
|
|
|
|
try
|
|
{
|
|
CheckHR( NULL, CoInitialize(NULL), true );
|
|
|
|
InitCommonControls();
|
|
|
|
CreateUI( nCmdShow );
|
|
|
|
LPTSTR lpCommandLine = GetCommandLine();
|
|
|
|
int argc;
|
|
WCHAR **argv =
|
|
CommandLineToArgvW(
|
|
lpCommandLine,
|
|
&argc );
|
|
|
|
if ( argc < 2 )
|
|
CheckHR( NULL, E_INVALIDARG, true );
|
|
|
|
if ( argc == 3 &&
|
|
_wcsicmp( L"/CREATEJOB", argv[1] ) == 0 )
|
|
CreateJob( argv[2] );
|
|
|
|
else if ( argc == 4 &&
|
|
_wcsicmp( L"/RESUMEJOB", argv[1] ) == 0 )
|
|
ResumeJob( argv[2], argv[3] );
|
|
|
|
else
|
|
CheckHR( NULL, E_INVALIDARG, true );
|
|
|
|
MSG msg;
|
|
while( GetMessage( &msg, NULL, 0, 0 ) )
|
|
{
|
|
TranslateMessage( &msg );
|
|
DispatchMessage( &msg );
|
|
}
|
|
|
|
}
|
|
catch(_com_error error )
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|