Download.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. /*
  2. ** Command & Conquer Generals(tm)
  3. ** Copyright 2025 Electronic Arts Inc.
  4. **
  5. ** This program is free software: you can redistribute it and/or modify
  6. ** it under the terms of the GNU General Public License as published by
  7. ** the Free Software Foundation, either version 3 of the License, or
  8. ** (at your option) any later version.
  9. **
  10. ** This program is distributed in the hope that it will be useful,
  11. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ** GNU General Public License for more details.
  14. **
  15. ** You should have received a copy of the GNU General Public License
  16. ** along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. // Download.cpp : Implementation of CDownload
  19. #include "DownloadDebug.h"
  20. #include "download.h"
  21. #include <mmsystem.h>
  22. #include <assert.h>
  23. #include <direct.h>
  24. #include <stdlib.h>
  25. #include <stdio.h>
  26. #include <sys/stat.h>
  27. /////////////////////////////////////////////////////////////////////////////
  28. // CDownload
  29. //
  30. // Kick off a download - copys the parameters for the download to the appropriate
  31. // data members, then sets the status flag to start the download when PumpMessages()
  32. // is called.
  33. //
  34. HRESULT CDownload::DownloadFile(LPCSTR server, LPCSTR username, LPCSTR password, LPCSTR file, LPCSTR localfile, LPCSTR regkey, bool tryresume)
  35. {
  36. // Check we're not in the middle of another download.
  37. if((m_Status != DOWNLOADSTATUS_DONE ) && (m_Status != DOWNLOADSTATUS_FINDINGFILE))
  38. {
  39. return( DOWNLOAD_STATUSERROR );
  40. }
  41. // If we're still connected, make sure we're on the right server
  42. if (m_Status == DOWNLOADSTATUS_FINDINGFILE)
  43. {
  44. if ((strcmp(m_Server, server)) || (strcmp(m_Login, username)))
  45. {
  46. // Damn, a server switch. Close conn & fix state
  47. m_Ftp->DisconnectFromServer();
  48. m_Status=DOWNLOADSTATUS_DONE;
  49. }
  50. }
  51. // Check all parameters are non-null.
  52. if( ( server == NULL ) || ( username == NULL ) ||
  53. ( password == NULL ) || ( file == NULL ) ||
  54. ( localfile == NULL ) || ( regkey == NULL ) )
  55. {
  56. //////////DBGMSG("Download Paramerror");
  57. return( DOWNLOAD_PARAMERROR );
  58. }
  59. // Make sure we have a download directory
  60. _mkdir("download");
  61. // Copy parameters to member variables.
  62. strncpy( m_Server, server, sizeof( m_Server ) );
  63. strncpy( m_Login, username, sizeof( m_Login ) );
  64. strncpy( m_Password, password, sizeof( m_Password ) );
  65. strncpy( m_File, file, sizeof( m_File ) );
  66. strncpy( m_LocalFile, localfile, sizeof( m_LocalFile ) );
  67. strncpy( m_LastLocalFile, localfile, sizeof( m_LastLocalFile ) );
  68. strncpy( m_RegKey, regkey, sizeof( m_RegKey ) );
  69. m_TryResume = tryresume;
  70. m_StartPosition=0;
  71. /*********
  72. // figure out file offset
  73. if (m_TryResume == false)
  74. {
  75. m_StartPosition=0;
  76. }
  77. else if(( m_StartPosition = m_Ftp->FileRecoveryPosition( m_LocalFile, m_RegKey ) ) != 0 )
  78. {
  79. if( Listener->OnQueryResume() == DOWNLOADEVENT_DONOTRESUME )
  80. {
  81. m_Ftp->RestartFrom( 0 );
  82. m_StartPosition = 0;
  83. }
  84. }
  85. ************/
  86. // Set status so we start to connect at the next PumpMessages()
  87. if (m_Status != DOWNLOADSTATUS_FINDINGFILE)
  88. m_Status = DOWNLOADSTATUS_GO;
  89. //////////DBGMSG("Ready to download");
  90. return S_OK;
  91. }
  92. //
  93. // Get the local filename of the last file we requested to download....
  94. //
  95. HRESULT CDownload::GetLastLocalFile(char *local_file, int maxlen) {
  96. if (local_file==0)
  97. return(E_FAIL);
  98. strncpy(local_file, m_LastLocalFile, maxlen);
  99. local_file[maxlen-1]=0;
  100. return(S_OK);
  101. }
  102. //
  103. // Abort the current download - disconnects from the server and sets the
  104. // status flag to stop PumpMessages() from doing anything.
  105. //
  106. HRESULT CDownload::Abort()
  107. {
  108. if( m_Status != DOWNLOADSTATUS_NONE )
  109. {
  110. m_Ftp->DisconnectFromServer();
  111. m_Status = DOWNLOADSTATUS_NONE;
  112. }
  113. m_TimeStarted = 0;
  114. m_StartPosition = 0;
  115. m_FileSize = 0;
  116. m_BytesRead = 0;
  117. m_Server[ 0 ] = '\0';
  118. m_Login[ 0 ] = '\0';
  119. m_Password[ 0 ] = '\0';
  120. m_File[ 0 ] = '\0';
  121. m_LocalFile[ 0 ] = '\0';
  122. m_RegKey[ 0 ] = '\0';
  123. return S_OK;
  124. }
  125. //
  126. // PumpMessages() does all the work - checks for possible resumption of a previous
  127. // download, connects to the server, finds the file and downloads it.
  128. //
  129. HRESULT CDownload::PumpMessages()
  130. {
  131. int iResult = 0;
  132. int timetaken = 0;
  133. int averagetimepredicted = -1;
  134. static int reenter = 0;
  135. /* Avoid reentrancy. */
  136. ////DBGMSG("Download Pump");
  137. if( reenter != 0 )
  138. {
  139. return( DOWNLOAD_REENTERERROR );
  140. }
  141. reenter = 1;
  142. if( m_Status == DOWNLOADSTATUS_GO )
  143. {
  144. // Check to see whether the file already exists locally
  145. /***********
  146. if (m_TryResume == false)
  147. {
  148. m_StartPosition = 0;
  149. }
  150. else if( ( m_StartPosition = m_Ftp->FileRecoveryPosition( m_LocalFile, m_RegKey ) ) != 0 )
  151. {
  152. if( Listener->OnQueryResume() == DOWNLOADEVENT_DONOTRESUME )
  153. {
  154. m_Ftp->RestartFrom( 0 );
  155. m_StartPosition = 0;
  156. }
  157. }
  158. ************/
  159. // Tell the client that we're starting to connect.
  160. Listener->OnStatusUpdate( DOWNLOADSTATUS_CONNECTING );
  161. m_Status = DOWNLOADSTATUS_CONNECTING;
  162. }
  163. if( m_Status == DOWNLOADSTATUS_CONNECTING )
  164. {
  165. // Connect to the server
  166. //////////DBGMSG("Connect to Server");
  167. iResult = m_Ftp->ConnectToServer( m_Server );
  168. ////////////DBGMSG("out of FTP connect");
  169. if( iResult == FTP_SUCCEEDED )
  170. {
  171. m_Status = DOWNLOADSTATUS_LOGGINGIN;
  172. }
  173. else
  174. {
  175. if( iResult == FTP_FAILED )
  176. {
  177. // Tell the client we couldn't connect
  178. ///////////DBGMSG("Couldn't connect");
  179. Listener->OnError( DOWNLOADEVENT_COULDNOTCONNECT );
  180. reenter = 0;
  181. return DOWNLOAD_NETWORKERROR;
  182. }
  183. }
  184. reenter = 0;
  185. return DOWNLOAD_SUCCEEDED;
  186. }
  187. if( m_Status == DOWNLOADSTATUS_LOGGINGIN )
  188. {
  189. // Login to the server
  190. ////////////DBGMSG("Login to server");
  191. iResult = m_Ftp->LoginToServer( m_Login, m_Password );
  192. if( iResult == FTP_SUCCEEDED )
  193. {
  194. Listener->OnStatusUpdate( DOWNLOADSTATUS_FINDINGFILE );
  195. m_Status = DOWNLOADSTATUS_FINDINGFILE;
  196. }
  197. if( iResult == FTP_FAILED )
  198. {
  199. // Tell the client we couldn't log in
  200. Listener->OnError( DOWNLOADEVENT_LOGINFAILED );
  201. reenter = 0;
  202. return( DOWNLOAD_NETWORKERROR );
  203. }
  204. reenter = 0;
  205. return DOWNLOAD_SUCCEEDED;
  206. }
  207. if(( m_Status == DOWNLOADSTATUS_FINDINGFILE ) && (strlen(m_File)))
  208. {
  209. // Find the file on the server
  210. ///////////DBGMSG("Find File");
  211. if( m_Ftp->FindFile( m_File, &m_FileSize ) == FTP_FAILED )
  212. {
  213. // Tell the client we couldn't find the file
  214. Listener->OnError( DOWNLOADEVENT_NOSUCHFILE );
  215. reenter = 0;
  216. return( DOWNLOAD_FILEERROR );
  217. }
  218. if( m_FileSize > 0 )
  219. {
  220. // First check to see if we already have the complete file in the proper dest location...
  221. //
  222. // Note, we will only skip the download if the file is a patch. Patch files should
  223. // never ever change so this is not a concern.
  224. //
  225. // We identify patches because they are written into the patches folder.
  226. struct _stat statdata;
  227. if ( (_stat(m_LocalFile, &statdata) == 0) &&
  228. (statdata.st_size == m_FileSize) &&
  229. (_strnicmp(m_LocalFile, "patches\\", strlen("patches\\"))==0)) {
  230. // OK, no need to download this again....
  231. m_Status = DOWNLOADSTATUS_FINDINGFILE; // ready to find another file
  232. m_TimeStarted = 0;
  233. m_StartPosition = 0;
  234. m_FileSize = 0;
  235. m_BytesRead = 0;
  236. m_File[ 0 ] = '\0';
  237. m_LocalFile[ 0 ] = '\0';
  238. Listener->OnEnd();
  239. reenter = 0;
  240. return DOWNLOAD_SUCCEEDED;
  241. }
  242. //
  243. // Check if we can do a file resume
  244. //
  245. if (m_TryResume == false)
  246. {
  247. m_StartPosition = 0;
  248. }
  249. else if( ( m_StartPosition = m_Ftp->FileRecoveryPosition( m_LocalFile, m_RegKey ) ) != 0 )
  250. {
  251. if( Listener->OnQueryResume() == DOWNLOADEVENT_DONOTRESUME )
  252. {
  253. m_Ftp->RestartFrom( 0 );
  254. m_StartPosition = 0;
  255. }
  256. }
  257. // end resume check
  258. m_Status = DOWNLOADSTATUS_DOWNLOADING;
  259. // Tell the client we're starting to download
  260. Listener->OnStatusUpdate( DOWNLOADSTATUS_DOWNLOADING );
  261. }
  262. reenter = 0;
  263. return DOWNLOAD_SUCCEEDED;
  264. }
  265. if( m_Status == DOWNLOADSTATUS_DOWNLOADING )
  266. {
  267. // Get the next chunk of the file
  268. ///DBGMSG("Get Next File Block");
  269. iResult = m_Ftp->GetNextFileBlock( m_LocalFile, &m_BytesRead );
  270. if( m_TimeStarted == 0 )
  271. {
  272. // This is the first time through here - record the starting time.
  273. m_TimeStarted = timeGetTime();
  274. }
  275. if( iResult == FTP_SUCCEEDED )
  276. {
  277. m_Status = DOWNLOADSTATUS_FINISHING;
  278. }
  279. else
  280. {
  281. if( iResult == FTP_FAILED )
  282. {
  283. Listener->OnError( DOWNLOADEVENT_TCPERROR );
  284. reenter = 0;
  285. return( DOWNLOAD_NETWORKERROR );
  286. }
  287. }
  288. // Calculate time taken so far, and predict how long there is left.
  289. // The prediction returned is the average of the last 8 predictions.
  290. timetaken = ( timeGetTime() - m_TimeStarted ) / 1000;
  291. //////////if( m_BytesRead > 0 ) // NAK - RP said this is wrong
  292. if( ( m_BytesRead - m_StartPosition ) > 0 )
  293. {
  294. // Not the first read.
  295. int predictionIndex = ( m_predictions++ ) & 0x7;
  296. m_predictionTimes[ predictionIndex ] = MulDiv( timetaken, (m_FileSize - m_BytesRead), (m_BytesRead - m_StartPosition) );
  297. //__int64 numerator = ( m_FileSize - m_BytesRead ) * timetaken;
  298. //__int64 denominator = ( m_BytesRead - m_StartPosition );
  299. //m_predictionTimes[ predictionIndex ] = numerator/denominator;
  300. //m_predictionTimes[ predictionIndex ] = ( ( m_FileSize - m_BytesRead ) * timetaken ) / ( m_BytesRead - m_StartPosition );
  301. //m_predictionTimes[ predictionIndex ] = ( ( m_FileSize - m_BytesRead ) / ( m_BytesRead - m_StartPosition ) * timetaken );
  302. DEBUG_LOG(("About to OnProgressUpdate() - m_FileSize=%d, m_BytesRead=%d, timetaken=%d, numerator=%d",
  303. m_FileSize, m_BytesRead, timetaken, ( ( m_FileSize - m_BytesRead ) * timetaken )));
  304. DEBUG_LOG((", m_startPosition=%d, denominator=%d, predictionTime=%d\n",
  305. m_StartPosition, ( m_BytesRead - m_StartPosition ), predictionIndex));
  306. DEBUG_LOG(("vals are %d %d %d %d %d %d %d %d\n",
  307. m_predictionTimes[ 0 ], m_predictionTimes[ 1 ], m_predictionTimes[ 2 ], m_predictionTimes[ 3 ],
  308. m_predictionTimes[ 4 ], m_predictionTimes[ 5 ], m_predictionTimes[ 6 ], m_predictionTimes[ 7 ]));
  309. if( m_predictions > 8 )
  310. {
  311. // We've had enough time to build up a few predictions, so calculate
  312. // an average.
  313. averagetimepredicted = ( m_predictionTimes[ 0 ] + m_predictionTimes[ 1 ] +
  314. m_predictionTimes[ 2 ] + m_predictionTimes[ 3 ] +
  315. m_predictionTimes[ 4 ] + m_predictionTimes[ 5 ] +
  316. m_predictionTimes[ 6 ] + m_predictionTimes[ 7 ] ) / 8;
  317. }
  318. else
  319. {
  320. // Wait a while longer before passing a "real" prediction
  321. averagetimepredicted = -1;
  322. }
  323. }
  324. else
  325. {
  326. averagetimepredicted = -1;
  327. }
  328. // Update the client's progress bar (or whatever)
  329. Listener->OnProgressUpdate( m_BytesRead, m_FileSize, timetaken, averagetimepredicted + 1);
  330. }
  331. if( m_Status == DOWNLOADSTATUS_FINISHING )
  332. {
  333. if (m_Ftp->m_iCommandSocket)
  334. m_Status = DOWNLOADSTATUS_FINDINGFILE; // ready to find another file
  335. else
  336. m_Status = DOWNLOADSTATUS_DONE; // command channel closed, connect again...
  337. m_TimeStarted = 0;
  338. m_StartPosition = 0;
  339. m_FileSize = 0;
  340. m_BytesRead = 0;
  341. m_File[ 0 ] = '\0';
  342. m_LocalFile[ 0 ] = '\0';
  343. Listener->OnEnd();
  344. }
  345. reenter = 0;
  346. return DOWNLOAD_SUCCEEDED;
  347. }