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