Merge pull request #5 from Tucos/feat-keyx
[quassel.git] / src / core / postgresqlstorage.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2013 by the Quassel Project                        *
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  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
19  ***************************************************************************/
20
21 #include "postgresqlstorage.h"
22
23 #include <QtSql>
24
25 #include "logger.h"
26 #include "network.h"
27 #include "quassel.h"
28
29 PostgreSqlStorage::PostgreSqlStorage(QObject *parent)
30     : AbstractSqlStorage(parent),
31     _port(-1)
32 {
33 }
34
35
36 PostgreSqlStorage::~PostgreSqlStorage()
37 {
38 }
39
40
41 AbstractSqlMigrationWriter *PostgreSqlStorage::createMigrationWriter()
42 {
43     PostgreSqlMigrationWriter *writer = new PostgreSqlMigrationWriter();
44     QVariantMap properties;
45     properties["Username"] = _userName;
46     properties["Password"] = _password;
47     properties["Hostname"] = _hostName;
48     properties["Port"] = _port;
49     properties["Database"] = _databaseName;
50     writer->setConnectionProperties(properties);
51     return writer;
52 }
53
54
55 bool PostgreSqlStorage::isAvailable() const
56 {
57     qDebug() << QSqlDatabase::drivers();
58     if (!QSqlDatabase::isDriverAvailable("QPSQL")) return false;
59     return true;
60 }
61
62
63 QString PostgreSqlStorage::displayName() const
64 {
65     return QString("PostgreSQL");
66 }
67
68
69 QString PostgreSqlStorage::description() const
70 {
71     // FIXME: proper description
72     return tr("PostgreSQL Turbo Bomber HD!");
73 }
74
75
76 QStringList PostgreSqlStorage::setupKeys() const
77 {
78     QStringList keys;
79     keys << "Username"
80          << "Password"
81          << "Hostname"
82          << "Port"
83          << "Database";
84     return keys;
85 }
86
87
88 QVariantMap PostgreSqlStorage::setupDefaults() const
89 {
90     QVariantMap map;
91     map["Username"] = QVariant(QString("quassel"));
92     map["Hostname"] = QVariant(QString("localhost"));
93     map["Port"] = QVariant(5432);
94     map["Database"] = QVariant(QString("quassel"));
95     return map;
96 }
97
98
99 void PostgreSqlStorage::initDbSession(QSqlDatabase &db)
100 {
101     // this blows... but unfortunately Qt's PG driver forces us to this...
102     db.exec("set standard_conforming_strings = off");
103     db.exec("set escape_string_warning = off");
104 }
105
106
107 void PostgreSqlStorage::setConnectionProperties(const QVariantMap &properties)
108 {
109     _userName = properties["Username"].toString();
110     _password = properties["Password"].toString();
111     _hostName = properties["Hostname"].toString();
112     _port = properties["Port"].toInt();
113     _databaseName = properties["Database"].toString();
114 }
115
116
117 int PostgreSqlStorage::installedSchemaVersion()
118 {
119     QSqlQuery query = logDb().exec("SELECT value FROM coreinfo WHERE key = 'schemaversion'");
120     if (query.first())
121         return query.value(0).toInt();
122
123     // maybe it's really old... (schema version 0)
124     query = logDb().exec("SELECT MAX(version) FROM coreinfo");
125     if (query.first())
126         return query.value(0).toInt();
127
128     return AbstractSqlStorage::installedSchemaVersion();
129 }
130
131
132 bool PostgreSqlStorage::updateSchemaVersion(int newVersion)
133 {
134     QSqlQuery query(logDb());
135     query.prepare("UPDATE coreinfo SET value = :version WHERE key = 'schemaversion'");
136     query.bindValue(":version", newVersion);
137     query.exec();
138
139     bool success = true;
140     if (query.lastError().isValid()) {
141         qCritical() << "PostgreSqlStorage::updateSchemaVersion(int): Updating schema version failed!";
142         success = false;
143     }
144     return success;
145 }
146
147
148 bool PostgreSqlStorage::setupSchemaVersion(int version)
149 {
150     QSqlQuery query(logDb());
151     query.prepare("INSERT INTO coreinfo (key, value) VALUES ('schemaversion', :version)");
152     query.bindValue(":version", version);
153     query.exec();
154
155     bool success = true;
156     if (query.lastError().isValid()) {
157         qCritical() << "PostgreSqlStorage::setupSchemaVersion(int): Updating schema version failed!";
158         success = false;
159     }
160     return success;
161 }
162
163
164 UserId PostgreSqlStorage::addUser(const QString &user, const QString &password)
165 {
166     QSqlQuery query(logDb());
167     query.prepare(queryString("insert_quasseluser"));
168     query.bindValue(":username", user);
169     query.bindValue(":password", cryptedPassword(password));
170     safeExec(query);
171     if (!watchQuery(query))
172         return 0;
173
174     query.first();
175     UserId uid = query.value(0).toInt();
176     emit userAdded(uid, user);
177     return uid;
178 }
179
180
181 bool PostgreSqlStorage::updateUser(UserId user, const QString &password)
182 {
183     QSqlQuery query(logDb());
184     query.prepare(queryString("update_userpassword"));
185     query.bindValue(":userid", user.toInt());
186     query.bindValue(":password", cryptedPassword(password));
187     safeExec(query);
188     return query.numRowsAffected() != 0;
189 }
190
191
192 void PostgreSqlStorage::renameUser(UserId user, const QString &newName)
193 {
194     QSqlQuery query(logDb());
195     query.prepare(queryString("update_username"));
196     query.bindValue(":userid", user.toInt());
197     query.bindValue(":username", newName);
198     safeExec(query);
199     emit userRenamed(user, newName);
200 }
201
202
203 UserId PostgreSqlStorage::validateUser(const QString &user, const QString &password)
204 {
205     QSqlQuery query(logDb());
206     query.prepare(queryString("select_authuser"));
207     query.bindValue(":username", user);
208     query.bindValue(":password", cryptedPassword(password));
209     safeExec(query);
210
211     if (query.first()) {
212         return query.value(0).toInt();
213     }
214     else {
215         return 0;
216     }
217 }
218
219
220 UserId PostgreSqlStorage::getUserId(const QString &user)
221 {
222     QSqlQuery query(logDb());
223     query.prepare(queryString("select_userid"));
224     query.bindValue(":username", user);
225     safeExec(query);
226
227     if (query.first()) {
228         return query.value(0).toInt();
229     }
230     else {
231         return 0;
232     }
233 }
234
235
236 UserId PostgreSqlStorage::internalUser()
237 {
238     QSqlQuery query(logDb());
239     query.prepare(queryString("select_internaluser"));
240     safeExec(query);
241
242     if (query.first()) {
243         return query.value(0).toInt();
244     }
245     else {
246         return 0;
247     }
248 }
249
250
251 void PostgreSqlStorage::delUser(UserId user)
252 {
253     QSqlDatabase db = logDb();
254     if (!db.transaction()) {
255         qWarning() << "PostgreSqlStorage::delUser(): cannot start transaction!";
256         return;
257     }
258
259     QSqlQuery query(db);
260     query.prepare(queryString("delete_quasseluser"));
261     query.bindValue(":userid", user.toInt());
262     safeExec(query);
263     if (!watchQuery(query)) {
264         db.rollback();
265         return;
266     }
267     else {
268         db.commit();
269         emit userRemoved(user);
270     }
271 }
272
273
274 void PostgreSqlStorage::setUserSetting(UserId userId, const QString &settingName, const QVariant &data)
275 {
276     QByteArray rawData;
277     QDataStream out(&rawData, QIODevice::WriteOnly);
278     out.setVersion(QDataStream::Qt_4_2);
279     out << data;
280
281     QSqlDatabase db = logDb();
282     QSqlQuery selectQuery(db);
283     selectQuery.prepare(queryString("select_user_setting"));
284     selectQuery.bindValue(":userid", userId.toInt());
285     selectQuery.bindValue(":settingname", settingName);
286     safeExec(selectQuery);
287
288     QString setQueryString;
289     if (!selectQuery.first()) {
290         setQueryString = queryString("insert_user_setting");
291     }
292     else {
293         setQueryString = queryString("update_user_setting");
294     }
295
296     QSqlQuery setQuery(db);
297     setQuery.prepare(setQueryString);
298     setQuery.bindValue(":userid", userId.toInt());
299     setQuery.bindValue(":settingname", settingName);
300     setQuery.bindValue(":settingvalue", rawData);
301     safeExec(setQuery);
302 }
303
304
305 QVariant PostgreSqlStorage::getUserSetting(UserId userId, const QString &settingName, const QVariant &defaultData)
306 {
307     QSqlQuery query(logDb());
308     query.prepare(queryString("select_user_setting"));
309     query.bindValue(":userid", userId.toInt());
310     query.bindValue(":settingname", settingName);
311     safeExec(query);
312
313     if (query.first()) {
314         QVariant data;
315         QByteArray rawData = query.value(0).toByteArray();
316         QDataStream in(&rawData, QIODevice::ReadOnly);
317         in.setVersion(QDataStream::Qt_4_2);
318         in >> data;
319         return data;
320     }
321     else {
322         return defaultData;
323     }
324 }
325
326
327 IdentityId PostgreSqlStorage::createIdentity(UserId user, CoreIdentity &identity)
328 {
329     IdentityId identityId;
330
331     QSqlDatabase db = logDb();
332     if (!db.transaction()) {
333         qWarning() << "PostgreSqlStorage::createIdentity(): Unable to start Transaction!";
334         qWarning() << " -" << qPrintable(db.lastError().text());
335         return identityId;
336     }
337
338     QSqlQuery query(db);
339     query.prepare(queryString("insert_identity"));
340     query.bindValue(":userid", user.toInt());
341     query.bindValue(":identityname", identity.identityName());
342     query.bindValue(":realname", identity.realName());
343     query.bindValue(":awaynick", identity.awayNick());
344     query.bindValue(":awaynickenabled", identity.awayNickEnabled());
345     query.bindValue(":awayreason", identity.awayReason());
346     query.bindValue(":awayreasonenabled", identity.awayReasonEnabled());
347     query.bindValue(":autoawayenabled", identity.awayReasonEnabled());
348     query.bindValue(":autoawaytime", identity.autoAwayTime());
349     query.bindValue(":autoawayreason", identity.autoAwayReason());
350     query.bindValue(":autoawayreasonenabled", identity.autoAwayReasonEnabled());
351     query.bindValue(":detachawayenabled", identity.detachAwayEnabled());
352     query.bindValue(":detachawayreason", identity.detachAwayReason());
353     query.bindValue(":detachawayreasonenabled", identity.detachAwayReasonEnabled());
354     query.bindValue(":ident", identity.ident());
355     query.bindValue(":kickreason", identity.kickReason());
356     query.bindValue(":partreason", identity.partReason());
357     query.bindValue(":quitreason", identity.quitReason());
358 #ifdef HAVE_SSL
359     query.bindValue(":sslcert", identity.sslCert().toPem());
360     query.bindValue(":sslkey", identity.sslKey().toPem());
361 #else
362     query.bindValue(":sslcert", QByteArray());
363     query.bindValue(":sslkey", QByteArray());
364 #endif
365     safeExec(query);
366     if (query.lastError().isValid()) {
367         watchQuery(query);
368         db.rollback();
369         return IdentityId();
370     }
371
372     query.first();
373     identityId = query.value(0).toInt();
374     identity.setId(identityId);
375
376     if (!identityId.isValid()) {
377         watchQuery(query);
378         db.rollback();
379         return IdentityId();
380     }
381
382     QSqlQuery insertNickQuery(db);
383     insertNickQuery.prepare(queryString("insert_nick"));
384     foreach(QString nick, identity.nicks()) {
385         insertNickQuery.bindValue(":identityid", identityId.toInt());
386         insertNickQuery.bindValue(":nick", nick);
387         safeExec(insertNickQuery);
388         if (!watchQuery(insertNickQuery)) {
389             db.rollback();
390             return IdentityId();
391         }
392     }
393
394     if (!db.commit()) {
395         qWarning() << "PostgreSqlStorage::createIdentity(): committing data failed!";
396         qWarning() << " -" << qPrintable(db.lastError().text());
397         return IdentityId();
398     }
399     return identityId;
400 }
401
402
403 bool PostgreSqlStorage::updateIdentity(UserId user, const CoreIdentity &identity)
404 {
405     QSqlDatabase db = logDb();
406     if (!db.transaction()) {
407         qWarning() << "PostgreSqlStorage::updateIdentity(): Unable to start Transaction!";
408         qWarning() << " -" << qPrintable(db.lastError().text());
409         return false;
410     }
411
412     QSqlQuery checkQuery(db);
413     checkQuery.prepare(queryString("select_checkidentity"));
414     checkQuery.bindValue(":identityid", identity.id().toInt());
415     checkQuery.bindValue(":userid", user.toInt());
416     safeExec(checkQuery);
417
418     // there should be exactly one identity for the given id and user
419     if (!checkQuery.first() || checkQuery.value(0).toInt() != 1) {
420         db.rollback();
421         return false;
422     }
423
424     QSqlQuery query(db);
425     query.prepare(queryString("update_identity"));
426     query.bindValue(":identityname", identity.identityName());
427     query.bindValue(":realname", identity.realName());
428     query.bindValue(":awaynick", identity.awayNick());
429     query.bindValue(":awaynickenabled", identity.awayNickEnabled());
430     query.bindValue(":awayreason", identity.awayReason());
431     query.bindValue(":awayreasonenabled", identity.awayReasonEnabled());
432     query.bindValue(":autoawayenabled", identity.awayReasonEnabled());
433     query.bindValue(":autoawaytime", identity.autoAwayTime());
434     query.bindValue(":autoawayreason", identity.autoAwayReason());
435     query.bindValue(":autoawayreasonenabled", identity.autoAwayReasonEnabled());
436     query.bindValue(":detachawayenabled", identity.detachAwayEnabled());
437     query.bindValue(":detachawayreason", identity.detachAwayReason());
438     query.bindValue(":detachawayreasonenabled", identity.detachAwayReasonEnabled());
439     query.bindValue(":ident", identity.ident());
440     query.bindValue(":kickreason", identity.kickReason());
441     query.bindValue(":partreason", identity.partReason());
442     query.bindValue(":quitreason", identity.quitReason());
443 #ifdef HAVE_SSL
444     query.bindValue(":sslcert", identity.sslCert().toPem());
445     query.bindValue(":sslkey", identity.sslKey().toPem());
446 #else
447     query.bindValue(":sslcert", QByteArray());
448     query.bindValue(":sslkey", QByteArray());
449 #endif
450     query.bindValue(":identityid", identity.id().toInt());
451
452     safeExec(query);
453     if (!watchQuery(query)) {
454         db.rollback();
455         return false;
456     }
457
458     QSqlQuery deleteNickQuery(db);
459     deleteNickQuery.prepare(queryString("delete_nicks"));
460     deleteNickQuery.bindValue(":identityid", identity.id().toInt());
461     safeExec(deleteNickQuery);
462     if (!watchQuery(deleteNickQuery)) {
463         db.rollback();
464         return false;
465     }
466
467     QSqlQuery insertNickQuery(db);
468     insertNickQuery.prepare(queryString("insert_nick"));
469     foreach(QString nick, identity.nicks()) {
470         insertNickQuery.bindValue(":identityid", identity.id().toInt());
471         insertNickQuery.bindValue(":nick", nick);
472         safeExec(insertNickQuery);
473         if (!watchQuery(insertNickQuery)) {
474             db.rollback();
475             return false;
476         }
477     }
478
479     if (!db.commit()) {
480         qWarning() << "PostgreSqlStorage::updateIdentity(): committing data failed!";
481         qWarning() << " -" << qPrintable(db.lastError().text());
482         return false;
483     }
484     return true;
485 }
486
487
488 void PostgreSqlStorage::removeIdentity(UserId user, IdentityId identityId)
489 {
490     QSqlDatabase db = logDb();
491     if (!db.transaction()) {
492         qWarning() << "PostgreSqlStorage::removeIdentity(): Unable to start Transaction!";
493         qWarning() << " -" << qPrintable(db.lastError().text());
494         return;
495     }
496
497     QSqlQuery query(db);
498     query.prepare(queryString("delete_identity"));
499     query.bindValue(":identityid", identityId.toInt());
500     query.bindValue(":userid", user.toInt());
501     safeExec(query);
502     if (!watchQuery(query)) {
503         db.rollback();
504     }
505     else {
506         db.commit();
507     }
508 }
509
510
511 QList<CoreIdentity> PostgreSqlStorage::identities(UserId user)
512 {
513     QList<CoreIdentity> identities;
514
515     QSqlDatabase db = logDb();
516     if (!beginReadOnlyTransaction(db)) {
517         qWarning() << "PostgreSqlStorage::identites(): cannot start read only transaction!";
518         qWarning() << " -" << qPrintable(db.lastError().text());
519         return identities;
520     }
521
522     QSqlQuery query(db);
523     query.prepare(queryString("select_identities"));
524     query.bindValue(":userid", user.toInt());
525
526     QSqlQuery nickQuery(db);
527     nickQuery.prepare(queryString("select_nicks"));
528
529     safeExec(query);
530
531     while (query.next()) {
532         CoreIdentity identity(IdentityId(query.value(0).toInt()));
533
534         identity.setIdentityName(query.value(1).toString());
535         identity.setRealName(query.value(2).toString());
536         identity.setAwayNick(query.value(3).toString());
537         identity.setAwayNickEnabled(!!query.value(4).toInt());
538         identity.setAwayReason(query.value(5).toString());
539         identity.setAwayReasonEnabled(!!query.value(6).toInt());
540         identity.setAutoAwayEnabled(!!query.value(7).toInt());
541         identity.setAutoAwayTime(query.value(8).toInt());
542         identity.setAutoAwayReason(query.value(9).toString());
543         identity.setAutoAwayReasonEnabled(!!query.value(10).toInt());
544         identity.setDetachAwayEnabled(!!query.value(11).toInt());
545         identity.setDetachAwayReason(query.value(12).toString());
546         identity.setDetachAwayReasonEnabled(!!query.value(13).toInt());
547         identity.setIdent(query.value(14).toString());
548         identity.setKickReason(query.value(15).toString());
549         identity.setPartReason(query.value(16).toString());
550         identity.setQuitReason(query.value(17).toString());
551 #ifdef HAVE_SSL
552         identity.setSslCert(query.value(18).toByteArray());
553         identity.setSslKey(query.value(19).toByteArray());
554 #endif
555
556         nickQuery.bindValue(":identityid", identity.id().toInt());
557         QList<QString> nicks;
558         safeExec(nickQuery);
559         watchQuery(nickQuery);
560         while (nickQuery.next()) {
561             nicks << nickQuery.value(0).toString();
562         }
563         identity.setNicks(nicks);
564         identities << identity;
565     }
566     db.commit();
567     return identities;
568 }
569
570
571 NetworkId PostgreSqlStorage::createNetwork(UserId user, const NetworkInfo &info)
572 {
573     NetworkId networkId;
574
575     QSqlDatabase db = logDb();
576     if (!db.transaction()) {
577         qWarning() << "PostgreSqlStorage::createNetwork(): failed to begin transaction!";
578         qWarning() << " -" << qPrintable(db.lastError().text());
579         return false;
580     }
581
582     QSqlQuery query(db);
583     query.prepare(queryString("insert_network"));
584     query.bindValue(":userid", user.toInt());
585     bindNetworkInfo(query, info);
586     safeExec(query);
587     if (query.lastError().isValid()) {
588         watchQuery(query);
589         db.rollback();
590         return NetworkId();
591     }
592
593     query.first();
594     networkId = query.value(0).toInt();
595
596     if (!networkId.isValid()) {
597         watchQuery(query);
598         db.rollback();
599         return NetworkId();
600     }
601
602     QSqlQuery insertServersQuery(db);
603     insertServersQuery.prepare(queryString("insert_server"));
604     foreach(Network::Server server, info.serverList) {
605         insertServersQuery.bindValue(":userid", user.toInt());
606         insertServersQuery.bindValue(":networkid", networkId.toInt());
607         bindServerInfo(insertServersQuery, server);
608         safeExec(insertServersQuery);
609         if (!watchQuery(insertServersQuery)) {
610             db.rollback();
611             return NetworkId();
612         }
613     }
614
615     if (!db.commit()) {
616         qWarning() << "PostgreSqlStorage::createNetwork(): committing data failed!";
617         qWarning() << " -" << qPrintable(db.lastError().text());
618         return NetworkId();
619     }
620     return networkId;
621 }
622
623
624 void PostgreSqlStorage::bindNetworkInfo(QSqlQuery &query, const NetworkInfo &info)
625 {
626     query.bindValue(":networkname", info.networkName);
627     query.bindValue(":identityid", info.identity.isValid() ? info.identity.toInt() : QVariant());
628     query.bindValue(":encodingcodec", QString(info.codecForEncoding));
629     query.bindValue(":decodingcodec", QString(info.codecForDecoding));
630     query.bindValue(":servercodec", QString(info.codecForServer));
631     query.bindValue(":userandomserver", info.useRandomServer);
632     query.bindValue(":perform", info.perform.join("\n"));
633     query.bindValue(":useautoidentify", info.useAutoIdentify);
634     query.bindValue(":autoidentifyservice", info.autoIdentifyService);
635     query.bindValue(":autoidentifypassword", info.autoIdentifyPassword);
636     query.bindValue(":usesasl", info.useSasl);
637     query.bindValue(":saslaccount", info.saslAccount);
638     query.bindValue(":saslpassword", info.saslPassword);
639     query.bindValue(":useautoreconnect", info.useAutoReconnect);
640     query.bindValue(":autoreconnectinterval", info.autoReconnectInterval);
641     query.bindValue(":autoreconnectretries", info.autoReconnectRetries);
642     query.bindValue(":unlimitedconnectretries", info.unlimitedReconnectRetries);
643     query.bindValue(":rejoinchannels", info.rejoinChannels);
644     if (info.networkId.isValid())
645         query.bindValue(":networkid", info.networkId.toInt());
646 }
647
648
649 void PostgreSqlStorage::bindServerInfo(QSqlQuery &query, const Network::Server &server)
650 {
651     query.bindValue(":hostname", server.host);
652     query.bindValue(":port", server.port);
653     query.bindValue(":password", server.password);
654     query.bindValue(":ssl", server.useSsl);
655     query.bindValue(":sslversion", server.sslVersion);
656     query.bindValue(":useproxy", server.useProxy);
657     query.bindValue(":proxytype", server.proxyType);
658     query.bindValue(":proxyhost", server.proxyHost);
659     query.bindValue(":proxyport", server.proxyPort);
660     query.bindValue(":proxyuser", server.proxyUser);
661     query.bindValue(":proxypass", server.proxyPass);
662 }
663
664
665 bool PostgreSqlStorage::updateNetwork(UserId user, const NetworkInfo &info)
666 {
667     QSqlDatabase db = logDb();
668     if (!db.transaction()) {
669         qWarning() << "PostgreSqlStorage::updateNetwork(): failed to begin transaction!";
670         qWarning() << " -" << qPrintable(db.lastError().text());
671         return false;
672     }
673
674     QSqlQuery updateQuery(db);
675     updateQuery.prepare(queryString("update_network"));
676     updateQuery.bindValue(":userid", user.toInt());
677     bindNetworkInfo(updateQuery, info);
678     safeExec(updateQuery);
679     if (!watchQuery(updateQuery)) {
680         db.rollback();
681         return false;
682     }
683     if (updateQuery.numRowsAffected() != 1) {
684         // seems this is not our network...
685         db.rollback();
686         return false;
687     }
688
689     QSqlQuery dropServersQuery(db);
690     dropServersQuery.prepare("DELETE FROM ircserver WHERE networkid = :networkid");
691     dropServersQuery.bindValue(":networkid", info.networkId.toInt());
692     safeExec(dropServersQuery);
693     if (!watchQuery(dropServersQuery)) {
694         db.rollback();
695         return false;
696     }
697
698     QSqlQuery insertServersQuery(db);
699     insertServersQuery.prepare(queryString("insert_server"));
700     foreach(Network::Server server, info.serverList) {
701         insertServersQuery.bindValue(":userid", user.toInt());
702         insertServersQuery.bindValue(":networkid", info.networkId.toInt());
703         bindServerInfo(insertServersQuery, server);
704         safeExec(insertServersQuery);
705         if (!watchQuery(insertServersQuery)) {
706             db.rollback();
707             return false;
708         }
709     }
710
711     if (!db.commit()) {
712         qWarning() << "PostgreSqlStorage::updateNetwork(): committing data failed!";
713         qWarning() << " -" << qPrintable(db.lastError().text());
714         return false;
715     }
716     return true;
717 }
718
719
720 bool PostgreSqlStorage::removeNetwork(UserId user, const NetworkId &networkId)
721 {
722     QSqlDatabase db = logDb();
723     if (!db.transaction()) {
724         qWarning() << "PostgreSqlStorage::removeNetwork(): cannot start transaction!";
725         qWarning() << " -" << qPrintable(db.lastError().text());
726         return false;
727     }
728
729     QSqlQuery query(db);
730     query.prepare(queryString("delete_network"));
731     query.bindValue(":userid", user.toInt());
732     query.bindValue(":networkid", networkId.toInt());
733     safeExec(query);
734     if (!watchQuery(query)) {
735         db.rollback();
736         return false;
737     }
738
739     db.commit();
740     return true;
741 }
742
743
744 QList<NetworkInfo> PostgreSqlStorage::networks(UserId user)
745 {
746     QList<NetworkInfo> nets;
747
748     QSqlDatabase db = logDb();
749     if (!beginReadOnlyTransaction(db)) {
750         qWarning() << "PostgreSqlStorage::networks(): cannot start read only transaction!";
751         qWarning() << " -" << qPrintable(db.lastError().text());
752         return nets;
753     }
754
755     QSqlQuery networksQuery(db);
756     networksQuery.prepare(queryString("select_networks_for_user"));
757     networksQuery.bindValue(":userid", user.toInt());
758
759     QSqlQuery serversQuery(db);
760     serversQuery.prepare(queryString("select_servers_for_network"));
761
762     safeExec(networksQuery);
763     if (!watchQuery(networksQuery)) {
764         db.rollback();
765         return nets;
766     }
767
768     while (networksQuery.next()) {
769         NetworkInfo net;
770         net.networkId = networksQuery.value(0).toInt();
771         net.networkName = networksQuery.value(1).toString();
772         net.identity = networksQuery.value(2).toInt();
773         net.codecForServer = networksQuery.value(3).toString().toAscii();
774         net.codecForEncoding = networksQuery.value(4).toString().toAscii();
775         net.codecForDecoding = networksQuery.value(5).toString().toAscii();
776         net.useRandomServer = networksQuery.value(6).toBool();
777         net.perform = networksQuery.value(7).toString().split("\n");
778         net.useAutoIdentify = networksQuery.value(8).toBool();
779         net.autoIdentifyService = networksQuery.value(9).toString();
780         net.autoIdentifyPassword = networksQuery.value(10).toString();
781         net.useAutoReconnect = networksQuery.value(11).toBool();
782         net.autoReconnectInterval = networksQuery.value(12).toUInt();
783         net.autoReconnectRetries = networksQuery.value(13).toInt();
784         net.unlimitedReconnectRetries = networksQuery.value(14).toBool();
785         net.rejoinChannels = networksQuery.value(15).toBool();
786         net.useSasl = networksQuery.value(16).toBool();
787         net.saslAccount = networksQuery.value(17).toString();
788         net.saslPassword = networksQuery.value(18).toString();
789
790         serversQuery.bindValue(":networkid", net.networkId.toInt());
791         safeExec(serversQuery);
792         if (!watchQuery(serversQuery)) {
793             db.rollback();
794             return nets;
795         }
796
797         Network::ServerList servers;
798         while (serversQuery.next()) {
799             Network::Server server;
800             server.host = serversQuery.value(0).toString();
801             server.port = serversQuery.value(1).toUInt();
802             server.password = serversQuery.value(2).toString();
803             server.useSsl = serversQuery.value(3).toBool();
804             server.sslVersion = serversQuery.value(4).toInt();
805             server.useProxy = serversQuery.value(5).toBool();
806             server.proxyType = serversQuery.value(6).toInt();
807             server.proxyHost = serversQuery.value(7).toString();
808             server.proxyPort = serversQuery.value(8).toUInt();
809             server.proxyUser = serversQuery.value(9).toString();
810             server.proxyPass = serversQuery.value(10).toString();
811             servers << server;
812         }
813         net.serverList = servers;
814         nets << net;
815     }
816     db.commit();
817     return nets;
818 }
819
820
821 QList<NetworkId> PostgreSqlStorage::connectedNetworks(UserId user)
822 {
823     QList<NetworkId> connectedNets;
824
825     QSqlDatabase db = logDb();
826     if (!beginReadOnlyTransaction(db)) {
827         qWarning() << "PostgreSqlStorage::connectedNetworks(): cannot start read only transaction!";
828         qWarning() << " -" << qPrintable(db.lastError().text());
829         return connectedNets;
830     }
831
832     QSqlQuery query(db);
833     query.prepare(queryString("select_connected_networks"));
834     query.bindValue(":userid", user.toInt());
835     safeExec(query);
836     watchQuery(query);
837
838     while (query.next()) {
839         connectedNets << query.value(0).toInt();
840     }
841
842     db.commit();
843     return connectedNets;
844 }
845
846
847 void PostgreSqlStorage::setNetworkConnected(UserId user, const NetworkId &networkId, bool isConnected)
848 {
849     QSqlQuery query(logDb());
850     query.prepare(queryString("update_network_connected"));
851     query.bindValue(":userid", user.toInt());
852     query.bindValue(":networkid", networkId.toInt());
853     query.bindValue(":connected", isConnected);
854     safeExec(query);
855     watchQuery(query);
856 }
857
858
859 QHash<QString, QString> PostgreSqlStorage::persistentChannels(UserId user, const NetworkId &networkId)
860 {
861     QHash<QString, QString> persistentChans;
862
863     QSqlDatabase db = logDb();
864     if (!beginReadOnlyTransaction(db)) {
865         qWarning() << "PostgreSqlStorage::persistentChannels(): cannot start read only transaction!";
866         qWarning() << " -" << qPrintable(db.lastError().text());
867         return persistentChans;
868     }
869
870     QSqlQuery query(db);
871     query.prepare(queryString("select_persistent_channels"));
872     query.bindValue(":userid", user.toInt());
873     query.bindValue(":networkid", networkId.toInt());
874     safeExec(query);
875     watchQuery(query);
876
877     while (query.next()) {
878         persistentChans[query.value(0).toString()] = query.value(1).toString();
879     }
880
881     db.commit();
882     return persistentChans;
883 }
884
885
886 void PostgreSqlStorage::setChannelPersistent(UserId user, const NetworkId &networkId, const QString &channel, bool isJoined)
887 {
888     QSqlQuery query(logDb());
889     query.prepare(queryString("update_buffer_persistent_channel"));
890     query.bindValue(":userid", user.toInt());
891     query.bindValue(":networkId", networkId.toInt());
892     query.bindValue(":buffercname", channel.toLower());
893     query.bindValue(":joined", isJoined);
894     safeExec(query);
895     watchQuery(query);
896 }
897
898
899 void PostgreSqlStorage::setPersistentChannelKey(UserId user, const NetworkId &networkId, const QString &channel, const QString &key)
900 {
901     QSqlQuery query(logDb());
902     query.prepare(queryString("update_buffer_set_channel_key"));
903     query.bindValue(":userid", user.toInt());
904     query.bindValue(":networkId", networkId.toInt());
905     query.bindValue(":buffercname", channel.toLower());
906     query.bindValue(":key", key);
907     safeExec(query);
908     watchQuery(query);
909 }
910
911
912 QString PostgreSqlStorage::awayMessage(UserId user, NetworkId networkId)
913 {
914     QSqlQuery query(logDb());
915     query.prepare(queryString("select_network_awaymsg"));
916     query.bindValue(":userid", user.toInt());
917     query.bindValue(":networkid", networkId.toInt());
918     safeExec(query);
919     watchQuery(query);
920     QString awayMsg;
921     if (query.first())
922         awayMsg = query.value(0).toString();
923     return awayMsg;
924 }
925
926
927 void PostgreSqlStorage::setAwayMessage(UserId user, NetworkId networkId, const QString &awayMsg)
928 {
929     QSqlQuery query(logDb());
930     query.prepare(queryString("update_network_set_awaymsg"));
931     query.bindValue(":userid", user.toInt());
932     query.bindValue(":networkid", networkId.toInt());
933     query.bindValue(":awaymsg", awayMsg);
934     safeExec(query);
935     watchQuery(query);
936 }
937
938
939 QString PostgreSqlStorage::userModes(UserId user, NetworkId networkId)
940 {
941     QSqlQuery query(logDb());
942     query.prepare(queryString("select_network_usermode"));
943     query.bindValue(":userid", user.toInt());
944     query.bindValue(":networkid", networkId.toInt());
945     safeExec(query);
946     watchQuery(query);
947     QString modes;
948     if (query.first())
949         modes = query.value(0).toString();
950     return modes;
951 }
952
953
954 void PostgreSqlStorage::setUserModes(UserId user, NetworkId networkId, const QString &userModes)
955 {
956     QSqlQuery query(logDb());
957     query.prepare(queryString("update_network_set_usermode"));
958     query.bindValue(":userid", user.toInt());
959     query.bindValue(":networkid", networkId.toInt());
960     query.bindValue(":usermode", userModes);
961     safeExec(query);
962     watchQuery(query);
963 }
964
965
966 BufferInfo PostgreSqlStorage::bufferInfo(UserId user, const NetworkId &networkId, BufferInfo::Type type, const QString &buffer, bool create)
967 {
968     QSqlDatabase db = logDb();
969     if (!db.transaction()) {
970         qWarning() << "PostgreSqlStorage::bufferInfo(): cannot start read only transaction!";
971         qWarning() << " -" << qPrintable(db.lastError().text());
972         return BufferInfo();
973     }
974
975     QSqlQuery query(db);
976     query.prepare(queryString("select_bufferByName"));
977     query.bindValue(":networkid", networkId.toInt());
978     query.bindValue(":userid", user.toInt());
979     query.bindValue(":buffercname", buffer.toLower());
980     safeExec(query);
981
982     if (query.first()) {
983         BufferInfo bufferInfo = BufferInfo(query.value(0).toInt(), networkId, (BufferInfo::Type)query.value(1).toInt(), 0, buffer);
984         if (query.next()) {
985             qCritical() << "PostgreSqlStorage::bufferInfo(): received more then one Buffer!";
986             qCritical() << "         Query:" << query.lastQuery();
987             qCritical() << "  bound Values:";
988             QList<QVariant> list = query.boundValues().values();
989             for (int i = 0; i < list.size(); ++i)
990                 qCritical() << i << ":" << list.at(i).toString().toAscii().data();
991             Q_ASSERT(false);
992         }
993         db.commit();
994         return bufferInfo;
995     }
996
997     if (!create) {
998         db.rollback();
999         return BufferInfo();
1000     }
1001
1002     QSqlQuery createQuery(db);
1003     createQuery.prepare(queryString("insert_buffer"));
1004     createQuery.bindValue(":userid", user.toInt());
1005     createQuery.bindValue(":networkid", networkId.toInt());
1006     createQuery.bindValue(":buffertype", (int)type);
1007     createQuery.bindValue(":buffername", buffer);
1008     createQuery.bindValue(":buffercname", buffer.toLower());
1009     createQuery.bindValue(":joined", type & BufferInfo::ChannelBuffer ? true : false);
1010
1011     safeExec(createQuery);
1012
1013     if (createQuery.lastError().isValid()) {
1014         qWarning() << "PostgreSqlStorage::bufferInfo(): unable to create buffer";
1015         watchQuery(createQuery);
1016         db.rollback();
1017         return BufferInfo();
1018     }
1019
1020     createQuery.first();
1021
1022     BufferInfo bufferInfo = BufferInfo(createQuery.value(0).toInt(), networkId, type, 0, buffer);
1023     db.commit();
1024     return bufferInfo;
1025 }
1026
1027
1028 BufferInfo PostgreSqlStorage::getBufferInfo(UserId user, const BufferId &bufferId)
1029 {
1030     QSqlQuery query(logDb());
1031     query.prepare(queryString("select_buffer_by_id"));
1032     query.bindValue(":userid", user.toInt());
1033     query.bindValue(":bufferid", bufferId.toInt());
1034     safeExec(query);
1035     if (!watchQuery(query))
1036         return BufferInfo();
1037
1038     if (!query.first())
1039         return BufferInfo();
1040
1041     BufferInfo bufferInfo(query.value(0).toInt(), query.value(1).toInt(), (BufferInfo::Type)query.value(2).toInt(), 0, query.value(4).toString());
1042     Q_ASSERT(!query.next());
1043
1044     return bufferInfo;
1045 }
1046
1047
1048 QList<BufferInfo> PostgreSqlStorage::requestBuffers(UserId user)
1049 {
1050     QList<BufferInfo> bufferlist;
1051
1052     QSqlDatabase db = logDb();
1053     if (!beginReadOnlyTransaction(db)) {
1054         qWarning() << "PostgreSqlStorage::requestBuffers(): cannot start read only transaction!";
1055         qWarning() << " -" << qPrintable(db.lastError().text());
1056         return bufferlist;
1057     }
1058
1059     QSqlQuery query(db);
1060     query.prepare(queryString("select_buffers"));
1061     query.bindValue(":userid", user.toInt());
1062
1063     safeExec(query);
1064     watchQuery(query);
1065     while (query.next()) {
1066         bufferlist << BufferInfo(query.value(0).toInt(), query.value(1).toInt(), (BufferInfo::Type)query.value(2).toInt(), query.value(3).toInt(), query.value(4).toString());
1067     }
1068     db.commit();
1069     return bufferlist;
1070 }
1071
1072
1073 QList<BufferId> PostgreSqlStorage::requestBufferIdsForNetwork(UserId user, NetworkId networkId)
1074 {
1075     QList<BufferId> bufferList;
1076
1077     QSqlDatabase db = logDb();
1078     if (!beginReadOnlyTransaction(db)) {
1079         qWarning() << "PostgreSqlStorage::requestBufferIdsForNetwork(): cannot start read only transaction!";
1080         qWarning() << " -" << qPrintable(db.lastError().text());
1081         return bufferList;
1082     }
1083
1084     QSqlQuery query(db);
1085     query.prepare(queryString("select_buffers_for_network"));
1086     query.bindValue(":networkid", networkId.toInt());
1087     query.bindValue(":userid", user.toInt());
1088
1089     safeExec(query);
1090     watchQuery(query);
1091     while (query.next()) {
1092         bufferList << BufferId(query.value(0).toInt());
1093     }
1094     db.commit();
1095     return bufferList;
1096 }
1097
1098
1099 bool PostgreSqlStorage::removeBuffer(const UserId &user, const BufferId &bufferId)
1100 {
1101     QSqlDatabase db = logDb();
1102     if (!db.transaction()) {
1103         qWarning() << "PostgreSqlStorage::removeBuffer(): cannot start transaction!";
1104         return false;
1105     }
1106
1107     QSqlQuery query(db);
1108     query.prepare(queryString("delete_buffer_for_bufferid"));
1109     query.bindValue(":userid", user.toInt());
1110     query.bindValue(":bufferid", bufferId.toInt());
1111     safeExec(query);
1112     if (!watchQuery(query)) {
1113         db.rollback();
1114         return false;
1115     }
1116
1117     int numRows = query.numRowsAffected();
1118     switch (numRows) {
1119     case 0:
1120         db.commit();
1121         return false;
1122     case 1:
1123         db.commit();
1124         return true;
1125     default:
1126         // there was more then one buffer deleted...
1127         qWarning() << "PostgreSqlStorage::removeBuffer(): Userid" << user << "BufferId" << "caused deletion of" << numRows << "Buffers! Rolling back transaction...";
1128         db.rollback();
1129         return false;
1130     }
1131 }
1132
1133
1134 bool PostgreSqlStorage::renameBuffer(const UserId &user, const BufferId &bufferId, const QString &newName)
1135 {
1136     QSqlDatabase db = logDb();
1137     if (!db.transaction()) {
1138         qWarning() << "PostgreSqlStorage::renameBuffer(): cannot start transaction!";
1139         return false;
1140     }
1141
1142     QSqlQuery query(db);
1143     query.prepare(queryString("update_buffer_name"));
1144     query.bindValue(":buffername", newName);
1145     query.bindValue(":buffercname", newName.toLower());
1146     query.bindValue(":userid", user.toInt());
1147     query.bindValue(":bufferid", bufferId.toInt());
1148     safeExec(query);
1149     if (query.lastError().isValid()) {
1150         watchQuery(query);
1151         db.rollback();
1152         return false;
1153     }
1154
1155     int numRows = query.numRowsAffected();
1156     switch (numRows) {
1157     case 0:
1158         db.commit();
1159         return false;
1160     case 1:
1161         db.commit();
1162         return true;
1163     default:
1164         // there was more then one buffer deleted...
1165         qWarning() << "PostgreSqlStorage::renameBuffer(): Userid" << user << "BufferId" << "affected" << numRows << "Buffers! Rolling back transaction...";
1166         db.rollback();
1167         return false;
1168     }
1169 }
1170
1171
1172 bool PostgreSqlStorage::mergeBuffersPermanently(const UserId &user, const BufferId &bufferId1, const BufferId &bufferId2)
1173 {
1174     QSqlDatabase db = logDb();
1175     if (!db.transaction()) {
1176         qWarning() << "PostgreSqlStorage::mergeBuffersPermanently(): cannot start transaction!";
1177         qWarning() << " -" << qPrintable(db.lastError().text());
1178         return false;
1179     }
1180
1181     QSqlQuery checkQuery(db);
1182     checkQuery.prepare("SELECT count(*) FROM buffer "
1183                        "WHERE userid = :userid AND bufferid IN (:buffer1, :buffer2)");
1184     checkQuery.bindValue(":userid", user.toInt());
1185     checkQuery.bindValue(":buffer1", bufferId1.toInt());
1186     checkQuery.bindValue(":buffer2", bufferId2.toInt());
1187     safeExec(checkQuery);
1188     if (!watchQuery(checkQuery)) {
1189         db.rollback();
1190         return false;
1191     }
1192     checkQuery.first();
1193     if (checkQuery.value(0).toInt() != 2) {
1194         db.rollback();
1195         return false;
1196     }
1197
1198     QSqlQuery query(db);
1199     query.prepare(queryString("update_backlog_bufferid"));
1200     query.bindValue(":oldbufferid", bufferId2.toInt());
1201     query.bindValue(":newbufferid", bufferId1.toInt());
1202     safeExec(query);
1203     if (!watchQuery(query)) {
1204         db.rollback();
1205         return false;
1206     }
1207
1208     QSqlQuery delBufferQuery(logDb());
1209     delBufferQuery.prepare(queryString("delete_buffer_for_bufferid"));
1210     delBufferQuery.bindValue(":userid", user.toInt());
1211     delBufferQuery.bindValue(":bufferid", bufferId2.toInt());
1212     safeExec(delBufferQuery);
1213     if (!watchQuery(delBufferQuery)) {
1214         db.rollback();
1215         return false;
1216     }
1217
1218     db.commit();
1219     return true;
1220 }
1221
1222
1223 void PostgreSqlStorage::setBufferLastSeenMsg(UserId user, const BufferId &bufferId, const MsgId &msgId)
1224 {
1225     QSqlQuery query(logDb());
1226     query.prepare(queryString("update_buffer_lastseen"));
1227
1228     query.bindValue(":userid", user.toInt());
1229     query.bindValue(":bufferid", bufferId.toInt());
1230     query.bindValue(":lastseenmsgid", msgId.toInt());
1231     safeExec(query);
1232     watchQuery(query);
1233 }
1234
1235
1236 QHash<BufferId, MsgId> PostgreSqlStorage::bufferLastSeenMsgIds(UserId user)
1237 {
1238     QHash<BufferId, MsgId> lastSeenHash;
1239
1240     QSqlDatabase db = logDb();
1241     if (!beginReadOnlyTransaction(db)) {
1242         qWarning() << "PostgreSqlStorage::bufferLastSeenMsgIds(): cannot start read only transaction!";
1243         qWarning() << " -" << qPrintable(db.lastError().text());
1244         return lastSeenHash;
1245     }
1246
1247     QSqlQuery query(db);
1248     query.prepare(queryString("select_buffer_lastseen_messages"));
1249     query.bindValue(":userid", user.toInt());
1250     safeExec(query);
1251     if (!watchQuery(query)) {
1252         db.rollback();
1253         return lastSeenHash;
1254     }
1255
1256     while (query.next()) {
1257         lastSeenHash[query.value(0).toInt()] = query.value(1).toInt();
1258     }
1259
1260     db.commit();
1261     return lastSeenHash;
1262 }
1263
1264
1265 void PostgreSqlStorage::setBufferMarkerLineMsg(UserId user, const BufferId &bufferId, const MsgId &msgId)
1266 {
1267     QSqlQuery query(logDb());
1268     query.prepare(queryString("update_buffer_markerlinemsgid"));
1269
1270     query.bindValue(":userid", user.toInt());
1271     query.bindValue(":bufferid", bufferId.toInt());
1272     query.bindValue(":markerlinemsgid", msgId.toInt());
1273     safeExec(query);
1274     watchQuery(query);
1275 }
1276
1277
1278 QHash<BufferId, MsgId> PostgreSqlStorage::bufferMarkerLineMsgIds(UserId user)
1279 {
1280     QHash<BufferId, MsgId> markerLineHash;
1281
1282     QSqlDatabase db = logDb();
1283     if (!beginReadOnlyTransaction(db)) {
1284         qWarning() << "PostgreSqlStorage::bufferMarkerLineMsgIds(): cannot start read only transaction!";
1285         qWarning() << " -" << qPrintable(db.lastError().text());
1286         return markerLineHash;
1287     }
1288
1289     QSqlQuery query(db);
1290     query.prepare(queryString("select_buffer_markerlinemsgids"));
1291     query.bindValue(":userid", user.toInt());
1292     safeExec(query);
1293     if (!watchQuery(query)) {
1294         db.rollback();
1295         return markerLineHash;
1296     }
1297
1298     while (query.next()) {
1299         markerLineHash[query.value(0).toInt()] = query.value(1).toInt();
1300     }
1301
1302     db.commit();
1303     return markerLineHash;
1304 }
1305
1306
1307 bool PostgreSqlStorage::logMessage(Message &msg)
1308 {
1309     QSqlDatabase db = logDb();
1310     if (!db.transaction()) {
1311         qWarning() << "PostgreSqlStorage::logMessage(): cannot start transaction!";
1312         qWarning() << " -" << qPrintable(db.lastError().text());
1313         return false;
1314     }
1315
1316     QSqlQuery getSenderIdQuery = executePreparedQuery("select_senderid", msg.sender(), db);
1317     int senderId;
1318     if (getSenderIdQuery.first()) {
1319         senderId = getSenderIdQuery.value(0).toInt();
1320     }
1321     else {
1322         // it's possible that the sender was already added by another thread
1323         // since the insert might fail we're setting a savepoint
1324         savePoint("sender_sp1", db);
1325         QSqlQuery addSenderQuery = executePreparedQuery("insert_sender", msg.sender(), db);
1326
1327         if (addSenderQuery.lastError().isValid()) {
1328             rollbackSavePoint("sender_sp1", db);
1329             getSenderIdQuery = db.exec(getSenderIdQuery.lastQuery());
1330             getSenderIdQuery.first();
1331             senderId = getSenderIdQuery.value(0).toInt();
1332         }
1333         else {
1334             releaseSavePoint("sender_sp1", db);
1335             addSenderQuery.first();
1336             senderId = addSenderQuery.value(0).toInt();
1337         }
1338     }
1339
1340     QVariantList params;
1341     params << msg.timestamp()
1342            << msg.bufferInfo().bufferId().toInt()
1343            << msg.type()
1344            << (int)msg.flags()
1345            << senderId
1346            << msg.contents();
1347     QSqlQuery logMessageQuery = executePreparedQuery("insert_message", params, db);
1348
1349     if (!watchQuery(logMessageQuery)) {
1350         db.rollback();
1351         return false;
1352     }
1353
1354     logMessageQuery.first();
1355     MsgId msgId = logMessageQuery.value(0).toInt();
1356     db.commit();
1357     if (msgId.isValid()) {
1358         msg.setMsgId(msgId);
1359         return true;
1360     }
1361     else {
1362         return false;
1363     }
1364 }
1365
1366
1367 bool PostgreSqlStorage::logMessages(MessageList &msgs)
1368 {
1369     QSqlDatabase db = logDb();
1370     if (!db.transaction()) {
1371         qWarning() << "PostgreSqlStorage::logMessage(): cannot start transaction!";
1372         qWarning() << " -" << qPrintable(db.lastError().text());
1373         return false;
1374     }
1375
1376     QList<int> senderIdList;
1377     QHash<QString, int> senderIds;
1378     QSqlQuery addSenderQuery;
1379     QSqlQuery selectSenderQuery;;
1380     for (int i = 0; i < msgs.count(); i++) {
1381         const QString &sender = msgs.at(i).sender();
1382         if (senderIds.contains(sender)) {
1383             senderIdList << senderIds[sender];
1384             continue;
1385         }
1386
1387         selectSenderQuery = executePreparedQuery("select_senderid", sender, db);
1388         if (selectSenderQuery.first()) {
1389             senderIdList << selectSenderQuery.value(0).toInt();
1390             senderIds[sender] = selectSenderQuery.value(0).toInt();
1391         }
1392         else {
1393             savePoint("sender_sp", db);
1394             addSenderQuery = executePreparedQuery("insert_sender", sender, db);
1395             if (addSenderQuery.lastError().isValid()) {
1396                 // seems it was inserted meanwhile... by a different thread
1397                 rollbackSavePoint("sender_sp", db);
1398                 selectSenderQuery = db.exec(selectSenderQuery.lastQuery());
1399                 selectSenderQuery.first();
1400                 senderIdList << selectSenderQuery.value(0).toInt();
1401                 senderIds[sender] = selectSenderQuery.value(0).toInt();
1402             }
1403             else {
1404                 releaseSavePoint("sender_sp", db);
1405                 addSenderQuery.first();
1406                 senderIdList << addSenderQuery.value(0).toInt();
1407                 senderIds[sender] = addSenderQuery.value(0).toInt();
1408             }
1409         }
1410     }
1411
1412     // yes we loop twice over the same list. This avoids alternating queries.
1413     bool error = false;
1414     for (int i = 0; i < msgs.count(); i++) {
1415         Message &msg = msgs[i];
1416         QVariantList params;
1417         params << msg.timestamp()
1418                << msg.bufferInfo().bufferId().toInt()
1419                << msg.type()
1420                << (int)msg.flags()
1421                << senderIdList.at(i)
1422                << msg.contents();
1423         QSqlQuery logMessageQuery = executePreparedQuery("insert_message", params, db);
1424         if (!watchQuery(logMessageQuery)) {
1425             db.rollback();
1426             error = true;
1427             break;
1428         }
1429         else {
1430             logMessageQuery.first();
1431             msg.setMsgId(logMessageQuery.value(0).toInt());
1432         }
1433     }
1434
1435     if (error) {
1436         // we had a rollback in the db so we need to reset all msgIds
1437         for (int i = 0; i < msgs.count(); i++) {
1438             msgs[i].setMsgId(MsgId());
1439         }
1440         return false;
1441     }
1442
1443     db.commit();
1444     return true;
1445 }
1446
1447
1448 QList<Message> PostgreSqlStorage::requestMsgs(UserId user, BufferId bufferId, MsgId first, MsgId last, int limit)
1449 {
1450     QList<Message> messagelist;
1451
1452     QSqlDatabase db = logDb();
1453     if (!beginReadOnlyTransaction(db)) {
1454         qWarning() << "PostgreSqlStorage::requestMsgs(): cannot start read only transaction!";
1455         qWarning() << " -" << qPrintable(db.lastError().text());
1456         return messagelist;
1457     }
1458
1459     BufferInfo bufferInfo = getBufferInfo(user, bufferId);
1460     if (!bufferInfo.isValid()) {
1461         db.rollback();
1462         return messagelist;
1463     }
1464
1465     QString queryName;
1466     QVariantList params;
1467     if (last == -1 && first == -1) {
1468         queryName = "select_messages";
1469     }
1470     else if (last == -1) {
1471         queryName = "select_messagesNewerThan";
1472         params << first.toInt();
1473     }
1474     else {
1475         queryName = "select_messagesRange";
1476         params << first.toInt();
1477         params << last.toInt();
1478     }
1479     params << bufferId.toInt();
1480     if (limit != -1)
1481         params << limit;
1482     else
1483         params << "ALL";
1484
1485     QSqlQuery query = executePreparedQuery(queryName, params, db);
1486
1487     if (!watchQuery(query)) {
1488         qDebug() << "select_messages failed";
1489         db.rollback();
1490         return messagelist;
1491     }
1492
1493     QDateTime timestamp;
1494     while (query.next()) {
1495         timestamp = query.value(1).toDateTime();
1496         timestamp.setTimeSpec(Qt::UTC);
1497         Message msg(timestamp,
1498             bufferInfo,
1499             (Message::Type)query.value(2).toUInt(),
1500             query.value(5).toString(),
1501             query.value(4).toString(),
1502             (Message::Flags)query.value(3).toUInt());
1503         msg.setMsgId(query.value(0).toInt());
1504         messagelist << msg;
1505     }
1506
1507     db.commit();
1508     return messagelist;
1509 }
1510
1511
1512 QList<Message> PostgreSqlStorage::requestAllMsgs(UserId user, MsgId first, MsgId last, int limit)
1513 {
1514     QList<Message> messagelist;
1515
1516     // requestBuffers uses it's own transaction.
1517     QHash<BufferId, BufferInfo> bufferInfoHash;
1518     foreach(BufferInfo bufferInfo, requestBuffers(user)) {
1519         bufferInfoHash[bufferInfo.bufferId()] = bufferInfo;
1520     }
1521
1522     QSqlDatabase db = logDb();
1523     if (!beginReadOnlyTransaction(db)) {
1524         qWarning() << "PostgreSqlStorage::requestAllMsgs(): cannot start read only transaction!";
1525         qWarning() << " -" << qPrintable(db.lastError().text());
1526         return messagelist;
1527     }
1528
1529     QSqlQuery query(db);
1530     if (last == -1) {
1531         query.prepare(queryString("select_messagesAllNew"));
1532     }
1533     else {
1534         query.prepare(queryString("select_messagesAll"));
1535         query.bindValue(":lastmsg", last.toInt());
1536     }
1537     query.bindValue(":userid", user.toInt());
1538     query.bindValue(":firstmsg", first.toInt());
1539     safeExec(query);
1540     if (!watchQuery(query)) {
1541         db.rollback();
1542         return messagelist;
1543     }
1544
1545     QDateTime timestamp;
1546     for (int i = 0; i < limit && query.next(); i++) {
1547         timestamp = query.value(1).toDateTime();
1548         timestamp.setTimeSpec(Qt::UTC);
1549         Message msg(timestamp,
1550             bufferInfoHash[query.value(1).toInt()],
1551             (Message::Type)query.value(3).toUInt(),
1552             query.value(6).toString(),
1553             query.value(5).toString(),
1554             (Message::Flags)query.value(4).toUInt());
1555         msg.setMsgId(query.value(0).toInt());
1556         messagelist << msg;
1557     }
1558
1559     db.commit();
1560     return messagelist;
1561 }
1562
1563
1564 // void PostgreSqlStorage::safeExec(QSqlQuery &query) {
1565 //   qDebug() << "PostgreSqlStorage::safeExec";
1566 //   qDebug() << "   executing:\n" << query.executedQuery();
1567 //   qDebug() << "   bound Values:";
1568 //   QList<QVariant> list = query.boundValues().values();
1569 //   for (int i = 0; i < list.size(); ++i)
1570 //     qCritical() << i << ": " << list.at(i).toString().toAscii().data();
1571
1572 //   query.exec();
1573
1574 //   qDebug() << "Success:" << !query.lastError().isValid();
1575 //   qDebug();
1576
1577 //   if(!query.lastError().isValid())
1578 //     return;
1579
1580 //   qDebug() << "==================== ERROR ====================";
1581 //   watchQuery(query);
1582 //   qDebug() << "===============================================";
1583 //   qDebug();
1584 //   return;
1585 // }
1586
1587 bool PostgreSqlStorage::beginReadOnlyTransaction(QSqlDatabase &db)
1588 {
1589     QSqlQuery query = db.exec("BEGIN TRANSACTION READ ONLY");
1590     return !query.lastError().isValid();
1591 }
1592
1593
1594 QSqlQuery PostgreSqlStorage::prepareAndExecuteQuery(const QString &queryname, const QString &paramstring, const QSqlDatabase &db)
1595 {
1596     // Query preparing is done lazily. That means that instead of always checking if the query is already prepared
1597     // we just EXECUTE and catch the error
1598     QSqlQuery query;
1599
1600     db.exec("SAVEPOINT quassel_prepare_query");
1601     if (paramstring.isNull()) {
1602         query = db.exec(QString("EXECUTE quassel_%1").arg(queryname));
1603     }
1604     else {
1605         query = db.exec(QString("EXECUTE quassel_%1 (%2)").arg(queryname).arg(paramstring));
1606     }
1607
1608     if (db.lastError().isValid()) {
1609         // and once again: Qt leaves us without error codes so we either parse (language dependant(!)) strings
1610         // or we just guess the error. As we're only interested in unprepared queries, this will be our guess. :)
1611         db.exec("ROLLBACK TO SAVEPOINT quassel_prepare_query");
1612         QSqlQuery checkQuery = db.exec(QString("SELECT count(name) FROM pg_prepared_statements WHERE name = 'quassel_%1' AND from_sql = TRUE").arg(queryname.toLower()));
1613         checkQuery.first();
1614         if (checkQuery.value(0).toInt() == 0) {
1615             db.exec(QString("PREPARE quassel_%1 AS %2").arg(queryname).arg(queryString(queryname)));
1616             if (db.lastError().isValid()) {
1617                 qWarning() << "PostgreSqlStorage::prepareQuery(): unable to prepare query:" << queryname << "AS" << queryString(queryname);
1618                 qWarning() << "  Error:" << db.lastError().text();
1619                 return QSqlQuery(db);
1620             }
1621         }
1622         // we alwas execute the query again, even if the query was already prepared.
1623         // this ensures, that the error is properly propagated to the calling function
1624         // (otherwise the last call would be the testing select to pg_prepared_statements
1625         // which always gives a proper result and the error would be lost)
1626         if (paramstring.isNull()) {
1627             query = db.exec(QString("EXECUTE quassel_%1").arg(queryname));
1628         }
1629         else {
1630             query = db.exec(QString("EXECUTE quassel_%1 (%2)").arg(queryname).arg(paramstring));
1631         }
1632     }
1633     else {
1634         // only release the SAVEPOINT
1635         db.exec("RELEASE SAVEPOINT quassel_prepare_query");
1636     }
1637     return query;
1638 }
1639
1640
1641 QSqlQuery PostgreSqlStorage::executePreparedQuery(const QString &queryname, const QVariantList &params, const QSqlDatabase &db)
1642 {
1643     QSqlDriver *driver = db.driver();
1644
1645     QStringList paramStrings;
1646     QSqlField field;
1647     for (int i = 0; i < params.count(); i++) {
1648         const QVariant &value = params.at(i);
1649         field.setType(value.type());
1650         if (value.isNull())
1651             field.clear();
1652         else
1653             field.setValue(value);
1654
1655         paramStrings << driver->formatValue(field);
1656     }
1657
1658     if (params.isEmpty()) {
1659         return prepareAndExecuteQuery(queryname, db);
1660     }
1661     else {
1662         return prepareAndExecuteQuery(queryname, paramStrings.join(", "), db);
1663     }
1664 }
1665
1666
1667 QSqlQuery PostgreSqlStorage::executePreparedQuery(const QString &queryname, const QVariant &param, const QSqlDatabase &db)
1668 {
1669     QSqlField field;
1670     field.setType(param.type());
1671     if (param.isNull())
1672         field.clear();
1673     else
1674         field.setValue(param);
1675
1676     QString paramString = db.driver()->formatValue(field);
1677     return prepareAndExecuteQuery(queryname, paramString, db);
1678 }
1679
1680
1681 void PostgreSqlStorage::deallocateQuery(const QString &queryname, const QSqlDatabase &db)
1682 {
1683     db.exec(QString("DEALLOCATE quassel_%1").arg(queryname));
1684 }
1685
1686
1687 // ========================================
1688 //  PostgreSqlMigrationWriter
1689 // ========================================
1690 PostgreSqlMigrationWriter::PostgreSqlMigrationWriter()
1691     : PostgreSqlStorage()
1692 {
1693 }
1694
1695
1696 bool PostgreSqlMigrationWriter::prepareQuery(MigrationObject mo)
1697 {
1698     QString query;
1699     switch (mo) {
1700     case QuasselUser:
1701         query = queryString("migrate_write_quasseluser");
1702         break;
1703     case Sender:
1704         query = queryString("migrate_write_sender");
1705         break;
1706     case Identity:
1707         _validIdentities.clear();
1708         query = queryString("migrate_write_identity");
1709         break;
1710     case IdentityNick:
1711         query = queryString("migrate_write_identity_nick");
1712         break;
1713     case Network:
1714         query = queryString("migrate_write_network");
1715         break;
1716     case Buffer:
1717         query = queryString("migrate_write_buffer");
1718         break;
1719     case Backlog:
1720         query = queryString("migrate_write_backlog");
1721         break;
1722     case IrcServer:
1723         query = queryString("migrate_write_ircserver");
1724         break;
1725     case UserSetting:
1726         query = queryString("migrate_write_usersetting");
1727         break;
1728     }
1729     newQuery(query, logDb());
1730     return true;
1731 }
1732
1733
1734 //bool PostgreSqlMigrationWriter::writeUser(const QuasselUserMO &user) {
1735 bool PostgreSqlMigrationWriter::writeMo(const QuasselUserMO &user)
1736 {
1737     bindValue(0, user.id.toInt());
1738     bindValue(1, user.username);
1739     bindValue(2, user.password);
1740     return exec();
1741 }
1742
1743
1744 //bool PostgreSqlMigrationWriter::writeSender(const SenderMO &sender) {
1745 bool PostgreSqlMigrationWriter::writeMo(const SenderMO &sender)
1746 {
1747     bindValue(0, sender.senderId);
1748     bindValue(1, sender.sender);
1749     return exec();
1750 }
1751
1752
1753 //bool PostgreSqlMigrationWriter::writeIdentity(const IdentityMO &identity) {
1754 bool PostgreSqlMigrationWriter::writeMo(const IdentityMO &identity)
1755 {
1756     _validIdentities << identity.id.toInt();
1757     bindValue(0, identity.id.toInt());
1758     bindValue(1, identity.userid.toInt());
1759     bindValue(2, identity.identityname);
1760     bindValue(3, identity.realname);
1761     bindValue(4, identity.awayNick);
1762     bindValue(5, identity.awayNickEnabled);
1763     bindValue(6, identity.awayReason);
1764     bindValue(7, identity.awayReasonEnabled);
1765     bindValue(8, identity.autoAwayEnabled);
1766     bindValue(9, identity.autoAwayTime);
1767     bindValue(10, identity.autoAwayReason);
1768     bindValue(11, identity.autoAwayReasonEnabled);
1769     bindValue(12, identity.detachAwayEnabled);
1770     bindValue(13, identity.detachAwayReason);
1771     bindValue(14, identity.detchAwayReasonEnabled);
1772     bindValue(15, identity.ident);
1773     bindValue(16, identity.kickReason);
1774     bindValue(17, identity.partReason);
1775     bindValue(18, identity.quitReason);
1776     bindValue(19, identity.sslCert);
1777     bindValue(20, identity.sslKey);
1778     return exec();
1779 }
1780
1781
1782 //bool PostgreSqlMigrationWriter::writeIdentityNick(const IdentityNickMO &identityNick) {
1783 bool PostgreSqlMigrationWriter::writeMo(const IdentityNickMO &identityNick)
1784 {
1785     bindValue(0, identityNick.nickid);
1786     bindValue(1, identityNick.identityId.toInt());
1787     bindValue(2, identityNick.nick);
1788     return exec();
1789 }
1790
1791
1792 //bool PostgreSqlMigrationWriter::writeNetwork(const NetworkMO &network) {
1793 bool PostgreSqlMigrationWriter::writeMo(const NetworkMO &network)
1794 {
1795     bindValue(0, network.networkid.toInt());
1796     bindValue(1, network.userid.toInt());
1797     bindValue(2, network.networkname);
1798     if (_validIdentities.contains(network.identityid.toInt()))
1799         bindValue(3, network.identityid.toInt());
1800     else
1801         bindValue(3, QVariant());
1802     bindValue(4, network.encodingcodec);
1803     bindValue(5, network.decodingcodec);
1804     bindValue(6, network.servercodec);
1805     bindValue(7, network.userandomserver);
1806     bindValue(8, network.perform);
1807     bindValue(9, network.useautoidentify);
1808     bindValue(10, network.autoidentifyservice);
1809     bindValue(11, network.autoidentifypassword);
1810     bindValue(12, network.useautoreconnect);
1811     bindValue(13, network.autoreconnectinterval);
1812     bindValue(14, network.autoreconnectretries);
1813     bindValue(15, network.unlimitedconnectretries);
1814     bindValue(16, network.rejoinchannels);
1815     bindValue(17, network.connected);
1816     bindValue(18, network.usermode);
1817     bindValue(19, network.awaymessage);
1818     bindValue(20, network.attachperform);
1819     bindValue(21, network.detachperform);
1820     bindValue(22, network.usesasl);
1821     bindValue(23, network.saslaccount);
1822     bindValue(24, network.saslpassword);
1823     return exec();
1824 }
1825
1826
1827 //bool PostgreSqlMigrationWriter::writeBuffer(const BufferMO &buffer) {
1828 bool PostgreSqlMigrationWriter::writeMo(const BufferMO &buffer)
1829 {
1830     bindValue(0, buffer.bufferid.toInt());
1831     bindValue(1, buffer.userid.toInt());
1832     bindValue(2, buffer.groupid);
1833     bindValue(3, buffer.networkid.toInt());
1834     bindValue(4, buffer.buffername);
1835     bindValue(5, buffer.buffercname);
1836     bindValue(6, (int)buffer.buffertype);
1837     bindValue(7, buffer.lastseenmsgid);
1838     bindValue(8, buffer.markerlinemsgid);
1839     bindValue(9, buffer.key);
1840     bindValue(10, buffer.joined);
1841     return exec();
1842 }
1843
1844
1845 //bool PostgreSqlMigrationWriter::writeBacklog(const BacklogMO &backlog) {
1846 bool PostgreSqlMigrationWriter::writeMo(const BacklogMO &backlog)
1847 {
1848     bindValue(0, backlog.messageid.toInt());
1849     bindValue(1, backlog.time);
1850     bindValue(2, backlog.bufferid.toInt());
1851     bindValue(3, backlog.type);
1852     bindValue(4, (int)backlog.flags);
1853     bindValue(5, backlog.senderid);
1854     bindValue(6, backlog.message);
1855     return exec();
1856 }
1857
1858
1859 //bool PostgreSqlMigrationWriter::writeIrcServer(const IrcServerMO &ircserver) {
1860 bool PostgreSqlMigrationWriter::writeMo(const IrcServerMO &ircserver)
1861 {
1862     bindValue(0, ircserver.serverid);
1863     bindValue(1, ircserver.userid.toInt());
1864     bindValue(2, ircserver.networkid.toInt());
1865     bindValue(3, ircserver.hostname);
1866     bindValue(4, ircserver.port);
1867     bindValue(5, ircserver.password);
1868     bindValue(6, ircserver.ssl);
1869     bindValue(7, ircserver.sslversion);
1870     bindValue(8, ircserver.useproxy);
1871     bindValue(9, ircserver.proxytype);
1872     bindValue(10, ircserver.proxyhost);
1873     bindValue(11, ircserver.proxyport);
1874     bindValue(12, ircserver.proxyuser);
1875     bindValue(13, ircserver.proxypass);
1876     return exec();
1877 }
1878
1879
1880 //bool PostgreSqlMigrationWriter::writeUserSetting(const UserSettingMO &userSetting) {
1881 bool PostgreSqlMigrationWriter::writeMo(const UserSettingMO &userSetting)
1882 {
1883     bindValue(0, userSetting.userid.toInt());
1884     bindValue(1, userSetting.settingname);
1885     bindValue(2, userSetting.settingvalue);
1886     return exec();
1887 }
1888
1889
1890 bool PostgreSqlMigrationWriter::postProcess()
1891 {
1892     QSqlDatabase db = logDb();
1893     QList<Sequence> sequences;
1894     sequences << Sequence("backlog", "messageid")
1895               << Sequence("buffer", "bufferid")
1896               << Sequence("identity", "identityid")
1897               << Sequence("identity_nick", "nickid")
1898               << Sequence("ircserver", "serverid")
1899               << Sequence("network", "networkid")
1900               << Sequence("quasseluser", "userid")
1901               << Sequence("sender", "senderid");
1902     QList<Sequence>::const_iterator iter;
1903     for (iter = sequences.constBegin(); iter != sequences.constEnd(); iter++) {
1904         resetQuery();
1905         newQuery(QString("SELECT setval('%1_%2_seq', max(%2)) FROM %1").arg(iter->table, iter->field), db);
1906         if (!exec())
1907             return false;
1908     }
1909     return true;
1910 }