www.pudn.com > NetPaw.rar > DownloadFile.cpp
#include "StdAfx.h"
#include "NetPaw.h"
#include "mainfrm.h"
#include "netpawview.h"
#include ".\downloadfile.h"
// 构造及析构函数
CDownloadFile::CDownloadFile(DLFILEITEM_S *pstDlFileIt)
: m_nConnections(pstDlFileIt->wBlocksToDo)
, m_nStoppedSockets(pstDlFileIt->wBlocksToDo)
, m_nFileLength(pstDlFileIt->nFileLength)
, m_nBytesReceived(pstDlFileIt->nBytesDone)
, m_sDownloadUrl(pstDlFileIt->szUrl)
, m_sReferer(pstDlFileIt->szReferer)
, m_sWebInfo(pstDlFileIt->szWebInfo)
, m_dUsedTime(pstDlFileIt->dUsedTime)
, m_sFileName(_T(""))
, m_hFrameWnd(NULL)
, m_dwRecvBytesOld(0)
, m_bStopDownld(TRUE) // default is stop
, m_bRestart(FALSE)
{
// create a sniffer socket to trace file length
m_pSnifferSock = new CHttpSocket(this);
// set download parameter
SEGMENT_S stSeg;
ZeroMemory( &stSeg, sizeof(SEGMENT_S) );
m_pSnifferSock->NewSegment( &stSeg );
}
CDownloadFile::~CDownloadFile(void)
{
Destroy();
}
// 类方法实现
void CDownloadFile::AddSocket(void)
{
try
{
CHttpSocket *pSocket = new CHttpSocket(this);
if( pSocket->Connect() )
{
CSingleLock sl(&m_csDataAccess);
sl.Lock();
m_nConnections++;
m_lstSocket.push_back(pSocket);
}
else
{
delete pSocket;
}
}
catch(...)
{
AtlTrace("CDownloadFile::AddSocket Error.\n");
}
}
void CDownloadFile::DeleteSocket(CHttpSocket *pSocket)
{
// lock the data
m_csDataAccess.Lock();
// decrease connections
m_nConnections--;
// get the last element
m_lstSocket.remove(pSocket);
// unlock data
m_csDataAccess.Unlock();
// delete the object
delete pSocket;
}
// because several threads write this file concurrently,
// each thread should be allocated a segment of the file to download.
// nOffset is the offset of a segment
void CDownloadFile::Write(char *pBuffer, DWORD dwBytes, LONGLONG nOffset)
{
ASSERT( m_hFile != CFile::hFileNull );
// before write the file, lock it
CSingleLock sl(&m_csFileAccess);
sl.Lock();
// now write the data
Seek(nOffset, CFile::begin);
// write the data
CFile::Write(pBuffer, dwBytes);
}
void CDownloadFile::Destroy(void)
{
list::iterator itor;
CHttpSocket *pSocket;
// we can delete sniffer socket now
DelSniffSocket();
// lock the data
m_csDataAccess.Lock();
// delete all the downloading sockets
for(itor = m_lstSocket.begin(); itor != m_lstSocket.end(); itor++)
{
pSocket = *itor;
delete pSocket;
}
m_lstSocket.clear();
// unlock here
m_csDataAccess.Unlock();
// close the file
CloseFile();
// for debug use
AtlTrace("DownloadFile closed and destroyed\n");
}
// downloading sockets begin to connect server
void CDownloadFile::MassConnect()
{
list::iterator itor;
CHttpSocket *pSocket;
// lock the data
CSingleLock sl(&m_csDataAccess);
sl.Lock();
for( itor = m_lstSocket.begin(); itor != m_lstSocket.end(); itor++ )
{
pSocket = *itor;
pSocket->m_sHostName = m_pSnifferSock->m_sHostName;
pSocket->m_sFileObj = m_pSnifferSock->m_sFileObj;
pSocket->m_nPort = m_pSnifferSock->m_nPort;
// should create the socket before connect
if( !pSocket->Connect() )
{
AtlTrace("Start connection failed\n");
}
}
}
void CDownloadFile::NotifyConnected(CHttpSocket *pSocket)
{
if( (SOCKET)pSocket == GetSnifferSock() )
{
// start time counter
m_obTimer.Start();
// set status bar information
NotifyStatus(STATUS_CONNECTED);
}
}
// a file data block is arrived, not including header
void CDownloadFile::NotifyRcvData(CHttpSocket *pSocket, char *pBuffer, DWORD dwBytesRcvd)
{
DWORD dwBytesWrite;
LONGLONG nOffset;
// lock the socket
pSocket->m_csSockAccess.Lock();
// get the file-writing parameters
dwBytesWrite = (DWORD)min((LONGLONG)dwBytesRcvd, pSocket->m_nDataToRead);
nOffset = pSocket->m_nFileOffset;
// move forward file pointer
pSocket->m_nFileOffset += dwBytesWrite;
pSocket->m_nDataToRead -= dwBytesWrite;
// set segment data
SEGMENT_S *pstSeg;
if( !pSocket->m_lstSegments.empty() )
{
pstSeg = pSocket->m_lstSegments.back();
pstSeg->nBytesDone += dwBytesWrite;
}
// unlock the socket
pSocket->m_csSockAccess.Unlock();
// write the buffer to the file
Write(pBuffer, dwBytesWrite, nOffset);
// increase received file data
m_csDataAccess.Lock();
m_nBytesReceived += dwBytesWrite;
m_csDataAccess.Unlock();
}
void CDownloadFile::NotifyFileLen(LONGLONG nFileLen)
{
AtlTrace("Notify FileLen\n");
// lock the data
m_csDataAccess.Lock();
// total file length
m_nFileLength = nFileLen;
m_nStoppedSockets = 0;
if( !m_bRestart )
{
m_nBytesReceived = 0;
}
m_dwRecvBytesOld = m_nBytesReceived;
m_csDataAccess.Unlock();
// assign task to sockets
if( !m_bRestart )
{
// Notify file length is available
NotifyStatus( STATUS_ADDFILE, this );
AssignTask();
}
// begin to download file
MassConnect();
// we can close sniffer socket now
m_pSnifferSock->Close();
}
// a socket has just finished its task
void CDownloadFile::NotifyFinished(CHttpSocket* pSocket)
{
// re-assign task to this socket
if( !ReAssignTask(pSocket) )
{
m_csDataAccess.Lock();
m_nStoppedSockets++;
m_csDataAccess.Unlock();
// for debug use
AtlTrace("Socket: %08X finished work.\n", pSocket);
}
else
{
AtlTrace( "Socket: %08X is re-assigned work.\n", pSocket );
}
// the whole file is done, close the file and stop all sockets
if( IsStopped() )
{
m_dUsedTime += m_obTimer.Stop();
// close the file
CloseFile();
if( IsFinished() )
{
AtlTrace( "All sockets have finished work.\n" );
NotifyStatus( STATUS_FINISHED, this );
}
else
{
// just stop the connection
CloseSockets();
AtlTrace( "All sockets are stopped.\n" );
NotifyStatus( STATUS_STOPDOWNLD );
}
}
}
BOOL CDownloadFile::ReAssignTask(CHttpSocket* pSocket)
{
LONGLONG nNewData;
SEGMENT_S *pstSeg, stNewSeg;
// if it is shutdown, don't assign task
if( IsStopDownld() )
{
return FALSE;
}
// if one socket doesn't finish its work by itself,
// it has no ability to help others (system limit?)
pSocket->m_csSockAccess.Lock();
if( pSocket->m_nFileOffset != (pSocket->m_nRequestTo + 1) )
{
pSocket->m_csSockAccess.Unlock();
return FALSE;
}
pSocket->m_csSockAccess.Unlock();
// get the first heavy task socket
CHttpSocket *pSlowSocket = GetSlowSocket();
if( !pSlowSocket )
{
return FALSE;
}
AtlTrace("Slow socket: %08X has: %I64d bytes left.\n", pSlowSocket, pSlowSocket->m_nDataToRead );
// lock slow socket
pSlowSocket->m_csSockAccess.Lock();
// recalculate the download length,
if( pSlowSocket->IsActive() )
{
// don't let slow socket exit too early
nNewData = pSlowSocket->m_nDataToRead * 2 / 3;
}
else
{
// the slow socket is dead, it can do nothing
nNewData = 0;
}
// for new joined socket
stNewSeg.nDataFrom = pSlowSocket->m_nFileOffset + nNewData;
stNewSeg.nDataTo = pSlowSocket->m_nFileOffset + pSlowSocket->m_nDataToRead - 1;
stNewSeg.nBytesDone = 0;
// adjust the parameter of slow socket
pSlowSocket->m_nDataToRead = nNewData;
if( !pSlowSocket->m_lstSegments.empty() )
{
pstSeg = pSlowSocket->m_lstSegments.back();
// readjust slow socket right bound
pstSeg->nDataTo = stNewSeg.nDataFrom - 1;
}
pSlowSocket->m_csSockAccess.Unlock();
// now allocate a download range block
pSocket->NewSegment(&stNewSeg);
// although this socket is aready connected, but its work is
// done, we should re-connect for some server, otherwise it doesn't work:)
if( !pSocket->Connect() )
{
return FALSE;
}
//pSocket->SendHttpRequest();
return TRUE;
}
void CDownloadFile::GetPerfData(PERFITEM_S* pstPerfIT)
{
// should zero the structure at first
ZeroMemory( pstPerfIT, sizeof(PERFITEM_S) );
// lock the data
m_csDataAccess.Lock();
// get data of download file
pstPerfIT->nFileLength = m_nFileLength;
pstPerfIT->nConnects = m_nConnections - m_nStoppedSockets;
pstPerfIT->fSpeed = (float)( (double)(m_nBytesReceived - m_dwRecvBytesOld) * 1000 / (TIMER_INTERVAL * 1024) );
if( m_nFileLength > 0 )
{
pstPerfIT->fRatio = (float)( (double)m_nBytesReceived / (double)m_nFileLength * 100.0 );
}
// get average speed
if( IsStopped() )
pstPerfIT->dUsedTime = m_dUsedTime;
else
pstPerfIT->dUsedTime = m_obTimer.Stop() + m_dUsedTime;
if( pstPerfIT->dUsedTime > 0 )
{
pstPerfIT->fMeanSpeed = (float)( (double)m_nBytesReceived / (pstPerfIT->dUsedTime * 1024) );
}
// save for the next time
m_dwRecvBytesOld = m_nBytesReceived;
m_csDataAccess.Unlock();
// get file name from full path name
CString sFileName = GetFileName();
StrCopy(pstPerfIT->szFileName, sFileName, MAX_PATH/2);
}
CHttpSocket* CDownloadFile::GetSlowSocket(void)
{
list::iterator itor;
CHttpSocket *pSlowSocket = NULL, *pSocket;
LONGLONG nMaxLeft = 0;
// lock the data
CSingleLock sl(&m_csDataAccess);
sl.Lock();
// loop to find first slow socket
for( itor = m_lstSocket.begin(); itor != m_lstSocket.end(); itor++ )
{
pSocket = *itor;
// lock the socket
CSingleLock sl(&pSocket->m_csSockAccess);
sl.Lock();
// find a slow socket, join it
if( pSocket->m_nDataToRead > nMaxLeft )
{
nMaxLeft = pSocket->m_nDataToRead;
pSlowSocket = pSocket;
}
}
// if block is too small, skip splitting
if( nMaxLeft < 2 * MAX_BUFF_SIZE )
{
return NULL;
}
return pSlowSocket;
}
void CDownloadFile::DrawDownldFile(CDC* pDC, CRect& rectDC, CNetPawView *pView)
{
list::iterator itor;
CHttpSocket *pSocket;
list::iterator itor2;
SEGMENT_S *pstSeg;
int nFromCell, nToCell, nDoneCells;
// loop all downloading sockets
CSingleLock sl(&m_csDataAccess);
sl.Lock();
for( itor = m_lstSocket.begin(); itor != m_lstSocket.end(); itor++ )
{
pSocket = *itor;
if( !pSocket )
{
continue;
}
// draw all segments of this socket done
pSocket->m_csSockAccess.Lock();
for( itor2 = pSocket->m_lstSegments.begin(); itor2 != pSocket->m_lstSegments.end(); itor2++ )
{
pstSeg = *itor2;
nFromCell = (int)( pstSeg->nDataFrom / 1024 );
nToCell = (int)( pstSeg->nDataTo / 1024 );
nDoneCells = (int)( (pstSeg->nBytesDone + 1023) / 1024 );
pView->DrawOneSegment(pDC, rectDC, nFromCell, nToCell, nDoneCells);
}
pSocket->m_csSockAccess.Unlock();
}
}
void CDownloadFile::Stop(void)
{
m_csDataAccess.Lock();
m_bStopDownld = TRUE;
m_csDataAccess.Unlock();
}
void CDownloadFile::CloseSockets(void)
{
list::iterator itor;
CHttpSocket *pSocket;
m_csDataAccess.Lock();
// stop all the downloading sockets
for(itor = m_lstSocket.begin(); itor != m_lstSocket.end(); itor++)
{
pSocket = *itor;
pSocket->Close();
}
m_csDataAccess.Unlock();
}
BOOL CDownloadFile::IsStopDownld(void)
{
BOOL bStop;
// lock the data
m_csDataAccess.Lock();
bStop = m_bStopDownld;
m_csDataAccess.Unlock();
return bStop;
}
SOCKET CDownloadFile::GetSnifferSock(void)
{
return (SOCKET)m_pSnifferSock;
}
BOOL CDownloadFile::IsStopped(void)
{
BOOL bStopped = FALSE;
m_csDataAccess.Lock();
if( m_nStoppedSockets >= (long)m_lstSocket.size() )
{
bStopped = TRUE;
}
m_csDataAccess.Unlock();
return bStopped;
}
void CDownloadFile::Restart(HWND hMainWnd)
{
m_csDataAccess.Lock();
m_bRestart = TRUE;
m_csDataAccess.Unlock();
// start to connect server
Start(hMainWnd);
}
BOOL CDownloadFile::ParseFileName()
{
// save the parameter at first
if( !m_pSnifferSock->ParseUrl(m_sDownloadUrl) )
{
return FALSE;
}
// get download file name
CString sFileName = m_pSnifferSock->m_sFileObj;
// get rid of remote folder
int nIndex = sFileName.ReverseFind('/');
if( nIndex != -1 )
{
sFileName = sFileName.Mid(nIndex + 1);
}
// get rid of log in parameters
nIndex = sFileName.Find('&');
if( nIndex != -1 )
{
sFileName = sFileName.Left(nIndex);
}
if( sFileName.IsEmpty() )
{
return FALSE;
}
m_sFileName = sFileName;
return TRUE;
}
void CDownloadFile::NewSocket(SEGMENT_S *pstSeg)
{
CHttpSocket *pSocket;
pSocket = new CHttpSocket(this);
// set download parameter
pSocket->NewSegment( pstSeg );
// lock the list
m_csDataAccess.Lock();
m_lstSocket.push_back(pSocket);
m_csDataAccess.Unlock();
}
void CDownloadFile::AssignTask(void)
{
int nConnects;
// lock the data
m_csDataAccess.Lock();
nConnects = m_nConnections;
m_csDataAccess.Unlock();
// many sockets download a very short file is avoided
LONGLONG nMeanLen;
while( (nMeanLen = m_nFileLength / nConnects) < MAX_BUFF_SIZE )
{
if( nConnects <= 1 )
{
break;
}
nConnects /= 2;
}
// lock the data
m_csDataAccess.Lock();
m_nConnections = nConnects;
m_csDataAccess.Unlock();
// split the file and assign task for each socket
LONGLONG nSegLength;
LONGLONG nOffset = 0;
SEGMENT_S stBlock;
for( int i = 0; i < nConnects; i++ )
{
nSegLength = nMeanLen;
if( i == 0 )
{
// this a little larger block assigned to the first socket
nSegLength += m_nFileLength % nConnects;
}
// assign file block to each socket
stBlock.nDataFrom = nOffset;
stBlock.nDataTo = nOffset + nSegLength - 1;
stBlock.nBytesDone = 0;
// create a socket to finish the task
NewSocket( &stBlock );
nOffset += nSegLength;
}
}
void CDownloadFile::Start(HWND hMainWnd)
{
// open a file to receive data
if( m_hFile == CFile::hFileNull )
{
CString sPathName;
// local file name, use .npf as suffix
CNetPawApp *pApp = (CNetPawApp *)AfxGetApp();
if( pApp )
{
sPathName = pApp->m_sSavePath + '\\' + m_sFileName;
sPathName += _T(".npf");
}
// open a file to receive data
if( !Open( sPathName, CFile::modeCreate | CFile::modeWrite | CFile::modeNoTruncate | CFile::shareExclusive ) )
{
NotifyStatus(STATUS_OPENFILEFAILED);
return;
}
}
// lock the data
m_csDataAccess.Lock();
// clear stop flag
m_bStopDownld = FALSE;
if( hMainWnd )
{
m_hFrameWnd = hMainWnd;
}
m_csDataAccess.Unlock();
// now connect the server
if( !m_pSnifferSock->Connect() )
{
DelSniffSocket();
NotifyStatus( STATUS_CONNECTFAIL );
return;
}
// set status bar information
NotifyStatus( STATUS_CONNECTING );
}
BOOL CDownloadFile::IsFinished(void)
{
BOOL bFinished = FALSE;
m_csDataAccess.Lock();
if( m_nBytesReceived >= m_nFileLength )
{
bFinished = TRUE;
}
m_csDataAccess.Unlock();
return bFinished;
}
void CDownloadFile::SaveDlFileData(CFile *pFile)
{
int nLefts = 0;
// get unfinished blocks
list lstUndone;
nLefts = GetLeftSegments(lstUndone);
// download file structure
DLFILEITEM_S stDlFile;
ZeroMemory( &stDlFile, sizeof(DLFILEITEM_S) );
// lock the data
CSingleLock sl(&m_csDataAccess);
sl.Lock();
// the last piece is a finished one, but we have to record it
LONGLONG nMaxBound = GetMaxBound(lstUndone);
if( (nMaxBound > 0) && (nMaxBound < m_nFileLength) )
{
nLefts++;
}
// fill download file item
stDlFile.wSeperator = 0x0A0D;
stDlFile.wBlocksToDo = nLefts;
stDlFile.nFileLength = m_nFileLength;
stDlFile.nBytesDone = m_nBytesReceived;
stDlFile.dUsedTime = m_dUsedTime;
StrCopy( stDlFile.szUrl, m_sDownloadUrl, MAX_PATH );
StrCopy( stDlFile.szReferer, m_sReferer, MAX_PATH );
StrCopy( stDlFile.szWebInfo, m_sWebInfo, MAX_PATH/2 );
// save the download file item
pFile->Write( &stDlFile, sizeof(DLFILEITEM_S) );
// save the unfinished block in increase-order
LONGLONG nOffset = 0;
SEGMENT_S *pstSeg;
while( (pstSeg = GetMinOffset(lstUndone)) != NULL )
{
pstSeg->nBytesDone = pstSeg->nDataFrom + pstSeg->nBytesDone - nOffset;
pstSeg->nDataFrom = nOffset;
pFile->Write( pstSeg, sizeof(SEGMENT_S) );
nOffset = pstSeg->nDataTo + 1;
}
// last block is a finished one
if( nOffset < m_nFileLength )
{
SEGMENT_S stSegLast;
stSegLast.nDataFrom = nOffset;
stSegLast.nDataTo = m_nFileLength - 1;
stSegLast.nBytesDone = m_nFileLength - nOffset;
pFile->Write( &stSegLast, sizeof(SEGMENT_S) );
}
}
int CDownloadFile::GetLeftSegments( list& lstUndone )
{
list::iterator itor;
CHttpSocket *pSocket;
SEGMENT_S *pstSeg;
// save blocks at first
m_csDataAccess.Lock();
// save blocks at first
for( itor = m_lstSocket.begin(); itor != m_lstSocket.end(); itor++ )
{
pSocket = *itor;
if( !pSocket )
{
continue;
}
if( !pSocket->m_lstSegments.empty() )
{
pstSeg = pSocket->m_lstSegments.back();
if( (pstSeg->nDataFrom + pstSeg->nBytesDone) < (pstSeg->nDataTo + 1) )
{
lstUndone.push_back(pstSeg);
}
}
}
m_csDataAccess.Unlock();
return (int)lstUndone.size();
}
SEGMENT_S* CDownloadFile::GetMinOffset( list& lstUndone )
{
list::iterator itor;
SEGMENT_S *pstSeg = NULL, *pstTemp;
// get the first element
if( !lstUndone.empty() )
{
pstSeg = lstUndone.front();
}
for( itor = ++lstUndone.begin(); itor != lstUndone.end(); itor++ )
{
pstTemp = *itor;
if( pstTemp->nDataFrom < pstSeg->nDataFrom )
{
pstSeg = pstTemp;
}
}
// remove this segment from the list
lstUndone.remove(pstSeg);
return pstSeg;
}
LONGLONG CDownloadFile::GetMaxBound(list& lstUndone)
{
list::iterator itor;
SEGMENT_S *pstSeg;
LONGLONG nMaxBound = 0;
// get the maximum right bound
for( itor = lstUndone.begin(); itor != lstUndone.end(); itor++ )
{
pstSeg = *itor;
if( nMaxBound < pstSeg->nDataTo + 1 )
{
nMaxBound = pstSeg->nDataTo + 1;
}
}
return nMaxBound;
}
void CDownloadFile::DelSniffSocket(void)
{
if( GetSnifferSock() != INVALID_SOCKET )
{
delete m_pSnifferSock;
m_pSnifferSock = NULL;
}
}
void CDownloadFile::NotifyStatus(int nCode, LPVOID pParam)
{
if( IsWindow(m_hFrameWnd) )
{
CWnd *pWnd = CWnd::FromHandle(m_hFrameWnd);
pWnd->PostMessage(WM_USER_STATUSNOTIFY, nCode, (LPARAM)pParam);
}
}
void CDownloadFile::CloseSocket(CHttpSocket* pSocket)
{
pSocket->Close();
m_csDataAccess.Lock();
m_nStoppedSockets++;
m_csDataAccess.Unlock();
AtlTrace("Socket: %08X is closed.\n", pSocket);
}