{*********************************************************

 SlavaNap source code.

 Copyright 2001,2002 by SlavaNap development team
 Released under GNU General Public License

 Latest version is available at
 http://www.slavanap.org

**********************************************************

 Unit: localusers

 Class for local users

*********************************************************}
unit localusers;

interface

{$I defines.pas}

uses
 Windows, Classes2, SysUtils, constants, stypes, blcksock, synsock, winsock, share,
 servers, users, registered, lang, slavastrings;

type
 TLocalUserState = set of (locSwapBytes, locNeedsUpdate, locWriteOnly,
    locFloodWarning, locPingable);
 TLocalUserDetector = set of (loc326, loc110, loc640, loc208,
    loc100, loc870, loc10300, locMD5Zero, locMD5NonZero, locMD5Zeros, locMacDir);
 TLocalUser = class(TObject)
  data            : POnlineUser;
  socket          : HSocket;
  last_seen       : Cardinal;
  hotlist         : TStringHash;
  ignored         : TStringHash;
  out_list        : TStringHash;
  {$IFDEF USERS_DOUBLE_QUEUE}
  out_list2       : TStringHash; // second queue - for search and browse results
  {$ENDIF}
  recv_buf        : String;
  recv_len        : SmallInt;
  SoftwareID      : ShortInt;
  shared          : TShareList;
  shared_mp3,
  shared_audio,
  shared_video,
  shared_cd,
  shared_images,
  shared_text,
  shared_blocked,
  shared_apps     : Word;
  shared_size     : Int64;
  last_search_time : Cardinal;
  searches_count  : SmallInt;
  searchespm      : Integer;
  localstate      : TLocalUserState;
  dlrequestsp3m   : Integer;
  wantqueuep3m    : Integer;
  blocked_incomplete,
  blocked_fakeext,
  blocked_toolong,
  blocked_toolarge,
  blocked_toomany,
  blocked_tooshort,
  blocked_toosmall,
  blocked_tooshortmp3,
  blocked_other   : Word; //blocked_otherはブロックワードによるブロックを含まない
  detector        : TLocalUserDetector;
  last_command_time: Cardinal;
  join_delay: SmallInt; //自動入室までの遅延時間(ミリ秒)
  constructor Create;
  destructor Destroy; override;
  function  logged: Boolean;
  function  ip: Cardinal;
  function  nick: String;
  function  software: String;
  function  level: TNapUserLevel;
  function  AddState(st: TUserState): TUserState;
  function  DelState(st: TUserState): TUserState;
  procedure Exec(id: Integer; cmd: String);
  procedure WriteData(str: String);
  procedure Flush(can_disconnect: Boolean=true);
  function  BufferEmpty: Boolean;
  procedure CheckPong(var id: Integer; var cmd: String);
  procedure Clear;
 end;

function  CreateLocalUser: TLocalUser;
procedure FreeLocalUser(user: TLocalUser);

implementation

uses
 vars, handler, thread, memory_manager;

var
 total_localusers: Integer;
 list_localusers: TMyList;

constructor TLocalUser.Create;
begin
 inherited Create;
 data:=nil;
 socket:=INVALID_SOCKET;
 last_seen:=current_time;
 StrHash_Reset(hotlist);
 StrHash_Reset(ignored);
 StrHash_Reset(out_list);
 SetLength(recv_buf,0);
 recv_len:=0;
 shared:=nil;
 shared_mp3:=0;
 shared_audio:=0;
 shared_video:=0;
 shared_cd:=0;
 shared_images:=0;
 shared_text:=0;
 shared_apps:=0;
 shared_size:=0;
 shared_blocked:=0;
 last_search_time:=0;
 searches_count:=0;
 searchespm:=0;
 dlrequestsp3m:=0;
 wantqueuep3m:=0;
 localstate:=[];
 softwareID:=softUnknown;
 blocked_incomplete :=0;
 blocked_fakeext    :=0;
 blocked_toolong    :=0;
 blocked_toolarge   :=0;
 blocked_toomany    :=0;
 blocked_tooshort   :=0;
 blocked_toosmall   :=0;
 blocked_tooshortmp3:=0;
 blocked_other      :=0;
 detector:=[];
 last_command_time:=current_time;
 join_delay := INITIAL_JOIN_DELAY;
{$IFDEF USERS_DOUBLE_QUEUE}
 StrHash_Reset(out_list2);
{$ENDIF}
end;

destructor TLocalUser.Destroy;
var
 num: Integer;
begin
 num:=0;
 try
   StrHash_Clear(hotlist);
   num:=1;
   StrHash_Clear(ignored);
   num:=2;
   StrHash_Clear(out_list);
   {$IFDEF USERS_DOUBLE_QUEUE}
   StrHash_Clear(out_list2);
   {$ENDIF}
   num:=3;
   if shared<>nil then
   begin
    shared.Free;
    shared:=nil;
   end;
   num:=4;
   DoCloseSocket(socket);
   socket:=INVALID_SOCKET;
   num:=5;
   SetLength(recv_buf,0);
  except
   on E:Exception do
    if running then
     DebugLog('Exception in TLocalUser::Destroy  num='+IntToStr(num)+' : '+E.Message);
 end;
 inherited Destroy;
end;

procedure TLocalUser.Clear;
begin
 DoCloseSocket(socket);
 socket:=INVALID_SOCKET;
 last_seen:=current_time;
 StrHash_Clear(hotlist);
 StrHash_Clear(ignored);
 StrHash_Clear(out_list);
 SetLength(recv_buf,0);
 recv_len:=0;
 if shared<>nil then
 begin
  shared.Free;
  shared:=nil;
 end;
 shared_mp3:=0;
 shared_audio:=0;
 shared_video:=0;
 shared_cd:=0;
 shared_images:=0;
 shared_text:=0;
 shared_apps:=0;
 shared_size:=0;
 shared_blocked:=0;
 last_search_time:=0;
 searches_count:=0;
 searchespm:=0;
 dlrequestsp3m:=0;
 wantqueuep3m:=0;
 localstate:=[];
 softwareID:=softUnknown;
 blocked_incomplete :=0;
 blocked_fakeext    :=0;
 blocked_toolong    :=0;
 blocked_toolarge   :=0;
 blocked_toomany    :=0;
 blocked_tooshort   :=0;
 blocked_toosmall   :=0;
 blocked_tooshortmp3:=0;
 blocked_other      :=0;
 detector:=[];
 last_command_time:=current_time;
 join_delay:=INITIAL_JOIN_DELAY;
{$IFDEF USERS_DOUBLE_QUEUE}
 StrHash_Clear(out_list2);
{$ENDIF}
end;

function  TLocalUser.logged: Boolean;
begin
 Result:=data<>nil;
end;

function  TLocalUser.AddState(st: TUserState): TUserState;
begin
 if data=nil then Result:=[]
 else
 begin
   data^.state:=data^.state+st;
   Result:=data^.state;
 end;
end;

function  TLocalUser.DelState(st: TUserState): TUserState;
begin
 if data=nil then Result:=[]
 else
 begin
   data^.state:=data^.state-st;
   Result:=data^.state;
 end;
end;

function  TLocalUser.nick: String;
begin
 if data=nil then Result:=''
 else Result:=data^.username;
end;

function  TLocalUser.software: String;
begin
 if data=nil then Result:=''
 else Result:=data^.software;
end;

function  TLocalUser.level: TNapUserLevel;
begin
 if data=nil then Result:=napUserUser
 else Result:=data^.level;
end;

function  TLocalUser.ip: Cardinal;
begin
 if data<>nil then Result:=data^.ip
 else if socket<>INVALID_SOCKET then Result:=TCPSocket_GetRemoteSin(socket).sin_addr.S_addr
 else Result:=0;
end;

procedure TLocalUser.CheckPong(var id: Integer; var cmd: String);
var
 list: TMyStringList;
 i: Integer;
 srv: TServer;
 server: String;
begin
 list:=CreateStringList;
 SplitString(cmd,list);
 if list.count=4 then
 if list.Strings[1]='pingall' then
 begin
   server:='';
   if list.Strings[0]=cons.nick then server:=servername_t
   else for i:=0 to db_servers.count-1 do
   begin
     srv:=db_servers.Items[i];
     if srv.logged then
      if srv.console=list.Strings[0] then
        server:=srv.host;
   end;
   if server<>'' then
   begin // pong response thru channel request
     id:=MSG_SERVER_PUBLIC;
     i:=GetTickCount-StrToIntDef(list.Strings[3],GetTickCount);
     cmd:=list.Strings[2]+' Server Pong from '+server+': '+IntToStrDot(i)+'ms.';
   end;
 end;
 FreeStringList(list);
end;

procedure TLocalUser.Exec(id: Integer; cmd: String);
var
 str: String;
begin
 try
  if log_commands then
  begin
    str:='Sending command ['+IntToStr(id)+'] "'+cmd+'" (';
    if nick<>'' then str:=str+nick+', '+software+', ';
    str:=str+decode_ip(ip)+')';
    Log(0,str,true);
  end;
  if id=MSG_SERVER_PONG then CheckPong(id,cmd);
  if self=cons then
  begin
    sync_reply_list.AddDoubleCmd(MSG_SR_CONSOLEREPLY,id,cmd,'');
    exit;
  end;
  str:='    '+cmd;
  if locSwapBytes in localstate then
  begin
    str[2]:=Chr(Length(cmd) and 255);
    str[1]:=Chr(Length(cmd) div 256);
    str[4]:=Chr(id and 255);
    str[3]:=Chr(id div 256);
   end
   else
   begin
     str[1]:=Chr(Length(cmd) and 255);
     str[2]:=Chr(Length(cmd) div 256);
     str[3]:=Chr(id and 255);
     str[4]:=Chr(id div 256);
   end;
   {$IFDEF USERS_DOUBLE_QUEUE}
   case id of
     MSG_SERVER_SEARCH_RESULT, // search results
     MSG_SERVER_SEARCH_END,
     MSG_SERVER_BROWSE_RESPONSE, // browse results
     MSG_SERVER_BROWSE_END,
     MSG_SERVER_RESUME_MATCH, // resume match
     MSG_SERVER_RESUME_MATCH_END,
     MSG_SERVER_GLOBAL_USER_LIST, // users list
     MSG_CLIENT_GLOBAL_USER_LIST: StrHash_AddEx(out_list2,str);
     else StrHash_AddEx(out_list,str);
   end;
   {$ELSE}
   StrHash_AddEx(out_list,str);
   {$ENDIF}
  except
 end;
end;

procedure TLocalUser.WriteData(str: String);
begin
 StrHash_AddEx(out_list,str);
end;

procedure TLocalUser.Flush(can_disconnect: Boolean=true);
var
 t: Cardinal;
 num: Integer;
 cmd: PStringHashItem;
 last_error: Integer;
begin
 if self=cons then exit;
 if socket=INVALID_SOCKET then
 begin
   StrHash_Clear(out_list);
   exit;
 end;
 {$IFDEF USERS_DOUBLE_QUEUE}
 if (out_list.count=0) and (out_list2.count=0) then exit;
 {$ELSE}
 if out_list.count=0 then exit;
 {$ENDIF}
 num:=0;
 if (logged=true) and (CanSend(false)=false) then exit;
 cmd:=out_list.first;
 {$I checksync.pas}
 while cmd<>nil do
 begin
   TCPSocket_SendString(socket,cmd^.data,last_error);
   if last_error=WSAEWOULDBLOCK then exit;
   if last_error<>0 then
   begin
     if can_disconnect then
      DisconnectUser(self,'',GetLangT(LNG_DISCONNECT_SOCKETERR,nick,software,IntToStr(last_error),GetErrorDesc(last_error)),'Flush (2)',true);
     exit;
   end;
   inc(bytes_out,Length(cmd^.data));
   inc(bandwidth_up,Length(cmd^.data));
   StrHash_DeleteFirst(out_list);
   cmd:=out_list.first;
   inc(num);
   if ((num mod 10)=9) then
   begin
    {$I checksync.pas}
   end;
   if (logged=true) and (CanSend(false)=false) then exit;
 end;
 {$IFDEF USERS_DOUBLE_QUEUE}
 cmd:=out_list2.first;
 while cmd<>nil do
 begin
   TCPSocket_SendString(socket,cmd^.data,last_error);
   if last_error=WSAEWOULDBLOCK then exit;
   if last_error<>0 then
   begin
     if can_disconnect then
      DisconnectUser(self,'',GetLangT(LNG_DISCONNECT_SOCKETERR,nick,software,IntToStr(last_error),GetErrorDesc(last_error)),'Flush (4)',true);
     exit;
   end;
   inc(bytes_out,Length(cmd^.data));
   inc(bandwidth_up,Length(cmd^.data));
   StrHash_DeleteFirst(out_list2);
   cmd:=out_list2.first;
   inc(num);
   if ((num mod 10)=9) then
   begin
    {$I checksync.pas}
   end;
   if not CanSend(false) then exit;
 end;
 {$ENDIF}
 if locWriteOnly in localstate then
  last_seen:=SetSocketCloseTime;
end;

function  TLocalUser.BufferEmpty: Boolean;
begin
 {$IFDEF USERS_DOUBLE_QUEUE}
 Result:=true;
 if out_list.count>9 then Result:=false;
 if out_list2.count>0 then Result:=false; 
 {$ELSE}
 result:=out_list.count<10;
 {$ENDIF}
end;

function  CreateLocalUser: TLocalUser;
begin
  if list_localusers.count>0 then
  begin
   Result:=list_localusers.Items[list_localusers.count-1];
   list_localusers.Delete(list_localusers.count-1);
  end
  else
  begin
   Result:=TLocalUser.Create;
   inc(total_localusers);
  end;
  result.last_seen:=current_time;
end;

procedure FreeLocalUser(user: TLocalUser);
begin
 user.Clear;
 list_localusers.Add(user);
end;

procedure ClearUsersList;
var
 i: Integer;
begin
 for i:=0 to list_localusers.count-1 do
  TLocalUser(list_localusers.Items[i]).Free;
end;

initialization
begin
 total_localusers:=0;
 list_localusers:=TMyList.Create;
end;

finalization
begin
 ClearUsersList;
 list_localusers.Free;
 total_localusers:=0;
end;

end.
