/* +------------------------------------+
* | Inspire Internet Relay Chat Daemon |
* +------------------------------------+
*
* InspIRCd: (C) 2002-2011 InspIRCd Development Team
* See: http://wiki.inspircd.org/Credits
*
* This program is free but copyrighted software; see
* the file COPYING for details.
*
* ---------------------------------------------------
*/
#include "inspircd.h"
#include "sql.h"
/* $ModDesc: Provides channel mode +r for channel registration */
namespace m_sql_channels
{
static bool reading = false;
class DatabaseReadQuery : public SQLQuery
{
public:
LocalIntExt& InDB;
DatabaseReadQuery(Module* me, LocalIntExt& indb) : SQLQuery(me), InDB(indb)
{
}
void OnResult(SQLResult& res)
{
SQLEntries row;
reading = true;
while (res.GetRow(row))
{
std::string channame = row[0];
Channel* chan = ServerInstance->FindChan(channame);
if (!chan)
{
time_t ts = atol(row[1].value.c_str());
if (!ts)
ts = ServerInstance->Time();
chan = new Channel(channame, ts);
}
InDB.set(chan, 1);
if (!row[2].nul)
{
irc::spacesepstream modes(row[2]);
std::string mode;
irc::modestacker ms;
while (modes.GetToken(mode))
{
std::string::size_type eq = mode.find('=');
std::string name = mode.substr(0, eq);
std::string value;
if (eq != std::string::npos)
value = mode.substr(eq + 1);
ModeHandler *mh = ServerInstance->Modes->FindMode(name);
if (!mh)
continue;
ms.push(irc::modechange(mh->id, value, true));
}
ServerInstance->Modes->Process(ServerInstance->FakeClient, chan, ms);
}
if (!row[3].nul)
{
chan->topic = row[3];
chan->setby = row[4];
chan->topicset = atol(row[5].value.c_str());
}
// delete the channel if it is empty and not +r or +P
chan->DelUser(ServerInstance->FakeClient);
}
reading = false;
}
void OnError(SQLerror& e)
{
ServerInstance->Logs->Log("m_sql_channels", SPARSE, "m_sql_channels got error on initial query: %s", e.str.c_str());
}
};
class DiscardQuery : public SQLQuery
{
public:
DiscardQuery(Module* me) : SQLQuery(me) {}
void OnResult(SQLResult& res) {}
void OnError(SQLerror& e)
{
ServerInstance->Logs->Log("m_sql_channels", DEFAULT, "SQL update returned error: %s", e.str.c_str());
}
};
class ChanSQLDB : public Module
{
private:
std::string tablename;
dynamic_reference<SQLProvider> sqldb;
LocalIntExt InDB;
public:
ChanSQLDB() : sqldb("SQL"), InDB(EXTENSIBLE_CHANNEL, "in_sql_chans", this)
{
}
Version GetVersion()
{
return Version("Provides channel state updates to an SQL database", VF_VENDOR);
}
void init()
{
Implementation eventlist[] = {
I_OnMode, I_OnPostTopicChange, I_OnUserJoin, I_OnChannelDelete
};
ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation));
ServerInstance->Modules->AddService(InDB);
ParamL n;
n.push_back(tablename);
// just dump the entire thing back to us, please
sqldb->submit(new DatabaseReadQuery(this, InDB),
"SELECT name, ts, modes, topic, topicset, topicts FROM ?", n);
}
void ReadConfig(ConfigReadStatus& status)
{
ConfigTag *tag = ServerInstance->Config->GetTag ("chandb");
tablename = tag->getString("table", "channels");
if (tablename.empty())
status.ReportError(tag, "Table name cannot be empty");
std::string dbid = tag->getString("dbid");
if (dbid.empty())
sqldb.SetProvider("SQL");
else
sqldb.SetProvider("SQL/" + dbid);
if (!sqldb)
status.ReportError(tag, "SQL database not found!");
}
void AddToDB(Channel* chan)
{
irc::modestacker ms;
chan->ChanModes(ms, MODELIST_FULL);
ParamL n;
n.push_back(tablename);
n.push_back(chan->name);
n.push_back(ConvToStr(chan->age));
n.push_back(ms.popModeLine(FORMAT_PERSIST, INT_MAX, INT_MAX));
if (chan->topic.empty())
{
sqldb->submit(new DiscardQuery(this),
"INSERT INTO ? (name, ts, modes) VALUES ('?', '?', '?')", n);
}
else
{
n.push_back(chan->topic);
n.push_back(chan->setby);
n.push_back(ConvToStr(chan->topicset));
sqldb->submit(new DiscardQuery(this),
"INSERT INTO ? (name, ts, modes, topic, topicset, topicts) VALUES ('?', '?', '?', '?', '?', '?')", n);
}
InDB.set(chan, 1);
}
void OnUserJoin(Membership* memb, bool, bool, CUList&)
{
if (!InDB.get(memb->chan))
AddToDB(memb->chan);
}
void OnPostTopicChange (User *user, Channel *chan, const std::string &topic)
{
if (reading || !InDB.get(chan))
return;
ParamL n;
n.push_back(tablename);
n.push_back(chan->topic);
n.push_back(chan->setby);
n.push_back(ConvToStr(chan->topicset));
n.push_back(chan->name);
sqldb->submit(new DiscardQuery(this),
"UPDATE ? SET (topic, topicset, topicts) = ('?', '?', '?') WHERE name = '?'", n);
}
void OnMode (User *user, Extensible *target, const irc::modestacker &modes)
{
Channel *chan = IS_CHANNEL(target);
if (reading || !chan || modes.empty())
return;
if (!InDB.get(chan))
{
AddToDB(chan);
return;
}
irc::modestacker ms;
chan->ChanModes(ms, MODELIST_FULL);
ParamL n;
n.push_back(tablename);
n.push_back(ms.popModeLine(FORMAT_PERSIST, INT_MAX, INT_MAX));
n.push_back(chan->name);
sqldb->submit(new DiscardQuery(this),
"UPDATE ? SET modes = '?' WHERE name = '?'", n);
}
void OnChannelDelete(Channel* chan)
{
if (!InDB.get(chan))
return;
ParamL n;
n.push_back(tablename);
n.push_back(chan->name);
sqldb->submit(new DiscardQuery(this),
"DELETE FROM ? WHERE name = '?'", n);
}
void Prioritize()
{
// database reading may depend on channel modes being loaded
ServerInstance->Modules->SetPriority(this, I_ModuleInit, PRIORITY_LAST);
}
};
}
using m_sql_channels::ChanSQLDB;
MODULE_INIT(ChanSQLDB)