Merge branch 'master' of github.com:vitalyster/libtransport
This commit is contained in:
commit
aafea80a3c
22 changed files with 242 additions and 89 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
*.pb.cc
|
||||
*.pb.h
|
|
@ -1,6 +1,10 @@
|
|||
cmake_minimum_required(VERSION 2.6)
|
||||
project(libtransport)
|
||||
|
||||
if(NOT LIB_INSTALL_DIR)
|
||||
set(LIB_INSTALL_DIR "lib")
|
||||
endif()
|
||||
|
||||
set(CMAKE_MODULE_PATH "cmake_modules")
|
||||
|
||||
set(cppunit_DIR "${CMAKE_SOURCE_DIR}/cmake_modules")
|
||||
|
|
11
ChangeLog
11
ChangeLog
|
@ -1,4 +1,4 @@
|
|||
Version 2.0.0-beta2 (2012-XX-XX):
|
||||
Version 2.0.0-beta2 (2012-03-28):
|
||||
General:
|
||||
* Fixed bug when Roster Item Exchange and subscribe stanzas were sent
|
||||
repeatedly.
|
||||
|
@ -6,13 +6,19 @@ Version 2.0.0-beta2 (2012-XX-XX):
|
|||
* Fixed username_mask setting.
|
||||
* Added new fields into statistics (backends_crashed, messages related
|
||||
stats).
|
||||
* Chatstates are now not counted as incoming messages in stats.
|
||||
* Log4cxx is now optional dependency. Without Log4cxx, Spectrum 2 logs
|
||||
to standard output.
|
||||
* Fixed crash when Log4cxx configuration file didn't exist.
|
||||
* Admin can now see "Admin" contact in server-mode.
|
||||
|
||||
libpurple:
|
||||
* Added support for MUC for prpl-jabber protocol.
|
||||
* Added initial support for MUC for prpl-jabber protocol.
|
||||
|
||||
LibCommuni IRC backend:
|
||||
* Fixed sending/receiving UTF8 messages.
|
||||
* Using the [registration] auto_register=1 config option, users don't
|
||||
have to register manually when connecting IRC network.
|
||||
|
||||
Skype:
|
||||
* Memory usage statistic now includes the Skype client.
|
||||
|
@ -21,6 +27,7 @@ Version 2.0.0-beta2 (2012-XX-XX):
|
|||
* Skype backend includes also Skype client memory usage into the account.
|
||||
* Working buddies adding/removing.
|
||||
* Information about missed call is now forwarded to XMPP user.
|
||||
* Fixed bug when Skype client instance wasn't killed by backend.
|
||||
|
||||
Version 2.0.0-beta (2012-02-28):
|
||||
General:
|
||||
|
|
|
@ -2,10 +2,13 @@
|
|||
#include <IrcCommand>
|
||||
#include <IrcMessage>
|
||||
|
||||
#define FROM_UTF8(WHAT) QString::fromUtf8((WHAT).c_str(), (WHAT).size())
|
||||
#define TO_UTF8(WHAT) std::string((WHAT).toUtf8().data(), (WHAT).toUtf8().size())
|
||||
|
||||
IRCNetworkPlugin::IRCNetworkPlugin(Config *config, Swift::QtEventLoop *loop, const std::string &host, int port) {
|
||||
this->config = config;
|
||||
m_socket = new QTcpSocket();
|
||||
m_socket->connectToHost(QString::fromStdString(host), port);
|
||||
m_socket->connectToHost(FROM_UTF8(host), port);
|
||||
connect(m_socket, SIGNAL(readyRead()), this, SLOT(readData()));
|
||||
}
|
||||
|
||||
|
@ -28,8 +31,8 @@ void IRCNetworkPlugin::handleLoginRequest(const std::string &user, const std::st
|
|||
if (CONFIG_BOOL(config, "service.server_mode")) {
|
||||
MyIrcSession *session = new MyIrcSession(user, this);
|
||||
std::string h = user.substr(0, user.find("@"));
|
||||
session->setNickName(QString::fromStdString(h.substr(0, h.find("%"))));
|
||||
session->setHost(QString::fromStdString(h.substr(h.find("%") + 1)));
|
||||
session->setNickName(FROM_UTF8(h.substr(0, h.find("%"))));
|
||||
session->setHost(FROM_UTF8(h.substr(h.find("%") + 1)));
|
||||
session->setPort(6667);
|
||||
session->open();
|
||||
std::cout << "CONNECTING IRC NETWORK " << h.substr(h.find("%") + 1) << "\n";
|
||||
|
@ -72,7 +75,7 @@ void IRCNetworkPlugin::handleMessageSendRequest(const std::string &user, const s
|
|||
}
|
||||
}
|
||||
std::cout << "MESSAGE " << u << " " << r << "\n";
|
||||
m_sessions[u]->sendCommand(IrcCommand::createMessage(QString::fromStdString(r), QString::fromStdString(message)));
|
||||
m_sessions[u]->sendCommand(IrcCommand::createMessage(FROM_UTF8(r), FROM_UTF8(message)));
|
||||
std::cout << "SENT\n";
|
||||
}
|
||||
|
||||
|
@ -89,8 +92,8 @@ void IRCNetworkPlugin::handleJoinRoomRequest(const std::string &user, const std:
|
|||
if (room.find("@") != std::string::npos) {
|
||||
// suffix is %irc.freenode.net to let MyIrcSession return #room%irc.freenode.net
|
||||
MyIrcSession *session = new MyIrcSession(user, this, room.substr(room.find("@")));
|
||||
session->setNickName(QString::fromStdString(nickname));
|
||||
session->setHost(QString::fromStdString(room.substr(room.find("@") + 1)));
|
||||
session->setNickName(FROM_UTF8(nickname));
|
||||
session->setHost(FROM_UTF8(room.substr(room.find("@") + 1)));
|
||||
session->setPort(6667);
|
||||
session->open();
|
||||
std::cout << "CONNECTING IRC NETWORK " << room.substr(room.find("@") + 1) << "\n";
|
||||
|
@ -103,10 +106,10 @@ void IRCNetworkPlugin::handleJoinRoomRequest(const std::string &user, const std:
|
|||
}
|
||||
std::cout << "JOINING " << r << "\n";
|
||||
m_sessions[u]->addAutoJoinChannel(r);
|
||||
m_sessions[u]->sendCommand(IrcCommand::createJoin(QString::fromStdString(r), QString::fromStdString(password)));
|
||||
m_sessions[u]->sendCommand(IrcCommand::createJoin(FROM_UTF8(r), FROM_UTF8(password)));
|
||||
m_sessions[u]->rooms += 1;
|
||||
// update nickname, because we have nickname per session, no nickname per room.
|
||||
handleRoomNicknameChanged(user, r, m_sessions[u]->nickName().toStdString());
|
||||
handleRoomNicknameChanged(user, r, TO_UTF8(m_sessions[u]->nickName()));
|
||||
}
|
||||
|
||||
void IRCNetworkPlugin::handleLeaveRoomRequest(const std::string &user, const std::string &room) {
|
||||
|
@ -120,7 +123,7 @@ void IRCNetworkPlugin::handleLeaveRoomRequest(const std::string &user, const std
|
|||
if (m_sessions[u] == NULL)
|
||||
return;
|
||||
|
||||
m_sessions[u]->sendCommand(IrcCommand::createPart(QString::fromStdString(r)));
|
||||
m_sessions[u]->sendCommand(IrcCommand::createPart(FROM_UTF8(r)));
|
||||
m_sessions[u]->removeAutoJoinChannel(r);
|
||||
m_sessions[u]->rooms -= 1;
|
||||
|
||||
|
|
|
@ -15,6 +15,9 @@
|
|||
#include <IrcCommand>
|
||||
#include <IrcMessage>
|
||||
|
||||
#define FROM_UTF8(WHAT) QString::fromUtf8((WHAT).c_str(), (WHAT).size())
|
||||
#define TO_UTF8(WHAT) std::string((WHAT).toUtf8().data(), (WHAT).toUtf8().size())
|
||||
|
||||
#include "transport/logging.h"
|
||||
|
||||
DEFINE_LOGGER(logger, "IRCSession");
|
||||
|
@ -38,13 +41,13 @@ void MyIrcSession::on_connected() {
|
|||
}
|
||||
|
||||
for(std::list<std::string>::const_iterator it = m_autoJoin.begin(); it != m_autoJoin.end(); it++) {
|
||||
sendCommand(IrcCommand::createJoin(QString::fromStdString(*it)));
|
||||
sendCommand(IrcCommand::createJoin(FROM_UTF8(*it)));
|
||||
}
|
||||
|
||||
if (getIdentify().find(" ") != std::string::npos) {
|
||||
std::string to = getIdentify().substr(0, getIdentify().find(" "));
|
||||
std::string what = getIdentify().substr(getIdentify().find(" ") + 1);
|
||||
sendCommand(IrcCommand::createMessage(QString::fromStdString(to), QString::fromStdString(what)));
|
||||
sendCommand(IrcCommand::createMessage(FROM_UTF8(to), FROM_UTF8(what)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -67,30 +70,30 @@ bool MyIrcSession::correctNickname(std::string &nickname) {
|
|||
void MyIrcSession::on_joined(IrcMessage *message) {
|
||||
IrcJoinMessage *m = (IrcJoinMessage *) message;
|
||||
bool flags = 0;
|
||||
std::string nickname = m->sender().name().toStdString();
|
||||
std::string nickname = TO_UTF8(m->sender().name());
|
||||
flags = correctNickname(nickname);
|
||||
np->handleParticipantChanged(user, nickname, m->channel().toStdString() + suffix, (int) flags, pbnetwork::STATUS_ONLINE);
|
||||
LOG4CXX_INFO(logger, user << ": Joined " << m->parameters()[0].toStdString());
|
||||
np->handleParticipantChanged(user, nickname, TO_UTF8(m->channel()), (int) flags, pbnetwork::STATUS_ONLINE);
|
||||
LOG4CXX_INFO(logger, user << ": Joined " << TO_UTF8(m->parameters()[0]));
|
||||
}
|
||||
|
||||
|
||||
void MyIrcSession::on_parted(IrcMessage *message) {
|
||||
IrcPartMessage *m = (IrcPartMessage *) message;
|
||||
bool flags = 0;
|
||||
std::string nickname = m->sender().name().toStdString();
|
||||
std::string nickname = TO_UTF8(m->sender().name());
|
||||
flags = correctNickname(nickname);
|
||||
LOG4CXX_INFO(logger, user << ": " << nickname << " parted " << m->channel().toStdString() + suffix);
|
||||
np->handleParticipantChanged(user, nickname, m->channel().toStdString() + suffix,(int) flags, pbnetwork::STATUS_NONE, m->reason().toStdString());
|
||||
LOG4CXX_INFO(logger, user << ": " << nickname << " parted " << TO_UTF8(m->channel()) + suffix);
|
||||
np->handleParticipantChanged(user, nickname, TO_UTF8(m->channel()) + suffix,(int) flags, pbnetwork::STATUS_NONE, TO_UTF8(m->reason()));
|
||||
}
|
||||
|
||||
void MyIrcSession::on_quit(IrcMessage *message) {
|
||||
IrcQuitMessage *m = (IrcQuitMessage *) message;
|
||||
for(std::list<std::string>::const_iterator it = m_autoJoin.begin(); it != m_autoJoin.end(); it++) {
|
||||
bool flags = 0;
|
||||
std::string nickname = m->sender().name().toStdString();
|
||||
std::string nickname = TO_UTF8(m->sender().name());
|
||||
flags = correctNickname(nickname);
|
||||
LOG4CXX_INFO(logger, user << ": " << nickname << " quit " << (*it) + suffix);
|
||||
np->handleParticipantChanged(user, nickname, (*it) + suffix,(int) flags, pbnetwork::STATUS_NONE, m->reason().toStdString());
|
||||
np->handleParticipantChanged(user, nickname, (*it) + suffix,(int) flags, pbnetwork::STATUS_NONE, TO_UTF8(m->reason()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,10 +101,10 @@ void MyIrcSession::on_nickChanged(IrcMessage *message) {
|
|||
IrcNickMessage *m = (IrcNickMessage *) message;
|
||||
|
||||
for(std::list<std::string>::const_iterator it = m_autoJoin.begin(); it != m_autoJoin.end(); it++) {
|
||||
std::string nickname = m->sender().name().toStdString();
|
||||
std::string nickname = TO_UTF8(m->sender().name());
|
||||
bool flags = m_modes[(*it) + nickname];
|
||||
LOG4CXX_INFO(logger, user << ": " << nickname << " changed nickname to " << m->nick().toStdString());
|
||||
np->handleParticipantChanged(user, nickname, (*it) + suffix,(int) flags, pbnetwork::STATUS_ONLINE, "", m->nick().toStdString());
|
||||
LOG4CXX_INFO(logger, user << ": " << nickname << " changed nickname to " << TO_UTF8(m->nick()));
|
||||
np->handleParticipantChanged(user, nickname, (*it) + suffix,(int) flags, pbnetwork::STATUS_ONLINE, "", TO_UTF8(m->nick()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,8 +112,8 @@ void MyIrcSession::on_modeChanged(IrcMessage *message) {
|
|||
IrcModeMessage *m = (IrcModeMessage *) message;
|
||||
|
||||
// mode changed: "#testik" "HanzZ" "+o" "hanzz_k"
|
||||
std::string nickname = m->argument().toStdString();
|
||||
std::string mode = m->mode().toStdString();
|
||||
std::string nickname = TO_UTF8(m->argument());
|
||||
std::string mode = TO_UTF8(m->mode());
|
||||
if (nickname.empty())
|
||||
return;
|
||||
LOG4CXX_INFO(logger, user << ": " << nickname << " changed mode to " << mode);
|
||||
|
@ -130,29 +133,29 @@ void MyIrcSession::on_topicChanged(IrcMessage *message) {
|
|||
IrcTopicMessage *m = (IrcTopicMessage *) message;
|
||||
|
||||
bool flags = 0;
|
||||
std::string nickname = m->sender().name().toStdString();
|
||||
std::string nickname = TO_UTF8(m->sender().name());
|
||||
flags = correctNickname(nickname);
|
||||
|
||||
LOG4CXX_INFO(logger, user << ": " << nickname << " topic changed to " << m->topic().toStdString());
|
||||
np->handleSubject(user, m->channel().toStdString() + suffix, m->topic().toStdString(), nickname);
|
||||
LOG4CXX_INFO(logger, user << ": " << nickname << " topic changed to " << TO_UTF8(m->topic()));
|
||||
np->handleSubject(user, TO_UTF8(m->channel()) + suffix, TO_UTF8(m->topic()), nickname);
|
||||
}
|
||||
|
||||
void MyIrcSession::on_messageReceived(IrcMessage *message) {
|
||||
IrcPrivateMessage *m = (IrcPrivateMessage *) message;
|
||||
|
||||
std::string target = m->target().toStdString();
|
||||
std::string target = TO_UTF8(m->target());
|
||||
LOG4CXX_INFO(logger, user << ": Message from " << target);
|
||||
if (target.find("#") == 0) {
|
||||
bool flags = 0;
|
||||
std::string nickname = m->sender().name().toStdString();
|
||||
std::string nickname = TO_UTF8(m->sender().name());
|
||||
flags = correctNickname(nickname);
|
||||
np->handleMessage(user, target + suffix, m->message().toStdString(), nickname);
|
||||
np->handleMessage(user, target + suffix, TO_UTF8(m->message()), nickname);
|
||||
}
|
||||
else {
|
||||
bool flags = 0;
|
||||
std::string nickname = m->sender().name().toStdString();
|
||||
std::string nickname = TO_UTF8(m->sender().name());
|
||||
flags = correctNickname(nickname);
|
||||
np->handleMessage(user, nickname, m->message().toStdString());
|
||||
np->handleMessage(user, nickname, TO_UTF8(m->message()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -163,10 +166,10 @@ void MyIrcSession::on_numericMessageReceived(IrcMessage *message) {
|
|||
IrcNumericMessage *m = (IrcNumericMessage *) message;
|
||||
switch (m->code()) {
|
||||
case 332:
|
||||
m_topicData = m->parameters().value(2).toStdString();
|
||||
m_topicData = TO_UTF8(m->parameters().value(2));
|
||||
break;
|
||||
case 333:
|
||||
np->handleSubject(user, m->parameters().value(1).toStdString() + suffix, m_topicData, m->parameters().value(2).toStdString());
|
||||
np->handleSubject(user, TO_UTF8(m->parameters().value(1)) + suffix, m_topicData, TO_UTF8(m->parameters().value(2)));
|
||||
break;
|
||||
case 353:
|
||||
channel = m->parameters().value(2);
|
||||
|
@ -174,10 +177,10 @@ void MyIrcSession::on_numericMessageReceived(IrcMessage *message) {
|
|||
|
||||
for (int i = 0; i < members.size(); i++) {
|
||||
bool flags = 0;
|
||||
std::string nickname = members.at(i).toStdString();
|
||||
std::string nickname = TO_UTF8(members.at(i));
|
||||
flags = correctNickname(nickname);
|
||||
m_modes[channel.toStdString() + nickname] = flags;
|
||||
np->handleParticipantChanged(user, nickname, channel.toStdString() + suffix,(int) flags, pbnetwork::STATUS_ONLINE);
|
||||
m_modes[TO_UTF8(channel) + nickname] = flags;
|
||||
np->handleParticipantChanged(user, nickname, TO_UTF8(channel) + suffix,(int) flags, pbnetwork::STATUS_ONLINE);
|
||||
}
|
||||
break;
|
||||
case 432:
|
||||
|
@ -193,7 +196,7 @@ void MyIrcSession::on_numericMessageReceived(IrcMessage *message) {
|
|||
}
|
||||
|
||||
void MyIrcSession::onMessageReceived(IrcMessage *message) {
|
||||
LOG4CXX_INFO(logger, user << ": " << message->toString().toStdString());
|
||||
LOG4CXX_INFO(logger, user << ": " << TO_UTF8(message->toString()));
|
||||
switch (message->type()) {
|
||||
case IrcMessage::Join:
|
||||
on_joined(message);
|
||||
|
|
|
@ -3,13 +3,16 @@
|
|||
#include <IrcCommand>
|
||||
#include <IrcMessage>
|
||||
|
||||
#define FROM_UTF8(WHAT) QString::fromUtf8((WHAT).c_str(), (WHAT).size())
|
||||
#define TO_UTF8(WHAT) std::string((WHAT).toUtf8().data(), (WHAT).toUtf8().size())
|
||||
|
||||
DEFINE_LOGGER(logger, "SingleIRCNetworkPlugin");
|
||||
|
||||
SingleIRCNetworkPlugin::SingleIRCNetworkPlugin(Config *config, Swift::QtEventLoop *loop, const std::string &host, int port) {
|
||||
this->config = config;
|
||||
m_server = config->getUnregistered().find("service.irc_server")->second;
|
||||
m_socket = new QTcpSocket();
|
||||
m_socket->connectToHost(QString::fromStdString(host), port);
|
||||
m_socket->connectToHost(FROM_UTF8(host), port);
|
||||
connect(m_socket, SIGNAL(readyRead()), this, SLOT(readData()));
|
||||
|
||||
if (config->getUnregistered().find("service.irc_identify") != config->getUnregistered().end()) {
|
||||
|
@ -44,16 +47,18 @@ void SingleIRCNetworkPlugin::handleLoginRequest(const std::string &user, const s
|
|||
LOG4CXX_INFO(logger, user << ": Connecting " << m_server << " as " << legacyName);
|
||||
|
||||
MyIrcSession *session = new MyIrcSession(user, this);
|
||||
session->setUserName(QString::fromStdString(legacyName));
|
||||
session->setNickName(QString::fromStdString(legacyName));
|
||||
session->setRealName(QString::fromStdString(legacyName));
|
||||
session->setHost(QString::fromStdString(m_server));
|
||||
session->setUserName(FROM_UTF8(legacyName));
|
||||
session->setNickName(FROM_UTF8(legacyName));
|
||||
session->setRealName(FROM_UTF8(legacyName));
|
||||
session->setHost(FROM_UTF8(m_server));
|
||||
session->setPort(6667);
|
||||
|
||||
std::string identify = m_identify;
|
||||
boost::replace_all(identify, "$password", password);
|
||||
boost::replace_all(identify, "$name", legacyName);
|
||||
session->setIdentify(identify);
|
||||
if (!password.empty()) {
|
||||
std::string identify = m_identify;
|
||||
boost::replace_all(identify, "$password", password);
|
||||
boost::replace_all(identify, "$name", legacyName);
|
||||
session->setIdentify(identify);
|
||||
}
|
||||
|
||||
session->open();
|
||||
|
||||
|
@ -88,10 +93,10 @@ void SingleIRCNetworkPlugin::handleMessageSendRequest(const std::string &user, c
|
|||
}
|
||||
|
||||
LOG4CXX_INFO(logger, user << ": Forwarding message to " << r);
|
||||
m_sessions[user]->sendCommand(IrcCommand::createMessage(QString::fromStdString(r), QString::fromStdString(message)));
|
||||
m_sessions[user]->sendCommand(IrcCommand::createMessage(FROM_UTF8(r), FROM_UTF8(message)));
|
||||
|
||||
if (r.find("#") == 0) {
|
||||
handleMessage(user, legacyName, message, m_sessions[user]->nickName().toStdString());
|
||||
handleMessage(user, legacyName, message, TO_UTF8(m_sessions[user]->nickName()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,11 +108,11 @@ void SingleIRCNetworkPlugin::handleJoinRoomRequest(const std::string &user, cons
|
|||
|
||||
LOG4CXX_INFO(logger, user << ": Joining " << room);
|
||||
m_sessions[user]->addAutoJoinChannel(room);
|
||||
m_sessions[user]->sendCommand(IrcCommand::createJoin(QString::fromStdString(room), QString::fromStdString(password)));
|
||||
m_sessions[user]->sendCommand(IrcCommand::createJoin(FROM_UTF8(room), FROM_UTF8(password)));
|
||||
m_sessions[user]->rooms += 1;
|
||||
|
||||
// update nickname, because we have nickname per session, no nickname per room.
|
||||
handleRoomNicknameChanged(user, room, m_sessions[user]->userName().toStdString());
|
||||
handleRoomNicknameChanged(user, room, TO_UTF8(m_sessions[user]->userName()));
|
||||
}
|
||||
|
||||
void SingleIRCNetworkPlugin::handleLeaveRoomRequest(const std::string &user, const std::string &room) {
|
||||
|
@ -120,7 +125,7 @@ void SingleIRCNetworkPlugin::handleLeaveRoomRequest(const std::string &user, con
|
|||
}
|
||||
|
||||
LOG4CXX_INFO(logger, user << ": Leaving " << room);
|
||||
m_sessions[u]->sendCommand(IrcCommand::createPart(QString::fromStdString(r)));
|
||||
m_sessions[u]->sendCommand(IrcCommand::createPart(FROM_UTF8(r)));
|
||||
m_sessions[u]->removeAutoJoinChannel(r);
|
||||
m_sessions[u]->rooms -= 1;
|
||||
}
|
||||
|
|
|
@ -366,7 +366,7 @@ bool Skype::createDBusProxy() {
|
|||
LOG4CXX_INFO(logger, m_username << ":" << error->message);
|
||||
|
||||
if (m_counter == 15) {
|
||||
LOG4CXX_ERROR(logger, "Logging out, proxy couldn't be created");
|
||||
LOG4CXX_ERROR(logger, "Logging out, proxy couldn't be created: " << error->message);
|
||||
np->handleDisconnected(m_user, pbnetwork::CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, error->message);
|
||||
logout();
|
||||
g_error_free(error);
|
||||
|
@ -444,7 +444,8 @@ void Skype::login() {
|
|||
gchar* argv[6] = {"skype", "--disable-cleanlooks", "--pipelogin", "--dbpath", db, 0};
|
||||
|
||||
int fd;
|
||||
g_spawn_async_with_pipes(NULL,
|
||||
GError *error = NULL;
|
||||
bool spawned = g_spawn_async_with_pipes(NULL,
|
||||
argv,
|
||||
NULL /*envp*/,
|
||||
G_SPAWN_SEARCH_PATH,
|
||||
|
@ -454,9 +455,16 @@ void Skype::login() {
|
|||
&fd,
|
||||
NULL,
|
||||
&fd_output,
|
||||
NULL /*error*/);
|
||||
&error);
|
||||
|
||||
if (!spawned) {
|
||||
LOG4CXX_ERROR(logger, "Error spawning the Skype instance: " << error->message)
|
||||
np->handleDisconnected(m_user, pbnetwork::CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, "Error spawning the Skype instance.");
|
||||
return;
|
||||
}
|
||||
|
||||
std::string login_data = std::string(m_username + " " + m_password + "\n");
|
||||
LOG4CXX_INFO(logger, m_username << ": Login data=" << login_data);
|
||||
LOG4CXX_INFO(logger, m_username << ": Login data=" << m_username);
|
||||
write(fd, login_data.c_str(), login_data.size());
|
||||
close(fd);
|
||||
|
||||
|
@ -469,12 +477,12 @@ void Skype::login() {
|
|||
|
||||
if (m_connection == NULL)
|
||||
{
|
||||
LOG4CXX_INFO(logger, "Creating DBus connection.");
|
||||
LOG4CXX_INFO(logger, "Creating DBUS connection.");
|
||||
GError *error = NULL;
|
||||
m_connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error);
|
||||
if (m_connection == NULL && error != NULL)
|
||||
{
|
||||
LOG4CXX_INFO(logger, m_username << ": DBUS Error: " << error->message);
|
||||
LOG4CXX_INFO(logger, m_username << ": Creating DBUS Connection error: " << error->message);
|
||||
g_error_free(error);
|
||||
return;
|
||||
}
|
||||
|
@ -616,8 +624,12 @@ void Skype::logout() {
|
|||
send_command("SET USERSTATUS OFFLINE");
|
||||
sleep(2);
|
||||
g_object_unref(m_proxy);
|
||||
LOG4CXX_INFO(logger, m_username << ": Killing Skype instance");
|
||||
LOG4CXX_INFO(logger, m_username << ": Terminating Skype instance (SIGTERM)");
|
||||
kill((int) m_pid, SIGTERM);
|
||||
// Give skype a chance
|
||||
sleep(2);
|
||||
LOG4CXX_INFO(logger, m_username << ": Killing Skype instance (SIGKILL)");
|
||||
kill((int) m_pid, SIGKILL);
|
||||
m_pid = 0;
|
||||
}
|
||||
}
|
||||
|
|
27
include/Swiften/Parser/PayloadParsers/MUCPayloadParser.cpp
Normal file
27
include/Swiften/Parser/PayloadParsers/MUCPayloadParser.cpp
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright (c) 2010 Kevin Smith
|
||||
* Licensed under the GNU General Public License v3.
|
||||
* See Documentation/Licenses/GPLv3.txt for more information.
|
||||
*/
|
||||
|
||||
#include <Swiften/Parser/PayloadParsers/MUCPayloadParser.h>
|
||||
|
||||
#include <boost/lexical_cast.hpp>
|
||||
|
||||
#include <Swiften/Parser/PayloadParserFactoryCollection.h>
|
||||
#include <Swiften/Parser/PayloadParserFactory.h>
|
||||
#include <Swiften/Base/foreach.h>
|
||||
#include <Swiften/Elements/MUCOccupant.h>
|
||||
#include <Swiften/Parser/Tree/TreeReparser.h>
|
||||
|
||||
namespace Swift {
|
||||
|
||||
void MUCPayloadParser::handleTree(ParserElement::ref root) {
|
||||
foreach (ParserElement::ref child, root->getAllChildren()) {
|
||||
if (child->getName() == "password" && child->getNamespace() == root->getNamespace()) {
|
||||
getPayloadInternal()->setPassword(child->getText());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
22
include/Swiften/Parser/PayloadParsers/MUCPayloadParser.h
Normal file
22
include/Swiften/Parser/PayloadParsers/MUCPayloadParser.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright (c) 2010 Kevin Smith
|
||||
* Licensed under the GNU General Public License v3.
|
||||
* See Documentation/Licenses/GPLv3.txt for more information.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include <Swiften/Elements/MUCPayload.h>
|
||||
#include <Swiften/Parser/GenericPayloadTreeParser.h>
|
||||
#include <Swiften/Parser/PayloadParsers/MUCItemParser.h>
|
||||
|
||||
namespace Swift {
|
||||
class PayloadParserFactoryCollection;
|
||||
class MUCPayloadParser : public GenericPayloadTreeParser<MUCPayload> {
|
||||
public:
|
||||
MUCPayloadParser(){}
|
||||
virtual void handleTree(ParserElement::ref root);
|
||||
};
|
||||
}
|
|
@ -3,12 +3,12 @@ if (PROTOBUF_FOUND)
|
|||
set (PROTOBUF_PROTOC_EXECUTABLE protoc)
|
||||
endif()
|
||||
ADD_CUSTOM_COMMAND(
|
||||
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/protocol.pb.cc ${CMAKE_CURRENT_BINARY_DIR}/protocol.pb.h
|
||||
COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} --cpp_out ${CMAKE_CURRENT_BINARY_DIR} --proto_path ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}/protocol.proto
|
||||
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/protocol.pb.cc ${CMAKE_CURRENT_SOURCE_DIR}/protocol.pb.h
|
||||
COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} --cpp_out ${CMAKE_CURRENT_SOURCE_DIR} --proto_path ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/protocol.proto
|
||||
COMMENT "Running C++ protocol buffer compiler on protocol.proto"
|
||||
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/protocol.proto
|
||||
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/protocol.proto
|
||||
)
|
||||
ADD_CUSTOM_TARGET(pb DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/protocol.pb.cc)
|
||||
ADD_CUSTOM_TARGET(pb DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/protocol.pb.cc)
|
||||
endif()
|
||||
|
||||
FILE(GLOB HEADERS *.h protocol.h)
|
||||
|
|
|
@ -51,10 +51,20 @@ class LocalBuddy : public Buddy {
|
|||
}
|
||||
|
||||
std::string getIconHash() { return m_iconHash; }
|
||||
void setIconHash(const std::string &iconHash) { m_iconHash = iconHash; }
|
||||
void setIconHash(const std::string &iconHash) {
|
||||
bool changed = m_iconHash != iconHash;
|
||||
m_iconHash = iconHash;
|
||||
if (changed)
|
||||
getRosterManager()->storeBuddy(this);
|
||||
}
|
||||
|
||||
std::vector<std::string> getGroups() { return m_groups; }
|
||||
void setGroups(const std::vector<std::string> &groups) { m_groups = groups; }
|
||||
void setGroups(const std::vector<std::string> &groups) {
|
||||
bool changed = m_groups.size() != groups.size();
|
||||
m_groups = groups;
|
||||
if (changed)
|
||||
getRosterManager()->storeBuddy(this);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_name;
|
||||
|
|
|
@ -2,10 +2,10 @@ cmake_minimum_required(VERSION 2.6)
|
|||
FILE(GLOB SRC *.cpp *.h)
|
||||
FILE(GLOB HEADERS ../include/transport/*.h)
|
||||
|
||||
set(EXTRA_SOURCES ${CMAKE_CURRENT_BINARY_DIR}/../../src/memoryusage.cpp)
|
||||
set(EXTRA_SOURCES ${EXTRA_SOURCES} ${CMAKE_CURRENT_BINARY_DIR}/../../src/logging.cpp)
|
||||
set(EXTRA_SOURCES ${EXTRA_SOURCES} ${CMAKE_CURRENT_BINARY_DIR}/../../src/config.cpp)
|
||||
set(EXTRA_SOURCES ${EXTRA_SOURCES} ${CMAKE_CURRENT_BINARY_DIR}/../../include/transport/protocol.pb.cc)
|
||||
set(EXTRA_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/../../src/memoryusage.cpp)
|
||||
set(EXTRA_SOURCES ${EXTRA_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/../../src/logging.cpp)
|
||||
set(EXTRA_SOURCES ${EXTRA_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/../../src/config.cpp)
|
||||
set(EXTRA_SOURCES ${EXTRA_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/../../include/transport/protocol.pb.cc)
|
||||
|
||||
if (NOT WIN32)
|
||||
ADD_LIBRARY(transport-plugin SHARED ${HEADERS} ${SRC} ${PROTOBUF_SRC} ${PROTOBUF_HDRS} ${EXTRA_SOURCES})
|
||||
|
@ -15,7 +15,7 @@ else()
|
|||
ADD_DEPENDENCIES(transport-plugin libprotobuf)
|
||||
endif()
|
||||
ADD_DEPENDENCIES(transport-plugin pb)
|
||||
SET_SOURCE_FILES_PROPERTIES(${CMAKE_CURRENT_BINARY_DIR}/../../include/transport/protocol.pb.cc PROPERTIES GENERATED 1)
|
||||
SET_SOURCE_FILES_PROPERTIES(${CMAKE_CURRENT_SOURCE_DIR}/../../include/transport/protocol.pb.cc PROPERTIES GENERATED 1)
|
||||
|
||||
if (CMAKE_COMPILER_IS_GNUCXX)
|
||||
ADD_DEFINITIONS(-fPIC)
|
||||
|
@ -31,7 +31,7 @@ SET_TARGET_PROPERTIES(transport-plugin PROPERTIES
|
|||
VERSION ${TRANSPORT_VERSION} SOVERSION ${TRANSPORT_VERSION}
|
||||
)
|
||||
|
||||
INSTALL(TARGETS transport-plugin LIBRARY DESTINATION lib ARCHIVE DESTINATION lib COMPONENT libraries)
|
||||
INSTALL(TARGETS transport-plugin LIBRARY DESTINATION ${LIB_INSTALL_DIR} ARCHIVE DESTINATION ${LIB_INSTALL_DIR} COMPONENT libraries)
|
||||
|
||||
#CONFIGURE_FILE(transport.pc.in "${CMAKE_CURRENT_BINARY_DIR}/transport.pc")
|
||||
#INSTALL(FILES "${CMAKE_CURRENT_BINARY_DIR}/transport.pc" DESTINATION lib/pkgconfig)
|
||||
#CONFIGURE_FILE(transport.pc.in "${CMAKE_CURRENT_SOURCE_DIR}/transport.pc")
|
||||
#INSTALL(FILES "${CMAKE_CURRENT_SOURCE_DIR}/transport.pc" DESTINATION lib/pkgconfig)
|
||||
|
|
|
@ -7,7 +7,9 @@ if (PROTOBUF_FOUND)
|
|||
COMMENT "Running Python protocol buffer compiler on protocol.proto"
|
||||
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/../../include/transport/protocol.proto
|
||||
)
|
||||
ADD_CUSTOM_TARGET(pb-python DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/protocol_pb2.py)
|
||||
ADD_CUSTOM_TARGET(pb-python ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/protocol_pb2.py)
|
||||
endif()
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -13,7 +13,8 @@ admin_password=test
|
|||
#cert=server.pfx #patch to PKCS#12 certificate
|
||||
#cert_password=test #password to that certificate if any
|
||||
users_per_backend=10
|
||||
backend=../..//backends/libpurple/spectrum2_libpurple_backend
|
||||
#backend=../..//backends/libpurple/spectrum2_libpurple_backend
|
||||
backend=../../backends/template/spectrum2_template_backend
|
||||
protocol=prpl-xmpp
|
||||
#protocol=prpl-msn
|
||||
#protocol=any
|
||||
|
|
BIN
spectrum/src/server.p12
Normal file
BIN
spectrum/src/server.p12
Normal file
Binary file not shown.
|
@ -46,8 +46,8 @@ endif()
|
|||
|
||||
if (NOT CMAKE_COMPILER_IS_GNUCXX)
|
||||
include_directories("${CMAKE_SOURCE_DIR}/msvc-deps/protobuf/libprotobuf")
|
||||
TARGET_LINK_LIBRARIES(transport transport-plugin ${PQXX_LIBRARY} ${PQ_LIBRARY} ${SQLITE3_LIBRARIES} ${MYSQL_LIBRARIES} ${SWIFTEN_LIBRARY} ${PROTOBUF_LIBRARIES} ${LOG4CXX_LIBRARIES})
|
||||
else ()
|
||||
TARGET_LINK_LIBRARIES(transport transport-plugin sqlite3 libprotobuf ${PQXX_LIBRARY} ${PQ_LIBRARY} ${MYSQL_LIBRARIES} ${SWIFTEN_LIBRARY} ${LOG4CXX_LIBRARIES})
|
||||
else (WIN32)
|
||||
TARGET_LINK_LIBRARIES(transport transport-plugin ${PQXX_LIBRARY} ${PQ_LIBRARY} ${SQLITE3_LIBRARIES} ${MYSQL_LIBRARIES} ${SWIFTEN_LIBRARY} ${PROTOBUF_LIBRARIES} ${LOG4CXX_LIBRARIES} ${POPT_LIBRARY})
|
||||
endif()
|
||||
|
||||
|
@ -55,7 +55,7 @@ SET_TARGET_PROPERTIES(transport PROPERTIES
|
|||
VERSION ${TRANSPORT_VERSION} SOVERSION ${TRANSPORT_VERSION}
|
||||
)
|
||||
|
||||
INSTALL(TARGETS transport LIBRARY DESTINATION lib ARCHIVE DESTINATION lib COMPONENT libraries)
|
||||
INSTALL(TARGETS transport LIBRARY DESTINATION ${LIB_INSTALL_DIR} ARCHIVE DESTINATION ${LIB_INSTALL_DIR} COMPONENT libraries)
|
||||
|
||||
#CONFIGURE_FILE(transport.pc.in "${CMAKE_CURRENT_BINARY_DIR}/transport.pc")
|
||||
#INSTALL(FILES "${CMAKE_CURRENT_BINARY_DIR}/transport.pc" DESTINATION lib/pkgconfig)
|
||||
|
|
|
@ -148,7 +148,6 @@ void Buddy::handleBuddyChanged() {
|
|||
if (presence) {
|
||||
m_rosterManager->getUser()->getComponent()->getStanzaChannel()->sendPresence(presence);
|
||||
}
|
||||
m_rosterManager->handleBuddyChanged(this);
|
||||
}
|
||||
|
||||
void Buddy::handleVCardReceived(const std::string &id, Swift::VCard::ref vcard) {
|
||||
|
|
|
@ -93,6 +93,7 @@ bool Config::load(std::istream &ifs, boost::program_options::options_description
|
|||
("registration.instructions", value<std::string>()->default_value("Enter your legacy network username and password."), "Instructions showed to user in registration form")
|
||||
("registration.username_label", value<std::string>()->default_value("Legacy network username:"), "Label for username field")
|
||||
("registration.username_mask", value<std::string>()->default_value(""), "Username mask")
|
||||
("registration.auto_register", value<bool>()->default_value(false), "Register new user automatically when the presence arrives.")
|
||||
("registration.encoding", value<std::string>()->default_value("utf8"), "Default encoding in registration form")
|
||||
("registration.require_local_account", value<bool>()->default_value(false), "True if users have to have a local account to register to this transport from remote servers.")
|
||||
("registration.local_username_label", value<std::string>()->default_value("Local username:"), "Label for local usernme field")
|
||||
|
|
|
@ -163,9 +163,14 @@ static unsigned long exec_(std::string path, const char *host, const char *port,
|
|||
if ( pid == 0 ) {
|
||||
setsid();
|
||||
// child process
|
||||
exit(execv(argv[0], argv));
|
||||
errno = 0;
|
||||
int ret = execv(argv[0], argv);
|
||||
if (ret == -1) {
|
||||
exit(errno);
|
||||
}
|
||||
exit(0);
|
||||
} else if ( pid < 0 ) {
|
||||
// fork failed
|
||||
LOG4CXX_ERROR(logger, "Fork failed");
|
||||
}
|
||||
free(p);
|
||||
|
||||
|
@ -258,12 +263,29 @@ NetworkPluginServer::NetworkPluginServer(Component *component, Config *config, U
|
|||
|
||||
LOG4CXX_INFO(logger, "Listening on host " << CONFIG_STRING(m_config, "service.backend_host") << " port " << CONFIG_STRING(m_config, "service.backend_port"));
|
||||
|
||||
unsigned long pid = exec_(CONFIG_STRING(m_config, "service.backend"), CONFIG_STRING(m_config, "service.backend_host").c_str(), CONFIG_STRING(m_config, "service.backend_port").c_str(), m_config->getConfigFile().c_str());
|
||||
LOG4CXX_INFO(logger, "Backend should now connect to Spectrum2 instance. Spectrum2 won't accept any connection before backend connects");
|
||||
|
||||
#ifndef _WIN32
|
||||
// wait if the backend process will still be alive after 1 second
|
||||
sleep(1);
|
||||
pid_t result;
|
||||
int status;
|
||||
result = waitpid(-1, &status, WNOHANG);
|
||||
if (result != 0) {
|
||||
if (WIFEXITED(status)) {
|
||||
if (WEXITSTATUS(status) != 0) {
|
||||
LOG4CXX_ERROR(logger, "Backend can not be started, exit_code=" << WEXITSTATUS(status) << ", possible error: " << strerror(WEXITSTATUS(status)));
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOG4CXX_ERROR(logger, "Backend can not be started");
|
||||
}
|
||||
}
|
||||
|
||||
signal(SIGCHLD, SigCatcher);
|
||||
#endif
|
||||
|
||||
exec_(CONFIG_STRING(m_config, "service.backend"), CONFIG_STRING(m_config, "service.backend_host").c_str(), CONFIG_STRING(m_config, "service.backend_port").c_str(), m_config->getConfigFile().c_str());
|
||||
LOG4CXX_INFO(logger, "Backend should now connect to Spectrum2 instance. Spectrum2 won't accept any connection before backend connects");
|
||||
}
|
||||
|
||||
NetworkPluginServer::~NetworkPluginServer() {
|
||||
|
|
|
@ -152,9 +152,6 @@ void RosterManager::sendBuddySubscribePresence(Buddy *buddy) {
|
|||
}
|
||||
|
||||
void RosterManager::handleBuddyChanged(Buddy *buddy) {
|
||||
if (m_rosterStorage) {
|
||||
m_rosterStorage->storeBuddy(buddy);
|
||||
}
|
||||
}
|
||||
|
||||
void RosterManager::setBuddyCallback(Buddy *buddy) {
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
#include "Swiften/Parser/PayloadParsers/GatewayPayloadParser.h"
|
||||
#include "Swiften/Serializer/PayloadSerializers/GatewayPayloadSerializer.h"
|
||||
#include "Swiften/Serializer/PayloadSerializers/SpectrumErrorSerializer.h"
|
||||
#include "Swiften/Parser/PayloadParsers/MUCPayloadParser.h"
|
||||
#include "transport/BlockParser.h"
|
||||
#include "transport/BlockSerializer.h"
|
||||
#include "Swiften/Parser/PayloadParsers/InvisibleParser.h"
|
||||
|
@ -107,6 +108,7 @@ Component::Component(Swift::EventLoop *loop, Swift::NetworkFactories *factories,
|
|||
m_server->addPayloadParserFactory(new GenericPayloadParserFactory<Swift::InvisibleParser>("invisible", "urn:xmpp:invisible:0"));
|
||||
m_server->addPayloadParserFactory(new GenericPayloadParserFactory<Swift::StatsParser>("query", "http://jabber.org/protocol/stats"));
|
||||
m_server->addPayloadParserFactory(new GenericPayloadParserFactory<Swift::GatewayPayloadParser>("query", "jabber:iq:gateway"));
|
||||
m_server->addPayloadParserFactory(new GenericPayloadParserFactory<Swift::MUCPayloadParser>("x", "http://jabber.org/protocol/muc"));
|
||||
|
||||
m_server->addPayloadSerializer(new Swift::AttentionSerializer());
|
||||
m_server->addPayloadSerializer(new Swift::XHTMLIMSerializer());
|
||||
|
@ -135,6 +137,7 @@ Component::Component(Swift::EventLoop *loop, Swift::NetworkFactories *factories,
|
|||
m_component->addPayloadParserFactory(new GenericPayloadParserFactory<Swift::InvisibleParser>("invisible", "urn:xmpp:invisible:0"));
|
||||
m_component->addPayloadParserFactory(new GenericPayloadParserFactory<Swift::StatsParser>("query", "http://jabber.org/protocol/stats"));
|
||||
m_component->addPayloadParserFactory(new GenericPayloadParserFactory<Swift::GatewayPayloadParser>("query", "jabber:iq:gateway"));
|
||||
m_component->addPayloadParserFactory(new GenericPayloadParserFactory<Swift::MUCPayloadParser>("x", "http://jabber.org/protocol/muc"));
|
||||
|
||||
m_component->addPayloadSerializer(new Swift::AttentionSerializer());
|
||||
m_component->addPayloadSerializer(new Swift::XHTMLIMSerializer());
|
||||
|
|
|
@ -214,6 +214,34 @@ void UserManager::handlePresence(Swift::Presence::ref presence) {
|
|||
res.password = m_userRegistry->getUserPassword(userkey);
|
||||
}
|
||||
|
||||
// We allow auto_register feature in gateway-mode. This allows IRC user to register
|
||||
// the transport just by joining the room.
|
||||
if (!m_component->inServerMode()) {
|
||||
if (!registered && CONFIG_BOOL(m_component->getConfig(), "registration.auto_register")) {
|
||||
res.password = "";
|
||||
res.jid = userkey;
|
||||
|
||||
bool isMUC = presence->getPayload<Swift::MUCPayload>() != NULL || *presence->getTo().getNode().c_str() == '#';
|
||||
if (isMUC) {
|
||||
res.uin = presence->getTo().getResource();
|
||||
}
|
||||
else {
|
||||
res.uin = presence->getFrom().toString();
|
||||
}
|
||||
LOG4CXX_INFO(logger, "Auto-registering user " << userkey << " with uin=" << res.uin);
|
||||
|
||||
if (m_storageBackend) {
|
||||
// store user and getUser again to get user ID.
|
||||
m_storageBackend->setUser(res);
|
||||
registered = m_storageBackend->getUser(userkey, res);
|
||||
}
|
||||
else {
|
||||
registered = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Unregistered users are not able to login
|
||||
if (!registered) {
|
||||
LOG4CXX_WARN(logger, "Unregistered user " << userkey << " tried to login");
|
||||
|
@ -282,7 +310,12 @@ void UserManager::handleMessageReceived(Swift::Message::ref message) {
|
|||
}
|
||||
|
||||
user->getConversationManager()->handleMessageReceived(message);
|
||||
messageToBackendSent();
|
||||
|
||||
// Do not count chatstate notification...
|
||||
boost::shared_ptr<Swift::ChatState> statePayload = message->getPayload<Swift::ChatState>();
|
||||
if (!statePayload) {
|
||||
messageToBackendSent();
|
||||
}
|
||||
}
|
||||
|
||||
void UserManager::handleGeneralPresenceReceived(Swift::Presence::ref presence) {
|
||||
|
|
Loading…
Add table
Reference in a new issue