Files
itgmania212121/src/FileDownload.cpp
T

349 lines
8.8 KiB
C++
Raw Normal View History

#include "global.h"
#if !defined(WITHOUT_NETWORKING)
#include "FileDownload.h"
#include "RageUtil.h"
#include "RageFileManager.h"
#include "SpecialFiles.h"
#include "RageLog.h"
FileDownload::FileDownload()
{
m_iPackagesPos = 0;
m_iLinksPos = 0;
m_bIsDownloading = false;
m_sStatus = "";
m_fLastUpdate = 0;
m_iTotalBytes = 0;
m_iDownloaded = 0;
m_bFinished = false;
UpdateProgress();
// Workaround: For some reason, the first download sometimes corrupts;
// by opening and closing the RageFile, this problem does not occur.
// Go figure?
// XXX: This is a really dirty work around! Why does RageFile do this?
// It's always some strange number of bytes at the end of the
// file when it corrupts.
m_fOutputFile.Open( "Packages/dummy.txt", RageFile::WRITE );
m_fOutputFile.Close();
}
FileDownload::~FileDownload()
{
m_fOutputFile.Close();
}
RString FileDownload::Update( float fDeltaTime )
{
HTTPUpdate();
m_fLastUpdate += fDeltaTime;
if ( m_fLastUpdate >= 1.0 )
{
if ( m_bIsDownloading && m_bGotHeader )
m_sStatus = ssprintf( "DL @ %d KB/s", int((m_iDownloaded-m_bytesLastUpdate)/1024) );
m_bytesLastUpdate = m_iDownloaded;
UpdateProgress();
m_fLastUpdate = 0;
}
return m_sStatus;
}
void FileDownload::UpdateProgress()
{
float DownloadedRatio;
if( m_iTotalBytes < 1 )
DownloadedRatio = 0;
else
DownloadedRatio = float(m_iDownloaded) / float(m_iTotalBytes);
}
void FileDownload::CancelDownload( )
{
m_wSocket.close();
m_bIsDownloading = false;
m_iDownloaded = 0;
m_bGotHeader = false;
m_fOutputFile.Close();
m_sStatus = "Failed.";
m_sBUFFER = "";
FILEMAN->Remove( "Packages/" + m_sEndName );
}
void FileDownload::StartDownload( const RString &sURL, const RString &sDestFile )
{
RString Proto;
RString Server;
int Port=80;
RString sAddress;
if( !ParseHTTPAddress( sURL, Proto, Server, Port, sAddress ) )
{
m_sStatus = "Invalid URL.";
UpdateProgress();
return;
}
//Determine if this is a website, or a package?
//Criteria: does it end with *zip?
if( sAddress.Right(3).CompareNoCase("zip") == 0 )
m_bIsPackage=true;
else
m_bIsPackage = false;
m_sBaseAddress = "http://" + Server;
if( Port != 80 )
m_sBaseAddress += ssprintf( ":%d", Port );
m_sBaseAddress += "/";
if( sAddress.Right(1) != "/" )
{
m_sEndName = Basename( sAddress );
m_sBaseAddress += Dirname( sAddress );
}
else
{
m_sEndName = "";
}
//Open the file...
//First find out if a file by this name already exists
//if so, then we gotta ditch out.
//XXX: This should be fixed by a prompt or something?
//if we are not talking about a file, let's not worry
if( m_sEndName != "" && m_bIsPackage )
{
vector<RString> AddTo;
GetDirListing( "Packages/"+m_sEndName, AddTo, false, false );
if ( AddTo.size() > 0 )
{
m_sStatus = "File Already Exists";
UpdateProgress();
return;
}
if( !m_fOutputFile.Open( sDestFile, RageFile::WRITE | RageFile::STREAMED ) )
{
m_sStatus = m_fOutputFile.GetError();
UpdateProgress();
return;
}
}
//Continue...
sAddress = URLEncode( sAddress );
if ( sAddress != "/" )
sAddress = "/" + sAddress;
m_wSocket.close();
m_wSocket.create();
m_wSocket.blocking = true;
if( !m_wSocket.connect( Server, (short) Port ) )
{
m_sStatus = "Failed to connect.";
UpdateProgress();
return;
}
//Produce HTTP header
RString Header="";
Header = "GET "+sAddress+" HTTP/1.0\r\n";
Header+= "Host: " + Server + "\r\n";
Header+= "Connection: closed\r\n\r\n";
m_wSocket.SendData( Header.c_str(), Header.length() );
m_sStatus = "Header Sent.";
m_wSocket.blocking = false;
m_bIsDownloading = true;
m_sBUFFER = "";
m_bGotHeader = false;
UpdateProgress();
}
static size_t FindEndOfHeaders( const RString &buf )
{
size_t iPos1 = buf.find( "\n\n" );
size_t iPos2 = buf.find( "\r\n\r\n" );
LOG->Trace("end: %u, %u", unsigned(iPos1), unsigned(iPos2));
if( iPos1 != string::npos && (iPos2 == string::npos || iPos2 > iPos1) )
return iPos1 + 2;
else if( iPos2 != string::npos && (iPos1 == string::npos || iPos1 > iPos2) )
return iPos2 + 4;
else
return string::npos;
}
void FileDownload::HTTPUpdate()
{
if( !m_bIsDownloading )
return;
int BytesGot=0;
//Keep this as a code block
//as there may be need to "if" it out some time.
/* If you need a conditional for a large block of code, stick it in
* a function and return. */
while(1)
{
char Buffer[1024];
int iSize = m_wSocket.ReadData( Buffer, 1024 );
if( iSize <= 0 )
break;
m_sBUFFER.append( Buffer, iSize );
BytesGot += iSize;
}
if( !m_bGotHeader )
{
m_sStatus = "Waiting for header.";
//We don't know if we are using unix-style or dos-style
size_t iHeaderEnd = FindEndOfHeaders( m_sBUFFER );
if( iHeaderEnd == m_sBUFFER.npos )
return;
// "HTTP/1.1 200 OK"
size_t i = m_sBUFFER.find(" ");
size_t j = m_sBUFFER.find(" ",i+1);
size_t k = m_sBUFFER.find("\n",j+1);
if ( i == string::npos || j == string::npos || k == string::npos )
{
m_iResponseCode = -100;
m_sResponseName = "Malformed response.";
return;
}
m_iResponseCode = atoi(m_sBUFFER.substr(i+1,j-i).c_str());
m_sResponseName = m_sBUFFER.substr( j+1, k-j );
i = m_sBUFFER.find("Content-Length:");
j = m_sBUFFER.find("\n", i+1 );
if( i != string::npos )
m_iTotalBytes = atoi(m_sBUFFER.substr(i+16,j-i).c_str());
else
m_iTotalBytes = -1; //We don't know, so go until disconnect
m_bGotHeader = true;
m_sBUFFER.erase( 0, iHeaderEnd );
}
if( m_bIsPackage )
{
m_iDownloaded += m_sBUFFER.length();
m_fOutputFile.Write( m_sBUFFER );
// HACK: We're ending up with incomplete files after close() if we don't flush here. Why?
m_fOutputFile.Flush();
m_sBUFFER = "";
}
else
{
m_iDownloaded = m_sBUFFER.length();
}
if ( ( m_iTotalBytes <= m_iDownloaded && m_iTotalBytes != -1 ) ||
//We have the full doc. (And we knew how big it was)
( m_iTotalBytes == -1 &&
( m_wSocket.state == EzSockets::skERROR || m_wSocket.state == EzSockets::skDISCONNECTED ) ) )
//We didn't know how big it was, and were disconnected
//So that means we have it all.
{
m_wSocket.close();
m_bIsDownloading = false;
m_bGotHeader=false;
m_sStatus = ssprintf( "Done;%dB", int(m_iDownloaded) );
m_bFinished = true;
if( m_iResponseCode < 200 || m_iResponseCode >= 400 )
{
m_sStatus = ssprintf( "%ld", m_iResponseCode ) + m_sResponseName;
}
else
{
if( m_bIsPackage && m_iResponseCode < 300 )
{
RString sZipFile = m_fOutputFile.GetRealPath();
m_fOutputFile.Close();
FILEMAN->FlushDirCache();
m_iDownloaded = 0;
}
}
}
}
bool FileDownload::ParseHTTPAddress( const RString &URL, RString &sProto, RString &sServer, int &iPort, RString &sAddress )
{
// [PROTO://]SERVER[:PORT][/URL]
Regex re(
"^([A-Z]+)://" // [0]: HTTP://
"([^/:]+)" // [1]: a.b.com
"(:([0-9]+))?" // [2], [3]: :1234 (optional, default 80)
"(/(.*))?$"); // [4], [5]: /foo.html (optional)
vector<RString> asMatches;
if( !re.Compare( URL, asMatches ) )
return false;
ASSERT( asMatches.size() == 6 );
sProto = asMatches[0];
sServer = asMatches[1];
if( asMatches[3] != "" )
{
iPort = atoi(asMatches[3]);
if( iPort == 0 )
return false;
}
else
iPort = 80;
sAddress = asMatches[5];
return true;
}
bool FileDownload::IsFinished()
{
return m_bFinished;
}
#endif
/*
* (c) 2004 Charles Lohr, Chris Danford
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, and/or sell copies of the Software, and to permit persons to
* whom the Software is furnished to do so, provided that the above
* copyright notice(s) and this permission notice appear in all copies of
* the Software and that both the above copyright notice(s) and this
* permission notice appear in supporting documentation.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/