More small tweaks to fix scrollbar behavior. This really sucks.
[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 "coreproxy.h"
25
26 #include <QSettings>
27
28 Core::Core() {
29   if(core) qFatal("Trying to instantiate more than one Core object!");
30
31   connect(coreProxy, SIGNAL(requestServerStates()), this, SIGNAL(serverStateRequested()));
32   connect(coreProxy, SIGNAL(gsRequestConnect(QStringList)), this, SLOT(connectToIrc(QStringList)));
33   connect(coreProxy, SIGNAL(gsUserInput(QString, QString, QString)), this, SIGNAL(msgFromGUI(QString, QString, QString)));
34   connect(this, SIGNAL(displayMsg(QString, Message)), coreProxy, SLOT(csDisplayMsg(QString, Message)));
35   connect(this, SIGNAL(displayStatusMsg(QString, QString)), coreProxy, SLOT(csDisplayStatusMsg(QString, QString)));
36
37   // Read global settings from config file
38   QSettings s;
39   s.beginGroup("Global");
40   QString key;
41   foreach(key, s.childKeys()) {
42     global->updateData(key, s.value(key));
43   }
44   initBackLog();
45   global->updateData("CoreReady", true);
46   // Now that we are in sync, we can connect signals to automatically store further updates.
47   // I don't think we care if global data changed locally or if it was updated by a client. 
48   connect(global, SIGNAL(dataUpdatedRemotely(QString)), SLOT(globalDataUpdated(QString)));
49   connect(global, SIGNAL(dataPutLocally(QString)), SLOT(globalDataUpdated(QString)));
50
51 }
52
53 Core::~Core() {
54   //foreach(Server *s, servers) {
55   //  delete s;
56   //}
57   foreach(QDataStream *s, logStreams) {
58     delete s;
59   }
60   foreach(QFile *f, logFiles) {
61     if(f->isOpen()) f->close();
62     delete f;
63   }
64 }
65
66 void Core::globalDataUpdated(QString key) {
67   QVariant data = global->getData(key);
68   QSettings s;
69   s.setValue(QString("Global/")+key, data);
70 }
71
72 void Core::connectToIrc(QStringList networks) {
73   foreach(QString net, networks) {
74     if(servers.contains(net)) {
75
76     } else {
77       Server *server = new Server(net);
78       connect(this, SIGNAL(serverStateRequested()), server, SLOT(sendState()));
79       connect(this, SIGNAL(connectToIrc(QString)), server, SLOT(connectToIrc(QString)));
80       connect(this, SIGNAL(disconnectFromIrc(QString)), server, SLOT(disconnectFromIrc(QString)));
81       connect(this, SIGNAL(msgFromGUI(QString, QString, QString)), server, SLOT(userInput(QString, QString, QString)));
82       connect(server, SIGNAL(serverState(QString, VarMap)), coreProxy, SLOT(csServerState(QString, VarMap)));
83       connect(server, SIGNAL(displayMsg(Message)), this, SLOT(recvMessageFromServer(Message)));
84       connect(server, SIGNAL(displayStatusMsg(QString)), this, SLOT(recvStatusMsgFromServer(QString)));
85       connect(server, SIGNAL(modeSet(QString, QString, QString)), coreProxy, SLOT(csModeSet(QString, QString, QString)));
86       connect(server, SIGNAL(topicSet(QString, QString, QString)), coreProxy, SLOT(csTopicSet(QString, QString, QString)));
87       connect(server, SIGNAL(setNicks(QString, QString, QStringList)), coreProxy, SLOT(csSetNicks(QString, QString, QStringList)));
88       connect(server, SIGNAL(nickAdded(QString, QString, VarMap)), coreProxy, SLOT(csNickAdded(QString, QString, VarMap)));
89       connect(server, SIGNAL(nickRenamed(QString, QString, QString)), coreProxy, SLOT(csNickRenamed(QString, QString, QString)));
90       connect(server, SIGNAL(nickRemoved(QString, QString)), coreProxy, SLOT(csNickRemoved(QString, QString)));
91       connect(server, SIGNAL(nickUpdated(QString, QString, VarMap)), coreProxy, SLOT(csNickUpdated(QString, QString, VarMap)));
92       connect(server, SIGNAL(ownNickSet(QString, QString)), coreProxy, SLOT(csOwnNickSet(QString, QString)));
93       // add error handling
94       connect(server, SIGNAL(connected(QString)), coreProxy, SLOT(csServerConnected(QString)));
95       connect(server, SIGNAL(disconnected(QString)), this, SLOT(serverDisconnected(QString)));
96
97       server->start();
98       servers[net] = server;
99     }
100     emit connectToIrc(net);
101   }
102 }
103
104 void Core::serverDisconnected(QString net) {
105   delete servers[net];
106   servers.remove(net);
107   coreProxy->csServerDisconnected(net);
108 }
109
110 // ALL messages coming pass through these functions before going to the GUI.
111 // So this is the perfect place for storing the backlog and log stuff.
112 void Core::recvMessageFromServer(Message msg) {
113   Server *s = qobject_cast<Server*>(sender());
114   Q_ASSERT(s);
115   logMessage(s->getNetwork(), msg);
116   emit displayMsg(s->getNetwork(), msg);
117 }
118
119 void Core::recvStatusMsgFromServer(QString msg) {
120   Server *s = qobject_cast<Server*>(sender());
121   Q_ASSERT(s);
122   emit displayStatusMsg(s->getNetwork(), msg);
123 }
124
125 // file name scheme: quassel-backlog-2006-29-10.bin
126 void Core::initBackLog() {
127   backLogDir = QDir(Global::quasselDir + "/backlog");
128   if(!backLogDir.exists()) {
129     qWarning(QString("Creating backlog directory \"%1\"...").arg(backLogDir.absolutePath()).toAscii());
130     if(!backLogDir.mkpath(backLogDir.absolutePath())) {
131       qWarning(QString("Could not create backlog directory! Disabling logging...").toAscii());
132       backLogEnabled = false;
133       return;
134     }
135   }
136   backLogDir.refresh();
137   //if(!backLogDir.isReadable()) {
138   //  qWarning(QString("Cannot read directory \"%1\". Disabling logging...").arg(backLogDir.absolutePath()).toAscii());
139   //  backLogEnabled = false;
140   //  return;
141   //}
142   QStringList networks = backLogDir.entryList(QDir::Dirs|QDir::NoDotAndDotDot|QDir::Readable, QDir::Name);
143   foreach(QString net, networks) {
144     QDir dir(backLogDir.absolutePath() + "/" + net);
145     if(!dir.exists()) {
146       qWarning(QString("Could not change to directory \"%1\"!").arg(dir.absolutePath()).toAscii());
147       continue;
148     }
149     QStringList logs = dir.entryList(QStringList("quassel-backlog-*.bin"), QDir::Files|QDir::Readable, QDir::Name);
150     foreach(QString name, logs) {
151       QFile f(dir.absolutePath() + "/" + name);
152       if(!f.open(QIODevice::ReadOnly)) {
153         qWarning(QString("Could not open \"%1\" for reading!").arg(f.fileName()).toAscii());
154         continue;
155       }
156       QDataStream in(&f);
157       in.setVersion(QDataStream::Qt_4_2);
158       QByteArray verstring; quint8 vernum; in >> verstring >> vernum;
159       if(verstring != BACKLOG_STRING) {
160         qWarning(QString("\"%1\" is not a Quassel backlog file!").arg(f.fileName()).toAscii());
161         f.close(); continue;
162       }
163       if(vernum != BACKLOG_FORMAT) {
164         qWarning(QString("\"%1\": Version mismatch!").arg(f.fileName()).toAscii());
165         f.close(); continue;
166       }
167       qDebug() << "Reading backlog from" << f.fileName();
168       logFileDates[net] = QDate::fromString(f.fileName(),
169           QString("'%1/quassel-backlog-'yyyy-MM-dd'.bin'").arg(dir.absolutePath()));
170       if(!logFileDates[net].isValid()) {
171         qWarning(QString("\"%1\" has an invalid file name!").arg(f.fileName()).toAscii());
172       }
173       while(!in.atEnd()) {
174         Message m;
175         in >> m;
176         backLog[net].append(m);
177       }
178       f.close();
179     }
180   }
181   backLogEnabled = true;
182 }
183
184 /** Log a core message (emitted via a displayMsg() signal) to the backlog file.
185  * If a file for the current day does not exist, one will be created. Otherwise, messages will be appended.
186  * The file header is the string defined by BACKLOG_STRING, followed by a quint8 specifying the format
187  * version (BACKLOG_FORMAT). The rest is simply serialized Message objects.
188  */
189 void Core::logMessage(QString net, Message msg) {
190   backLog[net].append(msg);
191   if(!logFileDirs.contains(net)) {
192     QDir dir(backLogDir.absolutePath() + "/" + net);
193     if(!dir.exists()) {
194       qWarning(QString("Creating backlog directory \"%1\"...").arg(dir.absolutePath()).toAscii());
195       if(!dir.mkpath(dir.absolutePath())) {
196         qWarning(QString("Could not create backlog directory!").toAscii());
197         return;
198       }
199     }
200     logFileDirs[net] = dir;
201     Q_ASSERT(!logFiles.contains(net) && !logStreams.contains(net));
202     if(!logFiles.contains(net)) logFiles[net] = new QFile();
203     if(!logStreams.contains(net)) logStreams[net] = new QDataStream();
204   }
205   if(!logFileDates[net].isValid() || logFileDates[net] < QDate::currentDate()) {
206     if(logFiles[net]->isOpen()) logFiles[net]->close();
207     logFileDates[net] = QDate::currentDate();
208   }
209   if(!logFiles[net]->isOpen()) {
210     logFiles[net]->setFileName(QString("%1/%2").arg(logFileDirs[net].absolutePath())
211         .arg(logFileDates[net].toString("'quassel-backlog-'yyyy-MM-dd'.bin'")));
212     if(!logFiles[net]->open(QIODevice::WriteOnly|QIODevice::Append|QIODevice::Unbuffered)) {
213       qWarning(QString("Could not open \"%1\" for writing: %2")
214           .arg(logFiles[net]->fileName()).arg(logFiles[net]->errorString()).toAscii());
215       return;
216     }
217     logStreams[net]->setDevice(logFiles[net]); logStreams[net]->setVersion(QDataStream::Qt_4_2);
218     if(!logFiles[net]->size()) *logStreams[net] << BACKLOG_STRING << (quint8)BACKLOG_FORMAT;
219   }
220   *logStreams[net] << msg;
221 }
222
223 Core *core = 0;