removed a no longer needed debug message... damn scared the hell out of me ;)
[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       Q_ASSERT(watchQuery(createNetworkQuery));
156       createBufferQuery->exec();
157       Q_ASSERT(watchQuery(createBufferQuery));
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(":networkid", networkId.toInt());
195   getBufferInfoQuery->bindValue(":userid", user.toInt());
196   getBufferInfoQuery->bindValue(":buffername", buffer);
197   getBufferInfoQuery->exec();
198
199   if(!getBufferInfoQuery->first()) {
200     createBuffer(user, network, buffer);
201     getBufferInfoQuery->exec();
202     if(getBufferInfoQuery->first()) {
203       bufferid = BufferInfo(getBufferInfoQuery->value(0).toInt(), networkId, 0, network, buffer);
204       emit bufferInfoUpdated(user, bufferid);
205     }
206   } else {
207     bufferid = BufferInfo(getBufferInfoQuery->value(0).toInt(), networkId, 0, network, buffer);
208   }
209
210   Q_ASSERT(!getBufferInfoQuery->next());
211
212   return bufferid;
213 }
214
215 QList<BufferInfo> SqliteStorage::requestBuffers(UserId user, QDateTime since) {
216   uint time = 0;
217   if(since.isValid())
218     time = since.toTime_t();
219   
220   QList<BufferInfo> bufferlist;
221   QSqlQuery query(logDb());
222   query.prepare(queryString("select_buffers"));
223   query.bindValue(":userid", user.toInt());
224   query.bindValue(":time", time);
225   
226   query.exec();
227   watchQuery(&query);
228   while(query.next()) {
229     bufferlist << BufferInfo(query.value(0).toInt(), query.value(2).toInt(), 0, query.value(3).toString(), query.value(1).toString());
230   }
231   return bufferlist;
232 }
233
234 MsgId SqliteStorage::logMessage(Message msg) {
235   QSqlQuery *logMessageQuery = cachedQuery("insert_message");
236   logMessageQuery->bindValue(":time", msg.timestamp().toTime_t());
237   logMessageQuery->bindValue(":bufferid", msg.buffer().uid().toInt());
238   logMessageQuery->bindValue(":type", msg.type());
239   logMessageQuery->bindValue(":flags", msg.flags());
240   logMessageQuery->bindValue(":sender", msg.sender());
241   logMessageQuery->bindValue(":message", msg.text());
242   logMessageQuery->exec();
243   
244   if(logMessageQuery->lastError().isValid()) {
245     // constraint violation - must be NOT NULL constraint - probably the sender is missing...
246     if(logMessageQuery->lastError().number() == 19) {
247       QSqlQuery *addSenderQuery = cachedQuery("insert_sender");
248       addSenderQuery->bindValue(":sender", msg.sender());
249       addSenderQuery->exec();
250       watchQuery(addSenderQuery);
251       logMessageQuery->exec();
252       if(!watchQuery(logMessageQuery))
253         return 0;
254     } else {
255       watchQuery(logMessageQuery);
256     }
257   }
258
259   QSqlQuery *getLastMessageIdQuery = cachedQuery("select_lastMessage");
260   getLastMessageIdQuery->bindValue(":time", msg.timestamp().toTime_t());
261   getLastMessageIdQuery->bindValue(":bufferid", msg.buffer().uid().toInt());
262   getLastMessageIdQuery->bindValue(":type", msg.type());
263   getLastMessageIdQuery->bindValue(":sender", msg.sender());
264   getLastMessageIdQuery->exec();
265
266   if(getLastMessageIdQuery->first()) {
267     return getLastMessageIdQuery->value(0).toInt();
268   } else { // somethin went wrong... :(
269     qDebug() << getLastMessageIdQuery->lastQuery() << "time/bufferid/type/sender:" << msg.timestamp().toTime_t() << msg.buffer().uid() << msg.type() << msg.sender();
270     Q_ASSERT(false);
271     return 0;
272   }
273 }
274
275 QList<Message> SqliteStorage::requestMsgs(BufferInfo buffer, int lastmsgs, int offset) {
276   QList<Message> messagelist;
277   // we have to determine the real offset first
278   QSqlQuery *requestMsgsOffsetQuery = cachedQuery("select_messagesOffset");
279   requestMsgsOffsetQuery->bindValue(":bufferid", buffer.uid().toInt());
280   requestMsgsOffsetQuery->bindValue(":messageid", offset);
281   requestMsgsOffsetQuery->exec();
282   requestMsgsOffsetQuery->first();
283   offset = requestMsgsOffsetQuery->value(0).toInt();
284
285   // now let's select the messages
286   QSqlQuery *requestMsgsQuery = cachedQuery("select_messages");
287   requestMsgsQuery->bindValue(":bufferid", buffer.uid().toInt());
288   requestMsgsQuery->bindValue(":bufferid2", buffer.uid().toInt());  // Qt can't handle the same placeholder used twice
289   requestMsgsQuery->bindValue(":limit", lastmsgs);
290   requestMsgsQuery->bindValue(":offset", offset);
291   requestMsgsQuery->exec();
292   while(requestMsgsQuery->next()) {
293     Message msg(QDateTime::fromTime_t(requestMsgsQuery->value(1).toInt()),
294                 buffer,
295                 (Message::Type)requestMsgsQuery->value(2).toUInt(),
296                 requestMsgsQuery->value(5).toString(),
297                 requestMsgsQuery->value(4).toString(),
298                 requestMsgsQuery->value(3).toUInt());
299     msg.setMsgId(requestMsgsQuery->value(0).toInt());
300     messagelist << msg;
301   }
302   return messagelist;
303 }
304
305
306 QList<Message> SqliteStorage::requestMsgs(BufferInfo buffer, QDateTime since, int offset) {
307   QList<Message> messagelist;
308   // we have to determine the real offset first
309   QSqlQuery *requestMsgsSinceOffsetQuery = cachedQuery("select_messagesSinceOffset");
310   requestMsgsSinceOffsetQuery->bindValue(":bufferid", buffer.uid().toInt());
311   requestMsgsSinceOffsetQuery->bindValue(":since", since.toTime_t());
312   requestMsgsSinceOffsetQuery->exec();
313   requestMsgsSinceOffsetQuery->first();
314   offset = requestMsgsSinceOffsetQuery->value(0).toInt();
315
316   // now let's select the messages
317   QSqlQuery *requestMsgsSinceQuery = cachedQuery("select_messagesSince");
318   requestMsgsSinceQuery->bindValue(":bufferid", buffer.uid().toInt());
319   requestMsgsSinceQuery->bindValue(":bufferid2", buffer.uid().toInt());
320   requestMsgsSinceQuery->bindValue(":since", since.toTime_t());
321   requestMsgsSinceQuery->bindValue(":offset", offset);
322   requestMsgsSinceQuery->exec();
323
324   while(requestMsgsSinceQuery->next()) {
325     Message msg(QDateTime::fromTime_t(requestMsgsSinceQuery->value(1).toInt()),
326                 buffer,
327                 (Message::Type)requestMsgsSinceQuery->value(2).toUInt(),
328                 requestMsgsSinceQuery->value(5).toString(),
329                 requestMsgsSinceQuery->value(4).toString(),
330                 requestMsgsSinceQuery->value(3).toUInt());
331     msg.setMsgId(requestMsgsSinceQuery->value(0).toInt());
332     messagelist << msg;
333   }
334
335   return messagelist;
336 }
337
338
339 QList<Message> SqliteStorage::requestMsgRange(BufferInfo buffer, int first, int last) {
340   QList<Message> messagelist;
341   QSqlQuery *requestMsgRangeQuery = cachedQuery("select_messageRange");
342   requestMsgRangeQuery->bindValue(":bufferid", buffer.uid().toInt());
343   requestMsgRangeQuery->bindValue(":bufferid2", buffer.uid().toInt());
344   requestMsgRangeQuery->bindValue(":firstmsg", first);
345   requestMsgRangeQuery->bindValue(":lastmsg", last);
346
347   while(requestMsgRangeQuery->next()) {
348     Message msg(QDateTime::fromTime_t(requestMsgRangeQuery->value(1).toInt()),
349                 buffer,
350                 (Message::Type)requestMsgRangeQuery->value(2).toUInt(),
351                 requestMsgRangeQuery->value(5).toString(),
352                 requestMsgRangeQuery->value(4).toString(),
353                 requestMsgRangeQuery->value(3).toUInt());
354     msg.setMsgId(requestMsgRangeQuery->value(0).toInt());
355     messagelist << msg;
356   }
357
358   return messagelist;
359 }
360
361 QString SqliteStorage::backlogFile() {
362   // kinda ugly, but I currently see no other way to do that
363 #ifdef Q_OS_WIN32
364   QString quasselDir = QDir::homePath() + qgetenv("APPDATA") + "\\quassel\\";
365 #else
366   QString quasselDir = QDir::homePath() + "/.quassel/";
367 #endif
368
369   QDir qDir(quasselDir);
370   if(!qDir.exists(quasselDir))
371     qDir.mkpath(quasselDir);
372   
373   return quasselDir + "quassel-storage.sqlite";  
374 }
375