summaryrefslogtreecommitdiffstats
path: root/pica-client
diff options
context:
space:
mode:
Diffstat (limited to 'pica-client')
-rw-r--r--pica-client/accounts.cpp107
-rw-r--r--pica-client/accounts.h35
-rw-r--r--pica-client/accountswindow.cpp205
-rw-r--r--pica-client/accountswindow.h43
-rw-r--r--pica-client/askpassword.cpp52
-rw-r--r--pica-client/askpassword.h27
-rw-r--r--pica-client/chatwindow.cpp140
-rw-r--r--pica-client/chatwindow.h53
-rw-r--r--pica-client/contactlistwidget.cpp188
-rw-r--r--pica-client/contactlistwidget.h43
-rw-r--r--pica-client/contacts.cpp133
-rw-r--r--pica-client/contacts.h34
-rw-r--r--pica-client/dialogs/addaccountdialog.cpp114
-rw-r--r--pica-client/dialogs/addaccountdialog.h41
-rw-r--r--pica-client/dialogs/forgedcertdialog.cpp53
-rw-r--r--pica-client/dialogs/forgedcertdialog.h34
-rw-r--r--pica-client/dialogs/viewcertdialog.cpp68
-rw-r--r--pica-client/dialogs/viewcertdialog.h28
-rw-r--r--pica-client/globals.h26
-rw-r--r--pica-client/main.cpp161
-rw-r--r--pica-client/mainwindow.cpp110
-rw-r--r--pica-client/mainwindow.h48
-rw-r--r--pica-client/mainwindow.ui40
-rw-r--r--pica-client/msguirouter.cpp93
-rw-r--r--pica-client/msguirouter.h37
-rw-r--r--pica-client/nodes.cpp73
-rw-r--r--pica-client/nodes.h30
-rw-r--r--pica-client/nodethread.cpp94
-rw-r--r--pica-client/nodethread.h40
-rw-r--r--pica-client/pica-client.pro70
-rw-r--r--pica-client/picaactioncenter.cpp37
-rw-r--r--pica-client/picaactioncenter.h28
-rw-r--r--pica-client/picapica-icon-fly.pngbin0 -> 1675 bytes
-rw-r--r--pica-client/picapica-icon-sit.pngbin0 -> 1092 bytes
-rw-r--r--pica-client/picasystray.cpp39
-rw-r--r--pica-client/picasystray.h31
-rw-r--r--pica-client/skynet.cpp437
-rw-r--r--pica-client/skynet.h83
38 files changed, 2875 insertions, 0 deletions
diff --git a/pica-client/accounts.cpp b/pica-client/accounts.cpp
new file mode 100644
index 0000000..e9cf0e9
--- /dev/null
+++ b/pica-client/accounts.cpp
@@ -0,0 +1,107 @@
+#include "accounts.h"
+#include <QFile>
+
+Accounts::Accounts(QString storage)
+{
+ dbconn=QSqlDatabase::addDatabase("QSQLITE");
+ dbconn.setDatabaseName(storage);
+
+ if (!dbconn.open())
+ {
+ lasterr=dbconn.lastError();
+ return;
+ }
+
+ QSqlQuery query;
+
+ query.exec("PRAGMA foreign_keys=ON;");
+
+ //check result, if foreign keys support is on
+}
+
+void Accounts::Add(AccountRecord &acc)
+{
+ QSqlQuery query;
+
+ query.prepare("insert into accounts (id, name, cert_file, pkey_file, ca_file) values (:id, :name, :cert_file, :pkey_file, :ca_file);");
+ query.bindValue(":id",acc.id);
+ query.bindValue(":name",acc.name);
+ query.bindValue(":cert_file",acc.cert_file);
+ query.bindValue(":pkey_file",acc.pkey_file);
+ query.bindValue(":ca_file",acc.CA_file);
+ query.exec();
+
+ lasterr=query.lastError();
+}
+
+void Accounts::Delete(quint32 id)
+{
+ QSqlQuery query;
+
+ query.prepare("delete from accounts where id=:id");
+ query.bindValue(":id",id);
+ query.exec();
+
+ lasterr=query.lastError();
+}
+
+QList<Accounts::AccountRecord> Accounts::GetAccounts()
+{
+ AccountRecord r;
+ QList<Accounts::AccountRecord> L;
+ QSqlQuery query;
+
+ //
+ query.setForwardOnly(true);
+ query.exec("select id, name, cert_file, pkey_file, ca_file from accounts");
+ lasterr=query.lastError();
+
+ if (lasterr.isValid())
+ return L;
+
+ while(query.next())
+ {
+ r.id = query.value(0).toUInt();
+ r.name = query.value(1).toString();
+ r.cert_file = query.value(2).toString();
+ r.pkey_file = query.value(3).toString();
+ r.CA_file = query.value(4).toString();
+
+ L<<r;
+ }
+
+ return L;
+}
+
+bool Accounts::isOK()
+{
+ return !lasterr.isValid();
+}
+
+QString Accounts::GetLastError()
+{
+ return lasterr.text();
+}
+
+bool Accounts::CheckFiles(AccountRecord &acc, QString &error_string)
+{
+ if (!QFile::exists(acc.cert_file))
+ {
+ error_string = QObject::tr("Certificate file %1 does not exist").arg(acc.cert_file);
+ return false;
+ }
+
+ if (!QFile::exists(acc.pkey_file))
+ {
+ error_string = QObject::tr("Private key file %1 does not exist").arg(acc.pkey_file);
+ return false;
+ }
+
+ if (!QFile::exists(acc.CA_file))
+ {
+ error_string = QObject::tr("Certificate Authority file %1 does not exist").arg(acc.CA_file);
+ return false;
+ }
+
+ return true;
+}
diff --git a/pica-client/accounts.h b/pica-client/accounts.h
new file mode 100644
index 0000000..1205583
--- /dev/null
+++ b/pica-client/accounts.h
@@ -0,0 +1,35 @@
+#ifndef ACCOUNTS_H
+#define ACCOUNTS_H
+
+#include <QString>
+#include <QtSql>
+#include <QSqlDatabase>
+#include <QStringList>
+
+class Accounts
+{
+public:
+ struct AccountRecord
+ {
+ quint32 id;
+ QString name;
+ QString cert_file;
+ QString pkey_file;
+ QString CA_file;
+ };
+
+ Accounts(QString storage);
+ void Add(AccountRecord& acc);
+ void Delete(quint32 id);
+ QList<AccountRecord> GetAccounts();
+ bool isOK();
+ QString GetLastError();
+
+ static bool CheckFiles(AccountRecord& acc, QString &error_string);
+
+private:
+ QSqlDatabase dbconn;
+ QSqlError lasterr;
+};
+
+#endif // ACCOUNTS_H
diff --git a/pica-client/accountswindow.cpp b/pica-client/accountswindow.cpp
new file mode 100644
index 0000000..151e366
--- /dev/null
+++ b/pica-client/accountswindow.cpp
@@ -0,0 +1,205 @@
+#include "accountswindow.h"
+#include <QVBoxLayout>
+#include "mainwindow.h"
+#include "globals.h"
+#include "dialogs/addaccountdialog.h"
+#include "../PICA_client.h"
+#include "skynet.h"
+#include <QRegExp>
+#include <QMessageBox>
+#include <QApplication>
+
+AccountsWindow::AccountsWindow(QWidget *parent) :
+ QMainWindow(parent),
+ accounts(config_dbname)
+{
+ QVBoxLayout *layout;
+ layout = new QVBoxLayout;
+
+ cb_accounts = new QComboBox();
+ bt_login = new QPushButton(tr("Log In"));
+ bt_registernew = new QPushButton(tr("Register New Account..."));
+ bt_addimport = new QPushButton(tr("Add (Import) existing account..."));
+ bt_delete = new QPushButton(tr("Delete Account"));
+
+ {
+ QFont f;
+ f= bt_login->font();
+ f.setBold(true);
+ bt_login->setFont(f);
+ }
+
+ layout->addWidget(cb_accounts);
+ layout->addWidget(bt_login);
+ layout->addWidget(bt_registernew);
+ layout->addWidget(bt_addimport);
+ layout->addWidget(bt_delete);
+
+ QWidget *central = new QWidget(this);
+ setCentralWidget(central);
+
+ centralWidget()->setLayout(layout);
+
+ setWindowIcon(picapica_ico_sit);
+
+ connect(bt_login,SIGNAL(clicked()),this,SLOT(login_click()));
+ connect(bt_registernew,SIGNAL(clicked()),this,SLOT(register_click()));
+ connect(bt_addimport,SIGNAL(clicked()),this,SLOT(add_click()));
+ connect(bt_delete,SIGNAL(clicked()),this,SLOT(delete_click()));
+
+ LoadAccounts();
+}
+
+void AccountsWindow::LoadAccounts()
+{
+ cb_accounts->clear();
+ L.clear();
+ L=accounts.GetAccounts();
+
+ if (0==L.count())
+ {
+ cb_accounts->addItem(tr(" ---No Accounts --"));
+
+ bt_login->setDisabled(true);
+ }
+ else
+ {
+ for (int i=0;i<L.count();i++)
+ cb_accounts->addItem(QString("(%1) %2").arg(L[i].id).arg(L[i].name),L[i].id);
+
+ bt_login->setEnabled(true);
+ }
+}
+
+void AccountsWindow::login_click()
+{
+
+ account_id = (cb_accounts->itemData(cb_accounts->currentIndex())).toUInt();
+
+ int L_index;
+
+ for (int i=0;i<L.count();i++)
+ {
+ if (L[i].id==account_id)
+ {
+ L_index = i;
+ break;
+ }
+ }
+
+ {
+ QString error_str;
+ if (!Accounts::CheckFiles(L[L_index],error_str))
+ {
+ QMessageBox mbx;
+ mbx.setText(error_str);
+ mbx.exec();
+ return;
+ }
+ }
+ skynet->Join(L[L_index]);
+
+ mainwindow = new MainWindow();
+ mainwindow->show();
+ this->hide();
+
+}
+
+void AccountsWindow::register_click()
+{
+
+}
+
+void AccountsWindow::add_click()
+{
+ AddAccountDialog d;
+ d.exec();
+
+ if (d.result())
+ {
+ quint32 id;
+
+ PICA_get_id_from_cert(d.GetCertFilename().toUtf8().constData(),&id);
+
+ //
+ QProcess openssl;
+
+ openssl.start("openssl",QStringList()<<"x509"<<"-in"<<d.GetCertFilename()<<"-subject"<<"-nameopt"<<"oneline,-esc_msb,utf8"<<"-noout");
+ if (!openssl.waitForStarted())
+ return;
+
+ if (!openssl.waitForFinished())
+ return;
+
+ QByteArray result = openssl.readAll();
+ QRegExp rx("CN = [0-9]+\\#([^\\s\\#]+)");
+
+
+ QString name=QString::fromUtf8( result.constData());
+
+ if (name.contains(rx))
+ {
+ name=rx.cap(1);
+ }
+
+ //
+ Accounts::AccountRecord rec;
+ QString certfilename,pkeyfilename;
+
+ if (d.isCopyFilesChecked())
+ {
+ QDir dir(config_dir);
+ dir.mkdir(QString::number(id));
+ dir.cd(QString::number(id));
+
+ certfilename=dir.absolutePath()+"/"+QString::number(id)+"_cert.pem";
+ pkeyfilename=dir.absolutePath()+"/"+QString::number(id)+"_pkey.pem";
+
+ QFile::copy(d.GetCertFilename(),certfilename);
+ QFile::copy(d.GetPkeyFilename(),pkeyfilename);
+ }
+ else
+ {
+ certfilename = d.GetCertFilename();
+ pkeyfilename = d.GetPkeyFilename();
+ }
+
+ rec.id=id;
+ rec.name=name;
+ rec.cert_file=certfilename;
+ rec.pkey_file=pkeyfilename;
+ rec.CA_file= config_defaultCA;
+
+ accounts.Add(rec);
+
+ if (!accounts.isOK())
+ {
+ QMessageBox mbx;
+ mbx.setText(accounts.GetLastError());
+ mbx.exec();
+ }
+ LoadAccounts();
+ }
+}
+
+void AccountsWindow::delete_click()
+{
+ quint32 id = (cb_accounts->itemData(cb_accounts->currentIndex())).toUInt();
+
+ if (QMessageBox::No==
+ QMessageBox::question(this,tr("Deleting account"),tr("Are you sure you want to delete account %1 ?").arg(id),QMessageBox::Yes,QMessageBox::No))
+ return;
+
+ //TODO ask user if he wants to wipe certificate and private key too
+
+ accounts.Delete(id);
+
+ LoadAccounts();
+
+}
+
+void AccountsWindow::closeEvent(QCloseEvent *e)
+{
+ QApplication::instance()->quit();
+ QMainWindow::closeEvent(e);
+}
diff --git a/pica-client/accountswindow.h b/pica-client/accountswindow.h
new file mode 100644
index 0000000..30e3c0d
--- /dev/null
+++ b/pica-client/accountswindow.h
@@ -0,0 +1,43 @@
+#ifndef ACCOUNTSWINDOW_H
+#define ACCOUNTSWINDOW_H
+
+#include <QMainWindow>
+#include <QComboBox>
+#include <QPushButton>
+#include "accounts.h"
+#include <QList>
+
+class AccountsWindow : public QMainWindow
+{
+ Q_OBJECT
+public:
+ explicit AccountsWindow(QWidget *parent = 0);
+
+private:
+ QComboBox *cb_accounts;
+ QPushButton *bt_login;
+ QPushButton *bt_registernew;
+ QPushButton *bt_addimport;
+ QPushButton *bt_delete;
+
+ Accounts accounts;
+ QList<Accounts::AccountRecord> L;
+
+ void LoadAccounts();
+
+ void closeEvent(QCloseEvent *);
+
+
+signals:
+
+public slots:
+
+private slots:
+ void login_click();
+ void register_click();
+ void add_click();
+ void delete_click();
+
+};
+
+#endif // ACCOUNTSWINDOW_H
diff --git a/pica-client/askpassword.cpp b/pica-client/askpassword.cpp
new file mode 100644
index 0000000..0629502
--- /dev/null
+++ b/pica-client/askpassword.cpp
@@ -0,0 +1,52 @@
+#include "askpassword.h"
+#include "globals.h"
+#include <string.h>
+#include <QInputDialog>
+
+QMutex AskPassword::passwd_mutex;
+QMap<quint32,QString> AskPassword::idpasswd;
+
+AskPassword::AskPassword(QObject *parent) :
+ QObject(parent)
+{
+ connect(this,SIGNAL(password_requested()),this,SLOT(password_dialog()),Qt::BlockingQueuedConnection);
+}
+
+int AskPassword::ask_password_cb(char *buf, int size, int rwflag, void *userdata)
+{
+ QString pswd;
+ quint32 id = *((quint32*)userdata);
+
+ passwd_mutex.lock();
+
+ if (idpasswd.contains(id))
+ {
+ pswd = idpasswd[id];
+ }
+ else
+ {
+ askpassword->emit_sig();
+ pswd = idpasswd[id];
+ }
+ passwd_mutex.unlock();
+
+ strncpy(buf, pswd.toUtf8().constData(), size);
+ buf[size - 1] = 0;
+ return(strlen(buf));
+}
+
+void AskPassword::emit_sig()
+{
+ emit password_requested();
+}
+
+void AskPassword::password_dialog()
+{
+ bool ok;
+ QString title = QObject::tr("Enter private key passphrase");
+ QString text = QObject::tr(QString("Passphrase for (%1)").arg(account_id).toUtf8().constData());
+ QString psw = QInputDialog::getText((QWidget*)mainwindow,title,text,QLineEdit::Password,QString(),&ok);
+ if (ok)
+ idpasswd[account_id] = psw;
+}
+
diff --git a/pica-client/askpassword.h b/pica-client/askpassword.h
new file mode 100644
index 0000000..c841693
--- /dev/null
+++ b/pica-client/askpassword.h
@@ -0,0 +1,27 @@
+#ifndef ASKPASSWORD_H
+#define ASKPASSWORD_H
+
+#include <QObject>
+#include <QMap>
+#include <QMutex>
+
+class AskPassword : public QObject
+{
+ Q_OBJECT
+public:
+ explicit AskPassword(QObject *parent = 0);
+ static int ask_password_cb(char *buf, int size, int rwflag, void *userdata);
+
+private:
+ void emit_sig();
+ static QMap<quint32,QString> idpasswd;
+ static QMutex passwd_mutex;
+
+signals:
+ void password_requested();
+
+public slots:
+ void password_dialog();
+};
+
+#endif // ASKPASSWORD_H
diff --git a/pica-client/chatwindow.cpp b/pica-client/chatwindow.cpp
new file mode 100644
index 0000000..dd495ae
--- /dev/null
+++ b/pica-client/chatwindow.cpp
@@ -0,0 +1,140 @@
+#include "chatwindow.h"
+#include "globals.h"
+#include <QVBoxLayout>
+#include <QDateTime>
+#include <QAction>
+#include <QKeyEvent>
+#include <QMessageBox>
+
+ChatWindow::ChatWindow(quint32 peer_id) :
+ QWidget(0), peer_id_(peer_id)
+{
+ QVBoxLayout *layout;
+
+ layout = new QVBoxLayout;
+
+ chatw = new QTextEdit(this);
+ sendtextw = new TextSend(this);
+
+ connect(sendtextw, SIGNAL(send_pressed()), this, SLOT(send_message()));
+
+ sendtextw->setMinimumHeight(48);
+ sendtextw->setMinimumWidth(240);
+ sendtextw->setAcceptRichText(false);
+
+ chatw->setReadOnly(true);
+
+ layout->addWidget(chatw, 3);
+ layout->addWidget(sendtextw, 1, Qt::AlignBottom);
+
+ setLayout(layout);
+
+ setWindowTitle(QString::number(peer_id));
+ setWindowIcon(picapica_ico_sit);
+
+ QWidget::setAttribute(Qt::WA_DeleteOnClose);
+ QWidget::setAttribute(Qt::WA_QuitOnClose, false);
+}
+
+void ChatWindow::put_message(QString msg, quint32 id, bool is_me)
+{
+ QString color;
+ QTextCharFormat fmt;
+
+ if (is_me)
+ color = "#0000ff";
+ else
+ color = "#ff0000";
+
+ QTextCursor c(chatw->document());
+
+ c.movePosition(QTextCursor::End, QTextCursor::MoveAnchor);
+
+ if (is_me)
+ undelivered_msgs.append(c.position());
+
+ fmt = chatw->currentCharFormat();
+ chatw->insertHtml(
+ QString("<b><font color=%1>&nbsp;(%2) %3 : </font></b>").
+ arg(color).
+ arg(QDateTime::currentDateTime().toString()).
+ arg(QString::number(id))
+ );
+ chatw->setCurrentCharFormat(fmt);
+ chatw->insertPlainText(msg + '\n');
+
+ chatw->moveCursor(QTextCursor::End, QTextCursor::KeepAnchor);
+}
+
+void ChatWindow::msg_from_peer(QString msg)
+{
+ if (isMinimized())
+ showNormal();
+
+ put_message(msg, peer_id_, false);
+}
+
+void ChatWindow::msg_delivered()
+{
+ QTextCursor c(chatw->document());
+ c.setPosition(undelivered_msgs.first());
+ c.deleteChar();
+ c.insertText("+");
+ undelivered_msgs.removeFirst();
+}
+
+void ChatWindow::msg_informational(QString text)
+{
+ QString color;
+ QTextCharFormat fmt;
+
+ color = "#303030";
+
+ fmt = chatw->currentCharFormat();
+
+ chatw->insertHtml(
+ QString("<i><font color=%1>(%2)</font></i>").
+ arg(color).
+ arg(text)
+ );
+ chatw->insertPlainText("\n");
+
+ chatw->setCurrentCharFormat(fmt);
+ chatw->moveCursor(QTextCursor::End, QTextCursor::KeepAnchor);
+}
+
+void ChatWindow::send_message()
+{
+ put_message(sendtextw->toPlainText(), account_id, true);
+
+ emit msg_input(sendtextw->toPlainText(), this);
+
+ sendtextw->clear();
+}
+
+void ChatWindow::closeEvent(QCloseEvent *e)
+{
+ emit chatwindow_close(this);
+
+ e->accept();
+}
+
+TextSend::TextSend(QWidget *parent) :
+ QTextEdit(parent)
+{
+
+}
+
+void TextSend::keyPressEvent(QKeyEvent *e)
+{
+ if (e->key() == Qt::Key_Return && e->modifiers() == Qt::NoModifier )
+ {
+ emit send_pressed();
+ return;
+ }
+
+ QTextEdit::keyPressEvent(e);
+}
+
+
+
diff --git a/pica-client/chatwindow.h b/pica-client/chatwindow.h
new file mode 100644
index 0000000..1babd31
--- /dev/null
+++ b/pica-client/chatwindow.h
@@ -0,0 +1,53 @@
+#ifndef CHATWINDOW_H
+#define CHATWINDOW_H
+
+#include <QWidget>
+#include <QTextEdit>
+#include <QList>
+
+class TextSend : public QTextEdit
+{
+ Q_OBJECT
+public:
+ explicit TextSend(QWidget *parent);
+signals:
+ void send_pressed();
+private:
+ void keyPressEvent(QKeyEvent *e);
+
+};
+
+class ChatWindow : public QWidget
+{
+ Q_OBJECT
+public:
+ explicit ChatWindow(quint32 peer_id);
+
+ quint32 getPeerId() { return peer_id_;};
+
+signals:
+ void msg_input(QString msg, ChatWindow *sender_window);
+ void chatwindow_close(ChatWindow *sender_window);
+
+public slots:
+ void msg_from_peer(QString msg);
+ void msg_delivered();
+ void msg_informational(QString text);//юзер в оффлайне, набирает сообщение, и т.д. серым шрифтом, курсив
+
+
+private:
+ QTextEdit *chatw;
+ TextSend *sendtextw;
+ quint32 peer_id_;
+ QList<int> undelivered_msgs;
+
+
+ void put_message(QString msg, quint32 id, bool is_me);
+
+ void closeEvent(QCloseEvent *e);
+
+private slots:
+ void send_message();
+};
+
+#endif // CHATWINDOW_H
diff --git a/pica-client/contactlistwidget.cpp b/pica-client/contactlistwidget.cpp
new file mode 100644
index 0000000..bff36bd
--- /dev/null
+++ b/pica-client/contactlistwidget.cpp
@@ -0,0 +1,188 @@
+#include "contactlistwidget.h"
+#include "globals.h"
+#include "dialogs/viewcertdialog.h"
+#include "msguirouter.h"
+#include <QMenu>
+#include <QMessageBox>
+#include <QContextMenuEvent>
+#include <QInputDialog>
+
+ContactListWidget::ContactListWidget(QWidget *parent) :
+ QListWidget(parent)
+{
+
+ addcontactAct = new QAction(tr("&Add Contact..."), this);
+ addcontactAct->setStatusTip(tr("add new contact"));
+ connect(addcontactAct,SIGNAL(triggered()),this,SLOT(add_contact()));
+
+
+ delcontactAct = new QAction(tr("&Delete Contact"),this);
+ connect(delcontactAct,SIGNAL(triggered()),this,SLOT(del_contact()));
+
+ startchatAct = new QAction(tr("Start &Chat"), this);
+ connect(startchatAct, SIGNAL(triggered()), this, SLOT(start_chat()));
+
+ viewcertAct = new QAction(tr("&View Certificate"), this);
+ connect(viewcertAct, SIGNAL(triggered()), this, SLOT(view_cert()));
+
+ connect(this, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(start_chat()));
+
+ setContactsStorage(new Contacts(config_dbname, account_id));
+}
+
+ContactListWidget::~ContactListWidget()
+{
+ delete storage;
+}
+
+
+void ContactListWidget::contextMenuEvent(QContextMenuEvent *event)
+{
+ QMenu menu(this);
+
+ if (this->itemAt(event->pos()))
+ menu.addAction(delcontactAct);
+
+ menu.addAction(addcontactAct);
+ menu.addAction(startchatAct);
+ menu.addAction(viewcertAct);
+ menu.exec(event->globalPos());
+}
+
+void ContactListWidget::setContactsStorage(Contacts *ct)
+{
+ clear();
+ // this->addItems(ct->GetContacts());
+ storage=ct;
+ contact_records=ct->GetContacts();
+
+ for (int i=0;i<contact_records.size();i++)
+ {
+ addItem(QString("(%1) ").arg(contact_records[i].id)+contact_records[i].name);
+ wgitem_to_recs[this->item(i)]=&contact_records[i];
+ this->item(i)->setStatusTip(QString().setNum(contact_records[i].id));
+ }
+}
+
+void ContactListWidget::add_contact()
+{
+ bool ok;
+ QString input=QInputDialog::getText(this,tr("Add New Contact"),tr("User ID: "),QLineEdit::Normal,NULL,&ok);
+
+ if (ok && !input.isEmpty())
+ {
+ quint32 user_id=input.toUInt(&ok);
+
+ if (!ok)
+ {
+ QMessageBox mbx;
+ mbx.setIcon(QMessageBox::Warning);
+ mbx.setText(tr("Invalid user ID"));
+ mbx.exec();
+ return;
+ }
+
+ for(int i=0;i<contact_records.size();i++)
+ if (contact_records[i].id==user_id)
+ {
+ QMessageBox mbx;
+ mbx.setIcon(QMessageBox::Warning);
+ mbx.setText(tr("User \"(%1) %2\" already exists").arg(user_id).arg(contact_records[i].name));
+ mbx.exec();
+ return;
+ }
+
+ Contacts::ContactRecord r;
+
+ r.id=user_id;
+ r.name="";
+ //void ContactListWidget::debug_onmsginput(QString msg, ChatWindow *sender_window)
+ //{
+ // skynet->SendMessage(sender_window->getPeerId(), msg);
+ //}
+
+ storage->Add(user_id);
+
+ if (!storage->isOK())
+ {
+ QMessageBox mbx;
+ mbx.setIcon(QMessageBox::Warning);
+ mbx.setText(storage->GetLastError());
+ mbx.exec();
+ return;
+ }
+
+ addItem(QString("(%1) ").arg(user_id));
+ contact_records.append(r);
+ wgitem_to_recs[this->item(this->count()-1)]=&contact_records.last();
+
+ }
+
+}
+
+void ContactListWidget::del_contact()
+{
+QMessageBox mbx;
+mbx.setText(QString(tr("Are you sure you want to remove user \"%1\" from your contact list?")).arg(currentItem()->text()));
+mbx.setIcon(QMessageBox::Question);
+mbx.addButton(QMessageBox::Yes);
+mbx.addButton(QMessageBox::No);
+mbx.setDefaultButton(QMessageBox::No);
+
+if (QMessageBox::Yes==mbx.exec())
+ {
+ quint32 rm_id;
+ QListWidgetItem *rm_ptr;
+
+ storage->Delete(rm_id = wgitem_to_recs[currentItem()]->id);
+ delete (rm_ptr=takeItem(currentRow()));
+
+ for(int i=0;i<contact_records.size();i++)
+ {
+ if (contact_records[i].id==rm_id)
+ {
+ contact_records.removeAt(i);
+ break;
+ }
+ }
+
+ wgitem_to_recs.remove(rm_ptr);
+ }
+
+}
+
+void ContactListWidget::start_chat()
+{
+// ChatWindow *cw_debug_remove;
+
+// (cw_debug_remove = new ChatWindow(wgitem_to_recs[currentItem()]->id)) ->show();//debug
+
+// connect(cw_debug_remove, SIGNAL(msg_input(QString,ChatWindow*)), this, SLOT(debug_onmsginput(QString, ChatWindow*)));
+
+ msguirouter->start_chat(wgitem_to_recs[currentItem()]->id);
+}
+
+void ContactListWidget::view_cert()
+{
+ QString cert_pem = storage->GetContactCert(wgitem_to_recs[currentItem()]->id);
+
+ if (!storage->isOK())
+ {
+ QMessageBox mbx;
+ mbx.setText(storage->GetLastError());
+ mbx.exec();
+ }
+
+ if (!cert_pem.isEmpty())
+ {
+ ViewCertDialog vcd;
+ vcd.SetCert(cert_pem);
+ vcd.exec();
+ }
+ else
+ {
+ QMessageBox mbx;
+ mbx.setText(tr("Certificate is not stored for this contact yet"));
+ mbx.exec();
+ }
+}
diff --git a/pica-client/contactlistwidget.h b/pica-client/contactlistwidget.h
new file mode 100644
index 0000000..d884bc4
--- /dev/null
+++ b/pica-client/contactlistwidget.h
@@ -0,0 +1,43 @@
+#ifndef CONTACTLISTWIDGET_H
+#define CONTACTLISTWIDGET_H
+
+#include <QListWidget>
+#include "contacts.h"
+#include <QAction>
+
+#include "chatwindow.h" //debug remove
+
+class ContactListWidget : public QListWidget
+{
+ Q_OBJECT
+public:
+ explicit ContactListWidget(QWidget *parent = 0);
+ ~ContactListWidget();
+
+ QAction *addcontactAct;
+ QAction *delcontactAct;
+ QAction *startchatAct;
+ QAction *viewcertAct;
+protected:
+ void contextMenuEvent(QContextMenuEvent *event);
+
+private:
+ void setContactsStorage(Contacts *ct);
+ Contacts* storage;
+ QList<Contacts::ContactRecord> contact_records;
+ QMap<QListWidgetItem*,Contacts::ContactRecord*> wgitem_to_recs;
+
+ //ChatWindow *cw_debug_remove;
+
+signals:
+
+private slots:
+ void add_contact();
+ void del_contact();
+ void start_chat();
+ void view_cert();
+
+ //void debug_onmsginput(QString msg, ChatWindow *sender_window);//debug remove after debug
+};
+
+#endif // CONTACTLISTWIDGET_H
diff --git a/pica-client/contacts.cpp b/pica-client/contacts.cpp
new file mode 100644
index 0000000..7b66c4f
--- /dev/null
+++ b/pica-client/contacts.cpp
@@ -0,0 +1,133 @@
+#include "contacts.h"
+
+
+//#include <QMessageBox>
+
+Contacts::Contacts(QString storage, quint32 user_account_id)
+ : account_id_(user_account_id)
+{
+ dbconn=QSqlDatabase::addDatabase("QSQLITE");
+ dbconn.setDatabaseName(storage);
+
+ if (!dbconn.open())
+ {
+ lasterr=dbconn.lastError();
+ return;
+ }
+
+//check if table contacts exists
+//...
+
+ QSqlQuery query;
+
+ query.exec("PRAGMA foreign_keys=ON;");
+
+ //check result, if foreign keys support is on
+}
+
+bool Contacts::isOK()
+{
+ return !lasterr.isValid();
+}
+
+QString Contacts::GetLastError()
+{
+ return lasterr.text();
+}
+
+void Contacts::Add(quint32 id)
+{
+ QSqlQuery query;
+
+ query.prepare("insert into contacts (id, name, cert_pem, account_id) values (:id, NULL, NULL, :account_id)");
+ query.bindValue(":id",id);
+ query.bindValue(":account_id",account_id_);
+ query.exec();
+
+ lasterr=query.lastError();
+}
+
+void Contacts::Delete(quint32 id)
+{
+ QSqlQuery query;
+
+ query.prepare("delete from contacts where id=:id and account_id=:account_id");
+ query.bindValue(":id",id);
+ query.bindValue(":account_id",account_id_);
+ query.exec();
+
+ lasterr=query.lastError();
+}
+
+QList<Contacts::ContactRecord> Contacts::GetContacts()
+{
+ QList<ContactRecord> L;
+ ContactRecord r;
+ QSqlQuery query;
+
+ query.setForwardOnly(true);
+ query.prepare("select id, name from contacts where account_id=:account_id");
+ query.bindValue(":account_id",account_id_);
+ query.exec();
+ lasterr=query.lastError();
+
+ if (lasterr.isValid())
+ return L;
+
+ while(query.next())
+ {
+ r.id=query.value(0).toUInt();
+ r.name=query.value(1).toString();
+ //CSTRL.append(" ("+query.value(0).toString()+") "+query.value(1).toString());
+ L<<r;
+ }
+
+ return L;
+}
+
+QString Contacts::GetContactCert(quint32 id)
+{
+ QSqlQuery query;
+
+ query.prepare("select cert_pem from contacts where id=:id and account_id=:account_id");
+ query.bindValue(":id",id);
+ query.bindValue(":account_id",account_id_);
+ query.exec();
+
+ lasterr=query.lastError();
+
+ if (lasterr.isValid())
+ return QString::null;
+
+ query.next();
+
+ return query.value(0).toString();
+}
+
+void Contacts::SetContactCert(quint32 id, QString &cert_pem)
+{
+ QSqlQuery query;
+
+ query.prepare("update contacts set cert_pem=:cert_pem where id=:id and account_id=:account_id");
+ query.bindValue(":cert_pem",cert_pem);
+ query.bindValue(":id",id);
+ query.bindValue(":account_id",account_id_);
+ query.exec();
+
+ lasterr=query.lastError();
+
+}
+
+void Contacts::SetContactName(quint32 id, QString name)
+{
+ QSqlQuery query;
+
+ query.prepare("update contacts set name=:name where id=:id and account_id=:account_id");
+ query.bindValue(":name",name);
+ query.bindValue(":id",id);
+ query.bindValue(":account_id",account_id_);
+ query.exec();
+
+ lasterr=query.lastError();
+}
+
diff --git a/pica-client/contacts.h b/pica-client/contacts.h
new file mode 100644
index 0000000..54042b5
--- /dev/null
+++ b/pica-client/contacts.h
@@ -0,0 +1,34 @@
+#ifndef CONTACTS_H
+#define CONTACTS_H
+
+#include <QString>
+#include <QtSql>
+#include <QSqlDatabase>
+#include <QStringList>
+
+class Contacts
+{
+public:
+ struct ContactRecord
+ {
+ quint32 id;
+ QString name;
+ };
+
+ Contacts(QString storage, quint32 user_account_id);
+
+ void Add(quint32 id);
+ void Delete(quint32 id);
+ QList<ContactRecord> GetContacts();
+ QString GetContactCert(quint32 id);
+ void SetContactCert(quint32 id, QString &cert_pem);
+ void SetContactName(quint32 id, QString name);
+ QString GetLastError();
+ bool isOK();
+private:
+ QSqlDatabase dbconn;
+ QSqlError lasterr;
+ quint32 account_id_;
+};
+
+#endif // CONTACTS_H
diff --git a/pica-client/dialogs/addaccountdialog.cpp b/pica-client/dialogs/addaccountdialog.cpp
new file mode 100644
index 0000000..86849d4
--- /dev/null
+++ b/pica-client/dialogs/addaccountdialog.cpp
@@ -0,0 +1,114 @@
+#include "addaccountdialog.h"
+#include <QGridLayout>
+#include <QLabel>
+#include <QFile>
+#include <QFileDialog>
+#include <QMessageBox>
+#include <QProcess>
+#include "../PICA_client.h"
+
+AddAccountDialog::AddAccountDialog(QWidget *parent) :
+ QDialog(parent)
+{
+ QGridLayout *grid = new QGridLayout(this);
+
+ QLabel *lb_cert_file = new QLabel(tr("Pica Pica Certificate File:"));
+ QLabel *lb_pkey_file = new QLabel(tr("Private Key File:"));
+
+ btOK = new QPushButton(tr("OK"));
+ btCancel = new QPushButton(tr("Cancel"));
+
+ cert_filename = new QLineEdit();
+ pkey_filename = new QLineEdit();
+
+ bt_browse_cert = new QPushButton("...");
+ bt_browse_pkey = new QPushButton("...");
+
+ rb_copyfiles = new QRadioButton(tr("Copy files to config directory"));
+ rb_readinplace = new QRadioButton(tr("Read files from selected place"));
+
+ grid->addWidget(lb_cert_file,0,0,1,1);
+
+ grid->addWidget(cert_filename,1,0,1,1);
+ grid->addWidget(bt_browse_cert,1,1,1,1);
+
+ grid->addWidget(lb_pkey_file,2,0,1,1);
+
+ grid->addWidget(pkey_filename,3,0,1,1);
+ grid->addWidget(bt_browse_pkey,3,1,1,1);
+
+ grid->addWidget(rb_copyfiles,4,0,1,1);
+ grid->addWidget(rb_readinplace,5,0,1,1);
+
+ grid->addWidget(btOK,6,0,1,1,Qt::AlignRight);
+ grid->addWidget(btCancel,6,1,1,1,Qt::AlignLeft);
+ //===========
+ rb_copyfiles->setChecked(true);
+
+ btOK->setMaximumWidth(90);
+ btCancel->setMaximumWidth(90);
+
+ connect(btOK,SIGNAL(clicked()),this,SLOT(OK()));
+ connect(btCancel,SIGNAL(clicked()),this,SLOT(Cancel()));
+ connect(bt_browse_cert,SIGNAL(clicked()),this,SLOT(browse_cert()));
+ connect(bt_browse_pkey,SIGNAL(clicked()),this,SLOT(browse_pkey()));
+
+
+}
+
+void AddAccountDialog::OK()
+{
+ {
+ QString s;
+ if (!QFile::exists(s=cert_filename->text()) || !QFile::exists(s=pkey_filename->text()) )
+ {
+ QMessageBox mbx;
+ mbx.setText(tr("File %1 does not exist").arg(s));
+ mbx.exec();
+ return;
+ }
+
+ quint32 id;
+
+ if (!PICA_get_id_from_cert(cert_filename->text().toUtf8().constData(),&id))
+ {
+ QMessageBox mbx;
+ mbx.setText(tr("Invalid certificate file"));
+ mbx.exec();
+ return;
+ }
+ }
+
+ done(1);
+
+}
+
+void AddAccountDialog::Cancel()
+{
+ done(0);
+}
+
+void AddAccountDialog::browse_cert()
+{
+ cert_filename->setText(QFileDialog::getOpenFileName(this,tr("Select Pica Pica Certificate"),"","PEM file (*.pem)"));
+}
+
+void AddAccountDialog::browse_pkey()
+{
+ pkey_filename->setText(QFileDialog::getOpenFileName(this,tr("Select Private Key"),"","PEM file (*.pem)"));
+}
+
+QString AddAccountDialog::GetCertFilename()
+{
+ return cert_filename->text();
+}
+
+QString AddAccountDialog::GetPkeyFilename()
+{
+ return pkey_filename->text();
+}
+
+bool AddAccountDialog::isCopyFilesChecked()
+{
+ return rb_copyfiles->isChecked();
+}
diff --git a/pica-client/dialogs/addaccountdialog.h b/pica-client/dialogs/addaccountdialog.h
new file mode 100644
index 0000000..976d020
--- /dev/null
+++ b/pica-client/dialogs/addaccountdialog.h
@@ -0,0 +1,41 @@
+#ifndef ADDACCOUNTDIALOG_H
+#define ADDACCOUNTDIALOG_H
+
+#include <QDialog>
+#include <QPushButton>
+#include <QLineEdit>
+#include <QRadioButton>
+
+class AddAccountDialog : public QDialog
+{
+ Q_OBJECT
+public:
+ explicit AddAccountDialog(QWidget *parent = 0);
+ QString GetCertFilename();
+ QString GetPkeyFilename();
+ bool isCopyFilesChecked();
+
+private:
+ QPushButton *btOK;
+ QPushButton *btCancel;
+
+ QLineEdit *cert_filename;
+ QLineEdit *pkey_filename;
+
+ QPushButton *bt_browse_cert;
+ QPushButton *bt_browse_pkey;
+
+ QRadioButton *rb_copyfiles;
+ QRadioButton *rb_readinplace;
+signals:
+
+public slots:
+private slots:
+ void OK();
+ void Cancel();
+ void browse_cert();
+ void browse_pkey();
+
+};
+
+#endif // ADDACCOUNTDIALOG_H
diff --git a/pica-client/dialogs/forgedcertdialog.cpp b/pica-client/dialogs/forgedcertdialog.cpp
new file mode 100644
index 0000000..5ca7f23
--- /dev/null
+++ b/pica-client/dialogs/forgedcertdialog.cpp
@@ -0,0 +1,53 @@
+#include "forgedcertdialog.h"
+#include "viewcertdialog.h"
+#include <QGridLayout>
+#include <QLabel>
+
+ForgedCertDialog::ForgedCertDialog(quint32 peer_id, QString received_cert, QString stored_cert, QWidget *parent) :
+ QDialog(parent),
+ received_cert_(received_cert),
+ stored_cert_(stored_cert)
+{
+ QGridLayout *grid = new QGridLayout(this);
+
+ QLabel *scary_text = new QLabel(tr(
+"Previously stored and currently present certificates for user %1 do not match.\n\
+This means that probably someone is trying to eavesdrop your communication\n\
+using man-in-the-middle attack or impersonate the user.\n\
+Connection aborted.").arg(peer_id));
+
+ btOK = new QPushButton(tr("OK"));
+ btShow_stored_cert = new QPushButton(tr("View Stored Certificate..."));
+ btShow_received_cert = new QPushButton(tr("View Received Certificate..."));
+
+ grid->addWidget(scary_text, 0,0, 1, 3, Qt::AlignCenter);
+ grid->addWidget(btShow_stored_cert, 1, 0, Qt::AlignCenter);
+ grid->addWidget(btShow_received_cert, 1, 2, Qt::AlignCenter);
+ grid->addWidget(btOK, 2, 1, Qt::AlignCenter);
+
+
+ setLayout(grid);
+
+ connect(btOK, SIGNAL(clicked()), this, SLOT(OK()));
+ connect(btShow_stored_cert, SIGNAL(clicked()), this, SLOT(StoredCert()));
+ connect(btShow_received_cert, SIGNAL(clicked()), this, SLOT(ReceivedCert()));
+}
+
+void ForgedCertDialog::OK()
+{
+ done(0);
+}
+
+void ForgedCertDialog::ReceivedCert()
+{
+ ViewCertDialog vcd;
+ vcd.SetCert(received_cert_);
+ vcd.exec();
+}
+
+void ForgedCertDialog::StoredCert()
+{
+ ViewCertDialog vcd;
+ vcd.SetCert(stored_cert_);
+ vcd.exec();
+}
diff --git a/pica-client/dialogs/forgedcertdialog.h b/pica-client/dialogs/forgedcertdialog.h
new file mode 100644
index 0000000..bc259d4
--- /dev/null
+++ b/pica-client/dialogs/forgedcertdialog.h
@@ -0,0 +1,34 @@
+#ifndef FORGEDCERTDIALOG_H
+#define FORGEDCERTDIALOG_H
+
+#include <QDialog>
+#include <QPushButton>
+#include "viewcertdialog.h"
+
+class ForgedCertDialog : public QDialog
+{
+ Q_OBJECT
+public:
+ explicit ForgedCertDialog(quint32 peer_id, QString received_cert, QString stored_cert, QWidget *parent = 0);
+
+private:
+ QString received_cert_;
+ QString stored_cert_;
+
+ QPushButton *btOK;
+ QPushButton *btShow_stored_cert;
+ QPushButton *btShow_received_cert;
+
+
+signals:
+
+public slots:
+
+private slots:
+ void OK();
+ void StoredCert();
+ void ReceivedCert();
+
+};
+
+#endif // FORGEDCERTDIALOG_H
diff --git a/pica-client/dialogs/viewcertdialog.cpp b/pica-client/dialogs/viewcertdialog.cpp
new file mode 100644
index 0000000..8b096c2
--- /dev/null
+++ b/pica-client/dialogs/viewcertdialog.cpp
@@ -0,0 +1,68 @@
+#include "viewcertdialog.h"
+#include <QVBoxLayout>
+#include <QProcess>
+
+#include <QMessageBox>
+
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/wait.h>
+#include <QFile>
+
+ViewCertDialog::ViewCertDialog(QWidget *parent) :
+ QDialog(parent)
+{
+ QVBoxLayout *layout;
+
+ layout = new QVBoxLayout;
+ cert_text = new QTextEdit(this);
+ btOK = new QPushButton(tr("OK"));
+ btOK->setMaximumWidth(45);
+ this->setMinimumWidth(640);
+ this->setMinimumHeight(480);
+
+ QTextCharFormat fmt;
+
+ fmt = cert_text->currentCharFormat();
+ fmt.setFontFixedPitch(true);
+ fmt.setFontStyleHint(QFont::Monospace);
+ fmt.setFontFamily("Liberation Mono");
+ cert_text->setCurrentCharFormat(fmt);
+
+ cert_text->setReadOnly(true);
+
+ layout->addWidget(cert_text);
+ layout->addWidget(btOK, 0, Qt::AlignCenter);
+
+ setLayout(layout);
+
+ connect(btOK, SIGNAL(clicked()), this, SLOT(OK()));
+
+}
+
+void ViewCertDialog::SetCert(QString cert_pem)
+{
+ QProcess openssl(this);
+ qint64 ret;
+ char *data;
+ QByteArray ba;
+
+ openssl.start("openssl", QStringList()<<"x509"<<"-noout"<<"-text"<<"-nameopt"<<"multiline,-esc_msb,utf8");
+
+ if (!openssl.waitForStarted())
+ return;
+
+ ba = cert_pem.toAscii();
+ ret = openssl.write(data = (char*)ba.constData());
+ openssl.closeWriteChannel();
+
+ if (!openssl.waitForFinished())
+ return;
+
+ cert_text->setPlainText(QString::fromUtf8(openssl.readAllStandardOutput().constData()));
+}
+
+void ViewCertDialog::OK()
+{
+ done(0);
+}
diff --git a/pica-client/dialogs/viewcertdialog.h b/pica-client/dialogs/viewcertdialog.h
new file mode 100644
index 0000000..013815c
--- /dev/null
+++ b/pica-client/dialogs/viewcertdialog.h
@@ -0,0 +1,28 @@
+#ifndef VIEWCERTDIALOG_H
+#define VIEWCERTDIALOG_H
+
+#include <QDialog>
+#include <QTextEdit>
+#include <QPushButton>
+
+class ViewCertDialog : public QDialog
+{
+ Q_OBJECT
+public:
+ explicit ViewCertDialog(QWidget *parent = 0);
+ void SetCert(QString cert_pem);
+
+private:
+ QTextEdit *cert_text;
+ QPushButton *btOK;
+
+signals:
+
+public slots:
+
+private slots:
+ void OK();
+
+};
+
+#endif // VIEWCERTDIALOG_H
diff --git a/pica-client/globals.h b/pica-client/globals.h
new file mode 100644
index 0000000..b6c2d7d
--- /dev/null
+++ b/pica-client/globals.h
@@ -0,0 +1,26 @@
+#ifndef GLOBALS_H
+#define GLOBALS_H
+
+#define PICA_CLIENT_STORAGEDB "pica-client.sqlite"
+#define PICA_CLIENT_CONFIGDIR ".pica-client"
+
+#ifndef PICA_INSTALLPREFIX
+
+#define PICA_INSTALLPREFIX "/usr/"
+
+#endif
+
+//variables are defined in main.cpp
+extern class QString config_dir;
+extern class QString config_dbname;
+extern class QString config_defaultCA;
+extern class SkyNet *skynet;
+extern class MainWindow *mainwindow;
+extern unsigned int account_id;
+extern class AskPassword *askpassword;
+extern class MsgUIRouter *msguirouter;
+extern class QIcon picapica_ico_sit;
+extern class QIcon picapica_ico_fly;
+extern class PicaActionCenter *action_center;
+extern class PicaSysTray *systray;
+#endif // GLOBALS_H
diff --git a/pica-client/main.cpp b/pica-client/main.cpp
new file mode 100644
index 0000000..f7a725b
--- /dev/null
+++ b/pica-client/main.cpp
@@ -0,0 +1,161 @@
+#include <QtGui/QApplication>
+#include "mainwindow.h"
+#include "accountswindow.h"
+#include "globals.h"
+#include "contacts.h"
+#include "skynet.h"
+#include "msguirouter.h"
+#include "picaactioncenter.h"
+#include "picasystray.h"
+#include <QDir>
+#include <QMessageBox>
+#include <QString>
+#include <QIcon>
+
+//globals
+QString config_dir;
+QString config_dbname;
+SkyNet *skynet;
+MainWindow *mainwindow;
+unsigned int account_id;
+class AskPassword *askpassword;
+class MsgUIRouter *msguirouter;
+
+QIcon picapica_ico_sit;
+QIcon picapica_ico_fly;
+
+class PicaActionCenter *action_center;
+class PicaSysTray *systray;
+
+// /usr/share/pica-client/pica-pica_CA.pem
+//QString config_defaultCA("/home/root_jr/files/projects/picapica_wc/localhost/project_picapica/trunk/tests/trusted_CA.pem");
+QString config_defaultCA(PICA_INSTALLPREFIX"share/pica-client/trusted_CA.pem");
+
+static bool create_config_dir()
+{
+ QMessageBox msgBox;
+ config_dir=QDir::homePath()+QDir::separator() +QString(PICA_CLIENT_CONFIGDIR);
+
+ if (!QFile::exists(config_dir))
+ {
+ msgBox.setText(QString("%1 does not exist").arg(config_dir));//debug
+ msgBox.exec();
+
+ QDir dir;
+
+ if (!dir.mkpath(config_dir))
+ {
+ msgBox.setText(QString(QObject::tr("Unable to create configuration directory:\n %1")).arg(config_dir));
+ msgBox.exec();
+ return false;
+ }
+ }
+
+ config_dbname=config_dir+QDir::separator()+QString(PICA_CLIENT_STORAGEDB);
+
+ if (!QFile::exists(config_dbname))
+ {
+ QSqlDatabase dbconn=QSqlDatabase::addDatabase("QSQLITE");
+ dbconn.setDatabaseName(config_dbname);
+
+ if (!dbconn.open())
+ {
+
+ msgBox.setText(dbconn.lastError().text());
+ msgBox.exec();
+ return false;
+ }
+
+ QSqlQuery query;
+
+ query.exec("PRAGMA foreign_keys=ON;");
+
+ if (query.lastError().isValid())
+ goto showerror;
+
+ query.exec("create table accounts \
+ (\
+ id int primary key, \
+ name varchar(64), \
+ cert_file varchar(255) not null, \
+ pkey_file varchar(255) not null, \
+ ca_file varchar(255) default null \
+ );");
+
+ if (query.lastError().isValid())
+ goto showerror;
+
+ query.exec("create table contacts \
+ (\
+ id int,\
+ name varchar(64), \
+ cert_pem text(2048), \
+ account_id int not null, \
+ primary key(id, account_id),\
+ foreign key(account_id) references accounts(id) on delete cascade\
+ );");
+
+ if (query.lastError().isValid())
+ goto showerror;
+
+ query.exec(
+ "create table nodes \
+ (\
+ address varchar(255) not null,\
+ port int not null,\
+ last_active int not null,\
+ inactive_count int not null,\
+ constraint pk primary key (address,port) on conflict replace \
+ );");
+
+
+ if (query.lastError().isValid())
+ showerror:
+ {
+ msgBox.setText(query.lastError().text());
+ msgBox.exec();
+ return false;
+ }
+
+ }
+
+
+ return true;
+
+
+}
+
+int main(int argc, char *argv[])
+{
+ QApplication a(argc, argv);
+
+ if (!create_config_dir())
+ return -1;
+
+ picapica_ico_sit = QIcon(PICA_INSTALLPREFIX"share/pica-client/picapica-icon-sit.png");
+ picapica_ico_fly = QIcon(PICA_INSTALLPREFIX"share/pica-client/picapica-icon-fly.png");
+
+ PicaActionCenter ac;
+ action_center = &ac;
+
+ AccountsWindow aw;
+
+ SkyNet s;
+ skynet=&s;
+
+ PicaSysTray pst;
+ systray = &pst;
+
+ AskPassword askpsw;
+ askpassword=&askpsw;
+
+ MsgUIRouter muir;
+ msguirouter = &muir;
+
+
+ aw.show();
+
+ a.setQuitOnLastWindowClosed(false);
+
+ return a.exec();
+}
diff --git a/pica-client/mainwindow.cpp b/pica-client/mainwindow.cpp
new file mode 100644
index 0000000..5bf44dd
--- /dev/null
+++ b/pica-client/mainwindow.cpp
@@ -0,0 +1,110 @@
+#include "mainwindow.h"
+#include "globals.h"
+#include "skynet.h"
+#include "picaactioncenter.h"
+#include "accounts.h"
+#include "ui_mainwindow.h"
+#include <QVBoxLayout>
+#include <QMessageBox>
+
+
+MainWindow::MainWindow(QWidget *parent) :
+ QMainWindow(parent),
+ ui(new Ui::MainWindow)
+{
+ ui->setupUi(this);
+
+ QVBoxLayout *layout;
+
+ layout = new QVBoxLayout;
+ contact_list = new ContactListWidget;
+ status = new QComboBox;
+
+ status->setMinimumHeight(36);
+
+ layout->addWidget(contact_list);
+ layout->addWidget(status,0,Qt::AlignBottom);
+
+ centralWidget()->setLayout(layout);
+ setWindowIcon(picapica_ico_sit);
+
+ createMenus();
+
+
+ //for (int i=0;i<255;i++)
+ // contact_list->addItem(QString("test %1").arg(i));
+
+ status->addItem(picapica_ico_fly, QString(tr("Connected")));
+ status->addItem(picapica_ico_sit, QString(tr("Disconnected")));
+
+ connect(skynet, SIGNAL(BecameSelfAware()), this, SLOT(set_online()));
+ connect(skynet, SIGNAL(LostSelfAwareness()), this, SLOT(set_offline()));
+ connect(status, SIGNAL(currentIndexChanged(int)), this, SLOT(status_changed(int)));
+}
+
+MainWindow::~MainWindow()
+{
+ delete ui;
+}
+
+void MainWindow::set_offline()
+{
+ SetStatus(false);
+}
+
+void MainWindow::set_online()
+{
+ SetStatus(true);
+}
+
+void MainWindow::status_changed(int index)
+{
+ if (status_change_disable_flag)
+ return;
+
+ if (index == 0 && !skynet->isSelfAware())
+ {
+ Accounts::AccountRecord curr_acc = skynet->CurrentAccount();
+ skynet->Join(curr_acc);
+ }
+ if (1 == index && skynet->isSelfAware())
+ skynet->Exit();
+}
+
+void MainWindow::SetStatus(bool connected)
+{
+ status_change_disable_flag = true;
+
+ if (connected)
+ {
+ status->setCurrentIndex(0);
+ setWindowTitle(tr("Pica Pica Messenger - ") + QString::number(account_id));
+ }
+ else
+ {
+ status->setCurrentIndex(1);
+ setWindowTitle(tr("Pica Pica Messenger - Disconnected"));
+ }
+
+ status_change_disable_flag = false;
+}
+
+
+void MainWindow::createMenus()
+{
+ contactsMenu=menuBar()->addMenu(tr("&Contacts"));
+ contactsMenu->addAction(contact_list->addcontactAct);
+ contactsMenu->addSeparator();
+ contactsMenu->addAction(action_center->ExitAct());
+
+ nodesMenu=menuBar()->addMenu(tr("&Nodes"));
+
+ helpMenu=menuBar()->addMenu(tr("&Help"));
+ helpMenu->addAction(action_center->AboutAct());
+
+}
+
+
+
+
+
diff --git a/pica-client/mainwindow.h b/pica-client/mainwindow.h
new file mode 100644
index 0000000..3361c69
--- /dev/null
+++ b/pica-client/mainwindow.h
@@ -0,0 +1,48 @@
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include <QMainWindow>
+#include <QListWidget>
+#include <QComboBox>
+#include <QSystemTrayIcon>
+
+#include "contacts.h"
+#include "contactlistwidget.h"
+#include "askpassword.h"
+
+namespace Ui {
+ class MainWindow;
+}
+
+class MainWindow : public QMainWindow
+{
+ Q_OBJECT
+
+public:
+ explicit MainWindow(QWidget *parent = 0);
+ ~MainWindow();
+
+protected:
+
+private:
+ void createMenus();
+
+ Ui::MainWindow *ui;
+ ContactListWidget *contact_list;
+ QComboBox *status;
+
+ QMenu *contactsMenu;
+ QMenu *helpMenu;
+ QMenu *nodesMenu;
+
+ bool status_change_disable_flag;
+ void SetStatus(bool connected);
+
+private slots:
+ void set_online();
+ void set_offline();
+ void status_changed(int index);
+
+};
+
+#endif // MAINWINDOW_H
diff --git a/pica-client/mainwindow.ui b/pica-client/mainwindow.ui
new file mode 100644
index 0000000..0f2e2a7
--- /dev/null
+++ b/pica-client/mainwindow.ui
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>442</width>
+ <height>565</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Pica Pica Messenger</string>
+ </property>
+ <widget class="QWidget" name="centralWidget"/>
+ <widget class="QMenuBar" name="menuBar">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>442</width>
+ <height>22</height>
+ </rect>
+ </property>
+ </widget>
+ <widget class="QToolBar" name="mainToolBar">
+ <attribute name="toolBarArea">
+ <enum>TopToolBarArea</enum>
+ </attribute>
+ <attribute name="toolBarBreak">
+ <bool>false</bool>
+ </attribute>
+ </widget>
+ <widget class="QStatusBar" name="statusBar"/>
+ </widget>
+ <layoutdefault spacing="6" margin="11"/>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/pica-client/msguirouter.cpp b/pica-client/msguirouter.cpp
new file mode 100644
index 0000000..efa080c
--- /dev/null
+++ b/pica-client/msguirouter.cpp
@@ -0,0 +1,93 @@
+#include "msguirouter.h"
+#include "dialogs/forgedcertdialog.h"
+
+
+/*
+ этот класс должен обеспечивать связь между приёмом-отправкой сообщений и их отображением в пользовательском интерфейсе.
+ Класс SkyNet отвечает только за прием и отправку сообщений, пересылку через сеть и работу с протоколом, ему пофиг,
+ как они показываются юзеру. Данный класс должен работать с GUI, например, по приходу нового сообщения (через сигнал от
+ класса SkyNet) либо помигать иконкой в трее, либо создать новое окно чата, либо развернуть уже имеющееся, либо помигать
+ свернутым окном на таскбаре. Это поведение может настраиваться, настройки хранить в базе и загружать в конструкторе из
+ базы sqlite. Например, данный класс должен управлять экземплярами ChatWindow, создавать новые при необходимости либо
+ активировать имеющиеся. Также этот класс должен обеспечивать хранение отложенных сообщений (когда собеседник недоступен).
+ Можно придумать название получше
+ */
+MsgUIRouter::MsgUIRouter(QObject *parent) :
+ QObject(parent)
+{
+ connect(skynet, SIGNAL(MessageReceived(quint32,QString)), this, SLOT(msg_from_peer(quint32,QString)));
+ connect(skynet, SIGNAL(UnableToDeliver(quint32,QString)), this, SLOT(delivery_failed(quint32,QString)));
+ connect(skynet, SIGNAL(Delivered(quint32)), this, SLOT(delivered(quint32)));
+ connect(skynet, SIGNAL(CertificateForged(quint32,QString,QString)), this, SLOT(scary_cert_message(quint32,QString,QString)));
+
+}
+
+void MsgUIRouter::create_chatwindow(quint32 peer_id)
+{
+ ChatWindow *cw;
+
+ cw = new ChatWindow(peer_id);
+
+ chatwindows[peer_id] = cw;
+
+ connect(cw, SIGNAL(msg_input(QString,ChatWindow*)), this, SLOT(msg_to_peer(QString,ChatWindow*)));
+ connect(cw, SIGNAL(chatwindow_close(ChatWindow*)), this, SLOT(chatwindow_closed(ChatWindow*)));
+
+ cw->show();
+}
+
+void MsgUIRouter::msg_from_peer(quint32 from, QString msg)
+{
+
+ if ( ! chatwindows.contains(from))
+ {
+ create_chatwindow(from);
+ }
+
+ chatwindows[from]->msg_from_peer(msg);
+
+}
+
+void MsgUIRouter::msg_to_peer(QString msg, ChatWindow *sender_window)
+{
+ skynet->SendMessage(sender_window->getPeerId(), msg);
+}
+
+void MsgUIRouter::start_chat(quint32 peer_id)
+{
+ if ( ! chatwindows.contains(peer_id))
+ {
+ create_chatwindow(peer_id);
+ }
+ else
+ {
+ chatwindows[peer_id]->showNormal();
+ }
+}
+
+void MsgUIRouter::chatwindow_closed(ChatWindow *sender_window)
+{
+ chatwindows.remove(sender_window->getPeerId());
+}
+
+void MsgUIRouter::delivery_failed(quint32 to, QString msg)
+{
+// if (chatwindows.contains(to))
+// {
+// chatwindows[to]->msg_informational(tr("failed to deliver"));
+// }
+}
+
+void MsgUIRouter::delivered(quint32 to)
+{
+ if (chatwindows.contains(to))
+ {
+ chatwindows[to]->msg_delivered();
+ }
+}
+
+void MsgUIRouter::scary_cert_message(quint32 peer_id, QString received_cert, QString stored_cert)
+{
+ ForgedCertDialog fcd(peer_id, received_cert, stored_cert);
+ fcd.exec();
+}
diff --git a/pica-client/msguirouter.h b/pica-client/msguirouter.h
new file mode 100644
index 0000000..5988878
--- /dev/null
+++ b/pica-client/msguirouter.h
@@ -0,0 +1,37 @@
+#ifndef MSGUIROUTER_H
+#define MSGUIROUTER_H
+
+#include <QObject>
+#include <QList>
+#include <QMap>
+#include "chatwindow.h"
+#include "skynet.h"
+#include "globals.h"
+
+class MsgUIRouter : public QObject
+{
+ Q_OBJECT
+public:
+ explicit MsgUIRouter(QObject *parent = 0);
+
+signals:
+
+public slots:
+ void msg_to_peer(QString msg, ChatWindow *sender_window);
+ void msg_from_peer(quint32 from, QString msg);
+
+ void chatwindow_closed(ChatWindow *sender_window);
+ void start_chat(quint32 peer_id);
+
+ void delivery_failed(quint32 to, QString msg);
+ void delivered(quint32 to);
+
+ void scary_cert_message(quint32 peer_id, QString received_cert, QString stored_cert);
+private:
+ QMap<quint32,ChatWindow*> chatwindows;
+
+ void create_chatwindow(quint32 peer_id);
+
+};
+
+#endif // MSGUIROUTER_H
diff --git a/pica-client/nodes.cpp b/pica-client/nodes.cpp
new file mode 100644
index 0000000..eb8bfcc
--- /dev/null
+++ b/pica-client/nodes.cpp
@@ -0,0 +1,73 @@
+#include "nodes.h"
+
+Nodes::Nodes(QString storage)
+{
+ dbconn=QSqlDatabase::addDatabase("QSQLITE");
+ dbconn.setDatabaseName(storage);
+
+ if (!dbconn.open())
+ {
+ lasterr=dbconn.lastError();
+ return;
+ }
+//check if table nodes exists
+}
+
+void Nodes::Add(NodeRecord &n)
+{
+ QSqlQuery query;
+
+ query.prepare("insert into nodes (address, port, last_active, inactive_count) values (:addr, :port, strftime('%s','now'), 0)");
+ query.bindValue(":addr",n.address);
+ query.bindValue(":port",n.port);
+ query.exec();
+
+ lasterr=query.lastError();
+}
+
+QList<Nodes::NodeRecord> Nodes::GetNodes()
+{
+ QList<NodeRecord> L;
+ NodeRecord r;
+ QSqlQuery query;
+
+ query.setForwardOnly(true);
+ query.exec("select address, port from nodes order by inactive_count asc, last_active desc");
+ lasterr=query.lastError();
+
+ if (lasterr.isValid())
+ return L;
+
+ while(query.next())
+ {
+ r.address =query.value(0).toString();
+ r.port =query.value(1).toUInt();
+
+ L<<r;
+ }
+
+ return L;
+}
+
+void Nodes::MakeClean()
+{
+
+}
+
+void Nodes::UpdateStatus(NodeRecord &n, bool alive)
+{
+ QSqlQuery query;
+
+ if (alive)
+ query.prepare("update nodes set last_active = strftime('%s','now'), inactive_count = 0 where address = :addr and port = :port");
+ else
+ query.prepare("update nodes set inactive_count = inactive_count + 1 where address = :addr and port = :port");
+
+ query.bindValue(":addr",n.address);
+ query.bindValue(":port",n.port);
+ query.exec();
+
+ lasterr=query.lastError();
+}
+
+
diff --git a/pica-client/nodes.h b/pica-client/nodes.h
new file mode 100644
index 0000000..a24fed2
--- /dev/null
+++ b/pica-client/nodes.h
@@ -0,0 +1,30 @@
+#ifndef NODES_H
+#define NODES_H
+
+#include <QList>
+#include <QString>
+#include <QtSql>
+#include <QSqlDatabase>
+
+class Nodes
+{
+public:
+ struct NodeRecord
+ {
+ QString address;
+ quint16 port;
+ };
+
+ Nodes(QString storage);
+ void Add(NodeRecord &n);
+ QList<NodeRecord> GetNodes();
+ void MakeClean();//remove old inactive node records
+ void UpdateStatus(NodeRecord &n, bool alive);
+
+private:
+ QSqlDatabase dbconn;
+ QSqlError lasterr;
+
+};
+
+#endif // NODES_H
diff --git a/pica-client/nodethread.cpp b/pica-client/nodethread.cpp
new file mode 100644
index 0000000..1524cbd
--- /dev/null
+++ b/pica-client/nodethread.cpp
@@ -0,0 +1,94 @@
+#include "nodethread.h"
+#include "mainwindow.h"
+
+QMutex NodeThread::finished_mutex;
+
+NodeThread::NodeThread(Nodes::NodeRecord &addr, bool *completed, Accounts::AccountRecord &acc, PICA_conninfo **pica_conn, QMutex *wm) :
+ QThread(0)
+{
+ node_addr = addr;
+ finished_flag = completed;
+ user_account = acc;
+ connection = pica_conn;
+ write_mutex = wm;
+ exit = 0;
+ start();
+}
+
+void NodeThread::run()
+{
+ int ret;
+ exit = 0;
+
+ ret = PICA_new_connection(node_addr.address.toUtf8().constData(),
+ node_addr.port,
+ user_account.CA_file.toUtf8().constData(),
+ user_account.cert_file.toUtf8().constData(),
+ user_account.pkey_file.toUtf8().constData(),
+ AskPassword::ask_password_cb,
+ &ci);
+
+ switch(ret)
+ {
+ case PICA_OK:
+
+ emit NodeStatusChanged(node_addr.address, node_addr.port, true);
+
+ finished_mutex.lock();
+ if (*finished_flag)
+ {
+ PICA_close_connection(ci);
+ exit = 1;
+ }
+ else
+ {
+ *finished_flag = true;
+ *connection = ci;
+ emit ConnectedToNode(node_addr.address, node_addr.port, this);
+ }
+ finished_mutex.unlock();
+
+
+
+ break;
+
+ case PICA_ERRSOCK:
+ case PICA_ERRDNSRESOLVE:
+
+ emit NodeStatusChanged(node_addr.address, node_addr.port, false);
+
+ exit = 1;
+ break;
+
+ case PICA_ERRSSL:
+ exit = 1;
+ break;
+
+ default:
+ exit = 1;
+ }
+
+ if (exit)
+ return;
+
+ do
+ {
+ write_mutex->lock();
+ if (PICA_read(ci,10000) != PICA_OK || PICA_write(ci) != PICA_OK)
+ exit=1;
+ write_mutex->unlock();
+ msleep(300);
+ }
+ while (!exit);
+
+ write_mutex->lock();
+ PICA_close_connection(ci);
+ *connection = NULL;
+ write_mutex->unlock();
+}
+
+void NodeThread::CloseThread()
+{
+ exit = 1;
+}
+
diff --git a/pica-client/nodethread.h b/pica-client/nodethread.h
new file mode 100644
index 0000000..1d17fda
--- /dev/null
+++ b/pica-client/nodethread.h
@@ -0,0 +1,40 @@
+#ifndef NODETHREAD_H
+#define NODETHREAD_H
+
+#include <QThread>
+#include <QMutex>
+#include "nodes.h"
+#include "../PICA_client.h"
+#include "accounts.h"
+
+
+class NodeThread : public QThread
+{
+ Q_OBJECT
+public:
+ explicit NodeThread( Nodes::NodeRecord &addr, bool *completed,Accounts::AccountRecord &acc,struct PICA_conninfo **pica_conn, QMutex *wm);
+ void CloseThread();
+private:
+ void run();
+ static QMutex finished_mutex;
+
+ QMutex *write_mutex;
+
+ bool *finished_flag;
+ Nodes::NodeRecord node_addr;
+ struct PICA_conninfo *ci;
+ Accounts::AccountRecord user_account;
+ struct PICA_conninfo **connection;
+
+ int exit;
+
+
+signals:
+ void NodeStatusChanged(QString addr, quint16 port, bool alive);
+ void ConnectedToNode(QString addr, quint16 port, NodeThread *thread);
+
+public slots:
+
+};
+
+#endif // NODETHREAD_H
diff --git a/pica-client/pica-client.pro b/pica-client/pica-client.pro
new file mode 100644
index 0000000..30c401e
--- /dev/null
+++ b/pica-client/pica-client.pro
@@ -0,0 +1,70 @@
+#-------------------------------------------------
+#
+# Project created by QtCreator 2011-10-22T02:19:02
+#
+#-------------------------------------------------
+
+QT += core gui sql
+
+TARGET = pica-client
+TEMPLATE = app
+
+
+SOURCES += main.cpp\
+ mainwindow.cpp \
+ chatwindow.cpp \
+ contacts.cpp \
+ ../PICA_client.c \
+ ../PICA_msgproc.c \
+ contactlistwidget.cpp \
+ skynet.cpp \
+ nodes.cpp \
+ accountswindow.cpp \
+ accounts.cpp \
+ dialogs/addaccountdialog.cpp \
+ nodethread.cpp \
+ askpassword.cpp \
+ msguirouter.cpp \
+ picasystray.cpp \
+ picaactioncenter.cpp \
+ dialogs/viewcertdialog.cpp \
+ dialogs/forgedcertdialog.cpp
+
+HEADERS += mainwindow.h \
+ chatwindow.h \
+ contacts.h \
+ globals.h \
+ ../PICA_client.h \
+ ../PICA_msgproc.h \
+ contactlistwidget.h \
+ skynet.h \
+ nodes.h \
+ accountswindow.h \
+ accounts.h \
+ dialogs/addaccountdialog.h \
+ nodethread.h \
+ askpassword.h \
+ msguirouter.h \
+ picasystray.h \
+ picaactioncenter.h \
+ dialogs/viewcertdialog.h \
+ dialogs/forgedcertdialog.h \
+ ../PICA_common.h \
+ ../PICA_proto.h
+
+
+DEFINES = PICA_MULTITHREADED
+
+FORMS += mainwindow.ui
+
+
+
+unix: CONFIG += link_pkgconfig
+unix: PKGCONFIG += libssl
+
+unix: CONFIG += link_pkgconfig
+unix: PKGCONFIG += libcrypto
+
+
+
+
diff --git a/pica-client/picaactioncenter.cpp b/pica-client/picaactioncenter.cpp
new file mode 100644
index 0000000..9a811dc
--- /dev/null
+++ b/pica-client/picaactioncenter.cpp
@@ -0,0 +1,37 @@
+#include "picaactioncenter.h"
+#include "globals.h"
+#include "mainwindow.h"
+#include "skynet.h"
+#include <QApplication>
+
+#include <QMessageBox>
+
+PicaActionCenter::PicaActionCenter(QObject *parent) :
+ QObject(parent)
+{
+ exitAct = new QAction(tr("E&xit"),this);
+ exitAct->setStatusTip(tr("Close Pica Pica messenger and exit"));
+ connect(exitAct,SIGNAL(triggered()),this,SLOT(exit()));
+
+ aboutAct = new QAction(tr("A&bout"),this);
+ aboutAct->setStatusTip(tr("About Pica Pica Messenger"));
+ connect(aboutAct,SIGNAL(triggered()),this,SLOT(about()));
+}
+
+void PicaActionCenter::about()
+{
+ QMessageBox mbx;
+ mbx.setWindowIcon(picapica_ico_sit);
+ mbx.setIconPixmap(picapica_ico_sit.pixmap(32));
+ mbx.setText(tr("(c) 2012 Anton Sviridenko"));
+ mbx.exec();
+}
+
+void PicaActionCenter::exit()
+{
+ if (mainwindow)
+ mainwindow->close();
+
+ skynet->Exit();
+ QApplication::instance()->quit();
+}
diff --git a/pica-client/picaactioncenter.h b/pica-client/picaactioncenter.h
new file mode 100644
index 0000000..a406743
--- /dev/null
+++ b/pica-client/picaactioncenter.h
@@ -0,0 +1,28 @@
+#ifndef PICAACTIONCENTER_H
+#define PICAACTIONCENTER_H
+
+#include <QObject>
+#include <QAction>
+
+class PicaActionCenter : public QObject
+{
+ Q_OBJECT
+public:
+ explicit PicaActionCenter(QObject *parent = 0);
+
+ QAction *AboutAct() {return aboutAct;};
+ QAction *ExitAct() {return exitAct;};
+
+private:
+ QAction *aboutAct;
+ QAction *exitAct;
+
+signals:
+
+public slots:
+private slots:
+ void about();
+ void exit();
+};
+
+#endif // PICAACTIONCENTER_H
diff --git a/pica-client/picapica-icon-fly.png b/pica-client/picapica-icon-fly.png
new file mode 100644
index 0000000..b01e972
--- /dev/null
+++ b/pica-client/picapica-icon-fly.png
Binary files differ
diff --git a/pica-client/picapica-icon-sit.png b/pica-client/picapica-icon-sit.png
new file mode 100644
index 0000000..6dfc5c9
--- /dev/null
+++ b/pica-client/picapica-icon-sit.png
Binary files differ
diff --git a/pica-client/picasystray.cpp b/pica-client/picasystray.cpp
new file mode 100644
index 0000000..ab31887
--- /dev/null
+++ b/pica-client/picasystray.cpp
@@ -0,0 +1,39 @@
+#include "picasystray.h"
+#include "globals.h"
+#include "picaactioncenter.h"
+
+PicaSysTray::PicaSysTray(QObject *parent) :
+ QObject(parent)
+{
+ systrayMenu_ = new QMenu();
+
+ systrayMenu_->addAction(action_center->AboutAct());
+ systrayMenu_->addSeparator();
+ systrayMenu_->addAction(action_center->ExitAct());
+
+ systray_ = new QSystemTrayIcon(picapica_ico_sit);
+ systray_->setContextMenu(systrayMenu_);
+ systray_->show();
+
+ connect(systray_, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(systray_activated(QSystemTrayIcon::ActivationReason)));
+ connect(skynet, SIGNAL(BecameSelfAware()), this, SLOT(skynet_became_self_aware()));
+ connect(skynet, SIGNAL(LostSelfAwareness()), this, SLOT(skynet_lost_self_awareness()));
+}
+
+void PicaSysTray::systray_activated(QSystemTrayIcon::ActivationReason r)
+{
+ if (r == QSystemTrayIcon::DoubleClick && mainwindow!=NULL)
+ {
+ mainwindow->showNormal();
+ }
+}
+
+void PicaSysTray::skynet_became_self_aware()
+{
+ systray_->setIcon(picapica_ico_fly);
+}
+
+void PicaSysTray::skynet_lost_self_awareness()
+{
+ systray_->setIcon(picapica_ico_sit);
+}
diff --git a/pica-client/picasystray.h b/pica-client/picasystray.h
new file mode 100644
index 0000000..cf9706d
--- /dev/null
+++ b/pica-client/picasystray.h
@@ -0,0 +1,31 @@
+#ifndef PICASYSTRAY_H
+#define PICASYSTRAY_H
+
+#include <QObject>
+#include <QSystemTrayIcon>
+#include <QMenu>
+#include "skynet.h"
+#include "mainwindow.h"
+#include "globals.h"
+
+class PicaSysTray : public QObject
+{
+ Q_OBJECT
+public:
+ explicit PicaSysTray(QObject *parent = 0);
+
+signals:
+
+public slots:
+
+private:
+ QSystemTrayIcon *systray_;
+ QMenu *systrayMenu_;
+
+private slots:
+ void systray_activated(QSystemTrayIcon::ActivationReason);
+ void skynet_became_self_aware();//debug
+ void skynet_lost_self_awareness();//debug
+};
+
+#endif // PICASYSTRAY_H
diff --git a/pica-client/skynet.cpp b/pica-client/skynet.cpp
new file mode 100644
index 0000000..d522d60
--- /dev/null
+++ b/pica-client/skynet.cpp
@@ -0,0 +1,437 @@
+#include "skynet.h"
+#include "globals.h"
+#include <QMutex>
+//#include "dialogs/viewcertdialog.h"
+#include "contacts.h"
+//#include <QMessageBox> //debug
+//#include "dialogs/forgedcertdialog.h"
+#include <QDebug>
+
+
+SkyNet::SkyNet()
+ : nodes(config_dbname), QObject(0)
+{
+ self_aware = false;
+
+PICA_client_callbacks cbs = {
+ newmsg_cb,
+ msgok_cb,
+ channel_established_cb,
+ channel_failed,
+ accept_cb,
+ notfound_cb,
+ channel_closed_cb,
+ nodelist_cb,
+ peer_cert_verify_cb
+ };
+
+PICA_client_init(&cbs);
+
+nodelink = NULL;
+
+connect(this, SIGNAL(PeerCertificateReceived(quint32,QString,bool*)), this, SLOT(verify_peer_cert(quint32,QString,bool*)),Qt::BlockingQueuedConnection);
+
+}
+
+void SkyNet::nodethread_finished()
+{
+//удалить здесь завершившийся поток
+ for (int i=0;i<threads.count();i++)
+ {
+ if (threads[i]->isFinished())
+ {
+ delete threads.takeAt(i);
+ break;
+ }
+ }
+
+ if (threads.count() == 0)
+ {
+ self_aware = false;
+ emit LostSelfAwareness();
+ }
+}
+
+void SkyNet::nodethread_connected(QString addr, quint16 port, NodeThread *thread)
+{
+ if (self_aware)
+ {
+ emit BecameSelfAware();
+
+ //restore old peer connections, if any
+ QList<quint32> c2c_peer_ids;
+
+ c2c_peer_ids = msgqueues.keys();
+
+ for (int i = 0; i < c2c_peer_ids.size(); i++)
+ {
+ int ret;
+ write_mutex.lock();
+ struct PICA_chaninfo *chan = NULL;
+
+ ret = PICA_create_channel(nodelink, c2c_peer_ids[i], &chan);
+
+ qDebug()<<"restoring channel to "<<c2c_peer_ids[i]<<" ret ="<<ret<<"\n";
+ write_mutex.unlock();
+ }
+
+ //start timer
+ timer_id = startTimer(10000);
+ }
+}
+
+void SkyNet::node_status_changed(QString addr, quint16 port, bool alive)
+{
+ Nodes::NodeRecord nr = {addr, port};
+ nodes.UpdateStatus(nr, alive);
+}
+
+void SkyNet::verify_peer_cert(quint32 peer_id, QString cert_pem, bool *verified)
+{
+// ViewCertDialog vcd;
+// vcd.SetCert(cert_pem);
+// vcd.exec();
+ Contacts cnt(config_dbname, account_id);
+ QString stored_cert;
+
+ if ((stored_cert = cnt.GetContactCert(peer_id)).isEmpty())
+ {
+ cnt.SetContactCert(peer_id, cert_pem);
+
+ // -- move to OpenSSL class
+ QProcess openssl;
+
+ openssl.start("openssl",QStringList()<<"x509"<<"-subject"<<"-nameopt"<<"oneline,-esc_msb,utf8"<<"-noout");
+ if (!openssl.waitForStarted())
+ return;
+
+ openssl.write(cert_pem.toAscii());
+
+ if (!openssl.waitForFinished())
+ return;
+
+ QByteArray result = openssl.readAll();
+ QRegExp rx("CN = [0-9]+\\#([^\\s\\#]+)");
+
+
+ QString name=QString::fromUtf8( result.constData());
+
+ if (name.contains(rx))
+ {
+ name=rx.cap(1);
+ }
+
+ //
+ cnt.SetContactName(peer_id, name);
+ }
+ else
+ {//compare certificates
+ QString strip_pem[4] = {
+ "-----BEGIN CERTIFICATE-----",
+ "-----END CERTIFICATE-----",
+ "-----BEGIN X509 CERTIFICATE-----",
+ "-----END X509 CERTIFICATE-----"
+ };
+ QString stripped_stored_cert = stored_cert;
+ QString stripped_received_cert = cert_pem;
+
+ for (int i = 0; i < 4; i++)
+ {
+ stripped_stored_cert.replace(strip_pem[i], "");
+ stripped_received_cert.replace(strip_pem[i], "");
+ }
+
+ QByteArray stored_DER, received_DER;
+
+ stored_DER = QByteArray::fromBase64(stripped_stored_cert.toAscii().constData());
+ received_DER = QByteArray::fromBase64(stripped_received_cert.toAscii().constData());
+
+ if (stored_DER.size() != received_DER.size() ||
+ memcmp(stored_DER.constData(), received_DER.constData(), stored_DER.size()) != 0)
+ {//put scary message here
+ //QMessageBox mbx;
+ //mbx.setText(tr("Certificate is forged!!!"));
+ //mbx.exec();
+ //ForgedCertDialog fcd(peer_id, cert_pem, stored_cert);
+ //fcd.exec();
+ *verified = false;
+ emit CertificateForged(peer_id, cert_pem, stored_cert);
+ }
+
+
+ }
+}
+
+void SkyNet::timerEvent(QTimerEvent *e)
+{
+if (self_aware)
+ {
+ QList<quint32> c2c_peer_ids = msgqueues.keys();
+
+ write_mutex.lock();
+
+ c2c_peer_ids = filter_existing_chans(c2c_peer_ids);
+
+ for (int i = 0; i < c2c_peer_ids.size(); i++)
+ {
+ int ret;
+
+ struct PICA_chaninfo *chan = NULL;
+
+ ret = PICA_create_channel(nodelink, c2c_peer_ids[i], &chan);
+
+ qDebug()<<"restoring channel to "<<c2c_peer_ids[i]<<" ret ="<<ret<<" in timer event\n";
+
+ }
+ write_mutex.unlock();
+ }
+else
+ {
+ killTimer(e->timerId());
+ Accounts::AccountRecord acc = this->CurrentAccount();
+ Join(acc);
+ }
+}
+
+void SkyNet::Join(Accounts::AccountRecord &accrec)
+{
+ QList<Nodes::NodeRecord> nodelist;
+ skynet_account = accrec;
+ nodelist = nodes.GetNodes();
+
+ if (nodelist.count()==0)
+ {
+ status = QObject::tr("No known Pica Pica nodes");
+ return;
+ }
+
+ for (int i=0;i<nodelist.count() && !self_aware;i++)
+ {//TODO FIXME сделать запуск потоков порциями, а не все сразу
+ threads.append(new NodeThread(nodelist[i],&self_aware,skynet_account,&nodelink, &write_mutex));
+ connect(threads.last(), SIGNAL(finished()),this,SLOT(nodethread_finished()));
+ connect(threads.last(), SIGNAL(NodeStatusChanged(QString,quint16,bool)), this, SLOT(node_status_changed(QString,quint16,bool)));
+ connect(threads.last(), SIGNAL(ConnectedToNode(QString,quint16,NodeThread*)), this, SLOT(nodethread_connected(QString,quint16,NodeThread*)));
+ }
+
+}
+
+void SkyNet::Exit()
+{
+ for (int i = 0; i< threads.count(); i++)
+ threads[i]->CloseThread();
+
+ self_aware = false;
+
+ killTimer(timer_id);
+}
+
+bool SkyNet::isSelfAware()
+{
+ return self_aware;
+}
+
+void SkyNet::SendMessage(quint32 to, QString msg)
+{
+int ret = PICA_OK;
+struct PICA_chaninfo *iptr;
+
+write_mutex.lock();//<<
+
+if ( (iptr = find_active_chan(to)) )
+{
+ //write_mutex.lock();//<<
+ ret = PICA_send_msg(iptr, msg.toUtf8().data(), msg.toUtf8().size());
+ //write_mutex.unlock();//>>
+
+ if (ret != PICA_OK)
+ emit UnableToDeliver(to, msg);
+
+ write_mutex.unlock();//>>
+ return;
+}
+
+
+ if (msgqueues.contains(to))
+ {
+ msgqueues[to].append(msg);
+ }
+ else
+ {
+ struct PICA_chaninfo *chan = NULL;
+
+ ret = PICA_create_channel(nodelink, to, &chan);
+
+ QList<QString> l;
+ l.append(msg);
+ msgqueues[to] = l;
+ }
+
+
+write_mutex.unlock();//>>
+
+ if (ret != PICA_OK)
+ emit UnableToDeliver(to, msg);
+
+}
+
+void SkyNet::flush_msgqueue(quint32 to)
+{
+ struct PICA_chaninfo *chan;
+ int ret;
+
+ if (self_aware && msgqueues.contains(to))
+ {
+ qDebug()<<"flushing "<<to<<" message queue\n";
+
+ if ((chan = find_active_chan(to)))
+ {
+ while( !msgqueues[to].empty() )
+ {
+ ret = PICA_send_msg(chan, msgqueues[to].first().toUtf8().data(), msgqueues[to].first().toUtf8().size());
+
+ if (ret != PICA_OK)
+ {
+ emit UnableToDeliver(to, msgqueues[to].first());
+ break;
+ }
+
+ msgqueues[to].removeFirst();
+
+ }
+
+ if (msgqueues[to].empty())
+ msgqueues.remove(to);
+ }
+ }
+}
+
+struct PICA_chaninfo * SkyNet::find_active_chan(quint32 peer_id)
+{
+if (!nodelink)
+ return NULL;
+
+struct PICA_chaninfo *iptr = nodelink->chan_list_head;
+
+while(iptr)
+ {
+ if (iptr->state == PICA_CHANSTATE_ACTIVE && iptr->peer_id == peer_id)
+ break;
+
+ iptr = iptr->next;
+ }
+
+return iptr;
+}
+
+QList<quint32> SkyNet::filter_existing_chans(QList<quint32> peer_ids)
+{
+ QList<quint32> ret = peer_ids;
+
+ if (!nodelink)
+ return ret;
+
+ struct PICA_chaninfo *iptr = nodelink->chan_list_head;
+
+ while(iptr)
+ {
+ if (peer_ids.contains(iptr->peer_id))
+ ret.removeOne(iptr->peer_id);
+
+ iptr = iptr->next;
+ }
+
+ return ret;
+}
+
+void SkyNet::emit_Delivered(quint32 to)
+{
+emit Delivered(to);
+}
+
+void SkyNet::emit_MessageReceived(quint32 from, QString msg)
+{
+emit MessageReceived(from, msg);
+}
+
+void SkyNet::emit_UnableToDeliver(quint32 to, QString msg)
+{
+emit UnableToDeliver(to, msg);
+}
+
+void SkyNet::emit_PeerCertificateReceived(quint32 peer_id, QString cert_pem, bool *verified)
+{
+ emit PeerCertificateReceived(peer_id, cert_pem, verified);
+}
+
+//callbacks
+
+//all callbacks are executed in separate thread, created in Nodethread instance, REMEMBER THAT !!!
+// write_mutex is locked
+
+void SkyNet::newmsg_cb(unsigned int peer_id, char *msgbuf, unsigned int nb, int type)
+{
+QString msg = QString::fromUtf8(msgbuf, nb);
+
+skynet->emit_MessageReceived(peer_id, msg);
+}
+
+void SkyNet::msgok_cb(unsigned int peer_id)
+{
+skynet->emit_Delivered(peer_id);
+}
+
+void SkyNet::channel_established_cb(unsigned int peer_id)
+{
+skynet->flush_msgqueue(peer_id);
+}
+
+void SkyNet::channel_failed(unsigned int peer_id)
+{
+ qDebug()<<"channel failed ("<<peer_id<<")\n";
+}
+
+int SkyNet::accept_cb(unsigned int caller_id)
+{
+return 1; //implement black list
+}
+
+void SkyNet::notfound_cb(unsigned int callee_id)
+{
+ qDebug()<<"not found ("<<callee_id<<")\n";
+
+ /*
+ if (skynet->msgqueues.contains(callee_id))
+ {
+ while ( ! skynet->msgqueues[callee_id].empty())
+ {
+ skynet->emit_UnableToDeliver(callee_id, skynet->msgqueues[callee_id].last());
+ skynet->msgqueues[callee_id].removeLast();
+ }
+ // msgqueues.remove(callee_id) was forgotten
+ }
+ */
+}
+
+void SkyNet::channel_closed_cb(unsigned int peer_id, int reason)
+{
+ qDebug()<<"channel closed ("<<peer_id<<")\n";
+
+}
+
+void SkyNet::nodelist_cb(int type, void *addr_bin, const char *addr_str, unsigned int port)
+{
+ Nodes::NodeRecord nr = {addr_str, port};
+ skynet->nodes.Add(nr);
+}
+
+int SkyNet::peer_cert_verify_cb(unsigned int peer_id, const char *cert_pem, unsigned int nb)
+{
+ bool verified = true;
+
+ skynet->emit_PeerCertificateReceived(peer_id, QString::fromAscii(cert_pem, nb), &verified);
+
+ if (!verified)
+ return 0;
+
+ return 1;
+}
diff --git a/pica-client/skynet.h b/pica-client/skynet.h
new file mode 100644
index 0000000..21d760c
--- /dev/null
+++ b/pica-client/skynet.h
@@ -0,0 +1,83 @@
+#ifndef SKYNET_H
+#define SKYNET_H
+
+#include "nodes.h"
+#include "nodethread.h"
+#include "../PICA_client.h"
+#include "accounts.h"
+#include <QObject>
+#include <QMap>
+
+
+class SkyNet : public QObject
+{
+ Q_OBJECT
+public:
+ SkyNet();
+ void Join(Accounts::AccountRecord &accrec);
+ bool isSelfAware();
+ QString& Status();
+ void Exit();
+
+ void SendMessage(quint32 to, QString msg);
+ Accounts::AccountRecord CurrentAccount() {return skynet_account;};
+
+signals:
+ void MessageReceived(quint32 from, QString msg);
+ void UnableToDeliver(quint32 to, QString msg);
+ void Delivered(quint32 to);
+ void BecameSelfAware();
+ void LostSelfAwareness();
+ void PeerCertificateReceived(quint32 peer_id, QString cert_pem, bool *verified);
+ void CertificateForged(quint32 peer_id, QString received_cert, QString stored_cert);
+
+private:
+ Nodes nodes;
+ QList<NodeThread*> threads;
+ bool self_aware;
+ QString status;
+ struct PICA_conninfo *nodelink;
+ Accounts::AccountRecord skynet_account;
+ QMutex write_mutex;
+ QMap<quint32, QList<QString> > msgqueues;
+ int timer_id;
+
+ void timerEvent(QTimerEvent * e);
+
+ void flush_msgqueue(quint32 to);
+ struct PICA_chaninfo *find_active_chan(quint32 peer_id);
+ QList<quint32> filter_existing_chans(QList<quint32> peer_ids);
+
+ void emit_MessageReceived(quint32 from, QString msg);
+ void emit_UnableToDeliver(quint32 to, QString msg);
+ void emit_Delivered(quint32 to);
+ void emit_PeerCertificateReceived(quint32 peer_id, QString cert_pem, bool *verified);
+
+ //получение сообщения.
+ static void newmsg_cb(unsigned int peer_id,char* msgbuf,unsigned int nb,int type);
+ //получение подтверждения о доставке сообщения
+ static void msgok_cb(unsigned int peer_id);
+ //создание канала с собеседником
+ static void channel_established_cb(unsigned int peer_id);
+ //создать канал не удалось
+ static void channel_failed(unsigned int peer_id);
+ //входящий запрос на создание канала от пользователя с номером caller_id
+ static int accept_cb(unsigned int caller_id);
+ //запрошенный пользователь не найден, в оффлайне или отказался от общения
+ static void notfound_cb(unsigned int callee_id);
+
+ static void channel_closed_cb(unsigned int peer_id, int reason);
+
+ static void nodelist_cb(int type, void *addr_bin, const char *addr_str, unsigned int port);
+
+ static int peer_cert_verify_cb(unsigned int peer_id, const char *cert_pem, unsigned int nb);
+
+private slots:
+ void nodethread_finished();
+ void nodethread_connected(QString addr, quint16 port, NodeThread *thread);
+ void node_status_changed(QString addr,quint16 port,bool alive);
+ void verify_peer_cert(quint32 peer_id, QString cert_pem, bool *verified);
+
+};
+
+#endif // SKYNET_H