Reworked the project layout
This commit is contained in:
+109
@@ -0,0 +1,109 @@
|
||||
bin_PROGRAMS = lms
|
||||
|
||||
lms_SOURCES = \
|
||||
$(srcdir)/main/main.cpp \
|
||||
$(srcdir)/av/CodecContext.cpp \
|
||||
$(srcdir)/av/Common.cpp \
|
||||
$(srcdir)/av/Dictionary.cpp \
|
||||
$(srcdir)/av/FormatContext.cpp \
|
||||
$(srcdir)/av/InputFormatContext.cpp \
|
||||
$(srcdir)/av/Stream.cpp \
|
||||
$(srcdir)/config/ConfigReader.cpp \
|
||||
$(srcdir)/cover/CoverArt.cpp \
|
||||
$(srcdir)/cover/CoverArtGrabber.cpp \
|
||||
$(srcdir)/database/Artist.cpp \
|
||||
$(srcdir)/database/Genre.cpp \
|
||||
$(srcdir)/database/Release.cpp \
|
||||
$(srcdir)/database/MediaDirectory.cpp \
|
||||
$(srcdir)/database/Track.cpp \
|
||||
$(srcdir)/database/DatabaseHandler.cpp \
|
||||
$(srcdir)/database/SqlQuery.cpp \
|
||||
$(srcdir)/database/Video.cpp \
|
||||
$(srcdir)/database/User.cpp \
|
||||
$(srcdir)/database-updater/DatabaseUpdater.cpp \
|
||||
$(srcdir)/database-updater/Checksum.cpp \
|
||||
$(srcdir)/logger/Logger.cpp \
|
||||
$(srcdir)/metadata/AvFormat.cpp \
|
||||
$(srcdir)/metadata/Utils.cpp \
|
||||
$(srcdir)/remote/server/Connection.cpp \
|
||||
$(srcdir)/remote/server/ConnectionManager.cpp \
|
||||
$(srcdir)/remote/server/AudioCollectionRequestHandler.cpp \
|
||||
$(srcdir)/remote/server/AuthRequestHandler.cpp \
|
||||
$(srcdir)/remote/server/MediaRequestHandler.cpp \
|
||||
$(srcdir)/remote/server/RequestHandler.cpp \
|
||||
$(srcdir)/remote/server/Server.cpp \
|
||||
$(srcdir)/service/ServiceManager.cpp \
|
||||
$(srcdir)/service/DatabaseUpdateService.cpp \
|
||||
$(srcdir)/service/UserInterfaceService.cpp \
|
||||
$(srcdir)/service/RemoteServerService.cpp \
|
||||
$(srcdir)/transcode/AvConvTranscoder.cpp \
|
||||
$(srcdir)/transcode/Format.cpp \
|
||||
$(srcdir)/transcode/Parameters.cpp \
|
||||
$(srcdir)/transcode/InputMediaFile.cpp \
|
||||
$(srcdir)/ui/LmsApplication.cpp \
|
||||
$(srcdir)/ui/LmsHome.cpp \
|
||||
$(srcdir)/ui/auth/LmsAuth.cpp \
|
||||
$(srcdir)/ui/audio/AudioWidget.cpp \
|
||||
$(srcdir)/ui/audio/AudioDatabaseWidget.cpp \
|
||||
$(srcdir)/ui/audio/AudioMediaPlayerWidget.cpp \
|
||||
$(srcdir)/ui/audio/SearchFilterWidget.cpp \
|
||||
$(srcdir)/ui/audio/TableFilterWidget.cpp \
|
||||
$(srcdir)/ui/audio/TrackWidget.cpp \
|
||||
$(srcdir)/ui/common/DirectoryValidator.cpp \
|
||||
$(srcdir)/ui/common/SessionData.cpp \
|
||||
$(srcdir)/ui/resource/AvConvTranscodeStreamResource.cpp \
|
||||
$(srcdir)/ui/video/VideoWidget.cpp \
|
||||
$(srcdir)/ui/video/VideoDatabaseWidget.cpp \
|
||||
$(srcdir)/ui/video/VideoMediaPlayerWidget.cpp \
|
||||
$(srcdir)/ui/video/VideoParametersDialog.cpp \
|
||||
$(srcdir)/ui/settings/Settings.cpp \
|
||||
$(srcdir)/ui/settings/SettingsAccountFormView.cpp \
|
||||
$(srcdir)/ui/settings/SettingsAudioFormView.cpp \
|
||||
$(srcdir)/ui/settings/SettingsDatabaseFormView.cpp \
|
||||
$(srcdir)/ui/settings/SettingsFirstConnectionFormView.cpp \
|
||||
$(srcdir)/ui/settings/SettingsMediaDirectories.cpp \
|
||||
$(srcdir)/ui/settings/SettingsMediaDirectoryFormView.cpp \
|
||||
$(srcdir)/ui/settings/SettingsUserFormView.cpp \
|
||||
$(srcdir)/ui/settings/SettingsUsers.cpp
|
||||
|
||||
nodist_lms_SOURCES = \
|
||||
$(builddir)/auth.pb.cc \
|
||||
$(builddir)/auth.pb.h \
|
||||
$(builddir)/collection.pb.cc \
|
||||
$(builddir)/collection.pb.h \
|
||||
$(builddir)/common.pb.cc \
|
||||
$(builddir)/common.pb.h \
|
||||
$(builddir)/media.pb.cc \
|
||||
$(builddir)/media.pb.h \
|
||||
$(builddir)/messages.pb.cc \
|
||||
$(builddir)/messages.pb.h
|
||||
|
||||
BUILT_SOURCES = \
|
||||
$(builddir)/auth.pb.cc \
|
||||
$(builddir)/auth.pb.h \
|
||||
$(builddir)/collection.pb.cc \
|
||||
$(builddir)/collection.pb.h \
|
||||
$(builddir)/common.pb.cc \
|
||||
$(builddir)/common.pb.h \
|
||||
$(builddir)/media.pb.cc \
|
||||
$(builddir)/media.pb.h \
|
||||
$(builddir)/messages.pb.cc \
|
||||
$(builddir)/messages.pb.h
|
||||
|
||||
MOSTLYCLEANFILES = \
|
||||
$(builddir)/auth.pb.cc \
|
||||
$(builddir)/auth.pb.h \
|
||||
$(builddir)/collection.pb.cc \
|
||||
$(builddir)/collection.pb.h \
|
||||
$(builddir)/common.pb.cc \
|
||||
$(builddir)/common.pb.h \
|
||||
$(builddir)/media.pb.cc \
|
||||
$(builddir)/media.pb.h \
|
||||
$(builddir)/messages.pb.cc \
|
||||
$(builddir)/messages.pb.h
|
||||
|
||||
%.pb.cc %.pb.h: $(srcdir)/remote/proto/%.proto
|
||||
$(PROTOC) --proto_path=$(srcdir)/remote/proto/ --cpp_out=$(builddir)/ $^
|
||||
|
||||
lms_CXXFLAGS=-DBOOST_LOG_DYN_LINK -std=c++11 -Wall -I$(top_srcdir) -I$(srcdir)/ui -I$(srcdir)/remote
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include <stdexcept>
|
||||
#include <iostream>
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include "Codec.hpp"
|
||||
|
||||
Codec::Codec(enum CodecID codec, Type type )
|
||||
: _codec(nullptr)
|
||||
{
|
||||
if (type == Encoder)
|
||||
_codec = avcodec_find_encoder(codec);
|
||||
else if (type == Decoder)
|
||||
_codec = avcodec_find_decoder(codec);
|
||||
|
||||
if (_codec == nullptr) {
|
||||
LMS_LOG(MOD_AV, SEV_ERROR) << "Codec constructor failed! codec = " << codec << ", type = " << type;
|
||||
throw std::runtime_error("can't find codec using this id!");
|
||||
}
|
||||
}
|
||||
|
||||
Codec::Codec(const AVCodec* codec)
|
||||
: _codec(codec)
|
||||
{
|
||||
assert(_codec != nullptr);
|
||||
}
|
||||
|
||||
Codec::~Codec()
|
||||
{
|
||||
if (_codec == nullptr) {
|
||||
// TODO release iif ownership has not been taken?
|
||||
// av_codec_close(_codec);
|
||||
}
|
||||
}
|
||||
|
||||
const AVCodec*
|
||||
Codec::get() const
|
||||
{
|
||||
assert(_codec != nullptr);
|
||||
return _codec;
|
||||
}
|
||||
|
||||
Codec::Id
|
||||
Codec::getId() const
|
||||
{
|
||||
assert(_codec != nullptr);
|
||||
return _codec->id;
|
||||
}
|
||||
|
||||
std::string
|
||||
Codec::getName() const
|
||||
{
|
||||
assert(_codec != nullptr);
|
||||
return _codec->name;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CODEC_HPP__
|
||||
#define CODEC_HPP__
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <boost/utility.hpp>
|
||||
|
||||
#include "Common.hpp"
|
||||
|
||||
class Codec : boost::noncopyable
|
||||
{
|
||||
friend class CodecContext;
|
||||
friend class FormatContext;
|
||||
friend class OutputFormatContext;
|
||||
|
||||
public:
|
||||
|
||||
typedef enum AVCodecID Id;
|
||||
enum Type {
|
||||
Encoder,
|
||||
Decoder,
|
||||
};
|
||||
|
||||
Codec(Id codecId, Type type);
|
||||
Codec(const AVCodec* codec); // Attach existing codec
|
||||
~Codec();
|
||||
|
||||
Id getId() const;
|
||||
std::string getName() const;
|
||||
|
||||
private:
|
||||
|
||||
const AVCodec* get() const;
|
||||
const AVCodec* _codec;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdexcept>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <boost/array.hpp>
|
||||
|
||||
#include "CodecContext.hpp"
|
||||
|
||||
namespace Av
|
||||
{
|
||||
|
||||
CodecContext::CodecContext(AVCodecContext* CodecContext)
|
||||
: _codecContext(CodecContext)
|
||||
{
|
||||
assert(_codecContext != nullptr);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
CodecContext::dumpInfo(std::ostream& ost) const
|
||||
{
|
||||
ost << "BitRate = " << getBitRate() << ", SampleFormat = " << getSampleFormat() << ", SampleRate = " << getSampleRate() << ", ChannelLayout = " << getChannelLayout() << ", NbChannels = " << getNbChannels() << ", Timebase = " << getTimeBase().num << "/" << getTimeBase().den;
|
||||
}
|
||||
|
||||
|
||||
std::string
|
||||
CodecContext::getCodecDesc(void) const
|
||||
{
|
||||
std::vector<char> buf(256, 0);
|
||||
avcodec_string(&buf[0], buf.size(), _codecContext, 0);
|
||||
|
||||
return std::string(buf.begin(), buf.end());
|
||||
}
|
||||
|
||||
} // namespace Av
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CODEC_CONTEXT_HPP
|
||||
#define CODEC_CONTEXT_HPP
|
||||
|
||||
#include <boost/utility.hpp>
|
||||
#include <iostream>
|
||||
|
||||
#include "Codec.hpp"
|
||||
|
||||
namespace Av
|
||||
{
|
||||
|
||||
class CodecContext
|
||||
{
|
||||
public:
|
||||
|
||||
CodecContext(AVCodecContext* CodecContext); // Attach existing codec context (no free will be done)
|
||||
|
||||
// Codec getCodec();
|
||||
|
||||
enum AVMediaType getType(void) const { return _codecContext->codec_type; }
|
||||
Codec::Id getCodecId(void) const { return _codecContext->codec_id; }
|
||||
|
||||
std::string getCodecDesc(void) const;
|
||||
|
||||
// Accessors
|
||||
std::size_t getBitRate() const {return _codecContext->bit_rate;}
|
||||
AVSampleFormat getSampleFormat() const {return _codecContext->sample_fmt;}
|
||||
std::size_t getSampleRate() const {return _codecContext->sample_rate;}
|
||||
std::uint64_t getChannelLayout() const {return _codecContext->channel_layout;}
|
||||
std::size_t getNbChannels() const {return _codecContext->channels; }
|
||||
AVRational getTimeBase() const {return _codecContext->time_base; }
|
||||
|
||||
void dumpInfo(std::ostream& ost) const;
|
||||
|
||||
private:
|
||||
AVCodecContext* native() { return _codecContext; }
|
||||
|
||||
AVCodecContext* _codecContext;
|
||||
};
|
||||
|
||||
} // namespace Av
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <boost/array.hpp>
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include "Common.hpp"
|
||||
|
||||
namespace Av
|
||||
{
|
||||
|
||||
std::string
|
||||
AvError::to_str(void) const
|
||||
{
|
||||
boost::array<char, 128> buf = {0};
|
||||
if (av_strerror(_errnum, buf.data(), buf.size()) == 0)
|
||||
return std::string(&buf[0]);
|
||||
else
|
||||
return "Unknown error";
|
||||
|
||||
}
|
||||
|
||||
|
||||
std::ostream& operator<<(std::ostream& ost, const AvError& err)
|
||||
{
|
||||
ost << err.to_str();
|
||||
return ost;
|
||||
}
|
||||
|
||||
void AvInit()
|
||||
{
|
||||
/* register all the codecs */
|
||||
avcodec_register_all();
|
||||
av_register_all();
|
||||
LMS_LOG(MOD_AV, SEV_INFO) << "avcodec version = " << avcodec_version();
|
||||
}
|
||||
|
||||
} // namespace Av
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TRANSCODE_COMMON_HPP__
|
||||
#define TRANSCODE_COMMON_HPP__
|
||||
|
||||
// Hack to properly iinclude libavcodec/avcodec.h...
|
||||
//
|
||||
#define __STDC_CONSTANT_MACROS
|
||||
#ifdef _STDINT_H
|
||||
#undef _STDINT_H
|
||||
#endif
|
||||
# include <stdint.h>
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/channel_layout.h>
|
||||
#include <libavutil/mathematics.h>
|
||||
}
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Av
|
||||
{
|
||||
|
||||
|
||||
void AvInit();
|
||||
|
||||
class AvError
|
||||
{
|
||||
public:
|
||||
AvError() : _errnum(0) {}
|
||||
AvError(int ernum) : _errnum(ernum) {}
|
||||
|
||||
void operator=(int errnum) { _errnum = errnum; }
|
||||
|
||||
operator bool() { return _errnum < 0; }
|
||||
|
||||
std::string to_str() const;
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& ost, const AvError&);
|
||||
|
||||
bool eof() { return _errnum == AVERROR_EOF; } // TODO
|
||||
|
||||
private:
|
||||
int _errnum;
|
||||
};
|
||||
|
||||
} // namespace Av
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Dictionary.hpp"
|
||||
|
||||
|
||||
namespace Av
|
||||
{
|
||||
|
||||
Dictionary::Dictionary(AVDictionary* dictionary)
|
||||
: _dictionary(dictionary)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
Dictionary::get(std::map<std::string, std::string>& entries)
|
||||
{
|
||||
AVDictionaryEntry *tag = NULL;
|
||||
while ((tag = av_dict_get(_dictionary, "", tag, AV_DICT_IGNORE_SUFFIX))) {
|
||||
entries.insert( std::make_pair(tag->key, tag->value));
|
||||
}
|
||||
}
|
||||
|
||||
std::string
|
||||
Dictionary::get(std::string key)
|
||||
{
|
||||
AVDictionaryEntry *tag = NULL;
|
||||
tag = av_dict_get(_dictionary, key.c_str(), tag, 0);
|
||||
|
||||
return tag != NULL ? std::string(tag->value) : std::string();
|
||||
}
|
||||
|
||||
} // namespace Av
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef AV_DICTIONARY_HPP
|
||||
#define AV_DICTIONARY_HPP
|
||||
|
||||
#include <map>
|
||||
#include "Common.hpp"
|
||||
|
||||
namespace Av
|
||||
{
|
||||
|
||||
class Dictionary
|
||||
{
|
||||
public:
|
||||
Dictionary(AVDictionary* dictionary);
|
||||
|
||||
// Get all
|
||||
void get(std::map<std::string, std::string>& entries);
|
||||
|
||||
// get a single entry
|
||||
std::string get(std::string key);
|
||||
|
||||
private:
|
||||
AVDictionary* _dictionary;
|
||||
};
|
||||
|
||||
} // namespace Av
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "FormatContext.hpp"
|
||||
|
||||
namespace Av
|
||||
{
|
||||
|
||||
FormatContext::FormatContext()
|
||||
: _context(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
FormatContext::~FormatContext()
|
||||
{
|
||||
}
|
||||
|
||||
} // namespace Av
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef FORMAT_CONTEXT_HPP
|
||||
#define FORMAT_CONTEXT_HPP
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include "Common.hpp"
|
||||
|
||||
namespace Av
|
||||
{
|
||||
|
||||
class FormatContext
|
||||
{
|
||||
public:
|
||||
FormatContext();
|
||||
~FormatContext();
|
||||
|
||||
|
||||
protected:
|
||||
void native(AVFormatContext* c) { _context = c;}
|
||||
AVFormatContext* native() { return _context; }
|
||||
const AVFormatContext* native() const { return _context; }
|
||||
|
||||
private:
|
||||
|
||||
AVFormatContext* _context;
|
||||
|
||||
|
||||
};
|
||||
|
||||
} // namespace Av
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdexcept>
|
||||
#include <iostream>
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include "InputFormatContext.hpp"
|
||||
|
||||
namespace Av
|
||||
{
|
||||
|
||||
InputFormatContext::InputFormatContext(const boost::filesystem::path& p)
|
||||
: _path(p)
|
||||
{
|
||||
AVFormatContext* context = nullptr;
|
||||
|
||||
// The last three parameters specify the file format, buffer size and
|
||||
// format parameters. By simply specifying NULL or 0 we ask libavformat
|
||||
// to auto-detect the format and use a default buffer size.
|
||||
AvError error = avformat_open_input(&context, p.string().c_str(), nullptr, nullptr);
|
||||
if (error)
|
||||
{
|
||||
LMS_LOG(MOD_AV, SEV_ERROR) << "Cannot open '" << p.string() << "', avformat_open_input returned " << error;
|
||||
throw std::runtime_error("avformat_open_input failed: " + error.to_str());
|
||||
}
|
||||
|
||||
native(context);
|
||||
|
||||
}
|
||||
|
||||
InputFormatContext::~InputFormatContext()
|
||||
{
|
||||
AVFormatContext* context = native();
|
||||
avformat_close_input(&context);
|
||||
}
|
||||
|
||||
std::vector<Stream>
|
||||
InputFormatContext::getStreams(void)
|
||||
{
|
||||
std::vector<Stream> res;
|
||||
|
||||
for (std::size_t i = 0; i < native()->nb_streams; ++i) {
|
||||
res.push_back( Stream(native()->streams[i]));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
InputFormatContext::getBestStreamIdx(AVMediaType type, Stream::Idx& index)
|
||||
{
|
||||
int res = av_find_best_stream(native(),
|
||||
type,
|
||||
-1, // Auto
|
||||
-1, // Auto
|
||||
NULL,
|
||||
0
|
||||
);
|
||||
|
||||
AvError error(res);
|
||||
if (error) {
|
||||
LMS_LOG(MOD_AV, SEV_DEBUG) << "Cannot get best stream for type " << type << ": " << error;
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
index = res;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
InputFormatContext::findStreamInfo(void)
|
||||
{
|
||||
native()->max_analyze_duration = 10 * AV_TIME_BASE; // 10 secs
|
||||
AvError err = avformat_find_stream_info(native(), NULL);
|
||||
if (err) {
|
||||
LMS_LOG(MOD_AV, SEV_ERROR) << "Couldn't find stream information: " << err;
|
||||
throw std::runtime_error("av_find_stream_info failed!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::size_t
|
||||
InputFormatContext::getDurationSecs() const
|
||||
{
|
||||
if (native()->duration != AV_NOPTS_VALUE )
|
||||
return native()->duration / AV_TIME_BASE;
|
||||
else
|
||||
return 0; // TODO, do something better?
|
||||
}
|
||||
|
||||
Dictionary
|
||||
InputFormatContext::getMetadata(void)
|
||||
{
|
||||
return Dictionary(native()->metadata);
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
InputFormatContext::getPictures(std::vector< std::vector<unsigned char> >& pictures) const
|
||||
{
|
||||
for (std::size_t i = 0; i < native()->nb_streams; ++i)
|
||||
{
|
||||
if (native()->streams[i]->disposition & AV_DISPOSITION_ATTACHED_PIC)
|
||||
{
|
||||
AVPacket pkt = native()->streams[i]->attached_pic;
|
||||
|
||||
std::vector<unsigned char> data;
|
||||
std::copy(pkt.data, pkt.data + pkt.size, std::back_inserter(data));
|
||||
|
||||
pictures.push_back( data );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} //namespace Av
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef INPUT_FORMAT_CONTEXT_HPP
|
||||
#define INPUT_FORMAT_CONTEXT_HPP
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include "FormatContext.hpp"
|
||||
#include "Stream.hpp"
|
||||
#include "Dictionary.hpp"
|
||||
|
||||
namespace Av
|
||||
{
|
||||
|
||||
class InputFormatContext : public FormatContext
|
||||
{
|
||||
public:
|
||||
|
||||
InputFormatContext(const boost::filesystem::path& p);
|
||||
~InputFormatContext();
|
||||
|
||||
|
||||
Dictionary getMetadata(void); // metadata access
|
||||
|
||||
// Scan file
|
||||
void findStreamInfo();
|
||||
|
||||
|
||||
// Get attached pictures
|
||||
void getPictures(std::vector< std::vector<unsigned char> >& pictures) const;
|
||||
|
||||
// Get the streams
|
||||
std::vector<Stream> getStreams(void);
|
||||
|
||||
bool getBestStreamIdx(enum AVMediaType type, Stream::Idx& idx);
|
||||
std::size_t getDurationSecs() const;
|
||||
|
||||
private:
|
||||
boost::filesystem::path _path;
|
||||
|
||||
|
||||
};
|
||||
|
||||
} // namespace av
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include "Stream.hpp"
|
||||
|
||||
namespace Av
|
||||
{
|
||||
|
||||
Stream::Stream(AVStream* stream)
|
||||
: _stream(stream)
|
||||
{
|
||||
assert(_stream != nullptr);
|
||||
assert(_stream->codec != nullptr);
|
||||
}
|
||||
|
||||
CodecContext
|
||||
Stream::getCodecContext()
|
||||
{
|
||||
return _stream->codec;
|
||||
}
|
||||
|
||||
Dictionary
|
||||
Stream::getMetadata(void)
|
||||
{
|
||||
return Dictionary(_stream->metadata);
|
||||
}
|
||||
|
||||
bool
|
||||
Stream::hasAttachedPic(void) const
|
||||
{
|
||||
return (_stream->disposition & AV_DISPOSITION_ATTACHED_PIC);
|
||||
}
|
||||
|
||||
} // namespace Av
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef STREAM_HPP__
|
||||
#define STREAM_HPP__
|
||||
|
||||
#include "Common.hpp"
|
||||
|
||||
#include "CodecContext.hpp"
|
||||
#include "Dictionary.hpp"
|
||||
|
||||
namespace Av
|
||||
{
|
||||
|
||||
class Stream
|
||||
{
|
||||
friend class InputFormatContext;
|
||||
|
||||
public:
|
||||
// Attach existing stream
|
||||
Stream(AVStream* stream);
|
||||
|
||||
typedef size_t Idx;
|
||||
|
||||
// Idx getIdx() const { return _stream->index; }
|
||||
|
||||
bool hasAttachedPic(void) const;
|
||||
Dictionary getMetadata(void);
|
||||
CodecContext getCodecContext(void);
|
||||
|
||||
private:
|
||||
|
||||
AVStream* _stream;
|
||||
};
|
||||
|
||||
} // namespace Av
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include "ConfigReader.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
void splitStrings(const std::string& source, std::vector<std::string>& res)
|
||||
{
|
||||
std::istringstream oss(source);
|
||||
|
||||
std::string str;
|
||||
while(oss >> str)
|
||||
res.push_back(str);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ConfigReader::ConfigReader(boost::filesystem::path p)
|
||||
{
|
||||
_config.readFile(p.string().c_str());
|
||||
}
|
||||
|
||||
void
|
||||
ConfigReader::getLoggerConfig(Logger::Config& config)
|
||||
{
|
||||
config.enableFileLogging = _config.lookupValue("main.logger.file", config.logPath);
|
||||
config.enableConsoleLogging = _config.lookup("main.logger.console");
|
||||
config.minSeverity = static_cast<Severity>((int)_config.lookup("main.logger.level"));
|
||||
}
|
||||
|
||||
void
|
||||
ConfigReader::getUserInterfaceConfig(Service::UserInterfaceService::Config& config)
|
||||
{
|
||||
|
||||
config.enable = _config.lookup("ui.enable");
|
||||
if (!config.enable)
|
||||
return;
|
||||
|
||||
config.docRootPath = _config.lookup("ui.resources.docroot");
|
||||
config.appRootPath = _config.lookup("ui.resources.approot");
|
||||
config.httpsPort = static_cast<unsigned int>(_config.lookup("ui.listen-endpoint.port"));
|
||||
config.httpsAddress = boost::asio::ip::address::from_string((const char*)_config.lookup("ui.listen-endpoint.addr"));
|
||||
config.sslCertificatePath = _config.lookup("ui.ssl-crypto.cert");
|
||||
config.sslPrivateKeyPath = _config.lookup("ui.ssl-crypto.key");
|
||||
config.sslTempDhPath = _config.lookup("ui.ssl-crypto.dh");
|
||||
|
||||
config.dbPath = _config.lookup("main.database.path");
|
||||
}
|
||||
|
||||
void
|
||||
ConfigReader::getRemoteServerConfig(Service::RemoteServerService::Config& config)
|
||||
{
|
||||
config.enable = _config.lookup("remote.enable");
|
||||
if (!config.enable)
|
||||
return;
|
||||
|
||||
config.port = static_cast<unsigned int>(_config.lookup("remote.listen-endpoint.port"));
|
||||
config.address = boost::asio::ip::address::from_string((const char*)_config.lookup("remote.listen-endpoint.addr"));
|
||||
config.sslCertificatePath = _config.lookup("remote.ssl-crypto.cert");
|
||||
config.sslPrivateKeyPath = _config.lookup("remote.ssl-crypto.key");
|
||||
config.sslTempDhPath = _config.lookup("remote.ssl-crypto.dh");
|
||||
|
||||
config.dbPath = _config.lookup("main.database.path");
|
||||
}
|
||||
|
||||
void
|
||||
ConfigReader::getDatabaseUpdateConfig(Service::DatabaseUpdateService::Config& config)
|
||||
{
|
||||
config.enable = true;
|
||||
|
||||
config.dbPath = _config.lookup("main.database.path");
|
||||
|
||||
std::string audioExtensions = _config.lookup("main.database.audio_extensions");
|
||||
std::string videoExtensions = _config.lookup("main.database.video_extensions");
|
||||
|
||||
splitStrings(audioExtensions, config.audioExtensions);
|
||||
splitStrings(videoExtensions, config.videoExtensions);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CONFIG_READER_HPP
|
||||
#define CONFIG_READER_HPP
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <libconfig.h++>
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include "service/UserInterfaceService.hpp"
|
||||
#include "service/RemoteServerService.hpp"
|
||||
#include "service/DatabaseUpdateService.hpp"
|
||||
|
||||
class ConfigReader
|
||||
{
|
||||
public:
|
||||
|
||||
ConfigReader(boost::filesystem::path p);
|
||||
|
||||
// Logger configuration
|
||||
void getLoggerConfig(Logger::Config& config);
|
||||
|
||||
// Service configurations
|
||||
void getUserInterfaceConfig(Service::UserInterfaceService::Config& config);
|
||||
void getRemoteServerConfig(Service::RemoteServerService::Config& config);
|
||||
void getDatabaseUpdateConfig(Service::DatabaseUpdateService::Config& config);
|
||||
|
||||
private:
|
||||
|
||||
libconfig::Config _config;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <boost/gil/image.hpp>
|
||||
#include <boost/gil/typedefs.hpp>
|
||||
#include <boost/gil/extension/io/jpeg_io.hpp>
|
||||
#include <boost/gil/extension/numeric/sampler.hpp>
|
||||
#include <boost/gil/extension/numeric/resample.hpp>
|
||||
#include <boost/gil/extension/io_new/jpeg_all.hpp>
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include "CoverArt.hpp"
|
||||
|
||||
namespace CoverArt {
|
||||
|
||||
bool
|
||||
CoverArt::scale(std::size_t size)
|
||||
{
|
||||
bool res = false;
|
||||
|
||||
if (!size)
|
||||
return false;
|
||||
|
||||
try {
|
||||
|
||||
boost::gil::rgb8_image_t source;
|
||||
boost::gil::rgb8_image_t dest(size, size);
|
||||
|
||||
// Read source
|
||||
{
|
||||
std::istringstream iss( std::string(_data.begin(), _data.end()));
|
||||
boost::gil::read_image(iss, source, boost::gil::jpeg_tag());
|
||||
}
|
||||
|
||||
// Resize
|
||||
boost::gil::resize_view(boost::gil::const_view(source),
|
||||
boost::gil::view(dest),
|
||||
boost::gil::bilinear_sampler());
|
||||
|
||||
// Output to dest
|
||||
{
|
||||
std::ostringstream oss;
|
||||
boost::gil::write_view(oss, boost::gil::const_view(dest), boost::gil::jpeg_tag());
|
||||
|
||||
std::string output = oss.str();
|
||||
_data.assign(output.begin(), output.end());
|
||||
}
|
||||
|
||||
res = true;
|
||||
}
|
||||
catch(std::exception& e)
|
||||
{
|
||||
LMS_LOG(MOD_COVER, SEV_ERROR) << "Caught exception: " << e.what();
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace CoverArt
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef COVER_ART_HPP
|
||||
#define COVER_ART_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
|
||||
namespace CoverArt
|
||||
{
|
||||
|
||||
class CoverArt
|
||||
{
|
||||
public:
|
||||
typedef std::vector<unsigned char> data_type;
|
||||
|
||||
CoverArt();
|
||||
CoverArt(const std::string& mime, const data_type& data) : _mimeType(mime), _data(data) {}
|
||||
|
||||
const std::string& getMimeType() const { return _mimeType; }
|
||||
const data_type& getData() const { return _data; }
|
||||
|
||||
void setMimeType(const std::string& mimeType) { _mimeType = mimeType;}
|
||||
void setData(const data_type& data) { _data = data; }
|
||||
|
||||
bool scale(std::size_t size);
|
||||
|
||||
private:
|
||||
|
||||
std::string _mimeType;
|
||||
data_type _data;
|
||||
};
|
||||
|
||||
|
||||
} // namespace CoverArt
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
#include "av/InputFormatContext.hpp"
|
||||
|
||||
#include "CoverArtGrabber.hpp"
|
||||
|
||||
|
||||
|
||||
namespace CoverArt {
|
||||
|
||||
|
||||
std::vector<CoverArt>
|
||||
Grabber::getFromInputFormatContext(const Av::InputFormatContext& input)
|
||||
{
|
||||
std::vector<CoverArt> res;
|
||||
|
||||
try
|
||||
{
|
||||
std::vector< std::vector<unsigned char> > pictures;
|
||||
input.getPictures(pictures);
|
||||
|
||||
BOOST_FOREACH(const std::vector<unsigned char>& picture, pictures)
|
||||
res.push_back( CoverArt("application/octet-stream", picture) );
|
||||
|
||||
}
|
||||
catch(std::exception& e)
|
||||
{
|
||||
LMS_LOG(MOD_COVER, SEV_ERROR) << "Cannot get pictures: " << e.what();
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
std::vector<CoverArt>
|
||||
Grabber::getFromTrack(Database::Track::pointer track)
|
||||
{
|
||||
std::vector<CoverArt> res;
|
||||
|
||||
if (!track)
|
||||
return std::vector<CoverArt>();
|
||||
|
||||
try
|
||||
{
|
||||
Av::InputFormatContext input(track->getPath());
|
||||
|
||||
return getFromInputFormatContext(input);
|
||||
}
|
||||
catch(std::exception& e)
|
||||
{
|
||||
LMS_LOG(MOD_COVER, SEV_ERROR) << "Cannot get pictures: " << e.what();
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::vector<CoverArt>
|
||||
Grabber::getFromRelease(Database::Release::pointer release)
|
||||
{
|
||||
if (!release)
|
||||
return std::vector<CoverArt>();
|
||||
|
||||
// TODO
|
||||
// Check if there is an image file in the directory of the release
|
||||
// For now, just get the cover art from the first track of the release
|
||||
|
||||
Wt::Dbo::collection<Database::Track::pointer> tracks (release->getTracks());
|
||||
|
||||
Database::Track::pointer firstTrack;
|
||||
if (tracks.begin() != tracks.end())
|
||||
firstTrack = *tracks.begin();
|
||||
|
||||
if (firstTrack)
|
||||
{
|
||||
return Grabber::getFromTrack(firstTrack);
|
||||
}
|
||||
else
|
||||
return std::vector<CoverArt>();
|
||||
|
||||
}
|
||||
|
||||
} // namespace CoverArt
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef COVER_ART_GRABBER_HPP
|
||||
#define COVER_ART_GRABBER_HPP
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "av/InputFormatContext.hpp"
|
||||
#include "database/AudioTypes.hpp"
|
||||
|
||||
#include "CoverArt.hpp"
|
||||
|
||||
|
||||
namespace CoverArt {
|
||||
|
||||
class Grabber
|
||||
{
|
||||
public:
|
||||
|
||||
static std::vector<CoverArt> getFromInputFormatContext(const Av::InputFormatContext& input);
|
||||
static std::vector<CoverArt> getFromTrack(Database::Track::pointer track);
|
||||
static std::vector<CoverArt> getFromRelease(Database::Release::pointer release);
|
||||
};
|
||||
|
||||
} // namespace CoverArt
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <boost/crc.hpp> // for boost::crc_32_type
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include "Checksum.hpp"
|
||||
|
||||
typedef boost::crc_32_type crc_type;
|
||||
|
||||
void computeCrc(const boost::filesystem::path& p, std::vector<unsigned char>& crc)
|
||||
{
|
||||
crc_type result;
|
||||
|
||||
std::ifstream ifs( p.string().c_str(), std::ios_base::binary );
|
||||
|
||||
if (ifs)
|
||||
{
|
||||
do
|
||||
{
|
||||
std::array<char,1024> buffer;
|
||||
|
||||
ifs.read( buffer.data(), buffer.size() );
|
||||
result.process_bytes( buffer.data(), ifs.gcount() );
|
||||
}
|
||||
while ( ifs );
|
||||
}
|
||||
else
|
||||
{
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_ERROR) << "Failed to open file '" << p << "'";
|
||||
throw std::runtime_error("Failed to open file '" + p.string() + "'" );
|
||||
}
|
||||
|
||||
// Copy back result into the vector
|
||||
|
||||
// Copy the result into a vector of unsigned char
|
||||
const crc_type::value_type checksum = result.checksum();
|
||||
for (std::size_t i = 0; (i+1)*8 <= crc_type::bit_count; i++)
|
||||
{
|
||||
const unsigned char* data = reinterpret_cast<const unsigned char*>( &checksum );
|
||||
crc.push_back(data[i]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <vector>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
|
||||
void computeCrc(const boost::filesystem::path& p, std::vector<unsigned char>& checksum);
|
||||
|
||||
@@ -0,0 +1,672 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
#include <boost/thread.hpp>
|
||||
#include <boost/asio/placeholders.hpp>
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include "database/MediaDirectory.hpp"
|
||||
#include "database/AudioTypes.hpp"
|
||||
#include "database/VideoTypes.hpp"
|
||||
|
||||
#include "Checksum.hpp"
|
||||
#include "DatabaseUpdater.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
boost::gregorian::date
|
||||
getNextDay(const boost::gregorian::date& current)
|
||||
{
|
||||
boost::gregorian::day_iterator it(current);
|
||||
return *(++it);
|
||||
}
|
||||
|
||||
boost::gregorian::date
|
||||
getNextMonday(const boost::gregorian::date& current)
|
||||
{
|
||||
boost::gregorian::day_iterator it(current);
|
||||
|
||||
++it;
|
||||
// While it's not monday
|
||||
while( it->day_of_week() != 1 )
|
||||
++it;
|
||||
|
||||
return *(it);
|
||||
}
|
||||
|
||||
boost::gregorian::date
|
||||
getNextFirstOfMonth(const boost::gregorian::date& current)
|
||||
{
|
||||
boost::gregorian::day_iterator it(current);
|
||||
|
||||
++it;
|
||||
// While it's not the 1st of the month
|
||||
while( it->day() != 1 )
|
||||
++it;
|
||||
|
||||
return (*it);
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
isFileSupported(const boost::filesystem::path& file, const std::vector<boost::filesystem::path> extensions)
|
||||
{
|
||||
|
||||
boost::filesystem::path fileExtension = file.extension();
|
||||
|
||||
BOOST_FOREACH(const boost::filesystem::path extension, extensions)
|
||||
{
|
||||
if (extension == fileExtension)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<boost::filesystem::path>
|
||||
getRootDirectoriesByType(Wt::Dbo::Session& session, Database::MediaDirectory::Type type)
|
||||
{
|
||||
std::vector<boost::filesystem::path> res;
|
||||
std::vector<Database::MediaDirectory::pointer> rootDirs = Database::MediaDirectory::getByType(session, type);
|
||||
|
||||
BOOST_FOREACH(Database::MediaDirectory::pointer rootDir, rootDirs)
|
||||
res.push_back(rootDir->getPath());
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
namespace DatabaseUpdater {
|
||||
|
||||
using namespace Database;
|
||||
|
||||
|
||||
Updater::Updater(boost::filesystem::path dbPath, MetaData::Parser& parser)
|
||||
: _running(false),
|
||||
_scheduleTimer(_ioService),
|
||||
_db(dbPath),
|
||||
_metadataParser(parser)
|
||||
{
|
||||
_ioService.setThreadCount(1);
|
||||
}
|
||||
|
||||
void
|
||||
Updater::setAudioExtensions(const std::vector<std::string>& extensions)
|
||||
{
|
||||
BOOST_FOREACH(const std::string& extension, extensions)
|
||||
_audioExtensions.push_back("." + extension);
|
||||
}
|
||||
|
||||
void
|
||||
Updater::setVideoExtensions(const std::vector<std::string>& extensions)
|
||||
{
|
||||
BOOST_FOREACH(const std::string& extension, extensions)
|
||||
_videoExtensions.push_back("." + extension);
|
||||
}
|
||||
|
||||
void
|
||||
Updater::start(void)
|
||||
{
|
||||
_running = true;
|
||||
|
||||
// post some jobs in the io_service
|
||||
processNextJob();
|
||||
|
||||
_ioService.start();
|
||||
}
|
||||
|
||||
void
|
||||
Updater::stop(void)
|
||||
{
|
||||
_running = false;
|
||||
|
||||
// TODO cancel all jobs (timer, ...)
|
||||
_scheduleTimer.cancel();
|
||||
|
||||
_ioService.stop();
|
||||
}
|
||||
|
||||
void
|
||||
Updater::processNextJob(void)
|
||||
{
|
||||
Wt::Dbo::Transaction transaction(_db.getSession());
|
||||
|
||||
MediaDirectorySettings::pointer settings = MediaDirectorySettings::get(_db.getSession());
|
||||
|
||||
if (settings->getManualScanRequested()) {
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_NOTICE) << "Manual scan requested!";
|
||||
scheduleScan( boost::posix_time::seconds(0) );
|
||||
}
|
||||
else
|
||||
{
|
||||
boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
|
||||
boost::posix_time::time_duration startTime = settings->getUpdateStartTime();
|
||||
|
||||
boost::gregorian::date nextScanDate;
|
||||
|
||||
switch( settings->getUpdatePeriod() )
|
||||
{
|
||||
case Database::MediaDirectorySettings::Never:
|
||||
// Nothing to do
|
||||
break;
|
||||
case Database::MediaDirectorySettings::Daily:
|
||||
if (now.time_of_day() < startTime)
|
||||
nextScanDate = now.date();
|
||||
else
|
||||
nextScanDate = getNextDay(now.date());
|
||||
break;
|
||||
case Database::MediaDirectorySettings::Weekly:
|
||||
if (now.time_of_day() < startTime && now.date().day_of_week() == 1)
|
||||
nextScanDate = now.date();
|
||||
else
|
||||
nextScanDate = getNextMonday(now.date());
|
||||
break;
|
||||
case Database::MediaDirectorySettings::Monthly:
|
||||
if (now.time_of_day() < startTime && now.date().day() == 1)
|
||||
nextScanDate = now.date();
|
||||
else
|
||||
nextScanDate = getNextFirstOfMonth(now.date());
|
||||
break;
|
||||
}
|
||||
|
||||
if (!nextScanDate.is_special())
|
||||
scheduleScan( boost::posix_time::ptime (nextScanDate, settings->getUpdateStartTime() ) );
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Updater::scheduleScan( boost::posix_time::time_duration duration)
|
||||
{
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_NOTICE) << "Scheduling next scan in " << duration;
|
||||
_scheduleTimer.expires_from_now(duration);
|
||||
_scheduleTimer.async_wait( boost::bind( &Updater::process, this, boost::asio::placeholders::error) );
|
||||
}
|
||||
|
||||
void
|
||||
Updater::scheduleScan( boost::posix_time::ptime time)
|
||||
{
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_NOTICE) << "Scheduling next scan at " << time;
|
||||
_scheduleTimer.expires_at(time);
|
||||
_scheduleTimer.async_wait( boost::bind( &Updater::process, this, boost::asio::placeholders::error) );
|
||||
}
|
||||
|
||||
void
|
||||
Updater::process(boost::system::error_code err)
|
||||
{
|
||||
if (!err)
|
||||
{
|
||||
Stats stats;
|
||||
|
||||
checkAudioFiles(stats);
|
||||
checkVideoFiles(stats);
|
||||
|
||||
typedef std::pair<boost::filesystem::path, Database::MediaDirectory::Type> RootDirectory;
|
||||
std::vector<RootDirectory> rootDirectories;
|
||||
{
|
||||
Wt::Dbo::Transaction transaction(_db.getSession());
|
||||
std::vector<MediaDirectory::pointer> mediaDirectories = MediaDirectory::getAll(_db.getSession());
|
||||
BOOST_FOREACH(MediaDirectory::pointer directory, mediaDirectories)
|
||||
rootDirectories.push_back( std::make_pair( directory->getPath(), directory->getType() ));
|
||||
}
|
||||
|
||||
BOOST_FOREACH( RootDirectory rootDirectory, rootDirectories)
|
||||
processDirectory(rootDirectory.first, rootDirectory.first, rootDirectory.second, stats);
|
||||
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Changes = " << stats.nbChanges();
|
||||
|
||||
// Update database stats
|
||||
boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
|
||||
{
|
||||
Wt::Dbo::Transaction transaction(_db.getSession());
|
||||
|
||||
Database::MediaDirectorySettings::pointer settings = Database::MediaDirectorySettings::get(_db.getSession());
|
||||
|
||||
if (stats.nbChanges() > 0)
|
||||
settings.modify()->setLastUpdate(now);
|
||||
|
||||
// Save the last scan only if it has been completed
|
||||
if (_running)
|
||||
settings.modify()->setLastScan(now);
|
||||
|
||||
// If the manual scan was required we can now set it to done
|
||||
// Update only if the scan is complete!
|
||||
if (settings->getManualScanRequested() && _running)
|
||||
settings.modify()->setManualScanRequested(false);
|
||||
|
||||
}
|
||||
|
||||
if (_running)
|
||||
processNextJob();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats)
|
||||
{
|
||||
try {
|
||||
|
||||
// Check last update time
|
||||
boost::posix_time::ptime lastWriteTime (boost::posix_time::from_time_t( boost::filesystem::last_write_time( file ) ) );
|
||||
|
||||
Wt::Dbo::Transaction transaction(_db.getSession());
|
||||
|
||||
// Skip file if last write is the same
|
||||
Wt::Dbo::ptr<Track> track = Track::getByPath(_db.getSession(), file);
|
||||
if (track && track->getLastWriteTime() == lastWriteTime)
|
||||
return;
|
||||
|
||||
MetaData::Items items;
|
||||
_metadataParser.parse(file, items);
|
||||
|
||||
// We estimate this is a audio file if:
|
||||
// - we found a least one audio stream
|
||||
// - the duration is not null
|
||||
if (items.find(MetaData::AudioStreams) == items.end()
|
||||
|| boost::any_cast<std::vector<MetaData::AudioStream> >(items[MetaData::AudioStreams]).empty())
|
||||
{
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Skipped '" << file << "' (no audio stream found)";
|
||||
|
||||
// If Track exists here, delete it!
|
||||
if (track) {
|
||||
track.remove();
|
||||
stats.nbRemoved++;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (items.find(MetaData::Duration) == items.end()
|
||||
|| boost::any_cast<boost::posix_time::time_duration>(items[MetaData::Duration]).total_seconds() == 0)
|
||||
{
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Skipped '" << file << "' (no duration or duration 0)";
|
||||
|
||||
// If Track exists here, delete it!
|
||||
if (track) {
|
||||
track.remove();
|
||||
stats.nbRemoved++;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// ***** Title
|
||||
std::string title;
|
||||
if (items.find(MetaData::Title) != items.end()) {
|
||||
title = boost::any_cast<std::string>(items[MetaData::Title]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO parse file name guess track etc.
|
||||
// For now juste use file name as title
|
||||
title = file.filename().string();
|
||||
}
|
||||
|
||||
// ***** Artist
|
||||
Wt::Dbo::ptr<Artist> artist;
|
||||
if (items.find(MetaData::Artist) != items.end())
|
||||
{
|
||||
const std::string artistName (boost::any_cast<std::string>(items[MetaData::Artist]));
|
||||
artist = Artist::getByName(_db.getSession(), artistName );
|
||||
if (!artist)
|
||||
artist = Artist::create( _db.getSession(), artistName );
|
||||
}
|
||||
else
|
||||
artist = Artist::getNone(_db.getSession());
|
||||
|
||||
assert(artist);
|
||||
|
||||
|
||||
// ***** Release
|
||||
Wt::Dbo::ptr<Release> release;
|
||||
if (items.find(MetaData::Album) != items.end())
|
||||
{
|
||||
const std::string albumName (boost::any_cast<std::string>(items[MetaData::Album]));
|
||||
release = Release::getByName(_db.getSession(), albumName);
|
||||
if (!release)
|
||||
release = Release::create( _db.getSession(), albumName );
|
||||
}
|
||||
else
|
||||
release = Release::getNone( _db.getSession() );
|
||||
|
||||
assert(release);
|
||||
|
||||
|
||||
// ***** Genres
|
||||
typedef std::list<std::string> GenreList;
|
||||
GenreList genreList;
|
||||
std::vector< Genre::pointer > genres;
|
||||
if (items.find(MetaData::Genres) != items.end())
|
||||
{
|
||||
genreList = (boost::any_cast<GenreList>(items[MetaData::Genres]));
|
||||
|
||||
BOOST_FOREACH(const std::string& genre, genreList) {
|
||||
Genre::pointer dbGenre ( Genre::getByName(_db.getSession(), genre) );
|
||||
if (!dbGenre)
|
||||
dbGenre = Genre::create(_db.getSession(), genre);
|
||||
|
||||
genres.push_back( dbGenre );
|
||||
}
|
||||
}
|
||||
if (genres.empty())
|
||||
genres.push_back( Genre::getNone( _db.getSession() ));
|
||||
|
||||
assert( !genres.empty() );
|
||||
|
||||
// If file already exist, update data
|
||||
// Otherwise, create it
|
||||
if (!track)
|
||||
{
|
||||
// Create a new song
|
||||
track = Track::create(_db.getSession(), file, artist, release);
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Adding '" << file << "'";
|
||||
stats.nbAdded++;
|
||||
}
|
||||
else
|
||||
{
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Updating '" << file << "'";
|
||||
stats.nbModified++;
|
||||
}
|
||||
|
||||
assert(track);
|
||||
|
||||
track.modify()->setLastWriteTime(lastWriteTime);
|
||||
track.modify()->setName(title);
|
||||
|
||||
{
|
||||
std::string trackGenreList;
|
||||
// Product genre list
|
||||
BOOST_FOREACH(const std::string& genre, genreList) {
|
||||
if (!trackGenreList.empty())
|
||||
trackGenreList += ", ";
|
||||
trackGenreList += genre;
|
||||
}
|
||||
|
||||
track.modify()->setGenres( trackGenreList );
|
||||
}
|
||||
track.modify()->setGenres( genres );
|
||||
track.modify()->setArtist( artist );
|
||||
track.modify()->setRelease( release );
|
||||
|
||||
if (items.find(MetaData::TrackNumber) != items.end())
|
||||
track.modify()->setTrackNumber( boost::any_cast<std::size_t>(items[MetaData::TrackNumber]) );
|
||||
|
||||
if (items.find(MetaData::DiscNumber) != items.end())
|
||||
track.modify()->setDiscNumber( boost::any_cast<std::size_t>(items[MetaData::DiscNumber]) );
|
||||
|
||||
if (items.find(MetaData::Duration) != items.end())
|
||||
track.modify()->setDuration( boost::any_cast<boost::posix_time::time_duration>(items[MetaData::Duration]) );
|
||||
|
||||
if (items.find(MetaData::CreationTime) != items.end())
|
||||
track.modify()->setCreationTime( boost::any_cast<boost::posix_time::ptime>(items[MetaData::CreationTime]) );
|
||||
|
||||
transaction.commit();
|
||||
|
||||
}
|
||||
catch( std::exception& e ) {
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_ERROR) << "Exception while parsing audio file : '" << file << "': '" << e.what() << "' => skipping!";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Updater::processDirectory(const boost::filesystem::path& rootDirectory,
|
||||
const boost::filesystem::path& p,
|
||||
Database::MediaDirectory::Type type,
|
||||
Stats& stats)
|
||||
{
|
||||
if (!_running)
|
||||
return;
|
||||
|
||||
if (!boost::filesystem::exists(p) || !boost::filesystem::is_directory(p))
|
||||
return;
|
||||
|
||||
boost::filesystem::recursive_directory_iterator itPath(rootDirectory);
|
||||
boost::filesystem::recursive_directory_iterator itEnd;
|
||||
while (itPath != itEnd)
|
||||
{
|
||||
if (!_running)
|
||||
return;
|
||||
|
||||
if (boost::filesystem::is_regular(*itPath)) {
|
||||
switch( type )
|
||||
{
|
||||
case Database::MediaDirectory::Audio:
|
||||
if (isFileSupported(*itPath, _audioExtensions))
|
||||
processAudioFile( *itPath, stats );
|
||||
|
||||
break;
|
||||
|
||||
case Database::MediaDirectory::Video:
|
||||
if (isFileSupported(*itPath, _videoExtensions))
|
||||
processVideoFile( *itPath, stats);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
++itPath;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
Updater::checkFile(const boost::filesystem::path& p, const std::vector<boost::filesystem::path>& rootDirs, const std::vector<boost::filesystem::path>& extensions)
|
||||
{
|
||||
bool status = true;
|
||||
|
||||
// For each track, make sure the the file still exists
|
||||
// and still belongs to a root directory
|
||||
if (!boost::filesystem::exists( p )
|
||||
|| !boost::filesystem::is_regular( p ) )
|
||||
{
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Missing file '" << p << "'";
|
||||
status = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool foundRoot = false;
|
||||
BOOST_FOREACH(const boost::filesystem::path& rootDir, rootDirs)
|
||||
{
|
||||
if (p.string().find( rootDir.string() ) != std::string::npos)
|
||||
{
|
||||
foundRoot = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundRoot)
|
||||
{
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Out of root file '" << p << "'";
|
||||
status = false;
|
||||
}
|
||||
else if (!isFileSupported(p, extensions))
|
||||
{
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "File format no longer supported for '" << p << "'";
|
||||
status = false;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Updater::checkAudioFiles( Stats& stats )
|
||||
{
|
||||
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking audio files...";
|
||||
Wt::Dbo::Transaction transaction(_db.getSession());
|
||||
|
||||
std::vector<boost::filesystem::path> rootDirs = getRootDirectoriesByType(_db.getSession(), Database::MediaDirectory::Audio);
|
||||
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking tracks...";
|
||||
typedef Wt::Dbo::collection< Wt::Dbo::ptr<Track> > Tracks;
|
||||
Tracks tracks = Track::getAll(_db.getSession());
|
||||
|
||||
for (Tracks::iterator it = tracks.begin(); it != tracks.end(); ++it)
|
||||
{
|
||||
Track::pointer track = (*it);
|
||||
|
||||
if (!checkFile(track->getPath(), rootDirs, _audioExtensions))
|
||||
{
|
||||
track.remove();
|
||||
stats.nbRemoved++;
|
||||
}
|
||||
}
|
||||
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking Artists...";
|
||||
// Now process orphan Artists (no track)
|
||||
typedef Wt::Dbo::collection< Wt::Dbo::ptr<Artist> > Artists;
|
||||
Artists artists = Artist::getAllOrphans(_db.getSession());
|
||||
|
||||
for (Artists::iterator it = artists.begin(); it != artists.end(); ++it)
|
||||
{
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Removing orphan artist " << (*it)->getName();
|
||||
(*it).remove();
|
||||
}
|
||||
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking Releases...";
|
||||
// Now process orphan Release (no track)
|
||||
typedef Wt::Dbo::collection< Wt::Dbo::ptr<Release> > Releases;
|
||||
Releases releases = Release::getAllOrphans(_db.getSession());
|
||||
|
||||
for (Releases::iterator it = releases.begin(); it != releases.end(); ++it)
|
||||
{
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Removing orphan release " << (*it)->getName();
|
||||
(*it).remove();
|
||||
}
|
||||
|
||||
// Now process orphan Genre (no track)
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking Genres...";
|
||||
typedef Wt::Dbo::collection< Wt::Dbo::ptr<Genre> > Genres;
|
||||
Genres genres = Genre::getAll(_db.getSession());
|
||||
|
||||
for (Genres::iterator it = genres.begin(); it != genres.end(); ++it)
|
||||
{
|
||||
Genre::pointer genre = (*it);
|
||||
|
||||
if (genre->getTracks().size() == 0)
|
||||
genre.remove();
|
||||
}
|
||||
|
||||
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Check audio files done!";
|
||||
}
|
||||
|
||||
void
|
||||
Updater::checkVideoFiles( Stats& stats )
|
||||
{
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking video files...";
|
||||
Wt::Dbo::Transaction transaction(_db.getSession());
|
||||
|
||||
std::vector<boost::filesystem::path> rootDirs = getRootDirectoriesByType(_db.getSession(), Database::MediaDirectory::Video);
|
||||
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking videos...";
|
||||
typedef Wt::Dbo::collection< Wt::Dbo::ptr<Video> > Videos;
|
||||
Videos videos = Video::getAll(_db.getSession());
|
||||
|
||||
for (Videos::iterator it = videos.begin(); it != videos.end(); ++it)
|
||||
{
|
||||
Video::pointer video = (*it);
|
||||
|
||||
if (!checkFile(video->getPath(), rootDirs, _videoExtensions))
|
||||
{
|
||||
video.remove();
|
||||
stats.nbRemoved++;
|
||||
}
|
||||
}
|
||||
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Check video files done!";
|
||||
}
|
||||
void
|
||||
Updater::processVideoFile( const boost::filesystem::path& file, Stats& stats)
|
||||
{
|
||||
try {
|
||||
|
||||
// Check last update time
|
||||
boost::posix_time::ptime lastWriteTime (boost::posix_time::from_time_t( boost::filesystem::last_write_time( file ) ) );
|
||||
|
||||
Wt::Dbo::Transaction transaction(_db.getSession());
|
||||
|
||||
// Skip file if last write is the same
|
||||
Wt::Dbo::ptr<Video> video = Video::getByPath(_db.getSession(), file);
|
||||
if (video && video->getLastWriteTime() == lastWriteTime)
|
||||
return;
|
||||
|
||||
MetaData::Items items;
|
||||
_metadataParser.parse(file, items);
|
||||
|
||||
// We estimate this is a video if:
|
||||
// - we found a least one video stream
|
||||
// - the duration is not null
|
||||
if (items.find(MetaData::VideoStreams) == items.end()
|
||||
|| boost::any_cast<std::vector<MetaData::VideoStream> >(items[MetaData::VideoStreams]).empty())
|
||||
{
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_ERROR) << "Skipped '" << file << "' (no video stream found)";
|
||||
|
||||
// If the video exists here, delete it!
|
||||
if (video) {
|
||||
video.remove();
|
||||
stats.nbRemoved++;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (items.find(MetaData::Duration) == items.end()
|
||||
|| boost::any_cast<boost::posix_time::time_duration>(items[MetaData::Duration]).total_seconds() == 0)
|
||||
{
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_ERROR) << "Skipped '" << file << "' (no duration or duration 0)";
|
||||
|
||||
// If Track exists here, delete it!
|
||||
if (video) {
|
||||
video.remove();
|
||||
stats.nbRemoved++;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If video already exist, update data
|
||||
// Otherwise, create it
|
||||
// Today we are very aggressive, but we could also guess names from path, etc.
|
||||
if (!video)
|
||||
{
|
||||
video = Video::create(_db.getSession(), file);
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Adding '" << file << "'";
|
||||
stats.nbAdded++;
|
||||
}
|
||||
else
|
||||
{
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Updating '" << file << "'";
|
||||
stats.nbModified++;
|
||||
}
|
||||
|
||||
assert(video);
|
||||
|
||||
video.modify()->setName( file.filename().string() );
|
||||
video.modify()->setDuration( boost::any_cast<boost::posix_time::time_duration>(items[MetaData::Duration]) );
|
||||
video.modify()->setLastWriteTime(lastWriteTime);
|
||||
|
||||
transaction.commit();
|
||||
}
|
||||
catch( std::exception& e ) {
|
||||
LMS_LOG(MOD_DBUPDATER, SEV_ERROR) << "Exception while parsing video file : '" << file << "': '" << e.what() << "' => skipping!";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace DatabaseUpdater
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef DB_UPDATER_HPP
|
||||
#define DB_UPDATER_HPP
|
||||
|
||||
#include <boost/asio/deadline_timer.hpp>
|
||||
#include <Wt/WIOService>
|
||||
|
||||
#include "metadata/MetaData.hpp"
|
||||
|
||||
#include "database/DatabaseHandler.hpp"
|
||||
#include "database/MediaDirectory.hpp"
|
||||
#include "database/DatabaseHandler.hpp"
|
||||
|
||||
namespace DatabaseUpdater {
|
||||
|
||||
class Updater
|
||||
{
|
||||
public:
|
||||
Updater(boost::filesystem::path db, MetaData::Parser& parser);
|
||||
|
||||
void setAudioExtensions(const std::vector<std::string>& extensions);
|
||||
void setVideoExtensions(const std::vector<std::string>& extensions);
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
private:
|
||||
|
||||
struct Stats
|
||||
{
|
||||
std::size_t nbAdded;
|
||||
std::size_t nbRemoved;
|
||||
std::size_t nbModified;
|
||||
Stats() : nbAdded(0), nbRemoved(0), nbModified(0) {}
|
||||
|
||||
void clear(void) { nbAdded = 0; nbRemoved = 0; nbModified = 0; }
|
||||
std::size_t nbChanges() const { return nbAdded + nbRemoved + nbModified;}
|
||||
};
|
||||
|
||||
// Job handling
|
||||
void processNextJob();
|
||||
void scheduleScan(boost::posix_time::time_duration duration);
|
||||
void scheduleScan(boost::posix_time::ptime time);
|
||||
|
||||
// Update database (scheduled callback)
|
||||
void process(boost::system::error_code ec);
|
||||
|
||||
// Check if a file exists and is still in a root directory
|
||||
static bool checkFile(const boost::filesystem::path& p,
|
||||
const std::vector<boost::filesystem::path>& rootDirectories,
|
||||
const std::vector<boost::filesystem::path>& extensions);
|
||||
|
||||
|
||||
void processDirectory( const boost::filesystem::path& rootDirectory,
|
||||
const boost::filesystem::path& directory,
|
||||
Database::MediaDirectory::Type type,
|
||||
Stats& stats);
|
||||
|
||||
|
||||
// Audio
|
||||
void checkAudioFiles( Stats& stats );
|
||||
void processAudioFile( const boost::filesystem::path& file, Stats& stats);
|
||||
|
||||
// Video
|
||||
void checkVideoFiles( Stats& stats );
|
||||
void processVideoFile( const boost::filesystem::path& file, Stats& stats);
|
||||
|
||||
bool _running;
|
||||
Wt::WIOService _ioService;
|
||||
|
||||
boost::asio::deadline_timer _scheduleTimer;
|
||||
|
||||
Database::Handler _db;
|
||||
|
||||
std::vector<boost::filesystem::path> _audioExtensions;
|
||||
std::vector<boost::filesystem::path> _videoExtensions;
|
||||
|
||||
MetaData::Parser& _metadataParser;
|
||||
|
||||
|
||||
}; // class Updater
|
||||
|
||||
} // DatabaseUpdater
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "AudioTypes.hpp"
|
||||
|
||||
namespace Database
|
||||
{
|
||||
|
||||
Artist::Artist(const std::string& name)
|
||||
: _name(std::string(name, 0 , _maxNameLength))
|
||||
{
|
||||
}
|
||||
|
||||
// Accesoors
|
||||
Artist::pointer
|
||||
Artist::getByName(Wt::Dbo::Session& session, const std::string& name)
|
||||
{
|
||||
return session.find<Artist>().where("name = ?").bind( std::string(name, 0, _maxNameLength) );
|
||||
}
|
||||
|
||||
// Create
|
||||
Artist::pointer
|
||||
Artist::create(Wt::Dbo::Session& session, const std::string& name)
|
||||
{
|
||||
return session.add( new Artist( name ) );
|
||||
}
|
||||
|
||||
Artist::pointer
|
||||
Artist::getNone(Wt::Dbo::Session& session)
|
||||
{
|
||||
pointer res = getByName(session, "<None>");
|
||||
if (!res)
|
||||
res = create(session, "<None>");
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
Wt::Dbo::collection<Artist::pointer>
|
||||
Artist::getAll(Wt::Dbo::Session& session, int offset, int size)
|
||||
{
|
||||
return session.find<Artist>().offset(offset).limit(size);
|
||||
}
|
||||
|
||||
Wt::Dbo::collection<Artist::pointer>
|
||||
Artist::getAllOrphans(Wt::Dbo::Session& session)
|
||||
{
|
||||
return session.query< Wt::Dbo::ptr<Artist> >("select a from artist a LEFT OUTER JOIN Track t ON a.id = t.artist_id WHERE t.id IS NULL");
|
||||
}
|
||||
|
||||
} // namespace Database
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _AUDIO_TYPES_HPP_
|
||||
#define _AUDIO_TYPES_HPP_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <Wt/Dbo/Dbo>
|
||||
#include <Wt/Dbo/WtSqlTraits>
|
||||
|
||||
#include <Wt/WDateTime>
|
||||
|
||||
namespace Database {
|
||||
|
||||
class Track;
|
||||
class Release;
|
||||
class Artist;
|
||||
|
||||
class Artist
|
||||
{
|
||||
public:
|
||||
|
||||
typedef Wt::Dbo::ptr<Artist> pointer;
|
||||
typedef Wt::Dbo::dbo_traits<Artist>::IdType id_type;
|
||||
|
||||
Artist() {}
|
||||
Artist(const std::string& p_name);
|
||||
|
||||
// Accessors
|
||||
static pointer getByName(Wt::Dbo::Session& session, const std::string& name);
|
||||
static pointer getNone(Wt::Dbo::Session& session);
|
||||
static Wt::Dbo::collection<pointer> getAll(Wt::Dbo::Session& session, int offset = -1, int size = -1);
|
||||
static Wt::Dbo::collection<pointer> getAllOrphans(Wt::Dbo::Session& session);
|
||||
|
||||
const std::string& getName(void) const { return _name; }
|
||||
const Wt::Dbo::collection< Wt::Dbo::ptr<Track> >& getTracks(void) const { return _tracks;}
|
||||
|
||||
// Create
|
||||
static pointer create(Wt::Dbo::Session& session, const std::string& name);
|
||||
|
||||
bool isNone(void) const;
|
||||
|
||||
template<class Action>
|
||||
void persist(Action& a)
|
||||
{
|
||||
Wt::Dbo::field(a, _name, "name");
|
||||
|
||||
Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToOne, "artist");
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
|
||||
static const std::size_t _maxNameLength = 128;
|
||||
|
||||
std::string _name;
|
||||
|
||||
Wt::Dbo::collection< Wt::Dbo::ptr<Track> > _tracks; // Tracks of this artist
|
||||
};
|
||||
|
||||
|
||||
// Album release
|
||||
class Release
|
||||
{
|
||||
public:
|
||||
|
||||
typedef Wt::Dbo::ptr<Release> pointer;
|
||||
typedef Wt::Dbo::dbo_traits<Release>::IdType id_type;
|
||||
|
||||
Release() {}
|
||||
Release(const std::string& name);
|
||||
|
||||
// Accessors
|
||||
static pointer getByName(Wt::Dbo::Session& session, const std::string& name);
|
||||
static pointer getById(Wt::Dbo::Session& session, id_type id);
|
||||
static pointer getNone(Wt::Dbo::Session& session);
|
||||
static Wt::Dbo::collection<pointer> getAllOrphans(Wt::Dbo::Session& session);
|
||||
static Wt::Dbo::collection<pointer> getAll(Wt::Dbo::Session& session, std::vector<Artist::id_type> artistIds, int offset = -1, int size = -1);
|
||||
|
||||
// Create
|
||||
static pointer create(Wt::Dbo::Session& session, const std::string& name);
|
||||
|
||||
std::string getName() const { return _name; }
|
||||
bool isNone(void) const;
|
||||
const Wt::Dbo::collection<Wt::Dbo::ptr<Track> >& getTracks(void) const { return _tracks;}
|
||||
boost::posix_time::time_duration getDuration(void) const;
|
||||
|
||||
|
||||
template<class Action>
|
||||
void persist(Action& a)
|
||||
{
|
||||
Wt::Dbo::field(a, _name, "name");
|
||||
|
||||
Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToOne, "release");
|
||||
}
|
||||
|
||||
private:
|
||||
static const std::size_t _maxNameLength = 128;
|
||||
std::string _name;
|
||||
|
||||
Wt::Dbo::collection< Wt::Dbo::ptr<Track> > _tracks; // Tracks in the release
|
||||
};
|
||||
|
||||
|
||||
class Genre
|
||||
{
|
||||
public:
|
||||
|
||||
typedef Wt::Dbo::ptr<Genre> pointer;
|
||||
typedef Wt::Dbo::dbo_traits<Genre>::IdType id_type;
|
||||
|
||||
Genre();
|
||||
Genre(const std::string& name);
|
||||
|
||||
// Find utility
|
||||
static pointer getByName(Wt::Dbo::Session& session, const std::string& name);
|
||||
static pointer getNone(Wt::Dbo::Session& session);
|
||||
static Wt::Dbo::collection<pointer> getAll(Wt::Dbo::Session& session, std::size_t offset = -1, std::size_t size = -1);
|
||||
|
||||
// Create utility
|
||||
static pointer create(Wt::Dbo::Session& session, const std::string& name);
|
||||
|
||||
// Accessors
|
||||
const std::string& getName(void) const { return _name; }
|
||||
bool isNone(void) const;
|
||||
const Wt::Dbo::collection< Wt::Dbo::ptr<Track> >& getTracks() const { return _tracks;}
|
||||
|
||||
template<class Action>
|
||||
void persist(Action& a)
|
||||
{
|
||||
Wt::Dbo::field(a, _name, "name");
|
||||
Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToMany, "track_genre", "", Wt::Dbo::OnDeleteCascade);
|
||||
}
|
||||
|
||||
private:
|
||||
static const std::size_t _maxNameLength = 128;
|
||||
std::string _name;
|
||||
|
||||
Wt::Dbo::collection< Wt::Dbo::ptr<Track> > _tracks;
|
||||
};
|
||||
|
||||
class Track
|
||||
{
|
||||
public:
|
||||
|
||||
typedef Wt::Dbo::ptr<Track> pointer;
|
||||
typedef Wt::Dbo::dbo_traits<Track>::IdType id_type;
|
||||
|
||||
Track() {}
|
||||
Track(const boost::filesystem::path& p, Artist::pointer artist, Release::pointer release);
|
||||
|
||||
// Find utilities
|
||||
static pointer getByPath(Wt::Dbo::Session& session, const boost::filesystem::path& p);
|
||||
static pointer getById(Wt::Dbo::Session& session, id_type id);
|
||||
static Wt::Dbo::collection< pointer > getAll(Wt::Dbo::Session& session);
|
||||
static Wt::Dbo::collection< pointer > getAll(Wt::Dbo::Session& session,
|
||||
const std::vector<Artist::id_type>& artistIds,
|
||||
const std::vector<Release::id_type>& releaseIds,
|
||||
const std::vector<Genre::id_type>& genreIds,
|
||||
int offset = -1, int size = -1);
|
||||
|
||||
// Create utility
|
||||
static pointer create(Wt::Dbo::Session& session, const boost::filesystem::path& p, Artist::pointer artist, Release::pointer release);
|
||||
|
||||
// Accessors
|
||||
void setTrackNumber(int num) { _trackNumber = num; }
|
||||
void setDiscNumber(int num) { _discNumber = num; }
|
||||
void setName(const std::string& name) { _name = std::string(name, 0, _maxNameLength); }
|
||||
void setDuration(boost::posix_time::time_duration duration) { _duration = duration; }
|
||||
void setLastWriteTime(boost::posix_time::ptime time) { _fileLastWrite = time; }
|
||||
void setChecksum(const std::vector<unsigned char>& checksum) { _fileChecksum = checksum; }
|
||||
void setCreationTime(const boost::posix_time::ptime& time) { _creationTime = time; }
|
||||
void setGenres(const std::string& genreList) { _genreList = genreList; }
|
||||
void setGenres(std::vector<Genre::pointer> genres);
|
||||
void setArtist(Artist::pointer artist) { _artist = artist; }
|
||||
void setRelease(Release::pointer release) { _release = release; }
|
||||
|
||||
int getTrackNumber(void) const { return _trackNumber; }
|
||||
int getDiscNumber(void) const { return _discNumber; }
|
||||
std::string getName(void) const { return _name; }
|
||||
const std::string& getPath(void) const { return _filePath; }
|
||||
boost::posix_time::time_duration getDuration(void) const { return _duration; }
|
||||
boost::posix_time::ptime getCreationTime(void) const { return _creationTime; }
|
||||
Artist::pointer getArtist(void) const { return _artist; }
|
||||
Release::pointer getRelease(void) const { return _release; }
|
||||
bool hasGenre(Genre::pointer genre) const { return _genres.count(genre); }
|
||||
std::vector< Genre::pointer > getGenres(void) const;
|
||||
|
||||
boost::posix_time::ptime getLastWriteTime(void) const { return _fileLastWrite; }
|
||||
const std::vector<unsigned char>& getChecksum(void) const { return _fileChecksum; }
|
||||
|
||||
template<class Action>
|
||||
void persist(Action& a)
|
||||
{
|
||||
Wt::Dbo::field(a, _trackNumber, "track_number");
|
||||
Wt::Dbo::field(a, _discNumber, "disc_number");
|
||||
Wt::Dbo::field(a, _name, "name");
|
||||
Wt::Dbo::field(a, _duration, "duration");
|
||||
Wt::Dbo::field(a, _creationTime, "creation_time");
|
||||
Wt::Dbo::field(a, _genreList, "genre_list");
|
||||
Wt::Dbo::field(a, _filePath, "path");
|
||||
Wt::Dbo::field(a, _fileLastWrite, "last_write");
|
||||
Wt::Dbo::field(a, _fileChecksum, "checksum");
|
||||
Wt::Dbo::hasMany(a, _genres, Wt::Dbo::ManyToMany, "track_genre", "", Wt::Dbo::OnDeleteCascade);
|
||||
Wt::Dbo::belongsTo(a, _release, "release", Wt::Dbo::OnDeleteCascade);
|
||||
Wt::Dbo::belongsTo(a, _artist, "artist", Wt::Dbo::OnDeleteCascade);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
static const std::size_t _maxNameLength = 128;
|
||||
|
||||
int _trackNumber;
|
||||
int _discNumber;
|
||||
std::string _name;
|
||||
boost::posix_time::time_duration _duration;
|
||||
boost::posix_time::ptime _creationTime;
|
||||
std::string _genreList;
|
||||
std::string _filePath;
|
||||
std::vector<unsigned char> _fileChecksum;
|
||||
boost::posix_time::ptime _fileLastWrite;
|
||||
|
||||
Artist::pointer _artist; // Associated Artist
|
||||
Release::pointer _release; // Associated Release
|
||||
Wt::Dbo::collection< Genre::pointer > _genres; // Tracks in the release
|
||||
};
|
||||
|
||||
} // namespace database
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <Wt/Auth/Dbo/AuthInfo>
|
||||
#include <Wt/Auth/Dbo/UserDatabase>
|
||||
#include <Wt/Auth/AuthService>
|
||||
#include <Wt/Auth/HashFunction>
|
||||
#include <Wt/Auth/PasswordService>
|
||||
#include <Wt/Auth/PasswordStrengthValidator>
|
||||
#include <Wt/Auth/PasswordVerifier>
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
// Db types
|
||||
#include "AudioTypes.hpp"
|
||||
#include "VideoTypes.hpp"
|
||||
#include "MediaDirectory.hpp"
|
||||
#include "User.hpp"
|
||||
|
||||
#include "DatabaseHandler.hpp"
|
||||
|
||||
namespace Database {
|
||||
|
||||
|
||||
namespace {
|
||||
Wt::Auth::AuthService authService;
|
||||
Wt::Auth::PasswordService passwordService(authService);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Handler::configureAuth(void)
|
||||
{
|
||||
authService.setEmailVerificationEnabled(true);
|
||||
|
||||
Wt::Auth::PasswordVerifier *verifier = new Wt::Auth::PasswordVerifier();
|
||||
verifier->addHashFunction(new Wt::Auth::BCryptHashFunction(8));
|
||||
passwordService.setVerifier(verifier);
|
||||
passwordService.setAttemptThrottlingEnabled(true);
|
||||
|
||||
Wt::Auth::PasswordStrengthValidator* strengthValidator = new Wt::Auth::PasswordStrengthValidator();
|
||||
// Reduce some constraints...
|
||||
strengthValidator->setMinimumLength( Wt::Auth::PasswordStrengthValidator::PassPhrase, 10);
|
||||
strengthValidator->setMinimumLength( Wt::Auth::PasswordStrengthValidator::OneCharClass, 8);
|
||||
strengthValidator->setMinimumLength( Wt::Auth::PasswordStrengthValidator::TwoCharClass, 7);
|
||||
strengthValidator->setMinimumLength( Wt::Auth::PasswordStrengthValidator::ThreeCharClass, 6 );
|
||||
strengthValidator->setMinimumLength( Wt::Auth::PasswordStrengthValidator::FourCharClass, 5 );
|
||||
|
||||
passwordService.setStrengthValidator(strengthValidator);
|
||||
}
|
||||
|
||||
const Wt::Auth::AuthService&
|
||||
Handler::getAuthService()
|
||||
{
|
||||
return authService;
|
||||
}
|
||||
|
||||
const Wt::Auth::PasswordService&
|
||||
Handler::getPasswordService()
|
||||
{
|
||||
return passwordService;
|
||||
}
|
||||
|
||||
|
||||
Handler::Handler(boost::filesystem::path db)
|
||||
:
|
||||
_dbBackend( db.string() )
|
||||
{
|
||||
_session.setConnection(_dbBackend);
|
||||
_session.mapClass<Database::Genre>("genre");
|
||||
_session.mapClass<Database::Track>("track");
|
||||
_session.mapClass<Database::Artist>("artist");
|
||||
_session.mapClass<Database::Release>("release");
|
||||
_session.mapClass<Database::Release>("release");
|
||||
_session.mapClass<Database::Video>("video");
|
||||
_session.mapClass<Database::MediaDirectory>("media_directory");
|
||||
_session.mapClass<Database::MediaDirectorySettings>("media_directory_settings");
|
||||
|
||||
_session.mapClass<Database::User>("user");
|
||||
_session.mapClass<Database::AuthInfo>("auth_info");
|
||||
_session.mapClass<Database::AuthInfo::AuthIdentityType>("auth_identity");
|
||||
_session.mapClass<Database::AuthInfo::AuthTokenType>("auth_token");
|
||||
|
||||
try {
|
||||
_session.createTables();
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
LMS_LOG(MOD_DB, SEV_ERROR) << "Cannot create tables: " << e.what();
|
||||
}
|
||||
|
||||
_dbBackend.executeSql("pragma journal_mode=WAL");
|
||||
|
||||
_users = new UserDatabase(_session);
|
||||
}
|
||||
|
||||
Handler::~Handler()
|
||||
{
|
||||
delete _users;
|
||||
}
|
||||
|
||||
Wt::Auth::AbstractUserDatabase&
|
||||
Handler::getUserDatabase()
|
||||
{
|
||||
return *_users;
|
||||
}
|
||||
|
||||
User::pointer
|
||||
Handler::getCurrentUser()
|
||||
{
|
||||
if (_login.loggedIn())
|
||||
return getUser(_login.user());
|
||||
else
|
||||
return User::pointer();
|
||||
}
|
||||
|
||||
User::pointer
|
||||
Handler::getUser(const Wt::Auth::User& authUser)
|
||||
{
|
||||
|
||||
if (!authUser.isValid()) {
|
||||
LMS_LOG(MOD_DB, SEV_ERROR) << "Handler::getUser: invalid authUser";
|
||||
return User::pointer();
|
||||
}
|
||||
|
||||
Wt::Dbo::ptr<AuthInfo> authInfo = _users->find(authUser);
|
||||
|
||||
User::pointer user = authInfo->user();
|
||||
|
||||
if (!user) {
|
||||
user = _session.add(new User());
|
||||
authInfo.modify()->setUser(user);
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
|
||||
} // namespace Database
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef DATABASE_HANDLER_HPP
|
||||
#define DATABASE_HANDLER_HPP
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <Wt/Dbo/Dbo>
|
||||
#include <Wt/Dbo/backend/Sqlite3>
|
||||
#include <Wt/Auth/Dbo/UserDatabase>
|
||||
#include <Wt/Auth/Login>
|
||||
#include <Wt/Auth/PasswordService>
|
||||
|
||||
#include "User.hpp"
|
||||
|
||||
namespace Database {
|
||||
|
||||
typedef Wt::Auth::Dbo::UserDatabase<AuthInfo> UserDatabase;
|
||||
|
||||
// Session living class handling the database and the login
|
||||
class Handler
|
||||
{
|
||||
public:
|
||||
|
||||
Handler(boost::filesystem::path db);
|
||||
~Handler();
|
||||
|
||||
Wt::Dbo::Session& getSession() { return _session; }
|
||||
|
||||
User::pointer getCurrentUser(); // get the current user, may return empty
|
||||
User::pointer getUser(const Wt::Auth::User& authUser); // Get or create the given user
|
||||
|
||||
Wt::Auth::AbstractUserDatabase& getUserDatabase();
|
||||
Wt::Auth::Login& getLogin() { return _login; }
|
||||
|
||||
// Long living shared associated services
|
||||
static void configureAuth();
|
||||
|
||||
static const Wt::Auth::AuthService& getAuthService();
|
||||
static const Wt::Auth::PasswordService& getPasswordService();
|
||||
|
||||
|
||||
private:
|
||||
|
||||
Wt::Dbo::backend::Sqlite3 _dbBackend;
|
||||
Wt::Dbo::Session _session;
|
||||
|
||||
UserDatabase* _users;
|
||||
Wt::Auth::Login _login;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Database
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "AudioTypes.hpp"
|
||||
|
||||
namespace Database {
|
||||
|
||||
Genre::Genre()
|
||||
{
|
||||
}
|
||||
|
||||
Genre::Genre(const std::string& name)
|
||||
: _name( std::string(name, 0, _maxNameLength) )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
Genre::pointer
|
||||
Genre::getByName(Wt::Dbo::Session& session, const std::string& name)
|
||||
{
|
||||
// TODO use like search
|
||||
return session.find<Genre>().where("name = ?").bind( std::string(name, 0, _maxNameLength) );
|
||||
}
|
||||
|
||||
Genre::pointer
|
||||
Genre::getNone(Wt::Dbo::Session& session)
|
||||
{
|
||||
pointer res = getByName(session, "<None>");
|
||||
if (!res)
|
||||
res = create(session, "<None>");
|
||||
return res;
|
||||
}
|
||||
|
||||
bool
|
||||
Genre::isNone(void) const
|
||||
{
|
||||
return (_name == "<None>");
|
||||
}
|
||||
|
||||
Genre::pointer
|
||||
Genre::create(Wt::Dbo::Session& session, const std::string& name)
|
||||
{
|
||||
return session.add(new Genre(name));
|
||||
}
|
||||
|
||||
Wt::Dbo::collection<Genre::pointer>
|
||||
Genre::getAll(Wt::Dbo::Session& session, std::size_t offset, std::size_t size)
|
||||
{
|
||||
return session.find<Genre>().offset(offset).limit(size);
|
||||
}
|
||||
|
||||
} // namespace Database
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include "MediaDirectory.hpp"
|
||||
|
||||
namespace Database {
|
||||
|
||||
MediaDirectorySettings::MediaDirectorySettings()
|
||||
: _manualScanRequested(false),
|
||||
_updatePeriod(Never)
|
||||
{
|
||||
}
|
||||
|
||||
MediaDirectory::MediaDirectory(boost::filesystem::path p, Type type)
|
||||
: _type(type),
|
||||
_path(p.string())
|
||||
{
|
||||
}
|
||||
|
||||
MediaDirectorySettings::pointer
|
||||
MediaDirectorySettings::get(Wt::Dbo::Session& session)
|
||||
{
|
||||
MediaDirectorySettings::pointer res;
|
||||
|
||||
res = session.find<MediaDirectorySettings>().where("id = ?").bind(1);
|
||||
// TODO bind necessary?
|
||||
if (!res)
|
||||
res = session.add( new MediaDirectorySettings());
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
MediaDirectory::pointer
|
||||
MediaDirectory::create(Wt::Dbo::Session& session, boost::filesystem::path p, Type type)
|
||||
{
|
||||
return session.add( new MediaDirectory( p, type ) );
|
||||
}
|
||||
|
||||
void
|
||||
MediaDirectory::eraseAll(Wt::Dbo::Session& session)
|
||||
{
|
||||
std::vector<MediaDirectory::pointer> dirs = getAll(session);
|
||||
BOOST_FOREACH(MediaDirectory::pointer dir, dirs)
|
||||
dir.remove();
|
||||
}
|
||||
|
||||
std::vector<MediaDirectory::pointer>
|
||||
MediaDirectory::getAll(Wt::Dbo::Session& session)
|
||||
{
|
||||
Wt::Dbo::collection< MediaDirectory::pointer > res = session.find<MediaDirectory>();
|
||||
|
||||
return std::vector<MediaDirectory::pointer>(res.begin(), res.end());
|
||||
}
|
||||
|
||||
std::vector<MediaDirectory::pointer>
|
||||
MediaDirectory::getByType(Wt::Dbo::Session& session, Type type)
|
||||
{
|
||||
Wt::Dbo::collection< MediaDirectory::pointer > res = session.find<MediaDirectory>().where("type = ?").bind (type);
|
||||
|
||||
return std::vector<MediaDirectory::pointer>(res.begin(), res.end());
|
||||
}
|
||||
|
||||
MediaDirectory::pointer
|
||||
MediaDirectory::get(Wt::Dbo::Session& session, boost::filesystem::path p, Type type)
|
||||
{
|
||||
return session.find<MediaDirectory>().where("path = ?").where("type = ?").bind( p.string()).bind(type);
|
||||
}
|
||||
|
||||
} // namespace Database
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef DATABASE_MEDIA_DIRECTORY_HPP
|
||||
#define DATABASE_MEDIA_DIRECTORY_HPP
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <boost/filesystem/path.hpp>
|
||||
|
||||
#include <Wt/Dbo/Dbo>
|
||||
#include <Wt/Dbo/WtSqlTraits>
|
||||
|
||||
namespace Database {
|
||||
|
||||
class MediaDirectory;
|
||||
|
||||
class MediaDirectorySettings
|
||||
{
|
||||
public:
|
||||
|
||||
enum UpdatePeriod {
|
||||
Never,
|
||||
Daily,
|
||||
Weekly,
|
||||
Monthly
|
||||
};
|
||||
|
||||
typedef Wt::Dbo::ptr<MediaDirectorySettings> pointer;
|
||||
|
||||
MediaDirectorySettings();
|
||||
|
||||
// accessors
|
||||
static pointer get(Wt::Dbo::Session& session);
|
||||
|
||||
// write accessors
|
||||
void setManualScanRequested(bool value) { _manualScanRequested = value;}
|
||||
void setUpdatePeriod(UpdatePeriod period) { _updatePeriod = period;}
|
||||
void setUpdateStartTime(boost::posix_time::time_duration dur) { _updateStartTime = dur;}
|
||||
void setLastUpdate(boost::posix_time::ptime time) { _lastUpdate = time; }
|
||||
void setLastScan(boost::posix_time::ptime time) { _lastScan = time; }
|
||||
|
||||
// Read accessors
|
||||
bool getManualScanRequested(void) const { return _manualScanRequested; }
|
||||
UpdatePeriod getUpdatePeriod(void) const { return _updatePeriod; }
|
||||
boost::posix_time::time_duration getUpdateStartTime(void) const { return _updateStartTime; }
|
||||
boost::posix_time::ptime getLastUpdated(void) const { return _lastUpdate; }
|
||||
boost::posix_time::ptime getLastScan(void) const { return _lastScan; }
|
||||
|
||||
template<class Action>
|
||||
void persist(Action& a)
|
||||
{
|
||||
Wt::Dbo::field(a, _manualScanRequested, "manual_scan_requested");
|
||||
Wt::Dbo::field(a, _updatePeriod, "update_period");
|
||||
Wt::Dbo::field(a, _updateStartTime, "update_start_time");
|
||||
Wt::Dbo::field(a, _lastUpdate, "last_update");
|
||||
Wt::Dbo::field(a, _lastScan, "last_scan");
|
||||
Wt::Dbo::hasMany(a, _mediaDirectories, Wt::Dbo::ManyToOne, "media_directory_settings");
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
bool _manualScanRequested; // Immadiate scan has been requested by user
|
||||
UpdatePeriod _updatePeriod; // How long between updates
|
||||
boost::posix_time::time_duration _updateStartTime; // Time of day to begin the update
|
||||
boost::posix_time::ptime _lastUpdate; // last time the database has changed
|
||||
boost::posix_time::ptime _lastScan; // last time the database has been scanned
|
||||
Wt::Dbo::collection< Wt::Dbo::ptr<MediaDirectory> > _mediaDirectories; // list of media directories
|
||||
};
|
||||
|
||||
|
||||
class MediaDirectory
|
||||
{
|
||||
public:
|
||||
|
||||
typedef Wt::Dbo::ptr<MediaDirectory> pointer;
|
||||
|
||||
enum Type {
|
||||
Audio = 1,
|
||||
Video = 2,
|
||||
};
|
||||
|
||||
MediaDirectory() {}
|
||||
MediaDirectory(boost::filesystem::path p, Type type);
|
||||
|
||||
// Accessors
|
||||
static pointer create(Wt::Dbo::Session& session, boost::filesystem::path p, Type type);
|
||||
static std::vector<MediaDirectory::pointer> getAll(Wt::Dbo::Session& session);
|
||||
static std::vector<MediaDirectory::pointer> getByType(Wt::Dbo::Session& session, Type type);
|
||||
static pointer get(Wt::Dbo::Session& session, boost::filesystem::path p, Type type);
|
||||
|
||||
static void eraseAll(Wt::Dbo::Session& session);
|
||||
|
||||
Type getType(void) const { return _type; }
|
||||
boost::filesystem::path getPath(void) const { return boost::filesystem::path(_path); }
|
||||
|
||||
template<class Action>
|
||||
void persist(Action& a)
|
||||
{
|
||||
Wt::Dbo::field(a, _type, "type");
|
||||
Wt::Dbo::field(a, _path, "path");
|
||||
Wt::Dbo::belongsTo(a, _settings, "media_directory_settings", Wt::Dbo::OnDeleteCascade);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
Type _type;
|
||||
std::string _path;
|
||||
|
||||
MediaDirectorySettings::pointer _settings; // back pointer
|
||||
|
||||
};
|
||||
|
||||
} // namespace Database
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include "SqlQuery.hpp"
|
||||
|
||||
#include "AudioTypes.hpp"
|
||||
|
||||
namespace Database {
|
||||
|
||||
Release::Release(const std::string& name)
|
||||
: _name(std::string(name, 0, _maxNameLength))
|
||||
{
|
||||
}
|
||||
|
||||
Release::pointer
|
||||
Release::getByName(Wt::Dbo::Session& session, const std::string& name)
|
||||
{
|
||||
return session.find<Release>().where("name = ?").bind( std::string(name, 0, _maxNameLength) );
|
||||
}
|
||||
|
||||
Release::pointer
|
||||
Release::getById(Wt::Dbo::Session& session, id_type id)
|
||||
{
|
||||
return session.find<Release>().where("id = ?").bind(id);
|
||||
}
|
||||
|
||||
Release::pointer
|
||||
Release::getNone(Wt::Dbo::Session& session)
|
||||
{
|
||||
pointer res = getByName(session, "<None>");
|
||||
if (!res)
|
||||
res = create(session, "<None>");
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
Release::pointer
|
||||
Release::create(Wt::Dbo::Session& session, const std::string& name)
|
||||
{
|
||||
return session.add(new Release(name));
|
||||
}
|
||||
|
||||
Wt::Dbo::collection<Release::pointer>
|
||||
Release::getAll(Wt::Dbo::Session& session, std::vector<Artist::id_type> artistIds, int offset, int size)
|
||||
{
|
||||
std::string sqlQuery = "SELECT r FROM release r";
|
||||
|
||||
if (!artistIds.empty())
|
||||
{
|
||||
sqlQuery += " INNER JOIN artist a ON a.id = t.artist_id";
|
||||
sqlQuery += " INNER JOIN track t ON t.release_id = r.id";
|
||||
}
|
||||
|
||||
Wt::Dbo::Query<Release::pointer> query = session.query<Release::pointer>( sqlQuery ).offset(offset).limit(size);
|
||||
|
||||
BOOST_FOREACH(const Artist::id_type artistId, artistIds)
|
||||
query.where("a.id = ?").bind(artistId);
|
||||
|
||||
query.groupBy("r");
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
boost::posix_time::time_duration
|
||||
Release::getDuration(void) const
|
||||
{
|
||||
typedef Wt::Dbo::collection< Wt::Dbo::ptr<Track> > Tracks;
|
||||
|
||||
boost::posix_time::time_duration res;
|
||||
|
||||
for (Tracks::const_iterator it = _tracks.begin(); it != _tracks.end(); ++it)
|
||||
res += (*it)->getDuration();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
Wt::Dbo::collection<Release::pointer>
|
||||
Release::getAllOrphans(Wt::Dbo::Session& session)
|
||||
{
|
||||
return session.query< Wt::Dbo::ptr<Release> >("select r from release r LEFT OUTER JOIN Track t ON r.id = t.release_id WHERE t.id IS NULL");
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace Database
|
||||
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "SqlQuery.hpp"
|
||||
|
||||
WhereClause&
|
||||
WhereClause::And(const WhereClause& otherClause)
|
||||
{
|
||||
if (!otherClause._clause.empty()) {
|
||||
if (!_clause.empty())
|
||||
_clause += " AND ";
|
||||
_clause += "(" + otherClause._clause + ")";
|
||||
|
||||
// Add associated bind args
|
||||
BOOST_FOREACH(const std::string& otherBindArg, otherClause._bindArgs) {
|
||||
_bindArgs.push_back(otherBindArg);
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
WhereClause&
|
||||
WhereClause::Or(const WhereClause& otherClause)
|
||||
{
|
||||
if (!otherClause._clause.empty()) {
|
||||
if (!_clause.empty())
|
||||
_clause += " OR ";
|
||||
_clause += "(" + otherClause._clause + ")";
|
||||
|
||||
// Add associated bind args
|
||||
BOOST_FOREACH(const std::string& otherBindArg, otherClause._bindArgs) {
|
||||
_bindArgs.push_back(otherBindArg);
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::string
|
||||
WhereClause::get(void) const
|
||||
{
|
||||
if (!_clause.empty())
|
||||
return "WHERE " + _clause;
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
WhereClause&
|
||||
WhereClause::bind(const std::string& bindArg)
|
||||
{
|
||||
if (_bindArgs.size() >= static_cast<std::size_t>( std::count(_clause.begin(), _clause.end(), '?') ))
|
||||
throw std::runtime_error("Too many bind args!");
|
||||
|
||||
_bindArgs.push_back(bindArg);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
InnerJoinClause::InnerJoinClause(const std::string& clause)
|
||||
:_clause(clause)
|
||||
{
|
||||
}
|
||||
|
||||
InnerJoinClause&
|
||||
InnerJoinClause::And(const InnerJoinClause& clause)
|
||||
{
|
||||
if (!_clause.empty())
|
||||
_clause += " ";
|
||||
|
||||
_clause += clause._clause;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
SelectStatement::SelectStatement(const std::string& statement)
|
||||
{
|
||||
And(statement);
|
||||
}
|
||||
|
||||
SelectStatement&
|
||||
SelectStatement::And(const std::string& statement)
|
||||
{
|
||||
_statement.push_back(statement);
|
||||
|
||||
_statement.sort();
|
||||
_statement.unique();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::string
|
||||
SelectStatement::get() const
|
||||
{
|
||||
std::string res = "SELECT ";
|
||||
|
||||
for (std::list<std::string>::const_iterator it = _statement.begin(); it != _statement.end(); ++it)
|
||||
{
|
||||
if (it != _statement.begin())
|
||||
res += ",";
|
||||
res += *it;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
|
||||
GroupByStatement&
|
||||
GroupByStatement::And(const GroupByStatement& statement)
|
||||
{
|
||||
if( _statement.empty() && !statement._statement.empty())
|
||||
_statement = "GROUP BY ";
|
||||
else if (!_statement.empty() && !statement._statement.empty())
|
||||
_statement += ",";
|
||||
|
||||
_statement += statement._statement;
|
||||
return *this;
|
||||
}
|
||||
|
||||
FromClause::FromClause(const std::string& clause)
|
||||
{
|
||||
_clause.push_back(clause);
|
||||
}
|
||||
|
||||
FromClause&
|
||||
FromClause::And(const FromClause& clause)
|
||||
{
|
||||
BOOST_FOREACH(const std::string fromClause, clause._clause) {
|
||||
_clause.push_back(fromClause);
|
||||
}
|
||||
|
||||
_clause.sort();
|
||||
_clause.unique();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::string
|
||||
FromClause::get() const
|
||||
{
|
||||
std::ostringstream oss;
|
||||
|
||||
if (!_clause.empty())
|
||||
{
|
||||
oss << "FROM ";
|
||||
for (std::list<std::string>::const_iterator it = _clause.begin(); it != _clause.end(); ++it) {
|
||||
if (it != _clause.begin())
|
||||
oss << ",";
|
||||
|
||||
oss << *it;
|
||||
}
|
||||
}
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::string
|
||||
SqlQuery::get(void) const
|
||||
{
|
||||
std::ostringstream oss;
|
||||
|
||||
oss << _selectStatement.get();
|
||||
|
||||
if (!_fromClause.get().empty())
|
||||
oss << " " << _fromClause.get();
|
||||
|
||||
if (!_innerJoinClause.get().empty())
|
||||
oss << " " << _innerJoinClause.get();
|
||||
|
||||
if (!_whereClause.get().empty())
|
||||
oss << " " << _whereClause.get();
|
||||
|
||||
if (!_groupByStatement.get().empty())
|
||||
oss << " " << _groupByStatement.get();
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SQL_QUERY_HPP___
|
||||
#define SQL_QUERY_HPP___
|
||||
|
||||
#include <list>
|
||||
#include <string>
|
||||
|
||||
|
||||
class WhereClause
|
||||
{
|
||||
public:
|
||||
|
||||
WhereClause() {}
|
||||
WhereClause(const std::string& clause) { _clause = clause; }
|
||||
|
||||
WhereClause& And(const WhereClause& clause);
|
||||
WhereClause& Or(const WhereClause& clause);
|
||||
|
||||
// Arguments binding (for each '?' in where clause)
|
||||
WhereClause& bind(const std::string& arg);
|
||||
|
||||
std::string get() const;
|
||||
const std::list<std::string>& getBindArgs(void) const {return _bindArgs;}
|
||||
|
||||
private:
|
||||
|
||||
std::string _clause; // WHERE clause
|
||||
std::list<std::string> _bindArgs;
|
||||
|
||||
};
|
||||
|
||||
class InnerJoinClause
|
||||
{
|
||||
public:
|
||||
|
||||
InnerJoinClause() {}
|
||||
InnerJoinClause(const std::string& clause);
|
||||
|
||||
InnerJoinClause& And(const InnerJoinClause& clause);
|
||||
std::string get() const { return _clause;}
|
||||
|
||||
private:
|
||||
|
||||
std::string _clause;
|
||||
};
|
||||
|
||||
class GroupByStatement
|
||||
{
|
||||
public:
|
||||
GroupByStatement() {}
|
||||
GroupByStatement(const std::string& statement) { _statement = statement; }
|
||||
|
||||
GroupByStatement& And(const GroupByStatement& statement);
|
||||
|
||||
std::string get() const {return _statement;}
|
||||
|
||||
private:
|
||||
std::string _statement; // SELECT statement
|
||||
|
||||
};
|
||||
|
||||
class SelectStatement
|
||||
{
|
||||
public:
|
||||
SelectStatement() {};
|
||||
SelectStatement(const std::string& item);
|
||||
|
||||
SelectStatement& And(const std::string& item);
|
||||
|
||||
std::string get() const;
|
||||
|
||||
private:
|
||||
|
||||
std::list<std::string> _statement;
|
||||
};
|
||||
|
||||
class FromClause
|
||||
{
|
||||
public:
|
||||
|
||||
FromClause() {}
|
||||
FromClause(const std::string& clause);
|
||||
|
||||
FromClause& And(const FromClause& clause);
|
||||
|
||||
std::string get() const;
|
||||
|
||||
private:
|
||||
|
||||
std::list<std::string> _clause;
|
||||
|
||||
};
|
||||
|
||||
class SqlQuery
|
||||
{
|
||||
public:
|
||||
|
||||
SelectStatement& select(void) { return _selectStatement;}
|
||||
SelectStatement& select(const std::string& statement) { _selectStatement = SelectStatement(statement); return _selectStatement; }
|
||||
FromClause& from(void) { return _fromClause; }
|
||||
FromClause& from(const std::string& clause) { _whereClause = WhereClause(clause); return _fromClause; }
|
||||
InnerJoinClause& innerJoin(void) { return _innerJoinClause; }
|
||||
WhereClause& where(void) { return _whereClause; }
|
||||
const WhereClause& where(void) const { return _whereClause; }
|
||||
GroupByStatement& groupBy(void) { return _groupByStatement; }
|
||||
const GroupByStatement& groupBy(void) const { return _groupByStatement; }
|
||||
|
||||
std::string get(void) const;
|
||||
|
||||
private:
|
||||
|
||||
SelectStatement _selectStatement; // SELECT statement
|
||||
InnerJoinClause _innerJoinClause; // INNER JOIN
|
||||
FromClause _fromClause; // FROM tables
|
||||
WhereClause _whereClause; // WHERE clause
|
||||
GroupByStatement _groupByStatement; // GROUP BY statement
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include "SqlQuery.hpp"
|
||||
|
||||
#include "AudioTypes.hpp"
|
||||
|
||||
namespace Database {
|
||||
|
||||
Track::Track(const boost::filesystem::path& p, Artist::pointer artist, Release::pointer release)
|
||||
:
|
||||
_trackNumber(0),
|
||||
_discNumber(0),
|
||||
_filePath( p.string() ),
|
||||
_artist(artist),
|
||||
_release(release)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Track::setGenres(std::vector<Genre::pointer> genres)
|
||||
{
|
||||
if (_genres.size())
|
||||
_genres.clear();
|
||||
|
||||
BOOST_FOREACH(Genre::pointer genre, genres) {
|
||||
_genres.insert( genre );
|
||||
}
|
||||
}
|
||||
|
||||
Track::pointer
|
||||
Track::getByPath(Wt::Dbo::Session& session, const boost::filesystem::path& p)
|
||||
{
|
||||
return session.find<Track>().where("path = ?").bind(p.string());
|
||||
}
|
||||
|
||||
Track::pointer
|
||||
Track::getById(Wt::Dbo::Session& session, id_type id)
|
||||
{
|
||||
return session.find<Track>().where("id = ?").bind(id);
|
||||
}
|
||||
|
||||
|
||||
Track::pointer
|
||||
Track::create(Wt::Dbo::Session& session, const boost::filesystem::path& p, Artist::pointer artist, Release::pointer release)
|
||||
{
|
||||
return session.add(new Track(p, artist, release) );
|
||||
}
|
||||
|
||||
Wt::Dbo::collection< Track::pointer >
|
||||
Track::getAll(Wt::Dbo::Session& session)
|
||||
{
|
||||
return session.find<Track>();
|
||||
}
|
||||
|
||||
std::vector< Genre::pointer >
|
||||
Track::getGenres(void) const
|
||||
{
|
||||
std::vector< Genre::pointer > genres;
|
||||
std::copy(_genres.begin(), _genres.end(), std::back_inserter(genres));
|
||||
return genres;
|
||||
}
|
||||
|
||||
Wt::Dbo::collection< Track::pointer >
|
||||
Track::getAll(Wt::Dbo::Session& session,
|
||||
const std::vector<Artist::id_type>& artistIds,
|
||||
const std::vector<Release::id_type>& releaseIds,
|
||||
const std::vector<Genre::id_type>& genreIds,
|
||||
int offset, int size)
|
||||
{
|
||||
std::string sqlQuery = "SELECT t FROM track t";
|
||||
|
||||
if (!artistIds.empty())
|
||||
sqlQuery += " INNER JOIN artist a ON a.id = t.artist_id";
|
||||
|
||||
if (!releaseIds.empty())
|
||||
sqlQuery += " INNER JOIN release r ON r.id = t.release_id";
|
||||
|
||||
if (!genreIds.empty())
|
||||
{
|
||||
sqlQuery += " INNER JOIN genre g ON g.id = t_g.genre_id";
|
||||
sqlQuery += " INNER JOIN track_genre t_g ON t_g.track_id = t.id AND t_d.genre_id = g.id";
|
||||
}
|
||||
|
||||
WhereClause where;
|
||||
|
||||
{
|
||||
WhereClause artistWhere;
|
||||
|
||||
for (std::size_t i = 0; i < artistIds.size(); ++i)
|
||||
artistWhere.Or( WhereClause("a.id = ?") );
|
||||
|
||||
where.And(artistWhere);
|
||||
}
|
||||
|
||||
{
|
||||
WhereClause releaseWhere;
|
||||
|
||||
for (std::size_t i = 0; i < releaseIds.size(); ++i)
|
||||
releaseWhere.Or( WhereClause("r.id = ?") );
|
||||
|
||||
where.And(releaseWhere);
|
||||
}
|
||||
|
||||
{
|
||||
WhereClause genreWhere;
|
||||
|
||||
for (std::size_t i = 0; i < genreIds.size(); ++i)
|
||||
genreWhere.Or( WhereClause("g.id = ?") );
|
||||
|
||||
where.And(genreWhere);
|
||||
}
|
||||
|
||||
|
||||
Wt::Dbo::Query<Track::pointer> query = session.query<Track::pointer>( sqlQuery + " " + where.get() ).offset(offset).limit(size);
|
||||
|
||||
BOOST_FOREACH(const Artist::id_type artistId, artistIds)
|
||||
query.bind(artistId);
|
||||
|
||||
BOOST_FOREACH(const Release::id_type releaseId, releaseIds)
|
||||
query.bind(releaseId);
|
||||
|
||||
BOOST_FOREACH(const Genre::id_type genreId, genreIds)
|
||||
query.bind(genreId);
|
||||
|
||||
query.groupBy("t");
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
} // namespace Database
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "User.hpp"
|
||||
|
||||
|
||||
namespace Database {
|
||||
|
||||
|
||||
// must be ordered
|
||||
const std::vector<std::size_t>
|
||||
User::audioBitrates =
|
||||
{
|
||||
64000,
|
||||
96000,
|
||||
128000,
|
||||
160000,
|
||||
192000,
|
||||
224000,
|
||||
256000,
|
||||
320000,
|
||||
512000
|
||||
};
|
||||
|
||||
|
||||
const std::vector<std::size_t>
|
||||
User::videoBitrates =
|
||||
{
|
||||
256000,
|
||||
512000,
|
||||
1024000,
|
||||
2048000,
|
||||
4096000,
|
||||
8192000
|
||||
};
|
||||
User::User()
|
||||
: _maxAudioBitrate(maxAudioBitrate),
|
||||
_maxVideoBitrate(maxVideoBitrate),
|
||||
_isAdmin(false),
|
||||
_audioBitrate(defaultAudioBitrate),
|
||||
_videoBitrate(defaultVideoBitrate)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
std::vector<User::pointer>
|
||||
User::getAll(Wt::Dbo::Session& session)
|
||||
{
|
||||
Wt::Dbo::collection<pointer> res = session.find<User>();
|
||||
return std::vector<pointer>(res.begin(), res.end());
|
||||
}
|
||||
|
||||
User::pointer
|
||||
User::getById(Wt::Dbo::Session& session, std::string id)
|
||||
{
|
||||
return session.find<User>().where("id = ?").bind( id );
|
||||
}
|
||||
|
||||
std::string
|
||||
User::getId( pointer user)
|
||||
{
|
||||
std::ostringstream oss; oss << user.id();
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
void
|
||||
User::setAudioBitrate(std::size_t bitrate)
|
||||
{
|
||||
_audioBitrate = std::min(bitrate, std::min(static_cast<std::size_t>(_maxAudioBitrate), audioBitrates.back()));
|
||||
}
|
||||
|
||||
void
|
||||
User::setVideoBitrate(std::size_t bitrate)
|
||||
{
|
||||
_videoBitrate = std::min(bitrate, std::min(static_cast<std::size_t>(_maxVideoBitrate), videoBitrates.back()));
|
||||
}
|
||||
|
||||
void
|
||||
User::setMaxAudioBitrate(std::size_t bitrate)
|
||||
{
|
||||
_maxAudioBitrate = std::min(bitrate, static_cast<std::size_t>(_maxAudioBitrate));
|
||||
}
|
||||
|
||||
void
|
||||
User::setMaxVideoBitrate(std::size_t bitrate)
|
||||
{
|
||||
_maxVideoBitrate = std::min(bitrate, static_cast<std::size_t>(_maxVideoBitrate));
|
||||
}
|
||||
|
||||
std::size_t
|
||||
User::getAudioBitrate(void) const
|
||||
{
|
||||
if (!isAdmin())
|
||||
return std::min(static_cast<std::size_t>(_audioBitrate), std::min(static_cast<std::size_t>(_maxAudioBitrate), audioBitrates.back()));
|
||||
else
|
||||
return std::min(static_cast<std::size_t>(_audioBitrate), audioBitrates.back());
|
||||
}
|
||||
|
||||
std::size_t
|
||||
User::getVideoBitrate(void) const
|
||||
{
|
||||
if (!isAdmin())
|
||||
return std::min(static_cast<std::size_t>(_videoBitrate), std::min(static_cast<std::size_t>(_maxVideoBitrate), videoBitrates.back()));
|
||||
else
|
||||
return std::min(static_cast<std::size_t>(_videoBitrate), videoBitrates.back());
|
||||
}
|
||||
|
||||
std::size_t
|
||||
User::getMaxAudioBitrate(void) const
|
||||
{
|
||||
if (!isAdmin())
|
||||
return std::min(static_cast<std::size_t>(_maxAudioBitrate), audioBitrates.back());
|
||||
else
|
||||
return audioBitrates.back();
|
||||
}
|
||||
|
||||
std::size_t
|
||||
User::getMaxVideoBitrate(void) const
|
||||
{
|
||||
if (!isAdmin())
|
||||
return std::min(static_cast<std::size_t>(_maxVideoBitrate), videoBitrates.back());
|
||||
else
|
||||
return videoBitrates.back();
|
||||
}
|
||||
|
||||
|
||||
} // namespace Database
|
||||
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef DATABASE_USER_HPP
|
||||
#define DATABASE_USER_HPP
|
||||
|
||||
|
||||
#include <Wt/Auth/Dbo/AuthInfo>
|
||||
|
||||
namespace Database {
|
||||
|
||||
class User;
|
||||
typedef Wt::Auth::Dbo::AuthInfo<User> AuthInfo;
|
||||
|
||||
class User {
|
||||
|
||||
public:
|
||||
static const std::size_t MaxNameLength = 15;
|
||||
|
||||
// list of commonly used bitrates
|
||||
static const std::vector<std::size_t> audioBitrates;
|
||||
static const std::vector<std::size_t> videoBitrates;
|
||||
|
||||
User();
|
||||
|
||||
typedef Wt::Dbo::ptr<User> pointer;
|
||||
|
||||
// accessors
|
||||
static pointer getById(Wt::Dbo::Session& session, std::string id);
|
||||
static std::vector<pointer> getAll(Wt::Dbo::Session& session);
|
||||
static std::string getId(pointer user);
|
||||
|
||||
// write
|
||||
void setAdmin(bool admin) { _isAdmin = admin; }
|
||||
void setAudioBitrate(std::size_t bitrate);
|
||||
void setVideoBitrate(std::size_t bitrate);
|
||||
void setMaxAudioBitrate(std::size_t bitrate);
|
||||
void setMaxVideoBitrate(std::size_t bitrate);
|
||||
|
||||
// read
|
||||
bool isAdmin() const {return _isAdmin;}
|
||||
std::size_t getAudioBitrate() const;
|
||||
std::size_t getVideoBitrate() const;
|
||||
std::size_t getMaxAudioBitrate() const;
|
||||
std::size_t getMaxVideoBitrate() const;
|
||||
|
||||
template<class Action>
|
||||
void persist(Action& a)
|
||||
{
|
||||
Wt::Dbo::field(a, _maxAudioBitrate, "max_audio_bitrate");
|
||||
Wt::Dbo::field(a, _maxVideoBitrate, "max_video_bitrate");
|
||||
Wt::Dbo::field(a, _isAdmin, "admin");
|
||||
Wt::Dbo::field(a, _audioBitrate, "audio_bitrate");
|
||||
Wt::Dbo::field(a, _videoBitrate, "video_bitrate");
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
static const std::size_t maxAudioBitrate = 320000;
|
||||
static const std::size_t maxVideoBitrate = 2048000;
|
||||
|
||||
static const std::size_t defaultAudioBitrate = 128000;
|
||||
static const std::size_t defaultVideoBitrate = 1024000;
|
||||
|
||||
// Admin defined settings
|
||||
int _maxAudioBitrate;
|
||||
int _maxVideoBitrate;
|
||||
bool _isAdmin;
|
||||
|
||||
// User defined settings
|
||||
int _audioBitrate;
|
||||
int _videoBitrate;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Databas'
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "VideoTypes.hpp"
|
||||
|
||||
namespace Database {
|
||||
|
||||
Video::Video()
|
||||
{
|
||||
}
|
||||
|
||||
Video::Video(const boost::filesystem::path& p)
|
||||
: _filePath(p.string())
|
||||
{
|
||||
}
|
||||
|
||||
Video::pointer
|
||||
Video::create(Wt::Dbo::Session& session, const boost::filesystem::path& p)
|
||||
{
|
||||
return session.add(new Video(p));
|
||||
}
|
||||
|
||||
Wt::Dbo::collection< Video::pointer >
|
||||
Video::getAll(Wt::Dbo::Session& session)
|
||||
{
|
||||
return session.find<Video>();
|
||||
}
|
||||
|
||||
Video::pointer
|
||||
Video::getByPath(Wt::Dbo::Session& session, const boost::filesystem::path& p)
|
||||
{
|
||||
return session.find<Video>().where("path = ?").bind(p.string());
|
||||
}
|
||||
|
||||
} // namespace Video
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _VIDEO_TYPES_HPP
|
||||
#define _VIDEO_TYPES_HPP
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <Wt/Dbo/Dbo>
|
||||
#include <Wt/Dbo/WtSqlTraits>
|
||||
|
||||
#include <Wt/WDateTime>
|
||||
|
||||
namespace Database {
|
||||
|
||||
class Video
|
||||
{
|
||||
public:
|
||||
|
||||
typedef Wt::Dbo::ptr<Video> pointer;
|
||||
|
||||
Video();
|
||||
Video( const boost::filesystem::path& p);
|
||||
|
||||
// Find utilities
|
||||
static pointer getByPath(Wt::Dbo::Session& session, const boost::filesystem::path& p);
|
||||
static Wt::Dbo::collection< pointer > getAll(Wt::Dbo::Session& session);
|
||||
static Wt::Dbo::collection< pointer > getByParentPath(Wt::Dbo::Session& session, const boost::filesystem::path& p);
|
||||
|
||||
// Create utility
|
||||
static pointer create(Wt::Dbo::Session& session, const boost::filesystem::path& p);
|
||||
|
||||
// Modifiers
|
||||
void setName(const std::string& name) { _name = name; }
|
||||
void setDuration(boost::posix_time::time_duration duration) { _duration = duration; }
|
||||
void setLastWriteTime(boost::posix_time::ptime time) { _fileLastWrite = time; }
|
||||
|
||||
// Accessors
|
||||
std::string getName(void) const { return _name; }
|
||||
boost::posix_time::time_duration getDuration(void) const { return _duration; }
|
||||
boost::filesystem::path getPath(void) const { return _filePath; }
|
||||
boost::posix_time::ptime getLastWriteTime(void) const { return _fileLastWrite; }
|
||||
|
||||
template<class Action>
|
||||
void persist(Action& a)
|
||||
{
|
||||
Wt::Dbo::field(a, _name, "name");
|
||||
Wt::Dbo::field(a, _duration, "duration");
|
||||
Wt::Dbo::field(a, _fileLastWrite, "last_write");
|
||||
Wt::Dbo::field(a, _filePath, "path");
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
std::string _name;
|
||||
boost::posix_time::time_duration _duration;
|
||||
std::string _filePath;
|
||||
boost::posix_time::ptime _fileLastWrite;
|
||||
|
||||
}; // Video
|
||||
|
||||
|
||||
} // namespace Database
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include <boost/log/core.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/log/expressions.hpp>
|
||||
#include <boost/log/sinks/text_file_backend.hpp>
|
||||
#include </usr/include/boost/log/keywords/filter.hpp>
|
||||
#include <boost/log/utility/setup/file.hpp>
|
||||
#include <boost/log/utility/setup/common_attributes.hpp>
|
||||
#include <boost/log/utility/setup/console.hpp>
|
||||
#include <boost/log/sources/severity_logger.hpp>
|
||||
#include <boost/log/sources/record_ostream.hpp>
|
||||
#include <boost/log/attributes/constant.hpp>
|
||||
#include <boost/log/sources/severity_logger.hpp>
|
||||
#include <boost/log/sources/record_ostream.hpp>
|
||||
#include <boost/log/support/date_time.hpp>
|
||||
#include <boost/log/attributes/named_scope.hpp>
|
||||
|
||||
#include <boost/date_time/posix_time/ptime.hpp>
|
||||
|
||||
|
||||
#include "Logger.hpp"
|
||||
|
||||
Logger&
|
||||
Logger::instance()
|
||||
{
|
||||
static Logger instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
Logger::Logger()
|
||||
{
|
||||
// Initialiaz loggers
|
||||
static const std::vector<Module> modules =
|
||||
{
|
||||
MOD_AV,
|
||||
MOD_COVER,
|
||||
MOD_DB,
|
||||
MOD_DBUPDATER,
|
||||
MOD_MAIN,
|
||||
MOD_METADATA,
|
||||
MOD_REMOTE,
|
||||
MOD_SERVICE,
|
||||
MOD_TRANSCODE,
|
||||
MOD_UI,
|
||||
};
|
||||
|
||||
BOOST_FOREACH(Module module, modules)
|
||||
_loggers[module].add_attribute("Module", boost::log::attributes::constant< Module >(module));
|
||||
}
|
||||
|
||||
boost::log::sources::severity_logger< Severity >&
|
||||
Logger::get(Module module)
|
||||
{
|
||||
return _loggers[module];
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Logger::init(const Config& config)
|
||||
{
|
||||
boost::log::add_common_attributes();
|
||||
|
||||
boost::log::register_simple_formatter_factory< Severity, char >("Severity");
|
||||
|
||||
if (config.enableFileLogging)
|
||||
{
|
||||
boost::log::add_file_log
|
||||
(
|
||||
boost::log::keywords::file_name = config.logPath + std::string(".%N"),
|
||||
boost::log::keywords::rotation_size = 10 * 1024 * 1024,
|
||||
boost::log::keywords::open_mode = std::ios_base::app,
|
||||
boost::log::keywords::format = (
|
||||
boost::log::expressions::stream
|
||||
<< boost::log::expressions::format_date_time< boost::posix_time::ptime >("TimeStamp", "[%Y-%m-%d %H:%M:%S]")
|
||||
<< " [" << boost::log::expressions::attr< Module >("Module") << "]"
|
||||
<< " [" << boost::log::expressions::attr< Severity >("Severity") << "]"
|
||||
<< " " << boost::log::expressions::smessage
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (config.enableConsoleLogging)
|
||||
{
|
||||
boost::log::add_console_log(std::cout,
|
||||
boost::log::keywords::format = (
|
||||
boost::log::expressions::stream
|
||||
<< boost::log::expressions::format_date_time< boost::posix_time::ptime >("TimeStamp", "[%Y-%m-%d %H:%M:%S]")
|
||||
<< " [" << boost::log::expressions::attr< Module >("Module") << "]"
|
||||
<< " [" << boost::log::expressions::attr< Severity >("Severity") << "]"
|
||||
<< " " << boost::log::expressions::smessage
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
boost::log::core::get()->set_filter
|
||||
(
|
||||
boost::log::expressions::attr<Severity>("Severity") <= config.minSeverity
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef LOGGER_HPP__
|
||||
#define LOGGER_HPP__
|
||||
|
||||
#include <map>
|
||||
|
||||
#include <boost/log/expressions/keyword_fwd.hpp>
|
||||
#include <boost/log/expressions/keyword.hpp>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/log/attributes/named_scope.hpp>
|
||||
|
||||
#define LMS_LOG(module, level) BOOST_LOG_SEV(Logger::instance().get(module), level)
|
||||
|
||||
enum Severity
|
||||
{
|
||||
SEV_CRIT = 2,
|
||||
SEV_ERROR = 3,
|
||||
SEV_WARNING = 4,
|
||||
SEV_NOTICE = 5,
|
||||
SEV_INFO = 6,
|
||||
SEV_DEBUG = 7,
|
||||
};
|
||||
|
||||
enum Module
|
||||
{
|
||||
MOD_AV = 0,
|
||||
MOD_COVER,
|
||||
MOD_DB,
|
||||
MOD_DBUPDATER,
|
||||
MOD_MAIN,
|
||||
MOD_METADATA,
|
||||
MOD_REMOTE,
|
||||
MOD_SERVICE,
|
||||
MOD_TRANSCODE,
|
||||
MOD_UI,
|
||||
};
|
||||
|
||||
BOOST_LOG_ATTRIBUTE_KEYWORD(module, "Module", Module)
|
||||
|
||||
class Logger
|
||||
{
|
||||
public:
|
||||
|
||||
static Logger& instance();
|
||||
|
||||
struct Config {
|
||||
bool enableFileLogging;
|
||||
bool enableConsoleLogging;
|
||||
std::string logPath;
|
||||
Severity minSeverity;
|
||||
};
|
||||
|
||||
//[ example_tutorial_file_advanced
|
||||
void init(const Config& config);
|
||||
|
||||
boost::log::sources::severity_logger< Severity >&
|
||||
get(Module module);
|
||||
|
||||
private:
|
||||
|
||||
Logger();
|
||||
|
||||
std::map<Module, boost::log::sources::severity_logger< Severity > > _loggers;
|
||||
};
|
||||
|
||||
// The formatting logic for the severity level
|
||||
template< typename CharT, typename TraitsT >
|
||||
inline std::basic_ostream< CharT, TraitsT >& operator<< (
|
||||
std::basic_ostream< CharT, TraitsT >& strm, Severity lvl)
|
||||
{
|
||||
static const char* const str[] =
|
||||
{
|
||||
"",
|
||||
"",
|
||||
"CRIT",
|
||||
"ERROR",
|
||||
"WARNING",
|
||||
"NOTICE",
|
||||
"INFO",
|
||||
"DEBUG"
|
||||
};
|
||||
if (static_cast< std::size_t >(lvl) < (sizeof(str) / sizeof(*str)))
|
||||
strm << str[lvl];
|
||||
else
|
||||
strm << static_cast< int >(lvl);
|
||||
return strm;
|
||||
}
|
||||
|
||||
template< typename CharT, typename TraitsT >
|
||||
inline std::basic_ostream< CharT, TraitsT >& operator<< (
|
||||
std::basic_ostream< CharT, TraitsT >& strm, Module val)
|
||||
{
|
||||
const char* res = NULL;
|
||||
|
||||
switch(val)
|
||||
{
|
||||
case MOD_AV: res = "AV"; break;
|
||||
case MOD_COVER: res = "COVER"; break;
|
||||
case MOD_DB: res = "DB"; break;
|
||||
case MOD_DBUPDATER: res = "DBUPDATER"; break;
|
||||
case MOD_MAIN: res = "MAIN"; break;
|
||||
case MOD_METADATA: res = "METADATA"; break;
|
||||
case MOD_REMOTE: res = "REMOTE"; break;
|
||||
case MOD_SERVICE: res = "SERVICE"; break;
|
||||
case MOD_TRANSCODE: res = "TRANSCODE"; break;
|
||||
case MOD_UI: res = "UI"; break;
|
||||
}
|
||||
|
||||
if (res)
|
||||
strm << res;
|
||||
else
|
||||
strm << static_cast< int >(val);
|
||||
return strm;
|
||||
}
|
||||
|
||||
|
||||
#endif // LOGGER_HPP__
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include "config/ConfigReader.hpp"
|
||||
#include "transcode/AvConvTranscoder.hpp"
|
||||
#include "av/Common.hpp"
|
||||
|
||||
#include "service/ServiceManager.hpp"
|
||||
#include "service/DatabaseUpdateService.hpp"
|
||||
#include "service/UserInterfaceService.hpp"
|
||||
#include "service/RemoteServerService.hpp"
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
|
||||
int res = EXIT_FAILURE;
|
||||
|
||||
assert(argc > 0);
|
||||
assert(argv[0] != NULL);
|
||||
|
||||
try
|
||||
{
|
||||
// TODO generate a nice command line help with args
|
||||
|
||||
// Open configuration file
|
||||
boost::filesystem::path configFile("/etc/lms.conf"); // TODO
|
||||
if (argc > 1)
|
||||
configFile = boost::filesystem::path(argv[1]);
|
||||
|
||||
if ( !boost::filesystem::exists(configFile))
|
||||
{
|
||||
std::cerr << "Config file '" << configFile << "' does not exist!" << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
else if (!boost::filesystem::is_regular(configFile))
|
||||
{
|
||||
std::cerr << "Config file '" << configFile << "' is not regular!" << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
ConfigReader configReader(configFile);
|
||||
|
||||
// Initializa logging facility
|
||||
{
|
||||
Logger::Config loggerConfig;
|
||||
configReader.getLoggerConfig(loggerConfig);
|
||||
Logger::instance().init(loggerConfig);
|
||||
}
|
||||
|
||||
LMS_LOG(MOD_MAIN, SEV_INFO) << "Reading service configurations...";
|
||||
|
||||
Service::DatabaseUpdateService::Config dbUpdateConfig;
|
||||
configReader.getDatabaseUpdateConfig(dbUpdateConfig);
|
||||
|
||||
Service::UserInterfaceService::Config uiConfig;
|
||||
configReader.getUserInterfaceConfig(uiConfig);
|
||||
|
||||
Service::RemoteServerService::Config remoteConfig;
|
||||
configReader.getRemoteServerConfig(remoteConfig);
|
||||
|
||||
Service::ServiceManager& serviceManager = Service::ServiceManager::instance();
|
||||
|
||||
// lib init
|
||||
Av::AvInit();
|
||||
Transcode::AvConvTranscoder::init();
|
||||
Database::Handler::configureAuth();
|
||||
|
||||
LMS_LOG(MOD_MAIN, SEV_INFO) << "Starting services...";
|
||||
|
||||
if (dbUpdateConfig.enable)
|
||||
serviceManager.startService( std::make_shared<Service::DatabaseUpdateService>( dbUpdateConfig ) );
|
||||
|
||||
if (remoteConfig.enable)
|
||||
serviceManager.startService( std::make_shared<Service::RemoteServerService>( remoteConfig ));
|
||||
|
||||
if (uiConfig.enable)
|
||||
serviceManager.startService( std::make_shared<Service::UserInterfaceService>(boost::filesystem::path(argv[0]), uiConfig));
|
||||
|
||||
LMS_LOG(MOD_MAIN, SEV_NOTICE) << "Now running...";
|
||||
|
||||
serviceManager.run();
|
||||
|
||||
res = EXIT_SUCCESS;
|
||||
|
||||
}
|
||||
catch( libconfig::ParseException& e)
|
||||
{
|
||||
std::cerr << "Caught libconfig::ParseException! error='" << e.getError() << "', file = '" << e.getFile() << "', line = " << e.getLine() << std::endl;
|
||||
}
|
||||
catch( Wt::WServer::Exception& e)
|
||||
{
|
||||
LMS_LOG(MOD_MAIN, SEV_CRIT) << "Caught a WServer::Exception: " << e.what();
|
||||
}
|
||||
catch( std::exception& e)
|
||||
{
|
||||
LMS_LOG(MOD_MAIN, SEV_CRIT) << "Caught std::exception: " << e.what();
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "AvFormat.hpp"
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
|
||||
#include "av/InputFormatContext.hpp"
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include "Utils.hpp"
|
||||
|
||||
namespace MetaData
|
||||
{
|
||||
|
||||
void
|
||||
AvFormat::parse(const boost::filesystem::path& p, Items& items)
|
||||
{
|
||||
|
||||
try {
|
||||
|
||||
Av::InputFormatContext input(p);
|
||||
input.findStreamInfo(); // needed by input.getDurationSecs
|
||||
|
||||
std::map<std::string, std::string> metadata;
|
||||
input.getMetadata().get(metadata);
|
||||
|
||||
// HACK or OGG files
|
||||
// If we did not find tags, searched metadata in streams
|
||||
if (metadata.empty())
|
||||
{
|
||||
// Get input streams
|
||||
std::vector<Av::Stream> streams = input.getStreams();
|
||||
|
||||
BOOST_FOREACH(Av::Stream& stream, streams)
|
||||
{
|
||||
stream.getMetadata().get(metadata);
|
||||
|
||||
if (!metadata.empty())
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Stream info
|
||||
{
|
||||
std::vector<Av::Stream> avStreams = input.getStreams();
|
||||
|
||||
std::vector<AudioStream> audioStreams;
|
||||
std::vector<VideoStream> videoStreams;
|
||||
std::vector<SubtitleStream> subtitleStreams;
|
||||
|
||||
BOOST_FOREACH(Av::Stream& avStream, avStreams)
|
||||
{
|
||||
switch(avStream.getCodecContext().getType())
|
||||
{
|
||||
case AVMEDIA_TYPE_VIDEO:
|
||||
if (!avStream.hasAttachedPic())
|
||||
{
|
||||
VideoStream stream;
|
||||
stream.bitRate = avStream.getCodecContext().getBitRate();
|
||||
videoStreams.push_back(stream);
|
||||
}
|
||||
break;
|
||||
|
||||
case AVMEDIA_TYPE_AUDIO:
|
||||
{
|
||||
AudioStream stream;
|
||||
stream.nbChannels = avStream.getCodecContext().getNbChannels();
|
||||
stream.bitRate = avStream.getCodecContext().getBitRate();
|
||||
audioStreams.push_back(stream);
|
||||
}
|
||||
break;
|
||||
|
||||
case AVMEDIA_TYPE_SUBTITLE:
|
||||
{
|
||||
subtitleStreams.push_back( SubtitleStream() );
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!videoStreams.empty())
|
||||
items.insert( std::make_pair(MetaData::VideoStreams, videoStreams));
|
||||
if (!audioStreams.empty())
|
||||
items.insert( std::make_pair(MetaData::AudioStreams, audioStreams));
|
||||
if (!subtitleStreams.empty())
|
||||
items.insert( std::make_pair(MetaData::SubtitleStreams, SubtitleStreams));
|
||||
|
||||
}
|
||||
|
||||
// Duration
|
||||
items.insert( std::make_pair(MetaData::Duration, boost::posix_time::time_duration( boost::posix_time::seconds( input.getDurationSecs() )) ));
|
||||
|
||||
// Embedded MetaData
|
||||
// Make sure to convert strings into UTF-8
|
||||
std::map<std::string, std::string>::const_iterator it;
|
||||
for (it = metadata.begin(); it != metadata.end(); ++it)
|
||||
{
|
||||
if (boost::iequals(it->first, "artist"))
|
||||
items.insert( std::make_pair(MetaData::Artist, string_trim( string_to_utf8(it->second)) ));
|
||||
else if (boost::iequals(it->first, "album"))
|
||||
items.insert( std::make_pair(MetaData::Album, string_trim( string_to_utf8(it->second)) ));
|
||||
else if (boost::iequals(it->first, "title"))
|
||||
items.insert( std::make_pair(MetaData::Title, string_trim( string_to_utf8(it->second)) ));
|
||||
else if (boost::iequals(it->first, "track")) {
|
||||
std::size_t number;
|
||||
if (readAs<std::size_t>(it->second, number))
|
||||
items.insert( std::make_pair(MetaData::TrackNumber, number ));
|
||||
}
|
||||
else if (boost::iequals(it->first, "disc"))
|
||||
{
|
||||
std::size_t number;
|
||||
if (readAs<std::size_t>(it->second, number))
|
||||
items.insert( std::make_pair(MetaData::DiscNumber, number ));
|
||||
}
|
||||
else if (boost::iequals(it->first, "date")
|
||||
|| boost::iequals(it->first, "year")
|
||||
|| boost::iequals(it->first, "WM/Year")
|
||||
|| boost::iequals(it->first, "TDOR") // Original date fallback
|
||||
|| boost::iequals(it->first, "TORY") // Original date fallback
|
||||
)
|
||||
{
|
||||
boost::posix_time::ptime p;
|
||||
if (readAsPosixTime(it->second, p))
|
||||
items.insert( std::make_pair(MetaData::CreationTime, p));
|
||||
}
|
||||
else if (boost::iequals(it->first, "genre"))
|
||||
{
|
||||
std::list<std::string> genres;
|
||||
if (readList(it->second, ";,", genres))
|
||||
items.insert( std::make_pair(MetaData::Genres, genres));
|
||||
|
||||
}
|
||||
/* else
|
||||
LMS_LOG(MOD_METADATA, SEV_DEBUG) << "key = " << it->first << ", value = " << it->second;
|
||||
*/
|
||||
}
|
||||
|
||||
}
|
||||
catch(std::exception &e)
|
||||
{
|
||||
LMS_LOG(MOD_METADATA, SEV_ERROR) << "Parsing of '" << p << "' failed!";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace MetaData
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef METADATA_AVFORMAT_HPP
|
||||
#define METADATA_AVFORMAT_HPP
|
||||
|
||||
#include "MetaData.hpp"
|
||||
|
||||
namespace MetaData
|
||||
{
|
||||
|
||||
// Implements AVFORMAT library
|
||||
class AvFormat : public Parser
|
||||
{
|
||||
public:
|
||||
|
||||
void parse(const boost::filesystem::path& p, Items& items);
|
||||
|
||||
private:
|
||||
|
||||
};
|
||||
|
||||
|
||||
} // namespace MetaData
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include <boost/date_time/gregorian/gregorian.hpp>
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include "Utils.hpp"
|
||||
|
||||
#include "Extractor.hpp"
|
||||
|
||||
namespace MetaData
|
||||
{
|
||||
|
||||
|
||||
extern "C" {
|
||||
|
||||
/**
|
||||
* Type of a function that libextractor calls for each
|
||||
* meta data item found.
|
||||
*
|
||||
* @param cls closure (user-defined)
|
||||
* @param plugin_name name of the plugin that produced this value;
|
||||
* special values can be used (i.e. '<zlib>' for zlib being
|
||||
* used in the main libextractor library and yielding
|
||||
* meta data).
|
||||
* @param type libextractor-type describing the meta data
|
||||
* @param format basic format information about data
|
||||
* @param data_mime_type mime-type of data (not of the original file);
|
||||
* can be NULL (if mime-type is not known)
|
||||
* @param data actual meta-data found
|
||||
* @param data_len number of bytes in data
|
||||
* @return 0 to continue extracting, 1 to abort
|
||||
*/
|
||||
|
||||
int processMetaData(void *cls,
|
||||
const char *plugin_name,
|
||||
enum EXTRACTOR_MetaType type,
|
||||
enum EXTRACTOR_MetaFormat format,
|
||||
const char *data_mime_type,
|
||||
const char *data,
|
||||
size_t data_len)
|
||||
{
|
||||
Items& items = *(reinterpret_cast<Items*>(cls));
|
||||
|
||||
switch (type) {
|
||||
case EXTRACTOR_METATYPE_ARTIST:
|
||||
assert( std::string(data_mime_type) == "text/plain" );
|
||||
items.insert( std::make_pair(MetaData::Artist, std::string( data, data_len ? data_len - 1 : 0)) );
|
||||
break;
|
||||
case EXTRACTOR_METATYPE_TITLE:
|
||||
assert( std::string(data_mime_type) == "text/plain" );
|
||||
items.insert( std::make_pair(MetaData::Title, std::string( data, data_len ? data_len - 1 : 0)) );
|
||||
break;
|
||||
case EXTRACTOR_METATYPE_ALBUM:
|
||||
assert( std::string(data_mime_type) == "text/plain" );
|
||||
items.insert( std::make_pair(MetaData::Album, std::string( data, data_len ? data_len - 1 : 0)) );
|
||||
break;
|
||||
case EXTRACTOR_METATYPE_GENRE:
|
||||
assert( std::string(data_mime_type) == "text/plain" );
|
||||
{
|
||||
std::list<std::string> genres;
|
||||
if (readList(std::string( data, data_len ? data_len - 1 : 0), ";-:/,", genres))
|
||||
items.insert( std::make_pair(MetaData::Genre, genres));
|
||||
}
|
||||
break;
|
||||
/* case EXTRACTOR_METATYPE_PICTURE:
|
||||
std::cout << "picture spotted" << std::endl;
|
||||
break;*/
|
||||
|
||||
case EXTRACTOR_METATYPE_COVER_PICTURE:
|
||||
{
|
||||
GenericData parsedData;
|
||||
const unsigned char* dataStart = reinterpret_cast<const unsigned char*>(data);
|
||||
parsedData.mimeType = std::string(data_mime_type);
|
||||
parsedData.data = std::vector<unsigned char>( &dataStart[0], &dataStart[data_len]);
|
||||
|
||||
items.insert( std::make_pair(MetaData::Cover, parsedData));
|
||||
}
|
||||
break;
|
||||
|
||||
/* case EXTRACTOR_METATYPE_EVENT_PICTURE:
|
||||
std::cout << "Event picture spotted" << std::endl;
|
||||
break;*/
|
||||
|
||||
/* case EXTRACTOR_METATYPE_CONTRIBUTOR_PICTURE:
|
||||
std::cout << "Contrib picture spotted" << std::endl;
|
||||
break;*/
|
||||
|
||||
/* case EXTRACTOR_METATYPE_SONG_COUNT:
|
||||
// How many songs in the album
|
||||
break;*/
|
||||
|
||||
/* case EXTRACTOR_METATYPE_AUDIO_CODEC:
|
||||
assert( std::string(data_mime_type) == "text/plain" );
|
||||
std::cout << "Aduio codec: " << std::string( data, data_len ? data_len - 1 : 0) << std::endl;
|
||||
break;*/
|
||||
case EXTRACTOR_METATYPE_PUBLICATION_YEAR:
|
||||
assert( std::string(data_mime_type) == "text/plain" );
|
||||
std::cout << "publication year = " << std::string( data, data_len ? data_len - 1 : 0) << std::endl;
|
||||
break;
|
||||
|
||||
case EXTRACTOR_METATYPE_PUBLICATION_DATE:
|
||||
assert( std::string(data_mime_type) == "text/plain" );
|
||||
std::cout << "publication date = " << std::string( data, data_len ? data_len - 1 : 0) << std::endl;
|
||||
break;
|
||||
|
||||
case EXTRACTOR_METATYPE_ORIGINAL_RELEASE_YEAR:
|
||||
assert( std::string(data_mime_type) == "text/plain" );
|
||||
std::cout << "original release year = " << std::string( data, data_len ? data_len - 1 : 0) << std::endl;
|
||||
break;
|
||||
|
||||
case EXTRACTOR_METATYPE_CREATION_TIME:
|
||||
assert( std::string(data_mime_type) == "text/plain" );
|
||||
{
|
||||
boost::posix_time::ptime p;
|
||||
if (readAs<boost::posix_time::ptime>(std::string( data, data_len ? data_len - 1 : 0), p))
|
||||
items.insert( std::make_pair(MetaData::CreationTime, p));
|
||||
else if (readAsPosixTime(std::string( data, data_len ? data_len - 1 : 0), p))
|
||||
items.insert( std::make_pair(MetaData::CreationTime, p));
|
||||
}
|
||||
break;
|
||||
case EXTRACTOR_METATYPE_DURATION:
|
||||
assert( std::string(data_mime_type) == "text/plain" );
|
||||
{
|
||||
boost::posix_time::time_duration duration;
|
||||
if (readAs<boost::posix_time::time_duration>( std::string( data, data_len ? data_len - 1 : 0), duration))
|
||||
items.insert( std::make_pair(MetaData::Duration, duration) );
|
||||
}
|
||||
break;
|
||||
case EXTRACTOR_METATYPE_TRACK_NUMBER:
|
||||
assert( std::string(data_mime_type) == "text/plain" );
|
||||
{
|
||||
std::size_t number(0);
|
||||
if (readAs<size_t>( std::string( data, data_len ? data_len - 1 : 0), number))
|
||||
items.insert( std::make_pair(MetaData::TrackNumber, number) );
|
||||
}
|
||||
break;
|
||||
case EXTRACTOR_METATYPE_DISC_NUMBER:
|
||||
assert( std::string(data_mime_type) == "text/plain" );
|
||||
{
|
||||
std::size_t number(0);
|
||||
if (readAs<size_t>( std::string( data, data_len ? data_len - 1 : 0), number))
|
||||
items.insert( std::make_pair(MetaData::DiscNumber, number) );
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* if (std::string(data_mime_type) == "text/plain")
|
||||
std::cout << "TYPE = " << type << ", data = '" << std::string(data, data_len ? data_len - 1 : 0) << "'" << std::endl;
|
||||
else
|
||||
std::cout << "TYPE = " << type << ", data_mime_type = '" << data_mime_type << "', data len = " << data_len << std::endl;
|
||||
*/
|
||||
break;
|
||||
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
Extractor::Extractor()
|
||||
: _plugins( nullptr )
|
||||
{
|
||||
// _plugins = EXTRACTOR_plugin_add_config (nullptr, "mp3:ogg:flac:wav:gstreamer", EXTRACTOR_OPTION_DEFAULT_POLICY);
|
||||
}
|
||||
|
||||
Extractor::~Extractor()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
Extractor::parse(const boost::filesystem::path& p, Items& items)
|
||||
{
|
||||
if (boost::filesystem::is_regular(p)) {
|
||||
_plugins = EXTRACTOR_plugin_add_defaults (EXTRACTOR_OPTION_DEFAULT_POLICY);
|
||||
if (_plugins == nullptr) {
|
||||
throw std::runtime_error("EXTRACTOR_plugin_add_config failed!");
|
||||
}
|
||||
EXTRACTOR_extract (_plugins, p.string().c_str(), NULL, 0, &processMetaData, &items);
|
||||
EXTRACTOR_plugin_remove_all (_plugins);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
Extractor::parseCover(const boost::filesystem::path& p, GenericData& data)
|
||||
{
|
||||
bool res = false;
|
||||
Items items;
|
||||
if (boost::filesystem::is_regular(p)) {
|
||||
_plugins = EXTRACTOR_plugin_add_defaults (EXTRACTOR_OPTION_DEFAULT_POLICY);
|
||||
if (_plugins == nullptr) {
|
||||
throw std::runtime_error("EXTRACTOR_plugin_add_config failed!");
|
||||
}
|
||||
EXTRACTOR_extract (_plugins, p.string().c_str(), NULL, 0, &processMetaData, &items);
|
||||
EXTRACTOR_plugin_remove_all (_plugins);
|
||||
|
||||
if (items.find(MetaData::Cover) != items.end()) {
|
||||
data = boost::any_cast<GenericData>(items[MetaData::Cover]);
|
||||
res = true;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
} // namespace MetaData
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef EXTRACTOR_HPP
|
||||
#define EXTRACTOR_HPP
|
||||
|
||||
#include <extractor.h>
|
||||
|
||||
#include "MetaData.hpp"
|
||||
|
||||
|
||||
namespace MetaData
|
||||
{
|
||||
|
||||
// Implements GNU libextractor library
|
||||
class Extractor : public Parser
|
||||
{
|
||||
public:
|
||||
|
||||
Extractor();
|
||||
~Extractor();
|
||||
|
||||
void parse(const boost::filesystem::path& p, Items& items);
|
||||
|
||||
bool parseCover(const boost::filesystem::path& p, GenericData& data);
|
||||
|
||||
private:
|
||||
|
||||
struct EXTRACTOR_PluginList *_plugins;
|
||||
};
|
||||
|
||||
} // namespace MetaData
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef METADATA_HPP
|
||||
#define METADATA_HPP
|
||||
|
||||
#include <map>
|
||||
|
||||
#include <boost/any.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
namespace MetaData
|
||||
{
|
||||
|
||||
enum Type
|
||||
{
|
||||
Artist, // string
|
||||
Title, // string
|
||||
Album, // string
|
||||
Genres, // list<string>
|
||||
Duration, // boost::posix_time::time_duration
|
||||
TrackNumber, // size_t
|
||||
DiscNumber, // size_t
|
||||
CreationTime, // boost::posix_time::ptime
|
||||
Cover, // GenericData
|
||||
AudioStreams, // vector<AudioStream>
|
||||
VideoStreams, // vector<VideoStream>
|
||||
SubtitleStreams, // vector<SubtitleStream>
|
||||
};
|
||||
|
||||
// Used by Cover
|
||||
struct GenericData {
|
||||
std::string mimeType;
|
||||
std::vector<unsigned char> data;
|
||||
};
|
||||
|
||||
// Used by Streams
|
||||
struct AudioStream {
|
||||
std::size_t nbChannels;
|
||||
std::size_t bitRate;
|
||||
};
|
||||
|
||||
struct VideoStream {
|
||||
std::size_t bitRate;
|
||||
};
|
||||
|
||||
struct SubtitleStream {
|
||||
;
|
||||
};
|
||||
|
||||
// Type and associated data
|
||||
// See enum Type's comments
|
||||
typedef std::map<Type, boost::any> Items;
|
||||
|
||||
class Parser
|
||||
{
|
||||
public:
|
||||
|
||||
typedef std::shared_ptr<Parser> pointer;
|
||||
|
||||
virtual void parse(const boost::filesystem::path& p, Items& items) = 0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace MetaData
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include "Utils.hpp"
|
||||
|
||||
namespace MetaData
|
||||
{
|
||||
|
||||
bool readAsPosixTime(const std::string& str, boost::posix_time::ptime& time)
|
||||
{
|
||||
const std::locale formats[] = {
|
||||
std::locale(std::locale::classic(), new boost::posix_time::time_input_facet("%Y-%m-%d")),
|
||||
std::locale(std::locale::classic(), new boost::posix_time::time_input_facet("%Y-%b-%d")),
|
||||
std::locale(std::locale::classic(), new boost::posix_time::time_input_facet("%Y-%B-%d")),
|
||||
std::locale(std::locale::classic(), new boost::posix_time::time_input_facet("%Y/%m/%d")),
|
||||
std::locale(std::locale::classic(), new boost::posix_time::time_input_facet("%d.%m.%Y")),
|
||||
std::locale(std::locale::classic(), new boost::posix_time::time_input_facet("%Y-%m")),
|
||||
std::locale(std::locale::classic(), new boost::posix_time::time_input_facet("%Y/%m")),
|
||||
std::locale(std::locale::classic(), new boost::posix_time::time_input_facet("%Y.%m")),
|
||||
std::locale(std::locale::classic(), new boost::posix_time::time_input_facet("%Y")),
|
||||
};
|
||||
for(size_t i=0; i < sizeof(formats)/sizeof(formats[0]); ++i)
|
||||
{
|
||||
std::istringstream iss(str);
|
||||
iss.imbue(formats[i]);
|
||||
if (iss >> time)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool readList(const std::string& str, const std::string& separators, std::list<std::string>& results)
|
||||
{
|
||||
std::string curStr;
|
||||
|
||||
BOOST_FOREACH(char c, str) {
|
||||
|
||||
if (separators.find(c) != std::string::npos) {
|
||||
if (!curStr.empty()) {
|
||||
results.push_back(string_to_utf8(curStr));
|
||||
curStr.clear();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (curStr.empty() && std::isspace(c))
|
||||
continue;
|
||||
|
||||
curStr.push_back(c);
|
||||
}
|
||||
}
|
||||
|
||||
if (!curStr.empty())
|
||||
results.push_back(string_to_utf8(curStr));
|
||||
|
||||
return !str.empty();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef METADATA_UTILS_HPP
|
||||
#define METADATA_UTILS_HPP
|
||||
|
||||
#include <string>
|
||||
#include <list>
|
||||
|
||||
#include <boost/locale.hpp>
|
||||
|
||||
namespace MetaData
|
||||
{
|
||||
|
||||
bool readAsPosixTime(const std::string& str, boost::posix_time::ptime& time);
|
||||
|
||||
bool readList(const std::string& str, const std::string& separators, std::list<std::string>& results);
|
||||
|
||||
template<typename T>
|
||||
static inline bool readAs(const std::string& str, T& data)
|
||||
{
|
||||
std::istringstream iss ( str );
|
||||
return iss >> data;
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::string
|
||||
static inline string_trim(const std::string& str,
|
||||
const std::string& whitespace = " \t")
|
||||
{
|
||||
const auto strBegin = str.find_first_not_of(whitespace);
|
||||
if (strBegin == std::string::npos)
|
||||
return ""; // no content
|
||||
|
||||
const auto strEnd = str.find_last_not_of(whitespace);
|
||||
const auto strRange = strEnd - strBegin + 1;
|
||||
|
||||
return str.substr(strBegin, strRange);
|
||||
}
|
||||
|
||||
|
||||
std::string
|
||||
static inline string_to_utf8(const std::string& str)
|
||||
{
|
||||
return boost::locale::conv::to_utf<char>(str, "UTF-8");
|
||||
}
|
||||
|
||||
} // namespace MetaData
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef REMOTE_HEADER_HPP
|
||||
#define REMOTE_HEADER_HPP
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include <iomanip>
|
||||
|
||||
namespace Remote
|
||||
{
|
||||
|
||||
|
||||
class Header
|
||||
{
|
||||
public:
|
||||
static const std::size_t size = 8; // HeaderSize
|
||||
static const std::size_t max_data_size = 65536*64 - size;
|
||||
|
||||
Header() : _dataSize(0) {}
|
||||
|
||||
void setDataSize(std::size_t size) { _dataSize = size; }
|
||||
std::size_t getDataSize(void) const {return _dataSize;}
|
||||
|
||||
bool from_istream(std::istream &is)
|
||||
{
|
||||
std::array<unsigned char, size> buffer;
|
||||
|
||||
bool res = is.read(reinterpret_cast<char*>(buffer.data()), buffer.size());
|
||||
|
||||
if (res && is.gcount() == buffer.size())
|
||||
return from_buffer(buffer);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool from_buffer(const std::array<unsigned char, size>& buffer)
|
||||
{
|
||||
if (decode32(&buffer[0]) != _magic) {
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Header: bad magic ('" << std::hex << std::setfill('0') << std::setw(8) << decode32(&buffer[0]) << "' instead of '" << std::hex << std::setfill('0') << std::setw(8) << _magic << "')";
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_dataSize = decode32(&buffer[4]);
|
||||
|
||||
if (_dataSize > max_data_size)
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Header: msg too big (" << _dataSize << ")!";
|
||||
|
||||
return _dataSize <= max_data_size;
|
||||
}
|
||||
}
|
||||
|
||||
/* void to_ostream(std::ostream& os)
|
||||
{
|
||||
|
||||
|
||||
}*/
|
||||
|
||||
void to_buffer(std::array<unsigned char, size>& buffer) const
|
||||
{
|
||||
encode32(_magic, &buffer[0]);
|
||||
encode32(_dataSize, &buffer[4]);
|
||||
}
|
||||
|
||||
private:
|
||||
static uint32_t decode32(const unsigned char* data)
|
||||
{
|
||||
return (static_cast<uint32_t>(data[0]) << 24)
|
||||
+ (static_cast<uint32_t>(data[1]) << 16)
|
||||
+ (static_cast<uint32_t>(data[2]) << 8)
|
||||
+ (static_cast<uint32_t>(data[3]));
|
||||
}
|
||||
|
||||
static void encode32(uint32_t value, unsigned char* data)
|
||||
{
|
||||
data[0] = (value >> 24) & 0xFF;
|
||||
data[1] = (value >> 16) & 0xFF;
|
||||
data[2] = (value >> 8) & 0xFF;
|
||||
data[3] = (value) & 0xFF;
|
||||
}
|
||||
|
||||
static const uint32_t _magic = 0xdeadbeef;
|
||||
|
||||
uint32_t _dataSize;
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // namespace Remote
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import "common.proto";
|
||||
|
||||
package Remote;
|
||||
|
||||
message AuthRequest
|
||||
{
|
||||
message Password
|
||||
{
|
||||
required string user_login = 1;
|
||||
required string user_password = 2;
|
||||
}
|
||||
|
||||
enum Type
|
||||
{
|
||||
TypePassword = 1;
|
||||
}
|
||||
|
||||
required Type type = 1;
|
||||
|
||||
optional Password password = 2;
|
||||
}
|
||||
|
||||
message AuthResponse
|
||||
{
|
||||
|
||||
message PasswordResult
|
||||
{
|
||||
enum Type
|
||||
{
|
||||
TypePasswordInvalid = 1;
|
||||
TypeLoginThrottling = 2; // The attempt was not processed because of throttling
|
||||
TypePasswordValid = 3;
|
||||
}
|
||||
required Type type = 1;
|
||||
|
||||
optional uint32 delay = 2; // in seconds, in case of PasswordInvalid/LoginThrottling
|
||||
}
|
||||
|
||||
enum Type
|
||||
{
|
||||
TypePasswordResult = 1;
|
||||
}
|
||||
|
||||
required Type type = 1;
|
||||
|
||||
optional PasswordResult password_result = 2;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
import "common.proto";
|
||||
|
||||
package Remote;
|
||||
|
||||
message AudioCollectionRequest
|
||||
{
|
||||
|
||||
message BatchParameter
|
||||
{
|
||||
required uint32 offset = 1; // First element requested
|
||||
required uint32 size = 2; // Number of elements requested. Server may not honor this size if too big
|
||||
}
|
||||
|
||||
message GetRevision
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
message GetGenreList
|
||||
{
|
||||
required BatchParameter batch_parameter = 1;
|
||||
}
|
||||
|
||||
message GetArtistList
|
||||
{
|
||||
required BatchParameter batch_parameter = 1;
|
||||
// Search filters
|
||||
repeated uint64 genre_id = 2; // Artist that has at least a track of the genre
|
||||
}
|
||||
|
||||
message GetReleaseList
|
||||
{
|
||||
required BatchParameter batch_parameter = 1;
|
||||
// Search filters
|
||||
repeated uint64 artist_id = 2; // Release that contains at least a track of one of these artists
|
||||
repeated uint64 genre_id = 3; // Release that has at least a track of the genre
|
||||
}
|
||||
|
||||
message GetTrackList
|
||||
{
|
||||
required BatchParameter batch_parameter = 1;
|
||||
// Search filters
|
||||
repeated uint64 artist_id = 2; // Track that belongs to these artists
|
||||
repeated uint64 release_id = 3; // Track that is part of these releases
|
||||
repeated uint64 genre_id = 4; // Track that has at least a track of the genre
|
||||
}
|
||||
|
||||
message GetCoverArt
|
||||
{
|
||||
enum Type
|
||||
{
|
||||
TypeGetCoverArtRelease = 1;
|
||||
TypeGetCoverArtTrack = 2;
|
||||
}
|
||||
|
||||
required Type type = 1;
|
||||
|
||||
optional uint64 release_id = 2; // Release that owns the cover art
|
||||
optional uint64 track_id = 3; // Track that owns the cover art
|
||||
|
||||
required uint32 size = 4; // Scale image to size*size pixels. Set 0 to get the biggest image
|
||||
}
|
||||
|
||||
enum Type
|
||||
{
|
||||
TypeGetRevision = 1;
|
||||
TypeGetGenreList = 2;
|
||||
TypeGetArtistList = 3;
|
||||
TypeGetReleaseList = 4;
|
||||
TypeGetTrackList = 5;
|
||||
TypeGetCoverArt = 6;
|
||||
}
|
||||
|
||||
required Type type = 1;
|
||||
|
||||
optional GetRevision get_revision = 2;
|
||||
optional GetGenreList get_genres = 3;
|
||||
optional GetArtistList get_artists = 4;
|
||||
optional GetReleaseList get_releases = 5;
|
||||
optional GetTrackList get_tracks = 6;
|
||||
optional GetCoverArt get_cover_art = 7;
|
||||
}
|
||||
|
||||
|
||||
message AudioCollectionResponse
|
||||
{
|
||||
|
||||
message Revision
|
||||
{
|
||||
required string rev = 1; // Unique identifier of the database revision
|
||||
}
|
||||
|
||||
message GenreList
|
||||
{
|
||||
repeated Genre genres = 2;
|
||||
}
|
||||
|
||||
message ArtistList
|
||||
{
|
||||
repeated Artist artists = 2;
|
||||
}
|
||||
|
||||
message ReleaseList
|
||||
{
|
||||
repeated Release releases = 2;
|
||||
}
|
||||
|
||||
message TrackList
|
||||
{
|
||||
repeated Track tracks = 2;
|
||||
}
|
||||
|
||||
message CoverArt
|
||||
{
|
||||
optional string mime_type = 1;
|
||||
required bytes data = 2;
|
||||
}
|
||||
|
||||
message Genre
|
||||
{
|
||||
required uint64 id = 1; // Genre Id
|
||||
|
||||
required string name = 2;
|
||||
}
|
||||
|
||||
message Artist
|
||||
{
|
||||
required uint64 id = 1; // Artist Id
|
||||
|
||||
required string name = 2;
|
||||
}
|
||||
|
||||
message Release
|
||||
{
|
||||
required uint64 id = 1; // Release id
|
||||
|
||||
required string name = 2;
|
||||
}
|
||||
|
||||
message Track
|
||||
{
|
||||
required uint64 id = 1; // Track id
|
||||
|
||||
required uint64 artist_id = 2;
|
||||
required uint64 release_id = 3;
|
||||
repeated uint64 genre_id = 4;
|
||||
|
||||
optional uint32 disc_number = 5;
|
||||
optional uint32 track_number = 6;
|
||||
required string name = 7;
|
||||
required uint32 duration_secs = 8;
|
||||
optional string release_date = 9;
|
||||
optional string original_release_date = 10;
|
||||
}
|
||||
|
||||
enum Type {
|
||||
TypeRevision = 1;
|
||||
TypeGenreList = 2;
|
||||
TypeArtistList = 3;
|
||||
TypeReleaseList = 4;
|
||||
TypeTrackList = 5;
|
||||
TypeCoverArt = 6;
|
||||
}
|
||||
|
||||
|
||||
required Type type = 1;
|
||||
|
||||
optional Revision revision = 2;
|
||||
optional GenreList genre_list = 3;
|
||||
optional ArtistList artist_list = 4;
|
||||
optional ReleaseList release_list = 5;
|
||||
optional TrackList track_list = 6;
|
||||
repeated CoverArt cover_art = 7;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
package Remote;
|
||||
|
||||
message Error
|
||||
{
|
||||
required bool error = 1;
|
||||
optional string message = 2;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
import "common.proto";
|
||||
|
||||
package Remote;
|
||||
|
||||
|
||||
|
||||
message MediaRequest
|
||||
{
|
||||
|
||||
message Prepare
|
||||
{
|
||||
|
||||
enum AudioCodecType
|
||||
{
|
||||
AudioCodecTypeOGA = 1;
|
||||
}
|
||||
enum AudioBitrate {
|
||||
AudioBitrate_32_kbps = 1;
|
||||
AudioBitrate_64_kbps = 2;
|
||||
AudioBitrate_96_kbps = 3;
|
||||
AudioBitrate_128_kbps = 4;
|
||||
AudioBitrate_192_kbps = 5;
|
||||
AudioBitrate_256_kbps = 6;
|
||||
}
|
||||
|
||||
enum VideoCodecType
|
||||
{
|
||||
VideoCodecTypeOGV = 1;
|
||||
}
|
||||
enum VideoBitrate {
|
||||
VideoBitrate_512_kbps = 1;
|
||||
}
|
||||
|
||||
message Audio
|
||||
{
|
||||
required int64 track_id = 1; // Id if the media
|
||||
required AudioCodecType codec_type = 2;
|
||||
required AudioBitrate bitrate = 3;
|
||||
optional uint32 stream_idx = 4;
|
||||
optional uint32 offset_secs = 5;
|
||||
}
|
||||
|
||||
message Video
|
||||
{
|
||||
required int64 video_id = 1; // Id if the media
|
||||
required VideoCodecType codec_type = 2;
|
||||
required AudioBitrate audio_bitrate = 3;
|
||||
required VideoBitrate video_bitrate = 4;
|
||||
optional uint32 offset_secs = 5;
|
||||
optional uint32 audio_stream_idx = 6;
|
||||
optional uint32 video_stream_idx = 7;
|
||||
optional uint32 subtitle_stream_idx = 8;
|
||||
}
|
||||
|
||||
enum Type {
|
||||
AudioRequest = 1;
|
||||
VideoRequest = 2;
|
||||
}
|
||||
|
||||
required Type type = 1;
|
||||
|
||||
optional Audio audio = 2;
|
||||
optional Video video = 3;
|
||||
}
|
||||
|
||||
message GetPart
|
||||
{
|
||||
required uint32 handle = 1; // handle that uniquely identify the media. Get using the Prepare request
|
||||
required uint32 requested_data_size = 2; // Amount of data requested. May receive more or less. May receive 0 byte if complete
|
||||
}
|
||||
|
||||
message Terminate
|
||||
{
|
||||
required uint32 handle = 1;
|
||||
}
|
||||
|
||||
enum Type
|
||||
{
|
||||
TypeMediaPrepare = 1;
|
||||
TypeMediaGetPart = 2;
|
||||
TypeMediaTerminate = 3;
|
||||
}
|
||||
|
||||
required Type type = 1;
|
||||
|
||||
optional Prepare prepare = 2;
|
||||
optional GetPart get_part = 3;
|
||||
optional Terminate terminate = 4;
|
||||
|
||||
} // MediaRequest
|
||||
|
||||
message MediaResponse
|
||||
{
|
||||
|
||||
message PrepareResult
|
||||
{
|
||||
optional uint32 handle = 1; // handle iif prepare request was ok
|
||||
}
|
||||
|
||||
message PartResult
|
||||
{
|
||||
optional bytes data = 2; // 0 bytes when the media is over, nothing when the request is ill formed
|
||||
}
|
||||
|
||||
message TerminateResult
|
||||
{
|
||||
}
|
||||
|
||||
enum Type
|
||||
{
|
||||
TypePrepareResult = 1;
|
||||
TypePartResult = 2;
|
||||
TypeTerminateResult = 3;
|
||||
}
|
||||
|
||||
required Type type = 1;
|
||||
|
||||
optional PrepareResult prepare_result = 2;
|
||||
optional PartResult part_result = 3;
|
||||
optional TerminateResult terminate_result = 4;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import "common.proto";
|
||||
import "auth.proto";
|
||||
import "collection.proto";
|
||||
import "media.proto";
|
||||
|
||||
package Remote;
|
||||
|
||||
message ClientMessage
|
||||
{
|
||||
enum Type
|
||||
{
|
||||
AuthRequest = 1;
|
||||
AudioCollectionRequest = 2;
|
||||
MediaRequest = 3;
|
||||
}
|
||||
|
||||
required Type type = 1;
|
||||
|
||||
optional AuthRequest auth_request = 2;
|
||||
optional AudioCollectionRequest audio_collection_request = 3;
|
||||
optional MediaRequest media_request = 4;
|
||||
}
|
||||
|
||||
|
||||
message ServerMessage
|
||||
{
|
||||
enum Type
|
||||
{
|
||||
AuthResponse = 1;
|
||||
AudioCollectionResponse = 2;
|
||||
MediaResponse = 3;
|
||||
}
|
||||
|
||||
required Type type = 1;
|
||||
|
||||
optional AuthResponse auth_response = 2;
|
||||
optional AudioCollectionResponse audio_collection_response = 3;
|
||||
optional MediaResponse media_response = 4;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,402 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <algorithm> // std::min
|
||||
|
||||
#include <boost/locale.hpp>
|
||||
#include <boost/uuid/sha1.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include "AudioCollectionRequestHandler.hpp"
|
||||
|
||||
#include "database/AudioTypes.hpp"
|
||||
#include "database/MediaDirectory.hpp"
|
||||
#include "cover/CoverArtGrabber.hpp"
|
||||
|
||||
namespace Remote {
|
||||
namespace Server {
|
||||
|
||||
AudioCollectionRequestHandler::AudioCollectionRequestHandler(Database::Handler& db)
|
||||
: _db(db)
|
||||
{}
|
||||
|
||||
|
||||
bool
|
||||
AudioCollectionRequestHandler::process(const AudioCollectionRequest& request, AudioCollectionResponse& response)
|
||||
{
|
||||
|
||||
bool res = false;
|
||||
|
||||
switch (request.type())
|
||||
{
|
||||
case AudioCollectionRequest::TypeGetRevision:
|
||||
if (request.has_get_revision())
|
||||
{
|
||||
res = processGetRevision(request.get_revision(), *response.mutable_revision());
|
||||
if (res)
|
||||
response.set_type(AudioCollectionResponse::TypeRevision);
|
||||
}
|
||||
else
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad AudioCollectionRequest::TypeGetRevision";
|
||||
break;
|
||||
|
||||
case AudioCollectionRequest::TypeGetGenreList:
|
||||
if (request.has_get_genres())
|
||||
{
|
||||
res = processGetGenres(request.get_genres(), *response.mutable_genre_list());
|
||||
if (res)
|
||||
response.set_type(AudioCollectionResponse::TypeArtistList);
|
||||
}
|
||||
else
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad AudioCollectionRequest::TypeGetGenreList";
|
||||
break;
|
||||
|
||||
case AudioCollectionRequest::TypeGetArtistList:
|
||||
if (request.has_get_artists())
|
||||
{
|
||||
res = processGetArtists(request.get_artists(), *response.mutable_artist_list());
|
||||
if (res)
|
||||
response.set_type(AudioCollectionResponse::TypeArtistList);
|
||||
|
||||
}
|
||||
else
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad AudioCollectionRequest::TypeGetArtistList message!";
|
||||
break;
|
||||
|
||||
case AudioCollectionRequest::TypeGetReleaseList:
|
||||
if (request.has_get_releases())
|
||||
{
|
||||
res = processGetReleases(request.get_releases(), *response.mutable_release_list());
|
||||
if (res)
|
||||
response.set_type(AudioCollectionResponse::TypeReleaseList);
|
||||
|
||||
}
|
||||
else
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad AudioCollectionRequest::TypeGetReleaseList message!";
|
||||
break;
|
||||
|
||||
case AudioCollectionRequest::TypeGetTrackList:
|
||||
if (request.has_get_tracks())
|
||||
{
|
||||
res = processGetTracks(request.get_tracks(), *response.mutable_track_list());
|
||||
if (res)
|
||||
response.set_type(AudioCollectionResponse::TypeTrackList);
|
||||
|
||||
}
|
||||
else
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad AudioCollectionRequest::TypeGetTrackList message!";
|
||||
break;
|
||||
|
||||
case AudioCollectionRequest::TypeGetCoverArt:
|
||||
if (request.has_get_cover_art())
|
||||
res = processGetCoverArt(request.get_cover_art(), response);
|
||||
else
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad AudioCollectionRequest::TypeGetCoverArt message!";
|
||||
break;
|
||||
|
||||
default:
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Unhandled AudioCollectionRequest_Type = " << request.type();
|
||||
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
AudioCollectionRequestHandler::processGetGenres(const AudioCollectionRequest::GetGenreList& request, AudioCollectionResponse::GenreList& response)
|
||||
{
|
||||
// sanity checks
|
||||
if (!request.has_batch_parameter())
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "No batch parameters found!";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t size = request.batch_parameter().size();
|
||||
if (!size)
|
||||
size = _maxListArtists;
|
||||
size = std::min(size, _maxListArtists);
|
||||
|
||||
Wt::Dbo::Transaction transaction( _db.getSession() );
|
||||
|
||||
Wt::Dbo::collection<Database::Genre::pointer> genres = Database::Genre::getAll( _db.getSession(), request.batch_parameter().offset(), static_cast<int>(size));
|
||||
|
||||
typedef Wt::Dbo::collection< Database::Genre::pointer > Genres;
|
||||
|
||||
for (Genres::const_iterator it = genres.begin(); it != genres.end(); ++it)
|
||||
{
|
||||
AudioCollectionResponse_Genre* genre = response.add_genres();
|
||||
|
||||
genre->set_name( std::string( boost::locale::conv::to_utf<char>((*it)->getName(), "UTF-8") ) );
|
||||
genre->set_id(it->id());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
AudioCollectionRequestHandler::processGetArtists(const AudioCollectionRequest::GetArtistList& request, AudioCollectionResponse::ArtistList& response)
|
||||
{
|
||||
// sanity checks
|
||||
if (!request.has_batch_parameter())
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "No batch parameters found!";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t size = request.batch_parameter().size();
|
||||
if (!size)
|
||||
size = _maxListArtists;
|
||||
size = std::min(size, _maxListArtists);
|
||||
|
||||
// Now fetch requested data...
|
||||
|
||||
Wt::Dbo::Transaction transaction( _db.getSession() );
|
||||
|
||||
Wt::Dbo::collection<Database::Artist::pointer> artists = Database::Artist::getAll( _db.getSession(), request.batch_parameter().offset(), static_cast<int>(size) );
|
||||
|
||||
typedef Wt::Dbo::collection< Database::Artist::pointer > Artists;
|
||||
|
||||
for (Artists::const_iterator it = artists.begin(); it != artists.end(); ++it)
|
||||
{
|
||||
AudioCollectionResponse_Artist* artist = response.add_artists();
|
||||
|
||||
artist->set_name( std::string( boost::locale::conv::to_utf<char>((*it)->getName(), "UTF-8") ) );
|
||||
artist->set_id(it->id());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
AudioCollectionRequestHandler::processGetReleases(const AudioCollectionRequest::GetReleaseList& request, AudioCollectionResponse::ReleaseList& response)
|
||||
{
|
||||
// sanity checks
|
||||
if (!request.has_batch_parameter())
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "No batch parameters found!";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t size = request.batch_parameter().size();
|
||||
if (!size)
|
||||
size = _maxListReleases;
|
||||
size = std::min(size, _maxListReleases);
|
||||
|
||||
std::vector<Database::Artist::id_type> artistIds;
|
||||
for (int id = 0; id < request.artist_id_size(); ++id)
|
||||
artistIds.push_back( request.artist_id(id) );
|
||||
|
||||
Wt::Dbo::Transaction transaction( _db.getSession() );
|
||||
|
||||
Wt::Dbo::collection<Database::Release::pointer> releases = Database::Release::getAll( _db.getSession(), artistIds, request.batch_parameter().offset(), static_cast<int>(size));
|
||||
|
||||
typedef Wt::Dbo::collection< Database::Release::pointer > Releases;
|
||||
|
||||
for (Releases::const_iterator it = releases.begin(); it != releases.end(); ++it)
|
||||
{
|
||||
AudioCollectionResponse_Release* release = response.add_releases();
|
||||
|
||||
release->set_name( std::string( boost::locale::conv::to_utf<char>((*it)->getName(), "UTF-8") ) );
|
||||
release->set_id(it->id());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
AudioCollectionRequestHandler::processGetTracks(const AudioCollectionRequest::GetTrackList& request, AudioCollectionResponse::TrackList& response)
|
||||
{
|
||||
// sanity checks
|
||||
if (!request.has_batch_parameter())
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "No batch parameters found!";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t size = request.batch_parameter().size();
|
||||
if (!size)
|
||||
size = _maxListTracks;
|
||||
size = std::min(size, _maxListTracks);
|
||||
|
||||
// Get filters
|
||||
std::vector<Database::Artist::id_type> artistIds;
|
||||
for (int id = 0; id < request.artist_id_size(); ++id)
|
||||
artistIds.push_back( request.artist_id(id) );
|
||||
|
||||
std::vector<Database::Release::id_type> releaseIds;
|
||||
for (int id = 0; id < request.release_id_size(); ++id)
|
||||
releaseIds.push_back( request.release_id(id) );
|
||||
|
||||
std::vector<Database::Release::id_type> genreIds;
|
||||
for (int id = 0; id < request.genre_id_size(); ++id)
|
||||
genreIds.push_back( request.genre_id(id) );
|
||||
|
||||
Wt::Dbo::Transaction transaction( _db.getSession() );
|
||||
|
||||
Wt::Dbo::collection<Database::Track::pointer> tracks
|
||||
= Database::Track::getAll( _db.getSession(),
|
||||
artistIds,
|
||||
releaseIds,
|
||||
genreIds,
|
||||
request.batch_parameter().offset(),
|
||||
static_cast<int>(size));
|
||||
|
||||
typedef Wt::Dbo::collection< Database::Track::pointer > Tracks;
|
||||
|
||||
for (Tracks::const_iterator it = tracks.begin(); it != tracks.end(); ++it)
|
||||
{
|
||||
AudioCollectionResponse_Track* track = response.add_tracks();
|
||||
|
||||
track->set_id(it->id());
|
||||
track->set_disc_number( (*it)->getDiscNumber() );
|
||||
track->set_track_number( (*it)->getTrackNumber() );
|
||||
track->set_artist_id( (*it)->getArtist().id() );
|
||||
track->set_release_id( (*it)->getRelease().id() );
|
||||
|
||||
track->set_name( std::string( boost::locale::conv::to_utf<char>((*it)->getName(), "UTF-8") ) );
|
||||
track->set_duration_secs( (*it)->getDuration().total_seconds() );
|
||||
// if (!(*it)->getCreationTime().is_special())
|
||||
// track->set_release_date( boost::posix_time::to_simple_string((*it)->getCreationTime()) );
|
||||
|
||||
BOOST_FOREACH(Database::Genre::pointer genre, (*it)->getGenres())
|
||||
track->add_genre_id( genre.id() );
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
AudioCollectionRequestHandler::processGetCoverArt(const AudioCollectionRequest::GetCoverArt& request, AudioCollectionResponse& response)
|
||||
{
|
||||
bool res = false;
|
||||
|
||||
response.set_type(AudioCollectionResponse::TypeCoverArt);
|
||||
|
||||
switch(request.type())
|
||||
{
|
||||
case AudioCollectionRequest::GetCoverArt::TypeGetCoverArtRelease:
|
||||
|
||||
if (request.has_release_id())
|
||||
{
|
||||
Wt::Dbo::Transaction transaction( _db.getSession() );
|
||||
|
||||
// Get the request release
|
||||
Database::Release::pointer release = Database::Release::getById( _db.getSession(), request.release_id());
|
||||
|
||||
std::vector<CoverArt::CoverArt> coverArts = CoverArt::Grabber::getFromRelease(release);
|
||||
|
||||
BOOST_FOREACH(CoverArt::CoverArt& coverArt, coverArts)
|
||||
{
|
||||
AudioCollectionResponse_CoverArt* cover_art = response.add_cover_art();
|
||||
|
||||
if (request.has_size())
|
||||
{
|
||||
std::size_t size = request.size();
|
||||
if (size > _maxCoverArtSize || size == 0)
|
||||
size = _maxCoverArtSize;
|
||||
if (size < _minCoverArtSize)
|
||||
size = _minCoverArtSize;
|
||||
|
||||
coverArt.scale(size);
|
||||
}
|
||||
|
||||
cover_art->set_mime_type(coverArt.getMimeType());
|
||||
cover_art->set_data( std::string( coverArt.getData().begin(), coverArt.getData().end()) );
|
||||
}
|
||||
}
|
||||
res = true;
|
||||
break;
|
||||
|
||||
case AudioCollectionRequest::GetCoverArt::TypeGetCoverArtTrack:
|
||||
if (request.has_track_id())
|
||||
{
|
||||
Wt::Dbo::Transaction transaction( _db.getSession() );
|
||||
|
||||
// Get the request release
|
||||
Database::Track::pointer track = Database::Track::getById( _db.getSession(), request.track_id());
|
||||
|
||||
std::vector<CoverArt::CoverArt> coverArts = CoverArt::Grabber::getFromTrack(track);
|
||||
|
||||
BOOST_FOREACH(CoverArt::CoverArt& coverArt, coverArts)
|
||||
{
|
||||
AudioCollectionResponse_CoverArt* cover_art = response.add_cover_art();
|
||||
|
||||
if (request.has_size())
|
||||
{
|
||||
std::size_t size = request.size();
|
||||
if (size > _maxCoverArtSize || size == 0)
|
||||
size = _maxCoverArtSize;
|
||||
if (size < _minCoverArtSize)
|
||||
size = _minCoverArtSize;
|
||||
|
||||
coverArt.scale(size);
|
||||
}
|
||||
|
||||
cover_art->set_mime_type(coverArt.getMimeType());
|
||||
cover_art->set_data( std::string( coverArt.getData().begin(), coverArt.getData().end()) );
|
||||
}
|
||||
}
|
||||
res = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool
|
||||
AudioCollectionRequestHandler::processGetRevision(const AudioCollectionRequest::GetRevision& request, AudioCollectionResponse::Revision& response)
|
||||
{
|
||||
|
||||
bool res = false;
|
||||
|
||||
Wt::Dbo::Transaction transaction( _db.getSession() );
|
||||
|
||||
Database::MediaDirectorySettings::pointer settings = Database::MediaDirectorySettings::get( _db.getSession() );
|
||||
|
||||
std::string hashStr = boost::posix_time::to_iso_string(settings->getLastUpdated());
|
||||
|
||||
boost::uuids::detail::sha1 s;
|
||||
BOOST_FOREACH(const char c, hashStr)
|
||||
s.process_byte(c);
|
||||
|
||||
unsigned int digest[5];
|
||||
s.get_digest(digest);
|
||||
|
||||
std::ostringstream oss;
|
||||
for (std::size_t i = 0; i < 5; ++i)
|
||||
oss << std::hex << std::setfill('0') << std::setw(4) << digest[i];
|
||||
|
||||
response.set_rev(oss.str());
|
||||
|
||||
res = true;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace Remote
|
||||
} // namespace Server
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef REMOTE_AUDIO_COLLECTION_REQUEST_HANDLER
|
||||
#define REMOTE_AUDIO_COLLECTION_REQUEST_HANDLER
|
||||
|
||||
#include "database/DatabaseHandler.hpp"
|
||||
|
||||
#include "messages.pb.h"
|
||||
|
||||
namespace Remote {
|
||||
namespace Server {
|
||||
|
||||
class AudioCollectionRequestHandler
|
||||
{
|
||||
public:
|
||||
AudioCollectionRequestHandler(Database::Handler& db);
|
||||
|
||||
bool process(const AudioCollectionRequest& request, AudioCollectionResponse& response);
|
||||
|
||||
private:
|
||||
|
||||
bool processGetRevision(const AudioCollectionRequest::GetRevision& request, AudioCollectionResponse::Revision& response);
|
||||
bool processGetArtists(const AudioCollectionRequest::GetArtistList& request, AudioCollectionResponse::ArtistList& response);
|
||||
bool processGetGenres(const AudioCollectionRequest::GetGenreList& request, AudioCollectionResponse::GenreList& response);
|
||||
bool processGetReleases(const AudioCollectionRequest::GetReleaseList& request, AudioCollectionResponse::ReleaseList& response);
|
||||
bool processGetTracks(const AudioCollectionRequest::GetTrackList& request, AudioCollectionResponse::TrackList& response);
|
||||
bool processGetCoverArt(const AudioCollectionRequest::GetCoverArt& request, AudioCollectionResponse& response);
|
||||
|
||||
Database::Handler& _db;
|
||||
|
||||
static const std::size_t _maxListArtists = 256;
|
||||
static const std::size_t _maxListGenres = 256;
|
||||
static const std::size_t _maxListReleases = 128;
|
||||
static const std::size_t _maxListTracks = 128;
|
||||
|
||||
static const std::size_t _minCoverArtSize = 64; // in pixels, square
|
||||
static const std::size_t _maxCoverArtSize = 512; // in pixels, square
|
||||
};
|
||||
|
||||
} // namespace Remote
|
||||
} // namespace Server
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <Wt/Auth/Identity>
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include "AuthRequestHandler.hpp"
|
||||
|
||||
namespace Remote {
|
||||
namespace Server {
|
||||
|
||||
AuthRequestHandler::AuthRequestHandler(Database::Handler& db)
|
||||
: _db(db)
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
AuthRequestHandler::process(const AuthRequest& request, AuthResponse& response)
|
||||
{
|
||||
bool res = false;
|
||||
|
||||
switch (request.type())
|
||||
{
|
||||
case AuthRequest::TypePassword:
|
||||
if (request.has_password())
|
||||
{
|
||||
res = processPassword(request.password(), *response.mutable_password_result());
|
||||
if (res)
|
||||
response.set_type(AuthResponse::TypePasswordResult);
|
||||
}
|
||||
else
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad AuthRequest::TypePassword";
|
||||
break;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
AuthRequestHandler::processPassword(const AuthRequest::Password& request, AuthResponse::PasswordResult& response)
|
||||
{
|
||||
bool res = false;
|
||||
|
||||
// Get the user
|
||||
const Wt::Auth::User& user = _db.getUserDatabase().findWithIdentity(Wt::Auth::Identity::LoginName, request.user_login());
|
||||
if (user.isValid())
|
||||
{
|
||||
// Now attempt to log the user in
|
||||
|
||||
Wt::Auth::PasswordResult result = _db.getPasswordService().verifyPassword(user, request.user_password());
|
||||
switch( result )
|
||||
{
|
||||
case Wt::Auth::PasswordInvalid:
|
||||
response.set_type(AuthResponse::PasswordResult::TypePasswordInvalid);
|
||||
res = true;
|
||||
break;
|
||||
case Wt::Auth::LoginThrottling:
|
||||
response.set_type(AuthResponse::PasswordResult::TypeLoginThrottling);
|
||||
response.set_delay(_db.getPasswordService().delayForNextAttempt(user));
|
||||
res = true;
|
||||
break;
|
||||
case Wt::Auth::PasswordValid:
|
||||
response.set_type(AuthResponse::PasswordResult::TypePasswordValid);
|
||||
// Log the user in
|
||||
_db.getLogin().login( user );
|
||||
res = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Invalid user '" << request.user_login();
|
||||
response.set_type(AuthResponse::PasswordResult::TypePasswordInvalid);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace Remote
|
||||
} // namespace Server
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef REMOTE_AUTH_REQUEST_HANDLER
|
||||
#define REMOTE_AUTH_REQUEST_HANDLER
|
||||
|
||||
#include "database/DatabaseHandler.hpp"
|
||||
|
||||
#include "messages.pb.h"
|
||||
|
||||
namespace Remote {
|
||||
namespace Server {
|
||||
|
||||
class AuthRequestHandler
|
||||
{
|
||||
public:
|
||||
AuthRequestHandler(Database::Handler& db);
|
||||
|
||||
bool process(const AuthRequest& request, AuthResponse& response);
|
||||
|
||||
private:
|
||||
|
||||
bool processPassword(const AuthRequest::Password& request, AuthResponse::PasswordResult& response);
|
||||
|
||||
Database::Handler& _db;
|
||||
};
|
||||
|
||||
} // namespace Remote
|
||||
} // namespace Server
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/bind.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include "messages.pb.h"
|
||||
|
||||
#include "RequestHandler.hpp"
|
||||
|
||||
#include "ConnectionManager.hpp"
|
||||
#include "Connection.hpp"
|
||||
|
||||
namespace Remote {
|
||||
namespace Server {
|
||||
|
||||
Connection::Connection(boost::asio::io_service& ioService,
|
||||
boost::asio::ssl::context& context,
|
||||
ConnectionManager& manager,
|
||||
const boost::filesystem::path& dbPath)
|
||||
: _closing(false),
|
||||
_socket(ioService, context),
|
||||
_connectionManager(manager),
|
||||
_requestHandler(dbPath)
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "Server::Connection::Connection, Creating connection";
|
||||
}
|
||||
|
||||
void
|
||||
Connection::start()
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "Starting connection...";
|
||||
_socket.async_handshake(boost::asio::ssl::stream_base::server,
|
||||
boost::bind(&Connection::handleHandshake, this,
|
||||
boost::asio::placeholders::error));
|
||||
}
|
||||
|
||||
void
|
||||
Connection::handleHandshake(const boost::system::error_code& error)
|
||||
{
|
||||
if (!error)
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "Handshake successfully performed... Now reading messages";
|
||||
readMsg();
|
||||
}
|
||||
else if (error != boost::asio::error::operation_aborted)
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Connection::handleHandshake: " << error.message();
|
||||
_connectionManager.stop(shared_from_this());
|
||||
}
|
||||
else
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Handshake error: " << error.message();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Connection::readMsg()
|
||||
{
|
||||
// Read a header first
|
||||
boost::asio::streambuf::mutable_buffers_type bufs = _inputStreamBuf.prepare(Remote::Header::size);
|
||||
|
||||
boost::asio::async_read(_socket,
|
||||
bufs,
|
||||
boost::asio::transfer_exactly(Remote::Header::size),
|
||||
boost::bind(&Connection::handleReadHeader, shared_from_this(),
|
||||
boost::asio::placeholders::error,
|
||||
boost::asio::placeholders::bytes_transferred));
|
||||
}
|
||||
|
||||
void
|
||||
Connection::stop()
|
||||
{
|
||||
if (!_closing)
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
|
||||
_closing = true;
|
||||
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "Server::Connection::stop, Stopping connection " << this;
|
||||
_socket.shutdown(ec);
|
||||
|
||||
if (ec)
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Error while shutting down connection " << this << ": " << ec.message();
|
||||
|
||||
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "Server::Connection::stop, connection stopped " << this;
|
||||
}
|
||||
else
|
||||
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "Stop: close already in progress...";
|
||||
}
|
||||
|
||||
void
|
||||
Connection::handleReadHeader(const boost::system::error_code& error, std::size_t bytes_transferred)
|
||||
{
|
||||
if (!error)
|
||||
{
|
||||
if (bytes_transferred != Remote::Header::size)
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "bytes_transferred (" << bytes_transferred << ") != Remote::Header::size!";
|
||||
_connectionManager.stop(shared_from_this());
|
||||
return;
|
||||
}
|
||||
|
||||
_inputStreamBuf.commit(bytes_transferred);
|
||||
|
||||
std::istream is(&_inputStreamBuf);
|
||||
|
||||
Remote::Header header;
|
||||
if (!header.from_istream(is))
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Cannot read header from buffer!";
|
||||
_connectionManager.stop(shared_from_this());
|
||||
return;
|
||||
}
|
||||
|
||||
// Now read the real message payload
|
||||
boost::asio::streambuf::mutable_buffers_type bufs = _inputStreamBuf.prepare(header.getDataSize());
|
||||
|
||||
boost::asio::async_read(_socket,
|
||||
bufs,
|
||||
boost::asio::transfer_exactly(header.getDataSize()),
|
||||
boost::bind(&Connection::handleReadMsg, shared_from_this(),
|
||||
boost::asio::placeholders::error,
|
||||
boost::asio::placeholders::bytes_transferred));
|
||||
|
||||
}
|
||||
else if (error != boost::asio::error::operation_aborted)
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Connection::handleReadHeader: " << error.message();
|
||||
_connectionManager.stop(shared_from_this());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Connection::handleReadMsg(const boost::system::error_code& error, std::size_t bytes_transferred)
|
||||
{
|
||||
if (!error)
|
||||
{
|
||||
_inputStreamBuf.commit(bytes_transferred);
|
||||
|
||||
std::istream is(&_inputStreamBuf);
|
||||
std::ostream os(&_outputStreamBuf);
|
||||
|
||||
Remote::ServerMessage response;
|
||||
Remote::ClientMessage request;
|
||||
|
||||
if (!request.ParseFromIstream(&is))
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Parse request failed!";
|
||||
_connectionManager.stop(shared_from_this());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_requestHandler.process(request, response))
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Process request failed!";
|
||||
_connectionManager.stop(shared_from_this());
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
|
||||
if (!response.SerializeToOstream(&os))
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Cannot serialize to ostream!";
|
||||
_connectionManager.stop(shared_from_this());
|
||||
return;
|
||||
}
|
||||
|
||||
if (_outputStreamBuf.size() >= Remote::Header::max_data_size)
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "output message is too big! " << _outputStreamBuf.size() << " > " << Remote::Header::max_data_size;
|
||||
_connectionManager.stop(shared_from_this());
|
||||
return;
|
||||
}
|
||||
|
||||
std::array<unsigned char, Remote::Header::size> headerBuffer;
|
||||
{
|
||||
Remote::Header header;
|
||||
header.setDataSize(_outputStreamBuf.size());
|
||||
header.to_buffer(headerBuffer);
|
||||
}
|
||||
|
||||
std::size_t n = boost::asio::write(_socket,
|
||||
boost::asio::buffer(headerBuffer),
|
||||
boost::asio::transfer_exactly(Remote::Header::size),
|
||||
ec);
|
||||
if (ec)
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "cannot write header: " << error.message();
|
||||
_connectionManager.stop(shared_from_this());
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(n == Remote::Header::size);
|
||||
}
|
||||
|
||||
// Now send serialized payload
|
||||
n = boost::asio::write(_socket,
|
||||
_outputStreamBuf.data(),
|
||||
boost::asio::transfer_exactly(_outputStreamBuf.size()),
|
||||
ec);
|
||||
|
||||
if (ec)
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "cannot write msg: " << error.message();
|
||||
_connectionManager.stop(shared_from_this());
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(n == _outputStreamBuf.size());
|
||||
_outputStreamBuf.consume(n);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// All good here, read another message
|
||||
readMsg();
|
||||
}
|
||||
else if (error != boost::asio::error::operation_aborted)
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Connection::handleRead: " << error.message();
|
||||
_connectionManager.stop(shared_from_this());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace Server
|
||||
} // namespace Remote
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef REMOTE_CONNECTION_HPP
|
||||
#define REMOTE_CONNECTION_HPP
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/ssl.hpp>
|
||||
|
||||
#include "RequestHandler.hpp"
|
||||
|
||||
#include "messages/Header.hpp"
|
||||
|
||||
namespace Remote {
|
||||
namespace Server {
|
||||
|
||||
class ConnectionManager;
|
||||
|
||||
/// Represents a single connection from a client.
|
||||
class Connection : public std::enable_shared_from_this<Connection>
|
||||
{
|
||||
public:
|
||||
|
||||
typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ssl_socket;
|
||||
|
||||
Connection(const Connection&) = delete;
|
||||
Connection& operator=(const Connection&) = delete;
|
||||
|
||||
typedef std::shared_ptr<Connection> pointer;
|
||||
|
||||
/// Construct a connection with the given io_service.
|
||||
explicit Connection(boost::asio::io_service& ioService, boost::asio::ssl::context& context,
|
||||
ConnectionManager& manager,
|
||||
const boost::filesystem::path& dbPath);
|
||||
|
||||
ssl_socket::lowest_layer_type& getSocket() {return _socket.lowest_layer();}
|
||||
|
||||
/// Start the first asynchronous operation for the connection.
|
||||
void start();
|
||||
|
||||
/// Stop all asynchronous operations associated with the connection.
|
||||
void stop();
|
||||
|
||||
private:
|
||||
bool _closing;
|
||||
|
||||
/// Read a new message on the the connection
|
||||
void readMsg();
|
||||
|
||||
/// Handle completion of ssl handshake
|
||||
void handleHandshake(const boost::system::error_code& error);
|
||||
|
||||
/// Handle completion of a read operation.
|
||||
void handleReadHeader(const boost::system::error_code& e,
|
||||
std::size_t bytes_transferred);
|
||||
|
||||
void handleReadMsg(const boost::system::error_code& e,
|
||||
std::size_t bytes_transferred);
|
||||
|
||||
/// Socket for the connection.
|
||||
ssl_socket _socket;
|
||||
|
||||
/// The manager for this connection.
|
||||
ConnectionManager& _connectionManager;
|
||||
|
||||
/// The handler used to process the incoming requests.
|
||||
RequestHandler _requestHandler;
|
||||
|
||||
boost::asio::streambuf _inputStreamBuf;
|
||||
boost::asio::streambuf _outputStreamBuf;
|
||||
};
|
||||
|
||||
|
||||
} // namespace Server
|
||||
} // namespace Remote
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include "ConnectionManager.hpp"
|
||||
|
||||
namespace Remote {
|
||||
namespace Server {
|
||||
|
||||
ConnectionManager::ConnectionManager()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ConnectionManager::start(Connection::pointer c)
|
||||
{
|
||||
_connections.insert(c);
|
||||
c->start();
|
||||
}
|
||||
|
||||
void
|
||||
ConnectionManager::stop(Connection::pointer c)
|
||||
{
|
||||
_connections.erase(c);
|
||||
c->stop();
|
||||
}
|
||||
|
||||
void
|
||||
ConnectionManager::stopAll()
|
||||
{
|
||||
BOOST_FOREACH(Connection::pointer c, _connections)
|
||||
{
|
||||
c->stop();
|
||||
}
|
||||
_connections.clear();
|
||||
}
|
||||
|
||||
|
||||
} // namespace Server
|
||||
} // namespace Remote
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef REMOTE_CONNECTION_MANAGER_HPP
|
||||
#define REMOTE_CONNECTION_MANAGER_HPP
|
||||
|
||||
#include <set>
|
||||
|
||||
#include "Connection.hpp"
|
||||
|
||||
namespace Remote {
|
||||
namespace Server {
|
||||
|
||||
/// Manages open connections so that they may be cleanly stopped when the server
|
||||
/// needs to shut down.
|
||||
|
||||
class ConnectionManager
|
||||
{
|
||||
public:
|
||||
ConnectionManager(const ConnectionManager&) = delete;
|
||||
ConnectionManager& operator=(const ConnectionManager&) = delete;
|
||||
|
||||
ConnectionManager();
|
||||
|
||||
/// Add the specified connection to the manager and start it.
|
||||
void start(Connection::pointer c);
|
||||
|
||||
/// Stop the specified connection.
|
||||
void stop(Connection::pointer c);
|
||||
|
||||
/// Stop all connections.
|
||||
void stopAll();
|
||||
|
||||
private:
|
||||
/// The managed connections.
|
||||
std::set<Connection::pointer> _connections;
|
||||
};
|
||||
|
||||
} // namespace Server
|
||||
} // namespace Remote
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include "MediaRequestHandler.hpp"
|
||||
|
||||
#include "database/AudioTypes.hpp"
|
||||
|
||||
namespace Remote {
|
||||
namespace Server {
|
||||
|
||||
MediaRequestHandler::MediaRequestHandler(Database::Handler& db)
|
||||
: _db(db)
|
||||
{}
|
||||
|
||||
bool
|
||||
MediaRequestHandler::process(const MediaRequest& request, MediaResponse& response)
|
||||
{
|
||||
|
||||
bool res = false;
|
||||
|
||||
switch (request.type())
|
||||
{
|
||||
case MediaRequest::TypeMediaPrepare:
|
||||
if (request.has_prepare())
|
||||
{
|
||||
if (request.prepare().has_audio())
|
||||
res = processAudioPrepare(request.prepare().audio(), *response.mutable_prepare_result());
|
||||
else if (request.prepare().has_video())
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Video prepare not supported!";
|
||||
else
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad MediaRequest::TypeMediaPrepare!";
|
||||
|
||||
if (res)
|
||||
response.set_type(MediaResponse::TypePrepareResult);
|
||||
}
|
||||
else
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad MediaRequest::TypeMediaPrepare!";
|
||||
break;
|
||||
|
||||
case MediaRequest::TypeMediaGetPart:
|
||||
if (request.has_get_part())
|
||||
{
|
||||
res = processGetPart(request.get_part(), *response.mutable_part_result());
|
||||
if (res)
|
||||
response.set_type(MediaResponse::TypePartResult);
|
||||
}
|
||||
else
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad MediaRequest::TypeMediaGet!";
|
||||
|
||||
break;
|
||||
|
||||
case MediaRequest::TypeMediaTerminate:
|
||||
if (request.has_terminate())
|
||||
{
|
||||
res = processTerminate(request.terminate(), *response.mutable_terminate_result());
|
||||
if (res)
|
||||
response.set_type(MediaResponse::TypePartResult);
|
||||
}
|
||||
else
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad MediaRequest::TypeMediaTerminate!";
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Unhandled MediaRequest type = " << request.type();
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool
|
||||
MediaRequestHandler::processAudioPrepare(const MediaRequest::Prepare::Audio& request, MediaResponse::PrepareResult& response)
|
||||
{
|
||||
std::size_t bitrate;
|
||||
Transcode::Format::Encoding format;
|
||||
|
||||
switch( request.codec_type())
|
||||
{
|
||||
case MediaRequest::Prepare::AudioCodecTypeOGA: format = Transcode::Format::OGA; break;
|
||||
default:
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Unhandled codec type = " << request.codec_type();
|
||||
return false;
|
||||
}
|
||||
|
||||
switch( request.bitrate() )
|
||||
{
|
||||
case MediaRequest::Prepare::AudioBitrate_32_kbps: bitrate = 32000; break;
|
||||
case MediaRequest::Prepare::AudioBitrate_64_kbps: bitrate = 64000; break;
|
||||
case MediaRequest::Prepare::AudioBitrate_96_kbps: bitrate = 96000; break;
|
||||
case MediaRequest::Prepare::AudioBitrate_128_kbps: bitrate = 128000; break;
|
||||
case MediaRequest::Prepare::AudioBitrate_192_kbps: bitrate = 192000; break;
|
||||
case MediaRequest::Prepare::AudioBitrate_256_kbps: bitrate = 256000; break;
|
||||
default:
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Unhandled bitrate type = " << request.bitrate();
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO use user's bitrate limits!
|
||||
// TODO limit transcoder number by user?
|
||||
|
||||
if (_transcoders.size() + 1 > _maxTranscoders)
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Transcoder limit reached!" << std::endl;
|
||||
// Just answer an empty response, dont delete existing trasncode jobs
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Wt::Dbo::Transaction transaction( _db.getSession());
|
||||
|
||||
Database::Track::pointer track = Database::Track::getById( _db.getSession(), request.track_id() );
|
||||
if (!track)
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Requested track does not exist" << std::endl;
|
||||
// Track does no longer exist, just answer an empty response
|
||||
return true;
|
||||
}
|
||||
|
||||
Transcode::InputMediaFile inputFile(track->getPath());
|
||||
Transcode::Parameters parameters(inputFile, Transcode::Format::get( format ));
|
||||
parameters.setBitrate( Transcode::Stream::Audio, bitrate);
|
||||
|
||||
std::shared_ptr<Transcode::AvConvTranscoder> transcoder = std::make_shared<Transcode::AvConvTranscoder>( parameters );
|
||||
|
||||
// now get a unique id (relative to this connection!)
|
||||
uint32_t handle = _curHandle++;
|
||||
assert(_transcoders.find(handle) == _transcoders.end());
|
||||
|
||||
_transcoders[handle] = transcoder;
|
||||
|
||||
response.set_handle(handle);
|
||||
|
||||
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "Set up new transcode, handle = " << handle;
|
||||
}
|
||||
catch(std::exception& e)
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Caught exception: " << e.what();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
MediaRequestHandler::processGetPart(const MediaRequest::GetPart& request, MediaResponse::PartResult& response)
|
||||
{
|
||||
std::size_t dataSize = request.requested_data_size();
|
||||
if (dataSize > _maxPartSize)
|
||||
dataSize = _maxPartSize;
|
||||
|
||||
if (_transcoders.find(request.handle()) == _transcoders.end())
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "No transcoder found for handle " << request.handle();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<Transcode::AvConvTranscoder> transcoder = _transcoders[request.handle()];
|
||||
|
||||
while (!transcoder->isComplete() && transcoder->getOutputData().size() < dataSize)
|
||||
transcoder->process();
|
||||
|
||||
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "MediaRequestHandler::processGetPart, handle = " << request.handle() << ", isComplete = " << std::boolalpha << transcoder->isComplete() << ", size = " << transcoder->getOutputData().size();
|
||||
|
||||
Transcode::AvConvTranscoder::data_type::iterator itEnd;
|
||||
if (transcoder->getOutputData().size() > dataSize)
|
||||
itEnd = transcoder->getOutputData().begin() + dataSize;
|
||||
else
|
||||
itEnd = transcoder->getOutputData().end();
|
||||
|
||||
std::copy(transcoder->getOutputData().begin(), itEnd, std::back_inserter(*response.mutable_data()));
|
||||
|
||||
// Consume sent bytes
|
||||
transcoder->getOutputData().erase(transcoder->getOutputData().begin(), itEnd);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
MediaRequestHandler::processTerminate(const MediaRequest::Terminate& request, MediaResponse::TerminateResult& response)
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "MediaRequestHandler: resetting transcoder for handle " << request.handle();
|
||||
|
||||
if (_transcoders.find(request.handle()) == _transcoders.end())
|
||||
{
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "No transcoder found for handle " << request.handle();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_transcoders.erase(request.handle());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace Remote
|
||||
} // namespace Server
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef REMOTE_MEDIA_REQUEST_HANDLER
|
||||
#define REMOTE_MEDIA_REQUEST_HANDLER
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
#include "database/DatabaseHandler.hpp"
|
||||
#include "transcode/AvConvTranscoder.hpp"
|
||||
|
||||
#include "media.pb.h"
|
||||
|
||||
namespace Remote {
|
||||
namespace Server {
|
||||
|
||||
class MediaRequestHandler
|
||||
{
|
||||
public:
|
||||
MediaRequestHandler(Database::Handler& db);
|
||||
|
||||
bool process(const MediaRequest& request, MediaResponse& response);
|
||||
|
||||
private:
|
||||
|
||||
bool processAudioPrepare(const MediaRequest::Prepare::Audio& request, MediaResponse::PrepareResult& response);
|
||||
bool processGetPart(const MediaRequest::GetPart& request, MediaResponse::PartResult& response);
|
||||
bool processTerminate(const MediaRequest::Terminate& request, MediaResponse::TerminateResult& response);
|
||||
|
||||
// bool processVideoPrepare(const AudioCollectionRequest::GetGenreList& request, AudioCollectionResponse::GenreList& response);
|
||||
|
||||
std::map<uint32_t, std::shared_ptr<Transcode::AvConvTranscoder> > _transcoders;
|
||||
|
||||
Database::Handler& _db;
|
||||
|
||||
uint32_t _curHandle = 0;
|
||||
|
||||
static const std::size_t _maxPartSize = 65536 - 128;
|
||||
static const std::size_t _maxTranscoders = 1;
|
||||
};
|
||||
|
||||
} // namespace Remote
|
||||
} // namespace Server
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include "RequestHandler.hpp"
|
||||
|
||||
namespace Remote {
|
||||
namespace Server {
|
||||
|
||||
RequestHandler::RequestHandler(boost::filesystem::path dbPath)
|
||||
: _db( dbPath ),
|
||||
_authRequestHandler(_db),
|
||||
_audioCollectionRequestHandler(_db),
|
||||
_mediaRequestHandler(_db)
|
||||
{
|
||||
}
|
||||
|
||||
RequestHandler::~RequestHandler()
|
||||
{
|
||||
// TODO manually log out user if needed?
|
||||
_db.getLogin().logout();
|
||||
}
|
||||
|
||||
bool
|
||||
RequestHandler::process(const ClientMessage& request, ServerMessage& response)
|
||||
{
|
||||
bool res = false;
|
||||
|
||||
switch(request.type())
|
||||
{
|
||||
|
||||
case ClientMessage::AuthRequest:
|
||||
if (request.has_auth_request())
|
||||
{
|
||||
res = _authRequestHandler.process(request.auth_request(), *response.mutable_auth_response());
|
||||
if (res)
|
||||
response.set_type(ServerMessage::AuthResponse);
|
||||
}
|
||||
else
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad ClientMessage::AuthRequest !";
|
||||
break;
|
||||
case ClientMessage::AudioCollectionRequest:
|
||||
// Not allowed if the user is not logged in
|
||||
if (_db.getLogin().loggedIn())
|
||||
{
|
||||
if (request.has_audio_collection_request())
|
||||
{
|
||||
res = _audioCollectionRequestHandler.process(request.audio_collection_request(), *response.mutable_audio_collection_response());
|
||||
if (res)
|
||||
response.set_type( ServerMessage::AudioCollectionResponse);
|
||||
}
|
||||
else
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad ClientMessage::AudioCollectionRequest message!";
|
||||
}
|
||||
break;
|
||||
|
||||
case ClientMessage::MediaRequest:
|
||||
// Not allowed if the user is not logged in
|
||||
if (_db.getLogin().loggedIn())
|
||||
{
|
||||
if (request.has_media_request())
|
||||
{
|
||||
res = _mediaRequestHandler.process(request.media_request(), *response.mutable_media_response());
|
||||
if (res)
|
||||
response.set_type( ServerMessage::MediaResponse);
|
||||
}
|
||||
else
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Malformed ClientMessage::MediaRequest message!";
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Unhandled message type = " << request.type();
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
} // namespace Server
|
||||
} // namespace Remote
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef REMOTE_REQUEST_HANDLER
|
||||
#define REMOTE_REQUEST_HANDLER
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include "messages.pb.h"
|
||||
|
||||
#include "database/DatabaseHandler.hpp"
|
||||
|
||||
#include "AuthRequestHandler.hpp"
|
||||
#include "AudioCollectionRequestHandler.hpp"
|
||||
#include "MediaRequestHandler.hpp"
|
||||
|
||||
namespace Remote {
|
||||
namespace Server {
|
||||
|
||||
class RequestHandler
|
||||
{
|
||||
public:
|
||||
|
||||
RequestHandler(boost::filesystem::path dbPath);
|
||||
~RequestHandler();
|
||||
|
||||
bool process(const ClientMessage& request, ServerMessage& response);
|
||||
|
||||
private:
|
||||
|
||||
Database::Handler _db;
|
||||
|
||||
AuthRequestHandler _authRequestHandler;
|
||||
AudioCollectionRequestHandler _audioCollectionRequestHandler;
|
||||
MediaRequestHandler _mediaRequestHandler;
|
||||
};
|
||||
|
||||
} // namespace Server
|
||||
} // namespace Remote
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <boost/asio/placeholders.hpp>
|
||||
#include <boost/bind.hpp>
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include "Server.hpp"
|
||||
|
||||
namespace Remote {
|
||||
namespace Server {
|
||||
|
||||
Server::Server(const endpoint_type& bindEndpoint,
|
||||
boost::filesystem::path certPath,
|
||||
boost::filesystem::path privKeyPath,
|
||||
boost::filesystem::path dhPath,
|
||||
boost::filesystem::path dbPath)
|
||||
:
|
||||
_acceptor(_ioService, bindEndpoint, true /*SO_REUSEADDR*/),
|
||||
_connectionManager(),
|
||||
_context(boost::asio::ssl::context::tlsv1_server),
|
||||
_dbPath(dbPath)
|
||||
{
|
||||
_ioService.setThreadCount(1); // TODO parametrize
|
||||
|
||||
_context.set_options( boost::asio::ssl::context::default_workarounds // TODO check this thing
|
||||
| boost::asio::ssl::context::single_dh_use
|
||||
| boost::asio::ssl::context::no_sslv2
|
||||
| boost::asio::ssl::context::no_sslv3
|
||||
);
|
||||
// context_.set_password_callback(boost::bind(&server::get_password, this));
|
||||
_context.use_certificate_chain_file(certPath.string());
|
||||
_context.use_private_key_file(privKeyPath.string(), boost::asio::ssl::context::pem);
|
||||
_context.use_tmp_dh_file(dhPath.string());
|
||||
}
|
||||
|
||||
void
|
||||
Server::start()
|
||||
{
|
||||
// While the server is running, there is always at least one
|
||||
// asynchronous operation outstanding: the asynchronous accept call waiting
|
||||
// for new incoming connections.
|
||||
asyncAccept();
|
||||
|
||||
_ioService.start();
|
||||
}
|
||||
|
||||
void
|
||||
Server::asyncAccept()
|
||||
{
|
||||
std::shared_ptr<Connection> newConnection = std::make_shared<Connection>(_ioService, _context, _connectionManager, _dbPath);
|
||||
|
||||
_acceptor.async_accept(newConnection->getSocket(),
|
||||
boost::bind(&Server::handleAccept, this, newConnection, boost::asio::placeholders::error));
|
||||
}
|
||||
|
||||
void
|
||||
Server::handleAccept(std::shared_ptr<Connection> newConnection, boost::system::error_code ec)
|
||||
{
|
||||
// Check whether the server was stopped before this
|
||||
// completion handler had a chance to run.
|
||||
if (!_acceptor.is_open())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ec)
|
||||
{
|
||||
_connectionManager.start(newConnection);
|
||||
|
||||
// Accept another connection
|
||||
// TODO: add some limit?
|
||||
asyncAccept();
|
||||
}
|
||||
else
|
||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "handleAccept: " << ec.message();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Server::stop()
|
||||
{
|
||||
// The server is stopped by cancelling all outstanding asynchronous
|
||||
// operations.
|
||||
_acceptor.close();
|
||||
_connectionManager.stopAll();
|
||||
|
||||
_ioService.stop();
|
||||
}
|
||||
|
||||
} // namespace Server
|
||||
} // namespace Remote
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef REMOTE_SERVER_HPP
|
||||
#define REMOTE_SERVER_HPP
|
||||
|
||||
#include <Wt/WIOService>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/ssl.hpp>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include "Connection.hpp"
|
||||
#include "ConnectionManager.hpp"
|
||||
|
||||
#include "RequestHandler.hpp"
|
||||
|
||||
namespace Remote {
|
||||
namespace Server {
|
||||
|
||||
class Server
|
||||
{
|
||||
public:
|
||||
|
||||
Server(const Server&) = delete;
|
||||
Server& operator=(const Server&) = delete;
|
||||
|
||||
typedef boost::asio::ip::tcp::endpoint endpoint_type;
|
||||
|
||||
// Serve up data from the given database
|
||||
Server(const endpoint_type& bindEndpoint,
|
||||
boost::filesystem::path certPath,
|
||||
boost::filesystem::path privKeyPath,
|
||||
boost::filesystem::path dhPath,
|
||||
boost::filesystem::path dbPath);
|
||||
|
||||
// Run the server's io_service loop.
|
||||
void start();
|
||||
|
||||
void stop();
|
||||
|
||||
private:
|
||||
/// Perform an asynchronous accept operation.
|
||||
void asyncAccept();
|
||||
void handleAccept(std::shared_ptr<Connection> newConnection, boost::system::error_code ec);
|
||||
|
||||
Wt::WIOService _ioService;
|
||||
|
||||
/// Acceptor used to listen for incoming connections.
|
||||
boost::asio::ip::tcp::acceptor _acceptor;
|
||||
|
||||
/// The connection manager which owns all live connections.
|
||||
ConnectionManager _connectionManager;
|
||||
|
||||
boost::asio::ssl::context _context;
|
||||
|
||||
/// The database to be used for requests
|
||||
boost::filesystem::path _dbPath;
|
||||
};
|
||||
|
||||
} // namespace Server
|
||||
} // namespace Remote
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <boost/thread.hpp>
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include "DatabaseUpdateService.hpp"
|
||||
|
||||
namespace Service {
|
||||
|
||||
DatabaseUpdateService::DatabaseUpdateService(const Config& config)
|
||||
: _metadataParser(),
|
||||
_databaseUpdater( config.dbPath, _metadataParser)
|
||||
{
|
||||
_databaseUpdater.setAudioExtensions(config.audioExtensions);
|
||||
_databaseUpdater.setVideoExtensions(config.videoExtensions);
|
||||
}
|
||||
|
||||
void
|
||||
DatabaseUpdateService::start(void)
|
||||
{
|
||||
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "DatabaseUpdateService, starting...";
|
||||
_databaseUpdater.start();
|
||||
}
|
||||
|
||||
void
|
||||
DatabaseUpdateService::stop(void)
|
||||
{
|
||||
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "DatabaseUpdateService, stopping...";
|
||||
_databaseUpdater.stop();
|
||||
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "DatabaseUpdateService, stopped";
|
||||
}
|
||||
|
||||
void
|
||||
DatabaseUpdateService::restart(void)
|
||||
{
|
||||
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "DatabaseUpdateService, restart";
|
||||
stop();
|
||||
start();
|
||||
}
|
||||
|
||||
} // namespace Service
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef DB_UPDATE_SERVICE_HPP
|
||||
#define DB_UPDATE_SERVICE_HPP
|
||||
|
||||
#include <boost/thread.hpp>
|
||||
#include <boost/asio/io_service.hpp>
|
||||
|
||||
#include "metadata/AvFormat.hpp"
|
||||
#include "database-updater/DatabaseUpdater.hpp"
|
||||
|
||||
#include "Service.hpp"
|
||||
|
||||
namespace Service {
|
||||
|
||||
class DatabaseUpdateService : public Service
|
||||
{
|
||||
public:
|
||||
|
||||
typedef std::shared_ptr<DatabaseUpdateService> pointer;
|
||||
|
||||
struct Config {
|
||||
bool enable;
|
||||
boost::filesystem::path dbPath;
|
||||
std::vector<std::string> audioExtensions;
|
||||
std::vector<std::string> videoExtensions;
|
||||
};
|
||||
|
||||
DatabaseUpdateService(const Config& config);
|
||||
|
||||
// Service interface
|
||||
void start(void);
|
||||
void stop(void);
|
||||
void restart(void);
|
||||
|
||||
private:
|
||||
|
||||
MetaData::AvFormat _metadataParser;
|
||||
DatabaseUpdater::Updater _databaseUpdater; // Todo use handler
|
||||
};
|
||||
|
||||
} // namespace Service
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include "RemoteServerService.hpp"
|
||||
|
||||
namespace Service {
|
||||
|
||||
RemoteServerService::RemoteServerService(const Config& config)
|
||||
: _server(boost::asio::ip::tcp::endpoint(config.address, config.port),
|
||||
config.sslCertificatePath,
|
||||
config.sslPrivateKeyPath,
|
||||
config.sslTempDhPath,
|
||||
config.dbPath)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
RemoteServerService::start(void)
|
||||
{
|
||||
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "RemoteServerService::start, starting...";
|
||||
_server.start();
|
||||
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "RemoteServerService::start, started!";
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
RemoteServerService::stop(void)
|
||||
{
|
||||
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "RemoteServerService::stop, stopping...";
|
||||
_server.stop();
|
||||
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "RemoteServerService::stop, stopped!";
|
||||
}
|
||||
|
||||
void
|
||||
RemoteServerService::restart(void)
|
||||
{
|
||||
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "RemoteServerService::restart, not implemented!";
|
||||
}
|
||||
|
||||
} // namespace Service
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef REMOTE_SERVER_SERVICE_HPP
|
||||
#define REMOTE_SERVER_SERVICE_HPP
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
|
||||
#include "Service.hpp"
|
||||
|
||||
#include "remote/server/Server.hpp"
|
||||
|
||||
namespace Service {
|
||||
|
||||
class RemoteServerService : public Service
|
||||
{
|
||||
public:
|
||||
|
||||
struct Config {
|
||||
bool enable;
|
||||
boost::asio::ip::address address;
|
||||
unsigned short port;
|
||||
boost::filesystem::path sslCertificatePath;
|
||||
boost::filesystem::path sslPrivateKeyPath;
|
||||
boost::filesystem::path sslTempDhPath;
|
||||
boost::filesystem::path dbPath;
|
||||
};
|
||||
|
||||
RemoteServerService(const Config& config);
|
||||
|
||||
void start(void);
|
||||
void stop(void);
|
||||
void restart(void);
|
||||
|
||||
private:
|
||||
|
||||
Remote::Server::Server _server;
|
||||
};
|
||||
|
||||
} // namespace Service
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SERVICE_HPP
|
||||
#define SERVICE_HPP
|
||||
|
||||
#include <boost/thread.hpp>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
|
||||
namespace Service {
|
||||
|
||||
// Interface class wrapper for running services
|
||||
class Service
|
||||
{
|
||||
public:
|
||||
|
||||
typedef std::shared_ptr<Service> pointer;
|
||||
|
||||
Service(const Service&) = delete;
|
||||
Service& operator=(const Service&) = delete;
|
||||
|
||||
Service() {}
|
||||
virtual ~Service() {}
|
||||
|
||||
virtual void start(void) = 0;
|
||||
virtual void stop(void) = 0;
|
||||
virtual void restart(void) = 0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Service
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
#include <boost/bind.hpp>
|
||||
|
||||
#include "ServiceManager.hpp"
|
||||
|
||||
namespace Service {
|
||||
|
||||
ServiceManager&
|
||||
ServiceManager::instance()
|
||||
{
|
||||
static ServiceManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
ServiceManager::ServiceManager()
|
||||
: _signalSet(_ioService)
|
||||
{
|
||||
_signalSet.add(SIGINT);
|
||||
_signalSet.add(SIGTERM);
|
||||
#if defined(SIGQUIT)
|
||||
_signalSet.add(SIGQUIT);
|
||||
#endif // defined(SIGQUIT)
|
||||
|
||||
_signalSet.add(SIGHUP);
|
||||
|
||||
// Excplicitely ignore SIGCHLD to avoid zombies
|
||||
// when avconv child processes are being killed
|
||||
if (::signal(SIGCHLD, SIG_IGN) == SIG_ERR)
|
||||
throw std::runtime_error("ServiceManager::ServiceManager, signal failed!");
|
||||
}
|
||||
|
||||
ServiceManager::~ServiceManager()
|
||||
{
|
||||
LMS_LOG(MOD_SERVICE, SEV_NOTICE) << "Stopping services...";
|
||||
stopServices();
|
||||
}
|
||||
|
||||
void
|
||||
ServiceManager::run()
|
||||
{
|
||||
|
||||
asyncWaitSignals();
|
||||
|
||||
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "ServiceManager: waiting for events...";
|
||||
try {
|
||||
// Wait for events
|
||||
_ioService.run();
|
||||
}
|
||||
catch( std::exception& e )
|
||||
{
|
||||
LMS_LOG(MOD_SERVICE, SEV_ERROR) << "ServiceManager: exception in ioService::run: " << e.what();
|
||||
}
|
||||
|
||||
// Stopping services
|
||||
stopServices();
|
||||
|
||||
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "ServiceManager: run complete !";
|
||||
}
|
||||
|
||||
void
|
||||
ServiceManager::asyncWaitSignals(void)
|
||||
{
|
||||
_signalSet.async_wait(boost::bind(&ServiceManager::handleSignal,
|
||||
this,
|
||||
boost::asio::placeholders::error,
|
||||
boost::asio::placeholders::signal_number));
|
||||
}
|
||||
|
||||
void
|
||||
ServiceManager::startService(Service::pointer service)
|
||||
{
|
||||
_services.insert(service);
|
||||
service->start();
|
||||
}
|
||||
|
||||
void
|
||||
ServiceManager::stopService(Service::pointer service)
|
||||
{
|
||||
_services.erase(service);
|
||||
service->stop();
|
||||
}
|
||||
|
||||
void
|
||||
ServiceManager::stopServices(void)
|
||||
{
|
||||
BOOST_FOREACH(Service::pointer service, _services)
|
||||
service->stop();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ServiceManager::restartServices(void)
|
||||
{
|
||||
LMS_LOG(MOD_SERVICE, SEV_NOTICE) << "Restarting services...";
|
||||
BOOST_FOREACH(Service::pointer service, _services)
|
||||
service->restart();
|
||||
}
|
||||
|
||||
void
|
||||
ServiceManager::handleSignal(boost::system::error_code /*ec*/, int signo)
|
||||
{
|
||||
LMS_LOG(MOD_SERVICE, SEV_INFO) << "Received signal " << signo;
|
||||
|
||||
switch (signo)
|
||||
{
|
||||
case SIGINT:
|
||||
case SIGTERM:
|
||||
case SIGQUIT:
|
||||
stopServices();
|
||||
|
||||
// Do not listen for signals, this will make the ioservice.run return
|
||||
break;
|
||||
case SIGHUP:
|
||||
restartServices();
|
||||
|
||||
asyncWaitSignals();
|
||||
break;
|
||||
default:
|
||||
LMS_LOG(MOD_SERVICE, SEV_NOTICE) << "Unhandled signal " << signo;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Service
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SERVICE_CONTROLER_HPP
|
||||
#define SERVICE_CONTROLER_HPP
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <set>
|
||||
|
||||
#include "Service.hpp"
|
||||
|
||||
namespace Service {
|
||||
|
||||
// Start/Stop/Reload Services
|
||||
class ServiceManager
|
||||
{
|
||||
public:
|
||||
|
||||
static ServiceManager& instance();
|
||||
~ServiceManager();
|
||||
|
||||
void stopService(Service::pointer service);
|
||||
void startService(Service::pointer service);
|
||||
|
||||
void stopAllServices();
|
||||
|
||||
// Return in case of failure/stop by user
|
||||
void run();
|
||||
|
||||
template <class T> typename T::pointer getService();
|
||||
|
||||
boost::mutex& mutex() { return _mutex;}
|
||||
|
||||
private:
|
||||
|
||||
ServiceManager();
|
||||
ServiceManager(ServiceManager const&); // Don't Implement
|
||||
void operator=(ServiceManager const&); // Don't implement
|
||||
|
||||
void restartServices(void);
|
||||
void stopServices(void);
|
||||
|
||||
void asyncWaitSignals(void);
|
||||
|
||||
void handleSignal(boost::system::error_code error, int signo);
|
||||
|
||||
boost::mutex _mutex;
|
||||
|
||||
boost::asio::io_service _ioService;
|
||||
|
||||
// Listen for interesting signals
|
||||
boost::asio::signal_set _signalSet;
|
||||
|
||||
std::set<Service::pointer> _services;
|
||||
};
|
||||
|
||||
|
||||
template <class T> typename T::pointer
|
||||
ServiceManager::getService()
|
||||
{
|
||||
std::set<Service::pointer>::iterator it;
|
||||
for (std::set<Service::pointer>::iterator it = _services.begin(); it != _services.end(); ++it)
|
||||
{
|
||||
if (typeid(*(*it)) == typeid(T)) {
|
||||
return std::dynamic_pointer_cast<T>(*it);
|
||||
}
|
||||
}
|
||||
return std::shared_ptr<T>();
|
||||
}
|
||||
|
||||
} // namespace Service
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include "UserInterfaceService.hpp"
|
||||
#include "ui/LmsApplication.hpp"
|
||||
|
||||
namespace Service {
|
||||
|
||||
UserInterfaceService::UserInterfaceService( boost::filesystem::path runAppPath, const Config& config)
|
||||
: _server(runAppPath.string())
|
||||
{
|
||||
std::vector<std::string> args;
|
||||
|
||||
args.push_back(runAppPath.string());
|
||||
args.push_back("--docroot=" + config.docRootPath.string());
|
||||
args.push_back("--approot=" + config.appRootPath.string());
|
||||
{
|
||||
std::ostringstream oss; oss << config.httpsPort;
|
||||
args.push_back("--https-port=" + oss.str());
|
||||
}
|
||||
args.push_back("--https-address=" + config.httpsAddress.to_string());
|
||||
args.push_back("--ssl-certificate=" + config.sslCertificatePath.string());
|
||||
args.push_back("--ssl-private-key=" + config.sslPrivateKeyPath.string());
|
||||
args.push_back("--ssl-tmp-dh=" + config.sslTempDhPath.string());
|
||||
|
||||
// Construct argc/argv
|
||||
int argc = args.size();
|
||||
const char* argv[args.size()];
|
||||
for (int i = 0; i < argc; ++i)
|
||||
argv[i] = args[i].c_str();
|
||||
|
||||
for(int i = 0; i < argc; ++i)
|
||||
{
|
||||
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "i = " << i << ", arg = '" << argv[i] << "'";
|
||||
}
|
||||
|
||||
_server.setServerConfiguration (argc, const_cast<char**>(argv));
|
||||
|
||||
// bind entry point
|
||||
_server.addEntryPoint(Wt::Application, boost::bind(UserInterface::LmsApplication::create, _1, config.dbPath));
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
UserInterfaceService::start(void)
|
||||
{
|
||||
_server.start();
|
||||
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "UserInterfaceService::start -> Service started...";
|
||||
}
|
||||
|
||||
void
|
||||
UserInterfaceService::stop(void)
|
||||
{
|
||||
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "UserInterfaceService::stop -> stopping...";
|
||||
_server.stop();
|
||||
LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "UserInterfaceService::stop -> stopped!";
|
||||
}
|
||||
|
||||
void
|
||||
UserInterfaceService::restart(void)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
} // namespace Service
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef WEB_SERVER_SERVICE_HPP
|
||||
#define WEB_SERVER_SERVICE_HPP
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
|
||||
#include <Wt/WServer>
|
||||
|
||||
#include "Service.hpp"
|
||||
|
||||
namespace Service {
|
||||
|
||||
class UserInterfaceService : public Service
|
||||
{
|
||||
public:
|
||||
|
||||
struct Config {
|
||||
bool enable;
|
||||
boost::filesystem::path docRootPath;
|
||||
boost::filesystem::path appRootPath;
|
||||
unsigned short httpsPort;
|
||||
boost::asio::ip::address httpsAddress;
|
||||
boost::filesystem::path sslCertificatePath;
|
||||
boost::filesystem::path sslPrivateKeyPath;
|
||||
boost::filesystem::path sslTempDhPath;
|
||||
boost::filesystem::path dbPath;
|
||||
};
|
||||
|
||||
UserInterfaceService(boost::filesystem::path runAppPath,
|
||||
const Config& config);
|
||||
|
||||
void start(void);
|
||||
void stop(void);
|
||||
void restart(void);
|
||||
|
||||
private:
|
||||
|
||||
Wt::WServer _server;
|
||||
|
||||
};
|
||||
|
||||
} //namespace Service
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include <boost/iostreams/stream.hpp>
|
||||
#include <boost/process.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include "AvConvTranscoder.hpp"
|
||||
|
||||
namespace Transcode
|
||||
{
|
||||
|
||||
// TODO, parametrize?
|
||||
const std::vector<std::string> execNames =
|
||||
{
|
||||
"avconv",
|
||||
"ffmpeg",
|
||||
};
|
||||
|
||||
|
||||
boost::mutex AvConvTranscoder::_mutex;
|
||||
|
||||
boost::filesystem::path AvConvTranscoder::_avConvPath = boost::filesystem::path();
|
||||
|
||||
void
|
||||
AvConvTranscoder::init()
|
||||
{
|
||||
BOOST_FOREACH(std::string execName, execNames)
|
||||
{
|
||||
const boost::filesystem::path p = boost::process::search_path(execName);
|
||||
if (!p.empty())
|
||||
{
|
||||
_avConvPath = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_avConvPath.empty())
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_INFO) << "Using transcoder " << _avConvPath;
|
||||
else
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_ERROR) << "Cannot find any transcoder binary!";
|
||||
}
|
||||
|
||||
|
||||
//boost::filesystem::path AvConvTranscoder::_avConvPath = "";
|
||||
|
||||
AvConvTranscoder::AvConvTranscoder(const Parameters& parameters)
|
||||
: _parameters(parameters),
|
||||
_outputPipe(boost::process::create_pipe()),
|
||||
_source(_outputPipe.source, boost::iostreams::close_handle),
|
||||
_is(_source),
|
||||
_in(&_is),
|
||||
_isComplete(false)
|
||||
{
|
||||
|
||||
if (!boost::filesystem::exists(_parameters.getInputMediaFile().getPath())) {
|
||||
throw std::runtime_error("File " + _parameters.getInputMediaFile().getPath().string() + " does not exists!");
|
||||
}
|
||||
else if (!boost::filesystem::is_regular( _parameters.getInputMediaFile().getPath())) {
|
||||
throw std::runtime_error("File " + _parameters.getInputMediaFile().getPath().string() + " is not regular!");
|
||||
}
|
||||
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_INFO) << "Transcoding file '" << _parameters.getInputMediaFile().getPath() << "'";
|
||||
|
||||
// Launch a process to handle the conversion
|
||||
boost::iostreams::file_descriptor_sink sink(_outputPipe.sink, boost::iostreams::close_handle);
|
||||
|
||||
std::ostringstream oss;
|
||||
|
||||
oss << _avConvPath;
|
||||
|
||||
// input Offset
|
||||
if (_parameters.getOffset().total_seconds() > 0)
|
||||
oss << " -ss " << _parameters.getOffset().total_seconds(); // to be placed before '-i' to speed up seeking
|
||||
|
||||
// Input file
|
||||
oss << " -i \"" << _parameters.getInputMediaFile().getPath().string() << "\"";
|
||||
|
||||
// Output bitrates
|
||||
oss << " -b:a " << _parameters.getOutputBitrate(Stream::Audio) ;
|
||||
if (_parameters.getOutputFormat().getType() == Format::Video)
|
||||
oss << " -b:v " << _parameters.getOutputBitrate(Stream::Video);
|
||||
|
||||
// Stream mapping
|
||||
{
|
||||
typedef std::map<Stream::Type, Stream::Id> StreamMap;
|
||||
|
||||
const StreamMap& streamMap = _parameters.getInputStreams();
|
||||
|
||||
BOOST_FOREACH(const StreamMap::value_type& inputStream, streamMap)
|
||||
{
|
||||
// HACK (no subtitle support yet)
|
||||
if (inputStream.first != Stream::Subtitle)
|
||||
oss << " -map 0:" << inputStream.second; // 0 means input file index
|
||||
}
|
||||
}
|
||||
|
||||
// Codecs and formats
|
||||
switch( _parameters.getOutputFormat().getEncoding())
|
||||
{
|
||||
case Format::MP3:
|
||||
oss << " -f mp3";
|
||||
break;
|
||||
case Format::OGA:
|
||||
oss << " -acodec libvorbis -f ogg";
|
||||
break;
|
||||
case Format::OGV:
|
||||
oss << " -acodec libvorbis -ac 2 -ar 44100 -vcodec libtheora -threads 4 -f ogg";
|
||||
break;
|
||||
case Format::WEBMA:
|
||||
oss << " -codec:a libvorbis -f webm";
|
||||
break;
|
||||
case Format::WEBMV:
|
||||
oss << " -acodec libvorbis -ac 2 -ar 44100 -vcodec libvpx -threads 4 -f webm";
|
||||
break;
|
||||
case Format::M4A:
|
||||
oss << " -acodec aac -f mp4";
|
||||
break;
|
||||
case Format::M4V:
|
||||
oss << " -acodec aac -strict experimental -ac 2 -ar 44100 -vcodec libx264 -f m4v";
|
||||
break;
|
||||
case Format::FLV:
|
||||
oss << " -acodec libmp3lame -ac 2 -ar 44100 -vcodec libx264 -f flv";
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
oss << " -"; // output to stdout
|
||||
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "Executing '" << oss.str() << "'";
|
||||
|
||||
// make sure only one thread is executing this part of code
|
||||
// See boost process FAQ
|
||||
{
|
||||
boost::lock_guard<boost::mutex> lock(_mutex);
|
||||
std::vector<int> ranges = { 3, 1024 }; // fd range to be closed
|
||||
|
||||
_child = std::make_shared<boost::process::child>( boost::process::execute(
|
||||
boost::process::initializers::run_exe(_avConvPath),
|
||||
boost::process::initializers::set_cmd_line(oss.str()),
|
||||
boost::process::initializers::bind_stdout(sink),
|
||||
boost::process::initializers::close_fd(STDIN_FILENO),
|
||||
boost::process::initializers::close_fds(ranges)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
AvConvTranscoder::process(void)
|
||||
{
|
||||
std::size_t readDatasSize = 1024; // TODO parametrize elsewhere?
|
||||
|
||||
char ch;
|
||||
while(readDatasSize != 0 && _in && _in.get(ch)) {
|
||||
_data.push_back(ch);
|
||||
--readDatasSize;
|
||||
}
|
||||
|
||||
if (!_in || _in.fail() || _in.eof()) {
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "Transcode complete!";
|
||||
|
||||
waitChild();
|
||||
_isComplete = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
AvConvTranscoder::~AvConvTranscoder()
|
||||
{
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "~AvConvTranscoder called!";
|
||||
|
||||
if (_in.eof())
|
||||
waitChild();
|
||||
else
|
||||
killChild();
|
||||
}
|
||||
|
||||
void
|
||||
AvConvTranscoder::waitChild()
|
||||
{
|
||||
if (_child)
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "Waiting for child...";
|
||||
boost::process::wait_for_exit(*_child, ec);
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "Waiting for child: OK";
|
||||
|
||||
if (ec)
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_ERROR) << "AvConvTranscoder::waitChild: error: " << ec.message();
|
||||
|
||||
_child.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AvConvTranscoder::killChild()
|
||||
{
|
||||
if (_child)
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "Killing child! pid = " << _child->pid;
|
||||
boost::process::terminate(*_child, ec);
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "Killing child DONE";
|
||||
|
||||
// If an error occured, force kill the child
|
||||
if (ec)
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_ERROR) << "AvConvTranscoder::killChild: error: " << ec.message();
|
||||
|
||||
_child.reset();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Transcode
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef AVCONV_TRANSCODER_HPP
|
||||
#define AVCONV_TRANSCODER_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <iostream>
|
||||
#include <deque>
|
||||
|
||||
#include <boost/iostreams/stream.hpp>
|
||||
#include <boost/process.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/thread.hpp>
|
||||
|
||||
#include "Parameters.hpp"
|
||||
|
||||
namespace Transcode
|
||||
{
|
||||
|
||||
class AvConvTranscoder
|
||||
{
|
||||
public:
|
||||
|
||||
typedef std::deque<unsigned char> data_type;
|
||||
|
||||
static void init();
|
||||
|
||||
~AvConvTranscoder();
|
||||
|
||||
AvConvTranscoder(const Parameters& parameters);
|
||||
|
||||
data_type& getOutputData() { return _data; }
|
||||
|
||||
const Parameters& getParameters(void) const { return _parameters; }
|
||||
|
||||
// Process a bunch of input data
|
||||
void process(void);
|
||||
bool isComplete(void) const { return _isComplete;};
|
||||
|
||||
private:
|
||||
|
||||
typedef std::shared_ptr<boost::process::child> ChildPtr;
|
||||
|
||||
void waitChild();
|
||||
void killChild();
|
||||
|
||||
const Parameters _parameters;
|
||||
|
||||
static boost::mutex _mutex;
|
||||
|
||||
boost::process::pipe _outputPipe;
|
||||
boost::iostreams::file_descriptor_source _source;
|
||||
boost::iostreams::stream_buffer<boost::iostreams::file_descriptor_source> _is;
|
||||
std::istream _in;
|
||||
|
||||
std::shared_ptr<boost::process::child> _child;
|
||||
|
||||
static boost::filesystem::path _avConvPath;
|
||||
|
||||
data_type _data;
|
||||
bool _isComplete;
|
||||
};
|
||||
|
||||
} // naspace Transcode
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
#include <stdexcept>
|
||||
#include <cassert>
|
||||
|
||||
#include "Format.hpp"
|
||||
|
||||
namespace Transcode
|
||||
{
|
||||
|
||||
const std::vector<Format> Format::_supportedFormats
|
||||
{
|
||||
{Format::OGA, Format::Audio, "audio/ogg", "Ogg"},
|
||||
{Format::OGV, Format::Video, "video/ogg", "Ogg"},
|
||||
{Format::MP3, Format::Audio, "audio/mp3", "MP3"},
|
||||
{Format::WEBMA, Format::Audio, "audio/webm", "WebM"},
|
||||
{Format::WEBMV, Format::Video, "video/webm", "WebM"},
|
||||
{Format::FLV, Format::Video, "video/x-flv", "Flash Video"},
|
||||
{Format::M4A, Format::Audio, "audio/mp4", "MP4"},
|
||||
{Format::M4V, Format::Video, "video/mp4", "MP4"},
|
||||
};
|
||||
|
||||
Format::Format(Encoding encoding, Type type, std::string mimeType, std::string desc)
|
||||
:
|
||||
_encoding(encoding),
|
||||
_type(type),
|
||||
_mineType(mimeType),
|
||||
_desc(desc)
|
||||
{}
|
||||
|
||||
|
||||
const Format&
|
||||
Format::get(Encoding encoding)
|
||||
{
|
||||
BOOST_FOREACH(const Format& format, _supportedFormats)
|
||||
{
|
||||
if (format.getEncoding() == encoding)
|
||||
return format;
|
||||
}
|
||||
|
||||
throw std::runtime_error("Cannot find format");
|
||||
}
|
||||
|
||||
std::vector<Format>
|
||||
Format::get(Type type)
|
||||
{
|
||||
std::vector<Format> res;
|
||||
BOOST_FOREACH(const Format& format, _supportedFormats)
|
||||
{
|
||||
if (format.getType() == type)
|
||||
res.push_back( format );
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool
|
||||
Format::operator==(const Format& other) const
|
||||
{
|
||||
return getEncoding() == other.getEncoding();
|
||||
}
|
||||
|
||||
|
||||
} // namespace Transcode
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TRANSCODE_FORMAT_HPP
|
||||
#define TRANSCODE_FORMAT_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace Transcode
|
||||
{
|
||||
|
||||
|
||||
class Format
|
||||
{
|
||||
public:
|
||||
|
||||
// Output format
|
||||
enum Encoding {
|
||||
OGA,
|
||||
OGV,
|
||||
MP3,
|
||||
WEBMA,
|
||||
WEBMV,
|
||||
FLV,
|
||||
M4A,
|
||||
M4V,
|
||||
};
|
||||
|
||||
enum Type {
|
||||
Video,
|
||||
Audio,
|
||||
};
|
||||
|
||||
// Utility
|
||||
static const Format& get(Encoding encoding);
|
||||
static std::vector<Format> get(Type type);
|
||||
|
||||
Format(Encoding format, Type type, std::string mimeType, std::string desc);
|
||||
|
||||
Encoding getEncoding(void) const { return _encoding;}
|
||||
Type getType(void) const { return _type;}
|
||||
const std::string& getMimeType(void) const { return _mineType;}
|
||||
const std::string& getDesc(void) const { return _desc;}
|
||||
|
||||
bool operator==(const Format& other) const;
|
||||
|
||||
private:
|
||||
Encoding _encoding;
|
||||
Type _type;
|
||||
std::string _mineType;
|
||||
std::string _desc;
|
||||
|
||||
static const std::vector<Format> _supportedFormats;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Transcode
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include <list>
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include "av/InputFormatContext.hpp"
|
||||
#include "cover/CoverArtGrabber.hpp"
|
||||
|
||||
#include "InputMediaFile.hpp"
|
||||
|
||||
namespace Transcode
|
||||
{
|
||||
|
||||
bool getStreamType(enum AVMediaType type, Transcode::Stream::Type& streamType)
|
||||
{
|
||||
switch (type) {
|
||||
case AVMEDIA_TYPE_VIDEO: streamType = Transcode::Stream::Video; return true;
|
||||
case AVMEDIA_TYPE_AUDIO: streamType = Transcode::Stream::Audio; return true;
|
||||
case AVMEDIA_TYPE_SUBTITLE: streamType = Transcode::Stream::Subtitle; return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
InputMediaFile::InputMediaFile(const boost::filesystem::path& p)
|
||||
: _path(p)
|
||||
{
|
||||
Av::InputFormatContext input(_path);
|
||||
input.findStreamInfo();
|
||||
|
||||
// Calculate estimated duration
|
||||
if (input.getDurationSecs())
|
||||
_duration = boost::posix_time::seconds(input.getDurationSecs() + 1);
|
||||
|
||||
// Get input streams
|
||||
std::vector<Av::Stream> avStreams = input.getStreams();
|
||||
|
||||
std::list<enum AVMediaType> avMediaTypes; // List of encountered streams
|
||||
for (std::size_t avStreamId = 0; avStreamId < avStreams.size(); ++avStreamId)
|
||||
{
|
||||
Av::Stream& avStream(avStreams[avStreamId]);
|
||||
Stream::Type type;
|
||||
|
||||
if (getStreamType(avStream.getCodecContext().getType(), type))
|
||||
{
|
||||
// Reject Video stream hat are in fact cover arts
|
||||
if (avStream.hasAttachedPic())
|
||||
continue;
|
||||
|
||||
avMediaTypes.push_back(avStream.getCodecContext().getType());
|
||||
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "Stream idx " << avStreamId << ", type = " << type << ", bitrate = " << avStream.getCodecContext().getBitRate() << ", codec desc = " << avStream.getCodecContext().getCodecDesc();
|
||||
|
||||
_streams.push_back( Stream(avStreamId,
|
||||
type,
|
||||
avStream.getCodecContext().getBitRate(),
|
||||
avStream.getMetadata().get("language"), // TODO define somewhere else?
|
||||
avStream.getCodecContext().getCodecDesc()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
avMediaTypes.unique();
|
||||
// Scan for best streams
|
||||
BOOST_FOREACH(enum AVMediaType type, avMediaTypes)
|
||||
{
|
||||
Av::Stream::Idx index;
|
||||
|
||||
if (input.getBestStreamIdx(type, index))
|
||||
{
|
||||
Stream::Type streamType;
|
||||
if (getStreamType(type, streamType))
|
||||
_bestStreams.insert(std::make_pair( streamType, index) );
|
||||
}
|
||||
else
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_WARNING) << "Cannot find best stream for type " << type;
|
||||
}
|
||||
|
||||
_covers = CoverArt::Grabber::getFromInputFormatContext(input);
|
||||
}
|
||||
|
||||
std::vector<Stream>
|
||||
InputMediaFile::getStreams(Stream::Type type) const
|
||||
{
|
||||
std::vector<Stream> res;
|
||||
BOOST_FOREACH(const Stream& stream, _streams)
|
||||
{
|
||||
if (stream.getType() == type)
|
||||
res.push_back(stream);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
const Stream&
|
||||
InputMediaFile::getStream(Stream::Id index) const
|
||||
{
|
||||
BOOST_FOREACH(const Stream& stream, _streams)
|
||||
{
|
||||
if (stream.getId() == index)
|
||||
return stream;
|
||||
}
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_CRIT) << "Cannot find stream index " << index << " in stream map!";
|
||||
throw std::runtime_error("InputMediaFile::getStream, cannot find stream idx");
|
||||
}
|
||||
|
||||
|
||||
} // namespace Transcode
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TRANSCODE_INPUT_MEDIA_FILE
|
||||
#define TRANSCODE_INPUT_MEDIA_FILE
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
|
||||
#include "cover/CoverArt.hpp"
|
||||
|
||||
#include "Stream.hpp"
|
||||
|
||||
|
||||
namespace Transcode
|
||||
{
|
||||
|
||||
|
||||
|
||||
class InputMediaFile
|
||||
{
|
||||
public:
|
||||
|
||||
enum StreamType
|
||||
{
|
||||
StreamAudio,
|
||||
StreamVideo,
|
||||
StreamSubtitle,
|
||||
};
|
||||
|
||||
InputMediaFile(const boost::filesystem::path& p);
|
||||
|
||||
// Accessors
|
||||
boost::filesystem::path getPath(void) const {return _path;}
|
||||
boost::posix_time::time_duration getDuration(void) const {return _duration;}
|
||||
|
||||
// Pictures
|
||||
const std::vector< CoverArt::CoverArt >& getCovers(void) const { return _covers; }
|
||||
|
||||
// Stream handling
|
||||
const Stream& getStream(Stream::Id id) const;
|
||||
std::vector<Stream> getStreams(Stream::Type type) const;
|
||||
const std::map<Stream::Type, Stream::Id>& getBestStreams(void) const {return _bestStreams;}
|
||||
|
||||
private:
|
||||
|
||||
boost::filesystem::path _path;
|
||||
boost::posix_time::time_duration _duration;
|
||||
|
||||
std::vector<Stream> _streams;
|
||||
std::map<Stream::Type, Stream::Id> _bestStreams;
|
||||
|
||||
std::vector< CoverArt::CoverArt > _covers;
|
||||
};
|
||||
|
||||
|
||||
} // namespace Transcode
|
||||
|
||||
#endif // TRANSCODE_INPUT_MEDIA_FILE
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include "av/InputFormatContext.hpp"
|
||||
|
||||
#include "Parameters.hpp"
|
||||
|
||||
namespace Transcode
|
||||
{
|
||||
|
||||
std::string
|
||||
getMimeType(Format format)
|
||||
{
|
||||
//TODO
|
||||
return "";
|
||||
}
|
||||
|
||||
Parameters::Parameters(const InputMediaFile& inputMediaFile,
|
||||
const Format& outputFormat)
|
||||
:
|
||||
_mediaFile(inputMediaFile),
|
||||
_outputFormat(outputFormat)
|
||||
{
|
||||
// By default, select the best stream indexes
|
||||
_inputStreams = _mediaFile.getBestStreams();
|
||||
|
||||
}
|
||||
|
||||
std::size_t
|
||||
Parameters::setBitrate(Stream::Type type, std::size_t bitrate)
|
||||
{
|
||||
// Limit the output bitrate to the input bitrate
|
||||
if (_inputStreams.find(type) != _inputStreams.end())
|
||||
{
|
||||
const Transcode::Stream& stream = _mediaFile.getStream( _inputStreams[type] );
|
||||
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "Stream bitrate = " << stream.getBitrate();
|
||||
|
||||
if (bitrate > stream.getBitrate())
|
||||
{
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_INFO) << "Setting bitrate for stream idx " << _inputStreams[type] << " to input bitrate (" << stream.getBitrate() << ")";
|
||||
_outputBitrate[type] = stream.getBitrate();
|
||||
}
|
||||
else
|
||||
{
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "Setting bitrate for stream idx " << _inputStreams[type] << " to " << bitrate;
|
||||
_outputBitrate[type] = bitrate;
|
||||
}
|
||||
return _outputBitrate[type];
|
||||
}
|
||||
else
|
||||
{
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "Cannot find stream type " << type;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t
|
||||
Parameters::getOutputBitrate(Stream::Type type) const
|
||||
{
|
||||
std::map<Stream::Type, std::size_t>::const_iterator it = _outputBitrate.find(type);
|
||||
|
||||
if (it != _inputStreams.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
LMS_LOG(MOD_TRANSCODE, SEV_DEBUG) << "output bitrate not set for type " << type;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Transcode
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TRANSCODE_PARAMETERS_HPP
|
||||
#define TRANSCODE_PARAMETERS_HPP
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
|
||||
#include "Format.hpp"
|
||||
|
||||
#include "InputMediaFile.hpp"
|
||||
|
||||
namespace Transcode
|
||||
{
|
||||
|
||||
|
||||
class Parameters {
|
||||
|
||||
public:
|
||||
|
||||
Parameters(const InputMediaFile& InputFile, const Format& outputFormat);
|
||||
|
||||
// Modifiers
|
||||
void setOffset(boost::posix_time::time_duration offset) { _offset = offset; } // Set input offset
|
||||
void setOutputFormat(const Format& format) { _outputFormat = format; }
|
||||
std::size_t setBitrate(Stream::Type type, std::size_t bitrate);
|
||||
|
||||
// Manually select an input stream to output
|
||||
// There can be only one stream per each type (video, audio, subtitle)
|
||||
typedef std::map<Stream::Type, Stream::Id> StreamMap;
|
||||
void selectInputStream(Stream::Type type, Stream::Id id) { _inputStreams[type] = id; }
|
||||
const StreamMap& getInputStreams(void) const {return _inputStreams;}
|
||||
|
||||
//Accessors
|
||||
boost::posix_time::time_duration getOffset(void) const {return _offset;}
|
||||
const Format& getOutputFormat(void) const { return _outputFormat; }
|
||||
std::size_t getOutputBitrate(Stream::Type type) const;
|
||||
const InputMediaFile& getInputMediaFile(void) const { return _mediaFile;}
|
||||
InputMediaFile& getInputMediaFile(void) { return _mediaFile;}
|
||||
|
||||
|
||||
private:
|
||||
|
||||
InputMediaFile _mediaFile;
|
||||
|
||||
boost::posix_time::time_duration _offset; // start input offset
|
||||
|
||||
Format _outputFormat; // OGA, OGV, etc.
|
||||
std::map<Stream::Type, std::size_t> _outputBitrate;
|
||||
|
||||
StreamMap _inputStreams;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Transcode
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TRANSCODE_STREAM_HPP
|
||||
#define TRANSCODE_STREAM_HPP
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Transcode
|
||||
{
|
||||
|
||||
class Stream
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
enum Type
|
||||
{
|
||||
Audio,
|
||||
Video,
|
||||
Subtitle,
|
||||
};
|
||||
|
||||
typedef std::size_t Id;
|
||||
|
||||
Stream(Id id, Type type, std::size_t bitrate, const std::string& lang, const std::string& desc)
|
||||
: _id(id), _type(type), _bitrate(bitrate), _language(lang), _desc(desc) {}
|
||||
|
||||
// Accessors
|
||||
Id getId() const { return _id;}
|
||||
Type getType() const { return _type;}
|
||||
std::size_t getBitrate() const { return _bitrate;}
|
||||
const std::string& getLanguage() const { return _language;}
|
||||
const std::string& getDesc() const { return _desc;}
|
||||
|
||||
private:
|
||||
|
||||
Id _id;
|
||||
Type _type;
|
||||
std::size_t _bitrate;
|
||||
std::string _language;
|
||||
std::string _desc;
|
||||
|
||||
};
|
||||
|
||||
|
||||
} // namespace Transcode
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <Wt/WBootstrapTheme>
|
||||
#include "auth/LmsAuth.hpp"
|
||||
|
||||
#include "LmsHome.hpp"
|
||||
#include "settings/SettingsFirstConnectionFormView.hpp"
|
||||
|
||||
#include "LmsApplication.hpp"
|
||||
|
||||
namespace skeletons {
|
||||
extern const char *AuthStrings_xml1;
|
||||
}
|
||||
|
||||
|
||||
namespace UserInterface {
|
||||
|
||||
Wt::WApplication*
|
||||
LmsApplication::create(const Wt::WEnvironment& env, boost::filesystem::path dbPath)
|
||||
{
|
||||
/*
|
||||
* You could read information from the environment to decide whether
|
||||
* the user has permission to start a new application
|
||||
*/
|
||||
return new LmsApplication(env, dbPath);
|
||||
}
|
||||
|
||||
/*
|
||||
* The env argument contains information about the new session, and
|
||||
* the initial request. It must be passed to the Wt::WApplication
|
||||
* constructor so it is typically also an argument for your custom
|
||||
* application constructor.
|
||||
*/
|
||||
LmsApplication::LmsApplication(const Wt::WEnvironment& env, boost::filesystem::path dbPath)
|
||||
: Wt::WApplication(env),
|
||||
_sessionData(dbPath),
|
||||
_home(nullptr)
|
||||
{
|
||||
|
||||
Wt::WBootstrapTheme *bootstrapTheme = new Wt::WBootstrapTheme(this);
|
||||
bootstrapTheme->setVersion(Wt::WBootstrapTheme::Version3);
|
||||
bootstrapTheme->setResponsive(true);
|
||||
setTheme(bootstrapTheme);
|
||||
|
||||
// Add a resource bundle
|
||||
messageResourceBundle().use(appRoot() + "templates");
|
||||
|
||||
setTitle("LMS"); // application title
|
||||
|
||||
bool firstConnection;
|
||||
{
|
||||
Wt::Dbo::Transaction transaction(_sessionData.getDatabaseHandler().getSession());
|
||||
|
||||
firstConnection = (Database::User::getAll(_sessionData.getDatabaseHandler().getSession()).size() == 0);
|
||||
}
|
||||
|
||||
// If here is no account in the database, launch the first connection wizard
|
||||
if (firstConnection)
|
||||
{
|
||||
// Hack, use the auth widget builtin strings
|
||||
builtinLocalizedStrings().useBuiltin(skeletons::AuthStrings_xml1);
|
||||
|
||||
root()->addWidget( new Settings::FirstConnectionFormView(_sessionData));
|
||||
}
|
||||
else
|
||||
{
|
||||
_sessionData.getDatabaseHandler().getLogin().changed().connect(this, &LmsApplication::handleAuthEvent);
|
||||
|
||||
LmsAuth *authWidget = new LmsAuth(_sessionData.getDatabaseHandler());
|
||||
|
||||
authWidget->model()->addPasswordAuth(&Database::Handler::getPasswordService());
|
||||
authWidget->setRegistrationEnabled(false);
|
||||
|
||||
authWidget->processEnvironment();
|
||||
|
||||
root()->addWidget(authWidget);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void
|
||||
LmsApplication::handleAuthEvent(void)
|
||||
{
|
||||
if (_sessionData.getDatabaseHandler().getLogin().loggedIn())
|
||||
{
|
||||
if (_home == nullptr) {
|
||||
_home = new LmsHome(_sessionData, root() );
|
||||
}
|
||||
else
|
||||
std::cerr << "Already logged in??" << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "user log out" << std::endl;
|
||||
if (_home != nullptr) {
|
||||
delete _home;
|
||||
_home = nullptr;
|
||||
|
||||
// Hack: quit/redirect in order to avoid 'signal not exposed' problems
|
||||
// TODO, investigate/remove?
|
||||
quit();
|
||||
redirect("/");
|
||||
|
||||
}
|
||||
else
|
||||
std::cerr << "Already logged out??" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace UserInterface
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef LMS_APPLICATION_HPP
|
||||
#define LMS_APPLICATION_HPP
|
||||
|
||||
#include <Wt/WApplication>
|
||||
|
||||
#include "common/SessionData.hpp"
|
||||
|
||||
#include "LmsHome.hpp"
|
||||
|
||||
namespace UserInterface {
|
||||
|
||||
|
||||
class LmsApplication : public Wt::WApplication
|
||||
{
|
||||
public:
|
||||
|
||||
static Wt::WApplication *create(const Wt::WEnvironment& env, boost::filesystem::path dbPath);
|
||||
|
||||
LmsApplication(const Wt::WEnvironment& env, boost::filesystem::path dbPath);
|
||||
|
||||
private:
|
||||
|
||||
void handleAuthEvent(void);
|
||||
|
||||
SessionData _sessionData;
|
||||
|
||||
LmsHome* _home;
|
||||
|
||||
};
|
||||
|
||||
|
||||
} // namespace UserInterface
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <Wt/WStackedWidget>
|
||||
#include <Wt/WMenu>
|
||||
#include <Wt/WNavigationBar>
|
||||
#include <Wt/WPopupMenu>
|
||||
#include <Wt/WPopupMenuItem>
|
||||
#include <Wt/Auth/Identity>
|
||||
|
||||
#include "settings/Settings.hpp"
|
||||
|
||||
#include "LmsHome.hpp"
|
||||
|
||||
namespace UserInterface {
|
||||
|
||||
LmsHome::LmsHome(SessionData& sessionData, Wt::WContainerWidget* parent)
|
||||
: Wt::WContainerWidget(parent),
|
||||
_sessionData(sessionData)
|
||||
{
|
||||
const Wt::Auth::User& user = sessionData.getDatabaseHandler().getLogin().user();
|
||||
|
||||
// Create a navigation bar with a link to a web page.
|
||||
Wt::WNavigationBar *navigation = new Wt::WNavigationBar(this);
|
||||
navigation->setTitle("LMS");
|
||||
navigation->setResponsive(true);
|
||||
navigation->addStyleClass("main-nav");
|
||||
|
||||
Wt::WStackedWidget *contentsStack = new Wt::WStackedWidget(this);
|
||||
|
||||
// Setup a Left-aligned menu.
|
||||
Wt::WMenu *leftMenu = new Wt::WMenu(contentsStack);
|
||||
navigation->addMenu(leftMenu);
|
||||
|
||||
_audioWidget = new AudioWidget(_sessionData);
|
||||
_videoWidget = new VideoWidget(_sessionData);
|
||||
|
||||
leftMenu->addItem("Audio", _audioWidget);
|
||||
leftMenu->addItem("Video", _videoWidget);
|
||||
leftMenu->addItem("Settings", new Settings::Settings(_sessionData));
|
||||
|
||||
// Setup a Right-aligned menu.
|
||||
Wt::WMenu *rightMenu = new Wt::WMenu();
|
||||
|
||||
navigation->addMenu(rightMenu, Wt::AlignRight);
|
||||
|
||||
Wt::WPopupMenu *popup = new Wt::WPopupMenu();
|
||||
popup->addItem("Logout");
|
||||
|
||||
popup->itemSelected().connect(this, &LmsHome::handleUserMenuSelected);
|
||||
|
||||
Wt::WMenuItem *item = new Wt::WMenuItem( user.identity(Wt::Auth::Identity::LoginName) );
|
||||
item->setMenu(popup);
|
||||
rightMenu->addItem(item);
|
||||
|
||||
// Add a Search control.
|
||||
_searchEdit = new Wt::WLineEdit();
|
||||
_searchEdit->setEmptyText("Search...");
|
||||
|
||||
_searchEdit->enterPressed().connect(this, &LmsHome::handleSearch);
|
||||
|
||||
navigation->addSearch(_searchEdit, Wt::AlignLeft);
|
||||
|
||||
addWidget(contentsStack);
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
LmsHome::handleUserMenuSelected( Wt::WMenuItem* item)
|
||||
{
|
||||
if (item && item->text() == "Logout") {
|
||||
_sessionData.getDatabaseHandler().getLogin().logout();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LmsHome::handleSearch(void)
|
||||
{
|
||||
// TODO Check currently selected menu item and search it
|
||||
_audioWidget->search( _searchEdit->text().toUTF8() );
|
||||
}
|
||||
|
||||
} // namespace UserInterface
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef LMS_HOME_HPP
|
||||
#define LMS_HOME_HPP
|
||||
|
||||
#include <Wt/WContainerWidget>
|
||||
#include <Wt/WLineEdit>
|
||||
|
||||
#include "common/SessionData.hpp"
|
||||
|
||||
#include "audio/AudioWidget.hpp"
|
||||
#include "video/VideoWidget.hpp"
|
||||
|
||||
namespace UserInterface {
|
||||
|
||||
class LmsHome : public Wt::WContainerWidget
|
||||
{
|
||||
public:
|
||||
|
||||
LmsHome(SessionData& sessionData, Wt::WContainerWidget* parent = 0);
|
||||
|
||||
private:
|
||||
|
||||
void handleSearch(void);
|
||||
void handleUserMenuSelected( Wt::WMenuItem* item );
|
||||
|
||||
SessionData& _sessionData;
|
||||
|
||||
Wt::WLineEdit* _searchEdit;
|
||||
AudioWidget* _audioWidget;
|
||||
VideoWidget* _videoWidget;
|
||||
};
|
||||
|
||||
} // namespace UserInterface
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,313 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<messages xmlns:if="Wt.WTemplate.conditions">
|
||||
<!--FORMS message blocks-->
|
||||
|
||||
<message id="firstConnectionForm-template">
|
||||
<legend>${title}</legend>
|
||||
<div class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:name}">
|
||||
Name
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${name}
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${name-info}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:email}">
|
||||
e-Mail
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${email}
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${email-info}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:password}">
|
||||
Password
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${password}
|
||||
</div>
|
||||
${password-info}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:password-confirm}">
|
||||
Confirm password
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${password-confirm}
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${password-confirm-info}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
${save-button}
|
||||
</div>
|
||||
</div>
|
||||
${apply-info}
|
||||
</div>
|
||||
</message>
|
||||
|
||||
|
||||
<message id="userForm-template">
|
||||
<legend>${title}</legend>
|
||||
<div class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:name}">
|
||||
Name
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${name}
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${name-info}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:email}">
|
||||
e-Mail
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${email}
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${email-info}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:password}">
|
||||
Password
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${password}
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${password-info}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:password-confirm}">
|
||||
Confirm password
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${password-confirm}
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${password-confirm-info}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<legend>${access}</legend>
|
||||
<div class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:admin}">
|
||||
Admin
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${admin}
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${admin-info}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:audio-bitrate-limit}">
|
||||
Audio Bitrate Limit
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
<div class="input-group">
|
||||
${audio-bitrate-limit}
|
||||
<span class="input-group-addon">kbps</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${audio-bitrate-limit-info}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:video-bitrate-limit}">
|
||||
Video Bitrate Limit
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
<div class="input-group">
|
||||
${video-bitrate-limit}
|
||||
<span class="input-group-addon">kbps</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${video-bitrate-limit-info}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
${save-button} ${cancel-button}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</message>
|
||||
|
||||
<message id="userAccountForm-template">
|
||||
<legend>${title}</legend>
|
||||
<div class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:name}">
|
||||
Name
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${name}
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${name-info}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:email}">
|
||||
e-Mail
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${email}
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${email-info}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:password}">
|
||||
Password
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${password}
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${password-info}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:password-confirm}">
|
||||
Confirm password
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${password-confirm}
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${password-confirm-info}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
${save-button} ${cancel-button}
|
||||
</div>
|
||||
</div>
|
||||
${apply-info}
|
||||
</div>
|
||||
</message>
|
||||
|
||||
<message id="audioForm-template">
|
||||
<legend>${title}</legend>
|
||||
<div class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:bitrate}">
|
||||
Audio bitrate
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
<div class="input-group">
|
||||
${bitrate}
|
||||
<span class="input-group-addon">kbps</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${bitrate-info}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
${save-button} ${cancel-button}
|
||||
</div>
|
||||
</div>
|
||||
${apply-info}
|
||||
</div>
|
||||
</message>
|
||||
|
||||
<message id="mediaDirectoryForm-template">
|
||||
<legend>${title}</legend>
|
||||
<div class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:path}">
|
||||
Path
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${path}
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${path-info}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:type}">
|
||||
Type
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${type}
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${type-info}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
${save-button} ${cancel-button}
|
||||
</div>
|
||||
</div>
|
||||
${apply-info}
|
||||
</div>
|
||||
</message>
|
||||
|
||||
<message id="databaseForm-template">
|
||||
<legend>${title}</legend>
|
||||
<div class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:update-period}">
|
||||
Update period
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${update-period}
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${update-period-info}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="${id:update-start-time}">
|
||||
Update start time
|
||||
</label>
|
||||
<div class="col-sm-5">
|
||||
${update-start-time}
|
||||
</div>
|
||||
<div class="help-block col-sm-5">
|
||||
${update-start-time-info}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
${apply-button} ${discard-button} ${immediate-scan-button}
|
||||
</div>
|
||||
</div>
|
||||
${apply-info}
|
||||
</div>
|
||||
</message>
|
||||
</messages>
|
||||
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include <Wt/WTable> // TODO
|
||||
#include <Wt/WBreak> // TODO
|
||||
|
||||
#include "AudioDatabaseWidget.hpp"
|
||||
|
||||
#include "TableFilterWidget.hpp"
|
||||
#include "SearchFilterWidget.hpp"
|
||||
#include "TrackWidget.hpp"
|
||||
|
||||
namespace UserInterface {
|
||||
|
||||
AudioDatabaseWidget::AudioDatabaseWidget( Database::Handler& db, Wt::WContainerWidget *parent)
|
||||
: Wt::WContainerWidget(parent),
|
||||
_refreshingFilters(false)
|
||||
{
|
||||
std::size_t idFilter (0);
|
||||
{
|
||||
SearchFilterWidget* search = new SearchFilterWidget(this);
|
||||
_filters.push_back( search );
|
||||
search->update().connect( boost::bind(&AudioDatabaseWidget::handleFilterUpdated, this, idFilter++) );
|
||||
}
|
||||
|
||||
Wt::WTable* table = new Wt::WTable(this);
|
||||
|
||||
{
|
||||
TableFilterWidget* filterTable = new TableFilterWidget(db, "genre", "name", table->elementAt(0,0));
|
||||
_filters.push_back( filterTable );
|
||||
filterTable->update().connect( boost::bind(&AudioDatabaseWidget::handleFilterUpdated, this, idFilter++) );
|
||||
}
|
||||
{
|
||||
TableFilterWidget* filterTable = new TableFilterWidget(db, "artist", "name", table->elementAt(0,1));
|
||||
_filters.push_back( filterTable );
|
||||
filterTable->update().connect( boost::bind(&AudioDatabaseWidget::handleFilterUpdated, this, idFilter++) );
|
||||
}
|
||||
{
|
||||
TableFilterWidget* filterTable = new TableFilterWidget(db, "release", "name", table->elementAt(0,2));
|
||||
_filters.push_back( filterTable );
|
||||
filterTable->update().connect( boost::bind(&AudioDatabaseWidget::handleFilterUpdated, this, idFilter++) );
|
||||
}
|
||||
|
||||
{
|
||||
TrackWidget* track = new TrackWidget(db, this);
|
||||
_filters.push_back( track );
|
||||
track->trackSelected().connect(this, &AudioDatabaseWidget::handleTrackSelected);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
AudioDatabaseWidget::search(const std::string& text)
|
||||
{
|
||||
SearchFilterWidget* searchWidget ( dynamic_cast<SearchFilterWidget*>(_filters.front() ) );
|
||||
|
||||
searchWidget->setText(text);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AudioDatabaseWidget::handleTrackSelected(boost::filesystem::path p)
|
||||
{
|
||||
_trackSelected.emit(p);
|
||||
}
|
||||
|
||||
void
|
||||
AudioDatabaseWidget::handleFilterUpdated(std::size_t idFilterUpdated)
|
||||
{
|
||||
// TODO disconnect from event!
|
||||
if (_refreshingFilters)
|
||||
return;
|
||||
|
||||
_refreshingFilters = true;
|
||||
|
||||
FilterWidget::Constraint currentConstraint;
|
||||
|
||||
currentConstraint.where.And( WhereClause( "track.artist_id = artist.id and track.release_id = release.id and track_genre.track_id = track.id and genre.id = track_genre.genre_id"));
|
||||
|
||||
for (std::size_t idFilter = 0; idFilter < _filters.size(); ++idFilter)
|
||||
{
|
||||
FilterWidget* filter = _filters.at(idFilter);
|
||||
|
||||
// Apply contraints created by previous filters
|
||||
if (idFilter > idFilterUpdated) {
|
||||
filter->refresh(currentConstraint);
|
||||
}
|
||||
|
||||
// Get constraints generated by this filter
|
||||
// (Note: adding accross successive calls)
|
||||
filter->getConstraint(currentConstraint);
|
||||
}
|
||||
|
||||
_refreshingFilters = false;
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
AudioDatabaseWidget::selectNextTrack(void)
|
||||
{
|
||||
TrackWidget* trackWidget ( dynamic_cast<TrackWidget*>(_filters.back() ) );
|
||||
|
||||
trackWidget->selectNextTrack();
|
||||
}
|
||||
|
||||
} // namespace UserInterface
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef AUDIO_DB_WIDGET_HPP
|
||||
#define AUDIO_DB_WIDGET_HPP
|
||||
|
||||
#include <boost/filesystem/path.hpp>
|
||||
|
||||
#include <Wt/WContainerWidget>
|
||||
#include <Wt/WSignal>
|
||||
|
||||
#include "common/SessionData.hpp"
|
||||
|
||||
#include "FilterWidget.hpp"
|
||||
|
||||
namespace UserInterface {
|
||||
|
||||
class AudioDatabaseWidget : public Wt::WContainerWidget
|
||||
{
|
||||
public:
|
||||
AudioDatabaseWidget( Database::Handler& db, Wt::WContainerWidget *parent = 0);
|
||||
|
||||
void search(const std::string& text);
|
||||
|
||||
void selectNextTrack(void); // Will later emit the next selected track
|
||||
|
||||
// Signals
|
||||
Wt::Signal< boost::filesystem::path >& trackSelected() { return _trackSelected; }
|
||||
|
||||
private:
|
||||
|
||||
Wt::Signal< boost::filesystem::path > _trackSelected;
|
||||
|
||||
void handleTrackSelected(boost::filesystem::path p);
|
||||
void handleFilterUpdated(std::size_t idFilter);
|
||||
|
||||
std::vector<FilterWidget*> _filters; // Free Search, Genre, Artist, Release, etc.
|
||||
|
||||
bool _refreshingFilters;
|
||||
|
||||
};
|
||||
|
||||
} // namespace UserInterface
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include <Wt/WMediaPlayer>
|
||||
#include <Wt/WProgressBar>
|
||||
|
||||
#include "AudioMediaPlayerWidget.hpp"
|
||||
|
||||
namespace UserInterface {
|
||||
|
||||
AudioMediaPlayerWidget::AudioMediaPlayerWidget( Wt::WContainerWidget *parent)
|
||||
: Wt::WContainerWidget(parent),
|
||||
_mediaResource(nullptr)
|
||||
{
|
||||
_mediaPlayer = new Wt::WMediaPlayer( Wt::WMediaPlayer::Audio, this );
|
||||
// _mediaPlayer->setAlternativeContent (new Wt::WText("You don't have HTML5 audio support!"));
|
||||
// _mediaPlayer->setOptions( Wt::WMediaPlayer::Autoplay );
|
||||
_mediaPlayer->addSource( Wt::WMediaPlayer::OGA, "" );
|
||||
|
||||
_mediaPlayer->ended().connect(this, &AudioMediaPlayerWidget::handleTrackEnded);
|
||||
|
||||
{
|
||||
Wt::WContainerWidget *container = new Wt::WContainerWidget(this);
|
||||
|
||||
_prevBtn = new Wt::WPushButton("<<", container );
|
||||
_playBtn = new Wt::WPushButton("Play", container );
|
||||
_pauseBtn = new Wt::WPushButton("Pause", container );
|
||||
_nextBtn = new Wt::WPushButton(">>", container );
|
||||
|
||||
_curTime = new Wt::WText(container);
|
||||
_timeSlider = new Wt::WSlider( container );
|
||||
_duration = new Wt::WText(container);
|
||||
|
||||
_volumeSlider = new Wt::WSlider( container );
|
||||
_volumeSlider->setRange(0,100);
|
||||
_volumeSlider->setValue(_mediaPlayer->volume() * 100);
|
||||
|
||||
_mediaPlayer->setControlsWidget( container );
|
||||
_mediaPlayer->setButton(Wt::WMediaPlayer::Play, _playBtn);
|
||||
_mediaPlayer->setButton(Wt::WMediaPlayer::Pause, _pauseBtn);
|
||||
|
||||
_mediaPlayer->setText( Wt::WMediaPlayer::CurrentTime, _curTime);
|
||||
_mediaPlayer->setText( Wt::WMediaPlayer::Duration, _duration);
|
||||
|
||||
_mediaPlayer->timeUpdated().connect(this, &AudioMediaPlayerWidget::handleTimeUpdated);
|
||||
}
|
||||
|
||||
_timeSlider->valueChanged().connect(this, &AudioMediaPlayerWidget::handlePlayOffset);
|
||||
_timeSlider->sliderMoved().connect(this, &AudioMediaPlayerWidget::handleSliderMoved);
|
||||
_timeSlider->setDisabled(true);
|
||||
|
||||
_volumeSlider->sliderMoved().connect(this, &AudioMediaPlayerWidget::handleVolumeSliderMoved);
|
||||
|
||||
_nextBtn->clicked().connect(this, &AudioMediaPlayerWidget::handlePlayNext);
|
||||
_prevBtn->clicked().connect(this, &AudioMediaPlayerWidget::handlePlayPrev);
|
||||
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AudioMediaPlayerWidget::loadPlayer(void)
|
||||
{
|
||||
_mediaPlayer->clearSources();
|
||||
|
||||
_mediaInternalLink.setResource( nullptr );
|
||||
if (_mediaResource)
|
||||
delete _mediaResource;
|
||||
|
||||
assert( _currentParameters );
|
||||
_mediaResource = new AvConvTranscodeStreamResource( *_currentParameters, this );
|
||||
_mediaInternalLink.setResource( _mediaResource );
|
||||
|
||||
_mediaPlayer->addSource( Wt::WMediaPlayer::OGA, _mediaInternalLink );
|
||||
}
|
||||
|
||||
void
|
||||
AudioMediaPlayerWidget::load(const Transcode::Parameters& parameters)
|
||||
{
|
||||
_timeSlider->setDisabled(false);
|
||||
|
||||
_currentParameters = std::make_shared<Transcode::Parameters>( parameters );
|
||||
|
||||
loadPlayer();
|
||||
|
||||
_timeSlider->setRange(0, parameters.getInputMediaFile().getDuration().total_seconds() );
|
||||
_timeSlider->setValue(0);
|
||||
|
||||
_duration->setText( boost::posix_time::to_simple_string( parameters.getInputMediaFile().getDuration() ));
|
||||
|
||||
_mediaPlayer->play();
|
||||
}
|
||||
|
||||
void
|
||||
AudioMediaPlayerWidget::handlePlayOffset(int offsetSecs)
|
||||
{
|
||||
if (!_currentParameters)
|
||||
return;
|
||||
|
||||
_currentParameters->setOffset( boost::posix_time::seconds(offsetSecs) );
|
||||
|
||||
loadPlayer();
|
||||
|
||||
_mediaPlayer->play();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AudioMediaPlayerWidget::handlePlayNext(void)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
void
|
||||
AudioMediaPlayerWidget::handlePlayPrev(void)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AudioMediaPlayerWidget::handleTrackEnded(void)
|
||||
{
|
||||
_playbackEnded.emit();
|
||||
}
|
||||
|
||||
void
|
||||
AudioMediaPlayerWidget::handleValueChanged(double value)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
void
|
||||
AudioMediaPlayerWidget::handleSliderMoved(int value)
|
||||
{
|
||||
;
|
||||
}
|
||||
|
||||
void
|
||||
AudioMediaPlayerWidget::handleTimeUpdated(void)
|
||||
{
|
||||
if (!_currentParameters)
|
||||
return;
|
||||
|
||||
boost::posix_time::time_duration currentTime ( boost::posix_time::seconds( _mediaPlayer->currentTime() + _currentParameters->getOffset().total_seconds()));
|
||||
|
||||
_timeSlider->setValue( currentTime.total_seconds() );
|
||||
_curTime->setText( boost::posix_time::to_simple_string( currentTime) );
|
||||
}
|
||||
|
||||
void
|
||||
AudioMediaPlayerWidget::handleVolumeSliderMoved(int value)
|
||||
{
|
||||
_mediaPlayer->setVolume( value / 100. );
|
||||
}
|
||||
|
||||
} // namespace UserInterface
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __MEDIA_PLAYER_WIDGET_HPP
|
||||
#define __MEDIA_PLAYER_WIDGET_HPP
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <Wt/WSlider>
|
||||
#include <Wt/WPushButton>
|
||||
#include <Wt/WContainerWidget>
|
||||
#include <Wt/WMediaPlayer>
|
||||
#include <Wt/WLink>
|
||||
#include <Wt/WText>
|
||||
|
||||
#include "transcode/Parameters.hpp"
|
||||
#include "resource/AvConvTranscodeStreamResource.hpp"
|
||||
|
||||
namespace UserInterface {
|
||||
|
||||
class AudioMediaPlayerWidget : public Wt::WContainerWidget
|
||||
{
|
||||
public:
|
||||
|
||||
AudioMediaPlayerWidget( Wt::WContainerWidget *parent = 0);
|
||||
|
||||
void load(const Transcode::Parameters& parameters);
|
||||
|
||||
// Signal slot Next
|
||||
Wt::Signal<void>& playbackEnded() {return _playbackEnded;}
|
||||
// Signal Slot Previous
|
||||
// Signal slot Ended
|
||||
|
||||
private:
|
||||
|
||||
void handlePlayOffset(int offsetSecs);
|
||||
void handlePlayNext(void);
|
||||
void handlePlayPrev(void);
|
||||
void handleTrackEnded(void);
|
||||
|
||||
void handleValueChanged(double);
|
||||
void handleTimeUpdated(void);
|
||||
void handleSliderMoved(int value);
|
||||
|
||||
void handleVolumeSliderMoved(int value);
|
||||
|
||||
void loadPlayer(void);
|
||||
|
||||
// Signals
|
||||
Wt::Signal<void> _playbackEnded;
|
||||
|
||||
// Core
|
||||
Wt::WMediaPlayer* _mediaPlayer;
|
||||
AvConvTranscodeStreamResource* _mediaResource;
|
||||
Wt::WLink _mediaInternalLink;
|
||||
|
||||
// Controls
|
||||
std::shared_ptr<Transcode::Parameters> _currentParameters;
|
||||
Wt::WPushButton* _playBtn;
|
||||
Wt::WPushButton* _pauseBtn;
|
||||
Wt::WPushButton* _nextBtn;
|
||||
Wt::WPushButton* _prevBtn;
|
||||
Wt::WSlider* _timeSlider;
|
||||
Wt::WSlider* _volumeSlider;
|
||||
Wt::WText* _curTime;
|
||||
Wt::WText* _duration;
|
||||
|
||||
};
|
||||
|
||||
} // namespace UserInterface
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <Wt/WBreak>
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include "AudioWidget.hpp"
|
||||
|
||||
namespace UserInterface {
|
||||
|
||||
AudioWidget::AudioWidget(SessionData& sessionData, Wt::WContainerWidget* parent)
|
||||
: Wt::WContainerWidget(parent),
|
||||
_db(sessionData.getDatabaseHandler()),
|
||||
_audioDbWidget(nullptr),
|
||||
_mediaPlayer(nullptr),
|
||||
_imgResource(nullptr),
|
||||
_img(nullptr)
|
||||
{
|
||||
_audioDbWidget = new AudioDatabaseWidget(sessionData.getDatabaseHandler(), this);
|
||||
|
||||
_audioDbWidget->trackSelected().connect(this, &AudioWidget::playTrack);
|
||||
|
||||
_mediaPlayer = new AudioMediaPlayerWidget(this);
|
||||
_mediaPlayer->playbackEnded().connect(this, &AudioWidget::handleTrackEnded);
|
||||
this->addWidget(new Wt::WBreak());
|
||||
|
||||
// Image
|
||||
_imgResource = new Wt::WMemoryResource(this);
|
||||
_imgLink.setResource( _imgResource);
|
||||
_img = new Wt::WImage(_imgLink, this);
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
AudioWidget::search(const std::string& searchText)
|
||||
{
|
||||
_audioDbWidget->search(searchText);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AudioWidget::playTrack(boost::filesystem::path p)
|
||||
{
|
||||
LMS_LOG(MOD_UI, SEV_DEBUG) << "play track '" << p << "'";
|
||||
try {
|
||||
|
||||
std::size_t bitrate = 0;
|
||||
|
||||
// Get user preferences
|
||||
{
|
||||
Wt::Dbo::Transaction transaction(_db.getSession());
|
||||
Database::User::pointer user = _db.getCurrentUser();
|
||||
if (user)
|
||||
bitrate = user->getAudioBitrate();
|
||||
else
|
||||
{
|
||||
LMS_LOG(MOD_UI, SEV_ERROR) << "Can't play video: user does not exists!";
|
||||
return; // TODO logout?
|
||||
}
|
||||
}
|
||||
|
||||
Transcode::InputMediaFile inputFile(p);
|
||||
|
||||
Transcode::Parameters parameters(inputFile, Transcode::Format::get(Transcode::Format::OGA));
|
||||
|
||||
parameters.setBitrate(Transcode::Stream::Audio, bitrate);
|
||||
|
||||
_mediaPlayer->load( parameters );
|
||||
|
||||
// Refresh cover
|
||||
{
|
||||
std::vector<CoverArt::CoverArt> covers = inputFile.getCovers();
|
||||
|
||||
if (!covers.empty())
|
||||
{
|
||||
LMS_LOG(MOD_UI, SEV_DEBUG) << "Cover found!";
|
||||
if (!covers.front().scale(256)) // TODO
|
||||
LMS_LOG(MOD_UI, SEV_ERROR) << "Cannot resize!";
|
||||
|
||||
//_imgResource->setMimeType(covers.front().getMimeType());
|
||||
_imgResource->setData(covers.front().getData());
|
||||
}
|
||||
else {
|
||||
LMS_LOG(MOD_UI, SEV_DEBUG) << "No cover found!";
|
||||
_imgResource->setData( std::vector<unsigned char>());
|
||||
}
|
||||
|
||||
_imgResource->setChanged();
|
||||
}
|
||||
}
|
||||
catch( std::exception &e)
|
||||
{
|
||||
LMS_LOG(MOD_UI, SEV_ERROR) << "Caught exception while loading '" << p << "': " << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AudioWidget::handleTrackEnded(void)
|
||||
{
|
||||
LMS_LOG(MOD_UI, SEV_DEBUG) << "Track playback ended!";
|
||||
_audioDbWidget->selectNextTrack();
|
||||
}
|
||||
|
||||
} // namespace UserInterface
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef AUDIO_WIDGET_HPP
|
||||
#define AUDIO_WIDGET_HPP
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <Wt/WLink>
|
||||
#include <Wt/WImage>
|
||||
#include <Wt/WContainerWidget>
|
||||
#include <Wt/WMemoryResource>
|
||||
|
||||
#include "audio/AudioMediaPlayerWidget.hpp"
|
||||
#include "audio/AudioDatabaseWidget.hpp"
|
||||
|
||||
namespace UserInterface {
|
||||
|
||||
class AudioWidget : public Wt::WContainerWidget
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
AudioWidget(SessionData& sessionData, Wt::WContainerWidget* parent = 0);
|
||||
|
||||
void search(const std::string& searchText);
|
||||
|
||||
private:
|
||||
|
||||
void playTrack(boost::filesystem::path p);
|
||||
|
||||
void handleTrackEnded(void);
|
||||
|
||||
Database::Handler& _db;
|
||||
|
||||
AudioDatabaseWidget* _audioDbWidget;
|
||||
|
||||
AudioMediaPlayerWidget* _mediaPlayer;
|
||||
|
||||
// Image
|
||||
Wt::WMemoryResource *_imgResource;
|
||||
Wt::WLink _imgLink;
|
||||
Wt::WImage *_img;
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
} // namespace UserInterface
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef FILTER_WIDGET_HPP
|
||||
#define FILTER_WIDGET_HPP
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
#include <string>
|
||||
#include <list>
|
||||
|
||||
#include <Wt/WSignal>
|
||||
#include <Wt/WContainerWidget>
|
||||
|
||||
#include "database/SqlQuery.hpp"
|
||||
|
||||
namespace UserInterface {
|
||||
|
||||
class FilterWidget : public Wt::WContainerWidget {
|
||||
|
||||
public:
|
||||
|
||||
struct Constraint {
|
||||
WhereClause where; // WHERE SQL clause
|
||||
};
|
||||
|
||||
FilterWidget(Wt::WContainerWidget* parent = 0) : Wt::WContainerWidget(parent) {}
|
||||
virtual ~FilterWidget() {}
|
||||
|
||||
// Refresh filter Widget using constraints created by parent filters
|
||||
virtual void refresh(const Constraint& constraint) = 0;
|
||||
|
||||
// Update constraints for child filters
|
||||
virtual void getConstraint(Constraint& constraint) = 0;
|
||||
|
||||
// Emitted when a constraint has changed
|
||||
Wt::Signal<void>& update() { return _update; };
|
||||
|
||||
protected:
|
||||
|
||||
void emitUpdate() { _update.emit(); }
|
||||
|
||||
private:
|
||||
|
||||
Wt::Signal<void> _update;
|
||||
};
|
||||
|
||||
} // namespace UserInterface
|
||||
|
||||
#endif
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user