// complete copy-paste job, because "somebody" *cough*cough* uses word "final" too much :( Class FTPTcpLinkSE extends TcpLink; var string TempFileName; var array PendingLoaders; var array ToSave; var ServerPerksMutSE Mut; var IpAddr SiteAddress; var FTPUploadDataConnection UploadDataConnection; var FTPDownloadDataConnection DownloadDataConnection; var transient float WelcomeTimer; var array TotalList; var byte RetryCounter; var bool bConnectionBroken,bUploadAllStats,bTotalUpload,bFileInProgress,bPostUploadCheck,bIsAsciiMode; var bool bFullVerbose,bLogAllCommands; var protected transient bool bCheckedWeb; var protected transient PlayerController WebAdmin; function BeginEvent() { Mut.SaveAllStats = SaveAllStats; Mut.RequestStats = RequestStats; if( Mut.bDebugDatabase ) { bLogAllCommands = true; bFullVerbose = Mut.bBroadcastFTPDebug; } LinkMode = MODE_Line; ReceiveMode = RMODE_Event; Resolve(Mut.RemoteDatabaseURL); } function Destroyed() { if ( UploadDataConnection != none ) UploadDataConnection.Destroy(); if ( DownloadDataConnection != none ) DownloadDataConnection.Destroy(); super.Destroyed(); } function CheckWebAdmin() { local MessagingSpectator MS; bCheckedWeb = true; if ( Level.NetMode == NM_Standalone ) WebAdmin = Level.GetLocalPlayerController(); else { foreach AllActors(class'MessagingSpectator',MS) { WebAdmin = MS; break; } } } function DebugLog( string Str ) { if( !bCheckedWeb ) CheckWebAdmin(); if( WebAdmin!=None ) WebAdmin.ClientMessage(Str,'FTP'); Log(Str,'FTP'); } function DebugMessage(string Msg, optional bool bError) { local Controller C; local PlayerController PC; if ( bFullVerbose || (bError && Mut.bBroadcastFTPErrors) ) { for ( C = Level.ControllerList; C != none; C = C.nextController ) { PC = PlayerController(C); if ( PC != none && (!Mut.bBroadcastToAdminsOnly || (PC.PlayerReplicationInfo != none && PC.PlayerReplicationInfo.bAdmin)) ) PC.ClientMessage(Msg,'FTP'); } } } function ReportError( int Code, string InEr ) { if( !bConnectionBroken ) { DebugMessage(Code$" FTP Error: "$InEr, true); DebugLog(Code$" FTP Error: "$InEr); } bConnectionBroken = true; GoToState('ErrorState'); } event Resolved( IpAddr Addr ) { SiteAddress = Addr; SiteAddress.Port = Mut.RemotePort; GoToState('Idle'); } event ResolveFailed() { ReportError(0,"Couldn't resolve address, aborting..."); } event Closed() { ReportError(1,"Connection was closed by FTP server!"); } event ReceivedLine( string Text ) { if( bLogAllCommands ) DebugLog("IN "$GetStateName()$": "$Text); ProcessResponse(int(Left(Text,3)),Mid(Text,4)); } function SendFTPLine( string Text ) { if( bLogAllCommands ) DebugLog("OUT "$GetStateName()$": "$Text); SendText(Text); } function SaveAllStats() { local int i; if( bTotalUpload ) return; ToSave = Mut.ActiveStats; for( i=0; i0 ) bUploadAllStats = true; } function RequestStats( ServerStStats Other ) { local int i; if( bTotalUpload ) return; for( i=0; i0 && PendingLoaders[0]==None ) PendingLoaders.Remove(0,1); if( bUploadAllStats || (bTotalUpload && HasMoreStats()) ) GoToState('UploadStats','Begin'); else if( PendingLoaders.Length>0 ) GoToState('DownloadStats','Begin'); else { if( bFullVerbose ) DebugMessage("FTP: All done!"); if( Mut.FTPKeepAliveSec>0 && !Level.Game.bGameEnded ) GoToState('KeepAlive'); else GoToState('EndConnection'); } } function ProcessResponse( int Code, string Line ) { switch( Code ) { case 220: // Welcome if( WelcomeTimer0 ) StartConnection(); } state InitConnection { function BeginState() { SetTimer(10,false); } event Closed() { ReportError(5,"Connection was closed by FTP server!"); } Begin: Sleep(5.f); SendFTPLine("USER "$Mut.RemoteFTPUser); } state ConnectionBase { event Closed() { GoToState('Idle'); } Begin: while( true ) { if( bUploadAllStats && Level.bLevelChange ) // Delay mapchange until all stats are uploaded. Level.NextSwitchCountdown = FMax(Level.NextSwitchCountdown,2.f); Sleep(0.5); } } state EndConnection extends ConnectionBase { function BeginState() { SendFTPLine("QUIT"); SetTimer(4,false); } } state KeepAlive extends ConnectionBase { Ignores Timer; function SaveAllStats() { Global.SaveAllStats(); if( bUploadAllStats ) StartConnection(); } function RequestStats( ServerStStats Other ) { Global.RequestStats(Other); StartConnection(); } function StartConnection() { CheckNextCommand(); } Begin: while( true ) { if( bUploadAllStats || PendingLoaders.Length>0 ) StartConnection(); Sleep(Mut.FTPKeepAliveSec); SendFTPLine("NOOP"); } } state UploadStats extends ConnectionBase { function BeginState() { bUploadAllStats = false; SetTimer(10,false); } function EndState() { if ( UploadDataConnection!= none ) UploadDataConnection.Destroy(); } function SaveAllStats(); function bool OpenDataConnection( string S ) { local int i,j; local IpAddr A; A = SiteAddress; // Get destination port S = Mid(S,InStr(S,"(")+1); for( i=0; i<4; ++i ) // Skip IP address S = Mid(S,InStr(S,",")+1); i = InStr(S,","); A.Port = int(Left(S,i))*256 + int(Mid(S,i+1)); // Now attempt to bind port and open connection. i = 0; for( j=Mut.LastDataPortUsed+1; j!=Mut.LastDataPortUsed && (++i < 100); ++j ) { if ( j > Mut.DataPortRangeEnd ) j = Mut.DataPortRangeStart; if( UploadDataConnection!=None ) UploadDataConnection.Destroy(); UploadDataConnection = Spawn(Class'FTPUploadDataConnection',Self); UploadDataConnection.BindPort(j,false); if( UploadDataConnection.OpenNoSteam(A) ) { DebugLog("PORT "$j$" bound for upload data connection"); Mut.LastDataPortUsed = j; return true; } } Mut.LastDataPortUsed = j; UploadDataConnection.Destroy(); ReportError(2,"Couldn't bind port for upload data connection!"); return false; } function InitDataConnection( string S ) { if ( bFileInProgress ) { ReportError(6, "Attempt to make new connection while file is in progress"); return; } if( bFullVerbose ) DebugMessage("FTP: Upload stats for "$ToSave[0].PlayerName$" ("$(ToSave.Length-1+TotalList.Length)$" remains)"); if( OpenDataConnection(S) ) { UploadDataConnection.BufferSize = Mut.BufferSize; UploadDataConnection.Data = ToSave[0].GetSaveData(); TempFileName = ToSave[0].Name$".txt.tmp"; SendFTPLine("STOR "$TempFileName); bFileInProgress = true; } } function NextPackage() { RetryCounter = 0; ToSave[0].bStatsChanged = false; ToSave.Remove(0,1); if( ToSave.Length==0 ) CheckNextCommand(); else if( !bIsAsciiMode ) { bIsAsciiMode = true; SendFTPLine("TYPE A"); } else SendFTPLine("PASV"); } function ProcessResponse( int Code, string Line ) { switch( Code ) { case 200: // Type set to A/I if( bPostUploadCheck ) { SetTimer(5,false); SendFTPLine("SIZE "$TempFileName); } else SendFTPLine("PASV"); break; case 227: // Entering passive mode InitDataConnection(Line); break; case 125: // Data connection already open; Transfer starting. case 150: // Opening ASCII mode data connection for file SetTimer(60,false); if( UploadDataConnection!=None ) UploadDataConnection.BeginUpload(); break; case 226: // File transfer completed. if( bFileInProgress ) { SetTimer(5,false); bFileInProgress = false; bPostUploadCheck = true; SendFTPLine("TYPE I"); bIsAsciiMode = false; } break; case 213: // File size response. if( bPostUploadCheck ) { SetTimer(5,false); if( int(Line)<=5 ) { bPostUploadCheck = false; if( ++RetryCounter>=5 ) NextPackage(); else { DebugMessage("213 FTP Error: Stats upload failed for "$ToSave[0].PlayerName$" retrying...", true); SendFTPLine("PASV"); } } else SendFTPLine("RNFR "$TempFileName); } break; case 350: // Rename accepted. if( bPostUploadCheck ) { SetTimer(5,false); SendFTPLine("RNTO "$Left(TempFileName,Len(TempFileName)-4)); } break; case 250: // File successfully renamed or moved bPostUploadCheck = false; NextPackage(); break; case 550: // Sorry, but that file doesn't exist if( bPostUploadCheck ) { SetTimer(5,false); bPostUploadCheck = false; if( ++RetryCounter>=5 ) NextPackage(); else { DebugMessage("550 FTP Error: Stats upload failed for "$ToSave[0].PlayerName$" retrying...", true); SendFTPLine("PASV"); } } break; default: Global.ProcessResponse(Code,Line); } } Begin: if( !bIsAsciiMode ) { SendFTPLine("TYPE A"); bIsAsciiMode = true; } else SendFTPLine("PASV"); while( true ) { if( Level.bLevelChange ) // Delay mapchange until all stats are uploaded. { bFullVerbose = true; Level.NextSwitchCountdown = FMax(Level.NextSwitchCountdown,2.f); } Sleep(0.5); } } state DownloadStats extends ConnectionBase { function BeginState() { SetTimer(10,false); } function EndState() { if ( DownloadDataConnection != none ) DownloadDataConnection.Destroy(); } function bool OpenDataConnection( string S ) { local int i,j; local IpAddr A; A = SiteAddress; // Get destination port S = Mid(S,InStr(S,"(")+1); for( i=0; i<4; ++i ) // Skip IP address S = Mid(S,InStr(S,",")+1); i = InStr(S,","); A.Port = int(Left(S,i))*256 + int(Mid(S,i+1)); // Now attempt to bind port and open connection. i = 0; for( j=Mut.LastDataPortUsed+1; j!=Mut.LastDataPortUsed && (++i < 100); ++j ) { if ( j > Mut.DataPortRangeEnd ) j = Mut.DataPortRangeStart; if( DownloadDataConnection!=None ) DownloadDataConnection.Destroy(); DownloadDataConnection = Spawn(Class'FTPDownloadDataConnection',Self); DownloadDataConnection.BindPort(j,false); if( DownloadDataConnection.OpenNoSteam(A) ) { DebugLog("PORT "$j$" bound for download data connection"); Mut.LastDataPortUsed = j; return true; } } Mut.LastDataPortUsed = j; DownloadDataConnection.Destroy(); DownloadDataConnection = None; ReportError(2,"Couldn't bind port for download data connection!"); return false; } function InitDataConnection( string S ) { while( PendingLoaders.Length>0 && (PendingLoaders[0]==None || PendingLoaders[0].MyStatsObject == none) ) PendingLoaders.Remove(0,1); if( PendingLoaders.Length==0 ) { CheckNextCommand(); return; } if( bFullVerbose ) DebugMessage("FTP: Download stats for "$PendingLoaders[0].MyStatsObject.PlayerName$" ("$(PendingLoaders.Length-1)$" remains)"); if( OpenDataConnection(S) ) { SendFTPLine("RETR "$PendingLoaders[0].MyStatsObject.Name$".txt"); bFileInProgress = true; SetTimer(10,false); } } function DataReceived() { bFileInProgress = false; if( PendingLoaders[0]!=None ) { if( DownloadDataConnection!=None ) PendingLoaders[0].GetData(DownloadDataConnection.Data); else PendingLoaders[0].GetData(""); } PendingLoaders.Remove(0,1); while( PendingLoaders.Length>0 && PendingLoaders[0]==None ) PendingLoaders.Remove(0,1); if ( DownloadDataConnection != none ) DownloadDataConnection.Destroy(); if( bUploadAllStats ) // Saving has higher priority. GoToState('UploadStats'); else if( PendingLoaders.Length>0 ) SendFTPLine("PASV"); else CheckNextCommand(); } function ProcessResponse( int Code, string Line ) { switch( Code ) { case 125: // Data connection already open, transfer starting. case 150: // Opening ASCII mode data connection for file SetTimer(60,false); break; case 200: // Type set to A SendFTPLine("PASV"); break; case 226: // File successfully transferred SetTimer(10,false); if ( bFileInProgress ) { if ( DownloadDataConnection != none && !DownloadDataConnection.bClosed ) DownloadDataConnection.OnCompleted = DataReceived; // incoming data is still in progress. Is it possible?.. else DataReceived(); } break; case 227: // Entering passive mode if( !bFileInProgress ) InitDataConnection(Line); break; case 550: // No such file or directory SetTimer(10,false); if( bFileInProgress ) { if( DownloadDataConnection!=None ) DownloadDataConnection.Destroy(); DataReceived(); } break; default: Global.ProcessResponse(Code,Line); } } Begin: if( !bIsAsciiMode ) { bIsAsciiMode = true; SendFTPLine("TYPE A"); } else SendFTPLine("PASV"); while( true ) { if( bUploadAllStats && Level.bLevelChange ) // Delay mapchange until all stats are uploaded. { bFullVerbose = true; Level.NextSwitchCountdown = FMax(Level.NextSwitchCountdown,2.f); } Sleep(0.5); } } state ErrorState { Ignores SaveAllStats,RequestStats; Begin: Sleep(1.f); Mut.RespawnNetworkLink(); } defaultproperties { }