You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kvirc/src/kvirc/kernel/kvi_notifylist.cpp

1278 lines
42 KiB

//=============================================================================
//
// File : kvi_notifylist.cpp
// Creation date : Fri Oct 27 2000 23:41:01 CEST by Szymon Stefanek
//
// This file is part of the KVirc irc client distribution
// Copyright (C) 2000-2004 Szymon Stefanek (pragma at kvirc dot 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 opinion) 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. ,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
//=============================================================================
#define __KVIRC__
#include "kvi_debug.h"
#include "kvi_notifylist.h"
#include "kvi_console.h"
#include "kvi_ircsocket.h"
#include "kvi_regusersdb.h"
#include "kvi_userlistview.h"
#include "kvi_channel.h"
#include "kvi_options.h"
#include "kvi_window.h"
#include "kvi_locale.h"
#include "kvi_out.h"
#include "kvi_sparser.h"
#include "kvi_ircmask.h"
#include "kvi_numeric.h"
#include "kvi_parameterlist.h"
#include "kvi_ircconnection.h"
#include "kvi_app.h"
#include "kvi_qstring.h"
#include "kvi_lagmeter.h"
#include "kvi_kvs_eventtriggers.h"
#include "kvi_qcstring.h"
#include <tqstringlist.h>
// FIXME: #warning "Finish this doc!"
/*
@doc: notify_list
@title:
Notify lists
@short:
Tracking users on IRC
@keyterms:
notify property, watch property, notify lists
@body:
The notify list is a means of keeping track of users on IRC.[br]
Once connected to an IRC server, you can tell KVIrc to check
periodically if your friends are online.[br]
This is basically achieved by setting a property in the [doc:registered_users]registered users database[/doc]
entry.[br]
The property is called "notify", and you have to set it to the nickname
that you want to look for.[br]
So for example, assume to register a frend of yours like Szymon:[br]
[example]
[cmd:reguser.add]reguser.add[/cmd] Szymon
[cmd:reguser.addmask]reguser.addmask[/cmd] Szymon Pragma!*@*.it
[/example]
And then want it in the notify list; nothing easier, just set
hist "notify" property to the nickname that you want him to be "looked for":[br]
[example]
[cmd:reguser.setproperty]reguser.setproperty[/cmd] Szymon notify Pragma
[/example]
In this way, once in a while, KVIrc will send to the server an ISON message
with the nickname Pragma. If Szymon is online, you will be notified with a message:[br]
"Pragma [someuser@somehost.it] is on IRC".[br]
If Szymon uses often "[Pragma]" as his secondary nickname , you can do the following:[br]
[example]
[cmd:reguser.addmask]reguser.addmask[/cmd] Szymon [Pragma]*@*.it
[cmd:reguser.setproperty]reguser.setproperty[/cmd] Szymon notify "Pragma [Pragma]"
[/example]
KVIrc will then look for both nicknames getting online.[br]
KVIrc supports three notify lists management methods:[br]
The "stupid ISON method", the "intelligent ISON method" and the "WATCH method".[br]
The "stupid ISON method" will assume that Szymon is online if any user with nickname
Pragma (or [Pragma] in the second example) gets online; this means that also Pragma!someuser@somehost.com will be
assumed to be "Szymon" and will be shown in the notify list.[br]
This might be a false assumption (since somehod.com does not even match *.it),
but it is the best result that the "stupid ISON method" can achieve.[br]
The "intelligent ISON method" will also check the Pragma's username and hostname
and match it in the registered masks; so in the example above, you will be notified if
any user that matches Pragma!*@*.it gets online; (but you will NOT be notified if
(for example) Pragma!someuser@somehost.com gets online).[br]
So what's the point in including a stupid method? :) Well...the intelligent
method "eats" some of your IRC bandwidth; it has to send USERHOST messages
for every group of 5 users in the notify list. If you have a lot of users
in the notify list, it might become slow and eventually cause a
client to server flood.[br]
So finally, the intelligent method is the default. If you have "flood" problems,
or if you think that the notify list is quite slow , try the "stupid" method:
it is not that bad after all.[br]
The third notify list management method is the "WATCH method".[br]
It uses a totally different (and better) approach to the notify lists management,
and can be used only on the networks that support the WATCH notify method (DALnet, WebNet, etc.).[br]
KVIrc will attempt to guess if the server you're currently using supports the WATCH command
and eventually use this last method.[br]
The WATCH method uses the "notify" property to get the nicknames that have to be
sent to the server in the /WATCH commands.
*/
// Basic NotifyListManager: this does completely nothing
KviNotifyListManager::KviNotifyListManager(KviIrcConnection * pConnection)
: TQObject(0,"notify_list_manager")
{
m_pConnection = pConnection;
m_pConsole = pConnection->console();
}
KviNotifyListManager::~KviNotifyListManager()
{
}
void KviNotifyListManager::start()
{
}
void KviNotifyListManager::stop()
{
}
bool KviNotifyListManager::handleUserhost(KviIrcMessage *)
{
return false;
}
bool KviNotifyListManager::handleIsOn(KviIrcMessage *)
{
return false;
}
bool KviNotifyListManager::handleWatchReply(KviIrcMessage *)
{
return false;
}
void KviNotifyListManager::notifyOnLine(const TQString &nick,const TQString &user,const TQString &host,const TQString &szReason,bool bJoin)
{
if(bJoin)
m_pConsole->notifyListView()->join(nick,user,host);
KviWindow * out = KVI_OPTION_BOOL(KviOption_boolNotifyListChangesToActiveWindow) ? m_pConsole->activeWindow() : m_pConsole;
if(KVS_TRIGGER_EVENT_1_HALTED(KviEvent_OnNotifyOnLine,out,nick))return;
TQString szWho;
TQString szMsg;
if(!(user.isEmpty() || host.isEmpty()))
KviTQString::sprintf(szWho,"\r!n\r%Q\r [%Q@\r!h\r%Q\r]",&nick,&user,&host);
else
KviTQString::sprintf(szWho,"\r!n\r%Q\r",&nick);
KviPointerHashTable<TQString,KviRegisteredUser> * d = g_pRegisteredUserDataBase->userDict();
KviPointerHashTableIterator<TQString,KviRegisteredUser> it(*d);
TQString szNotify;
while(KviRegisteredUser * u = it.current())
{
if(TQStringList::split(",",u->getProperty("notify")).findIndex(nick)!=-1)
{
TQString szComment=u->getProperty("comment");
if(!szComment.isEmpty())
KviTQString::sprintf(szMsg,"%Q (%Q), Group \"%Q\" is on IRC as (%Q)",&(u->name()),&szComment,&(u->group()),&szWho);
else
KviTQString::sprintf(szMsg,"%Q, Group \"%Q\" is on IRC as (%Q)",&(u->name()),&(u->group()),&szWho);
break;
}
++it;
}
TQString szFmt = __tr2qs("%Q is on IRC");
if(szMsg.isEmpty())
KviTQString::sprintf(szMsg,szFmt,&szWho);
if((!szReason.isEmpty()) && (_OUTPUT_VERBOSE))
{
szMsg += "(";
szMsg += szReason;
szMsg += ")";
}
out->outputNoFmt(KVI_OUT_NOTIFYONLINE,szMsg);
if(!(out->hasAttention()))
{
if(KVI_OPTION_BOOL(KviOption_boolFlashWindowOnNotifyOnLine))
out->demandAttention();
if(KVI_OPTION_BOOL(KviOption_boolPopupNotifierOnNotifyOnLine))
{
szWho = "<b>";
szWho += nick;
szWho += "</b>";
KviTQString::sprintf(szMsg,szFmt,&szWho);
g_pApp->notifierMessage(0,KVI_OPTION_MSGTYPE(KVI_OUT_NOTIFYONLINE).pixId(),szMsg,15);
}
}
}
void KviNotifyListManager::notifyOffLine(const TQString &nick,const TQString &user,const TQString &host,const TQString &szReason)
{
KviWindow * out = KVI_OPTION_BOOL(KviOption_boolNotifyListChangesToActiveWindow) ? m_pConsole->activeWindow() : m_pConsole;
if(!KVS_TRIGGER_EVENT_1_HALTED(KviEvent_OnNotifyOffLine,out,nick))
{
TQString szWho;
if(!(user.isEmpty() || host.isEmpty()))
KviTQString::sprintf(szWho,"\r!n\r%Q\r [%Q@\r!h\r%Q\r]",&nick,&user,&host);
else
KviTQString::sprintf(szWho,"\r!n\r%Q\r",&nick);
TQString szMsg;
KviPointerHashTable<TQString,KviRegisteredUser> * d = g_pRegisteredUserDataBase->userDict();
KviPointerHashTableIterator<TQString,KviRegisteredUser> it(*d);
TQString szNotify;
while(KviRegisteredUser * u = it.current())
{
if(TQStringList::split(",",u->getProperty("notify")).findIndex(nick)!=-1)
{
TQString szComment=u->getProperty("comment");
if(!szComment.isEmpty())
KviTQString::sprintf(szMsg,"%Q (%Q), Group \"%Q\" has left IRC as (%Q)",&(u->name()),&szComment,&(u->group()),&szWho);
else
KviTQString::sprintf(szMsg,"%Q, Group \"%Q\" has left IRC as (%Q)",&(u->name()),&(u->group()),&szWho);
break;
}
++it;
}
if(szMsg.isEmpty())
KviTQString::sprintf(szMsg,__tr2qs("%Q has left IRC"),&szWho);
if((!szReason.isEmpty()) && (_OUTPUT_VERBOSE))
{
szMsg += "(";
szMsg += szReason;
szMsg += ")";
}
out->outputNoFmt(KVI_OUT_NOTIFYOFFLINE,szMsg);
}
m_pConsole->notifyListView()->part(nick);
}
//
// INTELLIGENT NOTIFY LIST MANAGER: NOTIFY PROCESS:
//
// start() stop()
// | ^
// buildRegUserDict() |
// | |
// m_pRegUserDict->isEmpty() ? -- YES ---------->+
// | |
// NO |
// | |
// newNotifySession()<------- TIMER ---------------- delayedNotifySession() --------------------------------+
// | (can be stopped here) | ^ |
// | | ^ |
// buildNotifyList() | | YES
// | | | |
// m_pNotifyList->isEmpty() ? - YES ->+ | |
// | | |
// NO | |
// | | |
// newIsOnSession()<------------- TIMER -------------------- delayedIsOnSession() -- NO - m_pNotifyList->isEmpty() ?
// | (can be stopped here) | |
// | | |
// buildIsOnList() | |
// | | |
// m_pIsOnList->isEmpty() ? -- YES ---------->+ |
// | |
// NO |
// | |
// sendIsOn() - - - - - - - - - - - -> handleIsOn() |
// | |
// (build m_pOnlineList) |
// | |
// m_pOnlineList->isEmpty() ? - YES ----------------------->+
// | |
// NO YES
// | |
// delayedUserhostSession()<--------------- NO - m_pOnlineList->isEmpty() ?
// | ^
// TIMER (can be stopped here) |
// | |
// newUserhostSession() |
// | |
// buildUserhostList() |
// | |
// m_pUserhostList->isEmpty() ? - YES --->+
// | ^^^ |
// | (unexpected!)||| |
// NO |
// | |
// sendUserhost() - - - - - - - - > handleUserhost()
//
KviIsOnNotifyListManager::KviIsOnNotifyListManager(KviIrcConnection * pConnection)
: KviNotifyListManager(pConnection)
{
m_pRegUserDict = new KviPointerHashTable<TQString,TQString>(17,false); // case insensitive , copy keys
m_pRegUserDict->setAutoDelete(true);
m_pNotifyList = new KviPointerList<TQString>;
m_pNotifyList->setAutoDelete(true);
m_pIsOnList = new KviPointerList<TQString>;
m_pIsOnList->setAutoDelete(true);
m_pOnlineList = new KviPointerList<TQString>;
m_pOnlineList->setAutoDelete(true);
m_pUserhostList = new KviPointerList<TQString>;
m_pUserhostList->setAutoDelete(true);
m_pDelayedNotifyTimer = new TQTimer();
connect(m_pDelayedNotifyTimer,TQT_SIGNAL(timeout()),this,TQT_SLOT(newNotifySession()));
m_pDelayedIsOnTimer = new TQTimer();
connect(m_pDelayedIsOnTimer,TQT_SIGNAL(timeout()),this,TQT_SLOT(newIsOnSession()));
m_pDelayedUserhostTimer = new TQTimer();
connect(m_pDelayedUserhostTimer,TQT_SIGNAL(timeout()),this,TQT_SLOT(newUserhostSession()));
m_bRunning = false;
}
KviIsOnNotifyListManager::~KviIsOnNotifyListManager()
{
if(m_bRunning)stop();
delete m_pDelayedNotifyTimer;
delete m_pDelayedIsOnTimer;
delete m_pDelayedUserhostTimer;
delete m_pRegUserDict;
delete m_pOnlineList;
delete m_pNotifyList;
delete m_pIsOnList;
delete m_pUserhostList;
}
void KviIsOnNotifyListManager::start()
{
if(m_bRunning)stop();
m_bRunning = true;
m_pConsole->notifyListView()->partAllButOne(m_pConnection->currentNickName());
m_bExpectingIsOn = false;
m_bExpectingUserhost = false;
buildRegUserDict();
if(m_pRegUserDict->isEmpty())
{
if(_OUTPUT_VERBOSE)
m_pConsole->output(KVI_OUT_SYSTEMMESSAGE,__tr2qs("Notify list: No users to check for, quitting"));
stop();
return;
}
newNotifySession();
}
void KviIsOnNotifyListManager::buildRegUserDict()
{
m_pRegUserDict->clear();
const KviPointerHashTable<TQString,KviRegisteredUser> * d = g_pRegisteredUserDataBase->userDict();
KviPointerHashTableIterator<TQString,KviRegisteredUser> it(*d);
while(KviRegisteredUser * u = it.current())
{
TQString notify;
if(u->getProperty("notify",notify))
{
notify.stripWhiteSpace();
while(!notify.isEmpty())
{
int idx = notify.find(' ');
if(idx > 0)
{
TQString single = notify.left(idx);
m_pRegUserDict->replace(single,new TQString(u->name()));
notify.remove(0,idx+1);
} else {
m_pRegUserDict->replace(notify,new TQString(u->name()));
notify = "";
}
}
}
++it;
}
}
void KviIsOnNotifyListManager::delayedNotifySession()
{
unsigned int iTimeout = KVI_OPTION_UINT(KviOption_uintNotifyListCheckTimeInSecs);
if(iTimeout < 15)
{
// life first of all.
// don't allow the user to suicide
if(_OUTPUT_VERBOSE)
m_pConsole->output(KVI_OUT_SYSTEMWARNING,
__tr2qs("Notify list: Timeout (%d sec) is too short, resetting to something more reasonable (15 sec)"),
iTimeout);
iTimeout = 15;
KVI_OPTION_UINT(KviOption_uintNotifyListCheckTimeInSecs) = 15;
}
m_pDelayedNotifyTimer->start(iTimeout * 1000,true);
}
void KviIsOnNotifyListManager::newNotifySession()
{
buildNotifyList();
if(m_pNotifyList->isEmpty())
{
if(_OUTPUT_VERBOSE)
m_pConsole->output(KVI_OUT_SYSTEMMESSAGE,__tr2qs("Notify list: Notify list empty, quitting"));
stop();
return;
}
newIsOnSession();
}
void KviIsOnNotifyListManager::buildNotifyList()
{
m_pNotifyList->clear();
KviPointerHashTableIterator<TQString,TQString> it(*m_pRegUserDict);
while(it.current())
{
m_pNotifyList->append(new TQString(it.currentKey()));
++it;
}
}
void KviIsOnNotifyListManager::delayedIsOnSession()
{
unsigned int iTimeout = KVI_OPTION_UINT(KviOption_uintNotifyListIsOnDelayTimeInSecs);
if(iTimeout < 5)
{
// life first of all.
// don't allow the user to suicide
if(_OUTPUT_VERBOSE)
m_pConsole->output(KVI_OUT_SYSTEMWARNING,
__tr2qs("Notify list: ISON delay (%d sec) is too short, resetting to something more reasonable (5 sec)"),
iTimeout);
iTimeout = 5;
KVI_OPTION_UINT(KviOption_uintNotifyListIsOnDelayTimeInSecs) = 5;
}
m_pDelayedIsOnTimer->start(iTimeout * 1000,true);
}
void KviIsOnNotifyListManager::newIsOnSession()
{
buildIsOnList();
if(m_pIsOnList->isEmpty())delayedNotifySession();
else sendIsOn();
}
void KviIsOnNotifyListManager::buildIsOnList()
{
m_pIsOnList->clear();
m_szIsOnString = "";
m_pNotifyList->setAutoDelete(false);
while(TQString * s = m_pNotifyList->first())
{
if(((m_szIsOnString.length() + s->length()) + 1) < 504)
{
if(!m_szIsOnString.isEmpty())m_szIsOnString.append(' ');
m_szIsOnString.append(*s);
m_pIsOnList->append(s);
m_pNotifyList->removeFirst();
} else break;
}
m_pNotifyList->setAutoDelete(true);
}
void KviIsOnNotifyListManager::sendIsOn()
{
if(_OUTPUT_PARANOIC)
m_pConsole->output(KVI_OUT_SYSTEMMESSAGE,__tr2qs("Notify list: Checking for: %Q"),&m_szIsOnString);
KviTQCString szDec = m_pConnection->encodeText(m_szIsOnString);
m_pConnection->sendFmtData("ISON %s",szDec.data());
if(m_pConnection->lagMeter())
m_pConnection->lagMeter()->lagCheckRegister("@notify_ison",40); // not that reliable
m_szIsOnString = "";
m_bExpectingIsOn = true;
// FIXME: #warning "And if can't send ?"
}
bool KviIsOnNotifyListManager::handleIsOn(KviIrcMessage *msg)
{
if(!m_bExpectingIsOn)return false;
// Check if it is our ISON
// all the nicks must be on the IsOnList
KviPointerList<TQString> tmplist;
tmplist.setAutoDelete(false);
KviStr nk;
const char * aux = msg->trailing();
while(*aux)
{
nk = "";
aux = kvi_extractToken(nk,aux,' ');
if(nk.hasData())
{
bool bGotIt = false;
TQString dnk = m_pConnection->decodeText(nk.ptr());
for(TQString * s = m_pIsOnList->first();s && (!bGotIt);s = m_pIsOnList->next())
{
if(KviTQString::equalCI(*s,dnk))
{
tmplist.append(s);
bGotIt = true;
}
}
if(!bGotIt)
{
// ops...not my userhost!
if(_OUTPUT_VERBOSE)
m_pConsole->output(KVI_OUT_SYSTEMMESSAGE,__tr2qs("Notify list: Hey! You've used ISON behind my back? (I might be confused now...)"));
return false;
}
}
}
// Ok...looks to be my ison (still not sure at 100% , but can't do better)
if(m_pConnection->lagMeter())
m_pConnection->lagMeter()->lagCheckComplete("@notify_ison");
m_bExpectingIsOn = false;
m_pOnlineList->clear();
m_pIsOnList->setAutoDelete(false);
// Ok...we have an IsOn reply here
// The nicks in the IsOnList that are also in the reply are online , and go to the OnlineList
// the remaining in the IsOnList are offline
TQString * s;
for(s = tmplist.first();s;s = tmplist.next())
{
m_pIsOnList->removeRef(s);
m_pOnlineList->append(s);
}
m_pIsOnList->setAutoDelete(true);
// Ok...all the users that are online , are on the OnlineList
// the remaining users are in the m_pIsOnList , and are no longer online
// first the easy step: remove the users that have just left irc or have never been online
// we're clearling the m_pIsOnList
while((s = m_pIsOnList->first()))
{
if(m_pConsole->notifyListView()->findEntry(*s))
{
// has just left IRC... make him part
notifyOffLine(*s);
} // else has never been here
m_pIsOnList->removeFirst(); // autodelete is true
}
// ok... complex step now: the remaining users in the userhost list are online
// if they have been online before, just remove them from the list
// otherwise they must be matched for masks
// and eventually inserted in the notify view later
KviIrcUserDataBase * db = console()->connection()->userDataBase();
KviPointerList<TQString> l;
l.setAutoDelete(false);
for(s = m_pOnlineList->first();s;s = m_pOnlineList->next())
{
if(KviUserListEntry * ent = m_pConsole->notifyListView()->findEntry(*s))
{
// the user was online from a previous notify session
// might the mask have been changed ? (heh...this is tricky, maybe too much even)
if(KVI_OPTION_BOOL(KviOption_boolNotifyListSendUserhostForOnlineUsers))
{
// user wants to be sure about online users....
// check if he is on some channels
if(ent->globalData()->nRefs() > 1)
{
// mmmh...we have more than one ref , so the user is at least in one query or channel
// look him up on channels , if we find his entry , we can be sure that he is
// still the right user
KviPointerList<KviChannel> * chlist = m_pConsole->connection()->channelList();
for(KviChannel * ch = chlist->first();ch;ch = chlist->next())
{
if(KviUserListEntry * le = ch->findEntry(*s))
{
l.append(s); // ok...found on a channel...we don't need an userhost to match him
KviIrcMask mk(*s,le->globalData()->user(),le->globalData()->host());
if(!doMatchUser(*s,mk))return true; // critical problems = have to restart!!!
break;
}
}
} // else Only one ref...we need an userhost to be sure (don't remove from the list)
} else {
// user wants no userhost for online users...we "hope" that everything will go ok.
l.append(s);
}
//l.append(s); // we will remove him from the list
} else {
// the user was not online!
// check if we have a cached mask
if(db)
{
if(KviIrcUserEntry * ue = db->find(*s))
{
// already in the db... do we have a mask ?
if(ue->hasUser() && ue->hasHost())
{
// yup! we have a complete mask to match on
KviIrcMask mk(*s,ue->user(),ue->host());
// lookup the user's name in the m_pRegUserDict
if(!doMatchUser(*s,mk))return true; // critical problems = have to restart!!!
l.append(s); // remove anyway
}
}
}
}
}
for(s = l.first();s;s = l.next())
{
m_pOnlineList->removeRef(s); // autodelete is true
}
if(m_pOnlineList->isEmpty())
{
if(m_pNotifyList->isEmpty())delayedNotifySession();
else delayedIsOnSession();
} else delayedUserhostSession();
return true;
}
// FIXME: #warning "Nickname escapes (links) in the notifylist messages!"
bool KviIsOnNotifyListManager::doMatchUser(const TQString &notifyString,const KviIrcMask & mask)
{
TQString * nam = m_pRegUserDict->find(notifyString);
if(nam)
{
// ok...find the user
if(KviRegisteredUser * u = g_pRegisteredUserDataBase->findUserByName(*nam))
{
// ok ... match the user
if(u->matchesFixed(mask))
{
// new user online
if(!(m_pConsole->notifyListView()->findEntry(mask.nick())))
{
notifyOnLine(mask.nick(),mask.user(),mask.host());
} // else already online , and matching...all ok
} else {
// not matched.... has he been online before ?
if(m_pConsole->notifyListView()->findEntry(mask.nick()))
{
// has been online just a sec ago , but now the mask does not match
// either reguserdb has changed , or the user went offline and another one got his nick
// in the meantime... (ugly situation anyway)
notifyOffLine(mask.nick(),mask.user(),mask.host(),__tr2qs("registration mask changed, or nickname is being used by someone else"));
} else {
// has never been online
if(_OUTPUT_VERBOSE)
m_pConsole->output(KVI_OUT_SYSTEMMESSAGE,__tr2qs("Notify list: \r!n\r%Q\r appears to be online, but the mask [%Q@\r!h\r%Q\r] does not match (registration mask does not match, or nickname is being used by someone else)"),&(mask.nick()),&(mask.user()),&(mask.host()));
}
}
} else {
// ops... unexpected inconsistency .... reguser db modified ?
m_pConsole->output(KVI_OUT_SYSTEMWARNING,__tr2qs("Notify list: Unexpected inconsistency, registered user DB modified? (restarting)"));
stop();
start();
return false; // critical ... exit from the call stack
}
} else {
// ops...unexpected inconsistency
m_pConsole->output(KVI_OUT_SYSTEMWARNING,__tr2qs("Notify list: Unexpected inconsistency, expected \r!n\r%Q\r in the registered user DB"),&notifyString);
}
return true;
}
void KviIsOnNotifyListManager::delayedUserhostSession()
{
unsigned int iTimeout = KVI_OPTION_UINT(KviOption_uintNotifyListUserhostDelayTimeInSecs);
if(iTimeout < 5)
{
// life first of all.
// don't allow the user to suicide
if(_OUTPUT_VERBOSE)
m_pConsole->output(KVI_OUT_SYSTEMWARNING,
__tr2qs("Notify list: USERHOST delay (%d sec) is too short, resetting to something more reasonable (5 sec)"),
iTimeout);
iTimeout = 5;
KVI_OPTION_UINT(KviOption_uintNotifyListUserhostDelayTimeInSecs) = 5;
}
m_pDelayedUserhostTimer->start(iTimeout * 1000,true);
}
void KviIsOnNotifyListManager::newUserhostSession()
{
buildUserhostList();
if(m_pUserhostList->isEmpty())
{
// this is unexpected!
m_pConsole->output(KVI_OUT_SYSTEMWARNING,__tr2qs("Notify list: Unexpected inconsistency, userhost list is empty!"));
if(m_pOnlineList->isEmpty())
{
if(m_pNotifyList->isEmpty())delayedNotifySession();
else delayedIsOnSession();
} else delayedUserhostSession();
return;
}
sendUserhost();
}
#define MAX_USERHOST_ENTRIES 5
void KviIsOnNotifyListManager::buildUserhostList()
{
m_szUserhostString = "";
m_pUserhostList->clear();
m_pOnlineList->setAutoDelete(false);
int i = 0;
TQString * s;
while((s = m_pOnlineList->first()) && (i < MAX_USERHOST_ENTRIES))
{
if(!m_szUserhostString.isEmpty())m_szUserhostString.append(' ');
m_szUserhostString.append(*s);
m_pUserhostList->append(s);
m_pOnlineList->removeFirst();
i++;
}
m_pOnlineList->setAutoDelete(true);
}
void KviIsOnNotifyListManager::sendUserhost()
{
if(_OUTPUT_PARANOIC)
m_pConsole->output(KVI_OUT_SYSTEMMESSAGE,__tr2qs("Notify list: Checking userhost for: %Q"),&m_szUserhostString);
KviTQCString ccc = m_pConnection->encodeText(m_szUserhostString);
m_pConnection->sendFmtData("USERHOST %s",ccc.data());
if(m_pConnection->lagMeter())
m_pConnection->lagMeter()->lagCheckRegister("@notify_userhost",50);
m_szUserhostString = "";
m_bExpectingUserhost = true;
// FIXME: #warning "And if can't send ?"
}
bool KviIsOnNotifyListManager::handleUserhost(KviIrcMessage *msg)
{
if(!m_bExpectingUserhost)return false;
// first check for consistency: all the replies must be on the USERHOST list
KviPointerList<KviIrcMask> tmplist;
tmplist.setAutoDelete(true);
KviStr nk;
const char * aux = msg->trailing();
while(*aux)
{
nk = "";
aux = kvi_extractToken(nk,aux,' ');
if(nk.hasData())
{
// split it in a mask
KviStr nick;
KviStr user;
KviStr host;
int idx = nk.findFirstIdx('=');
if(idx != -1)
{
nick = nk.left(idx);
if(nick.lastCharIs('*'))nick.cutRight(1);
nk.cutLeft(idx + 1);
if(nk.firstCharIs('+') || nk.firstCharIs('-'))nk.cutLeft(1);
idx = nk.findFirstIdx('@');
if(idx != -1)
{
user = nk.left(idx);
nk.cutLeft(idx + 1);
host = nk;
} else {
user = "*";
host = nk;
}
bool bGotIt = false;
TQString szNick = m_pConnection->decodeText(nick.ptr());
TQString szUser = m_pConnection->decodeText(user.ptr());
TQString szHost = m_pConnection->decodeText(host.ptr());
for(TQString * s = m_pUserhostList->first();s && (!bGotIt);s = m_pUserhostList->next())
{
if(KviTQString::equalCI(*s,szNick))
{
KviIrcMask * mk = new KviIrcMask(szNick,szUser,szHost);
tmplist.append(mk);
bGotIt = true;
m_pUserhostList->removeRef(s);
}
}
if(!bGotIt)
{
// ops...not my userhost!
if(_OUTPUT_VERBOSE)
m_pConsole->output(KVI_OUT_SYSTEMWARNING,__tr2qs("Notify list: Hey! You've used USERHOST behind my back? (I might be confused now...)"));
return false;
}
} else {
if(_OUTPUT_VERBOSE)
m_pConsole->output(KVI_OUT_SYSTEMWARNING,__tr2qs("Notify list: Broken USERHOST reply from the server? (%s)"),nk.ptr());
}
}
}
// Ok...looks to be my usershot (still not sure at 100% , but can't do better)
if(m_pConnection->lagMeter())
m_pConnection->lagMeter()->lagCheckComplete("@notify_userhost");
m_bExpectingUserhost = false;
for(KviIrcMask * mk = tmplist.first();mk;mk = tmplist.next())
{
if(!doMatchUser(mk->nick(),*mk))return true; // have to restart!!!
}
if(!(m_pUserhostList->isEmpty()))
{
// ops...someone is no longer online ?
while(TQString * s = m_pUserhostList->first())
{
if(_OUTPUT_VERBOSE)
m_pConsole->output(KVI_OUT_SYSTEMMESSAGE,__tr2qs("Notify list: \r!n\r%Q\r appears to have gone offline before USERHOST reply was received, will recheck in the next loop"),s);
m_pUserhostList->removeFirst();
}
}
if(m_pOnlineList->isEmpty())
{
if(m_pNotifyList->isEmpty())delayedNotifySession();
else delayedIsOnSession();
} else delayedUserhostSession();
return true;
}
void KviIsOnNotifyListManager::stop()
{
if(!m_bRunning)return;
if(m_pConnection->lagMeter())
m_pConnection->lagMeter()->lagCheckAbort("@notify_userhost");
if(m_pConnection->lagMeter())
m_pConnection->lagMeter()->lagCheckAbort("@notify_ison");
m_pDelayedNotifyTimer->stop();
m_pDelayedIsOnTimer->stop();
m_pDelayedUserhostTimer->stop();
m_pConsole->notifyListView()->partAllButOne(m_pConnection->currentNickName());
m_pRegUserDict->clear();
m_pNotifyList->clear();
m_pIsOnList->clear();
m_pOnlineList->clear();
m_pUserhostList->clear();
m_szIsOnString = "";
m_szUserhostString = "";
m_bRunning = false;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
//
// Stupid notify list manager
//
///////////////////////////////////////////////////////////////////////////////////////////////////
KviStupidNotifyListManager::KviStupidNotifyListManager(KviIrcConnection * pConnection)
: KviNotifyListManager(pConnection)
{
m_pNickList = new KviPointerList<TQString>;
m_pNickList->setAutoDelete(true);
m_iRestartTimer = 0;
}
KviStupidNotifyListManager::~KviStupidNotifyListManager()
{
if(m_iRestartTimer)
{
killTimer(m_iRestartTimer);
m_iRestartTimer = 0;
}
delete m_pNickList;
}
void KviStupidNotifyListManager::start()
{
if(m_iRestartTimer)
{
killTimer(m_iRestartTimer);
m_iRestartTimer = 0;
}
if(_OUTPUT_VERBOSE)
m_pConsole->outputNoFmt(KVI_OUT_SYSTEMMESSAGE,__tr2qs("Starting notify list"));
buildNickList();
if(m_pNickList->isEmpty())
{
if(_OUTPUT_VERBOSE)
m_pConsole->outputNoFmt(KVI_OUT_SYSTEMMESSAGE,__tr2qs("No users in the notify list"));
return; // Ok...no nicknames in the list
}
m_iNextNickToCheck = 0;
m_pConsole->notifyListView()->partAllButOne(m_pConnection->currentNickName());
sendIsOn();
}
void KviStupidNotifyListManager::sendIsOn()
{
m_szLastIsOnMsg = "";
TQString * nick = m_pNickList->at(m_iNextNickToCheck);
__range_valid(nick);
int i = 0;
while(nick && ((nick->length() + 5 + m_szLastIsOnMsg.length()) < 510))
{
KviTQString::appendFormatted(m_szLastIsOnMsg," %Q",nick);
nick = m_pNickList->next();
i++;
}
if(_OUTPUT_PARANOIC)
m_pConsole->output(KVI_OUT_SYSTEMMESSAGE,__tr2qs("Notify list: Checking for:%Q"),&m_szLastIsOnMsg);
KviTQCString dat = m_pConnection->encodeText(m_szLastIsOnMsg);
m_pConnection->sendFmtData("ISON%s",dat.data());
if(m_pConnection->lagMeter())
m_pConnection->lagMeter()->lagCheckRegister("@notify_naive",20);
m_iNextNickToCheck += i;
}
bool KviStupidNotifyListManager::handleIsOn(KviIrcMessage * msg)
{
if(m_pConnection->lagMeter())
m_pConnection->lagMeter()->lagCheckComplete("@notify_naive");
KviStr nk;
const char * aux = msg->trailing();
while(*aux)
{
nk = "";
aux = kvi_extractToken(nk,aux,' ');
if(nk.hasData())
{
TQString nkd = m_pConnection->decodeText(nk.ptr());
TQString nksp = " " + nkd;
m_szLastIsOnMsg.replace(nksp,"",false);
if(!(m_pConsole->notifyListView()->findEntry(nkd)))
{
// not yet notified
notifyOnLine(nkd);
}
}
}
// ok...check the users that have left irc now...
TQStringList sl = TQStringList::split(' ',m_szLastIsOnMsg);
for(TQStringList::Iterator it = sl.begin();it != sl.end();++it)
{
if(m_pConsole->notifyListView()->findEntry(*it))
{
// has just left irc
notifyOffLine(*it);
} // else has never been here...
}
if(((unsigned int)m_iNextNickToCheck) >= m_pNickList->count())
{
// have to restart
unsigned int iTimeout = KVI_OPTION_UINT(KviOption_uintNotifyListCheckTimeInSecs);
if(iTimeout < 5)
{
// life first of all.
// don't allow the user to suicide
if(_OUTPUT_VERBOSE)
m_pConsole->output(KVI_OUT_SYSTEMWARNING,
__tr2qs("Notify list: Timeout (%d sec) is too short, resetting to something more reasonable (5 sec)"),
iTimeout);
iTimeout = 5;
KVI_OPTION_UINT(KviOption_uintNotifyListCheckTimeInSecs) = 5;
}
m_iRestartTimer = startTimer(iTimeout * 1000);
} else sendIsOn();
return true;
}
void KviStupidNotifyListManager::timerEvent(TQTimerEvent *e)
{
if(e->timerId() == m_iRestartTimer)
{
killTimer(m_iRestartTimer);
m_iRestartTimer = 0;
m_iNextNickToCheck = 0;
sendIsOn();
return;
}
TQObject::timerEvent(e);
}
void KviStupidNotifyListManager::stop()
{
if(m_pConnection->lagMeter())
m_pConnection->lagMeter()->lagCheckAbort("@notify_naive");
if(m_iRestartTimer)
{
killTimer(m_iRestartTimer);
m_iRestartTimer = 0;
}
m_pConsole->notifyListView()->partAllButOne(m_pConnection->currentNickName());
// The ISON Method needs no stopping
}
void KviStupidNotifyListManager::buildNickList()
{
const KviPointerHashTable<TQString,KviRegisteredUser> * d = g_pRegisteredUserDataBase->userDict();
KviPointerHashTableIterator<TQString,KviRegisteredUser> it(*d);
m_pNickList->clear();
while(it.current())
{
TQString notify;
if(it.current()->getProperty("notify",notify))
{
m_pNickList->append(new TQString(notify));
}
++it;
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
//
// Watch notify list manager
//
///////////////////////////////////////////////////////////////////////////////////////////////////
KviWatchNotifyListManager::KviWatchNotifyListManager(KviIrcConnection * pConnection)
: KviNotifyListManager(pConnection)
{
m_pRegUserDict = new KviPointerHashTable<TQString,TQString>(17,false);
m_pRegUserDict->setAutoDelete(true);
}
KviWatchNotifyListManager::~KviWatchNotifyListManager()
{
delete m_pRegUserDict;
}
void KviWatchNotifyListManager::buildRegUserDict()
{
m_pRegUserDict->clear();
const KviPointerHashTable<TQString,KviRegisteredUser> * d = g_pRegisteredUserDataBase->userDict();
KviPointerHashTableIterator<TQString,KviRegisteredUser> it(*d);
while(KviRegisteredUser * u = it.current())
{
TQString notify;
if(u->getProperty("notify",notify))
{
notify.stripWhiteSpace();
TQStringList sl = TQStringList::split(' ',notify);
for(TQStringList::Iterator it = sl.begin();it != sl.end();++it)
{
m_pRegUserDict->replace(*it,new TQString(u->name()));
}
}
++it;
}
}
void KviWatchNotifyListManager::start()
{
m_pConsole->notifyListView()->partAllButOne(m_pConnection->currentNickName());
buildRegUserDict();
TQString watchStr;
KviPointerHashTableIterator<TQString,TQString> it(*m_pRegUserDict);
while(it.current())
{
TQString nk = it.currentKey();
if(nk.find('*') == -1)
{
if((watchStr.length() + nk.length() + 2) > 501)
{
KviTQCString dat = m_pConnection->encodeText(watchStr);
m_pConnection->sendFmtData("WATCH%s",dat.data());
if(_OUTPUT_VERBOSE)
m_pConsole->output(KVI_OUT_SYSTEMMESSAGE,__tr2qs("Notify list: Adding watch entries for%Q"),&watchStr);
watchStr = "";
}
KviTQString::appendFormatted(watchStr," +%Q",&nk);
}
++it;
}
if(!watchStr.isEmpty())
{
KviTQCString dat = m_pConnection->encodeText(watchStr);
m_pConnection->sendFmtData("WATCH%s",dat.data());
if(_OUTPUT_VERBOSE)
m_pConsole->output(KVI_OUT_SYSTEMMESSAGE,__tr2qs("Notify list: Adding watch entries for%Q"),&watchStr);
}
}
void KviWatchNotifyListManager::stop()
{
m_pConsole->notifyListView()->partAllButOne(m_pConnection->currentNickName());
m_pConnection->sendFmtData("WATCH clear");
m_pRegUserDict->clear();
}
bool KviWatchNotifyListManager::doMatchUser(KviIrcMessage * msg,const TQString &notifyString,const KviIrcMask & mask)
{
TQString * nam = m_pRegUserDict->find(notifyString);
if(nam)
{
// ok...find the user
if(KviRegisteredUser * u = g_pRegisteredUserDataBase->findUserByName(*nam))
{
// ok ... match the user
if(u->matchesFixed(mask))
{
// new user online
if(!(m_pConsole->notifyListView()->findEntry(mask.nick())))
{
notifyOnLine(mask.nick(),mask.user(),mask.host(),"watch");
} else {
// else already online , and matching...all ok
if(msg->numeric() == RPL_NOWON)
{
// This is a reply to a /watch +something (should not happen, unless the user is messing) or to /watch l (user requested)
notifyOnLine(mask.nick(),mask.user(),mask.host(),
__tr2qs("watch entry listing requested by user"),false);
} else {
// This is a RPL_LOGON....we're desynched ?
notifyOnLine(mask.nick(),mask.user(),mask.host(),
__tr2qs("possible watch list desync"),false);
}
}
} else {
// not matched.... has he been online before ?
if(m_pConsole->notifyListView()->findEntry(mask.nick()))
{
// has been online just a sec ago , but now the mask does not match
// prolly the reguserdb has been changed
notifyOffLine(mask.nick(),mask.user(),mask.host(),
__tr2qs("registration mask changed or desync with the watch service"));
} else {
// has never been online
if(_OUTPUT_VERBOSE)
m_pConsole->output(KVI_OUT_SYSTEMMESSAGE,
__tr("Notify list: \r!n\r%Q\r appears to be online, but the mask [%Q@\r!h\r%Q\r] does not match (watch: registration mask does not match, or nickname is being used by someone else)"),
&(mask.nick()),&(mask.user()),&(mask.host()));
}
}
} else {
// ops... unexpected inconsistency .... reguser db modified ?
m_pConsole->output(KVI_OUT_SYSTEMWARNING,
__tr2qs("Notify list: Unexpected inconsistency, registered user DB modified? (watch: restarting)"));
stop();
start();
return false; // critical ... exit from the call stack
}
} else {
// not in our dictionary
// prolly someone used /WATCH behind our back... bad boy!
if(!(m_pConsole->notifyListView()->findEntry(mask.nick())))
{
notifyOnLine(mask.nick(),mask.user(),mask.host(),__tr2qs("watch entry added by user"));
}
}
return true;
}
// FIXME: #warning "DEDICATED WATCH LIST VERBOSITY FLAG ? (To allow the user to use /WATCH l and manual /WATCH)"
bool KviWatchNotifyListManager::handleWatchReply(KviIrcMessage *msg)
{
// 600: RPL_LOGON
// :prefix 600 <target> <nick> <user> <host> <logintime> :logged online
// 601: RPL_LOGON
// :prefix 601 <target> <nick> <user> <host> <logintime> :logged offline
// 604: PRL_NOWON
// :prefix 604 <target> <nick> <user> <host> <logintime> :is online
// 605: PRL_NOWOFF
// :prefix 605 <target> <nick> <user> <host> 0 :is offline
// FIXME: #warning "Use the logintime in some way ?"
const char * nk = msg->safeParam(1);
const char * us = msg->safeParam(2);
const char * ho = msg->safeParam(3);
TQString dnk = m_pConnection->decodeText(nk);
TQString dus = m_pConnection->decodeText(us);
TQString dho = m_pConnection->decodeText(ho);
if((msg->numeric() == RPL_LOGON) || (msg->numeric() == RPL_NOWON))
{
KviIrcMask m(dnk,dus,dho);
doMatchUser(msg,dnk,m);
return true;
} else if(msg->numeric() == RPL_WATCHOFF)
{
if(m_pConsole->notifyListView()->findEntry(dnk))
{
notifyOffLine(dnk,dus,dho,__tr2qs("removed from watch list"));
} else {
if(_OUTPUT_VERBOSE)
m_pConsole->output(KVI_OUT_SYSTEMMESSAGE,__tr2qs("Notify list: Stopped watching for \r!n\r%Q\r"),&dnk);
}
if(m_pRegUserDict->find(dnk))m_pRegUserDict->remove(dnk); // kill that
return true;
} else if((msg->numeric() == RPL_LOGOFF) || (msg->numeric() == RPL_NOWOFF))
{
if(m_pConsole->notifyListView()->findEntry(dnk))
{
notifyOffLine(dnk,dus,dho,__tr2qs("watch"));
} else {
if(msg->numeric() == RPL_NOWOFF)
{
// This is a reply to a /watch +something
if(_OUTPUT_VERBOSE)
m_pConsole->output(KVI_OUT_SYSTEMMESSAGE,__tr2qs("Notify list: \r!n\r%Q\r is offline (watch)"),&dnk);
} else {
// This is a RPL_LOGOFF for an user that has not matched the reg-mask
notifyOffLine(dnk,dus,dho,__tr2qs("unmatched watch list entry"));
}
}
return true;
}
return false;
}
#include "kvi_notifylist.moc"