/* +------------------------------------+ * | 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 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)