Merged changes from branch "sput" r56:61 back into trunk.
[quassel.git] / core / core.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005/06 by The Quassel Team                             *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
19  ***************************************************************************/
20
21 #include "core.h"
22 #include "server.h"
23 #include "global.h"
24 #include "util.h"
25 #include "coreproxy.h"
26
27 #include <QtSql>
28 #include <QSettings>
29
30 Core::Core() {
31   if(core) qFatal("Trying to instantiate more than one Core object!");
32
33   connect(coreProxy, SIGNAL(requestServerStates()), this, SIGNAL(serverStateRequested()));
34   connect(coreProxy, SIGNAL(gsRequestConnect(QStringList)), this, SLOT(connectToIrc(QStringList)));
35   connect(coreProxy, SIGNAL(gsUserInput(QString, QString, QString)), this, SIGNAL(msgFromGUI(QString, QString, QString)));
36   connect(this, SIGNAL(displayMsg(QString, Message)), coreProxy, SLOT(csDisplayMsg(QString, Message)));
37   connect(this, SIGNAL(displayStatusMsg(QString, QString)), coreProxy, SLOT(csDisplayStatusMsg(QString, QString)));
38
39   // Read global settings from config file
40   QSettings s;
41   s.beginGroup("Global");
42   QString key;
43   foreach(key, s.childKeys()) {
44     global->updateData(key, s.value(key));
45   }
46   initBackLogOld();
47   global->updateData("CoreReady", true);
48   // Now that we are in sync, we can connect signals to automatically store further updates.
49   // I don't think we care if global data changed locally or if it was updated by a client. 
50   connect(global, SIGNAL(dataUpdatedRemotely(QString)), SLOT(globalDataUpdated(QString)));
51   connect(global, SIGNAL(dataPutLocally(QString)), SLOT(globalDataUpdated(QString)));
52
53 }
54
55 Core::~Core() {
56   //foreach(Server *s, servers) {
57   //  delete s;
58   //}
59   foreach(QDataStream *s, logStreams) {
60     delete s;
61   }
62   foreach(QFile *f, logFiles) {
63     if(f->isOpen()) f->close();
64     delete f;
65   }
66   logDb.close();
67 }
68
69 void Core::globalDataUpdated(QString key) {
70   QVariant data = global->getData(key);
71   QSettings s;
72   s.setValue(QString("Global/")+key, data);
73 }
74
75 void Core::connectToIrc(QStringList networks) {
76   foreach(QString net, networks) {
77     if(servers.contains(net)) {
78
79     } else {
80       Server *server = new Server(net);
81       connect(this, SIGNAL(serverStateRequested()), server, SLOT(sendState()));
82       connect(this, SIGNAL(connectToIrc(QString)), server, SLOT(connectToIrc(QString)));
83       connect(this, SIGNAL(disconnectFromIrc(QString)), server, SLOT(disconnectFromIrc(QString)));
84       connect(this, SIGNAL(msgFromGUI(QString, QString, QString)), server, SLOT(userInput(QString, QString, QString)));
85       connect(server, SIGNAL(serverState(QString, VarMap)), coreProxy, SLOT(csServerState(QString, VarMap)));
86       connect(server, SIGNAL(displayMsg(Message)), this, SLOT(recvMessageFromServer(Message)));
87       connect(server, SIGNAL(displayStatusMsg(QString)), this, SLOT(recvStatusMsgFromServer(QString)));
88       connect(server, SIGNAL(modeSet(QString, QString, QString)), coreProxy, SLOT(csModeSet(QString, QString, QString)));
89       connect(server, SIGNAL(topicSet(QString, QString, QString)), coreProxy, SLOT(csTopicSet(QString, QString, QString)));
90       connect(server, SIGNAL(setNicks(QString, QString, QStringList)), coreProxy, SLOT(csSetNicks(QString, QString, QStringList)));
91       connect(server, SIGNAL(nickAdded(QString, QString, VarMap)), coreProxy, SLOT(csNickAdded(QString, QString, VarMap)));
92       connect(server, SIGNAL(nickRenamed(QString, QString, QString)), coreProxy, SLOT(csNickRenamed(QString, QString, QString)));
93       connect(server, SIGNAL(nickRemoved(QString, QString)), coreProxy, SLOT(csNickRemoved(QString, QString)));
94       connect(server, SIGNAL(nickUpdated(QString, QString, VarMap)), coreProxy, SLOT(csNickUpdated(QString, QString, VarMap)));
95       connect(server, SIGNAL(ownNickSet(QString, QString)), coreProxy, SLOT(csOwnNickSet(QString, QString)));
96       connect(server, SIGNAL(queryRequested(QString, QString)), coreProxy, SLOT(csQueryRequested(QString, QString)));
97       // add error handling
98       connect(server, SIGNAL(connected(QString)), coreProxy, SLOT(csServerConnected(QString)));
99       connect(server, SIGNAL(disconnected(QString)), this, SLOT(serverDisconnected(QString)));
100
101       server->start();
102       servers[net] = server;
103     }
104     emit connectToIrc(net);
105   }
106 }
107
108 void Core::serverDisconnected(QString net) {
109   delete servers[net];
110   servers.remove(net);
111   coreProxy->csServerDisconnected(net);
112 }
113
114 // ALL messages coming pass through these functions before going to the GUI.
115 // So this is the perfect place for storing the backlog and log stuff.
116 void Core::recvMessageFromServer(Message msg) {
117   Server *s = qobject_cast<Server*>(sender());
118   Q_ASSERT(s);
119   logMessageOld(s->getNetwork(), msg);
120   emit displayMsg(s->getNetwork(), msg);
121 }
122
123 void Core::recvStatusMsgFromServer(QString msg) {
124   Server *s = qobject_cast<Server*>(sender());
125   Q_ASSERT(s);
126   emit displayStatusMsg(s->getNetwork(), msg);
127 }
128
129 void Core::initBackLog() {
130   QDir backLogDir = QDir(Global::quasselDir);
131   if(!backLogDir.exists()) {
132     qWarning(QString("Creating backlog directory \"%1\"...").arg(backLogDir.absolutePath()).toAscii());
133     if(!backLogDir.mkpath(backLogDir.absolutePath())) {
134       qWarning(QString("Could not create backlog directory! Disabling logging...").toAscii());
135       backLogEnabled = false;
136       return;
137     }
138   }
139   QString backLogFile = Global::quasselDir + "/quassel-backlog.sqlite";
140   logDb = QSqlDatabase::addDatabase("QSQLITE", "backlog");
141   logDb.setDatabaseName(backLogFile);
142   bool ok = logDb.open();
143   if(!ok) {
144     qWarning(tr("Could not open backlog database: %1").arg(logDb.lastError().text()).toAscii());
145     qWarning(tr("Disabling logging...").toAscii());
146   }
147   // TODO store database version
148   QSqlQuery query = logDb.exec("CREATE TABLE IF NOT EXISTS backlog ("
149                          "Time INTEGER, User TEXT, Network TEXT, Buffer TEXT, Message BLOB"
150                          ");");
151   if(query.lastError().isValid()) {
152     qWarning(tr("Could not create backlog table: %1").arg(query.lastError().text()).toAscii());
153     qWarning(tr("Disabling logging...").toAscii());
154     backLogEnabled = false;
155     return;
156   }
157
158   backLogEnabled = true;
159 }
160
161 // file name scheme: quassel-backlog-2006-29-10.bin
162 void Core::initBackLogOld() {
163   backLogDir = QDir(Global::quasselDir + "/backlog");
164   if(!backLogDir.exists()) {
165     qWarning(QString("Creating backlog directory \"%1\"...").arg(backLogDir.absolutePath()).toAscii());
166     if(!backLogDir.mkpath(backLogDir.absolutePath())) {
167       qWarning(QString("Could not create backlog directory! Disabling logging...").toAscii());
168       backLogEnabled = false;
169       return;
170     }
171   }
172   backLogDir.refresh();
173   //if(!backLogDir.isReadable()) {
174   //  qWarning(QString("Cannot read directory \"%1\". Disabling logging...").arg(backLogDir.absolutePath()).toAscii());
175   //  backLogEnabled = false;
176   //  return;
177   //}
178   QStringList networks = backLogDir.entryList(QDir::Dirs|QDir::NoDotAndDotDot|QDir::Readable, QDir::Name);
179   foreach(QString net, networks) {
180     QDir dir(backLogDir.absolutePath() + "/" + net);
181     if(!dir.exists()) {
182       qWarning(QString("Could not change to directory \"%1\"!").arg(dir.absolutePath()).toAscii());
183       continue;
184     }
185     QStringList logs = dir.entryList(QStringList("quassel-backlog-*.bin"), QDir::Files|QDir::Readable, QDir::Name);
186     foreach(QString name, logs) {
187       QFile f(dir.absolutePath() + "/" + name);
188       if(!f.open(QIODevice::ReadOnly)) {
189         qWarning(QString("Could not open \"%1\" for reading!").arg(f.fileName()).toAscii());
190         continue;
191       }
192       QDataStream in(&f);
193       in.setVersion(QDataStream::Qt_4_2);
194       QByteArray verstring; quint8 vernum; in >> verstring >> vernum;
195       if(verstring != BACKLOG_STRING) {
196         qWarning(QString("\"%1\" is not a Quassel backlog file!").arg(f.fileName()).toAscii());
197         f.close(); continue;
198       }
199       if(vernum != BACKLOG_FORMAT) {
200         qWarning(QString("\"%1\": Version mismatch!").arg(f.fileName()).toAscii());
201         f.close(); continue;
202       }
203       //qDebug() << "Reading backlog from" << f.fileName();
204       logFileDates[net] = QDate::fromString(f.fileName(),
205           QString("'%1/quassel-backlog-'yyyy-MM-dd'.bin'").arg(dir.absolutePath()));
206       if(!logFileDates[net].isValid()) {
207         qWarning(QString("\"%1\" has an invalid file name!").arg(f.fileName()).toAscii());
208       }
209       while(!in.atEnd()) {
210         Message m;
211         in >> m;
212         backLog[net].append(m);
213       }
214       f.close();
215     }
216   }
217   backLogEnabled = true;
218 }
219
220 void Core::logMessage(QString net, Message msg) {
221   if(!backLogEnabled) return;
222   QString buf;
223   if(msg.flags & Message::PrivMsg) {
224     // query
225     if(msg.flags & Message::Self) buf = msg.target;
226     else buf = nickFromMask(msg.sender);
227   } else {
228     buf = msg.target;
229   }
230   QSqlQuery query = logDb.exec(QString("INSERT INTO backlog Time, User, Network, Buffer, Message "
231                                "VALUES %1, %2, %3, %4, %5;")
232       .arg(msg.timeStamp.toTime_t()).arg("Default").arg(net).arg(buf).arg(msg.text));
233   if(query.lastError().isValid()) {
234     qWarning(tr("Error while logging to database: %1").arg(query.lastError().text()).toAscii());
235   }
236
237 }
238
239
240 /** Log a core message (emitted via a displayMsg() signal) to the backlog file.
241  * If a file for the current day does not exist, one will be created. Otherwise, messages will be appended.
242  * The file header is the string defined by BACKLOG_STRING, followed by a quint8 specifying the format
243  * version (BACKLOG_FORMAT). The rest is simply serialized Message objects.
244  */
245 void Core::logMessageOld(QString net, Message msg) {
246   backLog[net].append(msg);
247   if(!logFileDirs.contains(net)) {
248     QDir dir(backLogDir.absolutePath() + "/" + net);
249     if(!dir.exists()) {
250       qWarning(QString("Creating backlog directory \"%1\"...").arg(dir.absolutePath()).toAscii());
251       if(!dir.mkpath(dir.absolutePath())) {
252         qWarning(QString("Could not create backlog directory!").toAscii());
253         return;
254       }
255     }
256     logFileDirs[net] = dir;
257     Q_ASSERT(!logFiles.contains(net) && !logStreams.contains(net));
258     if(!logFiles.contains(net)) logFiles[net] = new QFile();
259     if(!logStreams.contains(net)) logStreams[net] = new QDataStream();
260   }
261   if(!logFileDates[net].isValid() || logFileDates[net] < QDate::currentDate()) {
262     if(logFiles[net]->isOpen()) logFiles[net]->close();
263     logFileDates[net] = QDate::currentDate();
264   }
265   if(!logFiles[net]->isOpen()) {
266     logFiles[net]->setFileName(QString("%1/%2").arg(logFileDirs[net].absolutePath())
267         .arg(logFileDates[net].toString("'quassel-backlog-'yyyy-MM-dd'.bin'")));
268     if(!logFiles[net]->open(QIODevice::WriteOnly|QIODevice::Append|QIODevice::Unbuffered)) {
269       qWarning(QString("Could not open \"%1\" for writing: %2")
270           .arg(logFiles[net]->fileName()).arg(logFiles[net]->errorString()).toAscii());
271       return;
272     }
273     logStreams[net]->setDevice(logFiles[net]); logStreams[net]->setVersion(QDataStream::Qt_4_2);
274     if(!logFiles[net]->size()) *logStreams[net] << BACKLOG_STRING << (quint8)BACKLOG_FORMAT;
275   }
276   *logStreams[net] << msg;
277 }
278
279 Core *core = 0;