Removed dbus support.

Added unix signals support.
This commit is contained in:
codestation
2015-03-14 23:55:56 -04:30
parent c162b91b00
commit da6ad50759
17 changed files with 242 additions and 122 deletions

View File

@@ -29,37 +29,32 @@ official CMA and also offer some features missing in the original one.
## Headless version ## Headless version
There is a qcma_cli binary that doesn't need a X session running (useful for servers). There is a qcma_cli binary that doesn't need a X session running (useful for servers).
The daemon provides minimal interaction via dbus. Here are some usage examples: The daemon provides minimal interaction via unix signals. Here are some usage examples:
#### Send a signal to refresh the database #### Send a signal to refresh the database
``` ```
dbus-send --session --type=method_call --dest=org.qcma.HeadlessManager /HeadlessManager org.qcma.HeadlessManager.refreshDatabase kill -HUP $(pgrep qcma_cli)
``` ```
#### Send a signal to stop the daemon #### Send a signal to stop the daemon
``` ```
dbus-send --session --type=method_call --dest=org.qcma.HeadlessManager /HeadlessManager org.qcma.HeadlessManager.stop kill $(pgrep qcma_cli)
```
#### Receive confirmation when the database finished updating
```
dbus-monitor "type='signal',sender='org.qcma.HeadlessManager',member='databaseUpdated'"
``` ```
## Dependencies ## Dependencies
* [Qt 4.x or 5.x](http://qt-project.org/) * [Qt 4.x or 5.x](http://www.qt.io/)
* [VitaMTP fork](https://github.com/codestation/VitaMTP) * [VitaMTP fork](https://github.com/codestation/vitamtp)
* [FFmpeg](http://www.ffmpeg.org/) * [FFmpeg](http://www.ffmpeg.org/)
* [libnotify](http://library.gnome.org/devel/notification-spec/) * [libnotify](http://library.gnome.org/devel/notification-spec/)
* [libappindicator](https://launchpad.net/libappindicator) * [libappindicator (optional)](https://launchpad.net/libappindicator)
* [KNotifications (optional)](https://projects.kde.org/projects/frameworks/knotifications)
#### Where do I get the source code? #### Where do I get the source code?
Check the GitHub repo here: https://github.com/codestation/qcma Check the GitHub repo here: https://github.com/codestation/qcma
@@ -71,10 +66,7 @@ Contact me on [GitHub](https://github.com/codestation/)
[Yifan Lu](https://github.com/yifanlu/vitamtp/) - for the vitamtp library and [Yifan Lu](https://github.com/yifanlu/vitamtp/) - for the vitamtp library and
the reference implementation of OpenCMA. the reference implementation of OpenCMA.
[Xian Nox] (https://github.com/xiannox) - for the Wiki and various contributions. Other contributors: check [here](https://github.com/codestation/qcma/graphs/contributors)
[173210] (https://github.com/173210) - japanese translations.
#### License #### License
GPL v3: since some parts of QCMA are based on the reference implementation of GPLv3
OpenCMA.

2
qcma.6
View File

@@ -8,7 +8,7 @@ qcma \- Content manager for the PlayStation Vita
\fBqcma\fR is a cross-platform application to provide an Open Source implementation \fBqcma\fR is a cross-platform application to provide an Open Source implementation
of the original Content Manager Assistant that comes with the PS Vita. of the original Content Manager Assistant that comes with the PS Vita.
.TP .TP
QCMA will let you backup your games, songs, photos & videos via USB or wireless. Qcma will let you backup your games, songs, photos & videos via USB or wireless.
.SH "SEE ALSO" .SH "SEE ALSO"
.I /usr/share/doc/qcma/README.md .I /usr/share/doc/qcma/README.md

View File

@@ -12,7 +12,7 @@ android {
SUBDIRS = qcma_gui.pro SUBDIRS = qcma_gui.pro
} }
# Compile the headless binary only on Linux because it depends on dbus # Compile the headless binary only on Linux
unix:!macx:!android { unix:!macx:!android {
SUBDIRS += qcma_cli.pro SUBDIRS += qcma_cli.pro
# The appindicator and kde extensions are linux only too # The appindicator and kde extensions are linux only too

View File

@@ -8,23 +8,26 @@ qcma-cli \- Content manager for the PlayStation Vita
.B \-\-verbose .B \-\-verbose
print detailed output print detailed output
.TP
.B \-\-with-debug
print very verbose output (including hexdump transfers)
.SH DESCRIPTION .SH DESCRIPTION
\fBqcma_cli\fR is a cross-platform application to provide an Open Source implementation \fBqcma_cli\fR is a cross-platform application to provide an Open Source implementation
of the original Content Manager Assistant that comes with the PS Vita. of the original Content Manager Assistant that comes with the PS Vita.
.TP .TP
QCMA will let you backup your games, songs, photos & videos via USB or wireless. Qcma will let you backup your games, songs, photos & videos via USB or wireless.
.SH COMMANDS .SH SIGNALS
You can control qcma_cli over DBus: You can control \fBqcma_cli\fR using signals, i.e. using the \fBkill\fR command to send a signal to the process.
.TP .TP
Send a signal to refresh the database: .B SIGHUP
.BR Refreshes the database.
dbus-send \-\-session \-\-type=method_call \-\-dest=org.qcma.HeadlessManager /HeadlessManager org.qcma.HeadlessManager.refreshDatabase
.TP .TP
Send a signal to stop the daemon: .B SIGTERM
.BR Shuts down the process but waits until the current event is finished.
dbus-send \-\-session \-\-type=method_call \-\-dest=org.qcma.HeadlessManager /HeadlessManager org.qcma.HeadlessManager.stop
.SH "SEE ALSO" .SH "SEE ALSO"
.I /usr/share/doc/qcma/README.md .I /usr/share/doc/qcma/README.md

View File

@@ -1,7 +1,5 @@
include(qcma_common.pri) include(qcma_common.pri)
unix:!macx:QT += dbus
TARGET = qcma_cli TARGET = qcma_cli
SOURCES += \ SOURCES += \
@@ -19,13 +17,3 @@ unix:!macx {
INSTALLS += target INSTALLS += target
} }
# Create the introspection XML
QT5_SUFFIX {
system(qdbuscpp2xml-qt5 -M -s src/cli/headlessmanager.h -o org.qcma.HeadlessManager.xml)
} else {
system(qdbuscpp2xml -M -s src/cli/headlessmanager.h -o org.qcma.HeadlessManager.xml)
}
# Create the helper class
DBUS_ADAPTORS = org.qcma.HeadlessManager.xml

View File

@@ -59,15 +59,3 @@ unix:!macx {
INSTALLS += target desktop icon64 INSTALLS += target desktop icon64
} }
unix:!macx {
QT += dbus
# Create the introspection XML
QT5_SUFFIX {
system(qdbuscpp2xml-qt5 -M -s src/gui/mainwidget.h -o org.qcma.ClientManager.xml)
} else {
system(qdbuscpp2xml -M -s src/gui/mainwidget.h -o org.qcma.ClientManager.xml)
}
# Create the helper class
DBUS_ADAPTORS = org.qcma.ClientManager.xml
}

View File

@@ -22,20 +22,29 @@
#include "sqlitedb.h" #include "sqlitedb.h"
#include "qlistdb.h" #include "qlistdb.h"
#include "headlessmanager.h" #include "headlessmanager.h"
#include "headlessmanager_adaptor.h"
#include <QCoreApplication> #include <QCoreApplication>
#include <QSettings> #include <QSettings>
#include <sys/socket.h>
#include <unistd.h>
#include <vitamtp.h> #include <vitamtp.h>
int HeadlessManager::sighup_fd[2];
int HeadlessManager::sigterm_fd[2];
HeadlessManager::HeadlessManager(QObject *obj_parent) : HeadlessManager::HeadlessManager(QObject *obj_parent) :
QObject(obj_parent), dbus_conn(QDBusConnection::sessionBus()) QObject(obj_parent)
{ {
new HeadlessManagerAdaptor(this); if (::socketpair(AF_UNIX, SOCK_STREAM, 0, sighup_fd))
QDBusConnection dbus = QDBusConnection::sessionBus(); qFatal("Couldn't create HUP socketpair");
// expose qcma over dbus so the database update can be triggered
dbus.registerObject("/HeadlessManager", this); if (::socketpair(AF_UNIX, SOCK_STREAM, 0, sigterm_fd))
dbus.registerService("org.qcma.HeadlessManager"); qFatal("Couldn't create TERM socketpair");
sn_hup = new QSocketNotifier(sighup_fd[1], QSocketNotifier::Read, this);
connect(sn_hup, SIGNAL(activated(int)), this, SLOT(handleSigHup()));
sn_term = new QSocketNotifier(sigterm_fd[1], QSocketNotifier::Read, this);
connect(sn_term, SIGNAL(activated(int)), this, SLOT(handleSigTerm()));
} }
HeadlessManager::~HeadlessManager() HeadlessManager::~HeadlessManager()
@@ -71,7 +80,7 @@ void HeadlessManager::start()
// initializing database for the first use // initializing database for the first use
refreshDatabase(); refreshDatabase();
// send a signal over dbus of the connected peers knows when the update is finished // send a signal when the update is finished
connect(m_db, SIGNAL(updated(int)), this, SIGNAL(databaseUpdated(int))); connect(m_db, SIGNAL(updated(int)), this, SIGNAL(databaseUpdated(int)));
thread_count = 0; thread_count = 0;
@@ -134,3 +143,37 @@ void HeadlessManager::threadStopped()
} }
mutex.unlock(); mutex.unlock();
} }
void HeadlessManager::hupSignalHandler(int)
{
char a = 1;
::write(sighup_fd[0], &a, sizeof(a));
}
void HeadlessManager::termSignalHandler(int)
{
char a = 1;
::write(sigterm_fd[0], &a, sizeof(a));
}
void HeadlessManager::handleSigTerm()
{
sn_term->setEnabled(false);
char tmp;
::read(sigterm_fd[1], &tmp, sizeof(tmp));
stop();
sn_term->setEnabled(true);
}
void HeadlessManager::handleSigHup()
{
sn_hup->setEnabled(false);
char tmp;
::read(sighup_fd[1], &tmp, sizeof(tmp));
refreshDatabase();
sn_hup->setEnabled(true);
}

View File

@@ -24,12 +24,11 @@
#include <QObject> #include <QObject>
#include <QThread> #include <QThread>
#include <QDBusConnection> #include <QSocketNotifier>
class HeadlessManager : public QObject class HeadlessManager : public QObject
{ {
Q_OBJECT Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.qcma.HeadlessManager")
public: public:
explicit HeadlessManager(QObject *parent = 0); explicit HeadlessManager(QObject *parent = 0);
@@ -37,6 +36,10 @@ public:
void start(); void start();
// unix signal handlers
static void hupSignalHandler(int);
static void termSignalHandler(int);
private: private:
int thread_count; int thread_count;
QMutex mutex; QMutex mutex;
@@ -45,16 +48,26 @@ private:
QThread *usb_thread; QThread *usb_thread;
QThread *wireless_thread; QThread *wireless_thread;
QDBusConnection dbus_conn;
// signal handling
static int sighup_fd[2];
static int sigterm_fd[2];
QSocketNotifier *sn_hup;
QSocketNotifier *sn_term;
signals: signals:
void stopped(); void stopped();
Q_SCRIPTABLE void databaseUpdated(int count); void databaseUpdated(int count);
public slots: public slots:
void refreshDatabase(); void refreshDatabase();
void stop(); void stop();
// Qt signal handlers
void handleSigHup();
void handleSigTerm();
private slots: private slots:
void threadStopped(); void threadStopped();
}; };

View File

@@ -47,20 +47,47 @@ static void noMessageOutput(QtMsgType type, const char *msg)
Q_UNUSED(msg); Q_UNUSED(msg);
} }
static bool setup_handlers()
{
struct sigaction hup, term;
hup.sa_handler = HeadlessManager::hupSignalHandler;
sigemptyset(&hup.sa_mask);
hup.sa_flags = 0;
hup.sa_flags |= SA_RESTART;
if (sigaction(SIGHUP, &hup, NULL) != 0) {
qCritical("SIGHUP signal handle failed");
return false;
}
term.sa_handler = HeadlessManager::termSignalHandler;
sigemptyset(&term.sa_mask);
term.sa_flags |= SA_RESTART;
if (sigaction(SIGTERM, &term, NULL) != 0) {
qCritical("SIGTERM signal handle failed");
return false;
}
return true;
}
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
if(SingleCoreApplication::sendMessage(QObject::tr("A instance of QCMA is already running"))) { if(SingleCoreApplication::sendMessage(QObject::tr("An instance of Qcma is already running"))) {
return 0; return 0;
} }
SingleCoreApplication app(argc, argv); SingleCoreApplication app(argc, argv);
#ifndef Q_OS_WIN32
// FIXME: libmtp sends SIGPIPE if a socket write fails crashing the whole app // FIXME: libmtp sends SIGPIPE if a socket write fails crashing the whole app
// the proper fix is to libmtp to handle the cancel properly or ignoring // the proper fix is to libmtp to handle the cancel properly or ignoring
// SIGPIPE on the socket // SIGPIPE on the socket
signal(SIGPIPE, SIG_IGN); signal(SIGPIPE, SIG_IGN);
#endif
if(!setup_handlers())
return 1;
if(app.arguments().contains("--with-debug")) { if(app.arguments().contains("--with-debug")) {
VitaMTP_Set_Logging(VitaMTP_DEBUG); VitaMTP_Set_Logging(VitaMTP_DEBUG);
@@ -79,7 +106,7 @@ int main(int argc, char *argv[])
QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8")); QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8"));
#endif #endif
qDebug("Starting QCMA %s", QCMA_VER); QTextStream(stdout) << "Starting Qcma " << QCMA_VER << endl;
QTranslator translator; QTranslator translator;
QString locale = QLocale().system().name(); QString locale = QLocale().system().name();

View File

@@ -26,9 +26,29 @@
#include <vitamtp.h> #include <vitamtp.h>
#ifdef Q_OS_UNIX
#include <sys/socket.h>
#include <unistd.h>
int ClientManager::sighup_fd[2];
int ClientManager::sigterm_fd[2];
#endif
ClientManager::ClientManager(Database *db, QObject *obj_parent) : ClientManager::ClientManager(Database *db, QObject *obj_parent) :
QObject(obj_parent), m_db(db) QObject(obj_parent), m_db(db)
{ {
#ifdef Q_OS_UNIX
if (::socketpair(AF_UNIX, SOCK_STREAM, 0, sighup_fd))
qFatal("Couldn't create HUP socketpair");
if (::socketpair(AF_UNIX, SOCK_STREAM, 0, sigterm_fd))
qFatal("Couldn't create TERM socketpair");
sn_hup = new QSocketNotifier(sighup_fd[1], QSocketNotifier::Read, this);
connect(sn_hup, SIGNAL(activated(int)), this, SLOT(handleSigHup()));
sn_term = new QSocketNotifier(sigterm_fd[1], QSocketNotifier::Read, this);
connect(sn_term, SIGNAL(activated(int)), this, SLOT(handleSigTerm()));
#endif
} }
ClientManager::~ClientManager() ClientManager::~ClientManager()
@@ -150,3 +170,39 @@ void ClientManager::threadStopped()
} }
mutex.unlock(); mutex.unlock();
} }
#ifdef Q_OS_UNIX
void ClientManager::hupSignalHandler(int)
{
char a = 1;
::write(sighup_fd[0], &a, sizeof(a));
}
void ClientManager::termSignalHandler(int)
{
char a = 1;
::write(sigterm_fd[0], &a, sizeof(a));
}
void ClientManager::handleSigTerm()
{
sn_term->setEnabled(false);
char tmp;
::read(sigterm_fd[1], &tmp, sizeof(tmp));
stop();
sn_term->setEnabled(true);
}
void ClientManager::handleSigHup()
{
sn_hup->setEnabled(false);
char tmp;
::read(sighup_fd[1], &tmp, sizeof(tmp));
refreshDatabase();
sn_hup->setEnabled(true);
}
#endif

View File

@@ -27,6 +27,10 @@
#include <QObject> #include <QObject>
#include <QThread> #include <QThread>
#ifdef Q_OS_UNIX
#include <QSocketNotifier>
#endif
class ClientManager : public QObject class ClientManager : public QObject
{ {
Q_OBJECT Q_OBJECT
@@ -37,6 +41,12 @@ public:
void start(); void start();
void stop(); void stop();
#ifdef Q_OS_UNIX
// unix signal handlers
static void hupSignalHandler(int);
static void termSignalHandler(int);
#endif
private: private:
int thread_count; int thread_count;
QMutex mutex; QMutex mutex;
@@ -49,6 +59,15 @@ private:
QThread *usb_thread; QThread *usb_thread;
QThread *wireless_thread; QThread *wireless_thread;
#ifdef Q_OS_UNIX
// signal handling
static int sighup_fd[2];
static int sigterm_fd[2];
QSocketNotifier *sn_hup;
QSocketNotifier *sn_term;
#endif
signals: signals:
void updated(int); void updated(int);
void stopped(); void stopped();
@@ -60,6 +79,12 @@ signals:
public slots: public slots:
void refreshDatabase(); void refreshDatabase();
#ifdef Q_OS_UNIX
// Qt signal handlers
void handleSigHup();
void handleSigTerm();
#endif
private slots: private slots:
void threadStopped(); void threadStopped();
void databaseUpdated(int count); void databaseUpdated(int count);

View File

@@ -25,6 +25,7 @@
#include <QLibraryInfo> #include <QLibraryInfo>
#include <QLocale> #include <QLocale>
#include <QTextCodec> #include <QTextCodec>
#include <QTextStream>
#include <QThread> #include <QThread>
#include <QTranslator> #include <QTranslator>
@@ -45,9 +46,36 @@ static void noMessageOutput(QtMsgType type, const char *msg)
Q_UNUSED(msg); Q_UNUSED(msg);
} }
static bool setup_handlers()
{
struct sigaction hup, term;
hup.sa_handler = ClientManager::hupSignalHandler;
sigemptyset(&hup.sa_mask);
hup.sa_flags = 0;
hup.sa_flags |= SA_RESTART;
if (sigaction(SIGHUP, &hup, NULL) != 0) {
qCritical("SIGHUP signal handle failed");
return false;
}
term.sa_handler = ClientManager::termSignalHandler;
sigemptyset(&term.sa_mask);
term.sa_flags |= SA_RESTART;
if (sigaction(SIGTERM, &term, NULL) != 0) {
qCritical("SIGTERM signal handle failed");
return false;
}
return true;
}
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
if(SingleApplication::sendMessage(QObject::tr("A instance of QCMA is already running"))) { if(SingleApplication::sendMessage(QObject::tr("An instance of Qcma is already running"))) {
return 0; return 0;
} }
@@ -60,6 +88,8 @@ int main(int argc, char *argv[])
signal(SIGPIPE, SIG_IGN); signal(SIGPIPE, SIG_IGN);
#endif #endif
setup_handlers();
if(app.arguments().contains("--with-debug")) { if(app.arguments().contains("--with-debug")) {
VitaMTP_Set_Logging(VitaMTP_DEBUG); VitaMTP_Set_Logging(VitaMTP_DEBUG);
} else if(app.arguments().contains("--verbose")) { } else if(app.arguments().contains("--verbose")) {
@@ -77,7 +107,7 @@ int main(int argc, char *argv[])
QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8")); QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8"));
#endif #endif
qDebug("Starting QCMA %s", QCMA_VER); QTextStream(stdout) << "Starting Qcma " << QCMA_VER << endl;
QTranslator translator; QTranslator translator;
QString locale = QLocale().system().name(); QString locale = QLocale().system().name();

View File

@@ -21,10 +21,6 @@
#include "cmaclient.h" #include "cmaclient.h"
#include "cmautils.h" #include "cmautils.h"
#ifdef Q_OS_LINUX
#include "clientmanager_adaptor.h"
#endif
#include "qlistdb.h" #include "qlistdb.h"
#include "sqlitedb.h" #include "sqlitedb.h"
@@ -34,6 +30,7 @@
#include <QDebug> #include <QDebug>
#include <QDir> #include <QDir>
#include <QGridLayout> #include <QGridLayout>
#include <QLibrary>
#include <QMessageBox> #include <QMessageBox>
#include <QSettings> #include <QSettings>
#include <QTimer> #include <QTimer>
@@ -44,24 +41,11 @@ const QStringList MainWidget::path_list = QStringList() << "photoPath" << "music
bool sleptOnce = false; bool sleptOnce = false;
#ifdef Q_OS_LINUX
MainWidget::MainWidget(QWidget *obj_parent) : MainWidget::MainWidget(QWidget *obj_parent) :
QWidget(obj_parent), db(NULL), configForm(NULL), managerForm(NULL), backupForm(NULL), dbus_conn(QDBusConnection::sessionBus()) QWidget(obj_parent), db(NULL), configForm(NULL), managerForm(NULL), backupForm(NULL)
{
new ClientManagerAdaptor(this);
QDBusConnection dbus = QDBusConnection::sessionBus();
// expose qcma over dbus so the database update can be triggered
dbus.registerObject("/ClientManager", this);
dbus.registerService("org.qcma.ClientManager");
trayIcon = NULL;
}
#else
MainWidget::MainWidget(QWidget *parent) :
QWidget(parent), db(NULL), configForm(NULL), managerForm(NULL), backupForm(NULL)
{ {
trayIcon = NULL; trayIcon = NULL;
} }
#endif
void MainWidget::checkSettings() void MainWidget::checkSettings()
{ {

View File

@@ -31,10 +31,6 @@
#include <QSystemTrayIcon> #include <QSystemTrayIcon>
#ifdef Q_OS_LINUX
#include <QDBusConnection>
#endif
#include <vitamtp.h> #include <vitamtp.h>
class TrayIndicator; class TrayIndicator;
@@ -42,7 +38,6 @@ class TrayIndicator;
class MainWidget : public QWidget class MainWidget : public QWidget
{ {
Q_OBJECT Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.qcma.ClientManager")
public: public:
explicit MainWidget(QWidget *parent = 0); explicit MainWidget(QWidget *parent = 0);
@@ -66,19 +61,15 @@ private:
ClientManager *managerForm; ClientManager *managerForm;
BackupManagerForm *backupForm; BackupManagerForm *backupForm;
#ifdef Q_OS_LINUX
QDBusConnection dbus_conn;
#endif
TrayIndicator *trayIcon; TrayIndicator *trayIcon;
const static QStringList path_list; const static QStringList path_list;
signals: signals:
Q_SCRIPTABLE void deviceConnected(QString); void deviceConnected(QString);
Q_SCRIPTABLE void deviceDisconnected(); void deviceDisconnected();
Q_SCRIPTABLE void databaseUpdated(int count); void databaseUpdated(int count);
Q_SCRIPTABLE void messageReceived(QString message); void messageReceived(QString message);
public slots: public slots:
void openConfig(); void openConfig();

View File

@@ -1,11 +0,0 @@
[Unit]
Description=D-Bus User Message Bus
Documentation=man:dbus-daemon(1)
Requires=dbus.socket
[Service]
ExecStart=/usr/bin/dbus-daemon --session --address=systemd: --nofork --systemd-activation
ExecReload=/usr/bin/dbus-send --print-reply --session --type=method_call --dest=org.freedesktop.DBus / org.freedesktop.DBus.ReloadConfig
[Install]
Also=dbus.socket

View File

@@ -1,9 +0,0 @@
[Unit]
Description=D-Bus User Message Bus Socket
[Socket]
ListenStream=%t/bus
[Install]
WantedBy=sockets.target
Also=dbus.service

View File

@@ -5,7 +5,7 @@ ConditionPathExists=%h/.config/codestation/qcma.conf
[Service] [Service]
ExecStart=/usr/bin/qcma_cli --verbose ExecStart=/usr/bin/qcma_cli --verbose
ExecStop=/usr/bin/dbus-send --print-reply --session --type=method_call --dest=org.qcma.HeadlessManager /HeadlessManager org.qcma.HeadlessManager.stop ExecReload=/bin/kill -HUP $MAINPID
[Install] [Install]
Alias=qcma_cli.service Alias=qcma_cli.service