Initial commit.

This commit is contained in:
Jonas Kvinge
2018-02-27 18:06:05 +01:00
parent 85d9664df7
commit b2b1ba7abe
1393 changed files with 177311 additions and 1 deletions

View File

@@ -0,0 +1,41 @@
include_directories(${PROTOBUF_INCLUDE_DIRS})
include_directories(${CMAKE_CURRENT_BINARY_DIR})
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
include_directories(${CMAKE_SOURCE_DIR}/src)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --std=c++0x")
set(SOURCES
core/closure.cpp
core/logging.cpp
core/messagehandler.cpp
core/messagereply.cpp
core/waitforsignal.cpp
core/workerpool.cpp
)
set(HEADERS
core/closure.h
core/messagehandler.h
core/messagereply.h
core/workerpool.h
)
if(APPLE)
list(APPEND SOURCES core/scoped_nsautorelease_pool.mm)
endif(APPLE)
qt5_wrap_cpp(MOC ${HEADERS})
add_library(libstrawberry-common STATIC
${SOURCES}
${MOC}
)
target_link_libraries(libstrawberry-common
#${PROTOBUF_LIBRARY}
${TAGLIB_LIBRARIES}
${CMAKE_THREAD_LIBS_INIT}
)
QT5_USE_MODULES(libstrawberry-common Core Network)

View File

@@ -0,0 +1,34 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// From Chromium src/base/macros.h
#include <stddef.h> // For size_t.
// The arraysize(arr) macro returns the # of elements in an array arr.
// The expression is a compile-time constant, and therefore can be
// used in defining new arrays, for example. If you use arraysize on
// a pointer by mistake, you will get a compile-time error.
//
// One caveat is that arraysize() doesn't accept any array of an
// anonymous type or a type defined inside a function. In these rare
// cases, you have to use the unsafe ARRAYSIZE_UNSAFE() macro below. This is
// due to a limitation in C++'s template system. The limitation might
// eventually be removed, but it hasn't happened yet.
// This template function declaration is used in defining arraysize.
// Note that the function doesn't need an implementation, as we only
// use its type.
template <typename T, size_t N>
char (&ArraySizeHelper(T (&array)[N]))[N];
// That gcc wants both of these prototypes seems mysterious. VC, for
// its part, can't decide which to use (another mystery). Matching of
// template overloads: the final frontier.
#ifndef _MSC_VER
template <typename T, size_t N>
char (&ArraySizeHelper(const T (&array)[N]))[N];
#endif
#define arraysize(array) (sizeof(ArraySizeHelper(array)))

View File

@@ -0,0 +1,71 @@
/* This file is part of Strawberry.
Copyright 2011, David Sansome <me@davidsansome.com>
Strawberry 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.
Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#include "closure.h"
#include "core/timeconstants.h"
namespace _detail {
ClosureBase::ClosureBase(ObjectHelper *helper)
: helper_(helper) {
}
ClosureBase::~ClosureBase() {
}
CallbackClosure::CallbackClosure(QObject *sender, const char *signal, std::function<void()> callback)
: ClosureBase(new ObjectHelper(sender, signal, this)),
callback_(callback) {
}
void CallbackClosure::Invoke() {
callback_();
}
ObjectHelper* ClosureBase::helper() const {
return helper_;
}
ObjectHelper::ObjectHelper(QObject *sender, const char *signal, ClosureBase *closure) : closure_(closure) {
connect(sender, signal, SLOT(Invoked()));
connect(sender, SIGNAL(destroyed()), SLOT(deleteLater()));
}
void ObjectHelper::Invoked() {
closure_->Invoke();
deleteLater();
}
void Unpack(QList<QGenericArgument>*) {}
} // namespace _detail
_detail::ClosureBase* NewClosure(QObject *sender, const char *signal, std::function<void()> callback) {
return new _detail::CallbackClosure(sender, signal, callback);
}
void DoAfter(QObject *receiver, const char *slot, int msec) {
QTimer::singleShot(msec, receiver, slot);
}
void DoInAMinuteOrSo(QObject *receiver, const char *slot) {
int msec = (60 + (qrand() % 60)) * kMsecPerSec;
DoAfter(receiver, slot, msec);
}

View File

@@ -0,0 +1,248 @@
/* This file is part of Strawberry.
Copyright 2011, David Sansome <me@davidsansome.com>
Strawberry 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.
Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef CLOSURE_H
#define CLOSURE_H
#include <chrono>
#include <functional>
#include <memory>
#include <QFuture>
#include <QFutureWatcher>
#include <QMetaMethod>
#include <QObject>
#include <QSharedPointer>
#include <QTimer>
namespace _detail {
class ObjectHelper;
// Interface for ObjectHelper to call on signal emission.
class ClosureBase {
public:
virtual ~ClosureBase();
virtual void Invoke() = 0;
// Tests only.
ObjectHelper* helper() const;
protected:
explicit ClosureBase(ObjectHelper*);
ObjectHelper* helper_;
private:
Q_DISABLE_COPY(ClosureBase);
};
// QObject helper as templated QObjects do not work.
// Connects to the given signal and invokes the closure when called.
// Deletes itself and the Closure after being invoked.
class ObjectHelper : public QObject {
Q_OBJECT
public:
ObjectHelper(QObject* parent, const char* signal, ClosureBase* closure);
private slots:
void Invoked();
private:
std::unique_ptr<ClosureBase> closure_;
Q_DISABLE_COPY(ObjectHelper);
};
// Helpers for unpacking a variadic template list.
// Base case of no arguments.
void Unpack(QList<QGenericArgument>*);
template <typename Arg>
void Unpack(QList<QGenericArgument>* list, const Arg& arg) {
list->append(Q_ARG(Arg, arg));
}
template <typename Head, typename... Tail>
void Unpack(QList<QGenericArgument>* list, const Head& head, const Tail&... tail) {
Unpack(list, head);
Unpack(list, tail...);
}
template <typename... Args>
class Closure : public ClosureBase {
public:
Closure(
QObject* sender,
const char* signal,
QObject* receiver,
const char* slot,
const Args&... args)
: ClosureBase(new ObjectHelper(sender, signal, this)),
// std::bind is the easiest way to store an argument list.
function_(std::bind(&Closure<Args...>::Call, this, args...)),
receiver_(receiver) {
const QMetaObject* meta_receiver = receiver->metaObject();
QByteArray normalised_slot = QMetaObject::normalizedSignature(slot + 1);
const int index = meta_receiver->indexOfSlot(normalised_slot.constData());
Q_ASSERT(index != -1);
slot_ = meta_receiver->method(index);
QObject::connect(receiver_, SIGNAL(destroyed()), helper_, SLOT(deleteLater()));
}
virtual void Invoke() {
function_();
}
private:
void Call(const Args&... args) {
QList<QGenericArgument> arg_list;
Unpack(&arg_list, args...);
slot_.invoke(
receiver_,
arg_list.size() > 0 ? arg_list[0] : QGenericArgument(),
arg_list.size() > 1 ? arg_list[1] : QGenericArgument(),
arg_list.size() > 2 ? arg_list[2] : QGenericArgument(),
arg_list.size() > 3 ? arg_list[3] : QGenericArgument(),
arg_list.size() > 4 ? arg_list[4] : QGenericArgument(),
arg_list.size() > 5 ? arg_list[5] : QGenericArgument(),
arg_list.size() > 6 ? arg_list[6] : QGenericArgument(),
arg_list.size() > 7 ? arg_list[7] : QGenericArgument(),
arg_list.size() > 8 ? arg_list[8] : QGenericArgument(),
arg_list.size() > 9 ? arg_list[9] : QGenericArgument());
}
std::function<void()> function_;
QObject* receiver_;
QMetaMethod slot_;
};
template <typename T, typename... Args>
class SharedClosure : public Closure<Args...> {
public:
SharedClosure(
QSharedPointer<T> sender,
const char* signal,
QObject* receiver,
const char* slot,
const Args&... args)
: Closure<Args...>(
sender.data(),
signal,
receiver,
slot,
args...),
data_(sender) {
}
private:
QSharedPointer<T> data_;
};
class CallbackClosure : public ClosureBase {
public:
CallbackClosure(QObject* sender, const char* signal,
std::function<void()> callback);
virtual void Invoke();
private:
std::function<void()> callback_;
};
} // namespace _detail
template <typename... Args>
_detail::ClosureBase* NewClosure(
QObject* sender,
const char* signal,
QObject* receiver,
const char* slot,
const Args&... args) {
return new _detail::Closure<Args...>(
sender, signal, receiver, slot, args...);
}
// QSharedPointer variant
template <typename T, typename... Args>
_detail::ClosureBase* NewClosure(
QSharedPointer<T> sender,
const char* signal,
QObject* receiver,
const char* slot,
const Args&... args) {
return new _detail::SharedClosure<T, Args...>(
sender, signal, receiver, slot, args...);
}
_detail::ClosureBase* NewClosure(QObject* sender, const char* signal, std::function<void()> callback);
template <typename... Args>
_detail::ClosureBase* NewClosure(QObject* sender, const char* signal, std::function<void(Args...)> callback, const Args&... args) {
return NewClosure(sender, signal, std::bind(callback, args...));
}
template <typename... Args>
_detail::ClosureBase* NewClosure(
QObject* sender,
const char* signal,
void (*callback)(Args...),
const Args&... args) {
return NewClosure(sender, signal, std::bind(callback, args...));
}
template <typename T, typename Unused, typename... Args>
_detail::ClosureBase* NewClosure(
QObject* sender,
const char* signal,
T* receiver, Unused (T::*callback)(Args...),
const Args&... args) {
return NewClosure(sender, signal, std::bind(callback, receiver, args...));
}
template <typename T, typename... Args>
_detail::ClosureBase* NewClosure(QFuture<T> future, QObject* receiver, const char* slot, const Args&... args) {
QFutureWatcher<T>* watcher = new QFutureWatcher<T>;
watcher->setFuture(future);
QObject::connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater()));
return NewClosure(watcher, SIGNAL(finished()), receiver, slot, args...);
}
template <typename T, typename F, typename... Args>
_detail::ClosureBase* NewClosure(QFuture<T> future, const F& callback, const Args&... args) {
QFutureWatcher<T>* watcher = new QFutureWatcher<T>;
watcher->setFuture(future);
QObject::connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater()));
return NewClosure(watcher, SIGNAL(finished()), callback, args...);
}
void DoAfter(QObject* receiver, const char* slot, int msec);
void DoAfter(std::function<void()> callback, std::chrono::milliseconds msec);
void DoInAMinuteOrSo(QObject* receiver, const char* slot);
template <typename R, typename P>
void DoAfter(std::function<void()> callback, std::chrono::duration<R, P> duration) {
QTimer* timer = new QTimer;
timer->setSingleShot(true);
NewClosure(timer, SIGNAL(timeout()), callback);
QObject::connect(timer, SIGNAL(timeout()), timer, SLOT(deleteLater()));
std::chrono::milliseconds msec = std::chrono::duration_cast<std::chrono::milliseconds>(duration);
timer->start(msec.count());
}
#endif // CLOSURE_H

View File

@@ -0,0 +1,137 @@
/* This file is part of Strawberry.
Copyright 2012, David Sansome <me@davidsansome.com>
Strawberry 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.
Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef CONCURRENTRUN_H
#define CONCURRENTRUN_H
#include <functional>
#include <QFuture>
#include <QRunnable>
#include <QThreadPool>
/*
The aim of ThreadFunctor classes and ConcurrentRun::Run() functions is to
complete QtConcurrentRun, which lack support for using a particular
QThreadPool, as it always uses QThreadPool::globalInstance().
This is problematic when we do not want to share the same thread pool over
all the application, but want to keep the convenient QtConcurrent::run()
functor syntax.
With ConcurrentRun::Run(), time critical changes can be performed in their
own pool, which is not empty by other actions (as it happens when using
QtConcurrentRun::run()).
ThreadFunctor classes are used to store a functor and its arguments, and
Run() functions are used for convenience: to directly create a new
ThreadFunctor object and start it.
*/
/*
Base abstract classes ThreadFunctorBase and ThreadFunctor (for void and
non-void result):
*/
template<typename ReturnType>
class ThreadFunctorBase : public QFutureInterface<ReturnType>, public QRunnable {
public:
ThreadFunctorBase() {}
QFuture<ReturnType> Start(QThreadPool* thread_pool) {
this->setRunnable(this);
this->reportStarted();
Q_ASSERT(thread_pool);
QFuture<ReturnType> future = this->future();
thread_pool->start(this, 0 /* priority: currently we do not support
changing the priority. Might be added later
if needed */);
return future;
}
virtual void run() = 0;
};
template <typename ReturnType, typename... Args>
class ThreadFunctor : public ThreadFunctorBase<ReturnType> {
public:
ThreadFunctor(std::function<ReturnType (Args...)> function,
Args... args)
: function_(std::bind(function, args...)) {
}
virtual void run() {
this->reportResult(function_());
this->reportFinished();
}
private:
std::function<ReturnType()> function_;
};
// Partial specialisation for void return type.
template <typename... Args>
class ThreadFunctor <void, Args...> : public ThreadFunctorBase<void> {
public:
ThreadFunctor(std::function<void (Args...)> function,
Args... args)
: function_(std::bind(function, args...)) {
}
virtual void run() {
function_();
this->reportFinished();
}
private:
std::function<void()> function_;
};
/*
Run functions
*/
namespace ConcurrentRun {
// Empty argument form.
template <typename ReturnType>
QFuture<ReturnType> Run(
QThreadPool* threadpool,
std::function<ReturnType ()> function) {
return (new ThreadFunctor<ReturnType>(function))->Start(threadpool);
}
// Function object with arguments form.
template <typename ReturnType, typename... Args>
QFuture<ReturnType> Run(
QThreadPool* threadpool,
std::function<ReturnType (Args...)> function,
const Args&... args) {
return (new ThreadFunctor<ReturnType, Args...>(
function, args...))->Start(threadpool);
}
// Support passing C function pointers instead of function objects.
template <typename ReturnType, typename... Args>
QFuture<ReturnType> Run(
QThreadPool* threadpool,
ReturnType (*function) (Args...),
const Args&... args) {
return Run(
threadpool, std::function<ReturnType (Args...)>(function), args...);
}
}
#endif // CONCURRENTRUN_H

View File

@@ -0,0 +1,66 @@
/* This file is part of Clementine.
Copyright 2016, John Maguire <john.maguire@gmail.com>
Clementine 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.
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LAZY_H
#define LAZY_H
#include <functional>
#include <memory>
// Helper for lazy initialisation of objects.
// Usage:
// Lazy<Foo> my_lazy_object([]() { return new Foo; });
template <typename T>
class Lazy {
public:
explicit Lazy(std::function<T*()> init) : init_(init) {}
// Convenience constructor that will lazily default construct the object.
Lazy() : init_([]() { return new T; }) {}
T* get() const {
CheckInitialised();
return ptr_.get();
}
typename std::add_lvalue_reference<T>::type operator*() const {
CheckInitialised();
return *ptr_;
}
T* operator->() const { return get(); }
// Returns true if the object is not yet initialised.
explicit operator bool() const { return ptr_; }
// Deletes the underlying object and will re-run the initialisation function
// if the object is requested again.
void reset() { ptr_.reset(nullptr); }
private:
void CheckInitialised() const {
if (!ptr_) {
ptr_.reset(init_());
}
}
const std::function<T*()> init_;
mutable std::unique_ptr<T> ptr_;
};
#endif // LAZY_H

View File

@@ -0,0 +1,292 @@
/* This file is part of Strawberry.
Copyright 2011, David Sansome <me@davidsansome.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include <glib.h>
#include <iostream>
#include <QtGlobal>
#include <cxxabi.h>
#ifdef Q_OS_UNIX
#include <execinfo.h>
#endif
#include <QCoreApplication>
#include <QDateTime>
#include <QStringList>
#include <QtMessageHandler>
#include "logging.h"
namespace logging {
static Level sDefaultLevel = Level_Debug;
static QMap<QString, Level>* sClassLevels = nullptr;
static QIODevice *sNullDevice = nullptr;
const char* kDefaultLogLevels = "GstEnginePipeline:2,*:3";
static const char *kMessageHandlerMagic = "__logging_message__";
static const int kMessageHandlerMagicLength = strlen(kMessageHandlerMagic);
static QtMessageHandler sOriginalMessageHandler = nullptr;
void GLog(const char *domain, int level, const char *message, void *user_data) {
switch (level) {
case G_LOG_FLAG_RECURSION:
case G_LOG_FLAG_FATAL:
case G_LOG_LEVEL_ERROR:
case G_LOG_LEVEL_CRITICAL: qLog(Error) << message; break;
case G_LOG_LEVEL_WARNING: qLog(Warning) << message; break;
case G_LOG_LEVEL_MESSAGE:
case G_LOG_LEVEL_INFO: qLog(Info) << message; break;
case G_LOG_LEVEL_DEBUG:
default: qLog(Debug) << message; break;
}
}
static void MessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message) {
if (strncmp(kMessageHandlerMagic, message.toLocal8Bit().data(), kMessageHandlerMagicLength) == 0) {
fprintf(stderr, "%s\n", message.toLocal8Bit().data() + kMessageHandlerMagicLength);
return;
}
Level level = Level_Debug;
switch (type) {
case QtFatalMsg:
case QtCriticalMsg: level = Level_Error; break;
case QtWarningMsg: level = Level_Warning; break;
case QtDebugMsg:
default: level = Level_Debug; break;
}
for (const QString& line : message.split('\n')) {
CreateLogger(level, "unknown", -1) << line.toLocal8Bit().constData();
}
if (type == QtFatalMsg) {
abort();
}
}
void Init() {
delete sClassLevels;
delete sNullDevice;
sClassLevels = new QMap<QString, Level>();
sNullDevice = new NullDevice;
sNullDevice->open(QIODevice::ReadWrite);
// Catch other messages from Qt
if (!sOriginalMessageHandler) {
sOriginalMessageHandler = qInstallMessageHandler(MessageHandler);
}
}
void SetLevels(const QString &levels) {
if (!sClassLevels) return;
for (const QString& item : levels.split(',')) {
const QStringList class_level = item.split(':');
QString class_name;
bool ok = false;
int level = Level_Error;
if (class_level.count() == 1) {
level = class_level.last().toInt(&ok);
}
else if (class_level.count() == 2) {
class_name = class_level.first();
level = class_level.last().toInt(&ok);
}
if (!ok || level < Level_Error || level > Level_Debug) {
continue;
}
if (class_name.isEmpty() || class_name == "*") {
sDefaultLevel = (Level) level;
}
else {
sClassLevels->insert(class_name, (Level) level);
}
}
}
QString ParsePrettyFunction(const char *pretty_function) {
// Get the class name out of the function name.
QString class_name = pretty_function;
const int paren = class_name.indexOf('(');
if (paren != -1) {
const int colons = class_name.lastIndexOf("::", paren);
if (colons != -1) {
class_name = class_name.left(colons);
}
else {
class_name = class_name.left(paren);
}
}
const int space = class_name.lastIndexOf(' ');
if (space != -1) {
class_name = class_name.mid(space+1);
}
return class_name;
}
QDebug CreateLogger(Level level, const QString &class_name, int line) {
// Map the level to a string
const char *level_name = nullptr;
switch (level) {
case Level_Debug: level_name = " DEBUG "; break;
case Level_Info: level_name = " INFO "; break;
case Level_Warning: level_name = " WARN "; break;
case Level_Error: level_name = " ERROR "; break;
case Level_Fatal: level_name = " FATAL "; break;
}
// Check the settings to see if we're meant to show or hide this message.
Level threshold_level = sDefaultLevel;
if (sClassLevels && sClassLevels->contains(class_name)) {
threshold_level = sClassLevels->value(class_name);
}
if (level > threshold_level) {
return QDebug(sNullDevice);
}
QString function_line = class_name;
if (line != -1) {
function_line += ":" + QString::number(line);
}
QtMsgType type = QtDebugMsg;
if (level == Level_Fatal) {
type = QtFatalMsg;
}
QDebug ret(type);
ret.nospace() << kMessageHandlerMagic
<< QDateTime::currentDateTime()
.toString("hh:mm:ss.zzz")
.toLatin1()
.constData() << level_name
<< function_line.leftJustified(32).toLatin1().constData();
return ret.space();
}
QString CXXDemangle(const QString &mangled_function) {
int status;
char* demangled_function = abi::__cxa_demangle(mangled_function.toLatin1().constData(), nullptr, nullptr, &status);
if (status == 0) {
QString ret = QString::fromLatin1(demangled_function);
free(demangled_function);
return ret;
}
return mangled_function; // Probably not a C++ function.
}
QString DarwinDemangle(const QString &symbol) {
QStringList split = symbol.split(' ', QString::SkipEmptyParts);
QString mangled_function = split[3];
return CXXDemangle(mangled_function);
}
QString LinuxDemangle(const QString &symbol) {
QRegExp regex("\\(([^+]+)");
if (!symbol.contains(regex)) {
return symbol;
}
QString mangled_function = regex.cap(1);
return CXXDemangle(mangled_function);
}
QString DemangleSymbol(const QString &symbol) {
#ifdef Q_OS_DARWIN
return DarwinDemangle(symbol);
#elif defined(Q_OS_LINUX)
return LinuxDemangle(symbol);
#else
return symbol;
#endif
}
void DumpStackTrace() {
#ifdef Q_OS_UNIX
void* callstack[128];
int callstack_size = backtrace(reinterpret_cast<void**>(&callstack), sizeof(callstack));
char** symbols = backtrace_symbols(reinterpret_cast<void**>(&callstack), callstack_size);
// Start from 1 to skip ourself.
for (int i = 1; i < callstack_size; ++i) {
std::cerr << DemangleSymbol(QString::fromLatin1(symbols[i])).toStdString() << std::endl;
}
free(symbols);
#else
qLog(Debug) << "FIXME: Implement printing stack traces on this platform";
#endif
}
#if 0
QDebug CreateLoggerFatal(int line, const char *class_name) { return qCreateLogger(line, class_name, Fatal); }
QDebug CreateLoggerError(int line, const char *class_name) { return qCreateLogger(line, class_name, Error); }
#ifdef QT_NO_WARNING_OUTPUT
QNoDebug CreateLoggerWarning(int, const char*) { return QNoDebug(); }
#else
QDebug CreateLoggerWarning(int line, const char *class_name) { return qCreateLogger(line, class_name, Warning); }
#endif // QT_NO_WARNING_OUTPUT
#ifdef QT_NO_DEBUG_OUTPUT
QNoDebug CreateLoggerInfo(int, const char*) { return QNoDebug(); }
QNoDebug CreateLoggerDebug(int, const char*) { return QNoDebug(); }
#else
QDebug CreateLoggerInfo(int line, const char *class_name) { return qCreateLogger(line, class_name, Info); }
QDebug CreateLoggerDebug(int line, const char *class_name) { return qCreateLogger(line, class_name, Debug); }
#endif // QT_NO_DEBUG_OUTPUT
#endif
} // namespace logging
namespace {
template <typename T>
QString print_duration(T duration, const std::string& unit) {
return QString("%1%2").arg(duration.count()).arg(unit.c_str());
}
} // namespace
QDebug operator<<(QDebug dbg, std::chrono::seconds secs) {
dbg.nospace() << print_duration(secs, "s");
return dbg.space();
}

View File

@@ -0,0 +1,93 @@
/* This file is part of Strawberry.
Copyright 2011, David Sansome <me@davidsansome.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef LOGGING_H
#define LOGGING_H
#include <chrono>
#include <string>
#include <QDebug>
#ifdef QT_NO_DEBUG_STREAM
# define qLog(level) while (false) QNoDebug()
#else
#define qLog(level) \
logging::CreateLogger(logging::Level_##level, \
logging::ParsePrettyFunction(__PRETTY_FUNCTION__), \
__LINE__)
#endif // QT_NO_DEBUG_STREAM
#if 0
#define qLog(level) \
logging::CreateLogger##level(__LINE__, __PRETTY_FUNCTION__)
#define qCreateLogger(line, class_name, level) \
logging::CreateLogger(logging::Level_##level, \
logging::ParsePrettyFunction(class_name), \
line)
#endif
namespace logging {
class NullDevice : public QIODevice {
protected:
qint64 readData(char*, qint64) { return -1; }
qint64 writeData(const char*, qint64 len) { return len; }
};
enum Level {
Level_Fatal = -1,
Level_Error = 0,
Level_Warning,
Level_Info,
Level_Debug,
};
void Init();
void SetLevels(const QString& levels);
void DumpStackTrace();
QString ParsePrettyFunction(const char* pretty_function);
QDebug CreateLogger(Level level, const QString &class_name, int line);
QDebug CreateLoggerFatal(int line, const char* class_name);
QDebug CreateLoggerError(int line, const char* class_name);
#ifdef QT_NO_WARNING_OUTPUT
QNoDebug CreateLoggerWarning(int, const char*);
#else
QDebug CreateLoggerWarning(int line, const char* class_name);
#endif // QT_NO_WARNING_OUTPUT
#ifdef QT_NO_DEBUG_OUTPUT
QNoDebug CreateLoggerInfo(int, const char*);
QNoDebug CreateLoggerDebug(int, const char*);
#else
QDebug CreateLoggerInfo(int line, const char* class_name);
QDebug CreateLoggerDebug(int line, const char* class_name);
#endif // QT_NO_DEBUG_OUTPUT
void GLog(const char* domain, int level, const char* message, void* user_data);
extern const char* kDefaultLogLevels;
}
QDebug operator<<(QDebug debug, std::chrono::seconds secs);
#endif // LOGGING_H

View File

@@ -0,0 +1,112 @@
/* This file is part of Strawberry.
Copyright 2011, David Sansome <me@davidsansome.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include "messagehandler.h"
#include "core/logging.h"
#include <QAbstractSocket>
#include <QLocalSocket>
#include <QDataStream>
_MessageHandlerBase::_MessageHandlerBase(QIODevice *device, QObject *parent)
: QObject(parent),
device_(nullptr),
flush_abstract_socket_(nullptr),
flush_local_socket_(nullptr),
reading_protobuf_(false),
expected_length_(0),
is_device_closed_(false) {
if (device) {
SetDevice(device);
}
}
void _MessageHandlerBase::SetDevice(QIODevice *device) {
device_ = device;
buffer_.open(QIODevice::ReadWrite);
connect(device, SIGNAL(readyRead()), SLOT(DeviceReadyRead()));
// Yeah I know.
if (QAbstractSocket *socket = qobject_cast<QAbstractSocket*>(device)) {
flush_abstract_socket_ = &QAbstractSocket::flush;
connect(socket, SIGNAL(disconnected()), SLOT(DeviceClosed()));
}
else if (QLocalSocket* socket = qobject_cast<QLocalSocket*>(device)) {
flush_local_socket_ = &QLocalSocket::flush;
connect(socket, SIGNAL(disconnected()), SLOT(DeviceClosed()));
}
else {
qFatal("Unsupported device type passed to _MessageHandlerBase");
}
}
void _MessageHandlerBase::DeviceReadyRead() {
while (device_->bytesAvailable()) {
if (!reading_protobuf_) {
// Read the length of the next message
QDataStream s(device_);
s >> expected_length_;
reading_protobuf_ = true;
}
// Read some of the message
buffer_.write(device_->read(expected_length_ - buffer_.size()));
// Did we get everything?
if (buffer_.size() == expected_length_) {
// Parse the message
if (!RawMessageArrived(buffer_.data())) {
qLog(Error) << "Malformed protobuf message";
device_->close();
return;
}
// Clear the buffer
buffer_.close();
buffer_.setData(QByteArray());
buffer_.open(QIODevice::ReadWrite);
reading_protobuf_ = false;
}
}
}
void _MessageHandlerBase::WriteMessage(const QByteArray &data) {
QDataStream s(device_);
s << quint32(data.length());
s.writeRawData(data.data(), data.length());
// Sorry.
if (flush_abstract_socket_) {
((static_cast<QAbstractSocket*>(device_))->*(flush_abstract_socket_))();
}
else if (flush_local_socket_) {
((static_cast<QLocalSocket*>(device_))->*(flush_local_socket_))();
}
}
void _MessageHandlerBase::DeviceClosed() {
is_device_closed_ = true;
AbortAll();
}

View File

@@ -0,0 +1,181 @@
/* This file is part of Strawberry.
Copyright 2011, David Sansome <me@davidsansome.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef MESSAGEHANDLER_H
#define MESSAGEHANDLER_H
#include <QBuffer>
#include <QMap>
#include <QMutex>
#include <QMutexLocker>
#include <QObject>
#include <QSemaphore>
#include <QThread>
#include "core/logging.h"
#include "core/messagereply.h"
class QAbstractSocket;
class QIODevice;
class QLocalSocket;
#define QStringFromStdString(x) QString::fromUtf8(x.data(), x.size())
#define DataCommaSizeFromQString(x) x.toUtf8().constData(), x.toUtf8().length()
// Reads and writes uint32 length encoded protobufs to a socket.
// This base QObject is separate from AbstractMessageHandler because moc can't
// handle templated classes. Use AbstractMessageHandler instead.
class _MessageHandlerBase : public QObject {
Q_OBJECT
public:
// device can be NULL, in which case you must call SetDevice before writing
// any messages.
_MessageHandlerBase(QIODevice* device, QObject* parent);
void SetDevice(QIODevice* device);
// After this is true, messages cannot be sent to the handler any more.
bool is_device_closed() const { return is_device_closed_; }
protected slots:
void WriteMessage(const QByteArray& data);
void DeviceReadyRead();
virtual void DeviceClosed();
protected:
virtual bool RawMessageArrived(const QByteArray& data) = 0;
virtual void AbortAll() = 0;
protected:
typedef bool (QAbstractSocket::*FlushAbstractSocket)();
typedef bool (QLocalSocket::*FlushLocalSocket)();
QIODevice* device_;
FlushAbstractSocket flush_abstract_socket_;
FlushLocalSocket flush_local_socket_;
bool reading_protobuf_;
quint32 expected_length_;
QBuffer buffer_;
bool is_device_closed_;
};
// Reads and writes uint32 length encoded MessageType messages to a socket.
// You should subclass this and implement the MessageArrived(MessageType)
// method.
template <typename MT>
class AbstractMessageHandler : public _MessageHandlerBase {
public:
AbstractMessageHandler(QIODevice* device, QObject* parent);
~AbstractMessageHandler() { AbortAll(); }
typedef MT MessageType;
typedef MessageReply<MT> ReplyType;
// Serialises the message and writes it to the socket. This version MUST be
// called from the thread in which the AbstractMessageHandler was created.
void SendMessage(const MessageType& message);
// Serialises the message and writes it to the socket. This version may be
// called from any thread.
void SendMessageAsync(const MessageType& message);
// Sends the request message inside and takes ownership of the MessageReply.
// The MessageReply's Finished() signal will be emitted when a reply arrives
// with the same ID. Must be called from my thread.
void SendRequest(ReplyType* reply);
// Sets the "id" field of reply to the same as the request, and sends the
// reply on the socket. Used on the worker side.
void SendReply(const MessageType& request, MessageType* reply);
protected:
// Called when a message is received from the socket.
virtual void MessageArrived(const MessageType& message) {}
// _MessageHandlerBase
bool RawMessageArrived(const QByteArray& data);
void AbortAll();
private:
QMap<int, ReplyType*> pending_replies_;
};
template <typename MT>
AbstractMessageHandler<MT>::AbstractMessageHandler(QIODevice* device,
QObject* parent)
: _MessageHandlerBase(device, parent) {}
template <typename MT>
void AbstractMessageHandler<MT>::SendMessage(const MessageType& message) {
Q_ASSERT(QThread::currentThread() == thread());
std::string data = message.SerializeAsString();
WriteMessage(QByteArray(data.data(), data.size()));
}
template <typename MT>
void AbstractMessageHandler<MT>::SendMessageAsync(const MessageType& message) {
std::string data = message.SerializeAsString();
metaObject()->invokeMethod(this, "WriteMessage", Qt::QueuedConnection,
Q_ARG(QByteArray, QByteArray(data.data(), data.size())));
}
template<typename MT>
void AbstractMessageHandler<MT>::SendRequest(ReplyType* reply) {
pending_replies_[reply->id()] = reply;
SendMessage(reply->request_message());
}
template<typename MT>
void AbstractMessageHandler<MT>::SendReply(const MessageType& request,
MessageType* reply) {
reply->set_id(request.id());
SendMessage(*reply);
}
template<typename MT>
bool AbstractMessageHandler<MT>::RawMessageArrived(const QByteArray& data) {
MessageType message;
if (!message.ParseFromArray(data.constData(), data.size())) {
return false;
}
ReplyType* reply = pending_replies_.take(message.id());
if (reply) {
// This is a reply to a message that we created earlier.
reply->SetReply(message);
} else {
MessageArrived(message);
}
return true;
}
template<typename MT>
void AbstractMessageHandler<MT>::AbortAll() {
for (ReplyType* reply : pending_replies_) {
reply->Abort();
}
pending_replies_.clear();
}
#endif // MESSAGEHANDLER_H

View File

@@ -0,0 +1,38 @@
/* This file is part of Strawberry.
Copyright 2011, David Sansome <me@davidsansome.com>
Strawberry 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.
Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#include "messagereply.h"
_MessageReplyBase::_MessageReplyBase(QObject *parent)
: QObject(parent), finished_(false), success_(false) {}
bool _MessageReplyBase::WaitForFinished() {
qLog(Debug) << "Waiting on ID" << id();
semaphore_.acquire();
qLog(Debug) << "Acquired ID" << id();
return success_;
}
void _MessageReplyBase::Abort() {
Q_ASSERT(!finished_);
finished_ = true;
success_ = false;
emit Finished(success_);
qLog(Debug) << "Releasing ID" << id() << "(aborted)";
semaphore_.release();
}

View File

@@ -0,0 +1,97 @@
/* This file is part of Strawberry.
Copyright 2011, David Sansome <me@davidsansome.com>
Strawberry 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.
Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MESSAGEREPLY_H
#define MESSAGEREPLY_H
#include <QObject>
#include <QSemaphore>
#include "core/logging.h"
// Base QObject for a reply future class that is returned immediately for
// requests that will occur in the background. Similar to QNetworkReply.
// Use MessageReply instead.
class _MessageReplyBase : public QObject {
Q_OBJECT
public:
_MessageReplyBase(QObject* parent = nullptr);
virtual int id() const = 0;
bool is_finished() const { return finished_; }
bool is_successful() const { return success_; }
// Waits for the reply to finish by waiting on a semaphore. Never call this
// from the MessageHandler's thread or it will block forever.
// Returns true if the call was successful.
bool WaitForFinished();
void Abort();
signals:
void Finished(bool success);
protected:
bool finished_;
bool success_;
QSemaphore semaphore_;
};
// A reply future class that is returned immediately for requests that will
// occur in the background. Similar to QNetworkReply.
template <typename MessageType>
class MessageReply : public _MessageReplyBase {
public:
MessageReply(const MessageType& request_message, QObject* parent = nullptr);
int id() const { return request_message_.id(); }
const MessageType& request_message() const { return request_message_; }
const MessageType& message() const { return reply_message_; }
void SetReply(const MessageType& message);
private:
MessageType request_message_;
MessageType reply_message_;
};
template<typename MessageType>
MessageReply<MessageType>::MessageReply(const MessageType& request_message,
QObject* parent)
: _MessageReplyBase(parent)
{
request_message_.MergeFrom(request_message);
}
template<typename MessageType>
void MessageReply<MessageType>::SetReply(const MessageType& message) {
Q_ASSERT(!finished_);
reply_message_.MergeFrom(message);
finished_ = true;
success_ = true;
emit Finished(success_);
qLog(Debug) << "Releasing ID" << id() << "(finished)";
semaphore_.release();
}
#endif // MESSAGEREPLY_H

View File

@@ -0,0 +1,33 @@
/* This file is part of Strawberry.
Copyright 2012, David Sansome <me@davidsansome.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef OVERRIDE_H
#define OVERRIDE_H
// Defines the OVERRIDE macro as C++11's override control keyword if
// it is available.
#ifndef __has_extension
#define __has_extension(x) 0
#endif
#if __has_extension(cxx_override_control) // Clang feature checking macro.
# define OVERRIDE override
#else
# define OVERRIDE
#endif
#endif // OVERRIDE_H

View File

@@ -0,0 +1,26 @@
/* This file is part of Strawberry.
Copyright 2011, David Sansome <me@davidsansome.com>
Strawberry 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.
Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#include "waitforsignal.h"
#include <QEventLoop>
void WaitForSignal(QObject *sender, const char *signal) {
QEventLoop loop;
QObject::connect(sender, signal, &loop, SLOT(quit()));
loop.exec();
}

View File

@@ -0,0 +1,25 @@
/* This file is part of Strawberry.
Copyright 2011, David Sansome <me@davidsansome.com>
Strawberry 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.
Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef WAITFORSIGNAL_H
#define WAITFORSIGNAL_H
class QObject;
void WaitForSignal(QObject *sender, const char *signal);
#endif // WAITFORSIGNAL_H

View File

@@ -0,0 +1,20 @@
/* This file is part of Strawberry.
Copyright 2011, David Sansome <me@davidsansome.com>
Strawberry 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.
Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#include "workerpool.h"
_WorkerPoolBase::_WorkerPoolBase(QObject *parent) : QObject(parent) {}

View File

@@ -0,0 +1,402 @@
/* This file is part of Strawberry.
Copyright 2011, David Sansome <me@davidsansome.com>
Strawberry 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.
Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef WORKERPOOL_H
#define WORKERPOOL_H
#include <QAtomicInt>
#include <QCoreApplication>
#include <QFile>
#include <QLocalServer>
#include <QLocalSocket>
#include <QMutex>
#include <QObject>
#include <QProcess>
#include <QQueue>
#include <QThread>
#include "core/closure.h"
#include "core/logging.h"
// Base class containing signals and slots - required because moc doesn't do
// templated objects.
class _WorkerPoolBase : public QObject {
Q_OBJECT
public:
_WorkerPoolBase(QObject* parent = nullptr);
signals:
// Emitted when a worker failed to start. This usually happens when the
// worker wasn't found, or couldn't be executed.
void WorkerFailedToStart();
protected slots:
virtual void DoStart() {}
virtual void NewConnection() {}
virtual void ProcessError(QProcess::ProcessError) {}
virtual void SendQueuedMessages() {}
};
// Manages a pool of one or more external processes. A local socket server is
// started for each process, and the address is passed to the process as
// argv[1]. The process is expected to connect back to the socket server, and
// when it does a HandlerType is created for it.
// Instances of HandlerType are created in the WorkerPool's thread.
template <typename HandlerType>
class WorkerPool : public _WorkerPoolBase {
public:
WorkerPool(QObject* parent = nullptr);
~WorkerPool();
typedef typename HandlerType::MessageType MessageType;
typedef typename HandlerType::ReplyType ReplyType;
// Sets the name of the worker executable. This is looked for first in the
// current directory, and then in $PATH. You must call this before calling
// Start().
void SetExecutableName(const QString& executable_name);
// Sets the number of worker process to use. Defaults to
// 1 <= (processors / 2) <= 2.
void SetWorkerCount(int count);
// Sets the prefix to use for the local server (on unix this is a named pipe
// in /tmp). Defaults to QApplication::applicationName(). A random number
// is appended to this name when creating each server.
void SetLocalServerName(const QString& local_server_name);
// Starts all workers.
void Start();
// Fills in the message's "id" field and creates a reply future. The message
// is queued and the WorkerPool's thread will send it to the next available
// worker. Can be called from any thread.
ReplyType* SendMessageWithReply(MessageType* message);
protected:
// These are all reimplemented slots, they are called on the WorkerPool's
// thread.
void DoStart();
void NewConnection();
void ProcessError(QProcess::ProcessError error);
void SendQueuedMessages();
private:
struct Worker {
Worker() : local_server_(NULL), local_socket_(NULL), process_(NULL), handler_(NULL) {}
QLocalServer *local_server_;
QLocalSocket *local_socket_;
QProcess *process_;
HandlerType* handler_;
};
// Must only ever be called on my thread.
void StartOneWorker(Worker* worker);
template <typename T>
Worker* FindWorker(T Worker::*member, T value) {
for (typename QList<Worker>::iterator it = workers_.begin() ;
it != workers_.end() ; ++it) {
if ((*it).*member == value) {
return &(*it);
}
}
return NULL;
}
template <typename T>
void DeleteQObjectPointerLater(T** p) {
if (*p) {
(*p)->deleteLater();
*p = NULL;
}
}
// Creates a new reply future for the request with the next sequential ID,
// and sets the request's ID to the ID of the reply. Can be called from any
// thread
ReplyType* NewReply(MessageType* message);
// Returns the next handler, or NULL if there isn't one. Must be called from
// my thread.
HandlerType* NextHandler() const;
private:
QString local_server_name_;
QString executable_name_;
QString executable_path_;
int worker_count_;
mutable int next_worker_;
QList<Worker> workers_;
QAtomicInt next_id_;
QMutex message_queue_mutex_;
QQueue<ReplyType*> message_queue_;
};
template <typename HandlerType>
WorkerPool<HandlerType>::WorkerPool(QObject* parent)
: _WorkerPoolBase(parent),
next_worker_(0),
next_id_(0)
{
worker_count_ = qBound(1, QThread::idealThreadCount() / 2, 2);
local_server_name_ = qApp->applicationName().toLower();
if (local_server_name_.isEmpty())
local_server_name_ = "workerpool";
}
template <typename HandlerType>
WorkerPool<HandlerType>::~WorkerPool() {
for (const Worker& worker : workers_) {
if (worker.local_socket_ && worker.process_) {
disconnect(worker.process_, SIGNAL(error(QProcess::ProcessError)), this, SLOT(ProcessError(QProcess::ProcessError)));
// The worker is connected. Close his socket and wait for him to exit.
qLog(Debug) << "Closing worker socket";
worker.local_socket_->close();
worker.process_->waitForFinished(500);
}
if (worker.process_ && worker.process_->state() == QProcess::Running) {
// The worker is still running - kill it.
qLog(Debug) << "Killing worker process";
worker.process_->terminate();
if (!worker.process_->waitForFinished(500)) {
worker.process_->kill();
}
}
}
for (ReplyType* reply : message_queue_) {
reply->Abort();
}
}
template <typename HandlerType>
void WorkerPool<HandlerType>::SetWorkerCount(int count) {
Q_ASSERT(workers_.isEmpty());
worker_count_ = count;
}
template <typename HandlerType>
void WorkerPool<HandlerType>::SetLocalServerName(const QString& local_server_name) {
Q_ASSERT(workers_.isEmpty());
local_server_name_ = local_server_name;
}
template <typename HandlerType>
void WorkerPool<HandlerType>::SetExecutableName(const QString& executable_name) {
Q_ASSERT(workers_.isEmpty());
executable_name_ = executable_name;
}
template <typename HandlerType>
void WorkerPool<HandlerType>::Start() {
metaObject()->invokeMethod(this, "DoStart");
}
template <typename HandlerType>
void WorkerPool<HandlerType>::DoStart() {
Q_ASSERT(workers_.isEmpty());
Q_ASSERT(!executable_name_.isEmpty());
Q_ASSERT(QThread::currentThread() == thread());
// Find the executable if we can, default to searching $PATH
executable_path_ = executable_name_;
QStringList search_path;
search_path << qApp->applicationDirPath();
#ifdef Q_OS_MAC
search_path << qApp->applicationDirPath() + "/../PlugIns";
#endif
for (const QString& path_prefix : search_path) {
const QString executable_path = path_prefix + "/" + executable_name_;
if (QFile::exists(executable_path)) {
executable_path_ = executable_path;
break;
}
}
// Start all the workers
for (int i = 0; i < worker_count_; ++i) {
Worker worker;
StartOneWorker(&worker);
workers_ << worker;
}
}
template <typename HandlerType>
void WorkerPool<HandlerType>::StartOneWorker(Worker *worker) {
Q_ASSERT(QThread::currentThread() == thread());
DeleteQObjectPointerLater(&worker->local_server_);
DeleteQObjectPointerLater(&worker->local_socket_);
DeleteQObjectPointerLater(&worker->process_);
DeleteQObjectPointerLater(&worker->handler_);
worker->local_server_ = new QLocalServer(this);
worker->process_ = new QProcess(this);
connect(worker->local_server_, SIGNAL(newConnection()), SLOT(NewConnection()));
connect(worker->process_, SIGNAL(error(QProcess::ProcessError)), SLOT(ProcessError(QProcess::ProcessError)));
// Create a server, find an unused name and start listening
forever {
const int unique_number = qrand() ^ ((int)(quint64(this) & 0xFFFFFFFF));
const QString name = QString("%1_%2").arg(local_server_name_).arg(unique_number);
if (worker->local_server_->listen(name)) {
break;
}
}
qLog(Debug) << "Starting worker" << worker << executable_path_ << worker->local_server_->fullServerName();
// Start the process
worker->process_->setProcessChannelMode(QProcess::ForwardedChannels);
worker->process_->start(executable_path_, QStringList() << worker->local_server_->fullServerName());
}
template <typename HandlerType>
void WorkerPool<HandlerType>::NewConnection() {
Q_ASSERT(QThread::currentThread() == thread());
QLocalServer *server = qobject_cast<QLocalServer*>(sender());
// Find the worker with this server.
Worker* worker = FindWorker(&Worker::local_server_, server);
if (!worker) return;
qLog(Debug) << "Worker" << worker << "connected to" << server->fullServerName();
// Accept the connection.
worker->local_socket_ = server->nextPendingConnection();
// We only ever accept one connection per worker, so destroy the server now.
worker->local_socket_->setParent(this);
worker->local_server_->deleteLater();
worker->local_server_ = NULL;
// Create the handler.
worker->handler_ = new HandlerType(worker->local_socket_, this);
SendQueuedMessages();
}
template <typename HandlerType>
void WorkerPool<HandlerType>::ProcessError(QProcess::ProcessError error) {
Q_ASSERT(QThread::currentThread() == thread());
QProcess *process = qobject_cast<QProcess*>(sender());
// Find the worker with this process.
Worker *worker = FindWorker(&Worker::process_, process);
if (!worker) return;
switch (error) {
case QProcess::FailedToStart:
// Failed to start errors are bad - it usually means the worker isn't
// installed. Don't restart the process, but tell our owner, who will
// probably want to do something fatal.
qLog(Error) << "Worker failed to start";
emit WorkerFailedToStart();
break;
default:
// On any other error we just restart the process.
qLog(Debug) << "Worker" << worker << "failed with error" << error << "- restarting";
StartOneWorker(worker);
break;
}
}
template <typename HandlerType>
typename WorkerPool<HandlerType>::ReplyType*
WorkerPool<HandlerType>::NewReply(MessageType* message) {
const int id = next_id_.fetchAndAddOrdered(1);
message->set_id(id);
return new ReplyType(*message);
}
template <typename HandlerType>
typename WorkerPool<HandlerType>::ReplyType*
WorkerPool<HandlerType>::SendMessageWithReply(MessageType* message) {
ReplyType* reply = NewReply(message);
// Add the pending reply to the queue
{
QMutexLocker l(&message_queue_mutex_);
message_queue_.enqueue(reply);
}
// Wake up the main thread
metaObject()->invokeMethod(this, "SendQueuedMessages", Qt::QueuedConnection);
return reply;
}
template <typename HandlerType>
void WorkerPool<HandlerType>::SendQueuedMessages() {
QMutexLocker l(&message_queue_mutex_);
while (!message_queue_.isEmpty()) {
ReplyType *reply = message_queue_.dequeue();
// Find a worker for this message
HandlerType* handler = NextHandler();
if (!handler) {
// No available handlers - put the message on the front of the queue.
message_queue_.prepend(reply);
qLog(Debug) << "No available handlers to process request";
break;
}
handler->SendRequest(reply);
}
}
template <typename HandlerType>
HandlerType *WorkerPool<HandlerType>::NextHandler() const {
for (int i = 0; i < workers_.count(); ++i) {
const int worker_index = (next_worker_ + i) % workers_.count();
if (workers_[worker_index].handler_ &&
!workers_[worker_index].handler_->is_device_closed()) {
next_worker_ = (worker_index + 1) % workers_.count();
return workers_[worker_index].handler_;
}
}
return NULL;
}
#endif // WORKERPOOL_H

View File

@@ -0,0 +1,35 @@
include_directories(${PROTOBUF_INCLUDE_DIRS})
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
include_directories(${CMAKE_CURRENT_BINARY_DIR})
include_directories(${CMAKE_SOURCE_DIR}/ext/libstrawberry-common)
include_directories(${CMAKE_SOURCE_DIR}/src)
include_directories(${CMAKE_BINARY_DIR}/src)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --std=c++0x")
set(MESSAGES
tagreadermessages.proto
)
set(SOURCES
fmpsparser.cpp
tagreader.cpp
)
set(HEADERS
)
qt5_wrap_cpp(MOC ${HEADERS})
protobuf_generate_cpp(PROTO_SOURCES PROTO_HEADERS ${MESSAGES})
add_library(libstrawberry-tagreader STATIC
${PROTO_SOURCES}
${SOURCES}
${MOC}
)
target_link_libraries(libstrawberry-tagreader
${PROTOBUF_LIBRARY}
libstrawberry-common
)

View File

@@ -0,0 +1,127 @@
/* This file is part of Strawberry.
Copyright 2010, David Sansome <me@davidsansome.com>
Strawberry 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.
Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#include "fmpsparser.h"
#include <functional>
#include <QStringList>
#include <QtDebug>
using std::placeholders::_1;
using std::placeholders::_2;
FMPSParser::FMPSParser() :
// The float regex ends with (?:$|(?=::|;;)) to ensure it matches all the way
// up to the end of the value. Without it, it would match a string that
// starts with a number, like "123abc".
float_re_("\\s*([+-]?\\d+(?:\\.\\d+)?)\\s*(?:$|(?=::|;;))"),
// Matches any character except unescaped slashes, colons and semicolons.
string_re_("((?:[^\\\\;:]|(?:\\\\[\\\\:;]))+)(?:$|(?=::|;;))"),
// Used for replacing escaped characters.
escape_re_("\\\\([\\\\:;])") {}
// Parses a list of things (of type T) that are separated by two consecutive
// Separator characters. Each individual thing is parsed by the F function.
// For example, to parse this data:
// foo::bar::baz
// Use:
// QVariantList ret;
// ParseContainer<':'>(data, ParseValue, &ret);
// ret will then contain "foo", "bar", and "baz".
// Returns the number of characters that were consumed from data.
//
// You can parse lists of lists by using different separator characters:
// ParseContainer<';'>(data, ParseContainer<':'>, &ret);
template <char Separator, typename F, typename T>
static int ParseContainer(const QStringRef& data, F f, QList<T>* ret) {
ret->clear();
T value;
int pos = 0;
while (pos < data.length()) {
const int len = data.length() - pos;
int matched_len = f(QStringRef(data.string(), data.position() + pos, len), &value);
if (matched_len == -1 || matched_len > len)
break;
ret->append(value);
pos += matched_len;
// Expect two separators in a row
if (pos + 2 <= data.length() && data.at(pos) == Separator && data.at(pos+1) == Separator) {
pos += 2;
} else {
break;
}
}
return pos;
}
bool FMPSParser::Parse(const QString &data) {
result_ = Result();
// Only return success if we matched the whole string
return ParseListList(data, &result_) == data.length();
}
int FMPSParser::ParseValueRef(const QStringRef& data, QVariant* ret) const {
// Try to match a float
int pos = float_re_.indexIn(*data.string(), data.position());
if (pos == data.position()) {
*ret = float_re_.cap(1).toDouble();
return float_re_.matchedLength();
}
// Otherwise try to match a string
pos = string_re_.indexIn(*data.string(), data.position());
if (pos == data.position()) {
// Replace escape sequences with their actual characters
QString value = string_re_.cap(1);
value.replace(escape_re_, "\\1");
*ret = value;
return string_re_.matchedLength();
}
return -1;
}
// Parses an inner list - a list of values
int FMPSParser::ParseListRef(const QStringRef &data, QVariantList *ret) const {
return ParseContainer<':'>(data, std::bind(&FMPSParser::ParseValueRef, this, _1, _2), ret);
}
// Parses an outer list - a list of lists
int FMPSParser::ParseListListRef(const QStringRef &data, Result *ret) const {
return ParseContainer<';'>(data, std::bind(&FMPSParser::ParseListRef, this, _1, _2), ret);
}
// Convenience functions that take QStrings instead of QStringRefs. Use the
// QStringRef versions if possible, they're faster.
int FMPSParser::ParseValue(const QString &data, QVariant *ret) const {
return ParseValueRef(QStringRef(&data), ret);
}
int FMPSParser::ParseList(const QString &data, QVariantList *ret) const {
return ParseListRef(QStringRef(&data), ret);
}
int FMPSParser::ParseListList(const QString &data, Result *ret) const {
return ParseListListRef(QStringRef(&data), ret);
}

View File

@@ -0,0 +1,58 @@
/* This file is part of Strawberry.
Copyright 2010, David Sansome <me@davidsansome.com>
Strawberry 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.
Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef FMPSPARSER_H
#define FMPSPARSER_H
#include <QRegExp>
#include <QVariantList>
class FMPSParser {
public:
FMPSParser();
// A FMPS result is a list of lists of values (where a value is a string or
// a float).
typedef QList<QVariantList> Result;
// Parses a FMPS value and returns true on success.
bool Parse(const QString& data);
// Gets the result of the last successful Parse.
Result result() const { return result_; }
// Returns true if result() is empty.
bool is_empty() const { return result().isEmpty() || result()[0].isEmpty(); }
// Internal functions, public for unit tests
int ParseValue(const QString &data, QVariant *ret) const;
int ParseValueRef(const QStringRef &data, QVariant *ret) const;
int ParseList(const QString &data, QVariantList *ret) const;
int ParseListRef(const QStringRef &data, QVariantList *ret) const;
int ParseListList(const QString &data, Result *ret) const;
int ParseListListRef(const QStringRef &data, Result *ret) const;
private:
QRegExp float_re_;
QRegExp string_re_;
QRegExp escape_re_;
Result result_;
};
#endif // FMPSPARSER_H

View File

@@ -0,0 +1,705 @@
/* This file is part of Strawberry.
Copyright 2013, David Sansome <me@davidsansome.com>
Strawberry 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.
Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#include "tagreader.h"
#include <memory>
#include <QCoreApplication>
#include <QDateTime>
#include <QFileInfo>
#include <QNetworkAccessManager>
#include <QTextCodec>
#include <QUrl>
#include <QVector>
#include <aifffile.h>
#include <asffile.h>
#include <attachedpictureframe.h>
#include <commentsframe.h>
#include <fileref.h>
#include <audioproperties.h>
#include <flacfile.h>
#include <flacproperties.h>
#include <id3v2tag.h>
#include <mp4file.h>
#include <mp4tag.h>
#include <mpcfile.h>
#include <mpegfile.h>
#include <oggfile.h>
#ifdef TAGLIB_HAS_OPUS
#include <opusfile.h>
#endif
#include <oggflacfile.h>
#include <popularimeterframe.h>
#include <speexfile.h>
#include <tag.h>
#include <textidentificationframe.h>
#include <trueaudiofile.h>
#include <tstring.h>
#include <vorbisfile.h>
#include <wavfile.h>
#include <sys/stat.h>
#include "fmpsparser.h"
#include "core/logging.h"
#include "core/messagehandler.h"
#include "core/timeconstants.h"
// Taglib added support for FLAC pictures in 1.7.0
#if (TAGLIB_MAJOR_VERSION > 1) || (TAGLIB_MAJOR_VERSION == 1 && TAGLIB_MINOR_VERSION >= 7)
# define TAGLIB_HAS_FLAC_PICTURELIST
#endif
#define NumberToASFAttribute(x) TagLib::ASF::Attribute(QStringToTaglibString(QString::number(x)))
class FileRefFactory {
public:
virtual ~FileRefFactory() {}
virtual TagLib::FileRef *GetFileRef(const QString& filename) = 0;
};
class TagLibFileRefFactory : public FileRefFactory {
public:
virtual TagLib::FileRef *GetFileRef(const QString& filename) {
#ifdef Q_OS_WIN32
return new TagLib::FileRef(filename.toStdWString().c_str());
#else
return new TagLib::FileRef(QFile::encodeName(filename).constData());
#endif
}
};
namespace {
TagLib::String StdStringToTaglibString(const std::string& s) {
return TagLib::String(s.c_str(), TagLib::String::UTF8);
}
TagLib::String QStringToTaglibString(const QString& s) {
return TagLib::String(s.toUtf8().constData(), TagLib::String::UTF8);
}
}
namespace {
// Tags containing the year the album was originally released (in contrast to
// other tags that contain the release year of the current edition)
const char *kMP4_OriginalYear_ID = "----:com.apple.iTunes:ORIGINAL YEAR";
const char *kASF_OriginalDate_ID = "WM/OriginalReleaseTime";
const char *kASF_OriginalYear_ID = "WM/OriginalReleaseYear";
}
TagReader::TagReader()
: factory_(new TagLibFileRefFactory),
network_(new QNetworkAccessManager),
kEmbeddedCover("(embedded)") {}
void TagReader::ReadFile(const QString &filename, pb::tagreader::SongMetadata *song) const {
const QByteArray url(QUrl::fromLocalFile(filename).toEncoded());
const QFileInfo info(filename);
qLog(Debug) << "Reading tags from" << filename;
song->set_basefilename(DataCommaSizeFromQString(info.fileName()));
song->set_url(url.constData(), url.size());
song->set_filesize(info.size());
song->set_mtime(info.lastModified().toTime_t());
song->set_ctime(info.created().toTime_t());
std::unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
if (fileref->isNull()) {
qLog(Info) << "TagLib hasn't been able to read " << filename << " file";
return;
}
if (fileref->audioProperties()) {
song->set_bitrate(fileref->audioProperties()->bitrate());
song->set_samplerate(fileref->audioProperties()->sampleRate());
song->set_length_nanosec(fileref->audioProperties()->length() * kNsecPerSec);
}
// Get the filetype if we can
song->set_filetype(GuessFileType(fileref.get()));
TagLib::Tag *tag = fileref->tag();
if (tag) {
Decode(tag->title(), nullptr, song->mutable_title());
Decode(tag->artist(), nullptr, song->mutable_artist()); // TPE1
Decode(tag->album(), nullptr, song->mutable_album());
Decode(tag->genre(), nullptr, song->mutable_genre());
song->set_year(tag->year());
song->set_track(tag->track());
song->set_valid(true);
}
QString disc;
QString compilation;
// Handle all the files which have VorbisComments (Ogg, OPUS, ...) in the same way;
// apart, so we keep specific behavior for some formats by adding another
// "else if" block below.
if (TagLib::Ogg::XiphComment *tag = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref->file()->tag())) {
ParseOggTag(tag->fieldListMap(), nullptr, &disc, &compilation, song);
#if TAGLIB_MAJOR_VERSION >= 1 && TAGLIB_MINOR_VERSION >= 11
if (!tag->pictureList().isEmpty()) song->set_art_automatic(kEmbeddedCover);
#endif
}
if (TagLib::MPEG::File *file = dynamic_cast<TagLib::MPEG::File*>(fileref->file())) {
if (file->ID3v2Tag()) {
const TagLib::ID3v2::FrameListMap &map = file->ID3v2Tag()->frameListMap();
if (!map["TPOS"].isEmpty()) disc = TStringToQString(map["TPOS"].front()->toString()).trimmed();
//if (!map["TBPM"].isEmpty()) song->set_bpm(TStringToQString(map["TBPM"].front()->toString()).trimmed().toFloat());
if (!map["TCOM"].isEmpty()) Decode(map["TCOM"].front()->toString(), nullptr, song->mutable_composer());
// content group
if (!map["TIT1"].isEmpty()) Decode(map["TIT1"].front()->toString(), nullptr, song->mutable_grouping());
// ID3v2: lead performer/soloist
if (!map["TPE1"].isEmpty()) Decode(map["TPE1"].front()->toString(), nullptr, song->mutable_performer());
// original artist/performer
if (!map["TOPE"].isEmpty()) Decode(map["TOPE"].front()->toString(), nullptr, song->mutable_performer());
// Skip TPE1 (which is the artist) here because we already fetched it
// non-standard: Apple, Microsoft
if (!map["TPE2"].isEmpty()) Decode(map["TPE2"].front()->toString(), nullptr, song->mutable_albumartist());
if (!map["TCMP"].isEmpty()) compilation = TStringToQString(map["TCMP"].front()->toString()).trimmed();
if (!map["TDOR"].isEmpty()) { song->set_originalyear(map["TDOR"].front()->toString().substr(0, 4).toInt()); }
else if (!map["TORY"].isEmpty()) {
song->set_originalyear(map["TORY"].front()->toString().substr(0, 4).toInt());
}
if (!map["APIC"].isEmpty()) song->set_art_automatic(kEmbeddedCover);
// Find a suitable comment tag. For now we ignore iTunNORM comments.
for (int i = 0; i < map["COMM"].size(); ++i) {
const TagLib::ID3v2::CommentsFrame *frame = dynamic_cast<const TagLib::ID3v2::CommentsFrame*>(map["COMM"][i]);
if (frame && TStringToQString(frame->description()) != "iTunNORM") {
Decode(frame->text(), nullptr, song->mutable_comment());
break;
}
}
// Parse FMPS frames
for (int i = 0; i < map["TXXX"].size(); ++i) {
const TagLib::ID3v2::UserTextIdentificationFrame *frame = dynamic_cast<const TagLib::ID3v2::UserTextIdentificationFrame*>(map["TXXX"][i]);
if (frame && frame->description().startsWith("FMPS_")) {
ParseFMPSFrame(TStringToQString(frame->description()), TStringToQString(frame->fieldList()[1]), song);
}
}
}
}
else if (TagLib::FLAC::File *file = dynamic_cast<TagLib::FLAC::File *>(fileref->file())) {
song->set_bitdepth(file->audioProperties()->bitsPerSample());
if ( file->xiphComment() ) {
ParseOggTag(file->xiphComment()->fieldListMap(), nullptr, &disc, &compilation, song);
#ifdef TAGLIB_HAS_FLAC_PICTURELIST
if (!file->pictureList().isEmpty()) {
song->set_art_automatic(kEmbeddedCover);
}
#endif
}
Decode(tag->comment(), nullptr, song->mutable_comment());
}
else if (TagLib::MP4::File *file = dynamic_cast<TagLib::MP4::File*>(fileref->file())) {
song->set_bitdepth(file->audioProperties()->bitsPerSample());
if (file->tag()) {
TagLib::MP4::Tag *mp4_tag = file->tag();
const TagLib::MP4::ItemListMap& items = mp4_tag->itemListMap();
// Find album artists
TagLib::MP4::ItemListMap::ConstIterator it = items.find("aART");
if (it != items.end()) {
TagLib::StringList album_artists = it->second.toStringList();
if (!album_artists.isEmpty()) {
Decode(album_artists.front(), nullptr, song->mutable_albumartist());
}
}
// Find album cover art
if (items.find("covr") != items.end()) {
song->set_art_automatic(kEmbeddedCover);
}
if (items.contains("disk")) {
disc = TStringToQString(TagLib::String::number(items["disk"].toIntPair().first));
}
if(items.contains("\251wrt")) {
Decode(items["\251wrt"].toStringList().toString(", "), nullptr, song->mutable_composer());
}
if(items.contains("\251grp")) {
Decode(items["\251grp"].toStringList().toString(" "), nullptr, song->mutable_grouping());
}
if (items.contains(kMP4_OriginalYear_ID)) {
song->set_originalyear(
TStringToQString(
items[kMP4_OriginalYear_ID].toStringList().toString('\n'))
.left(4)
.toInt());
}
Decode(mp4_tag->comment(), nullptr, song->mutable_comment());
}
}
#ifdef TAGLIB_WITH_ASF
else if (TagLib::ASF::File *file = dynamic_cast<TagLib::ASF::File*>(fileref->file())) {
song->set_bitdepth(file->audioProperties()->bitsPerSample());
const TagLib::ASF::AttributeListMap &attributes_map = file->tag()->attributeListMap();
if (attributes_map.contains(kASF_OriginalDate_ID)) {
const TagLib::ASF::AttributeList &attributes =
attributes_map[kASF_OriginalDate_ID];
if (!attributes.isEmpty()) {
song->set_originalyear(
TStringToQString(attributes.front().toString()).left(4).toInt());
}
}
else if (attributes_map.contains(kASF_OriginalYear_ID)) {
const TagLib::ASF::AttributeList &attributes =
attributes_map[kASF_OriginalYear_ID];
if (!attributes.isEmpty()) {
song->set_originalyear(
TStringToQString(attributes.front().toString()).left(4).toInt());
}
}
}
#endif
else if (tag) {
Decode(tag->comment(), nullptr, song->mutable_comment());
}
if (!disc.isEmpty()) {
const int i = disc.indexOf('/');
if (i != -1) {
// disc.right( i ).toInt() is total number of discs, we don't use this at the moment
song->set_disc(disc.left(i).toInt());
}
else {
song->set_disc(disc.toInt());
}
}
if (compilation.isEmpty()) {
// well, it wasn't set, but if the artist is VA assume it's a compilation
if (QStringFromStdString(song->artist()).toLower() == "various artists") {
song->set_compilation(true);
}
}
else {
song->set_compilation(compilation.toInt() == 1);
}
// Set integer fields to -1 if they're not valid
#define SetDefault(field) if (song->field() <= 0) { song->set_##field(-1); }
SetDefault(track);
SetDefault(disc);
SetDefault(year);
SetDefault(bitrate);
SetDefault(samplerate);
SetDefault(bitdepth);
SetDefault(lastplayed);
#undef SetDefault
}
void TagReader::Decode(const TagLib::String &tag, const QTextCodec *codec, std::string *output) {
QString tmp;
if (codec && tag.isLatin1()) { // Never override UTF-8.
const std::string fixed = QString::fromUtf8(tag.toCString(true)).toStdString();
tmp = codec->toUnicode(fixed.c_str()).trimmed();
}
else {
tmp = TStringToQString(tag).trimmed();
}
output->assign(DataCommaSizeFromQString(tmp));
}
void TagReader::Decode(const QString &tag, const QTextCodec *codec, std::string *output) {
if (!codec) {
output->assign(DataCommaSizeFromQString(tag));
}
else {
const QString decoded(codec->toUnicode(tag.toUtf8()));
output->assign(DataCommaSizeFromQString(decoded));
}
}
void TagReader::ParseFMPSFrame(const QString &name, const QString &value, pb::tagreader::SongMetadata *song) const {
qLog(Debug) << "Parsing FMPSFrame" << name << ", " << value;
FMPSParser parser;
if (!parser.Parse(value) || parser.is_empty()) return;
QVariant var;
if (name == "FMPS_PlayCount") {
var = parser.result()[0][0];
if (var.type() == QVariant::Double) {
song->set_playcount(var.toDouble());
}
}
else if (name == "FMPS_PlayCount_User") {
// Take a user playcount only if there's no playcount already set
if (song->playcount() == 0 && parser.result()[0].count() >= 2) {
var = parser.result()[0][1];
if (var.type() == QVariant::Double) {
song->set_playcount(var.toDouble());
}
}
}
}
void TagReader::ParseOggTag(const TagLib::Ogg::FieldListMap &map, const QTextCodec *codec, QString *disc, QString *compilation, pb::tagreader::SongMetadata *song) const {
if (!map["COMPOSER"].isEmpty()) Decode(map["COMPOSER"].front(), codec, song->mutable_composer());
if (!map["PERFORMER"].isEmpty()) Decode(map["PERFORMER"].front(), codec, song->mutable_performer());
if (!map["CONTENT GROUP"].isEmpty()) Decode(map["CONTENT GROUP"].front(), codec, song->mutable_grouping());
if (!map["ALBUMARTIST"].isEmpty()) {
Decode(map["ALBUMARTIST"].front(), codec, song->mutable_albumartist());
}
else if (!map["ALBUM ARTIST"].isEmpty()) {
Decode(map["ALBUM ARTIST"].front(), codec, song->mutable_albumartist());
}
if (!map["ORIGINALDATE"].isEmpty())
song->set_originalyear(TStringToQString(map["ORIGINALDATE"].front()).left(4).toInt());
else if (!map["ORIGINALYEAR"].isEmpty())
song->set_originalyear(TStringToQString(map["ORIGINALYEAR"].front()).toInt());
if (!map["DISCNUMBER"].isEmpty()) *disc = TStringToQString( map["DISCNUMBER"].front() ).trimmed();
if (!map["COMPILATION"].isEmpty()) *compilation = TStringToQString( map["COMPILATION"].front() ).trimmed();
if (!map["COVERART"].isEmpty()) song->set_art_automatic(kEmbeddedCover);
if (!map["METADATA_BLOCK_PICTURE"].isEmpty()) song->set_art_automatic(kEmbeddedCover);
if (!map["FMPS_PLAYCOUNT"].isEmpty() && song->playcount() <= 0) song->set_playcount(TStringToQString( map["FMPS_PLAYCOUNT"].front() ).trimmed().toFloat());
}
void TagReader::SetVorbisComments(TagLib::Ogg::XiphComment *vorbis_comments, const pb::tagreader::SongMetadata &song) const {
vorbis_comments->addField("COMPOSER", StdStringToTaglibString(song.composer()), true);
vorbis_comments->addField("PERFORMER", StdStringToTaglibString(song.performer()), true);
vorbis_comments->addField("CONTENT GROUP", StdStringToTaglibString(song.grouping()), true);
vorbis_comments->addField("DISCNUMBER", QStringToTaglibString(song.disc() <= 0 -1 ? QString() : QString::number(song.disc())), true);
vorbis_comments->addField("COMPILATION", StdStringToTaglibString(song.compilation() ? "1" : "0"), true);
// Try to be coherent, the two forms are used but the first one is preferred
vorbis_comments->addField("ALBUMARTIST", StdStringToTaglibString(song.albumartist()), true);
vorbis_comments->removeField("ALBUM ARTIST");
}
pb::tagreader::SongMetadata_Type TagReader::GuessFileType(TagLib::FileRef *fileref) const {
#ifdef TAGLIB_WITH_ASF
if (dynamic_cast<TagLib::ASF::File*>(fileref->file())) return pb::tagreader::SongMetadata_Type_ASF;
#endif
if (dynamic_cast<TagLib::FLAC::File*>(fileref->file())) return pb::tagreader::SongMetadata_Type_FLAC;
#ifdef TAGLIB_WITH_MP4
if (dynamic_cast<TagLib::MP4::File*>(fileref->file())) return pb::tagreader::SongMetadata_Type_MP4;
#endif
if (dynamic_cast<TagLib::MPC::File*>(fileref->file())) return pb::tagreader::SongMetadata_Type_MPC;
if (dynamic_cast<TagLib::MPEG::File*>(fileref->file())) return pb::tagreader::SongMetadata_Type_MPEG;
if (dynamic_cast<TagLib::Ogg::FLAC::File*>(fileref->file())) return pb::tagreader::SongMetadata_Type_OGGFLAC;
if (dynamic_cast<TagLib::Ogg::Speex::File*>(fileref->file())) return pb::tagreader::SongMetadata_Type_OGGSPEEX;
if (dynamic_cast<TagLib::Ogg::Vorbis::File*>(fileref->file())) return pb::tagreader::SongMetadata_Type_OGGVORBIS;
#ifdef TAGLIB_HAS_OPUS
if (dynamic_cast<TagLib::Ogg::Opus::File*>(fileref->file())) return pb::tagreader::SongMetadata_Type_OGGOPUS;
#endif
if (dynamic_cast<TagLib::RIFF::AIFF::File*>(fileref->file())) return pb::tagreader::SongMetadata_Type_AIFF;
if (dynamic_cast<TagLib::RIFF::WAV::File*>(fileref->file())) return pb::tagreader::SongMetadata_Type_WAV;
if (dynamic_cast<TagLib::TrueAudio::File*>(fileref->file())) return pb::tagreader::SongMetadata_Type_TRUEAUDIO;
return pb::tagreader::SongMetadata_Type_UNKNOWN;
}
bool TagReader::SaveFile(const QString &filename, const pb::tagreader::SongMetadata &song) const {
if (filename.isNull()) return false;
qLog(Debug) << "Saving tags to" << filename;
std::unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
if (!fileref || fileref->isNull()) // The file probably doesn't exist
return false;
fileref->tag()->setTitle(StdStringToTaglibString(song.title()));
fileref->tag()->setArtist(StdStringToTaglibString(song.artist()));
fileref->tag()->setAlbum(StdStringToTaglibString(song.album()));
fileref->tag()->setGenre(StdStringToTaglibString(song.genre()));
fileref->tag()->setComment(StdStringToTaglibString(song.comment()));
fileref->tag()->setYear(song.year());
fileref->tag()->setTrack(song.track());
if (TagLib::MPEG::File *file = dynamic_cast<TagLib::MPEG::File*>(fileref->file())) {
TagLib::ID3v2::Tag *tag = file->ID3v2Tag(true);
SetTextFrame("TPOS", song.disc() <= 0 -1 ? QString() : QString::number(song.disc()), tag);
SetTextFrame("TCOM", song.composer(), tag);
SetTextFrame("TIT1", song.grouping(), tag);
SetTextFrame("TOPE", song.performer(), tag);
// Skip TPE1 (which is the artist) here because we already set it
SetTextFrame("TPE2", song.albumartist(), tag);
SetTextFrame("TCMP", std::string(song.compilation() ? "1" : "0"), tag);
}
else if (TagLib::FLAC::File *file = dynamic_cast<TagLib::FLAC::File*>(fileref->file())) {
TagLib::Ogg::XiphComment *tag = file->xiphComment();
SetVorbisComments(tag, song);
}
else if (TagLib::MP4::File *file = dynamic_cast<TagLib::MP4::File*>(fileref->file())) {
TagLib::MP4::Tag *tag = file->tag();
tag->itemListMap()["disk"] = TagLib::MP4::Item(song.disc() <= 0 -1 ? 0 : song.disc(), 0);
tag->itemListMap()["\251wrt"] = TagLib::StringList(song.composer().c_str());
tag->itemListMap()["\251grp"] = TagLib::StringList(song.grouping().c_str());
tag->itemListMap()["aART"] = TagLib::StringList(song.albumartist().c_str());
tag->itemListMap()["cpil"] = TagLib::StringList(song.compilation() ? "1" : "0");
}
// Handle all the files which have VorbisComments (Ogg, OPUS, ...) in the same way;
// apart, so we keep specific behavior for some formats by adding another
// "else if" block above.
if (TagLib::Ogg::XiphComment *tag = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref->file()->tag())) {
SetVorbisComments(tag, song);
}
bool ret = fileref->save();
#ifdef Q_OS_LINUX
if (ret) {
// Linux: inotify doesn't seem to notice the change to the file unless we
// change the timestamps as well. (this is what touch does)
utimensat(0, QFile::encodeName(filename).constData(), nullptr, 0);
}
#endif // Q_OS_LINUX
return ret;
}
void TagReader::SetUserTextFrame(const QString &description, const QString &value, TagLib::ID3v2::Tag *tag) const {
const QByteArray descr_utf8(description.toUtf8());
const QByteArray value_utf8(value.toUtf8());
qLog(Debug) << "Setting FMPSFrame:" << description << ", " << value;
SetUserTextFrame(std::string(descr_utf8.constData(), descr_utf8.length()), std::string(value_utf8.constData(), value_utf8.length()), tag);
}
void TagReader::SetUserTextFrame(const std::string &description, const std::string &value, TagLib::ID3v2::Tag *tag) const {
const TagLib::String t_description = StdStringToTaglibString(description);
// Remove the frame if it already exists
TagLib::ID3v2::UserTextIdentificationFrame *frame = TagLib::ID3v2::UserTextIdentificationFrame::find(tag, t_description);
if (frame) {
tag->removeFrame(frame);
}
// Create and add a new frame
frame = new TagLib::ID3v2::UserTextIdentificationFrame(TagLib::String::UTF8);
frame->setDescription(t_description);
frame->setText(StdStringToTaglibString(value));
tag->addFrame(frame);
}
void TagReader::SetTextFrame(const char *id, const QString &value, TagLib::ID3v2::Tag *tag) const {
const QByteArray utf8(value.toUtf8());
SetTextFrame(id, std::string(utf8.constData(), utf8.length()), tag);
}
void TagReader::SetTextFrame(const char *id, const std::string &value, TagLib::ID3v2::Tag *tag) const {
TagLib::ByteVector id_vector(id);
QVector<TagLib::ByteVector> frames_buffer;
// Store and clear existing frames
while (tag->frameListMap().contains(id_vector) && tag->frameListMap()[id_vector].size() != 0) {
frames_buffer.push_back(tag->frameListMap()[id_vector].front()->render());
tag->removeFrame(tag->frameListMap()[id_vector].front());
}
// If no frames stored create empty frame
if (frames_buffer.isEmpty()) {
TagLib::ID3v2::TextIdentificationFrame frame(id_vector, TagLib::String::UTF8);
frames_buffer.push_back(frame.render());
}
// add frame takes ownership and clears the memory
TagLib::ID3v2::TextIdentificationFrame *frame;
frame->setText(StdStringToTaglibString(value));
tag->addFrame(frame);
}
bool TagReader::IsMediaFile(const QString &filename) const {
qLog(Debug) << "Checking for valid file" << filename;
std::unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
return !fileref->isNull() && fileref->tag();
}
QByteArray TagReader::LoadEmbeddedArt(const QString &filename) const {
if (filename.isEmpty()) return QByteArray();
qLog(Debug) << "Loading art from" << filename;
#ifdef Q_OS_WIN32
TagLib::FileRef ref(filename.toStdWString().c_str());
#else
TagLib::FileRef ref(QFile::encodeName(filename).constData());
#endif
if (ref.isNull() || !ref.file()) return QByteArray();
// MP3
TagLib::MPEG::File *file = dynamic_cast<TagLib::MPEG::File*>(ref.file());
if (file && file->ID3v2Tag()) {
TagLib::ID3v2::FrameList apic_frames = file->ID3v2Tag()->frameListMap()["APIC"];
if (apic_frames.isEmpty())
return QByteArray();
TagLib::ID3v2::AttachedPictureFrame *pic = static_cast<TagLib::ID3v2::AttachedPictureFrame*>(apic_frames.front());
return QByteArray((const char*) pic->picture().data(), pic->picture().size());
}
// Ogg vorbis/speex
TagLib::Ogg::XiphComment *xiph_comment = dynamic_cast<TagLib::Ogg::XiphComment*>(ref.file()->tag());
if (xiph_comment) {
TagLib::Ogg::FieldListMap map = xiph_comment->fieldListMap();
#if TAGLIB_MAJOR_VERSION <= 1 && TAGLIB_MINOR_VERSION < 11
// Other than the below mentioned non-standard COVERART,
// METADATA_BLOCK_PICTURE
// is the proposed tag for cover pictures.
// (see http://wiki.xiph.org/VorbisComment#METADATA_BLOCK_PICTURE)
if (map.contains("METADATA_BLOCK_PICTURE")) {
TagLib::StringList pict_list = map["METADATA_BLOCK_PICTURE"];
for (std::list<TagLib::String>::iterator it = pict_list.begin(); it != pict_list.end(); ++it) {
QByteArray data(QByteArray::fromBase64(it->toCString()));
TagLib::ByteVector tdata(data.data(), data.size());
TagLib::FLAC::Picture p(tdata);
if (p.type() == TagLib::FLAC::Picture::FrontCover)
return QByteArray(p.data().data(), p.data().size());
}
// If there was no specific front cover, just take the first picture
QByteArray data(QByteArray::fromBase64(map["METADATA_BLOCK_PICTURE"].front().toCString()));
TagLib::ByteVector tdata(data.data(), data.size());
TagLib::FLAC::Picture p(tdata);
return QByteArray(p.data().data(), p.data().size());
}
#else
TagLib::List<TagLib::FLAC::Picture*> pics = xiph_comment->pictureList();
if (!pics.isEmpty()) {
for (auto p : pics) {
if (p->type() == TagLib::FLAC::Picture::FrontCover)
return QByteArray(p->data().data(), p->data().size());
}
// If there was no specific front cover, just take the first picture
std::list<TagLib::FLAC::Picture*>::iterator it = pics.begin();
TagLib::FLAC::Picture *picture = *it;
return QByteArray(picture->data().data(), picture->data().size());
}
#endif
// Ogg lacks a definitive standard for embedding cover art, but it seems
// b64 encoding a field called COVERART is the general convention
if (!map.contains("COVERART")) return QByteArray();
return QByteArray::fromBase64(map["COVERART"].toString().toCString());
}
#ifdef TAGLIB_HAS_FLAC_PICTURELIST
// Flac
TagLib::FLAC::File *flac_file = dynamic_cast<TagLib::FLAC::File*>(ref.file());
if (flac_file && flac_file->xiphComment()) {
TagLib::List<TagLib::FLAC::Picture*> pics = flac_file->pictureList();
if (!pics.isEmpty()) {
// Use the first picture in the file - this could be made cleverer and
// pick the front cover if it's present.
std::list<TagLib::FLAC::Picture*>::iterator it = pics.begin();
TagLib::FLAC::Picture *picture = *it;
return QByteArray(picture->data().data(), picture->data().size());
}
}
#endif
// MP4/AAC
TagLib::MP4::File *aac_file = dynamic_cast<TagLib::MP4::File*>(ref.file());
if (aac_file) {
TagLib::MP4::Tag *tag = aac_file->tag();
const TagLib::MP4::ItemListMap& items = tag->itemListMap();
TagLib::MP4::ItemListMap::ConstIterator it = items.find("covr");
if (it != items.end()) {
const TagLib::MP4::CoverArtList& art_list = it->second.toCoverArtList();
if (!art_list.isEmpty()) {
// Just take the first one for now
const TagLib::MP4::CoverArt &art = art_list.front();
return QByteArray(art.data().data(), art.data().size());
}
}
}
return QByteArray();
}

View File

@@ -0,0 +1,83 @@
/* This file is part of Strawberry.
Copyright 2013, David Sansome <me@davidsansome.com>
Strawberry 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.
Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef TAGREADER_H
#define TAGREADER_H
#include <QByteArray>
#include <taglib/xiphcomment.h>
#include "config.h"
#include "tagreadermessages.pb.h"
class QNetworkAccessManager;
class QString;
class QTextCodec;
class QUrl;
namespace TagLib {
class FileRef;
class String;
namespace ID3v2 {
class Tag;
class PopularimeterFrame;
}
}
class FileRefFactory;
/**
* This class holds all useful methods to read and write tags from/to files.
* You should not use it directly in the main process but rather use a
* TagReaderWorker process (using TagReaderClient)
*/
class TagReader {
public:
TagReader();
void ReadFile(const QString& filename, pb::tagreader::SongMetadata *song) const;
bool SaveFile(const QString& filename, const pb::tagreader::SongMetadata &song) const;
bool IsMediaFile(const QString &filename) const;
QByteArray LoadEmbeddedArt(const QString &filename) const;
static void Decode(const TagLib::String& tag, const QTextCodec *codec, std::string *output);
static void Decode(const QString &tag, const QTextCodec *codec, std::string *output);
void ParseFMPSFrame(const QString &name, const QString &value, pb::tagreader::SongMetadata *song) const;
void ParseOggTag(const TagLib::Ogg::FieldListMap &map, const QTextCodec *codec, QString *disc, QString *compilation, pb::tagreader::SongMetadata *song) const;
void SetVorbisComments(TagLib::Ogg::XiphComment *vorbis_comments, const pb::tagreader::SongMetadata &song) const;
pb::tagreader::SongMetadata_Type GuessFileType(TagLib::FileRef *fileref) const;
void SetUserTextFrame(const QString &description, const QString &value, TagLib::ID3v2::Tag *tag) const;
void SetUserTextFrame(const std::string &description, const std::string& value, TagLib::ID3v2::Tag *tag) const;
void SetTextFrame(const char *id, const QString &value, TagLib::ID3v2::Tag *tag) const;
void SetTextFrame(const char *id, const std::string &value, TagLib::ID3v2::Tag *tag) const;
private:
FileRefFactory *factory_;
QNetworkAccessManager *network_;
const std::string kEmbeddedCover;
};
#endif // TAGREADER_H

View File

@@ -0,0 +1,110 @@
package pb.tagreader;
message SongMetadata {
enum Type {
UNKNOWN = 0;
ASF = 1;
FLAC = 2;
MP4 = 3;
MPC = 4;
MPEG = 5;
OGGFLAC = 6;
OGGSPEEX = 7;
OGGVORBIS = 8;
AIFF = 9;
WAV = 10;
TRUEAUDIO = 11;
CDDA = 12;
OGGOPUS = 13;
//STREAM = 99;
}
optional bool valid = 1;
optional string title = 2;
optional string album = 3;
optional string artist = 4;
optional string albumartist = 5;
optional int32 track = 6;
optional int32 disc = 7;
optional int32 year = 8;
optional int32 originalyear = 9;
optional string genre = 10;
optional bool compilation = 11;
optional string composer = 12;
optional string performer = 13;
optional string grouping = 14;
optional string comment = 15;
optional uint64 length_nanosec = 16;
optional int32 bitrate = 17;
optional int32 samplerate = 18;
optional int32 bitdepth = 19;
optional string url = 20;
optional string basefilename = 21;
optional Type filetype = 22;
optional int32 filesize = 23;
optional int32 mtime = 24;
optional int32 ctime = 25;
optional int32 playcount = 26;
optional int32 skipcount = 27;
optional int32 lastplayed = 28;
optional bool suspicious_tags = 29;
optional string art_automatic = 30;
}
message ReadFileRequest {
optional string filename = 1;
}
message ReadFileResponse {
optional SongMetadata metadata = 1;
}
message SaveFileRequest {
optional string filename = 1;
optional SongMetadata metadata = 2;
}
message SaveFileResponse {
optional bool success = 1;
}
message IsMediaFileRequest {
optional string filename = 1;
}
message IsMediaFileResponse {
optional bool success = 1;
}
message LoadEmbeddedArtRequest {
optional string filename = 1;
}
message LoadEmbeddedArtResponse {
optional bytes data = 1;
}
message Message {
optional int32 id = 1;
optional ReadFileRequest read_file_request = 2;
optional ReadFileResponse read_file_response = 3;
optional SaveFileRequest save_file_request = 4;
optional SaveFileResponse save_file_response = 5;
optional IsMediaFileRequest is_media_file_request = 6;
optional IsMediaFileResponse is_media_file_response = 7;
optional LoadEmbeddedArtRequest load_embedded_art_request = 8;
optional LoadEmbeddedArtResponse load_embedded_art_response = 9;
}

View File

@@ -0,0 +1,53 @@
include_directories(${PROTOBUF_INCLUDE_DIRS})
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
include_directories(${CMAKE_CURRENT_BINARY_DIR})
include_directories(${CMAKE_SOURCE_DIR}/ext/libstrawberry-common)
include_directories(${CMAKE_SOURCE_DIR}/ext/libstrawberry-tagreader)
include_directories(${CMAKE_BINARY_DIR}/ext/libstrawberry-tagreader)
include_directories(${CMAKE_SOURCE_DIR}/src)
include_directories(${CMAKE_BINARY_DIR}/src)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --std=c++0x -U__STRICT_ANSI__")
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR})
set(SOURCES
main.cpp
tagreaderworker.cpp
)
qt5_wrap_cpp(MOC ${HEADERS})
qt5_add_resources(QRC data/data.qrc)
add_executable(strawberry-tagreader
${SOURCES}
${MOC}
${QRC}
)
target_link_libraries(strawberry-tagreader
${TAGLIB_LIBRARIES}
${QT_QTCORE_LIBRARY}
${QT_QTNETWORK_LIBRARY}
libstrawberry-common
libstrawberry-tagreader
)
if(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
target_link_libraries(strawberry-tagreader
execinfo
)
endif(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
if(APPLE)
target_link_libraries(strawberry-tagreader
/System/Library/Frameworks/Foundation.framework
)
endif(APPLE)
if(NOT APPLE)
# macdeploy.py takes care of this on mac
install(TARGETS strawberry-tagreader
RUNTIME DESTINATION bin
)
endif(NOT APPLE)

View File

@@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/certs">
<file>godaddy-root.pem</file>
</qresource>
</RCC>

View File

@@ -0,0 +1,24 @@
-----BEGIN CERTIFICATE-----
MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh
MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE
YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3
MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo
ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg
MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN
ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA
PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w
wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi
EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY
avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+
YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE
sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h
/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5
IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj
YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD
ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy
OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P
TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ
HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER
dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf
ReYNnyicsbkqWletNw+vHX/bvZ8=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,62 @@
/* This file is part of Strawberry.
Copyright 2011, David Sansome <me@davidsansome.com>
Strawberry 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.
Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#include "tagreaderworker.h"
#include "core/logging.h"
#include <QCoreApplication>
#include <QLocalSocket>
#include <QSslSocket>
#include <QStringList>
#include <iostream>
#include <sys/time.h>
int main(int argc, char **argv) {
QCoreApplication a(argc, argv);
QStringList args(a.arguments());
if (args.count() != 2) {
std::cerr << "This program is used internally by Strawberry to parse tags in music files\n"
"without exposing the whole application to crashes caused by malformed\n"
"files. It is not meant to be run on its own.\n";
return 1;
}
// Seed random number generator
timeval time;
gettimeofday(&time, nullptr);
qsrand((time.tv_sec * 1000) + (time.tv_usec / 1000));
logging::Init();
qLog(Info) << "TagReader worker connecting to" << args[1];
// Connect to the parent process.
QLocalSocket socket;
socket.connectToServer(args[1]);
if (!socket.waitForConnected(2000)) {
std::cerr << "Failed to connect to the parent process.\n";
return 1;
}
QSslSocket::addDefaultCaCertificates(QSslCertificate::fromPath(":/certs/godaddy-root.pem", QSsl::Pem));
TagReaderWorker worker(&socket);
return a.exec();
}

View File

@@ -0,0 +1,68 @@
/* This file is part of Strawberry.
Copyright 2011, David Sansome <me@davidsansome.com>
Strawberry 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.
Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#include "tagreaderworker.h"
#include <QCoreApplication>
#include <QDateTime>
#include <QFileInfo>
#include <QNetworkAccessManager>
#include <QTextCodec>
#include <QUrl>
TagReaderWorker::TagReaderWorker(QIODevice *socket, QObject *parent)
: AbstractMessageHandler<pb::tagreader::Message>(socket, parent)
{
}
void TagReaderWorker::MessageArrived(const pb::tagreader::Message& message) {
pb::tagreader::Message reply;
#if 0
// Crash every few requests
if (qrand() % 10 == 0) {
qLog(Debug) << "Crashing on request ID" << message.id();
abort();
}
#endif
if (message.has_read_file_request()) {
tag_reader_.ReadFile(QStringFromStdString(message.read_file_request().filename()), reply.mutable_read_file_response()->mutable_metadata());
}
else if (message.has_save_file_request()) {
reply.mutable_save_file_response()->set_success(tag_reader_.SaveFile(QStringFromStdString(message.save_file_request().filename()), message.save_file_request().metadata()));
}
else if (message.has_is_media_file_request()) {
reply.mutable_is_media_file_response()->set_success(tag_reader_.IsMediaFile(QStringFromStdString(message.is_media_file_request().filename())));
}
else if (message.has_load_embedded_art_request()) {
QByteArray data = tag_reader_.LoadEmbeddedArt(QStringFromStdString(message.load_embedded_art_request().filename()));
reply.mutable_load_embedded_art_response()->set_data(data.constData(), data.size());
}
SendReply(message, &reply);
}
void TagReaderWorker::DeviceClosed() {
AbstractMessageHandler<pb::tagreader::Message>::DeviceClosed();
qApp->exit();
}

View File

@@ -0,0 +1,38 @@
/* This file is part of Strawberry.
Copyright 2011, David Sansome <me@davidsansome.com>
Strawberry 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.
Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef TAGREADERWORKER_H
#define TAGREADERWORKER_H
#include "config.h"
#include "tagreader.h"
#include "tagreadermessages.pb.h"
#include "core/messagehandler.h"
class TagReaderWorker : public AbstractMessageHandler<pb::tagreader::Message> {
public:
TagReaderWorker(QIODevice *socket, QObject *parent = NULL);
protected:
void MessageArrived(const pb::tagreader::Message &message);
void DeviceClosed();
private:
TagReader tag_reader_;
};
#endif // TAGREADERWORKER_H