Make Quassel compile on MSVC.
[quassel.git] / src / core / sqlitestorage.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-07 by the Quassel IRC 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) version 3.                                           *
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 "sqlitestorage.h"
22
23 #include <QCryptographicHash>
24
25 #include <QtSql>
26
27 SqliteStorage::SqliteStorage(QObject *parent)
28   : AbstractSqlStorage(parent)
29 {
30 }
31
32 SqliteStorage::~SqliteStorage() {
33 }
34
35 bool SqliteStorage::isAvailable() {
36   if(!QSqlDatabase::isDriverAvailable("QSQLITE")) return false;
37   return true;
38 }
39
40 QString SqliteStorage::displayName() {
41   return QString("SQLite");
42 }
43
44 QString SqliteStorage::engineName() {
45   return SqliteStorage::displayName();
46 }
47
48 int SqliteStorage::installedSchemaVersion() {
49   QSqlQuery query = logDb().exec("SELECT value FROM coreinfo WHERE key = 'schemaversion'");
50   if(query.first())
51     return query.value(0).toInt();
52
53   // maybe it's really old... (schema version 0)
54   query = logDb().exec("SELECT MAX(version) FROM coreinfo");
55   if(query.first())
56     return query.value(0).toInt();
57
58   return AbstractSqlStorage::installedSchemaVersion();
59 }
60
61 UserId SqliteStorage::addUser(const QString &user, const QString &password) {
62   QByteArray cryptopass = QCryptographicHash::hash(password.toUtf8(), QCryptographicHash::Sha1);
63   cryptopass = cryptopass.toHex();
64
65   QSqlQuery query(logDb());
66   query.prepare(queryString("insert_quasseluser"));
67   query.bindValue(":username", user);
68   query.bindValue(":password", cryptopass);
69   query.exec();
70   if(query.lastError().isValid() && query.lastError().number() == 19) { // user already exists - sadly 19 seems to be the general constraint violation error...
71     return 0;
72   }
73
74   query.prepare(queryString("select_userid"));
75   query.bindValue(":username", user);
76   query.exec();
77   query.first();
78   UserId uid = query.value(0).toInt();
79   emit userAdded(uid, user);
80   return uid;
81 }
82
83 void SqliteStorage::updateUser(UserId user, const QString &password) {
84   QByteArray cryptopass = QCryptographicHash::hash(password.toUtf8(), QCryptographicHash::Sha1);
85   cryptopass = cryptopass.toHex();
86
87   QSqlQuery query(logDb());
88   query.prepare(queryString("update_userpassword"));
89   query.bindValue(":userid", user.toInt());
90   query.bindValue(":password", cryptopass);
91   query.exec();
92 }
93
94 void SqliteStorage::renameUser(UserId user, const QString &newName) {
95   QSqlQuery query(logDb());
96   query.prepare(queryString("update_username"));
97   query.bindValue(":userid", user.toInt());
98   query.bindValue(":username", newName);
99   query.exec();
100   emit userRenamed(user, newName);
101 }
102
103 UserId SqliteStorage::validateUser(const QString &user, const QString &password) {
104   QByteArray cryptopass = QCryptographicHash::hash(password.toUtf8(), QCryptographicHash::Sha1);
105   cryptopass = cryptopass.toHex();
106
107   QSqlQuery query(logDb());
108   query.prepare(queryString("select_authuser"));
109   query.bindValue(":username", user);
110   query.bindValue(":password", cryptopass);
111   query.exec();
112
113   if(query.first()) {
114     return query.value(0).toInt();
115   } else {
116     return 0;
117   }
118 }
119
120 void SqliteStorage::delUser(UserId user) {
121   QSqlQuery query(logDb());
122   query.prepare(queryString("delete_backlog_by_uid"));
123   query.bindValue(":userid", user.toInt());
124   query.exec();
125   
126   query.prepare(queryString("delete_buffers_by_uid"));
127   query.bindValue(":userid", user.toInt());
128   query.exec();
129   
130   query.prepare(queryString("delete_networks_by_uid"));
131   query.bindValue(":userid", user.toInt());
132   query.exec();
133   
134   query.prepare(queryString("delete_quasseluser"));
135   query.bindValue(":userid", user.toInt());
136   query.exec();
137   // I hate the lack of foreign keys and on delete cascade... :(
138   emit userRemoved(user);
139 }
140
141 void SqliteStorage::createBuffer(UserId user, const QString &network, const QString &buffer) {
142   QSqlQuery *createBufferQuery = cachedQuery("insert_buffer");
143   createBufferQuery->bindValue(":userid", user.toInt());
144   createBufferQuery->bindValue(":userid2", user.toInt());  // Qt can't handle same placeholder twice (maybe sqlites fault)
145   createBufferQuery->bindValue(":networkname", network);
146   createBufferQuery->bindValue(":buffername", buffer);
147   createBufferQuery->exec();
148
149   if(createBufferQuery->lastError().isValid()) {
150     if(createBufferQuery->lastError().number() == 19) { // Null Constraint violation
151       QSqlQuery *createNetworkQuery = cachedQuery("insert_network");
152       createNetworkQuery->bindValue(":userid", user.toInt());
153       createNetworkQuery->bindValue(":networkname", network);
154       createNetworkQuery->exec();
155       createBufferQuery->exec();
156       Q_ASSERT(!createNetworkQuery->lastError().isValid());
157       Q_ASSERT(!createBufferQuery->lastError().isValid());
158     } else {
159       // do panic!
160       qDebug() << "failed to create Buffer: ErrNo:" << createBufferQuery->lastError().number() << "ErrMsg:" << createBufferQuery->lastError().text();
161       Q_ASSERT(false);
162     }
163   }
164 }
165
166 NetworkId SqliteStorage::getNetworkId(UserId user, const QString &network) {
167   QSqlQuery query(logDb());
168   query.prepare("SELECT networkid FROM network "
169                 "WHERE userid = :userid AND networkname = :networkname");
170   query.bindValue(":userid", user.toInt());
171   query.bindValue(":networkname", network);
172   query.exec();
173   
174   if(query.first())
175     return query.value(0).toInt();
176   else {
177     createBuffer(user, network, "");
178     query.exec();
179     if(query.first())
180       return query.value(0).toInt();
181     else {
182       qWarning() << "NETWORK NOT FOUND:" << network << "for User:" << user;
183       return 0;
184     }
185   }
186 }
187
188 BufferInfo SqliteStorage::getBufferInfo(UserId user, const QString &network, const QString &buffer) {
189   BufferInfo bufferid;
190   // TODO: get rid of this hackaround
191   NetworkId networkId = getNetworkId(user, network);
192
193   QSqlQuery *getBufferInfoQuery = cachedQuery("select_bufferByName");
194   getBufferInfoQuery->bindValue(":networkname", network);
195   getBufferInfoQuery->bindValue(":userid", user.toInt());
196   getBufferInfoQuery->bindValue(":userid2", user.toInt()); // Qt can't handle same placeholder twice... though I guess it's sqlites fault
197   getBufferInfoQuery->bindValue(":buffername", buffer);
198   getBufferInfoQuery->exec();
199
200   if(!getBufferInfoQuery->first()) {
201     createBuffer(user, network, buffer);
202     getBufferInfoQuery->exec();
203     if(getBufferInfoQuery->first()) {
204       bufferid = BufferInfo(getBufferInfoQuery->value(0).toInt(), networkId, 0, network, buffer);
205       emit bufferInfoUpdated(user, bufferid);
206     }
207   } else {
208     bufferid = BufferInfo(getBufferInfoQuery->value(0).toInt(), networkId, 0, network, buffer);
209   }
210
211   Q_ASSERT(!getBufferInfoQuery->next());
212
213   return bufferid;
214 }
215
216 QList<BufferInfo> SqliteStorage::requestBuffers(UserId user, QDateTime since) {
217   uint time = 0;
218   if(since.isValid())
219     time = since.toTime_t();
220   
221   QList<BufferInfo> bufferlist;
222   QSqlQuery query(logDb());
223   query.prepare(queryString("select_buffers"));
224   query.bindValue(":userid", user.toInt());
225   query.bindValue(":time", time);
226   
227   query.exec();
228   watchQuery(&query);
229   while(query.next()) {
230     bufferlist << BufferInfo(query.value(0).toInt(), query.value(2).toInt(), 0, query.value(3).toString(), query.value(1).toString());
231   }
232   return bufferlist;
233 }
234
235 MsgId SqliteStorage::logMessage(Message msg) {
236   QSqlQuery *logMessageQuery = cachedQuery("insert_message");
237   logMessageQuery->bindValue(":time", msg.timestamp().toTime_t());
238   logMessageQuery->bindValue(":bufferid", msg.buffer().uid().toInt());
239   logMessageQuery->bindValue(":type", msg.type());
240   logMessageQuery->bindValue(":flags", msg.flags());
241   logMessageQuery->bindValue(":sender", msg.sender());
242   logMessageQuery->bindValue(":message", msg.text());
243   logMessageQuery->exec();
244   
245   if(logMessageQuery->lastError().isValid()) {
246     // constraint violation - must be NOT NULL constraint - probably the sender is missing...
247     if(logMessageQuery->lastError().number() == 19) {
248       QSqlQuery *addSenderQuery = cachedQuery("insert_sender");
249       addSenderQuery->bindValue(":sender", msg.sender());
250       addSenderQuery->exec();
251       watchQuery(addSenderQuery);
252       logMessageQuery->exec();
253       if(!watchQuery(logMessageQuery))
254         return 0;
255     } else {
256       watchQuery(logMessageQuery);
257     }
258   }
259
260   QSqlQuery *getLastMessageIdQuery = cachedQuery("select_lastMessage");
261   getLastMessageIdQuery->bindValue(":time", msg.timestamp().toTime_t());
262   getLastMessageIdQuery->bindValue(":bufferid", msg.buffer().uid().toInt());
263   getLastMessageIdQuery->bindValue(":type", msg.type());
264   getLastMessageIdQuery->bindValue(":sender", msg.sender());
265   getLastMessageIdQuery->exec();
266
267   if(getLastMessageIdQuery->first()) {
268     return getLastMessageIdQuery->value(0).toInt();
269   } else { // somethin went wrong... :(
270     qDebug() << getLastMessageIdQuery->lastQuery() << "time/bufferid/type/sender:" << msg.timestamp().toTime_t() << msg.buffer().uid() << msg.type() << msg.sender();
271     Q_ASSERT(false);
272     return 0;
273   }
274 }
275
276 QList<Message> SqliteStorage::requestMsgs(BufferInfo buffer, int lastmsgs, int offset) {
277   QList<Message> messagelist;
278   // we have to determine the real offset first
279   QSqlQuery *requestMsgsOffsetQuery = cachedQuery("select_messagesOffset");
280   requestMsgsOffsetQuery->bindValue(":bufferid", buffer.uid().toInt());
281   requestMsgsOffsetQuery->bindValue(":messageid", offset);
282   requestMsgsOffsetQuery->exec();
283   requestMsgsOffsetQuery->first();
284   offset = requestMsgsOffsetQuery->value(0).toInt();
285
286   // now let's select the messages
287   QSqlQuery *requestMsgsQuery = cachedQuery("select_messages");
288   requestMsgsQuery->bindValue(":bufferid", buffer.uid().toInt());
289   requestMsgsQuery->bindValue(":bufferid2", buffer.uid().toInt());  // Qt can't handle the same placeholder used twice
290   requestMsgsQuery->bindValue(":limit", lastmsgs);
291   requestMsgsQuery->bindValue(":offset", offset);
292   requestMsgsQuery->exec();
293   while(requestMsgsQuery->next()) {
294     Message msg(QDateTime::fromTime_t(requestMsgsQuery->value(1).toInt()),
295                 buffer,
296                 (Message::Type)requestMsgsQuery->value(2).toUInt(),
297                 requestMsgsQuery->value(5).toString(),
298                 requestMsgsQuery->value(4).toString(),
299                 requestMsgsQuery->value(3).toUInt());
300     msg.setMsgId(requestMsgsQuery->value(0).toInt());
301     messagelist << msg;
302   }
303   return messagelist;
304 }
305
306
307 QList<Message> SqliteStorage::requestMsgs(BufferInfo buffer, QDateTime since, int offset) {
308   QList<Message> messagelist;
309   // we have to determine the real offset first
310   QSqlQuery *requestMsgsSinceOffsetQuery = cachedQuery("select_messagesSinceOffset");
311   requestMsgsSinceOffsetQuery->bindValue(":bufferid", buffer.uid().toInt());
312   requestMsgsSinceOffsetQuery->bindValue(":since", since.toTime_t());
313   requestMsgsSinceOffsetQuery->exec();
314   requestMsgsSinceOffsetQuery->first();
315   offset = requestMsgsSinceOffsetQuery->value(0).toInt();
316
317   // now let's select the messages
318   QSqlQuery *requestMsgsSinceQuery = cachedQuery("select_messagesSince");
319   requestMsgsSinceQuery->bindValue(":bufferid", buffer.uid().toInt());
320   requestMsgsSinceQuery->bindValue(":bufferid2", buffer.uid().toInt());
321   requestMsgsSinceQuery->bindValue(":since", since.toTime_t());
322   requestMsgsSinceQuery->bindValue(":offset", offset);
323   requestMsgsSinceQuery->exec();
324
325   while(requestMsgsSinceQuery->next()) {
326     Message msg(QDateTime::fromTime_t(requestMsgsSinceQuery->value(1).toInt()),
327                 buffer,
328                 (Message::Type)requestMsgsSinceQuery->value(2).toUInt(),
329                 requestMsgsSinceQuery->value(5).toString(),
330                 requestMsgsSinceQuery->value(4).toString(),
331                 requestMsgsSinceQuery->value(3).toUInt());
332     msg.setMsgId(requestMsgsSinceQuery->value(0).toInt());
333     messagelist << msg;
334   }
335
336   return messagelist;
337 }
338
339
340 QList<Message> SqliteStorage::requestMsgRange(BufferInfo buffer, int first, int last) {
341   QList<Message> messagelist;
342   QSqlQuery *requestMsgRangeQuery = cachedQuery("select_messageRange");
343   requestMsgRangeQuery->bindValue(":bufferid", buffer.uid().toInt());
344   requestMsgRangeQuery->bindValue(":bufferid2", buffer.uid().toInt());
345   requestMsgRangeQuery->bindValue(":firstmsg", first);
346   requestMsgRangeQuery->bindValue(":lastmsg", last);
347
348   while(requestMsgRangeQuery->next()) {
349     Message msg(QDateTime::fromTime_t(requestMsgRangeQuery->value(1).toInt()),
350                 buffer,
351                 (Message::Type)requestMsgRangeQuery->value(2).toUInt(),
352                 requestMsgRangeQuery->value(5).toString(),
353                 requestMsgRangeQuery->value(4).toString(),
354                 requestMsgRangeQuery->value(3).toUInt());
355     msg.setMsgId(requestMsgRangeQuery->value(0).toInt());
356     messagelist << msg;
357   }
358
359   return messagelist;
360 }
361
362 QString SqliteStorage::backlogFile() {
363   // kinda ugly, but I currently see no other way to do that
364 #ifdef Q_OS_WIN32
365   QString quasselDir = QDir::homePath() + qgetenv("APPDATA") + "\\quassel\\";
366 #else
367   QString quasselDir = QDir::homePath() + "/.quassel/";
368 #endif
369
370   QDir qDir(quasselDir);
371   if(!qDir.exists(quasselDir))
372     qDir.mkpath(quasselDir);
373   
374   return quasselDir + "quassel-storage.sqlite";  
375 }
376