685 lines
15 KiB
Ucode
685 lines
15 KiB
Ucode
Class FTPTcpLink extends TcpLink;
|
|
|
|
var string TempFileName;
|
|
var array<ServerStStats> PendingLoaders;
|
|
var array<StatsObject> ToSave;
|
|
var ServerPerksMut Mut;
|
|
var IpAddr SiteAddress;
|
|
var FTPDataConnection DataConnection;
|
|
var transient float WelcomeTimer;
|
|
var array<string> TotalList;
|
|
var PlayerController WebAdminController;
|
|
var int IgnoreDateNum;
|
|
var byte RetryCounter;
|
|
var bool bConnectionBroken,bFullVerbose,bUploadAllStats,bTotalUpload,bFileInProgress,bLogAllCommands,bCheckedWeb,bPostUploadCheck,bIsAsciiMode,bObtainDir;
|
|
|
|
function BeginEvent()
|
|
{
|
|
Mut.SaveAllStats = SaveAllStats;
|
|
Mut.RequestStats = RequestStats;
|
|
if( Mut.bDebugDatabase )
|
|
{
|
|
bLogAllCommands = true;
|
|
bFullVerbose = true;
|
|
}
|
|
|
|
LinkMode = MODE_Line;
|
|
ReceiveMode = RMODE_Event;
|
|
Resolve(Mut.RemoteDatabaseURL);
|
|
}
|
|
final function ReportError( int Code, string InEr )
|
|
{
|
|
if( !bConnectionBroken )
|
|
{
|
|
Level.Game.Broadcast(Self,Code$" FTP Error: "$InEr);
|
|
Log(Code$" FTP Error: "$InEr,Class.Name);
|
|
}
|
|
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!");
|
|
}
|
|
final function DebugLog( string Str )
|
|
{
|
|
if( !bCheckedWeb )
|
|
{
|
|
bCheckedWeb = true;
|
|
foreach DynamicActors(class'PlayerController',WebAdminController)
|
|
if( WebAdminController.IsA('MessagingSpectator') )
|
|
break;
|
|
//if( Level.NetMode==NM_StandAlone )
|
|
// WebAdminController = Level.GetLocalPlayerController();
|
|
}
|
|
if( WebAdminController!=None )
|
|
WebAdminController.ClientMessage(Str,'FTP');
|
|
Log(Str,'FTP');
|
|
}
|
|
event ReceivedLine( string Text )
|
|
{
|
|
if( bLogAllCommands )
|
|
DebugLog("ReceiveFTP "$GetStateName()$":"@Text);
|
|
ProcessResponse(int(Left(Text,3)),Mid(Text,4));
|
|
}
|
|
final function SendFTPLine( string Text )
|
|
{
|
|
if( bLogAllCommands )
|
|
DebugLog("SendFTP "$GetStateName()$":"@Text);
|
|
SendText(Text);
|
|
}
|
|
|
|
function SaveAllStats()
|
|
{
|
|
local int i;
|
|
|
|
if( bTotalUpload )
|
|
return;
|
|
ToSave = Mut.ActiveStats;
|
|
for( i=0; i<ToSave.Length; ++i )
|
|
{
|
|
if( !ToSave[i].bStatsChanged )
|
|
ToSave.Remove(i--,1);
|
|
}
|
|
if( ToSave.Length>0 )
|
|
bUploadAllStats = true;
|
|
}
|
|
function RequestStats( ServerStStats Other )
|
|
{
|
|
local int i;
|
|
|
|
if( bTotalUpload )
|
|
return;
|
|
for( i=0; i<PendingLoaders.Length; ++i )
|
|
{
|
|
if( PendingLoaders[i]==None )
|
|
PendingLoaders.Remove(i--,1);
|
|
else if( PendingLoaders[i]==Other )
|
|
return;
|
|
}
|
|
PendingLoaders[PendingLoaders.Length] = Other;
|
|
}
|
|
final function FullUpload()
|
|
{
|
|
if( Class'ServerPerksMut'.Default.FTPUploadAllIgnoreDate!="" )
|
|
{
|
|
bObtainDir = true;
|
|
IgnoreDateNum = GetDateInt(int(Mid(Class'ServerPerksMut'.Default.FTPUploadAllIgnoreDate,6,2)),int(Mid(Class'ServerPerksMut'.Default.FTPUploadAllIgnoreDate,4,2)),int(Mid(Class'ServerPerksMut'.Default.FTPUploadAllIgnoreDate,0,4)));
|
|
}
|
|
|
|
TotalList = GetPerObjectNames("ServerPerksStat","StatsObject",9999999);
|
|
bTotalUpload = true;
|
|
bUploadAllStats = true;
|
|
bFullVerbose = true;
|
|
HasMoreStats();
|
|
SaveAllStats();
|
|
}
|
|
final function bool HasMoreStats()
|
|
{
|
|
local byte i;
|
|
local int j;
|
|
|
|
if( TotalList.Length==0 )
|
|
return false;
|
|
j = ToSave.Length;
|
|
for( i=0; i<Min(20,TotalList.Length); ++i )
|
|
{
|
|
ToSave.Length = j+1;
|
|
ToSave[j] = new(None,TotalList[i]) Class'StatsObject';
|
|
++j;
|
|
}
|
|
TotalList.Remove(0,20);
|
|
return true;
|
|
}
|
|
final function CheckNextCommand()
|
|
{
|
|
if( bObtainDir )
|
|
{
|
|
GoToState('GetDir');
|
|
return;
|
|
}
|
|
while( PendingLoaders.Length>0 && 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 )
|
|
Level.Game.Broadcast(Self,"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( WelcomeTimer<Level.TimeSeconds )
|
|
{
|
|
SendFTPLine("USER "$Mut.RemoteFTPUser);
|
|
WelcomeTimer = Level.TimeSeconds+0.2;
|
|
}
|
|
break;
|
|
case 331: // Password required
|
|
SendFTPLine("PASS "$Mut.RemotePassword);
|
|
break;
|
|
case 230: // User logged in.
|
|
if( Mut.RemoteFTPDir!="" )
|
|
SendFTPLine("CWD "$Mut.RemoteFTPDir);
|
|
else
|
|
{
|
|
SendFTPLine("TYPE A");
|
|
bIsAsciiMode = true;
|
|
}
|
|
break;
|
|
case 250: // CWD command successful.
|
|
SendFTPLine("TYPE A");
|
|
bIsAsciiMode = true;
|
|
break;
|
|
case 200: // Type set to A
|
|
CheckNextCommand();
|
|
break;
|
|
case 226: // File successfully transferred
|
|
case 150: // Opening ASCII mode data connection
|
|
case 221: // Good-bye
|
|
break;
|
|
case 421: // No transfer timeout: closing control connection
|
|
if( bFullVerbose )
|
|
Level.Game.Broadcast(Self,"FTP: Connection timed out, reconnecting!");
|
|
GoToState('EndConnection');
|
|
break;
|
|
case 221: // Good-bye
|
|
Close();
|
|
break;
|
|
default:
|
|
if( bFullVerbose )
|
|
Level.Game.Broadcast(Self,"FTP: Unknown FTP code '"$Code$"': "$Line);
|
|
Log("Unknown FTP code '"$Code$"': "$Line,Class.Name);
|
|
}
|
|
}
|
|
function DataReceived();
|
|
|
|
function DataProgress()
|
|
{
|
|
SetTimer(60,false);
|
|
}
|
|
|
|
final function bool OpenDataConnection( string S, bool bUpload )
|
|
{
|
|
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.
|
|
for( j=0; j<20; ++j )
|
|
{
|
|
if( DataConnection!=None )
|
|
DataConnection.Destroy();
|
|
DataConnection = Spawn(Class'FTPDataConnection',Self);
|
|
DataConnection.bUpload = bUpload;
|
|
DataConnection.BindPort();
|
|
if( DataConnection.Open(A) )
|
|
{
|
|
DataConnection.OnCompleted = DataReceived;
|
|
DataConnection.OnProgress = DataProgress;
|
|
return true;
|
|
}
|
|
}
|
|
DataConnection.Destroy();
|
|
DataConnection = None;
|
|
ReportError(2,"Couldn't bind port for upload data connection!");
|
|
return false;
|
|
}
|
|
|
|
function Timer()
|
|
{
|
|
ReportError(3,"FTP connection timed out!");
|
|
}
|
|
|
|
state Idle
|
|
{
|
|
Ignores Timer;
|
|
|
|
final function StartConnection()
|
|
{
|
|
local int i;
|
|
|
|
for( i=0; i<40; ++i )
|
|
{
|
|
BindPort();
|
|
if( Open(SiteAddress) )
|
|
{
|
|
GoToState('InitConnection');
|
|
return;
|
|
}
|
|
}
|
|
ReportError(4,"Port couldn't be bound or connection failed to open!");
|
|
}
|
|
function SaveAllStats()
|
|
{
|
|
Global.SaveAllStats();
|
|
if( bUploadAllStats )
|
|
StartConnection();
|
|
}
|
|
function RequestStats( ServerStStats Other )
|
|
{
|
|
Global.RequestStats(Other);
|
|
StartConnection();
|
|
}
|
|
Begin:
|
|
Sleep(0.1f);
|
|
if( bUploadAllStats || PendingLoaders.Length>0 )
|
|
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,1.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();
|
|
}
|
|
final 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 SaveAllStats();
|
|
|
|
final function InitDataConnection( string S )
|
|
{
|
|
if( bFullVerbose )
|
|
Level.Game.Broadcast(Self,"FTP: Upload stats for "$ToSave[0].PlayerName$" ("$(ToSave.Length-1+TotalList.Length)$" remains)");
|
|
if( OpenDataConnection(S,true) )
|
|
{
|
|
DataConnection.Data = ToSave[0].GetSaveData();
|
|
TempFileName = ToSave[0].Name$".txt.tmp";
|
|
SendFTPLine("STOR "$TempFileName);
|
|
bFileInProgress = true;
|
|
}
|
|
}
|
|
final 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
|
|
if( !bFileInProgress )
|
|
InitDataConnection(Line);
|
|
break;
|
|
case 150: // Opening ASCII mode data connection for file
|
|
SetTimer(60,false);
|
|
if( DataConnection!=None )
|
|
DataConnection.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
|
|
{
|
|
if( bFullVerbose )
|
|
Level.Game.Broadcast(Self,"213 FTP Error: Stats upload failed for "$ToSave[0].PlayerName$" retrying...");
|
|
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
|
|
NextPackage();
|
|
break;
|
|
case 550: // Sorry, but that file doesn't exist
|
|
if( bPostUploadCheck )
|
|
{
|
|
SetTimer(5,false);
|
|
bPostUploadCheck = false;
|
|
if( ++RetryCounter>=5 )
|
|
NextPackage();
|
|
else
|
|
{
|
|
if( bFullVerbose )
|
|
Level.Game.Broadcast(Self,"550 FTP Error: Stats upload failed for "$ToSave[0].PlayerName$" retrying...");
|
|
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,1.f);
|
|
}
|
|
Sleep(0.5);
|
|
}
|
|
}
|
|
state DownloadStats extends ConnectionBase
|
|
{
|
|
function BeginState()
|
|
{
|
|
SetTimer(10,false);
|
|
}
|
|
final function InitDataConnection( string S )
|
|
{
|
|
while( PendingLoaders.Length>0 && PendingLoaders[0]==None )
|
|
PendingLoaders.Remove(0,1);
|
|
if( PendingLoaders.Length==0 )
|
|
{
|
|
CheckNextCommand();
|
|
return;
|
|
}
|
|
|
|
if( bFullVerbose )
|
|
Level.Game.Broadcast(Self,"FTP: Download stats for "$PendingLoaders[0].MyStatsObject.PlayerName$" ("$(PendingLoaders.Length-1)$" remains)");
|
|
|
|
if( OpenDataConnection(S,false) )
|
|
{
|
|
SendFTPLine("RETR "$PendingLoaders[0].MyStatsObject.Name$".txt");
|
|
bFileInProgress = true;
|
|
}
|
|
}
|
|
function DataReceived()
|
|
{
|
|
bFileInProgress = false;
|
|
if( PendingLoaders[0]!=None )
|
|
{
|
|
if( DataConnection!=None )
|
|
PendingLoaders[0].GetData(DataConnection.Data);
|
|
else PendingLoaders[0].GetData("");
|
|
}
|
|
PendingLoaders.Remove(0,1);
|
|
while( PendingLoaders.Length>0 && PendingLoaders[0]==None )
|
|
PendingLoaders.Remove(0,1);
|
|
|
|
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 200: // Type set to A
|
|
SendFTPLine("PASV");
|
|
break;
|
|
case 227: // Entering passive mode
|
|
if( !bFileInProgress )
|
|
InitDataConnection(Line);
|
|
break;
|
|
case 150: // Opening ASCII mode data connection for file
|
|
SetTimer(60,false);
|
|
break;
|
|
case 550: // No such file or directory
|
|
SetTimer(10,false);
|
|
if( bFileInProgress )
|
|
{
|
|
if( DataConnection!=None )
|
|
DataConnection.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,1.f);
|
|
}
|
|
Sleep(0.5);
|
|
}
|
|
}
|
|
state ErrorState
|
|
{
|
|
Ignores SaveAllStats,RequestStats;
|
|
Begin:
|
|
Sleep(1.f);
|
|
Mut.RespawnNetworkLink();
|
|
}
|
|
|
|
static final function int GetDateInt( int Day, int Month, int Year )
|
|
{
|
|
return Day + (Month*31) + (Year*366);
|
|
}
|
|
state GetDir
|
|
{
|
|
function BeginState()
|
|
{
|
|
SendFTPLine("TYPE I");
|
|
bObtainDir = false;
|
|
}
|
|
final function BeginPassiveCon( string S )
|
|
{
|
|
if( bFullVerbose )
|
|
Level.Game.Broadcast(Self,"FTP: Get directory");
|
|
|
|
if( OpenDataConnection(S,false) )
|
|
SendFTPLine("MLSD");
|
|
}
|
|
function DataReceived()
|
|
{
|
|
local array<string> SA;
|
|
local string S;
|
|
local int i,j,Da,Mo,Ye,ol;
|
|
|
|
if( DataConnection!=None )
|
|
{
|
|
Split(DataConnection.Data,Chr(13)$Chr(10),SA);
|
|
ol = TotalList.Length;
|
|
for( i=0; i<SA.Length; ++i )
|
|
{
|
|
S = SA[i];
|
|
j = InStr(S,"modify=");
|
|
if( j==-1 )
|
|
continue;
|
|
S = Mid(S,j+7);
|
|
j = InStr(S,";");
|
|
if( j==-1 )
|
|
continue;
|
|
Ye = int(Mid(S,0,4));
|
|
Mo = int(Mid(S,4,2));
|
|
Da = int(Mid(S,6,2));
|
|
j = InStr(S," ");
|
|
if( j==-1 )
|
|
continue;
|
|
S = Mid(S,j+1);
|
|
if( Right(S,4)!=".txt" )
|
|
continue;
|
|
if( IgnoreDateNum<=GetDateInt(Da,Mo,Ye) )
|
|
Log(S@"is newer: "$Da$"."$Mo$"."$Ye);
|
|
else
|
|
{
|
|
S = Left(S,Len(S)-4);
|
|
for( j=0; j<TotalList.Length; ++j )
|
|
if( TotalList[j]==S )
|
|
{
|
|
TotalList.Remove(j,1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
Level.Game.Broadcast(Self,"Reduced list size: "$ol$" -> "$TotalList.Length);
|
|
}
|
|
CheckNextCommand();
|
|
}
|
|
function ProcessResponse( int Code, string Line )
|
|
{
|
|
switch( Code )
|
|
{
|
|
case 200: // Type set to I
|
|
bIsAsciiMode = false;
|
|
SendFTPLine("PASV");
|
|
break;
|
|
case 227: // Entering passive mode
|
|
BeginPassiveCon(Line);
|
|
break;
|
|
case 150: // Opening ASCII mode data connection for file
|
|
SetTimer(120,false);
|
|
break;
|
|
case 550: // No such file or directory
|
|
SetTimer(10,false);
|
|
if( bFileInProgress )
|
|
{
|
|
if( DataConnection!=None )
|
|
DataConnection.Destroy();
|
|
DataReceived();
|
|
}
|
|
break;
|
|
default:
|
|
Global.ProcessResponse(Code,Line);
|
|
}
|
|
}
|
|
}
|
|
|
|
defaultproperties
|
|
{
|
|
}
|