Merge branch 'feature-headless' into testing

Conflicts:
	qcma.pro
This commit is contained in:
codestation
2014-04-24 23:47:29 -04:30
42 changed files with 910 additions and 251 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
qcma.pro.user* qcma.pro.user*
*.qm *.qm
org.qcma.*.xml

View File

@@ -1,3 +1,16 @@
qcma (0.3.0) unstable; urgency=low
* Headless qcma version.
* Added dbus controls to qcma (Linux only)
* Set the default video codec to h264 if metadata skip is enabled.
* Delay the progress dialog by one second so it doesn't show on quick scans.
* Do not show the disconnect message if no connection is established.
* Show correct directory separators on Windows.
* Show if savedata, updates or dlc are present on backup manager.
* New database backend. Disabled for now.
-- codestation <codestation404@gmail.com> Thu, 28 Mar 2014 00:00:00 -0000
qcma (0.2.8) unstable; urgency=low qcma (0.2.8) unstable; urgency=low
* Fixed bug where USB transfers couldn't be cancelled. * Fixed bug where USB transfers couldn't be cancelled.

View File

@@ -10,7 +10,7 @@ meant to be compatible with Linux, Windows and MAC OS X.
The aim of this project is to provide an implementation that is on par with the The aim of this project is to provide an implementation that is on par with the
official CMA and also offer some features missing in the original one. official CMA and also offer some features missing in the original one.
#### Implemented features missing in OpenCMA (Yifan Lu CLI application) #### Implemented features.
* Metadata for PSP savedatas. * Metadata for PSP savedatas.
* Basic metadata for single songs (album, artist, title, cover art). * Basic metadata for single songs (album, artist, title, cover art).
* Basic metadata for videos (duration, dimensions, thumbnail). * Basic metadata for videos (duration, dimensions, thumbnail).
@@ -24,14 +24,35 @@ official CMA and also offer some features missing in the original one.
* SQLite backend for database. * SQLite backend for database.
#### Planned features #### Planned features
* **UPNP bridge**: connect an existing UPNP server to interface with the Vita * **Android version**: port of Qcma to Android.
using the wireless streaming feature.
## Headless version
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:
#### Send a signal to refresh the database
```
dbus-send --session --type=method_call --dest=org.qcma.HeadlessManager /HeadlessManager org.qcma.HeadlessManager.refreshDatabase
```
#### Send a signal to stop the daemon
```
dbus-send --session --type=method_call --dest=org.qcma.HeadlessManager /HeadlessManager org.qcma.HeadlessManager.stop
```
#### 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://qt-project.org/)
* [VitaMTP](https://github.com/yifanlu/VitaMTP). Use my * [VitaMTP fork](https://github.com/codestation/VitaMTP)
[fork](https://github.com/codestation/VitaMTP) until the patches are merged upstream.
* [FFmpeg](hhttp://www.ffmpeg.org/) * [FFmpeg](hhttp://www.ffmpeg.org/)
@@ -46,6 +67,8 @@ 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.
#### License #### License
GPL v3: since some parts of QCMA are based on the reference implementation of GPL v3: since some parts of QCMA are based on the reference implementation of
OpenCMA. OpenCMA.

4
debian/control vendored
View File

@@ -12,3 +12,7 @@ Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends} Depends: ${shlibs:Depends}, ${misc:Depends}
Description: Content Manager Assistant for the PS Vita Description: Content Manager Assistant for the PS Vita
Package: qcma-cli
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}
Description: Content Manager Assistant for the PS Vita (headless version)

1
debian/qcma-cli.install vendored Normal file
View File

@@ -0,0 +1 @@
usr/bin/qcma_cli

3
debian/qcma.install vendored Normal file
View File

@@ -0,0 +1,3 @@
usr/bin/qcma
resources/qcma.desktop usr/share/applications/qcma
resources/images/qcma.png usr/share/icons/hicolor/64x64/apps

2
debian/rules vendored
View File

@@ -6,4 +6,4 @@ include /usr/share/cdbs/1/class/qmake.mk
DEB_QMAKE_ARGS += PREFIX=/usr DEB_QMAKE_ARGS += PREFIX=/usr
pre-build:: pre-build::
lrelease-qt4 resources/translations/*.ts lrelease resources/translations/*.ts

166
qcma.pro
View File

@@ -4,168 +4,14 @@
# #
#------------------------------------------------- #-------------------------------------------------
QT += core \ TEMPLATE = subdirs
gui \ SUBDIRS = qcma_gui.pro
widgets \
network \
sql
TARGET = qcma # Compile the headless binary only on Linux because it depends on dbus
unix:!macx {
VERSION = 0.2.9 SUBDIRS += qcma_cli.pro
}
TEMPLATE = app
SOURCES += src/main.cpp \
src/capability.cpp \
src/cmaobject.cpp \
src/cmarootobject.cpp \
src/cmautils.cpp \
src/mainwidget.cpp \
src/singleapplication.cpp \
src/sforeader.cpp \
src/cmaclient.cpp \
src/cmabroadcast.cpp \
src/avdecoder.cpp \
src/cmaevent.cpp \
src/clientmanager.cpp \
src/filterlineedit.cpp \
src/dds.cpp \
src/sqlitedb.cpp \
src/httpdownloader.cpp \
src/qlistdb.cpp \
src/database.cpp \
# forms
src/forms/backupitem.cpp \
src/forms/backupmanagerform.cpp \
src/forms/configwidget.cpp \
src/forms/confirmdialog.cpp \
src/forms/pinform.cpp \
src/forms/progressform.cpp
HEADERS += \
src/capability.h \
src/cmaobject.h \
src/cmarootobject.h \
src/cmautils.h \
src/mainwidget.h \
src/singleapplication.h \
src/sforeader.h \
src/cmaclient.h \
src/cmabroadcast.h \
src/avdecoder.h \
src/cmaevent.h \
src/clientmanager.h \
src/filterlineedit.h \
src/dds.h \
src/sqlitedb.h \
src/httpdownloader.h \
src/qlistdb.h \
src/database.h \
# forms
src/forms/backupitem.h \
src/forms/backupmanagerform.h \
src/forms/configwidget.h \
src/forms/confirmdialog.h \
src/forms/pinform.h \
src/forms/progressform.h
FORMS += \
src/forms/configwidget.ui \
src/forms/backupmanagerform.ui \
src/forms/backupitem.ui \
src/forms/confirmdialog.ui \
src/forms/progressform.ui \
src/forms/pinform.ui
TRANSLATIONS += \ TRANSLATIONS += \
resources/translations/qcma_es.ts \ resources/translations/qcma_es.ts \
resources/translations/qcma_ja.ts resources/translations/qcma_ja.ts
OTHER_FILES += \
resources/xml/psp2-updatelist.xml \
resources/images/psv_icon.png \
resources/images/psv_icon_16.png \
resources/images/qcma.png \
resources/qcma.desktop \
qcma.rc
INCLUDEPATH += src/
RESOURCES += qcmares.qrc translations.qrc
# find packages using pkg-config
CONFIG += link_pkgconfig
PKGCONFIG += libvitamtp libavformat libavcodec libavutil libswscale
# custom CXXFLAGS
QMAKE_CXXFLAGS += -Wno-write-strings -Wall -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS
#Linux-only config
unix:!macx {
# largefile support
DEFINES += _FILE_OFFSET_BITS=64 _LARGEFILE_SOURCE
# installation prefix
isEmpty(PREFIX) {
PREFIX = /usr/local
}
BINDIR = $$PREFIX/bin
DATADIR = $$PREFIX/share
# config for desktop file and icon
desktop.path = $$DATADIR/applications/$${TARGET}
desktop.files += resources/$${TARGET}.desktop
icon64.path = $$DATADIR/icons/hicolor/64x64/apps
icon64.files += resources/images/$${TARGET}.png
target.path = $$BINDIR
INSTALLS += target desktop icon64
# KDE support
ENABLE_KDE {
greaterThan(QT_MAJOR_VERSION, 4) {
error("ENABLE_KDE can only be used with Qt4")
}
LIBS += -lkdeui
DEFINES += ENABLE_KDE_NOTIFIER=1
SOURCES += src/kdenotifier.cpp
HEADERS += src/kdenotifier.h
}
}
# Windows config
win32 {
# Windows icon
RC_FILE = qcma.rc
# avoid alignment issues with newer mingw compiler
QMAKE_CXXFLAGS += -mno-ms-bitfields
}
# OS X config
macx {
# OS X icon
ICON = resources/images/$${TARGET}.icns
# re-enable pkg-config on OS X (brew installs pkg-config files)
QT_CONFIG -= no-pkg-config
}
# try to get the current git version + hash
QCMA_GIT_VERSION=$$system(git describe --tags)
#use the generic version if the above command fails (no git executable or metadata)
isEmpty(QCMA_GIT_VERSION) {
DEFINES += QCMA_VER=\\\"$$VERSION\\\"
} else {
DEFINES += QCMA_VER=\\\"$$QCMA_GIT_VERSION\\\"
# git executable and metadata are available, might as well get commit hash and branch name
GET_HASHES {
DEFINES += QCMA_BUILD_HASH=\\\"$$system(git rev-parse --short HEAD)\\\"
DEFINES += QCMA_BUILD_BRANCH=\\\"$$system(git rev-parse --abbrev-ref HEAD)\\\"
}
}

31
qcma_cli.pro Normal file
View File

@@ -0,0 +1,31 @@
include(qcma_common.pri)
QT += dbus
TARGET = qcma_cli
SOURCES += \
src/cli/main_cli.cpp \
src/cli/singlecoreapplication.cpp \
src/cli/headlessmanager.cpp
HEADERS += \
src/cli/singlecoreapplication.h \
src/cli/headlessmanager.h
# Linux-only config
unix:!macx {
target.path = $$BINDIR
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

115
qcma_common.pri Normal file
View File

@@ -0,0 +1,115 @@
#-------------------------------------------------
#
# Project created by QtCreator 2013-07-23T15:34:17
#
#-------------------------------------------------
QT += core network sql
VERSION = 0.3.0
TEMPLATE = app
SOURCES += \
src/capability.cpp \
src/cmaobject.cpp \
src/cmarootobject.cpp \
src/cmautils.cpp \
src/sforeader.cpp \
src/cmaclient.cpp \
src/cmabroadcast.cpp \
src/avdecoder.cpp \
src/cmaevent.cpp \
src/dds.cpp \
src/sqlitedb.cpp \
src/httpdownloader.cpp \
src/qlistdb.cpp \
src/database.cpp \
HEADERS += \
src/capability.h \
src/cmaobject.h \
src/cmarootobject.h \
src/cmautils.h \
src/sforeader.h \
src/cmaclient.h \
src/cmabroadcast.h \
src/avdecoder.h \
src/cmaevent.h \
src/dds.h \
src/sqlitedb.h \
src/httpdownloader.h \
src/qlistdb.h \
src/database.h \
OTHER_FILES += \
resources/xml/psp2-updatelist.xml \
resources/images/psv_icon.png \
resources/images/psv_icon_16.png \
resources/images/qcma.png \
resources/qcma.desktop \
qcma.rc
INCLUDEPATH += src/
RESOURCES += qcmares.qrc translations.qrc
# find packages using pkg-config
CONFIG += link_pkgconfig
PKGCONFIG += libvitamtp libavformat libavcodec libavutil libswscale
# custom CXXFLAGS
QMAKE_CXXFLAGS += -Wno-write-strings -Wall -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS
#Linux-only config
unix:!macx {
# largefile support
DEFINES += _FILE_OFFSET_BITS=64 _LARGEFILE_SOURCE
# installation prefix
isEmpty(PREFIX) {
PREFIX = /usr/local
}
BINDIR = $$PREFIX/bin
DATADIR = $$PREFIX/share
}
# Windows config
win32 {
# Windows icon
RC_FILE = qcma.rc
# avoid alignment issues with newer mingw compiler
QMAKE_CXXFLAGS += -mno-ms-bitfields
}
# OS X config
macx {
# OS X icon
ICON = resources/images/$${TARGET}.icns
# re-enable pkg-config on OS X (brew installs pkg-config files)
QT_CONFIG -= no-pkg-config
}
# try to get the current git version + hash
QCMA_GIT_VERSION=$$system(git describe --tags)
#use the generic version if the above command fails (no git executable or metadata)
isEmpty(QCMA_GIT_VERSION) {
DEFINES += QCMA_VER=\\\"$$VERSION\\\"
} else {
DEFINES += QCMA_VER=\\\"$$QCMA_GIT_VERSION\\\"
}
GET_HASHES {
# try to get the current git commit and branch
QCMA_GIT_HASH=$$system(git rev-parse --short HEAD)
QCMA_GIT_BRANCH=$$system(git rev-parse --abbrev-ref HEAD)
# pass the current git commit hash
!isEmpty(QCMA_GIT_HASH):!isEmpty(QCMA_GIT_BRANCH) {
DEFINES += QCMA_BUILD_HASH=\\\"$$QCMA_GIT_HASH\\\"
DEFINES += QCMA_BUILD_BRANCH=\\\"$$QCMA_GIT_BRANCH\\\"
}
}

82
qcma_gui.pro Normal file
View File

@@ -0,0 +1,82 @@
include(qcma_common.pri)
QT += gui widgets
TARGET = qcma
SOURCES += \
src/gui/main.cpp \
src/gui/mainwidget.cpp \
src/gui/singleapplication.cpp \
src/gui/clientmanager.cpp \
src/gui/filterlineedit.cpp \
# forms
src/forms/backupitem.cpp \
src/forms/backupmanagerform.cpp \
src/forms/configwidget.cpp \
src/forms/confirmdialog.cpp \
src/forms/pinform.cpp \
src/forms/progressform.cpp
HEADERS += \
src/gui/mainwidget.h \
src/gui/singleapplication.h \
src/gui/clientmanager.h \
src/gui/filterlineedit.h \
# forms
src/forms/backupitem.h \
src/forms/backupmanagerform.h \
src/forms/configwidget.h \
src/forms/confirmdialog.h \
src/forms/pinform.h \
src/forms/progressform.h
FORMS += \
src/forms/configwidget.ui \
src/forms/backupmanagerform.ui \
src/forms/backupitem.ui \
src/forms/confirmdialog.ui \
src/forms/progressform.ui \
src/forms/pinform.ui
#Linux-only config
unix:!macx {
DATADIR = $$PREFIX/share
# config for desktop file and icon
desktop.path = $$DATADIR/applications/$${TARGET}
desktop.files += resources/$${TARGET}.desktop
icon64.path = $$DATADIR/icons/hicolor/64x64/apps
icon64.files += resources/images/$${TARGET}.png
target.path = $$BINDIR
INSTALLS += target desktop icon64
# KDE support
ENABLE_KDE {
greaterThan(QT_MAJOR_VERSION, 4) {
error("ENABLE_KDE can only be used with Qt4")
}
LIBS += -lkdeui
DEFINES += ENABLE_KDE_NOTIFIER=1
SOURCES += src/kdenotifier.cpp
HEADERS += src/kdenotifier.h
}
}
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

@@ -47,7 +47,8 @@ This package contains only the files necessary for development.
%prep %prep
rm -rf $RPM_SOURCE_DIR/%{name}%{sonum} rm -rf $RPM_SOURCE_DIR/%{name}%{sonum}
git clone https://github.com/codestation/VitaMTP.git $RPM_SOURCE_DIR/%{name}%{sonum} %{!?_vitamtp_repo:%define _vitamtp_repo https://github.com/codestation/VitaMTP.git}
git clone "%{_vitamtp_repo}" $RPM_SOURCE_DIR/%{name}%{sonum}
cp -r $RPM_SOURCE_DIR/%{name}%{sonum} $RPM_BUILD_DIR/%{name}%{sonum} cp -r $RPM_SOURCE_DIR/%{name}%{sonum} $RPM_BUILD_DIR/%{name}%{sonum}
%setup -n %{name}%{sonum} -DT %setup -n %{name}%{sonum} -DT
@@ -61,7 +62,7 @@ make %{?_smp_mflags}
%makeinstall %makeinstall
rm -rf %{buildroot}/%{_libdir}/*.la rm -rf %{buildroot}/%{_libdir}/*.la
mkdir -p %{buildroot}/usr/lib/udev/rules.d mkdir -p %{buildroot}/usr/lib/udev/rules.d
cp debian/vitamtp1.udev %{buildroot}/usr/lib/udev/rules.d/80-psvita.rules cp debian/vitamtp%{sonum}.udev %{buildroot}/usr/lib/udev/rules.d/80-psvita.rules
%post -n %{name}%{sonum} -p /sbin/ldconfig %post -n %{name}%{sonum} -p /sbin/ldconfig
%postun -n %{name}%{sonum} -p /sbin/ldconfig %postun -n %{name}%{sonum} -p /sbin/ldconfig

View File

@@ -47,7 +47,8 @@ This package contains only the files necessary for development.
%prep %prep
rm -rf $RPM_SOURCE_DIR/%{name}%{sonum} rm -rf $RPM_SOURCE_DIR/%{name}%{sonum}
git clone https://github.com/codestation/VitaMTP.git $RPM_SOURCE_DIR/%{name}%{sonum} %{!?_vitamtp_repo:%define _vitamtp_repo https://github.com/codestation/VitaMTP.git}
git clone "%{_vitamtp_repo}" $RPM_SOURCE_DIR/%{name}%{sonum}
cp -r $RPM_SOURCE_DIR/%{name}%{sonum} $RPM_BUILD_DIR/%{name}%{sonum} cp -r $RPM_SOURCE_DIR/%{name}%{sonum} $RPM_BUILD_DIR/%{name}%{sonum}
%setup -n %{name}%{sonum} -DT %setup -n %{name}%{sonum} -DT
@@ -61,7 +62,7 @@ make %{?_smp_mflags}
%makeinstall %makeinstall
rm -rf %{buildroot}/%{_libdir}/*.la rm -rf %{buildroot}/%{_libdir}/*.la
mkdir -p %{buildroot}/usr/lib/udev/rules.d mkdir -p %{buildroot}/usr/lib/udev/rules.d
cp debian/vitamtp1.udev %{buildroot}/usr/lib/udev/rules.d/80-psvita.rules cp debian/vitamtp%{sonum}.udev %{buildroot}/usr/lib/udev/rules.d/80-psvita.rules
%post -n %{name}%{sonum} -p /sbin/ldconfig %post -n %{name}%{sonum} -p /sbin/ldconfig
%postun -n %{name}%{sonum} -p /sbin/ldconfig %postun -n %{name}%{sonum} -p /sbin/ldconfig

View File

@@ -6,7 +6,7 @@ Name: qcma
Summary: PSVita Content Manager Assistant Summary: PSVita Content Manager Assistant
License: GPL-3.0 License: GPL-3.0
Release: 1 Release: 1
Version: 0.2.9 Version: 0.3.0
URL: https://github.com/codestation/qcma URL: https://github.com/codestation/qcma
Source: https://github.com/codestation/qcma.git Source: https://github.com/codestation/qcma.git
Group: Productivity/File utilities Group: Productivity/File utilities
@@ -27,14 +27,15 @@ is meant to be compatible with Linux, Windows and MAC OS X.
%prep %prep
rm -rf $RPM_SOURCE_DIR/%{name}-%{version} rm -rf $RPM_SOURCE_DIR/%{name}-%{version}
git clone https://github.com/codestation/qcma.git $RPM_SOURCE_DIR/%{name}-%{version} %{!?_qcma_repo:%define _qcma_repo https://github.com/codestation/qcma.git}
git clone "%{_qcma_repo}" $RPM_SOURCE_DIR/%{name}-%{version}
cp -r $RPM_SOURCE_DIR/%{name}-%{version} $RPM_BUILD_DIR/%{name}-%{version} cp -r $RPM_SOURCE_DIR/%{name}-%{version} $RPM_BUILD_DIR/%{name}-%{version}
%setup -n %{name}-%{version} -DT %setup -n %{name}-%{version} -DT
%build %build
lrelease-qt5 resources/translations/*.ts lrelease-qt5 resources/translations/*.ts
qmake-qt5 PREFIX=/usr qmake-qt5 PREFIX=/usr qcma.pro CONFIG+=QT5_SUFFIX
make %{?_smp_mflags} make %{?_smp_mflags}
%install %install
@@ -43,6 +44,7 @@ make install INSTALL_ROOT=%{buildroot}
%files %files
%defattr(-,root,root) %defattr(-,root,root)
%{_bindir}/qcma %{_bindir}/qcma
%{_bindir}/qcma_cli
%{_prefix}/share/applications/qcma/qcma.desktop %{_prefix}/share/applications/qcma/qcma.desktop
%{_prefix}/share/icons/hicolor/64x64/apps/qcma.png %{_prefix}/share/icons/hicolor/64x64/apps/qcma.png

View File

@@ -6,7 +6,7 @@ Name: qcma
Summary: PSVita Content Manager Assistant Summary: PSVita Content Manager Assistant
License: GPL-3.0 License: GPL-3.0
Release: 1 Release: 1
Version: 0.2.9 Version: 0.3.0
URL: https://github.com/codestation/qcma URL: https://github.com/codestation/qcma
Source: https://github.com/codestation/qcma.git Source: https://github.com/codestation/qcma.git
Group: Productivity/File utilities Group: Productivity/File utilities
@@ -27,14 +27,15 @@ is meant to be compatible with Linux, Windows and MAC OS X.
%prep %prep
rm -rf $RPM_SOURCE_DIR/%{name}-%{version} rm -rf $RPM_SOURCE_DIR/%{name}-%{version}
git clone https://github.com/codestation/qcma.git $RPM_SOURCE_DIR/%{name}-%{version} %{!?_qcma_repo:%define _qcma_repo https://github.com/codestation/qcma.git}
git clone "%{_qcma_repo}" $RPM_SOURCE_DIR/%{name}-%{version}
cp -r $RPM_SOURCE_DIR/%{name}-%{version} $RPM_BUILD_DIR/%{name}-%{version} cp -r $RPM_SOURCE_DIR/%{name}-%{version} $RPM_BUILD_DIR/%{name}-%{version}
%setup -n %{name}-%{version} -DT %setup -n %{name}-%{version} -DT
%build %build
lrelease-qt5 resources/translations/*.ts lrelease-qt5 resources/translations/*.ts
qmake-qt5 PREFIX=/usr qmake-qt5 PREFIX=/usr qcma.pro CONFIG+=QT5_SUFFIX
make %{?_smp_mflags} make %{?_smp_mflags}
%install %install
@@ -43,6 +44,7 @@ make install INSTALL_ROOT=%{buildroot}
%files %files
%defattr(-,root,root) %defattr(-,root,root)
%{_bindir}/qcma %{_bindir}/qcma
%{_bindir}/qcma_cli
%{_prefix}/share/applications/qcma/qcma.desktop %{_prefix}/share/applications/qcma/qcma.desktop
%{_prefix}/share/icons/hicolor/64x64/apps/qcma.png %{_prefix}/share/icons/hicolor/64x64/apps/qcma.png

View File

@@ -24,6 +24,7 @@
#include <QDebug> #include <QDebug>
#include <QBuffer> #include <QBuffer>
#include <QFile> #include <QFile>
#include <QImage>
#include <QSettings> #include <QSettings>
AVDecoder::AvInit init; AVDecoder::AvInit init;

View File

@@ -20,7 +20,6 @@
#ifndef AVDECODER_H #ifndef AVDECODER_H
#define AVDECODER_H #define AVDECODER_H
#include <QImage>
#include <QString> #include <QString>
extern "C" { extern "C" {

135
src/cli/headlessmanager.cpp Normal file
View File

@@ -0,0 +1,135 @@
/*
* QCMA: Cross-platform content manager assistant for the PS Vita
*
* Copyright (C) 2013 Codestation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "cmaclient.h"
#include "cmautils.h"
#include "sqlitedb.h"
#include "qlistdb.h"
#include "headlessmanager.h"
#include "headlessmanager_adaptor.h"
#include <QCoreApplication>
#include <QSettings>
#include <vitamtp.h>
HeadlessManager::HeadlessManager(QObject *parent) :
QObject(parent), dbus_conn(QDBusConnection::sessionBus())
{
new HeadlessManagerAdaptor(this);
QDBusConnection dbus = QDBusConnection::sessionBus();
// expose qcma over dbus so the database update can be triggered
dbus.registerObject("/HeadlessManager", this);
dbus.registerService("org.qcma.HeadlessManager");
}
HeadlessManager::~HeadlessManager()
{
VitaMTP_Cleanup();
delete m_db;
}
void HeadlessManager::refreshDatabase()
{
if(m_db->load()) {
return;
}
if(!m_db->rescan()) {
qDebug("No PS Vita system has been registered");
}
}
void HeadlessManager::start()
{
if(VitaMTP_Init() < 0) {
qDebug("Cannot initialize VitaMTP library");
return;
}
if(QSettings().value("useMemoryStorage", true).toBool()) {
m_db = new QListDB();
} else {
m_db = new SQLiteDB();
}
// initializing database for the first use
refreshDatabase();
// send a signal over dbus of the connected peers knows when the update is finished
connect(m_db, SIGNAL(updated(int)), this, SIGNAL(databaseUpdated(int)));
thread_count = 0;
qDebug("Starting cma threads");
CmaClient *client;
QSettings settings;
if(!settings.value("disableUSB", false).toBool()) {
usb_thread = new QThread();
client = new CmaClient(m_db);
usb_thread->setObjectName("usb_thread");
connect(usb_thread, SIGNAL(started()), client, SLOT(connectUsb()));
connect(client, SIGNAL(finished()), usb_thread, SLOT(quit()), Qt::DirectConnection);
connect(usb_thread, SIGNAL(finished()), usb_thread, SLOT(deleteLater()));
connect(usb_thread, SIGNAL(finished()), this, SLOT(threadStopped()));
connect(usb_thread, SIGNAL(finished()), client, SLOT(deleteLater()));
connect(client, SIGNAL(refreshDatabase()), this, SLOT(refreshDatabase()));
client->moveToThread(usb_thread);
usb_thread->start();
thread_count++;
}
if(!settings.value("disableWireless", false).toBool()) {
wireless_thread = new QThread();
client = new CmaClient(m_db);
wireless_thread->setObjectName("wireless_thread");
connect(wireless_thread, SIGNAL(started()), client, SLOT(connectWireless()));
connect(client, SIGNAL(finished()), wireless_thread, SLOT(quit()), Qt::DirectConnection);
connect(wireless_thread, SIGNAL(finished()), wireless_thread, SLOT(deleteLater()));
connect(wireless_thread, SIGNAL(finished()), this, SLOT(threadStopped()));
connect(wireless_thread, SIGNAL(finished()), client, SLOT(deleteLater()));
connect(client, SIGNAL(refreshDatabase()), this, SLOT(refreshDatabase()));
client->moveToThread(wireless_thread);
wireless_thread->start();
thread_count++;
}
if(thread_count == 0) {
qDebug("You must enable at least USB or Wireless monitoring");
}
}
void HeadlessManager::stop()
{
if(CmaClient::stop() < 0) {
QCoreApplication::quit();
}
}
void HeadlessManager::threadStopped()
{
mutex.lock();
if(--thread_count == 0) {
QCoreApplication::quit();
}
mutex.unlock();
}

62
src/cli/headlessmanager.h Normal file
View File

@@ -0,0 +1,62 @@
/*
* QCMA: Cross-platform content manager assistant for the PS Vita
*
* Copyright (C) 2013 Codestation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef HEADLESSMANAGER_H
#define HEADLESSMANAGER_H
#include "database.h"
#include <QObject>
#include <QThread>
#include <QDBusConnection>
class HeadlessManager : public QObject
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.qcma.HeadlessManager")
public:
explicit HeadlessManager(QObject *parent = 0);
~HeadlessManager();
void start();
private:
int thread_count;
QMutex mutex;
Database *m_db;
QThread *usb_thread;
QThread *wireless_thread;
QDBusConnection dbus_conn;
signals:
void stopped();
Q_SCRIPTABLE void databaseUpdated(int count);
public slots:
void refreshDatabase();
void stop();
private slots:
void threadStopped();
};
#endif // HEADLESSMANAGER_H

116
src/cli/main_cli.cpp Normal file
View File

@@ -0,0 +1,116 @@
/*
* QCMA: Cross-platform content manager assistant for the PS Vita
*
* Copyright (C) 2013 Codestation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef Q_OS_WIN32
#include <signal.h>
#endif
#include <QDebug>
#include <QLibraryInfo>
#include <QLocale>
#include <QStringList>
#include <QTextCodec>
#include <QThread>
#include <QTranslator>
#include <inttypes.h>
#include <vitamtp.h>
#include "singlecoreapplication.h"
#include "headlessmanager.h"
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
void noMessageOutput(QtMsgType type, const QMessageLogContext &, const QString & str)
{
const char * msg = str.toStdString().c_str();
#else
void noMessageOutput(QtMsgType type, const char *msg)
{
#endif
Q_UNUSED(type);
Q_UNUSED(msg);
}
int main(int argc, char *argv[])
{
if(SingleCoreApplication::sendMessage(QObject::tr("A instance of QCMA is already running"))) {
return 0;
}
SingleCoreApplication app(argc, argv);
#ifndef Q_OS_WIN32
// 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
// SIGPIPE on the socket
signal(SIGPIPE, SIG_IGN);
#endif
if(app.arguments().contains("--with-debug")) {
VitaMTP_Set_Logging(VitaMTP_DEBUG);
} else if(app.arguments().contains("--verbose")) {
VitaMTP_Set_Logging(VitaMTP_VERBOSE);
} else {
VitaMTP_Set_Logging(VitaMTP_NONE);
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
qInstallMessageHandler(noMessageOutput);
#else
qInstallMsgHandler(noMessageOutput);
#endif
}
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8"));
#endif
qDebug("Starting QCMA %s", QCMA_VER);
QTranslator translator;
QString locale = QLocale().system().name();
qDebug() << "Current locale:" << locale;
if(app.arguments().contains("--set-locale")) {
int index = app.arguments().indexOf("--set-locale");
if(index + 1 < app.arguments().length()) {
qDebug("Enforcing locale: %s", app.arguments().at(index + 1).toUtf8().data());
locale = app.arguments().at(index + 1);
}
}
if(translator.load("qcma_" + locale, ":/resources/translations")) {
app.installTranslator(&translator);
} else {
qDebug() << "Cannot load translation for locale:" << locale;
}
QTranslator system_translator;
system_translator.load("qt_" + locale, QLibraryInfo::location(QLibraryInfo::TranslationsPath));
app.installTranslator(&system_translator);
qDebug("Starting main thread: 0x%016" PRIxPTR, (uintptr_t)QThread::currentThreadId());
// set the organization/application for QSettings to work properly
app.setOrganizationName("qcma");
app.setApplicationName("qcma");
HeadlessManager manager;
manager.start();
return app.exec();
}

View File

@@ -0,0 +1,69 @@
/*
* QCMA: Cross-platform content manager assistant for the PS Vita
*
* Copyright (C) 2013 Codestation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "singlecoreapplication.h"
#include <QDebug>
const int SingleCoreApplication::timeout = 500;
const QString SingleCoreApplication::SHARED_KEY = "QCMA_KEY";
SingleCoreApplication::SingleCoreApplication(int &argc, char **argv) :
QCoreApplication(argc, argv)
{
server = new QLocalServer(this);
connect(server, SIGNAL(newConnection()), this, SLOT(receiveMessage()));
QLocalServer::removeServer(SHARED_KEY);
server->listen(SHARED_KEY);
}
void SingleCoreApplication::receiveMessage()
{
QLocalSocket *socket = server->nextPendingConnection();
if(!socket->waitForReadyRead(timeout)) {
qDebug() << socket->errorString();
return;
}
QByteArray byteArray = socket->readAll();
QString message = QString::fromUtf8(byteArray.constData());
emit messageAvailable(message);
socket->disconnectFromServer();
}
bool SingleCoreApplication::sendMessage(const QString &message)
{
QLocalSocket socket;
socket.connectToServer(SHARED_KEY, QIODevice::WriteOnly);
if(!socket.waitForConnected(timeout)) {
return false;
}
socket.write(message.toUtf8());
if(!socket.waitForBytesWritten(timeout)) {
qDebug() << socket.errorString();
return false;
}
socket.disconnectFromServer();
return true;
}

View File

@@ -0,0 +1,50 @@
/*
* QCMA: Cross-platform content manager assistant for the PS Vita
*
* Copyright (C) 2013 Codestation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SINGLECOREAPPLICATION_H
#define SINGLECOREAPPLICATION_H
#include <QCoreApplication>
#include <QLocalSocket>
#include <QLocalServer>
#include <QSharedMemory>
class SingleCoreApplication : public QCoreApplication
{
Q_OBJECT
public:
explicit SingleCoreApplication(int &argc, char **argv);
static bool sendMessage(const QString &message);
private:
QLocalServer *server;
static const int timeout;
static const QString SHARED_KEY;
signals:
void messageAvailable(QString message);
public slots:
void receiveMessage();
};
#endif // SINGLECOREAPPLICATION_H

View File

@@ -23,7 +23,6 @@
#include "cmaevent.h" #include "cmaevent.h"
#include "cmautils.h" #include "cmautils.h"
#include <QApplication>
#include <QDateTime> #include <QDateTime>
#include <QDebug> #include <QDebug>
#include <QDir> #include <QDir>

View File

@@ -504,6 +504,7 @@ void CmaEvent::vitaEventSendHttpObjectFromURL(vita_event_t *event, int eventId)
data = res.readAll(); data = res.readAll();
} else if(!offlineMode) { } else if(!offlineMode) {
qDebug("URL: %s", url);
HTTPDownloader downloader(url); HTTPDownloader downloader(url);
QThread *http_thread = new QThread(); QThread *http_thread = new QThread();
http_thread->setObjectName("http_thread"); http_thread->setObjectName("http_thread");

View File

@@ -100,7 +100,7 @@ void CMAObject::loadSfoMetadata(const QString &path)
title.chop(1); title.chop(1);
} }
metadata.data.saveData.savedataTitle = strdup(title.toStdString().c_str()); metadata.data.saveData.savedataTitle = strdup(title.toStdString().c_str());
metadata.data.saveData.dateTimeUpdated = QFileInfo(sfo).created().toTime_t(); metadata.data.saveData.dateTimeUpdated = QFileInfo(sfo).created().toUTC().toTime_t();
} else { } else {
metadata.data.saveData.title = strdup(metadata.name); metadata.data.saveData.title = strdup(metadata.name);
metadata.data.saveData.detail = strdup(""); metadata.data.saveData.detail = strdup("");
@@ -116,7 +116,7 @@ void CMAObject::initObject(const QFileInfo &file, int file_type)
metadata.ohfi = ohfi_count++; metadata.ohfi = ohfi_count++;
metadata.type = VITA_DIR_TYPE_MASK_REGULAR; // ignored for files metadata.type = VITA_DIR_TYPE_MASK_REGULAR; // ignored for files
metadata.dateTimeCreated = file.created().toTime_t(); metadata.dateTimeCreated = file.created().toUTC().toTime_t();
metadata.size = 0; metadata.size = 0;
DataType type = file.isFile() ? File : Folder; DataType type = file.isFile() ? File : Folder;
metadata.dataType = (DataType)(type | (parent->metadata.dataType & ~Folder)); metadata.dataType = (DataType)(type | (parent->metadata.dataType & ~Folder));
@@ -143,7 +143,7 @@ void CMAObject::initObject(const QFileInfo &file, int file_type)
Database::loadMusicMetadata(file.absoluteFilePath(), metadata); Database::loadMusicMetadata(file.absoluteFilePath(), metadata);
} else if(MASK_SET(metadata.dataType, Video | File)) { } else if(MASK_SET(metadata.dataType, Video | File)) {
metadata.data.video.fileName = strdup(metadata.name); metadata.data.video.fileName = strdup(metadata.name);
metadata.data.video.dateTimeUpdated = file.created().toTime_t(); metadata.data.video.dateTimeUpdated = file.created().toUTC().toTime_t();
metadata.data.video.statusType = 1; metadata.data.video.statusType = 1;
metadata.data.video.fileFormatType = FILE_FORMAT_MP4; metadata.data.video.fileFormatType = FILE_FORMAT_MP4;
metadata.data.video.parentalLevel = 0; metadata.data.video.parentalLevel = 0;
@@ -161,7 +161,7 @@ void CMAObject::initObject(const QFileInfo &file, int file_type)
metadata.data.photo.fileName = strdup(metadata.name); metadata.data.photo.fileName = strdup(metadata.name);
metadata.data.photo.fileFormatType = photo_list[file_type].file_format; metadata.data.photo.fileFormatType = photo_list[file_type].file_format;
metadata.data.photo.statusType = 1; metadata.data.photo.statusType = 1;
metadata.data.photo.dateTimeOriginal = file.created().toTime_t(); metadata.data.photo.dateTimeOriginal = file.created().toUTC().toTime_t();
metadata.data.photo.numTracks = 1; metadata.data.photo.numTracks = 1;
metadata.data.photo.tracks = new media_track(); metadata.data.photo.tracks = new media_track();
metadata.data.photo.tracks->type = VITA_TRACK_TYPE_PHOTO; metadata.data.photo.tracks->type = VITA_TRACK_TYPE_PHOTO;

View File

@@ -1,6 +1,7 @@
#include "database.h" #include "database.h"
#include "avdecoder.h" #include "avdecoder.h"
#include <QImage>
#include <QSettings> #include <QSettings>
#include <QThread> #include <QThread>

View File

@@ -23,7 +23,7 @@
#include "sforeader.h" #include "sforeader.h"
#include "confirmdialog.h" #include "confirmdialog.h"
#include "cmautils.h" #include "cmautils.h"
#include "filterlineedit.h" #include "gui/filterlineedit.h"
#include <QDebug> #include <QDebug>
#include <QDialogButtonBox> #include <QDialogButtonBox>
@@ -188,7 +188,10 @@ void BackupManagerForm::loadBackupListing(int index)
item->title = game_name; item->title = game_name;
connect(item, SIGNAL(deleteEntry(BackupItem*)), this, SLOT(removeEntry(BackupItem*))); connect(item, SIGNAL(deleteEntry(BackupItem*)), this, SLOT(removeEntry(BackupItem*)));
QString size = readable_size(meta->size);
// show better size info for multi GiB backups
bool use_gb = ohfi == VITA_OHFI_BACKUP && meta->size > 1024*1024*1024;
QString size = readable_size(meta->size, use_gb);
QString info; QString info;

View File

@@ -131,7 +131,7 @@
<customwidget> <customwidget>
<class>FilterLineEdit</class> <class>FilterLineEdit</class>
<extends>QLineEdit</extends> <extends>QLineEdit</extends>
<header>filterlineedit.h</header> <header>gui/filterlineedit.h</header>
</customwidget> </customwidget>
</customwidgets> </customwidgets>
<resources/> <resources/>

View File

@@ -45,6 +45,7 @@ void ClientManager::databaseUpdated(int count)
} else { } else {
emit messageSent(tr("Database indexing aborted by user")); emit messageSent(tr("Database indexing aborted by user"));
} }
emit updated(count);
} }
void ClientManager::showPinDialog(QString name, int pin) void ClientManager::showPinDialog(QString name, int pin)

View File

@@ -20,7 +20,7 @@
#ifndef CLIENTMANAGER_H #ifndef CLIENTMANAGER_H
#define CLIENTMANAGER_H #define CLIENTMANAGER_H
#include "qlistdb.h" #include "database.h"
#include "forms/pinform.h" #include "forms/pinform.h"
#include "forms/progressform.h" #include "forms/progressform.h"
@@ -50,15 +50,18 @@ private:
QThread *wireless_thread; QThread *wireless_thread;
signals: signals:
void updated(int);
void stopped(); void stopped();
void receivedPin(int); void receivedPin(int);
void deviceDisconnected(); void deviceDisconnected();
void messageSent(QString); void messageSent(QString);
void deviceConnected(QString); void deviceConnected(QString);
public slots:
void refreshDatabase();
private slots: private slots:
void threadStopped(); void threadStopped();
void refreshDatabase();
void databaseUpdated(int count); void databaseUpdated(int count);
void showPinDialog(QString name, int pin); void showPinDialog(QString name, int pin);
}; };

View File

@@ -110,8 +110,10 @@ int main(int argc, char *argv[])
//TODO: check if this is actually needed since we don't have a main window by default //TODO: check if this is actually needed since we don't have a main window by default
QApplication::setQuitOnLastWindowClosed(false); QApplication::setQuitOnLastWindowClosed(false);
bool showSystray = !app.arguments().contains("--no-systray");
MainWidget widget; MainWidget widget;
widget.prepareApplication(); widget.prepareApplication(showSystray);
// receive the message from another process // receive the message from another process
QObject::connect(&app, SIGNAL(messageAvailable(QString)), &widget, SLOT(receiveMessage(QString))); QObject::connect(&app, SIGNAL(messageAvailable(QString)), &widget, SLOT(receiveMessage(QString)));

View File

@@ -21,6 +21,10 @@
#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"
@@ -44,10 +48,32 @@ const QStringList MainWidget::path_list = QStringList() << "photoPath" << "music
bool sleptOnce = false; bool sleptOnce = false;
#ifdef Q_OS_LINUX
MainWidget::MainWidget(QWidget *parent) :
QWidget(parent), db(NULL), configForm(NULL), managerForm(NULL), backupForm(NULL), dbus_conn(QDBusConnection::sessionBus())
{
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");
#ifndef ENABLE_KDE_NOTIFIER
trayIcon = NULL;
#else
notifierItem = NULL;
#endif
}
#else
MainWidget::MainWidget(QWidget *parent) : MainWidget::MainWidget(QWidget *parent) :
QWidget(parent), db(NULL), configForm(NULL), managerForm(NULL), backupForm(NULL) QWidget(parent), db(NULL), configForm(NULL), managerForm(NULL), backupForm(NULL)
{ {
#ifndef ENABLE_KDE_NOTIFIER
trayIcon = NULL;
#else
notifierItem = NULL;
#endif
} }
#endif
void MainWidget::checkSettings() void MainWidget::checkSettings()
{ {
@@ -101,7 +127,7 @@ void MainWidget::deviceDisconnect()
receiveMessage(tr("The device has been disconnected")); receiveMessage(tr("The device has been disconnected"));
} }
void MainWidget::deviceConnected(QString message) void MainWidget::deviceConnect(QString message)
{ {
#ifndef ENABLE_KDE_NOTIFIER #ifndef ENABLE_KDE_NOTIFIER
#ifndef Q_OS_WIN32 #ifndef Q_OS_WIN32
@@ -117,7 +143,7 @@ void MainWidget::deviceConnected(QString message)
receiveMessage(message); receiveMessage(message);
} }
void MainWidget::prepareApplication() void MainWidget::prepareApplication(bool showSystray)
{ {
//TODO: delete database before exit //TODO: delete database before exit
if(QSettings().value("useMemoryStorage", true).toBool()) { if(QSettings().value("useMemoryStorage", true).toBool()) {
@@ -130,7 +156,11 @@ void MainWidget::prepareApplication()
backupForm = new BackupManagerForm(db, this); backupForm = new BackupManagerForm(db, this);
managerForm = new ClientManager(db, this); managerForm = new ClientManager(db, this);
connectSignals(); connectSignals();
createTrayIcon();
if(showSystray) {
createTrayIcon();
}
checkSettings(); checkSettings();
} }
@@ -138,19 +168,22 @@ void MainWidget::connectSignals()
{ {
connect(configForm, SIGNAL(finished(int)), this, SLOT(dialogResult(int))); connect(configForm, SIGNAL(finished(int)), this, SLOT(dialogResult(int)));
connect(managerForm, SIGNAL(stopped()), qApp, SLOT(quit())); connect(managerForm, SIGNAL(stopped()), qApp, SLOT(quit()));
connect(managerForm, SIGNAL(deviceConnected(QString)), this, SLOT(deviceConnected(QString))); connect(managerForm, SIGNAL(deviceConnected(QString)), this, SIGNAL(deviceConnected(QString)));
connect(managerForm, SIGNAL(deviceDisconnected()), this, SLOT(deviceDisconnect())); connect(managerForm, SIGNAL(deviceDisconnected()), this, SIGNAL(deviceDisconnected()));
connect(managerForm, SIGNAL(messageSent(QString)), this, SLOT(receiveMessage(QString))); connect(managerForm, SIGNAL(messageSent(QString)), this, SIGNAL(messageReceived(QString)));
connect(managerForm, SIGNAL(updated(int)), this, SIGNAL(databaseUpdated(int)));
//backupForm.db = managerForm.db;
} }
void MainWidget::setTrayTooltip(QString message) void MainWidget::setTrayTooltip(QString message)
{ {
#ifndef ENABLE_KDE_NOTIFIER #ifndef ENABLE_KDE_NOTIFIER
trayIcon->setToolTip(message); if(trayIcon) {
trayIcon->setToolTip(message);
}
#else #else
notifierItem->setToolTipSubTitle(message); if(notifierItem) {
notifierItem->setToolTipSubTitle(message);
}
#endif #endif
} }
@@ -167,9 +200,9 @@ void MainWidget::showAboutDialog()
about.setText(QString("QCMA ") + QCMA_VER); about.setText(QString("QCMA ") + QCMA_VER);
about.setWindowTitle(tr("About QCMA")); about.setWindowTitle(tr("About QCMA"));
#ifndef QCMA_BUILD_HASH #ifndef QCMA_BUILD_HASH
about.setInformativeText(tr("Copyright (C) 2013 Codestation") + "\n"); about.setInformativeText(tr("Copyright (C) 2014 Codestation") + "\n");
#else #else
about.setInformativeText(tr("Copyright (C) 2013 Codestation\n\nbuild hash: %1\nbuild branch: %2").arg(QCMA_BUILD_HASH).arg(QCMA_BUILD_BRANCH)); about.setInformativeText(tr("Copyright (C) 2014 Codestation\n\nbuild hash: %1\nbuild branch: %2").arg(QCMA_BUILD_HASH).arg(QCMA_BUILD_BRANCH));
#endif #endif
about.setStandardButtons(QMessageBox::Ok); about.setStandardButtons(QMessageBox::Ok);
about.setIconPixmap(QPixmap(":/main/resources/images/qcma.png")); about.setIconPixmap(QPixmap(":/main/resources/images/qcma.png"));
@@ -189,6 +222,16 @@ void MainWidget::showAboutQt()
QMessageBox::aboutQt(this); QMessageBox::aboutQt(this);
} }
void MainWidget::openConfig()
{
configForm->open();
}
void MainWidget::refreshDatabase()
{
managerForm->refreshDatabase();
}
void MainWidget::createTrayIcon() void MainWidget::createTrayIcon()
{ {
options = new QAction(tr("&Settings"), this); options = new QAction(tr("&Settings"), this);
@@ -198,9 +241,9 @@ void MainWidget::createTrayIcon()
about_qt = new QAction(tr("Abou&t Qt"), this); about_qt = new QAction(tr("Abou&t Qt"), this);
quit = new QAction(tr("&Quit"), this); quit = new QAction(tr("&Quit"), this);
connect(options, SIGNAL(triggered()), configForm, SLOT(open())); connect(options, SIGNAL(triggered()), this, SLOT(openConfig()));
connect(backup, SIGNAL(triggered()), this, SLOT(openManager())); connect(backup, SIGNAL(triggered()), this, SLOT(openManager()));
connect(reload, SIGNAL(triggered()), managerForm, SLOT(refreshDatabase())); connect(reload, SIGNAL(triggered()), this, SLOT(refreshDatabase()));
connect(about, SIGNAL(triggered()), this, SLOT(showAboutDialog())); connect(about, SIGNAL(triggered()), this, SLOT(showAboutDialog()));
connect(about_qt, SIGNAL(triggered()), this, SLOT(showAboutQt())); connect(about_qt, SIGNAL(triggered()), this, SLOT(showAboutQt()));
connect(quit, SIGNAL(triggered()), this, SLOT(stopServer())); connect(quit, SIGNAL(triggered()), this, SLOT(stopServer()));
@@ -241,6 +284,10 @@ void MainWidget::createTrayIcon()
notifierItem->setToolTipSubTitle(tr("Disconnected")); notifierItem->setToolTipSubTitle(tr("Disconnected"));
notifierItem->setStandardActionsEnabled(false); notifierItem->setStandardActionsEnabled(false);
#endif #endif
connect(managerForm, SIGNAL(deviceConnected(QString)), this, SLOT(deviceConnect(QString)));
connect(managerForm, SIGNAL(deviceDisconnected()), this, SLOT(deviceDisconnect()));
connect(managerForm, SIGNAL(messageSent(QString)), this, SLOT(receiveMessage(QString)));
} }
void MainWidget::receiveMessage(QString message) void MainWidget::receiveMessage(QString message)
@@ -265,7 +312,9 @@ void MainWidget::receiveMessage(QString message)
MainWidget::~MainWidget() MainWidget::~MainWidget()
{ {
#ifndef ENABLE_KDE_NOTIFIER #ifndef ENABLE_KDE_NOTIFIER
trayIcon->hide(); if(trayIcon) {
trayIcon->hide();
}
#endif #endif
delete db; delete db;
} }

View File

@@ -34,16 +34,22 @@
#include "kdenotifier.h" #include "kdenotifier.h"
#endif #endif
#ifdef Q_OS_LINUX
#include <QDBusConnection>
#endif
#include <vitamtp.h> #include <vitamtp.h>
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);
~MainWidget(); ~MainWidget();
void prepareApplication(); void prepareApplication(bool showSystray);
private: private:
void connectSignals(); void connectSignals();
@@ -68,6 +74,10 @@ private:
QAction *about; QAction *about;
QAction *about_qt; QAction *about_qt;
#ifdef Q_OS_LINUX
QDBusConnection dbus_conn;
#endif
#ifndef ENABLE_KDE_NOTIFIER #ifndef ENABLE_KDE_NOTIFIER
QSystemTrayIcon *trayIcon; QSystemTrayIcon *trayIcon;
#else #else
@@ -76,12 +86,22 @@ private:
const static QStringList path_list; const static QStringList path_list;
private slots: signals:
void stopServer(); Q_SCRIPTABLE void deviceConnected(QString);
Q_SCRIPTABLE void deviceDisconnected();
Q_SCRIPTABLE void databaseUpdated(int count);
Q_SCRIPTABLE void messageReceived(QString message);
public slots:
void openConfig();
void openManager(); void openManager();
void showAboutQt(); void showAboutQt();
void showAboutDialog(); void showAboutDialog();
void deviceConnected(QString message); void refreshDatabase();
void stopServer();
private slots:
void deviceConnect(QString message);
void deviceDisconnect(); void deviceDisconnect();
void dialogResult(int result); void dialogResult(int result);
void receiveMessage(QString message); void receiveMessage(QString message);

View File

@@ -19,6 +19,7 @@
#include "httpdownloader.h" #include "httpdownloader.h"
#include <QDebug>
#include <QThread> #include <QThread>
#include <vitamtp.h> #include <vitamtp.h>
@@ -42,6 +43,7 @@ HTTPDownloader::HTTPDownloader(const QString &url, QObject *parent) :
HTTPDownloader::~HTTPDownloader() HTTPDownloader::~HTTPDownloader()
{ {
lengthMutex.unlock(); lengthMutex.unlock();
dataAvailable.unlock();
free(buffer); free(buffer);
} }
@@ -82,30 +84,32 @@ void HTTPDownloader::readyRead()
dataRead.lock(); dataRead.lock();
int currOffset = bufferSize; int currOffset = bufferSize;
if(bufferSize == 0) { if(bufferSize == 0) {
bufferSize = reply->bytesAvailable(); bufferSize = reply->bytesAvailable();
if(firstRead) { if(firstRead) {
bufferSize += 8; bufferSize += 8;
currOffset += 8; currOffset += 8;
}
// start with a 16KiB buffer // start with a 16KiB buffer
buffer = (char *)malloc(16384 + 2048); buffer = (char *)malloc(16384);
if(firstRead) {
*(uint64_t *)buffer = m_contentLength; *(uint64_t *)buffer = m_contentLength;
firstRead = false; firstRead = false;
} }
} else { } else {
bufferSize += reply->bytesAvailable(); bufferSize += reply->bytesAvailable();
if(bufferSize > 16384 + 2048) {
if(bufferSize > 16384) {
buffer = (char *)realloc(buffer, bufferSize); buffer = (char *)realloc(buffer, bufferSize);
} }
} }
reply->read(buffer + currOffset, reply->bytesAvailable());
reply->read(buffer + currOffset, reply->bytesAvailable());
downloadLeft -= bufferSize; downloadLeft -= bufferSize;
if(bufferSize > 16384 || downloadLeft == 0) { if(bufferSize >= 16384 || downloadLeft == 0) {
dataAvailable.unlock(); dataAvailable.unlock();
} }
dataRead.unlock(); dataRead.unlock();
@@ -149,8 +153,10 @@ int HTTPDownloader::readCallback(unsigned char *data, unsigned long wantlen, uns
void HTTPDownloader::error(QNetworkReply::NetworkError errorCode) void HTTPDownloader::error(QNetworkReply::NetworkError errorCode)
{ {
Q_UNUSED(errorCode); Q_UNUSED(errorCode);
QString error = reply->errorString();
emit messageSent(tr("Network error: %1").arg(reply->errorString())); qWarning() << "Network error:" << error;
emit messageSent(tr("Network error: %1").arg(error));
// set buffer to zero so a read callback can be aborted // set buffer to zero so a read callback can be aborted
dataRead.lock(); dataRead.lock();

View File

@@ -383,12 +383,18 @@ bool QListDB::deleteEntry(int ohfi, int root_ohfi)
int QListDB::getObjectMetadatas(int parent_ohfi, metadata_t **metadata, int index, int max_number) int QListDB::getObjectMetadatas(int parent_ohfi, metadata_t **metadata, int index, int max_number)
{ {
QMutexLocker locker(&mutex); QMutexLocker locker(&mutex);
CMARootObject *parent = static_cast<CMARootObject *>(ohfiToObject(parent_ohfi)); CMARootObject *parent = static_cast<CMARootObject *>(ohfiToObject(parent_ohfi));
if(parent == NULL) { if(parent == NULL) {
return 0; return 0;
} }
if(parent->metadata.dataType & File) {
*metadata = &parent->metadata;
return 1;
}
int type = parent->metadata.type; int type = parent->metadata.type;
if(parent->metadata.ohfi < OHFI_OFFSET && parent->filters) { // if we have filters if(parent->metadata.ohfi < OHFI_OFFSET && parent->filters) { // if we have filters

View File

@@ -26,6 +26,7 @@
#include <QDebug> #include <QDebug>
#include <QDir> #include <QDir>
#include <QFileInfo> #include <QFileInfo>
#include <QImage>
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
#include <QStandardPaths> #include <QStandardPaths>
@@ -77,8 +78,8 @@ static const char create_apps[] = "CREATE TABLE IF NOT EXISTS application ("
"app_type INTEGER)"; "app_type INTEGER)";
static const char create_virtual[] = "CREATE TABLE IF NOT EXISTS virtual_nodes (" static const char create_virtual[] = "CREATE TABLE IF NOT EXISTS virtual_nodes ("
"object_id INTEGER PRIMARY KEY REFERENCES object_node(object_id) ON DELETE CASCADE," "object_id INTEGER PRIMARY KEY REFERENCES object_node(object_id) ON DELETE CASCADE,"
"app_type INTEGER)"; "app_type INTEGER)";
static const char create_photos[] = "CREATE TABLE IF NOT EXISTS photos (" static const char create_photos[] = "CREATE TABLE IF NOT EXISTS photos ("
"object_id INTEGER PRIMARY KEY REFERENCES object_node(object_id) ON DELETE CASCADE," "object_id INTEGER PRIMARY KEY REFERENCES object_node(object_id) ON DELETE CASCADE,"
@@ -385,7 +386,7 @@ int SQLiteDB::insertObjectEntry(const QString &path, const QString &name, int pa
int SQLiteDB::insertObjectEntryInternal(const QString &path, const QString &name, int parent_ohfi, int root_ohfi) int SQLiteDB::insertObjectEntryInternal(const QString &path, const QString &name, int parent_ohfi, int root_ohfi)
{ {
int ohfi; int ohfi = 0;
QFileInfo info(path, name); QFileInfo info(path, name);
if(info.isDir()) { if(info.isDir()) {
@@ -526,13 +527,13 @@ bool SQLiteDB::insertSourceEntry(uint object_id, const QString &path, const QStr
QFileInfo info(path, name); QFileInfo info(path, name);
if(info.isFile()) { if(info.isFile()) {
size = QVariant(info.size()); size = QVariant(info.size());
date_created = QVariant(info.created().toTime_t()); date_created = QVariant(info.created().toUTC().toTime_t());
} else { } else {
size = QVariant(QVariant::LongLong); size = QVariant(QVariant::LongLong);
date_created = QVariant(QVariant::UInt); date_created = QVariant(QVariant::UInt);
} }
date_modified = QVariant(info.lastModified().toTime_t()); date_modified = QVariant(info.lastModified().toUTC().toTime_t());
QSqlQuery query; QSqlQuery query;
query.prepare("REPLACE INTO sources (object_id, path, size, date_created, date_modified)" query.prepare("REPLACE INTO sources (object_id, path, size, date_created, date_modified)"
@@ -755,7 +756,7 @@ uint SQLiteDB::insertPhotoEntry(const QString &path, const QString &name, int pa
} }
QDateTime date = QFileInfo(path + "/" + name).created(); QDateTime date = QFileInfo(path + "/" + name).created();
date_created = date.toTime_t(); date_created = date.toUTC().toTime_t();
QString month_created = date.toString("yyyy/MM"); QString month_created = date.toString("yyyy/MM");
width = img.width(); width = img.width();
@@ -805,7 +806,7 @@ uint SQLiteDB::insertSavedataEntry(const QString &path, const QString &name, int
title = reader.value("TITLE", utf8name.constData()); title = reader.value("TITLE", utf8name.constData());
savedata_detail = reader.value("SAVEDATA_DETAIL", ""); savedata_detail = reader.value("SAVEDATA_DETAIL", "");
savedata_directory = reader.value("SAVEDATA_DIRECTORY", utf8name.constData()); savedata_directory = reader.value("SAVEDATA_DIRECTORY", utf8name.constData());
date_updated = QFileInfo(path + "/" + name).lastModified().toTime_t(); date_updated = QFileInfo(path + "/" + name).lastModified().toUTC().toTime_t();
} }
if((ohfi = insertDefaultEntry(path, name, title, parent, type)) == 0) { if((ohfi = insertDefaultEntry(path, name, title, parent, type)) == 0) {
@@ -880,14 +881,14 @@ bool SQLiteDB::deleteEntry(int ohfi, int root_ohfi)
void SQLiteDB::fillMetadata(const QSqlQuery &query, metadata_t &metadata) void SQLiteDB::fillMetadata(const QSqlQuery &query, metadata_t &metadata)
{ {
metadata.ohfi = query.value("ohfi").toInt(); metadata.ohfi = query.value(0).toInt(); // ohfi
metadata.ohfiParent = query.value("parent").toInt(); metadata.ohfiParent = query.value(1).toInt(); // parent
metadata.name = strdup(query.value("name").toByteArray().constData()); metadata.path = strdup(query.value(2).toByteArray().constData()); // path
metadata.path = strdup(query.value("path").toByteArray().constData()); metadata.name = strdup(query.value(3).toByteArray().constData()); // name
metadata.type = VITA_DIR_TYPE_MASK_REGULAR; metadata.type = VITA_DIR_TYPE_MASK_REGULAR;
metadata.dataType = (DataType)query.value("data_type").toInt(); metadata.dataType = (DataType)query.value(5).toInt(); // data_type
metadata.size = query.value("size").toULongLong(); metadata.size = query.value(6).toULongLong(); // size
metadata.dateTimeCreated = query.value("date_created").toInt(); metadata.dateTimeCreated = query.value(7).toInt(); // date_created
metadata.next_metadata = NULL; metadata.next_metadata = NULL;
//TODO: fill the rest of the metadata //TODO: fill the rest of the metadata
} }
@@ -1246,55 +1247,65 @@ bool SQLiteDB::insertVirtualEntries()
{ {
int ohfi; int ohfi;
if((ohfi = insertNodeEntry("Folders", VITA_DIR_TYPE_MASK_REGULAR, Video)) > 0) if((ohfi = insertNodeEntry("Folders", VITA_DIR_TYPE_MASK_REGULAR, Video)) > 0) {
insertVirtualEntry(ohfi); insertVirtualEntry(ohfi);
else } else {
return false; return false;
}
if((ohfi = insertNodeEntry("All", VITA_DIR_TYPE_MASK_ALL, Video)) > 0) if((ohfi = insertNodeEntry("All", VITA_DIR_TYPE_MASK_ALL, Video)) > 0) {
insertVirtualEntry(ohfi); insertVirtualEntry(ohfi);
else } else {
return false; return false;
}
if((ohfi = insertNodeEntry("Folders", VITA_DIR_TYPE_MASK_REGULAR, Photo)) > 0) if((ohfi = insertNodeEntry("Folders", VITA_DIR_TYPE_MASK_REGULAR, Photo)) > 0) {
insertVirtualEntry(ohfi); insertVirtualEntry(ohfi);
else } else {
return false; return false;
}
if((ohfi = insertNodeEntry("Month", VITA_DIR_TYPE_MASK_MONTH, Photo)) > 0) if((ohfi = insertNodeEntry("Month", VITA_DIR_TYPE_MASK_MONTH, Photo)) > 0) {
insertVirtualEntry(ohfi); insertVirtualEntry(ohfi);
else } else {
return false; return false;
}
if((ohfi = insertNodeEntry("All", VITA_DIR_TYPE_MASK_ALL, Photo)) > 0) if((ohfi = insertNodeEntry("All", VITA_DIR_TYPE_MASK_ALL, Photo)) > 0) {
insertVirtualEntry(ohfi); insertVirtualEntry(ohfi);
else } else {
return false; return false;
}
if((ohfi = insertNodeEntry("Artists", VITA_DIR_TYPE_MASK_ARTISTS, Music)) > 0) if((ohfi = insertNodeEntry("Artists", VITA_DIR_TYPE_MASK_ARTISTS, Music)) > 0) {
insertVirtualEntry(ohfi); insertVirtualEntry(ohfi);
else } else {
return false; return false;
}
if((ohfi = insertNodeEntry("Albums", VITA_DIR_TYPE_MASK_ALBUMS, Music)) > 0) if((ohfi = insertNodeEntry("Albums", VITA_DIR_TYPE_MASK_ALBUMS, Music)) > 0) {
insertVirtualEntry(ohfi); insertVirtualEntry(ohfi);
else } else {
return false; return false;
}
if((ohfi = insertNodeEntry("Songs", VITA_DIR_TYPE_MASK_SONGS, Music)) > 0) if((ohfi = insertNodeEntry("Songs", VITA_DIR_TYPE_MASK_SONGS, Music)) > 0) {
insertVirtualEntry(ohfi); insertVirtualEntry(ohfi);
else } else {
return false; return false;
}
if((ohfi = insertNodeEntry("Genres", VITA_DIR_TYPE_MASK_GENRES, Music)) > 0) if((ohfi = insertNodeEntry("Genres", VITA_DIR_TYPE_MASK_GENRES, Music)) > 0) {
insertVirtualEntry(ohfi); insertVirtualEntry(ohfi);
else } else {
return false; return false;
}
if((ohfi = insertNodeEntry("Playlists", VITA_DIR_TYPE_MASK_PLAYLISTS, Music)) > 0) if((ohfi = insertNodeEntry("Playlists", VITA_DIR_TYPE_MASK_PLAYLISTS, Music)) > 0) {
insertVirtualEntry(ohfi); insertVirtualEntry(ohfi);
else } else {
return false; return false;
}
return true; return true;
} }