{
 this file is part of Ares
 Aresgalaxy ( http://aresgalaxy.sourceforge.net )

  This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public License
  as published by the Free Software Foundation; either
  version 2 of the License, or (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 }

{
Description:
obtain list of available channels(client) and add/update and entry in it(server)

20 Nov 08 channellist is now shared among chatrooms using UDP protocol

}


unit helper_channellist;

interface

uses
  Classes,utility_ares,zlib,windows,const_ares,
  blcksock,sysutils,ares_types,comettrees,winsock,
  synsock,classes2,registry,tntwindows,math,cometPageView;

const
  OP_SERVERLIST_SENDINFO=2;
  OP_SERVERLIST_ACKINFO=3;
  MAX_SERVERS_TO_SCAN=5000;
  MAX_SAVED_SERVERS=400;

type
precord_server_list=^record_server_list;
record_server_list=record
 ipC:cardinal;
 portW:word;
 acked:boolean;
end;

type
 tthread_udp_channellist = class(tthread)
 protected
  UDP_socket:Hsocket;
  UDP_Buffer:array[0..9999] of byte;
  UDP_RemoteSin:TVarSin;
  UDP_len_recvd:integer;
  serverIPlist:tmylist;
    ipC:cardinal;
    portW,usersW:word;
    chname,topic:string;
    stripped_topic:widestring;
    has_colors_intopic:boolean;
    buildNo:word;
    filtered_strings:tmystringlist;
    has_prepared_first:boolean;
    index_scan:integer;
  procedure createListener;
  procedure execute; override;
  procedure UDP_Receive;
  procedure loadIPs;
  procedure handler_channel_info;
  procedure add_channel;//synch
  procedure GUI_searching;
  procedure WriteChannelsToDisk;
  procedure prepare_header;//synch
  procedure endSearch;  //synch
  procedure finalizeIPs;
  procedure parse_alt_servers(index:integer; numEntries:integer);
  procedure readOwnConf;
 end;

  procedure clear_chanlist_backup;
  procedure mainGui_trigger_channelfilter;
  procedure export_channellist;
  procedure export_channel_hashlink;
  function channel_to_arlnk(chan:precord_displayed_channel; plaintext:boolean=false):string;
  procedure join_arlnk_chat(serialized:string; isPlaintext:boolean=false);
  procedure join_channel(datas:precord_displayed_channel);
  function fav_channel_to_arlnk(chan:precord_chat_favorite; plaintext:boolean=false):string;
  procedure export_favorite_channel_hashlink; //export single channel hashlink
  function add_channel(ip,alt_ip:cardinal; port,users:word; const chname,topic:string;
  stripped_topic:widestring; has_colors_intopic:boolean; addBackup:boolean=true; checkFilter:boolean=true; killduplicates:boolean=true; buildNo:word=0):boolean;

  procedure add_channel_fromreg;
  procedure ChatListPutStats;
  function channellist_find_root(ip:cardinal; var Oldchildnode:pcmtvnode):pcmtvnode;
  function Chatlist_GetUserStatStr(node:PcmtVnode):string;
  function checkChatUserFilter(split_string:Tmystringlist; const matchStr:string):boolean;
  function chatlist_getrealcount:integer;
  procedure add_mandatory_channels;
  procedure strip_tags_from_name(var sname:string; var stopic:string);

implementation

uses
 ufrmmain,helper_crypt,const_timeouts,helper_unicode,vars_localiz,
 helper_strings,helper_sockets,helper_ipfunc,helper_chatroom,
 vars_global,helper_sorting,
 helper_base64_32,helper_chatroom_gui,helper_gui_misc,helper_datetime,
 thread_client_chat,helper_diskio,helper_filtering,helper_registry,
 helper_urls;

procedure tthread_udp_channellist.createListener;
var
sin:TVarSin;
begin
FillChar(Sin, Sizeof(Sin), 0);
 Sin.sin_family:=AF_INET;
 Sin.sin_port:=synsock.htons(vars_global.myport);
 Sin.sin_addr.s_addr:=0;

 UDP_socket:=synsock.socket(PF_INET,0,IPPROTO_UDP);

{
 x:=1; other processes are already using our UDP local endpoint?
 synsock.SetSockOpt(DHT_socket, SOL_SOCKET, SO_REUSEADDR, @x, SizeOf(x));
 }

 synsock.Bind(UDP_socket,@Sin,SizeOfVarSin(Sin));
end;

procedure tthread_udp_channellist.loadIPs;
var
 stream:thandleStream;
 str_tot:string;
 str:string;
 lun,i:integer;
 aServer:precord_server_list;
 buffer:array[0..2047] of char;
 ipC:cardinal;
 portW:word;
 found:boolean;
begin
serverIPlist:=tmylist.create;

if (not fileexistsW(vars_global.data_path+'\Data\ChatroomIPs.dat')) or
   (helper_registry.reg_first_load_chatroom) or
   (GetHugeFileSize(vars_global.data_path+'\Data\ChatroomIPs.dat')<60) then
      stream:=MyFileOpen(vars_global.app_path+'\Data\ChatroomIPs.dat',ARES_READONLY_BUT_SEQUENTIAL)
       else
      stream:=MyFileOpen(vars_global.data_path+'\Data\ChatroomIPs.dat',ARES_READONLY_BUT_SEQUENTIAL);

      if stream=nil then exit;

      str_tot:='';
      with stream do begin
        while (position+1<size) do begin
         lun:=read(buffer,sizeof(buffer));
         setlength(str,lun);
         move(buffer,str[1],lun);
          str_tot:=str_tot+
                   str;
        end;
      end;
      FreeHandleStream(stream);

while (length(str_tot)>=6) do begin
 str:=copy(str_tot,1,6);
      delete(str_tot,1,6);
 ipC:=chars_2_dword(copy(str,1,4));
 portW:=chars_2_word(copy(str,5,2));
 if ipC=0 then continue;
 if portW=0 then continue;
 if ip_firewalled(ipC) then continue;

 found:=false;
 for i:=0 to serverIPlist.count-1 do begin
  aServer:=serverIPlist[i];
  if aServer^.ipC=ipC then begin
   found:=true;
   break;
  end;
 end;
 if found then continue;
 aServer:=AllocMem(sizeof(record_server_list));
  aServer^.ipC:=ipC;
  aServer^.portW:=portW;
  aServer^.acked:=false;
 serverIPlist.add(aServer);
end;

shuffle_mylist(serverIPlist,0);

index_scan:=0;
end;

procedure tthread_udp_channellist.execute;
var
 lastSend,endTime,startTime:cardinal;
 aServer:precord_server_list;
 UDP_len_tosend:integer;
begin
priority:=tpnormal;
freeonterminate:=true;

has_prepared_first:=false;
createListener;
LoadIPs;

filtered_strings:=tmystringlist.create;
init_keywfilter('ChanListFilter',filtered_strings);

synchronize(GUI_searching);

endTime:=0;
lastSend:=0;
startTime:=gettickcount;

while (not terminated) do begin
 UDP_Receive;
 sleep(10);

 if gettickcount-lastSend<200 then continue;

  lastSend:=gettickcount;
  if lastSend-startTime>=300000 then begin //running for over 5 minutes?
   terminate;
   break;
  end;

  if index_scan>=serverIPlist.count then begin
    if endTime=0 then endTime:=lastSend else
     if lastSend-endTime>15000 then begin
      terminate;
      break;
     end;
  end else begin
    aServer:=serverIPlist[index_scan];
    inc(index_scan);

    UDP_RemoteSin.sin_family:=AF_INET;
    UDP_RemoteSin.sin_port:=synsock.htons(aServer^.portW);
    UDP_RemoteSin.sin_addr.s_addr:=aServer^.ipC;
    UDP_Buffer[0]:=OP_SERVERLIST_SENDINFO;
    UDP_len_tosend:=1;
  
    synsock.SendTo(UDP_socket,UDP_buffer,UDP_len_tosend,0,@UDP_RemoteSin,SizeOf(UDP_RemoteSin));
  end;

end;

WriteChannelsToDisk;
finalizeIPs;
TCPSocket_Free(UDP_socket);
filtered_strings.free;
synchronize(endSearch);
end;

procedure tthread_udp_channellist.finalizeIPs;
var
 aServer:precord_server_list;
begin
 while (serverIPlist.count>0) do begin
  aServer:=serverIPlist[serverIPlist.count-1];
           serverIPlist.delete(serverIPlist.count-1);
  FreeMem(aServer,sizeof(record_server_list));
 end;
serverIPlist.free;
end;

procedure tthread_udp_channellist.endSearch;  //synch
begin
helper_channellist.chatListPutStats;
end;

procedure tthread_udp_channellist.handler_channel_info;
var
 index:integer;
 lenW:word;
 str,lostr:string;
 widest:widestring;
 i,numEntries:integer;
 aServer:precord_server_list;
begin
// set to 'acked'
for i:=0 to serverIPlist.count-1 do begin
 aServer:=serverIPlist[i];
 if aServer^.ipC=cardinal(UDP_remoteSin.sin_addr.S_addr) then begin
  aServer^.acked:=true;
  break;
 end;
end;

index:=1;
ipC:=UDP_remoteSin.sin_addr.S_addr;
move(UDP_buffer[index],portW,2);
 inc(index,2);
move(UDP_buffer[index],usersW,2);
 inc(index,2);

//name
move(UDP_buffer[index],lenW,2);
if lenW<MIN_CHAT_NAME_LEN then begin
 exit;
end;
if lenW>MAX_CHAT_NAME_LEN then begin
 exit;
end;

 inc(index,2);
setLength(str,lenW);
move(UDP_buffer[index],str[1],lenW);
 inc(index,lenW);
chname:=str;


//topic
move(UDP_buffer[index],lenW,2);
if lenW>MAX_CHAT_TOPIC_LEN then begin
 exit;
end;

 inc(index,2);
setLength(str,lenW);
move(UDP_buffer[index],str[1],lenW);
 inc(index,lenW);
topic:=str;

inc(index); //skip language byte
move(UDP_buffer[index],lenW,2);
inc(index,2);
setLength(str,lenW);
move(UDP_buffer[index],str[1],lenW);

inc(index,lenW); //skip version

numEntries:=UDP_buffer[index];
inc(index);

if numEntries>10 then numEntries:=10;
if numEntries>0 then parse_alt_servers(index,numEntries);

  
widest:=utf8strtowidestr(chname)+' '+utf8strtowidestr(topic);
normalize_special_unicode(widest);

lostr:=lowercase(widestrtoutf8str(widest));
if is_filtered_text(lostr,filtered_strings) then begin
 exit;  //filtering?
end;

strip_Tags_From_Name(chname,topic);
if length(chname)<4 then begin
 exit; //another can't be
end;
 stripped_topic:=strip_color_string(utf8strtowidestr(topic),has_colors_intopic);
 synchronize(add_channel);
end;

procedure tthread_udp_channellist.parse_alt_servers(index:integer; numEntries:integer);
var
 i,h:integer;
 aServer:precord_server_list;
 ipC:cardinal;
 portW:word;
 found:boolean;
begin
if serverIPlist.count>=MAX_SERVERS_TO_SCAN then exit; //hardlimit

 for i:=1 to numEntries do begin
  move(UDP_buffer[index],ipC,4);
  move(UDP_buffer[index+4],portW,2);
  inc(index,6);
   found:=false;
   for h:=0 to serverIPlist.count-1 do begin
    aServer:=serverIPlist[h];
    if aServer^.ipC=ipC then begin
     found:=true;
     break;
    end;
   end;
   if not found then begin
    aServer:=AllocMem(sizeof(record_server_list));
     aServer^.ipC:=ipC;
     aServer^.portW:=portW;
     aServer^.acked:=false;
    serverIPlist.add(aServer);
   end;

 end;
end;

procedure tthread_udp_channellist.add_channel;//synch
begin
if not has_prepared_first then begin
 has_prepared_first:=true;
 prepare_header;
end;

 if helper_channellist.add_channel(ipC,
                                   0,
                                   portW,
                                   usersW,
                                   chname,
                                   topic,
                                   stripped_topic,
                                   has_colors_intopic,
                                   true,
                                   true,
                                   true,
                                   buildNo) then begin
 if ares_frmmain.listview_chat_channel.header.sortcolumn>=0 then
 ares_frmmain.listview_chat_channel.sort(nil,ares_frmmain.listview_chat_channel.header.sortcolumn,ares_frmmain.listview_chat_channel.header.sortdirection);
end;
end;

procedure tthread_udp_channellist.WriteChannelsToDisk;
var
 buffer:array[0..1023] of byte;
 strin:string;
 doneCount,i:integer;
 db:thandleStream;
 aServer:precord_server_list;
begin
try
db:=MyFileOpen(data_path+'\Data\ChatroomIPs.dat',ARES_OVERWRITE_EXISTING);
if db=nil then exit;

doneCount:=0;
for i:=0 to serverIPlist.count-1 do begin
 aServer:=serverIPlist[i];
 if not aServer^.acked then continue;
 strin:=int_2_dword_string(aServer^.ipC)+
        int_2_word_string(aServer^.portW);
 move(strin[1],buffer,length(strin));
 db.write(buffer,length(strin));
 inc(doneCount);
 if doneCount>=MAX_SAVED_SERVERS then break;
end;

FreeHandleStream(db);
except
end;
end;

procedure tthread_udp_channellist.UDP_Receive;
var
 er,len:integer;
begin

 if not TCPSocket_canRead(UDP_socket,0,er) then exit;
 Len:=SizeOf(UDP_RemoteSin);

 UDP_len_recvd:=synsock.RecvFrom(UDP_socket,
                                 UDP_Buffer,
                                 sizeof(UDP_buffer),
                                 0,
                                 @UDP_RemoteSin,
                                 Len);


 if UDP_len_recvd<10 then begin
  exit;
 end;

 if isAntiP2PIP(UDP_remoteSin.sin_addr.S_addr) then begin
  exit;
 end;

 if ip_firewalled(UDP_remoteSin.sin_addr.S_addr) then begin
  exit;
 end;
 if probable_fw(UDP_remoteSin.sin_addr.S_addr) then begin
  exit;
 end;
 if UDP_buffer[0]<>OP_SERVERLIST_ACKINFO then begin
  exit;
 end;
 handler_channel_info;
end;

procedure strip_tags_from_name(var sname:string; var stopic:string);
var
i:integer;
lochname:string;
begin


while true do begin
 lochname:=lowercase(sname);

 i:=pos(' -',lochname);
 if i>0 then begin
  delete(sname,i,3);
  continue;
 end;

 i:=pos(' ',lochname);
 if i>0 then begin
  delete(sname,i,2);
  continue;
 end;

 i:=pos('[is]',lochname);
 if i>0 then begin
  delete(sname,i,4);
  stopic:='[is] '+stopic;
  continue;
 end;

 i:=pos('[asax]',lochname);
 if i>0 then begin
  delete(sname,i,6);
  stopic:='[ASAX] '+stopic;
  continue;
 end;

 i:=pos('[dg-x]',lochname);
 if i>0 then begin
  delete(sname,i,6);
  stopic:='[Dg-x] '+stopic;
  continue;
 end;

 i:=pos('[Σk]',lochname);
 if i>0 then begin
  delete(sname,i,5);
  stopic:='[ΣK] '+stopic;
  continue;
 end;

 i:=pos('[ae]',lochname);
 if i>0 then begin
  delete(sname,i,4);
  stopic:='[AE] '+stopic;
  continue;
 end;

 i:=pos(chr(160),lochname);
 if i>0 then delete(lochname,i,1);

 break;
end;

end;


function channel_to_arlnk(chan:precord_displayed_channel; plaintext:boolean=false):string;
var
str:string;
begin
if plaintext then begin
 result:='arlnk://Chatroom:'+ipint_to_dotstring(chan^.ip)+':'+inttostr(chan^.port)+'|'+
         chan^.name+' ('+ipint_to_dotstring(chan^.alt_ip)+') '+
         widestrtoutf8str(chan^.stripped_topic);
 exit;
end;

   str:=CHRNULL+CHRNULL+CHRNULL+CHRNULL+
        CHRNULL+CHRNULL+CHRNULL+CHRNULL+
        CHRNULL+CHRNULL+CHRNULL+CHRNULL+
        CHRNULL+CHRNULL+CHRNULL+CHRNULL+
        CHRNULL+CHRNULL+CHRNULL+CHRNULL+
        'CHATCHANNEL'+CHRNULL+
        int_2_dword_string(chan^.ip)+
        int_2_word_string(chan^.port)+
        int_2_dword_string(chan^.alt_ip)+
        chan^.name+CHRNULL+
        //chan^.topic+    // 12/26/2005 removed topic , shorter hashlink
        CHRNULL;

str:=zcompressstr(str);
str:=e67(str,28435);

result:='arlnk://'+encodebase64(str);
end;

function fav_channel_to_arlnk(chan:precord_chat_favorite; plaintext:boolean=false):string;
var
str:string;
begin
if plaintext then begin
 result:='arlnk://Chatroom:'+ipint_to_dotstring(chan^.ip)+':'+inttostr(chan^.port)+'|'+chan^.name+' ('+ipint_to_dotstring(chan^.alt_ip)+')';
 exit;
end;

   str:=CHRNULL+CHRNULL+CHRNULL+CHRNULL+
        CHRNULL+CHRNULL+CHRNULL+CHRNULL+
        CHRNULL+CHRNULL+CHRNULL+CHRNULL+
        CHRNULL+CHRNULL+CHRNULL+CHRNULL+
        CHRNULL+CHRNULL+CHRNULL+CHRNULL+
        'CHATCHANNEL'+CHRNULL+
        int_2_dword_string(chan^.ip)+
        int_2_word_string(chan^.port)+
        int_2_dword_string(chan^.alt_ip)+
        chan^.name+CHRNULL+
        //chan^.topic+
        CHRNULL;

str:=zcompressstr(str);
str:=e67(str,28435);

result:='arlnk://'+encodebase64(str);
end;



procedure join_arlnk_chat(serialized:string; isPlaintext:boolean=false);
var
ip,alt_ip:cardinal;
locrc,port:word;
chname,topic,ips,lochname,urlDeco:string;
chan:precord_displayed_channel;
has_colors_intopic:boolean;
begin

if isPlainText then begin
 ips:=copy(serialized,1,pos(':',serialized)-1);
 ip:=inet_addr(pchar(ips));
     delete(serialized,1,pos(':',serialized));
 port:=strtointdef(copy(serialized,1,pos('|',serialized)-1),0);
     delete(serialized,1,pos('|',serialized));
 if serialized[length(serialized)]='/' then delete(serialized,length(serialized),1);
 urlDeco:=helper_urls.urldecode(serialized);

 if length(urlDeco)=length(serialized) then chname:=serialized
  else chname:=urldeco;  //browser urlencodes UTF-8

 topic:='';
 alt_ip:=0;
end else begin
 ip:=chars_2_dword(copy(serialized,1,4));
 port:=chars_2_word(copy(serialized,5,2));
 alt_ip:=chars_2_dword(copy(serialized,7,4));
 if port=0 then exit;
 if ip=0 then exit;

 delete(serialized,1,10);
  chname:=copy(serialized,1,pos(CHRNULL,serialized)-1);
 if length(chname)<MIN_CHAT_NAME_LEN then exit;

 delete(serialized,1,pos(CHRNULL,serialized));
  topic:=copy(serialized,1,pos(CHRNULL,serialized)-1);
  ips:=ipint_to_dotstring(ip);
end;

 lochname:=lowercase(chname);
 locrc:=stringcrc(lochname,true);

 chan:=AllocMem(sizeof(record_displayed_channel));
  chan^.ip:=ip;
  chan^.port:=port;
  chan^.name:=chname;
  chan^.locrc:=locrc;
  chan^.topic:=topic;
  chan^.stripped_topic:=strip_color_string(utf8strtowidestr(topic),has_colors_intopic);
  chan^.has_colors_intopic:=has_colors_intopic;
  chan^.users:=1; //don't know...
  chan^.alt_ip:=alt_ip;

 join_channel(chan);
 if ares_frmmain.tabs_pageview.activepage<>IDTAB_CHAT then ares_frmmain.tabs_pageview.activepage:=IDTAB_CHAT;

 with chan^ do begin
  name:='';
  topic:='';
  stripped_topic:='';
 end;
  FreeMem(chan,sizeof(record_displayed_channel));
end;


//join channel triggered event
procedure join_channel(datas:precord_displayed_channel);
var
i:integer;
pcanale:precord_canale_chat_visual;
begin
 try

with ares_frmmain do begin

  for i:=0 to list_chatchan_visual.count-1 do begin
     pcanale:=list_chatchan_visual[i];
    if ((pcanale^.ip=datas^.ip) and
        ((pcanale^.alt_ip=datas^.alt_ip) and (pcanale^.port=datas^.port))) then begin
     panel_chat.activepanel:=pcanale^.containerPageview;
     exit;
    end;
  end;

                                         //ora inviamo in widestring
Pcanale:=channel_create_visuals(utf8strtowidestr(datas^.name),datas^.topic);
 with pcanale^ do begin
  ip:=datas^.ip;
  just_created:=true;
  alt_ip:=datas^.alt_ip;
  port:=datas^.port;
  out_text:=tmystringlist.create;
  support_pvt:=false; //di default non supportiamo extra features
  support_files:=false;
 end;
  list_chatchan_visual.add(pcanale);

  panel_chat.activepage:=panel_chat.PanelsCount-1;
  mainGui_togglechats(pcanale, false, false,nil);

  helper_chatroom_gui.out_text_memo(pcanale^.memo,COLORE_NOTIFICATION,'',GetLangStringW(STR_CONNECTING_PLEASE_WAIT));

 if client_chat<>nil then exit;
 client_chat:=tthread_client_chat.create(false);

 end;

except
end;
end;

procedure export_favorite_channel_hashlink; //export single channel hashlink
var
node:pcmtvnode;
chan:precord_chat_favorite;
buffer:array[0..500] of char;
stream:thandlestream;
str:string;
filenw:widestring;
begin
with ares_frmmain do begin
  node:=treeview_chat_favorites.getfirstselected;
  if node=nil then exit;

 filenw:=vars_global.data_path+'\Temp\'+formatdatetime('mm-dd-yyyy hh.nn.ss',now)+' Channel Hashlink.txt';

   tntwindows.Tnt_createdirectoryW(pwidechar(data_path+'\Temp'),nil);

    stream:=MyFileOpen(filenw,ARES_CREATE_ALWAYSAND_WRITETHROUGH);
    if stream=nil then exit;

 with stream do begin
   chan:=treeview_chat_favorites.getdata(node);
     str:=chan^.name+CRLF+
          fav_channel_to_arlnk(chan)+CRLF+CRLF;
   move(str[1],buffer,length(str));
   write(buffer,length(str));
 end;
 FreeHandleStream(stream);
end;

 Tnt_ShellExecuteW(0,'open',pwidechar(widestring('notepad')),pwidechar(filenw),nil,SW_SHOW);
end;

procedure export_channel_hashlink; //export single channel hashlink
var
node:pcmtvnode;
chan:precord_displayed_channel;
buffer:array[0..500] of char;
stream:thandlestream;
str:string;
filenw:widestring;
begin
with ares_frmmain do begin
  node:=listview_chat_channel.getfirstselected;
  if node=nil then exit;

 filenw:=vars_global.data_path+'\Temp\'+formatdatetime('mm-dd-yyyy hh.nn.ss',now)+' Channel Hashlink.txt';

   tntwindows.Tnt_createdirectoryW(pwidechar(data_path+'\Temp'),nil);

    stream:=MyFileOpen(filenw,ARES_CREATE_ALWAYSAND_WRITETHROUGH);
    if stream=nil then exit;

 with stream do begin
   chan:=listview_chat_channel.getdata(node);
     str:=chan^.name+CRLF+
          channel_to_arlnk(chan)+CRLF;

    if vars_global.IDEIsRunning then 
     str:=str+
          channel_to_arlnk(chan,true)+CRLF+CRLF
         else
         str:=str+CRLF;

   move(str[1],buffer,length(str));
   write(buffer,length(str));
 end;
 FreeHandleStream(stream);
end;

 Tnt_ShellExecuteW(0,'open',pwidechar(widestring('notepad')),pwidechar(filenw),nil,SW_SHOW);
end;

// export channellist , this list may be of some use to website-chat owners
procedure export_channellist;
var
node:pcmtvnode;
chan:precord_displayed_channel;
buffer:array[0..1023] of char;
stream:thandlestream;
str:string;
filenw:widestring;
begin
with ares_frmmain do begin

if listview_chat_channel.rootnodecount=0 then exit;
 filenw:=vars_global.data_path+'\Temp\'+formatdatetime('mm-dd-yyyy hh.nn.ss',now)+' Ares ChannelList.txt';

   tntwindows.Tnt_createdirectoryW(pwidechar(data_path+'\Temp'),nil);


    stream:=MyFileOpen(filenw,ARES_CREATE_ALWAYSAND_WRITETHROUGH);
    if stream=nil then exit;

 with stream do begin

  node:=listview_chat_channel.getfirst;
  while (node<>nil) do begin

   if node.childcount>0 then begin
    node:=listview_chat_channel.getnext(node);
    continue;
   end;

   chan:=listview_chat_channel.getdata(node);
     str:=chan^.name+CRLF+
          chan^.topic+CRLF+
          inttostr(chan^.users)+CRLF+
          channel_to_arlnk(chan)+CRLF;

     if vars_global.IDEIsRunning then str:=str+
                               channel_to_arlnk(chan,true)+CRLF
          else
          str:=str+CRLF;

          try
    move(str[1],buffer,length(str));
    write(buffer,length(str));
         except
         end;

      node:=listview_chat_channel.getnext(node);
  end;

 end;
 FreeHandleStream(stream);
end;

 Tnt_ShellExecuteW(0,'open',pwidechar(widestring('notepad')),pwidechar(filenw),nil,SW_SHOW);
end;

procedure clear_chanlist_backup;
var
canale:precord_displayed_channel;
begin

while (vars_global.chat_chanlist_backup.count>0) do begin
 canale:=vars_global.chat_chanlist_backup[vars_global.chat_chanlist_backup.count-1];
    vars_global.chat_chanlist_backup.delete(vars_global.chat_chanlist_backup.count-1);
      with canale^ do begin
       topic:='';
       name:='';
       stripped_topic:='';
      end;
FreeMem(canale,sizeof(record_displayed_channel));
end;

end;

procedure mainGui_trigger_channelfilter;
var
i:integer;
canale:precord_displayed_channel;
search_str:string;
split_string,filtered_strings:tmystringlist;
added:integer;
begin

with ares_frmmain do begin
 with listview_chat_channel do begin
   BeginUpdate;
   Clear;

if length(edit_chat_chanfilter.text)<1 then begin
  for i:=0 to vars_global.chat_chanlist_backup.count-1 do begin
    canale:=vars_global.chat_chanlist_backup[i];
    add_channel(canale^.ip,
                canale^.alt_ip,
                canale^.port,
                canale^.users,
                canale^.name,
                canale^.topic,
                canale^.stripped_topic,
                canale^.has_colors_intopic,
                false,false,false,canale^.buildNo);
  end;
  if header.sortcolumn>=0 then sort(nil,header.sortcolumn,header.sortdirection);
  EndUpdate;
  (ares_frmmain.panel_chat.Panels[0] as TCometPagePanel).btncaption:=GetLangStringW(STR_CHANNELS)+' ('+inttostr(vars_global.chat_chanlist_backup.count)+')';
  ares_frmmain.panel_chat.invalidate;
    ares_frmmain.edit_chat_chanfilter.glyphindex:=12;
    ares_frmmain.edit_chat_chanfilter.text:='';
exit;
end;

   ares_frmmain.edit_chat_chanfilter.glyphIndex:=11;
   search_str:=lowercase(widestrtoutf8str(edit_chat_chanfilter.text));



 filtered_strings:=tmystringlist.create;
 init_keywfilter('ChanListFilter',filtered_strings);
 if is_filtered_text(search_str,filtered_strings) then begin
 filtered_strings.free;
      for i:=0 to vars_global.chat_chanlist_backup.count-1 do begin
      canale:=vars_global.chat_chanlist_backup[i];
      add_channel(canale^.ip,
                  canale^.alt_ip,
                  canale^.port,
                  canale^.users,
                  canale^.name,
                  canale^.topic,
                  canale^.stripped_topic,
                  canale^.has_colors_intopic,
                  false,false,false,canale^.buildNo);
   end;
   if header.sortcolumn>=0 then sort(nil,header.sortcolumn,header.sortdirection);
   EndUpdate;
   (ares_frmmain.panel_chat.Panels[0] as TCometPagePanel).btncaption:=GetLangStringW(STR_CHANNELS)+' ('+inttostr(vars_global.chat_chanlist_backup.count)+')';
    ares_frmmain.panel_chat.invalidate;
    exit;
 end else filtered_strings.free;

 

   split_string:=tmystringlist.create;
   SplitString(search_str,split_string);
   added:=0;

for i:=0 to vars_global.chat_chanlist_backup.count-1 do begin
    canale:=vars_global.chat_chanlist_backup[i];

    if not checkChatUserFilter(split_string,lowercase(canale^.name+' '+canale^.topic)) then continue;

    add_channel(canale^.ip,
                canale^.alt_ip,
                canale^.port,
                canale^.users,
                canale^.name,
                canale^.topic,
                canale^.stripped_topic,
                canale^.has_colors_intopic,
                false,false,false,canale^.buildNo);
    inc(added);
    
end;

if header.sortcolumn>=0 then sort(nil,header.sortcolumn,header.sortdirection);
endupdate;
end;
end;

split_string.free;

if ares_frmmain.listview_chat_channel.rootnodecount=cardinal(vars_global.chat_chanlist_backup.count) then
(ares_frmmain.panel_chat.Panels[0] as TCometPagePanel).btncaption:=GetLangStringW(STR_CHANNELS)+' ('+inttostr(vars_global.chat_chanlist_backup.count)+')'
else
 (ares_frmmain.panel_chat.Panels[0] as TCometPagePanel).btncaption:=GetLangStringW(STR_CHANNELS)+' ('+inttostr(added)+'/'+inttostr(vars_global.chat_chanlist_backup.count)+')';

  ares_frmmain.panel_chat.invalidate;
end;

procedure ChatListPutStats;
begin

with ares_frmmain do begin

 with listview_chat_channel do begin
  //endupdate;
    if vars_global.chat_chanlist_backup.count=0 then
     if RootNodeCount=1 then clear;
     
    if header.sortcolumn>=0 then sort(nil,header.sortcolumn,header.sortdirection);

    edit_chat_chanfilter.enabled:=true; //impediamo search while listing
    if ((length(edit_chat_chanfilter.text)>1) and (edit_chat_chanfilter.glyphindex<>12) and (edit_chat_chanfilter.glyphindex>0)) then (ares_frmmain.panel_chat.Panels[0] as TCometPagePanel).btncaption:=GetLangStringW(STR_CHANNELS)+' ('+inttostr(chatlist_getrealcount)+'/'+inttostr(vars_global.chat_chanlist_backup.count)+')'
     else (ares_frmmain.panel_chat.Panels[0] as TCometPagePanel).btncaption:=GetLangStringW(STR_CHANNELS)+' ('+inttostr(vars_global.chat_chanlist_backup.count)+')';
     ares_frmmain.panel_chat.invalidate;

 end;
end;
end;

procedure add_mandatory_channels;
var
i:integer;
pcanale:precord_canale_chat_visual;

ip,alt_ip:cardinal;
topic,chname:string;
port,users:word;
has_colors_intopic:boolean;
stripped_topic:widestring;
begin
 /////////////////////////////// add all the channels I'm in
  for i:=0 to list_chatchan_visual.count-1 do begin  //ora aggiungiamo tutti i canali nei quali sono dentro, evviva imbecilli!!
   pcanale:=list_chatchan_visual[i];

    alt_ip:=pcanale^.alt_ip;
    ip:=pcanale^.ip;
    port:=pcanale^.port;
    users:=pcanale^.listview.RootNodeCount;
    if users<1 then users:=1;
     chname:=pcanale^.name;
     topic:=pcanale^.topic;
     stripped_topic:=strip_color_string(utf8strtowidestr(topic),has_colors_intopic);
       helper_channellist.add_channel(ip,alt_ip,port,users,chname,topic,stripped_topic,has_colors_intopic);
  end;
 //////////////////////////////////////////////////////////////////////////////////////////////////
end;

function chatlist_getrealcount:integer;
var
node:pcmtVnode;
begin
result:=0;

with ares_frmmain.listview_chat_channel do begin

 node:=getfirst;
 while (node<>nil) do begin
  if node.childcount=0 then inc(result);
  node:=getNext(node);
 end;

end;

end;


procedure tthread_udp_channellist.prepare_header;//synch
begin
with ares_frmmain do begin
 with listview_chat_channel do begin
   beginupdate;

   Clear;
   canbgcolor:=true;
   selectable:=true;
   with header.columns do begin
    Items[0].text:=GetLangStringW(STR_NAME);
    Items[1].text:=GetLangStringW(STR_USERS);
    Items[2].text:=GetLangStringW(STR_TOPIC);
     Items[0].width:=gettextwidth( Items[0].text,ares_FrmMain.canvas)+30;
    if Items[0].width<200 then Items[0].width:=200;
    Items[1].width:=gettextwidth(Items[1].text,ares_FrmMain.canvas)+30;
    Items[2].width:=(listview_chat_channel.width-(Items[1].width+Items[0].width))-60;
   end;
   endupdate;
 end;

end;
end;

procedure tthread_udp_channellist.readOwnConf;
var
 tof:Textfile;
 lineStr,varName,varValue:string;
  widest:widestring;
begin
try


if not fileExistsW(app_path+'\Data\ChatConf.txt') then exit;
portW:=0;
chname:='';
SetCurrentDirectoryW(pwidechar(app_path+'\Data'));
assignfile(tof,'ChatConf.txt');
reset(tof);

while (not eof(tof)) do begin

 readln(tof,lineStr);
 linestr:=trim(lineStr);

 if pos('#',linestr)=1 then continue;
 if pos(' ',linestr)=1 then continue;
 if pos('/',lineStr)=1 then continue;
 if pos(';',lineStr)=1 then continue;
 
 if length(lineStr)=0 then continue;

 varName:=lowercase(copy(linestr,1,pos('=',linestr)-1));
 if length(varName)=0 then continue;
 if pos(STR_UTF8BOM,varName)=1 then delete(varName,1,3);

 varValue:=trim(copy(linestr,pos('=',linestr)+1,length(linestr)));
 if length(varValue)=0 then continue;

 if varName='channelport' then begin
  portW:=strtointdef(varValue,5000);
  end
  else
 if varName='channelname' then begin
  chname:=varValue;
  end
  else
 if varName='channeltopic' then begin
  Topic:=hexstr_to_bytestr(varValue);
  end;


end;

closefile(tof);

if (length(chname)<MAX_CHAT_NAME_LEN) and
   (length(chname)>MIN_CHAT_NAME_LEN) and
   (portW>1024) then begin
   widest:=utf8strtowidestr(chname)+' '+utf8strtowidestr(topic);
   normalize_special_unicode(widest);
   ipC:=inet_addr(pchar('127.0.0.1'));
   usersW:=0;
   buildNo:=3027;
   strip_Tags_From_Name(chname,topic);
   stripped_topic:=strip_color_string(utf8strtowidestr(topic),has_colors_intopic);
    synchronize(add_channel)
   end;
except
end;
end;

procedure tthread_udp_channellist.GUI_searching; //synch
var
 nodo:pCmtVnode;
 datao:precord_displayed_channel;
 mutex_chat:string;
 hGMutex:hwnd;
begin

 with areS_frmmain do begin
 clear_chanlist_backup;
 (ares_frmmain.panel_chat.Panels[0] as TCometPagePanel).btncaption:=GetLangStringW(STR_CHANNELS);
  with listview_chat_channel do begin
   Clear;
   with header.columns do begin
    Items[0].width:=width;
    Items[1].width:=0;
    Items[2].width:=0;
    Items[0].text:='';
    Items[1].text:='';
    Items[2].text:='';
   end;

   selectable:=false;
   canbgcolor:=false;

   nodo:=addchild(nil);
     datao:=getdata(nodo);
     with datao^ do begin
      name:=GetLangStringA(STR_RETRIEVINGLIST_PLEASEWAIT);
      topic:='';
      ip:=0;
      port:=0;
     end;

   end;
 end;
 

mutex_chat:='AresChatGlbMtx';
hGMutex:=OpenMutex(windows.SYNCHRONIZE,FALSE,pchar(mutex_chat));
if (hGMutex<>0) then begin
  CloseHandle(hGMutex);
  ReadOwnConf;
end else begin
  ReleaseMutex(hGMutex);
  CloseHandle(hGMutex);
end;
end;

function checkChatUserFilter(split_string:Tmystringlist; const matchStr:string):boolean;
var
h:integer;
search_str:string;
deleteit:boolean;
begin
result:=true;

    if split_string=nil then begin
     deleteit:=true;
     search_str:=lowercase(widestrtoutf8str(ares_frmmain.edit_chat_chanfilter.text));
     split_string:=tmystringlist.create;
     SplitString(search_str,split_string);
    end else deleteit:=false;

     for h:=0 to split_string.count-1 do begin
      if pos(split_string.strings[h],matchstr)=0 then begin
       result:=false;
       break;
      end;
     end;
     
    if deleteit then split_string.free;

end;

function add_channel(ip,alt_ip:cardinal; port,users:word; const chname,topic:string;
  stripped_topic:widestring; has_colors_intopic:boolean; addBackup:boolean=true; checkFilter:boolean=true;
  killduplicates:boolean=true; buildNo:word=0):boolean;
var
ips:string;
lochname:string;
locrc:word;
canale,canale_backup,NewChildChannel,OldChildChannel,dummychan:precord_displayed_channel;
node,rootc,oldChildNode,newchildnode:pCmtVnode;
i:integer;
//is_firewalled:boolean;
begin
//is_firewalled:=false;
result:=false;

lochname:=lowercase(chname);
locrc:=stringcrc(lochname,true);

with ares_frmmain do begin
 with listview_chat_channel do begin

if KillDuplicates then begin
  // use backup list if we're adding channels by threads
  for i:=0 to vars_global.chat_chanlist_backup.count-1 do begin
   canale:=vars_global.chat_chanlist_backup[i];
    if canale^.ip=ip then exit;
  end;

end else begin
  // compare using listview if we're called by filter triggers
  node:=Getfirst;
  while (node<>nil) do begin
      canale:=GetData(node);
      if canale^.ip=ip then exit;
   node:=getNextSibling(node);
  end;

end;

 ips:=ipint_to_dotstring(ip);

 if addbackup then begin
  canale_backup:=AllocMem(sizeof(record_displayed_channel));
   canale_backup^.ip:=ip;
   canale_backup^.port:=port;
   canale_backup^.name:=chname;
   canale_backup^.locrc:=locrc;
   canale_backup^.topic:=topic;
   canale_backup^.stripped_topic:=stripped_topic;
   canale_backup^.has_colors_intopic:=has_colors_intopic;
   canale_backup^.users:=users;
   canale_backup^.alt_ip:=alt_ip;
   canale_backup^.buildNo:=buildNo;
    vars_global.chat_chanlist_backup.add(canale_backup);
  end;

if (edit_chat_chanfilter.glyphindex<>12) and
   (edit_chat_chanfilter.glyphindex>0) then
 if checkFilter then
  if length(ares_frmmain.edit_chat_chanfilter.text)>0 then
   if not checkChatUserFilter(nil,lochname+' '+lowercase(topic)) then exit;

  node:=AddChild(nil);
   canale:=getdata(node);
    canale^.ip:=ip;
    canale^.port:=port;
    canale^.name:=chname;
    canale^.locrc:=locrc;
    canale^.topic:=topic;
    canale^.stripped_topic:=stripped_topic;
    canale^.has_colors_intopic:=has_colors_intopic;
    canale^.users:=users;
    canale^.alt_ip:=alt_ip;
    canale^.buildNo:=buildNo;
    result:=true;
 end;
end;
end;

function Chatlist_GetUserStatStr(node:PcmtVnode):string;
var
child:pcmtVnode;
datas:precorD_displayed_channel;
begin
result:='';

with ares_frmmain.listview_chat_channel do begin

if node.FirstChild=nil then exit;
  datas:=GetData(node.FirstChild);
  result:=inttostr(datas^.users);

  child:=GetNextSibling(node.FirstChild);
  while (child<>nil) do begin
    datas:=GetData(child);
    result:=result+'+'+inttostr(datas^.users);
    child:=GetNextSibling(child);
  end;

end;

end;


function channellist_find_root(ip:cardinal; var Oldchildnode:pcmtvnode):pcmtvnode;
var
datac:precord_displayed_channel;
begin
result:=nil;
oldChildNode:=nil;

with ares_frmmain.listview_chat_channel do begin
   result:=getFirst;
    while (result<>nil) do begin
     datac:=getData(result);
     if dataC^.ip=ip then begin

       if result.childcount=0 then begin  // this is not a parent node...
        OldChildNode:=result;
        result:=nil;
       end;

       exit;
     end;
      result:=GetNextSibling(result);
    end;
end;
end;

procedure add_channel_fromreg;
var
fname,ports,ips:string;
reg:tregistry;

ip:cardinal;
port:word;
begin
try

 reg:=tregistry.create;
 with reg do begin
  openkey(areskey,true);

  fname:=readstring('ch_name');
  ips:=readstring('ch_ip');
  ports:=readstring('ch_port');

  closekey;
  destroy;
 end;

if length(fname)>0 then
 if length(ports)>0 then
  if length(ips)>0 then begin
     ip:=inet_addr(pchar(ips));
     port:=strtointdef(ports,6666);
      add_channel(ip,0,port,1,fname,'','',false);
end;


except
end;
end;



end.
