From dda0f3831512f7db881519cbe65fbbe090dc4f8d Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 9 Apr 2016 16:23:20 +0200 Subject: [PATCH 001/195] [UI] Changed internal template names used by settings views --- approot/templates.xml | 12 ++++++------ src/ui/settings/SettingsAccountFormView.cpp | 2 +- src/ui/settings/SettingsAudioFormView.cpp | 2 +- src/ui/settings/SettingsDatabaseFormView.cpp | 2 +- src/ui/settings/SettingsFirstConnectionFormView.cpp | 2 +- src/ui/settings/SettingsMediaDirectoryFormView.cpp | 2 +- src/ui/settings/SettingsUserFormView.cpp | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/approot/templates.xml b/approot/templates.xml index 5be05553..82790cf2 100644 --- a/approot/templates.xml +++ b/approot/templates.xml @@ -14,7 +14,7 @@ - + ${title}
@@ -69,7 +69,7 @@ - + ${title}
@@ -169,7 +169,7 @@ - + ${title}
@@ -225,7 +225,7 @@
- + ${title}
@@ -262,7 +262,7 @@
- + ${title}
@@ -296,7 +296,7 @@
- + ${title}
diff --git a/src/ui/settings/SettingsAccountFormView.cpp b/src/ui/settings/SettingsAccountFormView.cpp index 4747bc37..c7f265e8 100644 --- a/src/ui/settings/SettingsAccountFormView.cpp +++ b/src/ui/settings/SettingsAccountFormView.cpp @@ -192,7 +192,7 @@ AccountFormView::AccountFormView(std::string userId, Wt::WContainerWidget *paren _model = new AccountFormModel(userId, this); - setTemplateText(tr("userAccountForm-template")); + setTemplateText(tr("settings-account")); addFunction("id", &WTemplate::Functions::id); addFunction("block", &WTemplate::Functions::id); diff --git a/src/ui/settings/SettingsAudioFormView.cpp b/src/ui/settings/SettingsAudioFormView.cpp index 93475c48..e86e171d 100644 --- a/src/ui/settings/SettingsAudioFormView.cpp +++ b/src/ui/settings/SettingsAudioFormView.cpp @@ -175,7 +175,7 @@ AudioFormView::AudioFormView(Wt::WContainerWidget *parent) _model = new AudioFormModel(this); - setTemplateText(tr("audioForm-template")); + setTemplateText(tr("settings-audio")); addFunction("id", &WTemplate::Functions::id); addFunction("block", &WTemplate::Functions::id); diff --git a/src/ui/settings/SettingsDatabaseFormView.cpp b/src/ui/settings/SettingsDatabaseFormView.cpp index d0c8cb16..f9ad0ccf 100644 --- a/src/ui/settings/SettingsDatabaseFormView.cpp +++ b/src/ui/settings/SettingsDatabaseFormView.cpp @@ -297,7 +297,7 @@ DatabaseFormView::DatabaseFormView(Wt::WContainerWidget *parent) { _model = new DatabaseFormModel(this); - setTemplateText(tr("databaseForm-template")); + setTemplateText(tr("settings-database")); addFunction("id", &WTemplate::Functions::id); addFunction("block", &WTemplate::Functions::id); diff --git a/src/ui/settings/SettingsFirstConnectionFormView.cpp b/src/ui/settings/SettingsFirstConnectionFormView.cpp index fff6a398..7f0628a0 100644 --- a/src/ui/settings/SettingsFirstConnectionFormView.cpp +++ b/src/ui/settings/SettingsFirstConnectionFormView.cpp @@ -152,7 +152,7 @@ FirstConnectionFormView::FirstConnectionFormView(Wt::WContainerWidget *parent) _model = new FirstConnectionFormModel(this); - setTemplateText(tr("firstConnectionForm-template")); + setTemplateText(tr("settings-first-connection")); addFunction("id", &WTemplate::Functions::id); addFunction("block", &WTemplate::Functions::id); diff --git a/src/ui/settings/SettingsMediaDirectoryFormView.cpp b/src/ui/settings/SettingsMediaDirectoryFormView.cpp index 650f75f3..8d9c485a 100644 --- a/src/ui/settings/SettingsMediaDirectoryFormView.cpp +++ b/src/ui/settings/SettingsMediaDirectoryFormView.cpp @@ -109,7 +109,7 @@ MediaDirectoryFormView::MediaDirectoryFormView(Wt::WContainerWidget *parent) _model = new MediaDirectoryFormModel(this); - setTemplateText(tr("mediaDirectoryForm-template")); + setTemplateText(tr("settings-new-media-directory")); addFunction("id", &WTemplate::Functions::id); addFunction("block", &WTemplate::Functions::id); diff --git a/src/ui/settings/SettingsUserFormView.cpp b/src/ui/settings/SettingsUserFormView.cpp index f0d94cc5..f3c45b69 100644 --- a/src/ui/settings/SettingsUserFormView.cpp +++ b/src/ui/settings/SettingsUserFormView.cpp @@ -300,7 +300,7 @@ UserFormView::UserFormView(std::string userId, Wt::WContainerWidget *parent) _model = new UserFormModel(userId, this); - setTemplateText(tr("userForm-template")); + setTemplateText(tr("settings-new-user")); addFunction("id", &WTemplate::Functions::id); addFunction("block", &WTemplate::Functions::id); From 0dace5cbdfd2aa5236f047d66d69d6e551e7c494 Mon Sep 17 00:00:00 2001 From: emeric Date: Thu, 14 Apr 2016 12:53:29 +0200 Subject: [PATCH 002/195] [DB] Importing KNNL library --- src/Makefile.am | 2 +- .../knnl/basic_activation_function.hpp | 89 ++++ src/third-party/knnl/basic_neuron.hpp | 174 +++++++ .../knnl/basic_neuron_fun_spec.hpp | 169 +++++++ .../knnl/basic_weak_distance_function.hpp | 114 +++++ src/third-party/knnl/data_parser.hpp | 157 ++++++ src/third-party/knnl/debugger.hpp | 114 +++++ .../knnl/euclidean_distance_function.hpp | 157 ++++++ src/third-party/knnl/functors.hpp | 366 ++++++++++++++ .../knnl/generalized_training_weight.hpp | 454 ++++++++++++++++++ src/third-party/knnl/kohonen_network.hpp | 146 ++++++ src/third-party/knnl/max_type.hpp | 134 ++++++ src/third-party/knnl/neural_net_headers.hpp | 80 +++ src/third-party/knnl/numeric_iterator.hpp | 162 +++++++ src/third-party/knnl/operators.hpp | 440 +++++++++++++++++ src/third-party/knnl/print_network.hpp | 156 ++++++ src/third-party/knnl/randomize_policy.hpp | 108 +++++ src/third-party/knnl/ranges.hpp | 156 ++++++ .../knnl/rectangular_container.hpp | 146 ++++++ src/third-party/knnl/std_headers.hpp | 68 +++ src/third-party/knnl/training_functional.hpp | 357 ++++++++++++++ src/third-party/knnl/value_type.hpp | 98 ++++ .../weighted_euclidean_distance_function.hpp | 231 +++++++++ .../knnl/wta_training_algorithm.hpp | 278 +++++++++++ src/third-party/knnl/wtm_topology.hpp | 288 +++++++++++ .../knnl/wtm_training_algorithm.hpp | 293 +++++++++++ 26 files changed, 4936 insertions(+), 1 deletion(-) create mode 100755 src/third-party/knnl/basic_activation_function.hpp create mode 100755 src/third-party/knnl/basic_neuron.hpp create mode 100755 src/third-party/knnl/basic_neuron_fun_spec.hpp create mode 100755 src/third-party/knnl/basic_weak_distance_function.hpp create mode 100755 src/third-party/knnl/data_parser.hpp create mode 100755 src/third-party/knnl/debugger.hpp create mode 100755 src/third-party/knnl/euclidean_distance_function.hpp create mode 100755 src/third-party/knnl/functors.hpp create mode 100755 src/third-party/knnl/generalized_training_weight.hpp create mode 100755 src/third-party/knnl/kohonen_network.hpp create mode 100755 src/third-party/knnl/max_type.hpp create mode 100755 src/third-party/knnl/neural_net_headers.hpp create mode 100755 src/third-party/knnl/numeric_iterator.hpp create mode 100755 src/third-party/knnl/operators.hpp create mode 100755 src/third-party/knnl/print_network.hpp create mode 100755 src/third-party/knnl/randomize_policy.hpp create mode 100755 src/third-party/knnl/ranges.hpp create mode 100755 src/third-party/knnl/rectangular_container.hpp create mode 100755 src/third-party/knnl/std_headers.hpp create mode 100755 src/third-party/knnl/training_functional.hpp create mode 100755 src/third-party/knnl/value_type.hpp create mode 100755 src/third-party/knnl/weighted_euclidean_distance_function.hpp create mode 100755 src/third-party/knnl/wta_training_algorithm.hpp create mode 100755 src/third-party/knnl/wtm_topology.hpp create mode 100755 src/third-party/knnl/wtm_training_algorithm.hpp diff --git a/src/Makefile.am b/src/Makefile.am index 5b9e6480..18a8bcbc 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -67,5 +67,5 @@ lms_SOURCES += \ $(srcdir)/ui/video/VideoParametersDialog.cpp endif -lms_CXXFLAGS=-std=c++11 -Wall -I$(top_srcdir)/third-party -I$(srcdir)/ui $(MAGICKXX_CFLAGS) +lms_CXXFLAGS=-std=c++11 -Wall -I$(srcdir)/third-party -I$(srcdir)/ui $(MAGICKXX_CFLAGS) lms_LDADD=$(MAGICKXX_LIBS) diff --git a/src/third-party/knnl/basic_activation_function.hpp b/src/third-party/knnl/basic_activation_function.hpp new file mode 100755 index 00000000..43629846 --- /dev/null +++ b/src/third-party/knnl/basic_activation_function.hpp @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2006, Seweryn Habdank-Wojewodzki + * Copyright (c) 2006, Janusz Rybarski + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions + * and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS + * AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * e-mail: habdank AT gmail DOT com + * e-mail: janusz.rybarski AT gmail DOT com + * + * File created: Tue 11 Apr 2006 17:47:44 CEST + * Last modified: Mon 22 May 2006 13:28:47 CEST + */ + +#ifndef BASIC_ACTIVATION_FUNCTION_HPP_INCLUDED +#define BASIC_ACTIVATION_FUNCTION_HPP_INCLUDED + +/** + * \file basic_activation_function.hpp + * \brief File contains template class Basic_activation_function. + * \ingroup neural_net + */ + +namespace neural_net +{ + /** + * \addtogroup neural_net + */ + /*\@{*/ + + /** + * Basic_activation_function template class. + * \param Parameters_type is type of parameters. + * \param Value_type is a type of values. + * \param Result_type is a type of results. + */ + template + < + typename Parameters_type, + typename Value_type, + typename Result_type + > + class Basic_activation_function + { + public: + /** Result type. */ + typedef Result_type result_type; + + /** Value type. */ + typedef Value_type value_type; + + /** Parameters type. */ + typedef Parameters_type parameters_type; + }; + /*\@}*/ + +} // namespace neural_net + +#endif // BASIC_ACTIVATION_FUNCTION_HPP_INCLUDED diff --git a/src/third-party/knnl/basic_neuron.hpp b/src/third-party/knnl/basic_neuron.hpp new file mode 100755 index 00000000..8f303ecc --- /dev/null +++ b/src/third-party/knnl/basic_neuron.hpp @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2006, Seweryn Habdank-Wojewodzki + * Copyright (c) 2006, Janusz Rybarski + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions + * and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS + * AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * e-mail: habdank AT gmail DOT com + * e-mail: janusz.rybarski AT gmail DOT com + * + * File created: Tue 11 Apr 2006 15:01:26 CEST + * Last modified: Sun 26 Nov 2006 09:28:46 CET + */ + +#ifndef BASIC_NEURON_HPP_INCLUDED +#define BASIC_NEURON_HPP_INCLUDED + +/** + * \file basic_neuron.hpp + * \brief File contains template class Basic_neuron. + * \ingroup neural_net + */ + +/** + * \defgroup neural_net Neural network + */ + +/** + * \namespace neural_net + * \brief Neural network namespace + * \ingroup neural_net + */ + +/** + * \addtogroup neural_net + */ +/*\@{*/ +namespace neural_net +{ + /** + * \class Basic_neuron + * \brief Basic_neuron template class. + * \param Activation_function_type + * is a functor type of activation function. + * \param Binary_operation_type + * is a type of binary operation used in neuron values. + * \param Weigths_type is type of weights. + */ + template + < + typename Activation_function_type, + typename Binary_operation_type + > + class Basic_neuron + { + public: + + /** Weights type. */ + typedef typename Binary_operation_type::value_type weights_type; + + /** Activation function type. */ + typedef Activation_function_type activation_function_type; + + /** Binary operation type. */ + typedef Binary_operation_type binary_operation_type; + typedef typename Binary_operation_type::value_type value_type; + typedef typename Activation_function_type::result_type result_type; + + /** Activation function functor. */ + Activation_function_type activation_function; + + /** Weak and generalized distance function. */ + Binary_operation_type binary_operation; + + /** Weights. */ + weights_type weights; + + /** + * Constructor. + * \param weights_ are weights of the neuron. + * \param activation_function_ is an activation + * function of the neuron. + * \param binary_operation_ is an operation calculated + * on weights and data, before + * activation function. + */ + Basic_neuron + ( + weights_type const & weights_, + Activation_function_type const & activation_function_, + Binary_operation_type const & binary_operation_ + ) + : activation_function ( activation_function_ ), + binary_operation ( binary_operation_ ), + weights ( weights_ ) + {} + + /** + * Calculation function. + * \param x input value for the neuron. + * \return output from neuron. + * \f[ + * y = f ( g ( w,x ) ) + * \f] + * where: f is activation function, g is binary operation, + * w is weight and x is input value. + */ + typename Activation_function_type::result_type + operator() ( value_type const & x ) const + { + // calculate output of the neuron as activation fuction + // working on results from binary operation on weights and value + // composition of activation and distance function + return activation_function ( binary_operation ( weights, x ) ); + } + + /** Copy constructor. */ + template + < + typename Activation_function_type_2, + typename Binary_operation_type_2 + > + Basic_neuron + ( + Basic_neuron + < + Activation_function_type_2, + Binary_operation_type_2 + > + const & neuron_ + ) + { + activation_function = neuron_.activation_function; + binary_operation = neuron_.binary_operation; + weights = neuron_.weights; + } + + protected: + Basic_neuron(); + }; +} // namespace neural_net +/*\@}*/ + +#endif // BASIC_NEURON_HPP_INCLUDED diff --git a/src/third-party/knnl/basic_neuron_fun_spec.hpp b/src/third-party/knnl/basic_neuron_fun_spec.hpp new file mode 100755 index 00000000..e60883db --- /dev/null +++ b/src/third-party/knnl/basic_neuron_fun_spec.hpp @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2006, Seweryn Habdank-Wojewodzki + * Copyright (c) 2006, Janusz Rybarski + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions + * and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS + * AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * e-mail: habdank AT gmail DOT com + * e-mail: janusz.rybarski AT gmail DOT com + * + * File created: Tue 11 Apr 2006 15:01:26 CEST + * Last modified: Wed 08 Aug 2007 18:27:41 CEST + */ + +#ifndef BASIC_NEURON_FUN_SPEC_HPP_INCLUDED +#define BASIC_NEURON_FUN_SPEC_HPP_INCLUDED + +#include +#include "basic_neuron.hpp" + +/** + * \file basic_neuron_fun_spec.hpp + * \brief File contains specialization of template class Basic_neuron. + * using boost::function. + * \ingroup neural_net + */ + +namespace neural_net +{ + /** + * \addtogroup neural_net + */ + /*\@{*/ + + /** + * Basic_neuron template class specialization for the functions instead + * of functors. + * \param Activation_function_type is a function of activation. + * \param Binary_operation_type is a type of binary operation e.g. distance function. + */ + template + < + typename Activation_function_type, + typename Binary_operation_type + > + class Basic_neuron + < + Activation_function_type ( typename Binary_operation_type::result_type ), + Binary_operation_type ( Value_type, Value_type ) + > + { + public: + + /** Weights type. */ + typedef typename Binary_operation_type::value_type weights_type; + + /** Activation function type. */ + typedef Activation_function_type activation_function_type; + + /** Binary operation type. */ + typedef Binary_operation_type binary_operation_type; + typedef typename Binary_operation_type::value_type value_type; + + /** Activation function. */ + ::boost::function < + Activation_function_type ( typename Binary_operation_type::result_type ) + > activation_function; + + /** Weak and generalized distance function. */ + ::boost::function < + Binary_operation_type ( value_type, value_type ) + > binary_operation; + + /** Weights. */ + weights_type weights; + + /** + * Constructor. + * \param weights_ are weights of the neuron. + * \param activation_function_ is an activation + * function of the neuron. + * \param binary_operation_ is an operation calculated + * on weights and data, before + * activation function. + */ + Basic_neuron ( + weights_type const & weights_, + + ::boost::function < + Activation_function_type ( typename Binary_operation_type::result_type ) + > + const & activation_function_, + + ::boost::function < + Binary_operation_type ( value_type, value_type ) + > + const & binary_operation_ + ) + : activation_function ( activation_function_ ), + binary_operation ( binary_operation_ ), + weights ( weights_ ) + {} + + /** + * Calculation function. + * \param x input value for the neuron. + * \return output from neuron. + * \f[ + * y = f ( g ( w,x ) ) + * \f] + * where: f is activation function, g is binary operation, + * w is weight and x is input value. + */ + typename Activation_function_type::result_type + operator() ( value_type const & x ) const + { + // calculate output of the neuron as activation fuction + // working on results from binary operation on weights and value + // composition of activation and distance function + return activation_function ( binary_operation ( weights, x ) ); + } + + /** Copy constructor. */ + template < + typename Activation_function_type_2, + typename Binary_operation_type_2 + > Basic_neuron ( + const Basic_neuron < + Activation_function_type_2 ( typename Binary_operation_type_2::result_type ), + Binary_operation_type_2 ( value_type, value_type ) + > & neuron_ + ) + {} + }; + /*\@}*/ + +} // namespace neural_net + +#endif // BASIC_NEURON_FUN_SPEC_HPP_INCLUDED diff --git a/src/third-party/knnl/basic_weak_distance_function.hpp b/src/third-party/knnl/basic_weak_distance_function.hpp new file mode 100755 index 00000000..dac949c2 --- /dev/null +++ b/src/third-party/knnl/basic_weak_distance_function.hpp @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2006, Seweryn Habdank-Wojewodzki + * Copyright (c) 2006, Janusz Rybarski + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions + * and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS + * AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * e-mail: habdank AT gmail DOT com + * e-mail: janusz.rybarski AT gmail DOT com + * + * File created: Thu 13 Apr 2006 08:30:20 CEST + * Last modified: Sun 26 Nov 2006 08:32:48 CET + */ + +#ifndef BASIC_WEAK_DISTANCE_FUNCTION_HPP_INCLUDED +#define BASIC_WEAK_DISTANCE_FUNCTION_HPP_INCLUDED + +/** + * \defgroup distance Generalized distance + */ + +/** + * \file basic_weak_distance_function.hpp + * \brief File contains template class Basic_weak_distance_function. + * \ingroup distance + */ + +/** + * \namespace distance + * \brief namespace contains functors for distance calculation in the data. + * \ingroup distance + */ +namespace distance +{ + /** + * \addtogroup distance + */ + /*\@{*/ + + /** + * Basic_weak_distance_function template class. + * \param Value_type is a type of values. + * \param Parameters_type ia a type of parameters. + */ + template + < + typename Value_type, + typename Result_type + > + class Basic_weak_distance_function + { + public: + + typedef Value_type value_type; + typedef Result_type result_type; + + /** + * Constructor. + */ + Basic_weak_distance_function() + {} + + /** Copy constructor. */ + template + < + typename Value_type_2, + typename Result_type_2 + > + Basic_weak_distance_function + ( + Basic_weak_distance_function + < + Value_type_2, + Result_type_2 + > + const & weak_distance_function_ + ) + {} + }; + /*\@}*/ + +} // namespace distance + +#endif // BASIC_WEAK_DISTANCE_FUNCTION_HPP_INCLUDED diff --git a/src/third-party/knnl/data_parser.hpp b/src/third-party/knnl/data_parser.hpp new file mode 100755 index 00000000..e2867de2 --- /dev/null +++ b/src/third-party/knnl/data_parser.hpp @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2006, Seweryn Habdank-Wojewodzki + * Copyright (c) 2006, Janusz Rybarski + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions + * and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS + * AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * e-mail: habdank AT gmail DOT com + * e-mail: janusz.rybarski AT gmail DOT com + * + * File created: Sat 29 Apr 2006 19:10:50 CEST + * Last modified: Wed 08 Aug 2007 17:15:45 CEST + */ + +#ifndef DATA_PARSER_HPP_INCLUDED +#define DATA_PARSER_HPP_INCLUDED + +#include +#include +#include +#include +#include + +/** + * \defgroup data_parser Data parser + */ + +/** + * \file data_parser.hpp + * \brief File contains template class Data_parser. + * \ingroup data_parser + */ + +/** + * \addtogroup data_parser + */ +/*\@{*/ + +/** + * \namespace data_parser + * \brief Data parser namespace includes functionality to parsing and storing data into containers. + * \ingroup data_parser + */ +namespace data_parser +{ + /** + * Class for parsing data stream. + * \param Data_container is a container type. + */ + template < typename Data_container > + class Data_parser + { + public: + /** Constructor. */ + Data_parser() + {} + + /** + * Function that parse stream and store data in container. + * \param is is a reference to the stream. + * \param data_container is a reference to the container. + * \return modified container. + * \throw ::std::runtime_error when after parsing size of data_container is still zero. + */ + Data_container & operator() ( ::std::istream & is, Data_container & data_container ) const + { + ::std::string tmp_string; + ::boost::int32_t counter = 0; + + typename Data_container::value_type tmp_sub_container; + + // get line of data from stream + while ( ::std::getline ( is, tmp_string ) ) + { + // count data + ++counter; + + // parse data and push data back into the container + data_container.push_back ( parse_string ( tmp_sub_container, tmp_string ) ); + + // reset temporal container + tmp_sub_container.clear(); + } + + // if data container is empty after all procedure throw exception + if ( data_container.size() == 0 ) + { + throw ::std::runtime_error ( "Data file corupted." ); + } + + // return data container of containers + return data_container; + } + + private: + /** + * This function is for parsing string. + * \param container is a reference to the data conatiner. + * \param str is a reference to the string. + * \return container of the values stored in one string. + */ + typename Data_container::value_type parse_string + ( + typename Data_container::value_type & container, + ::std::string & str + ) const + { + ::std::stringstream tmp_sstr ( str ); + + typedef typename Data_container::value_type::value_type internal_type; + + // parse data as sequence of fixed type data and store them into the container. + ::std::copy + ( + ::std::istream_iterator < internal_type > ( tmp_sstr ), + ::std::istream_iterator < internal_type >(), + ::std::back_inserter ( container ) + ); + + return container; + } + + }; +} // namespace data_parser +/*\@}*/ + +#endif // DATA_PARSER_HPP_INCLUDED diff --git a/src/third-party/knnl/debugger.hpp b/src/third-party/knnl/debugger.hpp new file mode 100755 index 00000000..ec50fb23 --- /dev/null +++ b/src/third-party/knnl/debugger.hpp @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2005, Seweryn Habdank-Wojewodzki + * + * All rights reserved. + * + * Permission to copy, use, modify, sell and distribute this software + * is granted provided this copyright notice appears in all copies. + * This software is provided "as is" without express or implied + * warranty, and with no claim as to its suitability for any purpose. + * This software has generative abilities, but does not claim any rights + * nor guarantees with regard to the generated code. + */ + +/* + * e-mail: habdank{AT}gmail{dot}com + * + * File created: Sat 05 Nov 2005 14:20:28 CET + * Last modified: Wed 08 Aug 2007 17:16:20 CEST + */ + +/** + * \defgroup trivial_debugger Trivial Debugger + */ + +/** + * \file debugger.hpp + * \ingroup trivial_debugger + * \brief Defines macro for Trivial Debugger. + * When compiler send to program one of flags: TDEBUG, ETDEBUG or FTDEBUG debugger functionality is set. + */ + +/** + * \def D + * \brief Macro D will send to proper stream value of the variable. + * Stream depends on set flag. Information which is send looks like: + * + * __FILE__ [__LINE__] : variable name = variable value + * + * \param name is name of debugged variable. + * \par Usage: + * + * In the main file e.g. main.cpp in global scope should be placed: + * + * #ifdef FTDEBUG + * ::std::auto_ptr < ::std::ofstream > DEBUGGER_STREAM; + * #endif //FTDEGUG + * + * And at the very beginnig of the main function should be placed: + * + * #ifdef FTDEBUG + * DEBUGGER_STREAM = + * ::std::auto_ptr < ::std::ofstream > + * ( new ::std::ofstream ( "_debugger.out" ) ); + * #endif //FTDEBUG + * + * if we suppose to use file to make logs. + * + * And in the application we have to include this header and just use macro: + * + * Available for all cases including compilation + * without any flag: + * + * D ( my_variable_name ); or D ( "I am here." ); + * + * If are sure that debugger will work by default any of flag TDEBUG, ETDEBUG and FTDEBUG is set: + * + * D ( ) << "Send other information to the stream." << ::std::endl; + * + * Or just work on if we are sure about existence of the log file FTDEBUG is set: + * + * *DEBUGGER_STREAM << any_function_that_returns_reference_to_ostream ( ) << ::std::endl; + * + * \par Remark: + * Remember to put semicolon at the end! + * + * \par Flags: + * + * - TDEBUG - Trivial Debugger sends debugging information to the stdout. + * + * - ETDEBUG - Trivial Debugger sends debugging information to the stderr. + * + * - FTDEBUG - Trivial Debugger sends debugging information to the file + * (generally declared and defined in main() function of the program). + */ + +#ifndef DEBUGGER_HPP_INCLUDED +#define DEBUGGER_HPP_INCLUDED + +#ifdef FTDEBUG + + #include + #include + + #define D(name) *DEBUGGER_STREAM << __FILE__ << " [" << __LINE__ << "] : " << #name << " = " << (name) << ::std::endl + extern ::std::auto_ptr < ::std::ofstream > DEBUGGER_STREAM; + +#elif defined(TDEBUG) + + #include + #define D(name) ::std::cout << __FILE__ << " [" << __LINE__ << "] : " << #name << " = " << (name) << ::std::endl + +#elif defined(ETDEBUG) + + #include + #define D(name) ::std::cerr << __FILE__ << " [" << __LINE__ << "] : " << #name << " = " << (name) << ::std::endl + +#else + + #define D(name) {} + +#endif // ..TDEBUG + +#endif // DEBUGGER_HPP_INCLUDED + diff --git a/src/third-party/knnl/euclidean_distance_function.hpp b/src/third-party/knnl/euclidean_distance_function.hpp new file mode 100755 index 00000000..2eddf8c8 --- /dev/null +++ b/src/third-party/knnl/euclidean_distance_function.hpp @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2006, Seweryn Habdank-Wojewodzki + * Copyright (c) 2006, Janusz Rybarski + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions + * and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS + * AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * e-mail: habdank AT gmail DOT com + * e-mail: janusz.rybarski AT gmail DOT com + * + * File created: Fri 14 Apr 2006 22:42:31 CEST + * Last modified: Wed 08 Aug 2007 18:21:06 CEST + */ + +#ifndef EUCLIDEAN_DISTANCE_FUNCTION_HPP_INCLUDED +#define EUCLIDEAN_DISTANCE_FUNCTION_HPP_INCLUDED + +#include + +#include "operators.hpp" +#include "basic_weak_distance_function.hpp" +#include "value_type.hpp" + +/** + * \file euclidean_distance_function.hpp + * \brief File contains template class Euclidean_distance_function. + * \ingroup distance + */ + +namespace distance +{ + /** + * \addtogroup distance + */ + /*\@{*/ + + /** + * Euclidean_distance_function template class. + * \param Value_type is a type of values. + * \f[ + * d (x,y) = \sum\limits_{i=0}^{N} cdot (x_i-y_i)^2 + * \f] + */ + template + < + typename Value_type + > + class Euclidean_distance_function + : public Basic_weak_distance_function + < + Value_type, + Value_type + > + { + public: + /** + * Constructor. + */ + Euclidean_distance_function() + {} + + /** + * Calculation function. + * \param x first input value for the function. + * \param y second input value for the function. + * \return square of the Euclidean distance function. + * \f[ + * d (x,y) = \sum\limits_{i=0}^{N} cdot (x_i-y_i)^2 + * \f] + */ + typename Value_type::value_type operator() + ( + Value_type const & x, + Value_type const & y + ) const + { + return + ( + euclidean_distance_square + ( + x.begin(), + x.end(), + y.begin(), + static_cast < typename Value_type::value_type const & > ( 0 ) + ) + ); + } + + private: + typedef typename Value_type::value_type inner_type; + + /** + * Function calculates Euclidean distance between two containers. + * \param begin_1 is a begin iterator for the first container. + * \param end_1 is an end iterator for the first container. + * \param begin_2 is an begin iterator for the second container. + * \param init is an initial value. + * \result sqare of Euclidean distance. + */ + inner_type euclidean_distance_square + ( + typename Value_type::const_iterator begin_1, + typename Value_type::const_iterator end_1, + typename Value_type::const_iterator begin_2, + inner_type const & init + ) const + { + return ::std::inner_product + ( + begin_1, end_1, begin_2, + static_cast < inner_type > ( init ), + ::std::plus < inner_type >(), + ::operators::compose_f_gxy_gxy + ( + ::std::multiplies < inner_type >(), + ::std::minus < inner_type >() + ) + ); + } + + }; + /*\@}*/ + +} // namespace distance + +#endif // EUCLIDEAN_DISTANCE_FUNCTION_HPP_INCLUDED + diff --git a/src/third-party/knnl/functors.hpp b/src/third-party/knnl/functors.hpp new file mode 100755 index 00000000..7fa79fc5 --- /dev/null +++ b/src/third-party/knnl/functors.hpp @@ -0,0 +1,366 @@ +/* + * Copyright (c) 2006, Seweryn Habdank-Wojewodzki + * Copyright (c) 2006, Janusz Rybarski + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions + * and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS + * AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * e-mail: habdank AT gmail DOT com + * e-mail: janusz.rybarski AT gmail DOT com + * + * File created: Wed 10 May 2006 11:16:03 CEST + * Last modified: Wed 08 Aug 2007 18:21:33 CEST + */ + +#ifndef FUNCTORS_HPP_INCLUDED +#define FUNCTORS_HPP_INCLUDED + +#include + +#include "basic_activation_function.hpp" +#include "operators.hpp" +#include "training_functional.hpp" +/** + * \file functors.hpp + * \brief File contains template class Basic_function and some other classes derived form that one. + * \ingroup neural_net + */ + +namespace neural_net +{ + /** + * \addtogroup neural_net + */ + /*\@{*/ + + /** + * \class Basic_function + * \brief Basic class for defining functions. + * \param Value_type is a type of values. + */ + template < typename Value_type > + struct Basic_function + { + typedef Value_type value_type; + }; + + /** + * \class Gauss_function + * \brief Functor that compute Gauss hat function. + * \param Value_type is a type of values. + * \param Scalar_type is a type of scaling factor which multiplies values. + * \param Exponent_type is a type of exponential factor. + * \f[ + * y = e ^{-\frac{1}{2}\left (\frac{v}{\sigma}\right)^p} + * \f] + */ + template + < + typename Value_type, + typename Scalar_type, + typename Exponent_type + > + class Gauss_function + : public Basic_function < Value_type >, + public Basic_activation_function + < + typename ::operators::Max_type < Scalar_type, Exponent_type >::type, + Value_type, + typename ::operators::Max_type + < + typename ::operators::Max_type + < + typename ::operators::Max_type < Scalar_type, Exponent_type >::type, + Value_type + >::type, + double + >::type + > + { + public: + + typedef Scalar_type scalar_type; + typedef Exponent_type exponent_type; + typedef Value_type value_type; + + typedef typename ::operators::Max_type + < + typename ::operators::Max_type + < + typename ::operators::Max_type < Scalar_type, Exponent_type >::type, + Value_type + >::type, + double + >::type result_type; + + /** Sigma coafficient in the function. */ + Scalar_type sigma; + + /** Exponential factor. */ + Exponent_type exponent; + + /** + * Constructor. + * \param sigma_ is sigma coefficient in Gauss hat function. + * \param exp_ is exponential factor in Gauss hat function. + */ + Gauss_function ( Scalar_type const & sigma_, Exponent_type const & exp_ ) + : sigma ( sigma_ ), exponent ( exp_ ) + {} + + /** + * Result of the functor. + * \param value is a value. + * \return calcutaled result. + * \f[ + * y = e ^{-\frac{1}{2}\left (\frac{v}{\sigma}\right)^p} + * \f] + * where: v is value. + */ + result_type operator() ( Value_type const & value ) const + { + ::operators::power < result_type, Exponent_type > power_v; + + // calculate result + return + ( + ::std::exp + ( + - ::operators::inverse ( static_cast < Scalar_type > ( 2 ) ) + * (power_v) + ( + ::operators::inverse ( sigma ) * value, + exponent + ) + ) + ); + } + + /** Copy constructor. */ + template + < + typename Value_type_2, + typename Scalar_type_2, + typename Exponent_type_2 + > + Gauss_function + ( + Gauss_function + < + Value_type_2, + Scalar_type_2, + Exponent_type_2 + > + const & gauss_function + ) + : sigma ( gauss_function.sigma ), exponent ( gauss_function.exponent ) + {} + }; + + /** + * \class Cauchy_function + * \brief Functor that computes Cauchy hat function. + * \param Value_type is a type of value. + * \param Scalar_type is a type of scalar which will multiplies values. + * \param Power_type is a type of exponential factor. + * \f[ + * y=\frac{1}{1 + (\frac{x}{\sigma})^p} + * \f] + */ + template + < + typename Value_type, + typename Scalar_type, + typename Exponent_type + > + class Cauchy_function + : public Basic_function < Value_type >, + public Basic_activation_function + < + typename ::operators::Max_type < Scalar_type, Exponent_type >::type, + Value_type, + typename ::operators::Max_type + < + typename ::operators::Max_type < Scalar_type, Exponent_type >::type, + Value_type + >::type + > + { + public: + + typedef Scalar_type scalar_type; + typedef Exponent_type exponent_type; + typedef Value_type value_type; + + typedef typename ::operators::Max_type + < + typename ::operators::Max_type < Scalar_type, Exponent_type >::type, + Value_type + >::type result_type; + + /** Sigma scaling coefficient. */ + Scalar_type sigma; + + /** Exponential factor. */ + Exponent_type exponent; + + /** + * Constuctor. + * \param sigma_ is scailing coefficient. + * \param exp_ is exponential factor. + */ + Cauchy_function ( Scalar_type const & sigma_, Exponent_type const & exp_ ) + : sigma ( sigma_ ), exponent ( exp_ ) + {} + + /** + * Function calculates values of the Cauchy hat function. + * \param value is a value. + * \return value of the function. + * \f[ + * y=\frac{1}{1 + (\frac{x}{\sigma})^p} + * \f] + * where: x is value. + */ + result_type operator() ( Value_type const & value ) const + { + ::operators::power < result_type, Exponent_type > power_v; + + // calculate result + return + ( + ::operators::inverse + ( + (power_v) + ( + ::operators::inverse ( sigma ) * value, + exponent + ) + 1 + ) + ); + } + + /**constructor. */ + template + < + typename Value_type_2, + typename Scalar_type_2, + typename Exponent_type_2 + > + Cauchy_function + ( + Cauchy_function + < + Value_type_2, + Scalar_type_2, + Exponent_type_2 + > + const & cauchy_function + ) + : sigma ( cauchy_function.sigma ), exponent ( cauchy_function.exponent ) + {} + }; + + /** + * \class Constant_function + * \brief Functor that computes constant function. + * \param Value_type is a type of value. + * \param Scalar_type is a type of constant value. + * \f[ + * y=c + * \f] + */ + template + < + typename Value_type, + typename Scalar_type + > + class Constant_function + : public Basic_function < Value_type > + { + public: + + typedef Scalar_type scalar_type; + typedef Scalar_type result_type; + + /** Sigma constant value, but not const. */ + Scalar_type sigma; + + /** + * Constuctor. + * \param sigma_ is constant value. + */ + Constant_function ( const Scalar_type & sigma_ ) + : sigma ( sigma_ ) + {} + + /** Copy constructor. */ + template + < + typename Value_type_2, + typename Scalar_type_2 + > + Constant_function + ( + Constant_function + < + Value_type_2, + Scalar_type_2 + > + const & constant_function + ) + : sigma ( constant_function.sigma ) + {} + + /** + * Constant function. + * \param value is a value. + * \return constant value. + * \f[ + * y=c + * \f] + * where: x is value. + */ + result_type operator() ( Value_type const & //value + ) const + { + // result + return ( sigma ); + } + }; + /*\@}*/ + +} // namespace neural_net + +#endif // FUNCTORS_HPP_INCLUDED + diff --git a/src/third-party/knnl/generalized_training_weight.hpp b/src/third-party/knnl/generalized_training_weight.hpp new file mode 100755 index 00000000..ab3341af --- /dev/null +++ b/src/third-party/knnl/generalized_training_weight.hpp @@ -0,0 +1,454 @@ +/* + * Copyright (c) 2006, Seweryn Habdank-Wojewodzki + * Copyright (c) 2006, Janusz Rybarski + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions + * and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS + * AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * e-mail: habdank AT gmail DOT com + * e-mail: janusz.rybarski AT gmail DOT com + * + * File created: Sun 07 May 2006 13:51:04 CEST + * Last modified: Wed 08 Aug 2007 18:21:59 CEST + */ + +#ifndef GENERALIZED_TRAINING_WEIGHT_HPP_INCLUDED +#define GENERALIZED_TRAINING_WEIGHT_HPP_INCLUDED + +#include "operators.hpp" +#include "training_functional.hpp" + +/** + * \file generalized_training_weight.hpp + * \brief File contains template class Basic_generalized_training_weight and some other classes derived form that one. + * \ingroup neural_net + */ + +namespace neural_net +{ + /** + * \addtogroup neural_net + */ + /*\@{*/ + + /** + * \class Basic_generalized_training_weight + * \brief Template class for the generalized training functions. + * \param Value_type is a type of values. + * \param Iteration_type is a type of interation counter. + * \param Network_function_type is a type of function that will return proper value + * based on network topology. + * \param Space_funtion_type is a type of function that will return proper value + * based on space topology. + * \param Network_topology is a type of function that computes distances between + * neurons based on network topology. + * \param Space_topology is a type of function that computes distance between + * value and weight in proper topology. + * \param Index_type is a type of index in the neural network container. + */ + template + < + typename Value_type, + typename Iteration_type, + typename Network_function_type, + typename Space_function_type, + typename Network_topology, + typename Space_topology, + typename Index_type + > + struct Basic_generalized_training_weight + { + typedef Network_function_type network_function_type; + typedef Space_function_type space_function_type; + typedef Value_type value_type; + typedef Iteration_type iteration_type; + typedef Index_type index_type; + typedef Network_topology network_topology; + typedef Space_topology space_topology; + }; + + /** + * \class Classic_training_weight + * \brief Template class for the generalize training functions that calculates + * generalized weight classical way. + * \param Value_type is a type of values. + * \param Iteration_type is a type of interation counter. + * \param Network_function_type is a type of function that will return proper value + * based on network topology. + * \param Space_funtion_type is a type of function that will return proper value + * based on space topology. + * \param Network_topology is a type of function that computes distances between + * neurons based on network topology. + * \param Space_topology is a type of function that computes distance between + * value and weight in proper topology. + * \param Index_type is a type of index in the neural network container. + * \f[ + * y=n_f (n_t (c_1,c_2,v_1,v_2)) \cdot s_f (s_t (x,w)) + * \f] + */ + template + < + typename Value_type, + typename Iteration_type, + typename Network_function_type, + typename Space_function_type, + typename Network_topology, + typename Space_topology, + typename Index_type + > + class Classic_training_weight + : public Basic_generalized_training_weight + < + Value_type, + Iteration_type, + Network_function_type, + Space_function_type, + Network_topology, + Space_topology, + Index_type + > + { + public: + + /** Functor computes weight based on the result from network topology. */ + Network_function_type network_function; + + /** Functor computes weight based on the result from space topology. */ + Space_function_type space_function; + + /** Functor computes generalized distance in network. */ + Network_topology network_topology; + + /** Functor computes generalized distance in data space. */ + Space_topology space_topology; + + /** + * Function computes generalized weight for training proces. + * This weight is not the same as weights in neural network. + * \param weight is a weight from neural network. + * \param value is a input value (value that trains network). + * \param iteration is a number of training steps could be + * number of training data sample. + * \param c_1 is a row number (position) in the network of the central neuron. + * \param c_2 is a column number (position) in the network of the central neuron. + * \param v_1 is a row number (position) in the network of the trained neuron. + * \param v_2 is a column number (position) in the network of the trained neuron. + * \f[ + * y=n_f (n_t (c_1,c_2,v_1,v_2)) \cdot s_f (s_t (x,w)) + * \f] + * where x is value and w is neuron weight. + */ + typename Space_function_type::value_type operator() + ( + Value_type const & weight, + Value_type const & value, + Iteration_type const & //iteration + , + Index_type const & c_1, + Index_type const & c_2, + Index_type const & v_1, + Index_type const & v_2 + ) + { + // calculate result + return + ( + (network_function) ( (network_topology) ( c_1, c_2, v_1, v_2 ) ) + * (space_function) ( (space_topology) ( value, weight ) ) + ); + } + + /** + * Constructor. + * \param n_f is a network functor. + * \param s_f is a data space functor. + * \param n_t is a network topology functor. + * \param s_t is a space topology functor. + */ + Classic_training_weight + ( + Network_function_type const & n_f, + Space_function_type const & s_f, + Network_topology const & n_t, + Space_topology const & s_t + ) + : Basic_generalized_training_weight + < + Value_type, + Iteration_type, + Network_function_type, + Space_function_type, + Network_topology, + Space_topology, + Index_type + >(), + network_function ( n_f ), + space_function ( s_f ), + network_topology ( n_t ), + space_topology ( s_t ) + {} + + /** Copy constructor */ + template + < + typename Value_type_2, + typename Iteration_type_2, + typename Network_function_type_2, + typename Space_function_type_2, + typename Network_topology_2, + typename Space_topology_2, + typename Index_type_2 + > + Classic_training_weight + ( + const Classic_training_weight + < + Value_type_2, + Iteration_type_2, + Network_function_type_2, + Space_function_type_2, + Network_topology_2, + Space_topology_2, + Index_type_2 + > + & classic_training_weight + ) + : Basic_generalized_training_weight + < + Value_type_2, + Iteration_type_2, + Network_function_type_2, + Space_function_type_2, + Network_topology_2, + Space_topology_2, + Index_type_2 + >(), + network_function ( classic_training_weight.network_function ), + space_function ( classic_training_weight.space_function ), + network_topology ( classic_training_weight.network_topology ), + space_topology ( classic_training_weight.space_topology ) + {} + + }; + + /** + * \class Experimental_training_weight + * \brief Template class for the generalize training functions that calculates + * generalizeg weight in experimental way. + * \param Value_type is a type of values. + * \param Iteration_type is a type of interation counter. + * \param Network_function_type is a type of function that will return proper value + * based on network topology. + * \param Space_funtion_type is a type of function that will return proper value + * based on space topology. + * \param Network_topology is a type of function that computes distances between + * neurons based on network topology. + * \param Space_topology is a type of function that computes distance between + * value and weight in proper topology. + * \param Index_type is a type of index in the neural network container. + * \param Parameters_type is a type of the parameters for experimenntal training. + * \param n_power is a power q_N + * \param s_power is a power q_S + * \f[ + * y= ( p_1 \cdot n_f (n_t (c_1,c_2,v_1,v_2)) - p_0 )^q_N \cdot s_f (s_t (x,w))^q_S + * \f] + */ + template + < + typename Value_type, + typename Iteration_type, + typename Network_function_type, + typename Space_function_type, + typename Network_topology, + typename Space_topology, + typename Index_type, + typename Parameter_type, + ::boost::int32_t n_power = 1, + ::boost::int32_t s_power = 1 + > + class Experimental_training_weight + : public Basic_generalized_training_weight + < + Value_type, + Iteration_type, + Network_function_type, + Space_function_type, + Network_topology, + Space_topology, + Index_type + > + { + public: + + //::boost::int32_t const s_power; + //::boost::int32_t const n_power; + + /** Scaling parameter. */ + Parameter_type parameter_1; + + /** Shifting parameter. */ + Parameter_type parameter_0; + + + /** Functor computes weight based on the result from network topology. */ + Network_function_type network_function; + + /** Functor computes weight based on the result from space topology. */ + Space_function_type space_function; + + /** Functor computes generalized distance in network. */ + Network_topology network_topology; + + /** Functor computes generalized distance in data space. */ + Space_topology space_topology; + + /** + * Constructor. + * \param n_f is a network functor. + * \param s_f is a data space functor. + * \param n_t is a network topology functor. + * \param s_t is a space topology functor. + * \param parameter_0_ is a scailing parameter. + * \param parameter_1_ is a shifting parameter. + */ + Experimental_training_weight + ( + Network_function_type const & n_f, + Space_function_type const & s_f, + Network_topology const & n_t, + Space_topology const & s_t, + Parameter_type const & parameter_0_, + Parameter_type const & parameter_1_//, +// ::boost::int32_t const s_power_ = 1, +// ::boost::int32_t const n_power_ = 1 + ) + : //s_power ( s_power_ ), + //n_power ( n_power_ ), + parameter_1 ( parameter_1_), + parameter_0 ( parameter_0_), + network_function ( n_f ), + space_function ( s_f ), + network_topology ( n_t ), + space_topology ( s_t ) + {} + + /** Copy constructor. */ + template + < + typename Value_type_2, + typename Iteration_type_2, + typename Network_function_type_2, + typename Space_function_type_2, + typename Network_topology_2, + typename Space_topology_2, + typename Index_type_2, + typename Parameter_type_2 + > + Experimental_training_weight + ( + const Experimental_training_weight + < + Value_type_2, + Iteration_type_2, + Network_function_type_2, + Space_function_type_2, + Network_topology_2, + Space_topology_2, + Index_type_2, + Parameter_type_2 + > + & experimental_training_weight + ) + : Basic_generalized_training_weight + < + Value_type_2, + Iteration_type_2, + Network_function_type_2, + Space_function_type_2, + Network_topology_2, + Space_topology_2, + Index_type_2 + >(), + parameter_1 ( experimental_training_weight.parameter_1_), + parameter_0 ( experimental_training_weight.parameter_0_), + network_function ( experimental_training_weight.n_f ), + space_function ( experimental_training_weight.s_f ), + network_topology ( experimental_training_weight.n_t ), + space_topology ( experimental_training_weight.s_t ) + {} + + /** + * Function computes generalized weight for training proces. + * This weight is not the same as weights in neural network. + * \param weight is a weight from neural network. + * \param value is a input value (value that trains network). + * \param iteration is a number of training steps could be + * number of training data sample. + * \param c_1 is a row number (position) in the network of the central neuron. + * \param c_2 is a column number (position) in the network of the central neuron. + * \param v_1 is a row number (position) in the network of the trained neuron. + * \param v_2 is a column number (position) in the network of the trained neuron. + * \f[ + * y= ( p_1 \cdot n_f (n_t (c_1,c_2,v_1,v_2)) - p_0 ) \cdot s_f (s_t (x,w)) + * \f] + * where x is value, w is neuron weight, \f$p_1\f$ is scaling parameter, \f$p_0\f$ is shifting parameter. + */ + typename Space_function_type::value_type operator() + ( + Value_type & weight, + Value_type const & value, + Iteration_type const & //iteration + , + Index_type const & c_1, + Index_type const & c_2, + Index_type const & v_1, + Index_type const & v_2 + ) const + { + //::operators::power < typename Space_function_type::value_type, ::boost::int32_t > power_v; + // calculate result + return + ( + // power_v( parameter_1 * (network_function) ( (network_topology) ( c_1, c_2, v_1, v_2 ) ) - parameter_0, n_power ) + // * power_v( (space_function) ( (space_topology) ( value, weight ) ), s_power ) + ::operators::static_power( parameter_1 * (network_function) ( (network_topology) ( c_1, c_2, v_1, v_2 ) ) - parameter_0 ) + * ::operators::static_power( (space_function) ( (space_topology) ( value, weight ) ) ) + ); + } + }; + /*\@}*/ + +} // namespace neural_net + +#endif // GENERALIZED_TRAINING_WEIGHT_HPP_INCLUDED + diff --git a/src/third-party/knnl/kohonen_network.hpp b/src/third-party/knnl/kohonen_network.hpp new file mode 100755 index 00000000..ec2f3b9a --- /dev/null +++ b/src/third-party/knnl/kohonen_network.hpp @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2006, Seweryn Habdank-Wojewodzki + * Copyright (c) 2006, Janusz Rybarski + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions + * and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS + * AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * e-mail: habdank AT gmail DOT com + * e-mail: janusz.rybarski AT gmail DOT com + * + * File created: Tue 18 Apr 2006 19:25:15 CEST + * Last modified: Wed 08 Aug 2007 17:23:33 CEST + */ + +#ifndef KOHONEN_NETWORK_HPP_INCLUDED +#define KOHONEN_NETWORK_HPP_INCLUDED + +#include "ranges.hpp" + +#include +#include +#include + +/** + * \file kohonen_network.hpp + * \brief File contains template functions for preparing + * Kohonen neural networks. + * \ingroup neural_net + */ + +namespace neural_net +{ + /** + * \addtogroup neural_net + */ + /*\@{*/ + + /** + * Function generates randomly distributed weights for neural network. + * Distribution is uniform and weights are generated based on + * multidimensional ranges of training data. + * \param no_rows is a number of rows that will be created in neural network. + * \param no_columns is a number of columns that will be created in neural network. + * \param activation_function is activation function that will be set. + * \param binary_operation is a function tat will be set under activation function. + * \param data is a reference to data container. + * \param kohonen_network is a reference to the network. + * \param randomize_policy is a policy class for setting up random number generator. + * \todo TODO: When Rectangular_container will be changed then this class should be repaired too. + */ + template + < + typename Data_container_type, + typename Kohonen_network_type, + typename Randomize_policy + > + void generate_kohonen_network + ( + typename Kohonen_network_type::row_type::size_type const & no_rows, + typename Kohonen_network_type::column_type::size_type const & no_columns, + typename Kohonen_network_type::value_type::activation_function_type const & activation_function, + typename Kohonen_network_type::value_type::binary_operation_type const & binary_operation, + Data_container_type & data, + Kohonen_network_type & kohonen_network, + Randomize_policy const & randomize_policy + ) + { + randomize_policy(); + + typedef typename Kohonen_network_type::value_type Neuron_type; + typedef typename Kohonen_network_type::row_type::size_type row_size_t; + typedef typename Kohonen_network_type::column_type::size_type col_size_t; + typedef typename Neuron_type::weights_type weights_t; + typedef typename weights_t::size_type w_size_t; + + ::std::vector < Neuron_type > tmp_neuron_vector; + tmp_neuron_vector.reserve ( no_columns ); + + const w_size_t K = data.begin()->size(); + weights_t weights ( K ); + + Ranges < Data_container_type > data_ranges ( *data.begin() ); + data_ranges ( data ); + + for ( row_size_t i = 0; i < no_rows; ++i ) + { + for ( col_size_t j = 0; j < no_columns; ++j ) + { + for ( w_size_t k = 0; k < K; ++k ) + { + weights[k] = ( + static_cast < typename Data_container_type::value_type::value_type > + ( ( data_ranges.get_max().at ( k ) + - data_ranges.get_min().at ( k ) ) + * ( rand() / ( 1.0 + RAND_MAX ) ) + + data_ranges.get_min().at ( k ) ) ); + } + + Neuron_type local_neuron + ( + weights, + activation_function, + binary_operation + ); + tmp_neuron_vector.push_back ( local_neuron ); + } + kohonen_network.objects.push_back ( tmp_neuron_vector ); + tmp_neuron_vector.clear(); + } + } + /*\@}*/ + +} // namespace neural_net + +#endif // KOHONEN_NETWORK_HPP_INCLUDED + diff --git a/src/third-party/knnl/max_type.hpp b/src/third-party/knnl/max_type.hpp new file mode 100755 index 00000000..a7c3ffc1 --- /dev/null +++ b/src/third-party/knnl/max_type.hpp @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2006, Seweryn Habdank-Wojewodzki + * Copyright (c) 2006, Janusz Rybarski + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions + * and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS + * AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * e-mail: habdank AT gmail DOT com + * e-mail: janusz.rybarski AT gmail DOT com + * + * File created: Tue 23 May 2006 23:53:14 CEST + * Last modified: Wed 08 Aug 2007 17:25:27 CEST + */ + +#ifndef MAX_TYPE_HPP_INCLUDED +#define MAX_TYPE_HPP_INCLUDED + +#include +#include + +/** + * \file max_type.hpp + * \brief File contains template Max_type class. + * \ingroup operators + */ + +/** \addtogroup operators */ +/*\@{*/ +namespace operators +{ + template < typename S, typename T > + struct Max_type_private + {}; + + template < typename T > + struct Max_type_private < T, T > + { + typedef T type; + }; + + // just some examples to set up results for Max_type + // if anyone would like to add his/her types they just have to use + // macro MAX_TYPE_3( argument_1_type, argument_2_type, result_type ) + #define MAX_TYPE_3(T1,T2,T3) template <>\ + struct Max_type_private < T1, T2 >\ + { typedef T3 type; }; + + MAX_TYPE_3(::boost::int32_t,double,double) + MAX_TYPE_3(short,::boost::int32_t,::boost::int32_t) + MAX_TYPE_3(int,long,long) + MAX_TYPE_3(short,double,double) + MAX_TYPE_3(unsigned char,double,double) + MAX_TYPE_3(unsigned int,double,double) + MAX_TYPE_3(unsigned long,double,double) + MAX_TYPE_3(unsigned short,double,double) + // and example where we can explicitly use three different types + // MAX_TYPE(::std::complex < int >, double, ::std::complex < double >) + + // macro below assumes that bigger type is the first one + // macro MAX_TYPE_2( argument_1_type, argument_2_type ) + // simulate that result_type = argument_1_type + #define MAX_TYPE_2(T1,T2) template <>\ + struct Max_type_private < T1, T2 >\ + { typedef T1 type; }; + + MAX_TYPE_2(double,::boost::int32_t) + MAX_TYPE_2(long,int) + MAX_TYPE_2(long,short) + MAX_TYPE_2(double,short) + MAX_TYPE_2(double,unsigned char) + MAX_TYPE_2(double,unsigned int) + MAX_TYPE_2(double,unsigned long) + MAX_TYPE_2(double,unsigned short) + + /** + * \class Max_type + * \brief Template that estimates maximal of the mathematical types. + * \param T_1 first type. + * \param T_2 second type. + * \return type e.g. typeid (typename Max_type ::type) == typeid (double), + * typeid (typename Max_type <::std::complex<::boost::int32_t>,double>::type) == typeid (::std::complex). + */ + template + < + typename T_1, + typename T_2 + > + class Max_type + { + private: + typedef typename ::boost::remove_all_extents < T_1 >::type T_1_t; + typedef typename ::boost::remove_all_extents < T_2 >::type T_2_t; + + public: + typedef typename Max_type_private < T_1_t, T_2_t >::type type; + }; + + //#undef MAX_TYPE_2 + //#undef MAX_TYPE_3 + +} // namespace operators +/*\@}*/ +#endif // MAX_TYPE_HPP_INCLUDED + diff --git a/src/third-party/knnl/neural_net_headers.hpp b/src/third-party/knnl/neural_net_headers.hpp new file mode 100755 index 00000000..5993c42a --- /dev/null +++ b/src/third-party/knnl/neural_net_headers.hpp @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2006, Seweryn Habdank-Wojewodzki + * Copyright (c) 2006, Janusz Rybarski + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions + * and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS + * AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * e-mail: habdank AT gmail DOT com + * e-mail: janusz.rybarski AT gmail DOT com + * + * File created: Fri 12 May 2006 14:39:01 CEST + * Last modified: Sat 16 Dec 2006 16:06:28 CET + */ + +#ifndef NEURAL_NET_HEADERS_HPP_INCLUDED +#define NEURAL_NET_HEADERS_HPP_INCLUDED + +/** \mainpage Kohonen Neural Network Library + * + * \section intro_sec Introduction + * + * KNNL is header based library for developing Kohonen Neural Networks. + * + */ + + +/** + * \file neural_net_headers.hpp + * \brief All headers for kohonen neural network. + * \ingroup neural_net + */ + +#include "basic_neuron.hpp" +#include "euclidean_distance_function.hpp" +#include "functors.hpp" +#include "generalized_training_weight.hpp" +#include "kohonen_network.hpp" +#include "print_network.hpp" +#include "randomize_policy.hpp" +#include "ranges.hpp" +#include "rectangular_container.hpp" +#include "training_functional.hpp" +#include "weighted_euclidean_distance_function.hpp" +#include "wta_training_algorithm.hpp" +#include "wtm_topology.hpp" +#include "wtm_training_algorithm.hpp" + + +#endif // NEURAL_NET_HEADERS_HPP_INCLUDED + diff --git a/src/third-party/knnl/numeric_iterator.hpp b/src/third-party/knnl/numeric_iterator.hpp new file mode 100755 index 00000000..cb787c35 --- /dev/null +++ b/src/third-party/knnl/numeric_iterator.hpp @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2006, Seweryn Habdank-Wojewodzki + * Copyright (c) 2006, Janusz Rybarski + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions + * and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS + * AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * e-mail: habdank AT gmail DOT com + * e-mail: janusz.rybarski AT gmail DOT com + * + * File created: Sun 18 Jun 2006 13:22:04 CEST + * Last modified: Sun 26 Nov 2006 08:54:24 CET + */ + +#ifndef NUMERIC_ITERATOR_HPP_INCLUDED +#define NUMERIC_ITERATOR_HPP_INCLUDED + +#include + +/** + * \file numeric_iterator.hpp + * \brief File contains template classes for preparing numeric iterators. + * \ingroup neural_net + */ + +namespace neural_net +{ + /** + * \addtogroup neural_net + */ + /*\@{*/ + + /** + * \class Numeric_iterator + * \brief Class that defines bahavior of the numeric iterator. + * \param Value_type is a type of value. + */ + template < typename Value_type > + struct Numeric_iterator + { + typedef Value_type value_type; + value_type state; + + /** + * Constructor. + * \param state_ is a state in the beginning of the work of iterator + */ + Numeric_iterator ( Value_type state_ ) + : state ( state_ ) + {} + }; + + /** + * \class Linear_numeric_iterator + * \brief Class that defines bahavior of the linear numeric iterator. + * It begins with given value and iterate using given step. + * \param Value_type is a type of value. + */ + template < typename Value_type > + class Linear_numeric_iterator + : public Numeric_iterator < Value_type > + { + public: + /** + * Constructor. + * \param state_ is a begin value. + * \param step_ is a step value. + */ + Linear_numeric_iterator ( + Value_type state_ = static_cast < Value_type > ( 0 ), + Value_type step_ = static_cast < Value_type > ( 1 ) + ) + : Numeric_iterator < Value_type > ( state_ ), step ( step_ ) + {} + + /** + * Reset state. + * \param state_ is a new state. + */ + void reset ( Value_type const & state_ = static_cast < Value_type > ( 0 ) ) + { + Numeric_iterator < Value_type >::state = state_; + return; + } + + /** + * Method returns state. + */ + inline Value_type operator() ( void ) const + { + return ( Numeric_iterator < Value_type >::state ); + } + + /** + * Preincrementation. + */ + inline Linear_numeric_iterator < Value_type > & + operator++() + { + Numeric_iterator < Value_type >::state += step; + return *this; + } + + /** + * Postincrementation. + */ + inline Linear_numeric_iterator < Value_type > + operator++ ( int ) + { + typedef Numeric_iterator < Value_type > Value_numeric_iterator; + Value_type tmp ( Value_numeric_iterator::state ); + Numeric_iterator < Value_type >::state += step; + return tmp; + } + + ///\todo TODO: try to prepare conversion on Value_type + /*inline const Value_type operator Value_type ( void ) const + { + return Numeric_iterator::state; + }*/ + + protected: + Value_type step; + }; + + typedef Linear_numeric_iterator < ::boost::int32_t > linear_numeric_iterator; + /*\@}*/ + +} // namespace neural_net + +#endif // NUMERIC_ITERATOR_HPP_INCLUDED + diff --git a/src/third-party/knnl/operators.hpp b/src/third-party/knnl/operators.hpp new file mode 100755 index 00000000..bbf198ff --- /dev/null +++ b/src/third-party/knnl/operators.hpp @@ -0,0 +1,440 @@ +/* + * Copyright (c) 2006, Seweryn Habdank-Wojewodzki + * Copyright (c) 2006, Janusz Rybarski + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions + * and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS + * AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * e-mail: habdank AT gmail DOT com + * e-mail: janusz.rybarski AT gmail DOT com + * + * File created: Tue 11 Apr 2006 17:47:44 CEST + * Last modified: Wed 08 Aug 2007 18:29:02 CEST + */ + +#ifndef OPERATORS_HPP_INCLUDED +#define OPERATORS_HPP_INCLUDED + +#include +#include +#include + +#include + +#include "max_type.hpp" + +/** + * \defgroup operators Operators + */ + +/** + * \file operators.hpp + * \brief File contains template operators. + * \ingroup operators + */ + +/** + * \namespace operators + * \brief Operators. + * \ingroup operators + */ + +/** \addtogroup operators */ +/*\@{*/ +namespace operators +{ + /** + * Absolute function. + * \param value is value. + * \return absolute value. + */ + template < typename T > + inline T abs ( T const & value ) + { + return ( value > 0 ? value : -value ); + } + + /** + * \class compose_f_gxy_gxy_t + * \brief Adaptator class compose_f_gxy_gxy_t. + * \param OP1 is a type of first operator f. + * \param OP2 is a type of second operator g. + * \f[ + * y=f (g (x,y),g (x,y)) + * \f] + */ + template < typename OP1, typename OP2 > + class compose_f_gxy_gxy_t + : public ::std::binary_function + < + typename OP2::first_argument_type, + typename OP2::second_argument_type, + typename OP1::result_type + > + { + public: + /** + * Constructor. + * \param o1 is a reference to the f operator. + * \param o2 is a reference to the g operator. + */ + compose_f_gxy_gxy_t + ( + OP1 const & o1, + OP2 const & o2 + ) + : op1 ( o1 ), op2 ( o2 ) + {} + + /** + * Function calculate results. + * \param x is first argument. + * \param y is second argument. + * \f[ + * y=f (g (x,y),g (x,y)) + * \f] + * where: f is OP1 type, g is OP2 type + */ + typename OP1::result_type + operator() + ( + typename OP2::first_argument_type const & x, + typename OP2::second_argument_type const & y + ) const + { + return op1 ( op2 ( x, y ),op2 ( x, y ) ); + } + + private: + /** First operator f. */ + OP1 op1; // calculate: op1 (op2 (x,y),op2 (x,y)) + + /** Secong operator g. */ + OP2 op2; + }; + + /** + * Useful function for creating adaptator compose_f_gxy_gxy. + * \param o1 is first operator. + * \param o2 is send operator. + * \return composition of the operators. + */ + template < class OP1, class OP2 > + inline compose_f_gxy_gxy_t < OP1, OP2 > + compose_f_gxy_gxy + ( + OP1 const & o1, + OP2 const & o2 + ) + { + return compose_f_gxy_gxy_t < OP1, OP2 > ( o1, o2 ); + } + + /** + * Overloading operator+ for containers. + * \param lhs is a reference to container x. + * \param rhs is a reference to container y. + * \return container. + * \f[ + * v_i = x_i + y_i + * \f] + * where: x is lhs and y is rhs. + */ + template + < + typename T, + template < typename > class Alloc_type, + template < typename, typename > class CONT + > + CONT < T, Alloc_type > + operator+ + ( + CONT < T, Alloc_type > const & lhs, + CONT < T, Alloc_type > const & rhs + ) + { + CONT < T, Alloc_type > result ( lhs ); + + ::std::transform + ( + result.begin(), + result.end(), + rhs.begin(), + result.begin(), + ::std::plus < typename CONT < T, Alloc_type >::value_type >() + ); + + return result; + } + + /** + * Overloading operator- for containers. + * \param lhs is a reference to container x. + * \param rhs is a reference to container y. + * \return container. + * \f[ + * v_i = x_i - y_i + * \f] + * where: x is lhs and y is rhs. + */ + template + < + typename T, + template < typename > class Alloc_type, + template < typename, typename > class CONT + > + CONT < T, Alloc_type > + operator- + ( + CONT < T, Alloc_type > const & lhs, + CONT < T, Alloc_type > const & rhs + ) + { + CONT < T , Alloc_type > result ( lhs ); + + ::std::transform + ( + result.begin(), + result.end(), + rhs.begin(), + result.begin(), + ::std::minus < typename CONT < T , Alloc_type >::value_type >() + ); + + return result; + } + + /** + * Overloading operator* for container as product of the scalar value and container. + * \param a is a reference to container x. + * \param rhs is a reference to container y. + * \return container. + * \f[ + * v_i = a * y_i + * \f] + * where: a is scaling coefficient and y is rhs. + */ + template + < + typename K, + typename T, + template < typename > class Alloc_type, + template < typename, typename > class CONT + > + CONT < T, Alloc_type > + operator* + ( + K const & a, + CONT < T, Alloc_type > const & rhs + ) + { + CONT < T , Alloc_type > result ( rhs ); + + ::std::transform + ( + result.begin(), + result.end(), + result.begin(), + ::std::bind2nd + ( + ::std::multiplies < typename CONT < T , Alloc_type >::value_type >(), + a + ) + ); + + return result; + } + + /** + * Template function calculates inverse of the value. + * It could be overloaded/specialized for matrix + * and other complicated types. + * \param x is a value to be inversed. + */ + template < typename Value_type > + inline typename Max_type < double, Value_type >::type + inverse ( Value_type const & x ) + { + typedef typename Max_type < double, Value_type >::type internal_type; + + return + ( + static_cast < internal_type > ( 1 ) + / static_cast < internal_type > ( x ) + ); + } + + /** + * \class power + * \brief Helper class for calculating power. + * \param T is value type. + * \param E is exponent type. + */ + template + < + typename T, + typename E, + bool ISINTEGRAL = ::boost::is_integral::value + > + class power; + + /** + * Specialization for the integral exponents. + * \param T is value type. + * \param E is exponent type. + * \f[ + * y=v^e + * \f] + */ + template < typename T, typename E > + class power < T, E, true > + { + public: + typedef typename Max_type < T, E >::type result_type; + + result_type operator() ( T const & value_, E const & exp_ ) const + { + if ( exp_ == 0 ) + { + return static_cast < result_type > ( 1 ); + } + + if ( exp_ < 0 ) + { + return power_int ( value_, -exp_ ); + } + else + { + return power_int ( value_, exp_ ); + } + } + + private: + /** + * Fast power algorithm. + * \param value_ value. + * \param exp_ exponent factor. + * \return value of power + * \f[ + * z=x^y + * \f] + * where: x is value_, y is exp_. + */ + result_type power_int ( T const & value_, E const & exp_ ) const + { + T z = value_; + result_type y; + E m = exp_; + + while ( ! ( m & 1 ) ) + { + m = m / 2; + z = z * z; + } + y = z; + + while ( m > 1 ) + { + m = m / 2; + z = z * z; + if ( m & 1 ) + { + y = y * z; + } + } + return y; + } + }; + + /** + * Specialization for the real exponents. + * \param T is value type. + * \param E is exponent type. + * \f[ + * y=v^e + * \f] + */ + template < typename T, typename E > + class power < T, E, false > + { + public: + typedef typename Max_type < T, E >::type result_type; + + /** + * Fast power algorithm. + * \param value_ value. + * \param exp_ exponent factor. + * \return value of power + * \f[ + * z=x^y + * \f] + * where: x is value_, y is exp_. + */ + result_type operator() ( T const & value_, E const & exp_ ) const + { + return ::std::pow ( static_cast < result_type > ( value_ ), exp_ ); + } + }; + + template < typename T, ::boost::int32_t N > + struct static_power_t; + + template < typename T > + struct static_power_t + { + T operator()(T const) + { + return static_cast(1); + } + }; + + template < typename T, ::boost::int32_t N > + struct static_power_t + { + T operator()( T const x ) + { + //static_power_t sp; + return x * static_power_t()(x); + } + }; + + template < typename T, ::boost::int32_t N > + T static_power ( T const x ) + { + return static_power_t()(x); + } + +} // namespace operators +/*\@}*/ +#endif // OPERATORS_HPP_INCLUDED + diff --git a/src/third-party/knnl/print_network.hpp b/src/third-party/knnl/print_network.hpp new file mode 100755 index 00000000..fb027251 --- /dev/null +++ b/src/third-party/knnl/print_network.hpp @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2006, Seweryn Habdank-Wojewodzki + * Copyright (c) 2006, Janusz Rybarski + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions + * and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS + * AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * e-mail: habdank AT gmail DOT com + * e-mail: janusz.rybarski AT gmail DOT com + * + * File created: Fri 21 Apr 2006 19:30:09 CEST + * Last modified: Wed 08 Aug 2007 17:26:34 CEST + */ + +#ifndef PRINT_NETWORK_HPP_INCLUDED +#define PRINT_NETWORK_HPP_INCLUDED + +#include +#include +#include + +/** + * \file print_network.hpp + * \brief File contains template function to print network. + * \ingroup neural_net + */ + +namespace neural_net +{ + /** + * \addtogroup neural_net + */ + /*\@{*/ + + /** + * Function prints weight of the neural network. + * \param os is output stream. + * \param network is a reference to network. + * \return modified stream. + */ + template < typename T > + ::std::ostream & print_network_weights + ( ::std::ostream & os, T const & network, char const * sep = "\t" ) + { + const typename T::row_size_t M = network.objects.size(); + const typename T::col_size_t N = network.objects[0].size(); + + for ( typename T::row_size_t i = 0; i < M; ++i ) + { + for ( typename T::col_size_t j = 0; j < N; ++j ) + { + ::std::copy + ( + network.objects[i][j].weights.begin(), + network.objects[i][j].weights.end(), + ::std::ostream_iterator + < + typename T::value_type::weights_type::value_type + > ( os, sep ) + ); + os << ::std::endl; + } + } + return os; + } + + /** + * Function puts container of any values to the stream. + * \param os is a output stream. + * \param value is a value. + * \return modified stream. + */ + template < typename T > + inline ::std::ostream & container_to_ostream ( ::std::ostream & os, T const & container ) + { + typedef typename T::value_type Value; + ::std::copy ( container.begin(), container.end(), ::std::ostream_iterator < Value > ( os, " " ) ); + return os; + } + + /** + * Function prints structure and results of the neural network. + * \param os is output stream. + * \param network is a reference to network. + * \param value is a value that network will calculate results. + * \return modified stream. + */ + template < typename T, typename U > + ::std::ostream & print_network ( ::std::ostream & os, T const & network, U const & value ) + { + const typename T::row_size_t M = network.objects.size(); + const typename T::col_size_t N = network.objects[0].size(); + + for ( typename T::row_size_t i = 0; i < M; ++i ) + { + for ( typename T::col_size_t j = 0; j < N; ++j ) + { + os << "weights[" << i <<"][" << j << "] = "; + ::std::copy + ( + network.objects[i][j].weights.begin(), + network.objects[i][j].weights.end(), + ::std::ostream_iterator + < + typename T::value_type::weights_type::value_type + > ( os, "\t" ) + ); + os << " ( "; + + container_to_ostream ( os, value ); + + os << " ) == "; + //os << network.objects[i][j] ( value ); + //os << " == "; + os << network.objects [ i ][ j ]( value ); + os << ::std::endl; + } + os << ::std::endl; + } + return os; + } + /*\@}*/ +}// namespace neural_net + +#endif // PRINT_NETWORK_HPP_INCLUDED + diff --git a/src/third-party/knnl/randomize_policy.hpp b/src/third-party/knnl/randomize_policy.hpp new file mode 100755 index 00000000..80f473a5 --- /dev/null +++ b/src/third-party/knnl/randomize_policy.hpp @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2006, Seweryn Habdank-Wojewodzki + * Copyright (c) 2006, Janusz Rybarski + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions + * and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS + * AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * e-mail: habdank AT gmail DOT com + * e-mail: janusz.rybarski AT gmail DOT com + * + * File created: Mon 22 May 2006 20:03:59 CEST + * Last modified: Wed 08 Aug 2007 17:27:14 CEST + */ + +#ifndef RANDOMIZE_POLICY_HPP_INCLUDED +#define RANDOMIZE_POLICY_HPP_INCLUDED + +#include +#include + +/** + * \file randomize_policy.hpp + * \brief File contains policy classes to set randomize functionality + * in to network generation process. + * \ingroup neural_net + */ + +namespace neural_net +{ + /** + * \addtogroup neural_net + */ + /*\@{*/ + class Randomize_policy + { + typedef Randomize_policy this_type; + }; + + /** + * \class External_randomize + * \brief This class force not to use srand() in generation proces, + * therefore srand function is initialized externally. + */ + struct External_randomize + : public Randomize_policy + { + typedef External_randomize this_type; + + /** Do nothing :-). */ + void operator() ( void ) const + { + return; + } + }; + + /** + * \class Internal_randomize + * \brief This class force to use srand() in generation proces, + * therefore srand function is NOT initialized. + */ + struct Internal_randomize + : public Randomize_policy + { + typedef Internal_randomize this_type; + + /** Initialize random number generator using srand(). */ + void operator() ( void ) const + { + ::std::srand (static_cast (::std::time (NULL))); + return; + } + }; + /*\@}*/ + +} // namespace neural_net + +#endif // RANDOMIZE_POLICY_HPP_INCLUDED + diff --git a/src/third-party/knnl/ranges.hpp b/src/third-party/knnl/ranges.hpp new file mode 100755 index 00000000..ce084f4c --- /dev/null +++ b/src/third-party/knnl/ranges.hpp @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2006, Seweryn Habdank-Wojewodzki + * Copyright (c) 2006, Janusz Rybarski + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions + * and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS + * AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * e-mail: habdank AT gmail DOT com + * e-mail: janusz.rybarski AT gmail DOT com + * + * File created: Mon 24 Apr 2006 12:11:38 CEST + * Last modified: Wed 08 Aug 2007 17:27:38 CEST + */ + +#ifndef RANGES_HPP_INCLUDED +#define RANGES_HPP_INCLUDED + +#include +#include + +/** + * \file ranges.hpp + * \brief File contains template class Ranges. + * \ingroup neural_net + */ + +namespace neural_net +{ + /** + * \addtogroup neural_net + */ + /*\@{*/ + + /** + * \class Ranges + * \brief Class creates and claculates ranges of data. + * \param Container_type is a type of container. + */ + template < typename Container_type > + class Ranges + { + public: + /** + * Constructor. + * \param value_ is a value that will be set at he begining. + */ + explicit Ranges ( typename Container_type::value_type const & value_ ) + :ranges ( value_, value_ ) + {} + + /** Copy constructor. */ + template < typename Container_type_2 > + Ranges ( Ranges < Container_type_2 > const & ranges_ ) + : ranges ( ranges_.ranges ) + {} + + /** + * Function that is going through the all container. + * \param value is a reference to the container. + */ + void operator() ( Container_type const & value ) + { + typename Container_type::const_iterator pos; + + typename Container_type::value_type::const_iterator pos_range; + typename Container_type::value_type::iterator pos_min_range; + typename Container_type::value_type::iterator pos_max_range; + + // go through the all data in container and ... + for ( pos = value.begin(); pos != value.end(); ++pos ) + { + // look for the highest and lowest values to set ranges. + // iterate through position of the container and ranges type to compare + // proper values and set ranges. + for + ( + pos_min_range = ranges.first.begin(), + pos_max_range = ranges.second.begin(), + pos_range = pos->begin(); + pos_min_range != ranges.first.end(); + ++pos_min_range, ++pos_max_range, ++pos_range + ) + { + *pos_min_range = ::std::min ( *pos_min_range, *pos_range ); + *pos_max_range = ::std::max ( *pos_max_range, *pos_range ); + } + } + } + + /** + * Function returns supremum value of the data in the container. + * This value could not exests in container, because is created from + * all maximums of data e.g. { (1,2), (2,1) } -> (2,2) + */ + typename Container_type::value_type const & get_max() const + { + return ranges.second; + } + + /** + * Function returns infimum of the data in the container. + * This value could not exests in container, because is created from + * all minimums of data e.g. { (1,2), (2,1) } -> (1,1) + */ + typename Container_type::value_type const & get_min() const + { + return ranges.first; + } + + protected: + Ranges(); + + private: + ::std::pair + < + typename Container_type::value_type, + typename Container_type::value_type + > ranges; + + }; + /*\@}*/ + +} // namespace neural_net + +#endif // RANGES_HPP_INCLUDED + diff --git a/src/third-party/knnl/rectangular_container.hpp b/src/third-party/knnl/rectangular_container.hpp new file mode 100755 index 00000000..9edbd366 --- /dev/null +++ b/src/third-party/knnl/rectangular_container.hpp @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2006, Seweryn Habdank-Wojewodzki + * Copyright (c) 2006, Janusz Rybarski + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions + * and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS + * AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * e-mail: habdank AT gmail DOT com + * e-mail: janusz.rybarski AT gmail DOT com + * + * File created: Fri 14 Apr 2006 22:37:57 CEST + * Last modified: Wed 08 Aug 2007 18:29:17 CEST + */ + +#ifndef RECTANGULAR_CONTAINER_HPP_INCLUDED +#define RECTANGULAR_CONTAINER_HPP_INCLUDED + +#include + +/** + * \file rectangular_container.hpp + * \brief File contains class Rectangular_container + * \ingroup neural_net + */ + +namespace neural_net +{ + /** + * \addtogroup neural_net + */ + /*\@{*/ + + /** + * Rectangular_grid_container template class. + * \param Object_type is type of stored objects. + * \todo TODO: Rectangular_container class should be rewritten + * using new version of ::boost::matrix. + */ + template + < + typename Object_type + > + class Rectangular_container + { + public: + + typedef Object_type value_type; + typedef typename ::std::vector < Object_type > column_type; + typedef typename ::std::vector < column_type > row_type; + + typedef typename row_type::iterator row_iterator; + typedef typename column_type::iterator column_iterator; + + typedef typename row_type::size_type row_size_t; + typedef typename column_type::size_type col_size_t; + + /** + * Stores size of container. + */ + typedef ::std::pair < row_size_t, col_size_t > Matrix_index; + + /** Objects. */ + row_type objects; + + /** + * Get object. + * \param i is the row number. + * \param j is the column number. + * \return const reference to the (i,j)-th object. + */ + Object_type const & operator() ( row_size_t i, col_size_t j ) + { + return objects[i][j]; + } + + /** + * Constructor. + */ + Rectangular_container() + {} + + /** Copy constructor. */ + template < typename Object_type_2 > + Rectangular_container + ( + Rectangular_container < Object_type_2 > const & rectangular_container + ) + : objects ( rectangular_container.objects ) + {} + + inline col_size_t get_no_columns() const + { + return objects.begin()->size(); + } + + inline row_size_t get_no_rows() const + { + return objects.size(); + } + + inline Matrix_index get_size() const + { + Matrix_index idx; + idx.first = objects.size(); + idx.second = objects.begin()->size(); + + return idx; + } + + }; + /*\@}*/ + +} // namespace neural_net + +#endif // RECTANGULAR_CONTAINER_HPP_INCLUDED + diff --git a/src/third-party/knnl/std_headers.hpp b/src/third-party/knnl/std_headers.hpp new file mode 100755 index 00000000..3c4f82b5 --- /dev/null +++ b/src/third-party/knnl/std_headers.hpp @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2006, Seweryn Habdank-Wojewodzki + * Copyright (c) 2006, Janusz Rybarski + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions + * and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS + * AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * e-mail: habdank AT gmail DOT com + * e-mail: janusz.rybarski AT gmail DOT com + * + * File created: Fri 12 May 2006 14:39:01 CEST + * Last modified: Sun 18 Jun 2006 09:44:28 CEST + */ + +#ifndef STD_HEADERS_HPP_INCLUDED +#define STD_HEADERS_HPP_INCLUDED + +/** + * \file std_headers.hpp + * \brief Contains all important standard headers. + * \ingroup neural_net + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif // STD_HEADERS_HPP_INCLUDED + diff --git a/src/third-party/knnl/training_functional.hpp b/src/third-party/knnl/training_functional.hpp new file mode 100755 index 00000000..1bbca798 --- /dev/null +++ b/src/third-party/knnl/training_functional.hpp @@ -0,0 +1,357 @@ +/* + * Copyright (c) 2006, Seweryn Habdank-Wojewodzki + * Copyright (c) 2006, Janusz Rybarski + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions + * and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS + * AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * e-mail: habdank AT gmail DOT com + * e-mail: janusz.rybarski AT gmail DOT com + * + * File created: Wed 26 Apr 2006 14:55:28 CEST + * Last modified: Wed 08 Aug 2007 18:25:18 CEST + */ + +#ifndef TRAINING_FUNCTIONAL_HPP_INCLUDED +#define TRAINING_FUNCTIONAL_HPP_INCLUDED + +/** + * \file training_functional.hpp + * \brief File contains template classes that support + * training proces of the network. + * \ingroup neural_net + */ + +namespace neural_net +{ + /** + * \addtogroup neural_net + */ + /*\@{*/ + + /** + * \class Basic_training_functional + * \brief Basic trainng functional. + */ + class Basic_training_functional + {}; + + /** + * \class Basic_wta_training_functional + * \brief Class that is basic for Winner Takes All (WTA) algorithms. + * \param Value_type is a type of values. + * \param Parameters_type is a type of parameters of training functional. + */ + template + < + typename Value_type, + typename Parametres_type + > + class Basic_wta_training_functional + : public Basic_training_functional + { + public: + typedef Value_type value_type; + typedef Parametres_type parameters_type; + }; + + /** + * \class Basic_wtm_training_functional + * \brief Class that is basic for Winner Takes Most (WTM) algorithms. + * \param Value_type is a type of values. + * \param Parameters_type is a type of parameters of training functional. + * \param Iteration_type is a type of step counter value. + * \param Index_type is a type of index used in network. + * \param Topology_type is a type of topology that should be used + * for measuring distance in network. + */ + template + < + typename Value_type, + typename Parameters_type, + typename Iteration_type, + typename Index_type, + typename Topology_type + > + class Basic_wtm_training_functional + : public Basic_wta_training_functional + < Value_type, Parameters_type > + { + public: + typedef Iteration_type iteration_type; + typedef Index_type index_type; + typedef Topology_type topology_type; + }; + + /** + * \class Wta_proportional_training_functional + * \brief Class that certain kind of WTA algorithm. + * \param Value_type is a type of values. + * \param Parameters_type is a type of parameters of training functional. + * \param Iteration_type is a type of step counter value. + * Algorithm in the time of training for single data set could change + * weight in training function. + * \f[ + * w_{i,j} (t+1)=w_{i,j} (t) + ( p_0 + p_1 * s ) * ( x (t) - w_{i,j} (t) ) + * \f] + */ + template + < + typename Value_type, + typename Parameters_type, + typename Iteration_type + > + class Wta_proportional_training_functional + : public Basic_wta_training_functional + < + Value_type, + Parameters_type + > + { + public: + + typedef Iteration_type iteration_type; + + /** Shifting parameter for linear function */ + Parameters_type parameter_0; + + /** Scaling parameter for linear function */ + Parameters_type parameter_1; + + /** + * Constructor. + * \param parameter_0_ is a shifting parameter for linear function + * used for training. + * \param parameter_1_ is a scaling parameter for linear function + * used for training. + */ + Wta_proportional_training_functional + ( + Parameters_type const & parameter_0_, + Parameters_type const & parameter_1_ + ) + : parameter_0 ( parameter_0_ ), parameter_1 ( parameter_1_ ) + {} + + /** Copy constructor. */ + template + < + typename Value_type_2, + typename Parameters_type_2, + typename Iteration_type_2 + > + Wta_proportional_training_functional + ( + Wta_proportional_training_functional + < + Value_type_2, + Parameters_type_2, + Iteration_type_2 + > + const & training_functional_ + ) + : Basic_wta_training_functional < Value_type, Parameters_type >(), + parameter_0 ( training_functional_.parameter_0 ), + parameter_1 ( training_functional_.parameter_1 ) + {} + + /** + * Training function. + * \param weight is weight of the neuron. + * \param value is value that trains winner neuron. + * \param s is step number. + * \return modified weight. + * \f[ + * w_{i,j} (t+1)=w_{i,j} (t) + ( p_0 + p_1 * s ) * ( x (t) - w_{i,j} (t) ) + * \f] + * where: x is value, w is weight and s is step number. + */ + Value_type & operator() + ( + Value_type & weight, + Value_type const & value, + iteration_type const & s + ) const + { + using namespace ::operators; + return + ( + weight + = weight + + ( parameter_0 + parameter_1 * s ) + * ( value - weight ) + ); + } + }; + + /** + * \class Wtm_classical_training_functional + * \brief Class that is basic for Winner Takes Most (WTM) algorithms. + * \param Value_type is a type of values. + * \param Parameters_type is a type of parameters of training functional. + * \param Iteration_type is a type of step counter value. + * \param Index_type is a type of index used in network. + * \param Generalized_training_weight_type is a type of functor that will + * be used for set up training weight. + */ + template + < + typename Value_type, + typename Parameters_type, + typename Iteration_type, + typename Index_type, + typename Generalized_training_weight_type + > + class Wtm_classical_training_functional + : public Basic_wtm_training_functional + < + Value_type, + Parameters_type, + Iteration_type, + Index_type, + Generalized_training_weight_type + > + { + public: + + /** Final scaling of the function. */ + Parameters_type parameter; + + /** Functor calculates training weight. */ + Generalized_training_weight_type generalized_training_weight; + + /** + * Constructor. + * \param generalized_weight is a reference to the functor. + * \param parameter_ is a reference to scaling parameter. + */ + Wtm_classical_training_functional + ( + Generalized_training_weight_type const & generalized_weight, + Parameters_type const & parameter_ + ) + : Basic_wtm_training_functional + < + Value_type, + Parameters_type, + Iteration_type, + Index_type, + Generalized_training_weight_type + >(), + parameter ( parameter_ ), + generalized_training_weight ( generalized_weight ) + {} + + /** Copy constructor. */ + template + < + typename Value_type_2, + typename Parameters_type_2, + typename Iteration_type_2, + typename Index_type_2, + typename Generalized_training_weight_type_2 + > + Wtm_classical_training_functional + ( + const Wtm_classical_training_functional + < + Value_type_2, + Parameters_type_2, + Iteration_type_2, + Index_type_2, + Generalized_training_weight_type_2 + > + & training_functional_ + ) + : Basic_wtm_training_functional + < + Value_type, + Parameters_type, + Iteration_type, + Index_type, + Generalized_training_weight_type + >(), + parameter ( training_functional_.parameter ), + generalized_training_weight ( training_functional_.generalized_training_weight ) + {} + + /** + * Function calculates new value of the neuron weight. + * \param weight is a reference to the trained neuron weight. + * \param value is a reference to the value that trains neuron. + * \param s is step number. + * \param center_i is a first index of the central neuron (row number). + * \param center_j is a second index of the central neuron (column number). + * \param i_ is a first index of a trained neuron (row number). + * \param j_ is a second index of a trained neuron (column number). + * \return modified neuron weight. + * \f[ + * w_{i,j} (t+1) = w_{i,j} (t) + G ( w_{i,j} (t), x (t), s, c_i, c_j, i, j ) * ( x (t) - w_{i,j} (t) ) + * \f] + */ + Value_type & operator() + ( + Value_type & weight, + Value_type const & value, + Iteration_type const & s, + Index_type const & center_i, + Index_type const & center_j, + Index_type const & i_, + Index_type const & j_ + ) + { + using namespace ::operators; + + return + ( + weight + = weight + + parameter + * (generalized_training_weight) + ( + weight, + value, + s, + center_i, center_j, + i_, j_ + ) + * ( value - weight ) + ); + } + }; + /*\@}*/ + +} // namespace neural_net + +#endif // TRAINING_FUNCTIONAL_HPP_INCLUDED + diff --git a/src/third-party/knnl/value_type.hpp b/src/third-party/knnl/value_type.hpp new file mode 100755 index 00000000..d9949c6d --- /dev/null +++ b/src/third-party/knnl/value_type.hpp @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2006, Seweryn Habdank-Wojewodzki + * Copyright (c) 2006, Janusz Rybarski + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions + * and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS + * AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * e-mail: habdank AT megapolis DOT pl + * e-mail: janusz.rybarski AT gmail DOT com + * + * File created: Thu 08 Jun 2006 18:12:00 CEST + * Last modified: Thu 08 Jun 2006 18:18:00 CEST + */ + +#ifndef VALUE_TYPE_HPP_INCLUDED +#define VALUE_TYPE_HPP_INCLUDED + +#include + +/** + * \file value_type.hpp + * \brief File contains template to recognize type of value. + * \ingroup operators + */ + +namespace operators +{ + /** + * \addtogroup operators + */ + /*\@{*/ + + template < typename T > + struct Value_type + { + typedef typename T::value_type type; + }; + + template <> + struct Value_type < double > + { + typedef double type; + }; + + template <> + struct Value_type < unsigned int > + { + typedef double type; + }; + + template <> + struct Value_type < ::boost::int32_t > + { + typedef double type; + }; + + template <> + struct Value_type < unsigned long > + { + typedef double type; + }; + + /*\@}*/ + +} // namespace operators + +#endif // VALUE_TYPE_HPP_INCLUDED + diff --git a/src/third-party/knnl/weighted_euclidean_distance_function.hpp b/src/third-party/knnl/weighted_euclidean_distance_function.hpp new file mode 100755 index 00000000..791d8d2d --- /dev/null +++ b/src/third-party/knnl/weighted_euclidean_distance_function.hpp @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2006, Seweryn Habdank-Wojewodzki + * Copyright (c) 2006, Janusz Rybarski + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions + * and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS + * AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * e-mail: habdank AT gmail DOT com + * e-mail: janusz.rybarski AT gmail DOT com + * + * File created: Mon 17 Apr 2006 23:54:20 CEST + * Last modified: Sun 26 Nov 2006 09:33:07 CET + */ + +#ifndef WEIGHTED_EUCLIDEAN_DISTANCE_FUNCTION_HPP_INCLUDED +#define WEIGHTED_EUCLIDEAN_DISTANCE_FUNCTION_HPP_INCLUDED + +#include "operators.hpp" +#include "basic_weak_distance_function.hpp" +#include + +#include "value_type.hpp" + +/** + * \file weighted_euclidean_distance_function.hpp + * \brief File contains template class Weighted_euclidean_distance_function. + * \ingroup distance + */ + +namespace distance +{ + /** + * \addtogroup distance + */ + /*\@{*/ + + /** + * Weighted_euclidean_distance_function template class. + * \param Value_type is a type of values. + * \param Parameters_type is a type of parameters (weights) used in weighted Euclidean distance. + * \f[ + * d (x,y,w) = \sum\limits_{i=0}^{N} w_i\cdot (x_i-y_i)^2 + * \f] + */ + template + < + typename Parameters_type, + typename Value_type + > + class Weighted_euclidean_distance_function + : public Basic_weak_distance_function + < + Value_type, + Value_type + > + { + private: + typedef typename Value_type::value_type inner_type; + + /** Parameters. */ + Parameters_type const * parameters; + + /** Parameters size. */ + ::boost::int32_t parameters_size; + + public: + typedef Parameters_type parameters_type; + + /** + * Constructor. + */ + explicit Weighted_euclidean_distance_function ( Parameters_type const * weights ) + : parameters ( weights ) + { + assert ( parameters != static_cast < Parameters_type * > ( 0 ) ); + parameters_size = parameters->size(); + } + + /** + * Calculation function. + * \param x input value for the function. + * \param y input value for the function. + * \return square of the weighted Euclidean distance function. + * \f[ + * d (x,y,w) = \sum\limits_{i=0}^{N} w_i\cdot (x_i-y_i)^2 + * \f] + * where: w are parameters given in constructor. + */ + inner_type operator() + ( + Value_type const & x, + Value_type const & y + ) const + { + return + ( + weighted_euclidean_distance_square + ( + x.begin(), + x.end(), + y.begin(), + parameters->begin(), + static_cast < inner_type const & > (0) + ) + ); + } + + /** Copy constructor */ + template + < + typename Parameters_type_2, + typename Value_type_2 + > + Weighted_euclidean_distance_function + ( + Weighted_euclidean_distance_function + < + Parameters_type_2, + Value_type_2 + > + const & weighted_euclidean_distance + ) + : parameters ( weighted_euclidean_distance.parameters ) + {} + + /** Operator= */ + template + < + typename Parameters_type_2, + typename Value_type_2 + > + Weighted_euclidean_distance_function + < + Parameters_type, + Value_type + > & + operator= + ( + Weighted_euclidean_distance_function + < + Parameters_type_2, + Value_type_2 + > + const & weighted_euclidean_distance + ) + { + // Handle self-assignment: + if ( this == &weighted_euclidean_distance ) + { + return *this; + } + + parameters = weighted_euclidean_distance.parameters; + parameters_size = weighted_euclidean_distance.parameters_size; + + return *this; + } + + private: + /** + * Function calculates weighted Euclidean distance between two containers. + * \param begin_1 is a begin iterator for the first container. + * \param end_1 is an end iterator for the first container. + * \param begin_2 is a begin iterator for the second container. + * \param begin_3 is a bagin iterator for the parameters. + * \param init is an initial value. + * \result sqare of weighted Euclidean distance. + * \f[ + * d (x,y,w) = d_0 + \sum\limits_{i=0}^{N} w_i\cdot (x_i-y_i)^2 + * \f] + * where: x is given by range of Input_iterator_1, + * y is given through Input_iterator_2, + * w is given through Input_iterator_3, + * \f$d_0\f$ is initial value (init). + */ + inner_type weighted_euclidean_distance_square + ( + typename Value_type::const_iterator begin_1, + typename Value_type::const_iterator end_1, + typename Value_type::const_iterator begin_2, + typename Parameters_type::const_iterator begin_3, + inner_type const & init + ) const + { + inner_type tmp_val; + inner_type result = init; + + for ( ; begin_1 != end_1 ; ++begin_1, ++begin_2, ++begin_3 ) + { + tmp_val = *begin_1 - *begin_2; + result = result + *begin_3 * ( tmp_val * tmp_val ); + } + return result; + } + }; + /*\@}*/ + +} // namespace distance + +#endif // WEIGHTED_EUCLIDEAN_DISTANCE_FUNCTION_HPP_INCLUDED + diff --git a/src/third-party/knnl/wta_training_algorithm.hpp b/src/third-party/knnl/wta_training_algorithm.hpp new file mode 100755 index 00000000..ffe964ab --- /dev/null +++ b/src/third-party/knnl/wta_training_algorithm.hpp @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2006, Seweryn Habdank-Wojewodzki + * Copyright (c) 2006, Janusz Rybarski + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions + * and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS + * AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * e-mail: habdank AT gmail DOT com + * e-mail: janusz.rybarski AT gmail DOT com + * + * File created: Fri 21 Apr 2006 17:33:34 CEST + * Last modified: Wed 08 Aug 2007 18:29:31 CEST + */ + +#ifndef WTA_TRAINING_ALGORITM_HPP_INCLUDED +#define WTA_TRAINING_ALGORITM_HPP_INCLUDED + +#include +#include +#include +#include + +#include + +#include "training_functional.hpp" +#include "numeric_iterator.hpp" + +/** + * \file wta_training_algorithm.hpp + * \brief File contains template class Wta_training_algoritm. + * \ingroup neural_net + */ + +namespace neural_net +{ + /** + * \addtogroup neural_net + */ + /*\@{*/ + + /** + * \class Wta_training_algorithm + * \brief Class contains functionality for training + * kohonen network using WTA method. + * \param Network_type is a network type. + * \param Value_type is a type os single data + * in mathematical meanning, so it could be ::std::vector, too. + * \param Data_iterator_type is is iterator for container + * with training data. + * \param Training_functional_type is a type of functional. + * \param Numeric_iterator_type is a type of numeric iterator. + */ + template + < + typename Network_type, + typename Value_type, + typename Data_iterator_type, + typename Training_functional_type, + typename Numeric_iterator_type + = Linear_numeric_iterator < + typename Training_functional_type::iteration_type + > + > + class Wta_training_algorithm + { + public: + + typedef typename Training_functional_type::iteration_type iteration_type; + typedef Numeric_iterator_type numeric_iterator_type; + typedef Network_type network_type; + typedef Value_type value_type; + typedef Data_iterator_type data_iterator_type; + typedef Training_functional_type training_functional_type; + + /** Training functional. */ + Training_functional_type training_functional; + + /** + * Constructor. + * \param training_functional_ is a training functor. + * \param numeric_iterator_ is a numeric iterator. + */ + Wta_training_algorithm + ( + Training_functional_type const & training_functional_, + Numeric_iterator_type numeric_iterator_ = linear_numeric_iterator() + ) + : training_functional ( training_functional_ ), + numeric_iterator ( numeric_iterator_ ) + { + network = static_cast < Network_type * > ( 0 ); + } + + /** + * Copy constructor. + * It makes flat copy of neural network, so it copies only pointer not structure. + */ + template + < + typename Network_type_2, + typename Value_type_2, + typename Data_iterator_type_2, + typename Training_functional_type_2, + typename Numeric_iterator_type_2 + > + Wta_training_algorithm + ( + Wta_training_algorithm + < + Network_type_2, + Value_type_2, + Data_iterator_type_2, + Training_functional_type_2, + Numeric_iterator_type_2 + > + const & wta_training_alg_ + ) + : training_functional ( wta_training_alg_.training_functional ), + numeric_iterator ( wta_training_alg_.numeric_iterator ), + iteration ( wta_training_alg_.iteration ) + { + network = wta_training_alg_.network; + } + + /** + * Function that starts training proces. + * \param network_ is a pointer to the existing kohonen neural network. + * \param data_begin is a begin iterator, it could be revers. + * \param data_end is end iterator. + * \return error code. + */ + ::boost::int32_t operator() + ( + Data_iterator_type data_begin, + Data_iterator_type data_end, + Network_type * network_ + ) + { + network = network_; + + // check if pointer is not null + assert ( network != static_cast < Network_type * > ( 0 ) ); + + // for each data train neural network + ::std::for_each + ( + data_begin, data_end, + ::boost::bind + ( + & Wta_training_algorithm + < + Network_type, + Value_type, + Data_iterator_type, + Training_functional_type, + Numeric_iterator_type + >::train, + this, + _1 + ) + ); + + return 0; + } + + protected: + /** Pointer to the network. */ + Network_type * network; + + iteration_type iteration; + + Numeric_iterator_type numeric_iterator; + + /** + * Function trains neural network using single value. + * \param value is a value. + * As is set in WTA algoritm method is looking for the best neuron, + * and train it to have better results with actual data in the future. + */ + void train ( Value_type const & value ) + { + typename Network_type::row_size_t index_1 = 0; + typename Network_type::col_size_t index_2 = 0; + + typename Network_type::value_type::result_type tmp_result; + + // reset max_result + typename Network_type::value_type::result_type max_result + = ::std::numeric_limits + < + typename Network_type::value_type::result_type + >::min(); + + typename Network_type::row_iterator r_iter; + typename Network_type::column_iterator c_iter; + + // set ranges for iteration procedure + typename Network_type::row_size_t r_counter = 0; + typename Network_type::col_size_t c_counter = 0; + + for ( r_iter = network->objects.begin(); + r_iter != network->objects.end(); + ++r_iter + ) + { + for ( c_iter = r_iter->begin(); + c_iter != r_iter->end(); + ++c_iter + ) + { + tmp_result = ( *c_iter ) ( value ); + if ( tmp_result > max_result ) + { + index_1 = r_counter; + index_2 = c_counter; + max_result = tmp_result; + } + ++c_counter; + } + ++r_counter; + c_counter = 0; + } + + r_iter = network->objects.begin(); + ::std::advance ( r_iter, index_1 ); + + c_iter = r_iter->begin(); + ::std::advance ( c_iter, index_2 ); + + // train the winning neuron + (training_functional) + ( + c_iter->weights, + value, + this->iteration + ); + + // increase iteration + ++numeric_iterator; + iteration = numeric_iterator(); + } + }; + /*\@}*/ + +} // namespace neural_net + +#endif // WTA_TRAINING_ALGORITM_HPP_INCLUDED + diff --git a/src/third-party/knnl/wtm_topology.hpp b/src/third-party/knnl/wtm_topology.hpp new file mode 100755 index 00000000..3fad0512 --- /dev/null +++ b/src/third-party/knnl/wtm_topology.hpp @@ -0,0 +1,288 @@ +/* + * Copyright (c) 2006, Seweryn Habdank-Wojewodzki + * Copyright (c) 2006, Janusz Rybarski + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions + * and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS + * AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * e-mail: habdank AT gmail DOT com + * e-mail: janusz.rybarski AT gmail DOT com + * + * File created: Wed 03 May 2006 13:18:41 CEST + * Last modified: Wed 08 Aug 2007 18:22:48 CEST + */ + +#ifndef WTM_TOPOLOGY_HPP_INCLUDED +#define WTM_TOPOLOGY_HPP_INCLUDED + +#include + +#include "operators.hpp" + +/** + * \file wtm_topology.hpp + * \brief File contains template classes for setting up topology in the network. + * \ingroup neural_net + */ +namespace neural_net +{ + /** + * \addtogroup neural_net + */ + /*\@{*/ + + /** + * \class Basic_topology + * \brief Basic class for topologies. + * \param Result_type type of result of the topology as a functor + * that calculates didtance between two points + * in particular case two neurons. + * \param Index_type is a type is index that is used in neural network. + */ + template + < + typename Result_type, + typename Index_type + > + class Basic_topology + { + public: + typedef Result_type result_type; + typedef Index_type value_type; + }; + + /** + * \class City_topology + * \brief Topology for neural network that calculates distance between two neurons. + * \param Index_type is a type of index. + * \f[ + * d (n,m) = |n_1-m_1| + |n_2-m_2| + * \f] + */ + template < typename Index_type > + class City_topology + : public Basic_topology < Index_type, Index_type > + { + public: + + /** + * Function claculates distance. + * \param index_1_1 is first index of first neuron. + * \param index_1_2 is second index of first neuron. + * \param index_2_1 is first index of second neuron. + * \param index_2_2 is second index of second neuron. + * \f[ + * d (n,m) = |n_1-m_1| + |n_2-m_2| + * \f] + * where: n is first neuron, m is second neuron. + */ + inline Index_type operator() + ( + Index_type const & index_1_1, + Index_type const & index_1_2, + Index_type const & index_2_1, + Index_type const & index_2_2 + ) const + { + return + ( + ::operators::abs ( index_1_1 - index_2_1 ) + + ::operators::abs ( index_1_2 - index_2_2 ) + ); + } + }; + + /** + * \class Max_topology + * \brief Topology for neural network that calculates distance between two neurons. + * \param Index_type is a type of index. + * \f[ + * d (n,m) = \max ( |n_1-m_1|, |n_2-m_2| ) + * \f] + */ + template < typename Index_type > + class Max_topology + : public Basic_topology < Index_type, Index_type > + { + public: + + /** + * Function claculates distance. + * \param index_1_1 is first index of first neuron. + * \param index_1_2 is second index of first neuron. + * \param index_2_1 is first index of second neuron. + * \param index_2_2 is second index of second neuron. + * \f[ + * d (n,m) = \max ( |n_1-m_1|, |n_2-m_2| ) + * \f] + * where: n is first neuron, m is second neuron. + */ + inline Index_type operator() + ( + Index_type const & index_1_1, + Index_type const & index_1_2, + Index_type const & index_2_1, + Index_type const & index_2_2 + ) const + { + return + ( + ::std::max + ( + ::operators::abs ( index_1_1 - index_2_1 ), + ::operators::abs ( index_1_2 - index_2_2 ) + ) + ); + } + }; + + /** + * \class Hexagonal_topology + * \brief Topology for neural network that calculates distance between two neurons. + * \param Index_type is a type of index. + * \f{eqnarray*} + * h_1 (x) & = & \frac{ x_1 + 1 }{2} + x_2\\ + * h_2 (x) & = & \frac{h_{off}}{2} + x_2 - x_1 / 2;\\ + * t_1 & = & \max ( h_1 (n), h_1 (m) ) - \min ( h_1 (n), h_1 (m) )\\ + * t_2 & = & \max ( h_2 (n), h_2 (m) ) - \min ( h_2 (n), h_2 (m) )\\ + * d (n,m) & = & \left\{ + * \begin{array}{ll} + * \max (|t_1|,|t_2|) & if \; sign \; of \; t_1 \; and \; t_2 \; is \; the \; same\\ + * |t_1|+|t_2| & otherwise + * \end{array} \right. + * \f} + */ + template < typename Index_type > + class Hexagonal_topology + : public Basic_topology < Index_type, Index_type > + { + public: + /** + * Constructor. + * \param hex_offset_ is an offset of the hexagonal topology. + * This value have to be not less than number of rows in neuron + * container counted from 0. + */ + inline explicit Hexagonal_topology ( Index_type const & hex_offset_ ) + : hex_offset ( hex_offset_ ) + {} + + /** Copy constructor. */ + template < typename Index_type_2 > + inline Hexagonal_topology ( Hexagonal_topology < Index_type_2 > const & hex_topology ) + : Basic_topology < Index_type_2, Index_type_2 >(), + hex_offset ( hex_topology.hex_offset ) + {} + + /** + * Function calculates distance. + * \param index_1_1 is first index of first neuron. + * \param index_1_2 is second index of first neuron. + * \param index_2_1 is first index of second neuron. + * \param index_2_2 is second index of second neuron. + * \f{eqnarray*} + * h_1 (x) & = & \frac{ x_1 + 1 }{2} + x_2\\ + * h_2 (x) & = & \frac{h_{off}}{2} + x_2 - x_1 / 2;\\ + * t_1 & = & \max ( h_1 (n), h_1 (m) ) - \min ( h_1 (n), h_1 (m) )\\ + * t_2 & = & \max ( h_2 (n), h_2 (m) ) - \min ( h_2 (n), h_2 (m) )\\ + * d (n,m) & = & \left\{ + * \begin{array}{ll} + * \max (|t_1|,|t_2|) & if \; sign \; of \; t_1 \; and \; t_2 \; is \; the \; same\\ + * |t_1|+|t_2| & otherwise + * \end{array} \right. + * \f} + * where: n is firs neuron, m is second. + */ + Index_type operator() + ( + Index_type const & index_1_1, + Index_type const & index_1_2, + Index_type const & index_2_1, + Index_type const & index_2_2 + ) const + { + Index_type hex_index_1_1; + Index_type hex_index_1_2; + Index_type hex_index_2_1; + Index_type hex_index_2_2; + + Index_type tmp_hex_index_1; + Index_type tmp_hex_index_2; + + // recalculate indexes to the better indexes used in hexagonal space + hex_index_1_1 = ( index_1_1 + 1 ) / 2 + index_1_2; + hex_index_1_2 = ( hex_offset / 2 + index_1_2 ) - index_1_1 / 2; + + hex_index_2_1 = ( index_2_1 + 1 ) / 2 + index_2_2; + hex_index_2_2 = ( hex_offset / 2 + index_2_2 ) - index_2_1 / 2; + + // calculate difference between points in hexagonal space + tmp_hex_index_1 = ::std::max ( hex_index_1_1, hex_index_2_1 ) + - ::std::min ( hex_index_1_1, hex_index_2_1 ); + tmp_hex_index_2 = ::std::max ( hex_index_1_2, hex_index_2_2 ) + - ::std::min ( hex_index_1_2, hex_index_2_2 ); + + // here we have special algebra to calculate distance, + // bacause of special basis in this space: + // ( 1, 1 ); ( -1, -1 ) have distance 1 the same as + // ( -1, 0 ); ( 0, -1 ); ( 1, 0 ); ( 0, 1 ). + if ( tmp_hex_index_1 == 0 && tmp_hex_index_2 == 0 ) + { + return static_cast < Index_type > ( 0 ); + } + + // check if values have the same direction if yes it means that we have to use + // reasoning based on assumption that ( -1, -1 ) and ( 1, 1 ) have distance 1. + if ( ( ( hex_index_1_1 > hex_index_2_1 ) && ( hex_index_1_2 > hex_index_2_2 ) ) + || ( ( hex_index_1_1 < hex_index_2_1 ) && ( hex_index_1_2 < hex_index_2_2 ) ) + ) + { + return ::std::max ( tmp_hex_index_1, tmp_hex_index_2 ); + } + else + { + return ::operators::abs ( tmp_hex_index_1 ) + ::operators::abs ( tmp_hex_index_2 ); + } + + return static_cast < Index_type > ( 0 ); + } + + protected: + Index_type hex_offset; + }; + /*\@}*/ + +} // namespace neural_net + +#endif // WTM_TOPOLOGY_HPP_INCLUDED + diff --git a/src/third-party/knnl/wtm_training_algorithm.hpp b/src/third-party/knnl/wtm_training_algorithm.hpp new file mode 100755 index 00000000..f98428e9 --- /dev/null +++ b/src/third-party/knnl/wtm_training_algorithm.hpp @@ -0,0 +1,293 @@ +/* + * Copyright (c) 2006, Seweryn Habdank-Wojewodzki + * Copyright (c) 2006, Janusz Rybarski + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions + * and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS + * AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * e-mail: habdank AT gmail DOT com + * e-mail: janusz.rybarski AT gmail DOT com + * + * File created: Tue 02 May 2006 11:39:22 CEST + * Last modified: Wed 08 Aug 2007 18:29:44 CEST + */ + +#ifndef WTM_TRAINING_ALGORITM_HPP_INCLUDED +#define WTM_TRAINING_ALGORITM_HPP_INCLUDED + +#include +#include +#include + +#include + +#include "training_functional.hpp" +#include "numeric_iterator.hpp" + +/** + * \file wtm_training_algorithm.hpp + * \brief File contains template class Wtm_training_algoritm. + * \ingroup neural_net + */ + +namespace neural_net +{ + /** + * \addtogroup neural_net + */ + /*\@{*/ + + /** + * \class Wtm_training_algorithm + * \brief Class contains functionality for training + * kohonen network using WTM method. + * \param Network_type is a network type. + * \param Value_type is a type os single data + * in mathematical meanning, so it could be ::std::vector, too. + * \param Data_iterator_type is is iterator for container + * with training data. + * \param Training_functional_type is a type of functional. + * \param Index_type is a type of index of neurons used in network. + */ + template + < + typename Network_type, + typename Value_type, + typename Data_iterator_type, + typename Training_functional_type, + typename Index_type, + typename Numeric_iterator_type + = Linear_numeric_iterator < + typename Training_functional_type::iteration_type + > + > + class Wtm_training_algorithm + { + public: + + typedef typename Training_functional_type::iteration_type iteration_type; + typedef Network_type network_type; + typedef Value_type value_type; + typedef Data_iterator_type data_iterator_type; + typedef Training_functional_type training_functional_type; + typedef Index_type index_type; + typedef Numeric_iterator_type numeric_iterator_type; + + /** Training functional. */ + Training_functional_type training_functional; + + /** + * Constructor. + * \param training_functional_ is a training functor. + * \param numeric_iterator_ is a numeric iterator. + */ + Wtm_training_algorithm + ( + Training_functional_type const & training_functional_, + Numeric_iterator_type numeric_iterator_ = linear_numeric_iterator() + ) + : training_functional ( training_functional_ ), + numeric_iterator ( numeric_iterator_ ) + { + network = static_cast < Network_type* > ( 0 ); + } + + /** + * Copy constructor. + * It makes flat copy of neural network, so it copies only pointer not structure. + */ + template + < + typename Network_type_2, + typename Value_type_2, + typename Data_iterator_type_2, + typename Training_functional_type_2, + typename Index_type_2, + typename Numeric_iterator_type_2 + > + inline Wtm_training_algorithm + ( + Wtm_training_algorithm + < + Network_type_2, + Value_type_2, + Data_iterator_type_2, + Training_functional_type_2, + Index_type_2, + Numeric_iterator_type_2 + > + const & wtm_training_alg_ + ) + : training_functional ( wtm_training_alg_.training_functional ), + numeric_iterator ( wtm_training_alg_.numeric_iterator ), + iteration ( wtm_training_alg_.iteration ) + { + network = wtm_training_alg_.network; + } + + /** + * Function that starts training proces. + * \param network_ is a pointer to the existing kohonen neural network. + * \param data_begin is a begin iterator, it could be revers. + * \param data_end is end iterator. + * \return error code. + */ + ::boost::int32_t operator() + ( + Data_iterator_type data_begin, + Data_iterator_type data_end, + Network_type * network_ + ) + { + network = network_; + + assert ( network != static_cast < Network_type * > ( 0 ) ); + + // for each data train network + ::std::for_each + ( + data_begin, data_end, + ::boost::bind + ( + & Wtm_training_algorithm + < + Network_type, + Value_type, + Data_iterator_type, + Training_functional_type, + Index_type, + Numeric_iterator_type + >::train, + this, + _1 + ) + ); + + return 0; + } + + protected: + /** Pointer to the network. */ + Network_type * network; + + iteration_type iteration; + + Numeric_iterator_type numeric_iterator; + + /** + * Function trains neural network using single value. + * \param value is a value. + * As is set in WTM algoritm method is looking for the best neuron, + * and train it to have better results with actual data in the future. + */ + void train ( Value_type const & value ) + { + Index_type index_1 = Index_type(); + Index_type index_2 = Index_type(); + + typename Network_type::value_type::result_type tmp_result; + + // reset max_result + typename Network_type::value_type::result_type max_result + = ::std::numeric_limits < + typename Network_type::value_type::result_type + >::min(); + + typename Network_type::row_iterator r_iter; + typename Network_type::column_iterator c_iter; + + // set ranges for iteration procedure + ::boost::int32_t r_counter = 0;//network->objects.size(); + ::boost::int32_t c_counter = 0;//network->objects[0].size(); + + for ( r_iter = network->objects.begin(); + r_iter != network->objects.end(); + ++r_iter + ) + { + for ( c_iter = r_iter->begin(); + c_iter != r_iter->end(); + ++c_iter + ) + { + tmp_result = ( *c_iter ) ( value ); + if ( tmp_result > max_result ) + { + index_1 = r_counter; + index_2 = c_counter; + max_result = tmp_result; + } + ++c_counter; + } + ++r_counter; + c_counter = 0; + } + + r_counter = 0; + c_counter = 0; + + // train all neurons in network with respect to the + // training functional + for ( r_iter = network->objects.begin(); + r_iter != network->objects.end(); + ++r_iter + ) + { + for ( c_iter = r_iter->begin(); + c_iter != r_iter->end(); + ++c_iter + ) + { + (training_functional) + ( + c_iter->weights, + value, + iteration, + index_1, index_2, r_counter, c_counter + ); + ++c_counter; + } + ++r_counter; + c_counter = 0; + } + + // increase iteration + ++numeric_iterator; + iteration = numeric_iterator(); + } + }; + /*\@}*/ +} // namespace neural_net + +#endif // WTM_TRAINING_ALGORITM_HPP_INCLUDED + From 7723d461a54bbd61d861fb748c7c73f92d6e19f6 Mon Sep 17 00:00:00 2001 From: emeric Date: Fri, 15 Apr 2016 23:43:59 +0200 Subject: [PATCH 003/195] [DB] Some code refactoring --- TODO | 3 + src/Makefile.am | 6 +- src/database/DatabaseHandler.cpp | 2 + .../DatabaseUpdater.cpp | 139 ++++++++++-------- .../DatabaseUpdater.hpp | 47 ++++-- src/database/Track.hpp | 11 +- src/database/Types.hpp | 1 + src/main/main.cpp | 34 ++--- src/service/DatabaseUpdateService.cpp | 52 ------- src/service/DatabaseUpdateService.hpp | 55 ------- src/service/Service.hpp | 51 ------- src/service/ServiceManager.cpp | 84 ----------- src/service/ServiceManager.hpp | 77 ---------- src/ui/settings/Settings.cpp | 49 +++--- src/ui/settings/Settings.hpp | 5 - src/{database-updater => utils}/Checksum.cpp | 0 src/{database-updater => utils}/Checksum.hpp | 2 +- 17 files changed, 164 insertions(+), 454 deletions(-) rename src/{database-updater => database}/DatabaseUpdater.cpp (86%) rename src/{database-updater => database}/DatabaseUpdater.hpp (87%) delete mode 100644 src/service/DatabaseUpdateService.cpp delete mode 100644 src/service/DatabaseUpdateService.hpp delete mode 100644 src/service/Service.hpp delete mode 100644 src/service/ServiceManager.cpp delete mode 100644 src/service/ServiceManager.hpp rename src/{database-updater => utils}/Checksum.cpp (100%) rename src/{database-updater => utils}/Checksum.hpp (97%) diff --git a/TODO b/TODO index 55ea8b4e..1a37b681 100644 --- a/TODO +++ b/TODO @@ -2,6 +2,9 @@ [Book] - Make the feature? +[Build] +- Makefile per directory. Use the include flags only where needed? + [Cover] - Handle preferred cover file names ("front.xxx", "cover.xxx", ...) - Implement a cache and a grabber from some web service (mandatory for artists?) diff --git a/src/Makefile.am b/src/Makefile.am index 18a8bcbc..22f5e246 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -7,6 +7,7 @@ lms_SOURCES = \ $(srcdir)/cover/CoverArtGrabber.cpp \ $(srcdir)/database/Artist.cpp \ $(srcdir)/database/DatabaseHandler.cpp \ + $(srcdir)/database/DatabaseUpdater.cpp \ $(srcdir)/database/MediaDirectory.cpp \ $(srcdir)/database/Playlist.cpp \ $(srcdir)/database/Release.cpp \ @@ -15,13 +16,9 @@ lms_SOURCES = \ $(srcdir)/database/Track.cpp \ $(srcdir)/database/User.cpp \ $(srcdir)/database/Video.cpp \ - $(srcdir)/database-updater/DatabaseUpdater.cpp \ - $(srcdir)/database-updater/Checksum.cpp \ $(srcdir)/image/Image.cpp \ $(srcdir)/logger/Logger.cpp \ $(srcdir)/metadata/AvFormat.cpp \ - $(srcdir)/service/ServiceManager.cpp \ - $(srcdir)/service/DatabaseUpdateService.cpp \ $(srcdir)/ui/LmsApplication.cpp \ $(srcdir)/ui/auth/LmsAuth.cpp \ $(srcdir)/ui/audio/AudioPlayer.cpp \ @@ -57,6 +54,7 @@ lms_SOURCES = \ $(srcdir)/ui/settings/SettingsMediaDirectoryFormView.cpp \ $(srcdir)/ui/settings/SettingsUserFormView.cpp \ $(srcdir)/ui/settings/SettingsUsers.cpp \ + $(srcdir)/utils/Checksum.cpp \ $(srcdir)/utils/Utils.cpp if VIDEO diff --git a/src/database/DatabaseHandler.cpp b/src/database/DatabaseHandler.cpp index b44588ea..1a6307dd 100644 --- a/src/database/DatabaseHandler.cpp +++ b/src/database/DatabaseHandler.cpp @@ -83,6 +83,8 @@ Handler::Handler(Wt::Dbo::SqlConnectionPool& connectionPool) _session.mapClass("artist"); _session.mapClass("genre"); _session.mapClass("track"); + _session.mapClass("classification"); + _session.mapClass("classification_data"); _session.mapClass("playlist"); _session.mapClass("playlist_entry"); _session.mapClass("release"); diff --git a/src/database-updater/DatabaseUpdater.cpp b/src/database/DatabaseUpdater.cpp similarity index 86% rename from src/database-updater/DatabaseUpdater.cpp rename to src/database/DatabaseUpdater.cpp index 872f8478..d1420d89 100644 --- a/src/database-updater/DatabaseUpdater.cpp +++ b/src/database/DatabaseUpdater.cpp @@ -17,19 +17,19 @@ * along with LMS. If not, see . */ +#include #include #include #include #include "logger/Logger.hpp" -#include "utils/Utils.hpp" - -#include "database/Types.hpp" - -#include "Checksum.hpp" -#include "DatabaseUpdater.hpp" #include "cover/CoverArtGrabber.hpp" +#include "utils/Utils.hpp" +#include "utils/Checksum.hpp" + +#include "Types.hpp" +#include "DatabaseUpdater.hpp" namespace { @@ -115,23 +115,40 @@ isPathInParentPath(const boost::filesystem::path& path, const boost::filesystem: } // namespace -namespace DatabaseUpdater { +namespace Database { -using namespace Database; +Updater& Updater::instance(void) +{ + static Updater updater; + return updater; +} - -Updater::Updater(Wt::Dbo::SqlConnectionPool &connectionPool, MetaData::Parser& parser) +Updater::Updater() : _running(false), -_scheduleTimer(_ioService), -_db(connectionPool), -_metadataParser(parser) +_scheduleTimer(_ioService) { _ioService.setThreadCount(1); } +void +Updater::setConnectionPool(Wt::Dbo::SqlConnectionPool& connectionPool) +{ + _db = new Database::Handler(connectionPool); +} + +void +Updater::restart(void) +{ + stop(); + start(); +} + void Updater::start(void) { + if (_db == nullptr) + throw std::logic_error("uninitialized db!"); + _running = true; // post some jobs in the io_service @@ -154,9 +171,9 @@ Updater::stop(void) void Updater::processNextJob(void) { - Wt::Dbo::Transaction transaction(_db.getSession()); + Wt::Dbo::Transaction transaction(_db->getSession()); - MediaDirectorySettings::pointer settings = MediaDirectorySettings::get(_db.getSession()); + MediaDirectorySettings::pointer settings = MediaDirectorySettings::get(_db->getSession()); if (settings->getManualScanRequested()) { LMS_LOG(DBUPDATER, INFO) << "Manual scan requested!"; @@ -229,9 +246,9 @@ Updater::process(boost::system::error_code err) std::vector rootDirectories; { - Wt::Dbo::Transaction transaction(_db.getSession()); + Wt::Dbo::Transaction transaction(_db->getSession()); - for (MediaDirectory::pointer directory : MediaDirectory::getAll(_db.getSession())) + for (MediaDirectory::pointer directory : MediaDirectory::getAll(_db->getSession())) rootDirectories.push_back( RootDirectory( directory->getType(), directory->getPath() )); } @@ -253,9 +270,9 @@ Updater::process(boost::system::error_code err) // Update database stats boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); { - Wt::Dbo::Transaction transaction(_db.getSession()); + Wt::Dbo::Transaction transaction(_db->getSession()); - Database::MediaDirectorySettings::pointer settings = Database::MediaDirectorySettings::get(_db.getSession()); + Database::MediaDirectorySettings::pointer settings = Database::MediaDirectorySettings::get(_db->getSession()); if (stats.nbChanges() > 0) settings.modify()->setLastUpdate(now); @@ -279,10 +296,10 @@ Updater::process(boost::system::error_code err) void Updater::updateFileExtensions() { - Wt::Dbo::Transaction transaction(_db.getSession()); + Wt::Dbo::Transaction transaction(_db->getSession()); - _audioFileExtensions = MediaDirectorySettings::get(_db.getSession())->getAudioFileExtensions(); - _videoFileExtensions = MediaDirectorySettings::get(_db.getSession())->getVideoFileExtensions(); + _audioFileExtensions = MediaDirectorySettings::get(_db->getSession())->getAudioFileExtensions(); + _videoFileExtensions = MediaDirectorySettings::get(_db->getSession())->getVideoFileExtensions(); } Artist::pointer @@ -293,9 +310,9 @@ Updater::getArtist( const boost::filesystem::path& file, const std::string& name // First try to get by MBID if (!mbid.empty()) { - artist = Artist::getByMBID( _db.getSession(), mbid ); + artist = Artist::getByMBID( _db->getSession(), mbid ); if (!artist) - artist = Artist::create( _db.getSession(), name, mbid); + artist = Artist::create( _db->getSession(), name, mbid); return artist; } @@ -303,7 +320,7 @@ Updater::getArtist( const boost::filesystem::path& file, const std::string& name // Fall back on artist name (collisions may occur) if (!name.empty()) { - for (Artist::pointer sameNamedArtist : Artist::getByName( _db.getSession(), name )) + for (Artist::pointer sameNamedArtist : Artist::getByName( _db->getSession(), name )) { if (sameNamedArtist->getMBID().empty()) { @@ -314,12 +331,12 @@ Updater::getArtist( const boost::filesystem::path& file, const std::string& name // No Artist found with the same name and without MBID -> creating if (!artist) - artist = Artist::create( _db.getSession(), name); + artist = Artist::create( _db->getSession(), name); return artist; } - return Artist::getNone( _db.getSession() ); + return Artist::getNone( _db->getSession() ); } Release::pointer @@ -330,9 +347,9 @@ Updater::getRelease( const boost::filesystem::path& file, const std::string& nam // First try to get by MBID if (!mbid.empty()) { - release = Release::getByMBID( _db.getSession(), mbid ); + release = Release::getByMBID( _db->getSession(), mbid ); if (!release) - release = Release::create( _db.getSession(), name, mbid); + release = Release::create( _db->getSession(), name, mbid); return release; } @@ -340,7 +357,7 @@ Updater::getRelease( const boost::filesystem::path& file, const std::string& nam // Fall back on release name (collisions may occur) if (!name.empty()) { - for (Release::pointer sameNamedRelease : Release::getByName( _db.getSession(), name )) + for (Release::pointer sameNamedRelease : Release::getByName( _db->getSession(), name )) { if (sameNamedRelease->getMBID().empty()) { @@ -351,12 +368,12 @@ Updater::getRelease( const boost::filesystem::path& file, const std::string& nam // No release found with the same name and without MBID -> creating if (!release) - release = Release::create( _db.getSession(), name); + release = Release::create( _db->getSession(), name); return release; } - return Release::getNone( _db.getSession() ); + return Release::getNone( _db->getSession() ); } std::vector @@ -366,15 +383,15 @@ Updater::getGenres( const std::list& names) for (const std::string& name : names) { - Genre::pointer genre ( Genre::getByName(_db.getSession(), name) ); + Genre::pointer genre ( Genre::getByName(_db->getSession(), name) ); if (!genre) - genre = Genre::create(_db.getSession(), name); + genre = Genre::create(_db->getSession(), name); genres.push_back( genre ); } if (genres.empty()) - genres.push_back( Genre::getNone( _db.getSession() )); + genres.push_back( Genre::getNone( _db->getSession() )); return genres; } @@ -386,9 +403,9 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) // Skip file if last write is the same { - Wt::Dbo::Transaction transaction(_db.getSession()); + Wt::Dbo::Transaction transaction(_db->getSession()); - Wt::Dbo::ptr track = Track::getByPath(_db.getSession(), file); + Wt::Dbo::ptr track = Track::getByPath(_db->getSession(), file); if (track && track->getLastWriteTime() == lastWriteTime) { @@ -412,9 +429,9 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) std::vector checksum ; computeCrc(file, checksum); - Wt::Dbo::Transaction transaction(_db.getSession()); + Wt::Dbo::Transaction transaction(_db->getSession()); - Wt::Dbo::ptr track = Track::getByPath(_db.getSession(), file); + Wt::Dbo::ptr track = Track::getByPath(_db->getSession(), file); // We estimate this is a audio file if: // - we found a least one audio stream @@ -510,7 +527,7 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) if (!track) { // Create a new song - track = Track::create(_db.getSession(), file); + track = Track::create(_db->getSession(), file); LMS_LOG(DBUPDATER, INFO) << "Adding '" << file << "'"; stats.nbAdded++; } @@ -675,8 +692,8 @@ Updater::checkAudioFiles( Stats& stats ) { LMS_LOG(DBUPDATER, INFO) << "Checking audio files..."; - std::vector trackPaths = Track::getAllPaths(_db.getSession());; - std::vector rootDirs = getRootDirectoriesByType(_db.getSession(), Database::MediaDirectory::Audio); + std::vector trackPaths = Track::getAllPaths(_db->getSession());; + std::vector rootDirs = getRootDirectoriesByType(_db->getSession(), Database::MediaDirectory::Audio); LMS_LOG(DBUPDATER, DEBUG) << "Checking tracks..."; for (auto& trackPath : trackPaths) @@ -686,9 +703,9 @@ Updater::checkAudioFiles( Stats& stats ) if (!checkFile(trackPath, rootDirs, _audioFileExtensions)) { - Wt::Dbo::Transaction transaction(_db.getSession()); + Wt::Dbo::Transaction transaction(_db->getSession()); - Track::pointer track = Track::getByPath(_db.getSession(), trackPath); + Track::pointer track = Track::getByPath(_db->getSession(), trackPath); if (track) { track.remove(); @@ -699,10 +716,10 @@ Updater::checkAudioFiles( Stats& stats ) LMS_LOG(DBUPDATER, DEBUG) << "Checking Genres..."; { - Wt::Dbo::Transaction transaction(_db.getSession()); + Wt::Dbo::Transaction transaction(_db->getSession()); // Now process orphan Genre (no track) - auto genres = Genre::getAll(_db.getSession()); + auto genres = Genre::getAll(_db->getSession()); for (auto genre : genres) { if (genre->getTracks().size() == 0) @@ -715,9 +732,9 @@ Updater::checkAudioFiles( Stats& stats ) LMS_LOG(DBUPDATER, DEBUG) << "Checking artists..."; { - Wt::Dbo::Transaction transaction(_db.getSession()); + Wt::Dbo::Transaction transaction(_db->getSession()); - auto artists = Artist::getAllOrphans(_db.getSession()); + auto artists = Artist::getAllOrphans(_db->getSession()); for (auto artist : artists) { LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan artist '" << artist->getName() << "'"; @@ -727,9 +744,9 @@ Updater::checkAudioFiles( Stats& stats ) LMS_LOG(DBUPDATER, DEBUG) << "Checking releases..."; { - Wt::Dbo::Transaction transaction(_db.getSession()); + Wt::Dbo::Transaction transaction(_db->getSession()); - auto releases = Release::getAllOrphans(_db.getSession()); + auto releases = Release::getAllOrphans(_db->getSession()); for (auto release : releases) { LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan release '" << release->getName() << "'"; @@ -745,15 +762,15 @@ Updater::checkDuplicatedAudioFiles(Stats& stats) { LMS_LOG(DBUPDATER, INFO) << "Checking duplicated audio files"; - Wt::Dbo::Transaction transaction(_db.getSession()); + Wt::Dbo::Transaction transaction(_db->getSession()); - std::vector tracks = Database::Track::getMBIDDuplicates(_db.getSession()); + std::vector tracks = Database::Track::getMBIDDuplicates(_db->getSession()); for (Track::pointer track : tracks) { LMS_LOG(DBUPDATER, INFO) << "Found duplicated MBID [" << track->getMBID() << "], file: " << track->getPath() << " - " << track->getArtist()->getName() << " - " << track->getName(); } - tracks = Database::Track::getChecksumDuplicates(_db.getSession()); + tracks = Database::Track::getChecksumDuplicates(_db->getSession()); for (Track::pointer track : tracks) { LMS_LOG(DBUPDATER, INFO) << "Found duplicated checksum [" << bufferToString(track->getChecksum()) << "], file: " << track->getPath() << " - " << track->getArtist()->getName() << " - " << track->getName(); @@ -766,8 +783,8 @@ Updater::checkDuplicatedAudioFiles(Stats& stats) void Updater::checkVideoFiles( Stats& stats ) { - std::vector rootDirs = getRootDirectoriesByType(_db.getSession(), Database::MediaDirectory::Video); - std::vector videoPaths = Video::getAllPaths(_db.getSession()); + std::vector rootDirs = getRootDirectoriesByType(_db->getSession(), Database::MediaDirectory::Video); + std::vector videoPaths = Video::getAllPaths(_db->getSession()); LMS_LOG(DBUPDATER, DEBUG) << "Checking videos..."; for (auto& videoPath : videoPaths) @@ -777,9 +794,9 @@ Updater::checkVideoFiles( Stats& stats ) if (!checkFile(videoPath, rootDirs, _videoFileExtensions)) { - Wt::Dbo::Transaction transaction(_db.getSession()); + Wt::Dbo::Transaction transaction(_db->getSession()); - Video::pointer video = Video::getByPath(_db.getSession(), videoPath); + Video::pointer video = Video::getByPath(_db->getSession(), videoPath); if (video) { video.remove(); @@ -797,10 +814,10 @@ Updater::processVideoFile( const boost::filesystem::path& file, Stats& stats) // 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()); + Wt::Dbo::Transaction transaction(_db->getSession()); // Skip file if last write is the same - Wt::Dbo::ptr
+
+ +
+ ${tags-highlevel-acousticbrainz} +
+
+ ${tags-highlevel-acousticbrainz-info} +
+
+ +
+ +
+ ${tags-similarity-acousticbrainz} +
+
+ ${tags-similarity-acousticbrainz-info} +
+
+
${apply-button} ${discard-button} ${immediate-scan-button} diff --git a/conf/lms.conf b/conf/lms.conf index bc42e092..b482a6f1 100644 --- a/conf/lms.conf +++ b/conf/lms.conf @@ -4,10 +4,11 @@ # Must have write privileges in order to create and modify the file db-path = "/storage/emeric/lms/lms.dev.db"; -# Listen port of the web server -listen-port = 5082; +# Cache directory for downloaded contents +cache-dir-path = "/var/lms/cache"; -# Listen address of the web server +# Listen port/addr of the web server +listen-port = 5082; listen-addr = "0.0.0.0"; # If enabled, these files have to exist and have correct permissions @@ -23,16 +24,3 @@ approot = "/usr/share/lms/approot"; # Path to the wt_config.xml file wt-config = "/etc/wt/wt_config.xml"; -# -- Settings to be moved in the web interface -- - -features-dir-path = "/var/lms/features/"; - -# Create tags from genres in metadata -# tag-genres = true; - -# Create tags from the high level data on AcousticBrainz -tag-highlevel-acousticbrainz = true; - -# Create similarity tags from the low level data on AcousticBrainz -tag-similarity-acousticbrainz = true; - diff --git a/src/database/DatabaseClassifier.cpp b/src/database/DatabaseClassifier.cpp index 45b8cb98..5aff1b00 100644 --- a/src/database/DatabaseClassifier.cpp +++ b/src/database/DatabaseClassifier.cpp @@ -42,7 +42,7 @@ Classifier::processTrackUpdate(bool added, Track::id_type trackId, std::string m if (mbid.empty()) { // TODO compute from file - LMS_LOG(CLASSIFICATION, INFO) << "File '" << path << "' has no MBID: skipping feature extraction"; + LMS_LOG(DBUPDATER, INFO) << "File '" << path << "' has no MBID: skipping feature extraction"; return; } @@ -332,7 +332,7 @@ static std::vector features = { "lowlevel.spectral_centroid.mean", 1, 1.0 }, // { "lowlevel.spectral_centroid.median", 1, 1.0 }, // { "lowlevel.spectral_centroid.min", 1, 1.0 }, -// { "lowlevel.spectral_centroid.var", 1, 1.0 }, + { "lowlevel.spectral_centroid.var", 1, 1.0 }, // { "lowlevel.spectral_complexity.dmean", 1, 1.0 }, // { "lowlevel.spectral_complexity.dmean2", 1, 1.0 }, // { "lowlevel.spectral_complexity.dvar", 1, 1.0 }, @@ -341,7 +341,7 @@ static std::vector features = { "lowlevel.spectral_complexity.mean", 1, 1.0 }, // { "lowlevel.spectral_complexity.median", 1, 1.0 }, // { "lowlevel.spectral_complexity.min", 1, 1.0 }, -// { "lowlevel.spectral_complexity.var", 1, 1.0 }, + { "lowlevel.spectral_complexity.var", 1, 1.0 }, // { "lowlevel.spectral_decrease.dmean", 1, 1.0 }, // { "lowlevel.spectral_decrease.dmean2", 1, 1.0 }, // { "lowlevel.spectral_decrease.dvar", 1, 1.0 }, @@ -350,7 +350,7 @@ static std::vector features = { "lowlevel.spectral_decrease.mean", 1, 1.0 }, // { "lowlevel.spectral_decrease.median", 1, 1.0 }, // { "lowlevel.spectral_decrease.min", 1, 1.0 }, -// { "lowlevel.spectral_decrease.var", 1, 1.0 }, + { "lowlevel.spectral_decrease.var", 1, 1.0 }, // { "lowlevel.spectral_energy.dmean", 1, 1.0 }, // { "lowlevel.spectral_energy.dmean2", 1, 1.0 }, // { "lowlevel.spectral_energy.dvar", 1, 1.0 }, @@ -359,7 +359,7 @@ static std::vector features = { "lowlevel.spectral_energy.mean", 1, 1.0 }, // { "lowlevel.spectral_energy.median", 1, 1.0 }, // { "lowlevel.spectral_energy.min", 1, 1.0 }, -// { "lowlevel.spectral_energy.var", 1, 1.0 }, + { "lowlevel.spectral_energy.var", 1, 1.0 }, // { "lowlevel.spectral_energyband_low.dmean", 1, 1.0 }, // { "lowlevel.spectral_energyband_low.dmean2", 1, 1.0 }, // { "lowlevel.spectral_energyband_low.dvar", 1, 1.0 }, @@ -368,7 +368,7 @@ static std::vector features = { "lowlevel.spectral_energyband_low.mean", 1, 1.0 }, // { "lowlevel.spectral_energyband_low.median", 1, 1.0 }, // { "lowlevel.spectral_energyband_low.min", 1, 1.0 }, -// { "lowlevel.spectral_energyband_low.var", 1, 1.0 }, + { "lowlevel.spectral_energyband_low.var", 1, 1.0 }, // { "lowlevel.spectral_energyband_middle_high.dmean", 1, 1.0 }, // { "lowlevel.spectral_energyband_middle_high.dmean2", 1, 1.0 }, // { "lowlevel.spectral_energyband_middle_high.dvar", 1, 1.0 }, @@ -377,7 +377,7 @@ static std::vector features = { "lowlevel.spectral_energyband_middle_high.mean", 1, 1.0 }, // { "lowlevel.spectral_energyband_middle_high.median", 1, 1.0 }, // { "lowlevel.spectral_energyband_middle_high.min", 1, 1.0 }, -// { "lowlevel.spectral_energyband_middle_high.var", 1, 1.0 }, + { "lowlevel.spectral_energyband_middle_high.var", 1, 1.0 }, // { "lowlevel.spectral_energyband_middle_low.dmean", 1, 1.0 }, // { "lowlevel.spectral_energyband_middle_low.dmean2", 1, 1.0 }, // { "lowlevel.spectral_energyband_middle_low.dvar", 1, 1.0 }, @@ -386,7 +386,7 @@ static std::vector features = { "lowlevel.spectral_energyband_middle_low.mean", 1, 1.0 }, // { "lowlevel.spectral_energyband_middle_low.median", 1, 1.0 }, // { "lowlevel.spectral_energyband_middle_low.min", 1, 1.0 }, -// { "lowlevel.spectral_energyband_middle_low.var", 1, 1.0 }, + { "lowlevel.spectral_energyband_middle_low.var", 1, 1.0 }, // { "lowlevel.spectral_entropy.dmean", 1, 1.0 }, // { "lowlevel.spectral_entropy.dmean2", 1, 1.0 }, // { "lowlevel.spectral_entropy.dvar", 1, 1.0 }, @@ -395,7 +395,7 @@ static std::vector features = { "lowlevel.spectral_entropy.mean", 1, 1.0 }, // { "lowlevel.spectral_entropy.median", 1, 1.0 }, // { "lowlevel.spectral_entropy.min", 1, 1.0 }, -// { "lowlevel.spectral_entropy.var", 1, 1.0 }, + { "lowlevel.spectral_entropy.var", 1, 1.0 }, // { "lowlevel.spectral_flux.dmean", 1, 1.0 }, // { "lowlevel.spectral_flux.dmean2", 1, 1.0 }, // { "lowlevel.spectral_flux.dvar", 1, 1.0 }, @@ -422,7 +422,7 @@ static std::vector features = { "lowlevel.spectral_rms.mean", 1, 1.0 }, // { "lowlevel.spectral_rms.median", 1, 1.0 }, // { "lowlevel.spectral_rms.min", 1, 1.0 }, -// { "lowlevel.spectral_rms.var", 1, 1.0 }, + { "lowlevel.spectral_rms.var", 1, 1.0 }, // { "lowlevel.spectral_rolloff.dmean", 1, 1.0 }, // { "lowlevel.spectral_rolloff.dmean2", 1, 1.0 }, // { "lowlevel.spectral_rolloff.dvar", 1, 1.0 }, @@ -449,7 +449,7 @@ static std::vector features = { "lowlevel.spectral_spread.mean", 1, 1.0 }, // { "lowlevel.spectral_spread.median", 1, 1.0 }, // { "lowlevel.spectral_spread.min", 1, 1.0 }, -// { "lowlevel.spectral_spread.var", 1, 1.0 }, + { "lowlevel.spectral_spread.var", 1, 1.0 }, // { "lowlevel.spectral_strongpeak.dmean", 1, 1.0 }, // { "lowlevel.spectral_strongpeak.dmean2", 1, 1.0 }, // { "lowlevel.spectral_strongpeak.dvar", 1, 1.0 }, @@ -458,7 +458,7 @@ static std::vector features = { "lowlevel.spectral_strongpeak.mean", 1, 1.0 }, // { "lowlevel.spectral_strongpeak.median", 1, 1.0 }, // { "lowlevel.spectral_strongpeak.min", 1, 1.0 }, -// { "lowlevel.spectral_strongpeak.var", 1, 1.0 }, + { "lowlevel.spectral_strongpeak.var", 1, 1.0 }, // { "lowlevel.zerocrossingrate.dmean", 1, 1.0 }, // { "lowlevel.zerocrossingrate.dmean2", 1, 1.0 }, // { "lowlevel.zerocrossingrate.dvar", 1, 1.0 }, @@ -467,7 +467,7 @@ static std::vector features = { "lowlevel.zerocrossingrate.mean", 1, 1.0 }, // { "lowlevel.zerocrossingrate.median", 1, 1.0 }, // { "lowlevel.zerocrossingrate.min", 1, 1.0 }, -// { "lowlevel.zerocrossingrate.var", 1, 1.0 }, + { "lowlevel.zerocrossingrate.var", 1, 1.0 }, // { "rhythm.beats_count", 1, 1.0 }, // { "rhythm.beats_loudness.dmean", 1, 1.0 }, // { "rhythm.beats_loudness.dmean2", 1, 1.0 }, @@ -477,7 +477,7 @@ static std::vector features = { "rhythm.beats_loudness.mean", 1, 1.0 }, // { "rhythm.beats_loudness.median", 1, 1.0 }, // { "rhythm.beats_loudness.min", 1, 1.0 }, -// { "rhythm.beats_loudness.var", 1, 1.0 }, + { "rhythm.beats_loudness.var", 1, 1.0 }, // { "rhythm.bpm", 1, 1.0 }, // { "rhythm.bpm_histogram_first_peak_bpm.dmean", 1, 1.0 }, // { "rhythm.bpm_histogram_first_peak_bpm.dmean2", 1, 1.0 }, @@ -664,12 +664,7 @@ Classifier::processDatabaseUpdate(Updater::Stats stats) LMS_LOG(DBUPDATER, DEBUG) << "Getting track ids"; - std::vector trackIdsAll; - { - Wt::Dbo::Transaction transaction(_db.getSession()); - - trackIdsAll = Database::Track::getAllIds(_db.getSession()); - } + std::vector trackIdsAll = Database::Track::getAllIds(_db.getSession()); ::std::random_shuffle ( trackIdsAll.begin(), trackIdsAll.end() ); diff --git a/src/database/DatabaseFeatureExtractor.cpp b/src/database/DatabaseFeatureExtractor.cpp index 35574170..c4239b39 100644 --- a/src/database/DatabaseFeatureExtractor.cpp +++ b/src/database/DatabaseFeatureExtractor.cpp @@ -17,12 +17,12 @@ * along with LMS. If not, see . */ -#include "config/Config.hpp" #include "logger/Logger.hpp" #include "feature/FeatureExtractor.hpp" #include "feature/FeatureStore.hpp" +#include "Setting.hpp" #include "DatabaseFeatureExtractor.hpp" namespace Database { @@ -37,8 +37,8 @@ static std::string getMBID(Track::id_type trackId) void FeatureExtractor::processDatabaseUpdate(Updater::Stats stats) { - bool fetchHighLevel = Config::instance().getBool("tag-highlevel-acousticbrainz", false); - bool fetchLowLevel = Config::instance().getBool("tag-similarity-acousticbrainz", false); + bool fetchHighLevel = Setting::getBool(UpdaterDboSession(), "tags_highlevel_acousticbrainz"); + bool fetchLowLevel = Setting::getBool(UpdaterDboSession(), "tags_similarity_acousticbrainz"); if (!fetchHighLevel && !fetchLowLevel) { diff --git a/src/database/DatabaseHandler.cpp b/src/database/DatabaseHandler.cpp index e6caf88f..270e4fe1 100644 --- a/src/database/DatabaseHandler.cpp +++ b/src/database/DatabaseHandler.cpp @@ -28,6 +28,8 @@ #include #include +#include "Setting.hpp" + #include "logger/Logger.hpp" #include "DatabaseHandler.hpp" @@ -79,7 +81,6 @@ Handler::Handler(Wt::Dbo::SqlConnectionPool& connectionPool) { _session.setConnectionPool(connectionPool); - _session.mapClass("artist"); _session.mapClass("cluster"); _session.mapClass("track"); @@ -99,11 +100,6 @@ Handler::Handler(Wt::Dbo::SqlConnectionPool& connectionPool) Wt::Dbo::Transaction transaction(_session); _session.createTables(); - _session.execute("CREATE INDEX artist_name_idx ON artist(name)"); - _session.execute("CREATE INDEX release_name_idx ON release(name)"); - _session.execute("CREATE INDEX track_artist_idx ON track(artist_id)"); - _session.execute("CREATE INDEX track_release_idx ON track(release_id)"); - _session.execute("CREATE INDEX cluster_type_idx ON cluster(type)"); } catch(std::exception& e) { LMS_LOG(DB, ERROR) << "Cannot create tables: " << e.what(); @@ -112,7 +108,26 @@ Handler::Handler(Wt::Dbo::SqlConnectionPool& connectionPool) { Wt::Dbo::Transaction transaction(_session); + // Indexes _session.execute("PRAGMA journal_mode=WAL"); + _session.execute("CREATE INDEX IF NOT EXISTS artist_name_idx ON artist(name)"); + _session.execute("CREATE INDEX IF NOT EXISTS release_name_idx ON release(name)"); + _session.execute("CREATE INDEX IF NOT EXISTS track_artist_idx ON track(artist_id)"); + _session.execute("CREATE INDEX IF NOT EXISTS track_release_idx ON track(release_id)"); + _session.execute("CREATE INDEX IF NOT EXISTS cluster_type_idx ON cluster(type)"); + + // Default values + if (!Setting::exists(_session, "audio_file_extensions")) + Setting::setString(_session, "audio_file_extensions", ".mp3 .ogg .oga .aac .m4a .flac .wav .wma .aif .aiff .ape .mpc .shn" ); + + if (!Setting::exists(_session, "video_file_extensions")) + Setting::setString(_session, "video_file_extensions", ".flv .avi .mpg .mpeg .mp4 .m4v .mkv .mov .wmv .ogv .divx .m2ts"); + + if (!Setting::exists(_session, "tags_highlevel_acousticbrainz")) + Setting::setBool(_session, "tags_highlevel_acousticbrainz", true); + + if (!Setting::exists(_session, "tags_similarity_acousticbrainz")) + Setting::setBool(_session, "tags_similarity_acousticbrain", false); } _users = new UserDatabase(_session); diff --git a/src/database/DatabaseUpdater.cpp b/src/database/DatabaseUpdater.cpp index 35b62a4e..7a1fa6b7 100644 --- a/src/database/DatabaseUpdater.cpp +++ b/src/database/DatabaseUpdater.cpp @@ -28,6 +28,7 @@ #include "utils/Utils.hpp" #include "utils/Path.hpp" +#include "Setting.hpp" #include "Types.hpp" #include "DatabaseUpdater.hpp" @@ -70,7 +71,6 @@ getNextFirstOfMonth(const boost::gregorian::date& current) bool isFileSupported(const boost::filesystem::path& file, const std::vector extensions) { - boost::filesystem::path fileExtension = file.extension(); for (auto& extension : extensions) @@ -87,9 +87,9 @@ getRootDirectoriesByType(Wt::Dbo::Session& session, Database::MediaDirectory::Ty { Wt::Dbo::Transaction transaction(session); - std::vector res; std::vector rootDirs = Database::MediaDirectory::getByType(session, type); + std::vector res; for (auto rootDir : rootDirs) res.push_back(rootDir->getPath()); @@ -170,48 +170,43 @@ Updater::stop(void) void Updater::processNextJob(void) { - Wt::Dbo::Transaction transaction(_db->getSession()); - - MediaDirectorySettings::pointer settings = MediaDirectorySettings::get(_db->getSession()); - - if (settings->getManualScanRequested()) { + if (Setting::getBool(_db->getSession(), "manual_scan_requested", false)) + { LMS_LOG(DBUPDATER, INFO) << "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::posix_time::time_duration startTime = Setting::getDuration(_db->getSession(), "update_start_time"); boost::gregorian::date nextScanDate; - switch( settings->getUpdatePeriod() ) + std::string updatePeriod = Setting::getString(_db->getSession(), "update_period", "never"); + if (updatePeriod == "daily") { - 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 (now.time_of_day() < startTime) + nextScanDate = now.date(); + else + nextScanDate = getNextDay(now.date()); + } + else if (updatePeriod == "weekly") + { + if (now.time_of_day() < startTime && now.date().day_of_week() == 1) + nextScanDate = now.date(); + else + nextScanDate = getNextMonday(now.date()); + } + else if (updatePeriod == "monthly") + { + if (now.time_of_day() < startTime && now.date().day() == 1) + nextScanDate = now.date(); + else + nextScanDate = getNextFirstOfMonth(now.date()); } if (!nextScanDate.is_special()) - scheduleScan( boost::posix_time::ptime (nextScanDate, settings->getUpdateStartTime() ) ); + scheduleScan( boost::posix_time::ptime (nextScanDate, startTime) ); } } @@ -274,27 +269,17 @@ Updater::process(boost::system::error_code err) // 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 (stats.nbChanges() > 0) + Setting::setTime(_db->getSession(), "last_update", now); + // Save the last scan only if it has been completed if (_running) + { + Setting::setTime(_db->getSession(), "last_scan", now); + Setting::setBool(_db->getSession(), "manual_scan_requested", false); + processNextJob(); + } } void @@ -302,8 +287,13 @@ Updater::updateFileExtensions() { Wt::Dbo::Transaction transaction(_db->getSession()); - _audioFileExtensions = MediaDirectorySettings::get(_db->getSession())->getAudioFileExtensions(); - _videoFileExtensions = MediaDirectorySettings::get(_db->getSession())->getVideoFileExtensions(); + _audioFileExtensions.clear(); + for (auto extension : splitString(Setting::getString(_db->getSession(), "audio_file_extensions"), " ")) + _audioFileExtensions.push_back( extension ); + + _videoFileExtensions.clear(); + for (auto extension : splitString(Setting::getString(_db->getSession(), "video_file_extensions"), " ")) + _videoFileExtensions.push_back( extension ); } Artist::pointer diff --git a/src/database/MediaDirectory.cpp b/src/database/MediaDirectory.cpp index 3bbf1ab2..cd2a0cc2 100644 --- a/src/database/MediaDirectory.cpp +++ b/src/database/MediaDirectory.cpp @@ -17,89 +17,18 @@ * along with LMS. If not, see . */ +#include "utils/Utils.hpp" + #include "Types.hpp" -static std::string pathsToString(const std::vector& paths) -{ - std::ostringstream oss; - - bool first = true; - for (auto& path : paths) - { - if (!first) - oss << " "; - - oss << path.string(); - first = false; - } - - return oss.str(); -} - -static std::vector stringToPaths(const std::string value) -{ - std::vector res; - std::istringstream iss(value); - - std::copy(std::istream_iterator(iss), std::istream_iterator(), std::back_inserter(res)); - - return std::vector(res.begin(), res.end()); -} - namespace Database { -MediaDirectorySettings::MediaDirectorySettings() -: _manualScanRequested(false), -_updatePeriod(Never), -_audioFileExtensions(".mp3 .ogg .oga .aac .m4a .flac .wav .wma .aif .aiff .ape .mpc .shn"), -_videoFileExtensions(".flv .avi .mpg .mpeg .mp4 .m4v .mkv .mov .wmv .ogv .divx .m2ts") -{ -} - MediaDirectory::MediaDirectory(boost::filesystem::path p, Type type) : _type(type), - _path(p.string()) + _path(stringTrimEnd(p.string(), "/\\")) { } -MediaDirectorySettings::pointer -MediaDirectorySettings::get(Wt::Dbo::Session& session) -{ - MediaDirectorySettings::pointer res; - - res = session.find().where("id = ?").bind(1); - // TODO bind necessary? - if (!res) - res = session.add( new MediaDirectorySettings()); - - return res; -} - -std::vector -MediaDirectorySettings::getAudioFileExtensions(void) const -{ - return stringToPaths(_audioFileExtensions); -} - -std::vector -MediaDirectorySettings::getVideoFileExtensions(void) const -{ - return stringToPaths(_videoFileExtensions); -} - -void -MediaDirectorySettings::setAudioFileExtensions(std::vector extensions) -{ - _audioFileExtensions = pathsToString(extensions); -} - -void -MediaDirectorySettings::setVideoFileExtensions(std::vector extensions) -{ - _videoFileExtensions = pathsToString(extensions); -} - - MediaDirectory::pointer MediaDirectory::create(Wt::Dbo::Session& session, boost::filesystem::path p, Type type) { @@ -135,5 +64,10 @@ MediaDirectory::get(Wt::Dbo::Session& session, boost::filesystem::path p, Type t return session.find().where("path = ?").where("type = ?").bind( p.string()).bind(type); } +boost::filesystem::path +MediaDirectory::getPath(void) const +{ + return boost::filesystem::path(stringTrimEnd(_path, "/\\")); +} } // namespace Database diff --git a/src/database/MediaDirectory.hpp b/src/database/MediaDirectory.hpp index f4085be5..c77b6b0c 100644 --- a/src/database/MediaDirectory.hpp +++ b/src/database/MediaDirectory.hpp @@ -26,73 +26,8 @@ #include #include - namespace Database { -class MediaDirectory; - -class MediaDirectorySettings -{ - public: - - enum UpdatePeriod { - Never, - Daily, - Weekly, - Monthly - }; - - typedef Wt::Dbo::ptr 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; } - void setAudioFileExtensions(std::vector extensions); - void setVideoFileExtensions(std::vector extensions); - - // 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; } - std::vector getAudioFileExtensions(void) const; - std::vector getVideoFileExtensions(void) const; - - template - 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, _audioFileExtensions, "audio_file_extensions"); - Wt::Dbo::field(a, _videoFileExtensions, "video_file_extensions"); - 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 - std::string _audioFileExtensions; // Extension of the audio files to be scanned - std::string _videoFileExtensions; // Extension of the video files to be scanned - 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 > _mediaDirectories; // list of media directories -}; - - class MediaDirectory { public: @@ -116,14 +51,13 @@ class MediaDirectory static void eraseAll(Wt::Dbo::Session& session); Type getType(void) const { return _type; } - boost::filesystem::path getPath(void) const { return boost::filesystem::path(_path); } + boost::filesystem::path getPath(void) const; template 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: @@ -131,8 +65,6 @@ class MediaDirectory Type _type; std::string _path; - MediaDirectorySettings::pointer _settings; // back pointer - }; } // namespace Database diff --git a/src/database/Setting.cpp b/src/database/Setting.cpp new file mode 100644 index 00000000..c5dc5200 --- /dev/null +++ b/src/database/Setting.cpp @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2016 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 . + */ + +#include "Setting.hpp" + +namespace Database { + +bool +Setting::exists(Wt::Dbo::Session& session, std::string setting) +{ + Wt::Dbo::Transaction transaction(session); + return (getByName(session, setting) != Setting::pointer()); +} + +std::string +Setting::getString(Wt::Dbo::Session& session, std::string setting, std::string defaultValue) +{ + Wt::Dbo::Transaction transaction(session); + + pointer res = getByName(session, setting); + if (!res) + return defaultValue; + + return res->_value; +} + +bool +Setting::getBool(Wt::Dbo::Session& session, std::string setting, bool defaultValue) +{ + Wt::Dbo::Transaction transaction(session); + + pointer res = getByName(session, setting); + if (!res) + return defaultValue; + + return (res->_value == "true"); +} + +boost::posix_time::time_duration +Setting::getDuration(Wt::Dbo::Session& session, std::string setting, boost::posix_time::time_duration defaultValue) +{ + Wt::Dbo::Transaction transaction(session); + + pointer res = getByName(session, setting); + if (!res) + return defaultValue; + + return boost::posix_time::duration_from_string(res->_value); +} + +boost::posix_time::ptime +Setting::getTime(Wt::Dbo::Session& session, std::string setting, boost::posix_time::ptime defaultValue) +{ + Wt::Dbo::Transaction transaction(session); + + pointer res = getByName(session, setting); + if (!res) + return defaultValue; + + return boost::posix_time::time_from_string(res->_value); +} + +int +Setting::getInt(Wt::Dbo::Session& session, std::string setting, int defaultValue) +{ + Wt::Dbo::Transaction transaction(session); + + pointer res = getByName(session, setting); + if (!res) + return defaultValue; + + return std::stoi(res->_value); +} + + + +Setting::pointer +Setting::create(Wt::Dbo::Session& session, std::string name) +{ + return session.add(new Setting(name)); +} + +Setting::pointer +Setting::getByName(Wt::Dbo::Session& session, std::string name) +{ + return session.find().where("name = ?").bind(name); +} + +Setting::pointer +Setting::getOrCreateByName(Wt::Dbo::Session& session, std::string name) +{ + pointer res = getByName(session, name); + if (!res) + res = create(session, name); + + return res; +} + +void +Setting::setString(Wt::Dbo::Session& session, std::string setting, std::string value) +{ + Wt::Dbo::Transaction transaction(session); + getOrCreateByName(session, setting).modify()->_value = value; +} + +void +Setting::setBool(Wt::Dbo::Session& session, std::string setting, bool value) +{ + Wt::Dbo::Transaction transaction(session); + getOrCreateByName(session, setting).modify()->_value = (value ? "true" : "false"); +} + +void +Setting::setDuration(Wt::Dbo::Session& session, std::string setting, boost::posix_time::time_duration value) +{ + Wt::Dbo::Transaction transaction(session); + getOrCreateByName(session, setting).modify()->_value = boost::posix_time::to_simple_string(value); +} + +void +Setting::setTime(Wt::Dbo::Session& session, std::string setting, boost::posix_time::ptime value) +{ + Wt::Dbo::Transaction transaction(session); + getOrCreateByName(session, setting).modify()->_value = boost::posix_time::to_simple_string(value); +} + +void +Setting::setInt(Wt::Dbo::Session& session, std::string setting, int value) +{ + Wt::Dbo::Transaction transaction(session); + getOrCreateByName(session, setting).modify()->_value = std::to_string(value); +} + + +} // namespace Database + diff --git a/src/database/Setting.hpp b/src/database/Setting.hpp new file mode 100644 index 00000000..394fc7bc --- /dev/null +++ b/src/database/Setting.hpp @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2016 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 . + */ + +#pragma once + +#include + +#include + +namespace Database { + +// class meant to store general settings +class Setting +{ + public: + Setting() {} + + // check if a setting exists or not + static bool exists(Wt::Dbo::Session& session, std::string setting); + + // Getters + // Nested transactions + static std::string getString(Wt::Dbo::Session& session, std::string setting, std::string defaultValue = ""); + static bool getBool(Wt::Dbo::Session& session, std::string setting, bool defaultValue = false); + static boost::posix_time::time_duration getDuration(Wt::Dbo::Session& session, std::string setting, boost::posix_time::time_duration defaultDuration = boost::posix_time::seconds(0) ); + static boost::posix_time::ptime getTime(Wt::Dbo::Session& session, std::string setting, boost::posix_time::ptime defaultTime = boost::posix_time::ptime()); + static int getInt(Wt::Dbo::Session& session, std::string setting, int defaultValue = 0); + + // Setters + // Nested transactions + static void setString(Wt::Dbo::Session& session, std::string setting, std::string value); + static void setBool(Wt::Dbo::Session& session, std::string setting, bool value); + static void setDuration(Wt::Dbo::Session& session, std::string setting, boost::posix_time::time_duration value); + static void setTime(Wt::Dbo::Session& session, std::string setting, boost::posix_time::ptime time); + static void setInt(Wt::Dbo::Session& session, std::string setting, int value); + + template + void persist(Action& a) + { + Wt::Dbo::field(a, _name, "name"); + Wt::Dbo::field(a, _value, "value"); + } + + private: + Setting(std::string name) : _name(name) {} + + typedef Wt::Dbo::ptr pointer; + + static pointer getByName(Wt::Dbo::Session& session, std::string name); + static pointer create(Wt::Dbo::Session& session, std::string name); + static pointer getOrCreateByName(Wt::Dbo::Session& session, std::string name); + + std::string _name; + std::string _value; +}; + + +} // namespace Database + diff --git a/src/database/cluster/DatabaseHighLevelCluster.cpp b/src/database/cluster/DatabaseHighLevelCluster.cpp new file mode 100644 index 00000000..c517da3d --- /dev/null +++ b/src/database/cluster/DatabaseHighLevelCluster.cpp @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2016 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 . + */ + +#include "DatabaseHighLevelCluster.hpp" + +#include "config/Config.hpp" +#include "logger/Logger.hpp" +#include "feature/FeatureStore.hpp" + +namespace Database { + +static Cluster::pointer getCluster(std::string type, std::string value) +{ + Cluster::pointer cluster = ( Cluster::get(UpdaterDboSession(), type, value) ); + if (!cluster) + cluster = Cluster::create(UpdaterDboSession(), type, value); + + return cluster; +} + +static std::list getClustersFromFeature(Feature::Type& feature) +{ + struct HighLevelNodeDesc + { + std::string node; + std::map valueMapping; + }; + + static const std::vector nodes = + { + { + "highlevel.danceability", + { + {"not_danceable", "Not danceable"}, + {"danceable", "Danceable"}, + }, + }, + { + "highlevel.gender", + { + {"male", "Male"}, + {"female", "Female"}, + }, + }, + { + "highlevel.mood_acoustic", + { + {"not_acoustic", "Not acoustic"}, + {"acoustic", "Acoustic"}, + }, + }, + { + "highlevel.mood_happy", + { + {"not_happy", "Not happy"}, + {"happy", "Happy"}, + }, + }, + { + "highlevel.mood_aggressive", + { + {"not_aggressive", "Not aggressive"}, + {"aggressive", "Aggressive"}, + }, + }, + { + "highlevel.mood_electronic", + { + {"not_electronic", "Not electronic"}, + {"electronic", "Electronic"}, + }, + }, + { + "highlevel.mood_party", + { + {"not_party", "Not party"}, + {"party", "Party"}, + }, + }, + { + "highlevel.mood_relaxed", + { + {"not_relaxed", "Not relaxed"}, + {"relaxed", "Relaxed"}, + }, + }, + { + "highlevel.mood_sad", + { + {"not_sad", "Not sad"}, + {"sad", "Sad"}, + }, + }, + { + "highlevel.timbre", + { + {"bright", "Bright"}, + {"dark", "Dark"}, + }, + }, + { + "highlevel.tonal_atonal", + { + {"atonal", "Atonal"}, + {"tonal", "Tonal"}, + }, + }, + { + "highlevel.voice_instrumental", + { + {"instrumental", "Instrumental"}, + {"voice", "Voice"}, + }, + }, + }; + + + // Extract info and build clusters + std::list newClusterNames; + + for (auto node : nodes) + { + auto value = feature.get_child_optional(node.node + ".value"); + auto probability = feature.get_child_optional(node.node + ".probability"); + + if (!probability || !value) + { + LMS_LOG(DBUPDATER, DEBUG) << "Missing " << node.node; + continue; + } + + if (std::stod(probability->data()) < 0.90) + { + LMS_LOG(DBUPDATER, DEBUG) << "Probability too low for " << node.node << "(" << std::stod(probability->data()) << ")"; + continue; + } + + if (node.valueMapping[value->data()] == "") + { + LMS_LOG(DBUPDATER, DEBUG) << "Unknown value '" << value->data() << "'"; + continue; + } + + newClusterNames.push_back(node.valueMapping[value->data()]); + } + + return newClusterNames; +} + +void +HighLevelCluster::processDatabaseUpdate(Updater::Stats stats) +{ + bool createTags = Config::instance().getBool("tag-highlevel-acousticbrainz", false); + + + LMS_LOG(DBUPDATER, INFO) << "Creating high level based clusters..."; + + std::vector trackIds = Track::getAllIds(UpdaterDboSession()); + + LMS_LOG(DBUPDATER, DEBUG) << "Got " << trackIds.size() << " tracks"; + for (auto trackId : trackIds) + { + if (UpdaterQuitRequested()) + return; + + LMS_LOG(DBUPDATER, DEBUG) << "Processing track " << trackId; + + // Get current cluster names + std::list newClusterNames; + if (createTags) + { + Feature::Type feature; + if (!Feature::Store::instance().get(UpdaterDboSession(), trackId, "high_level", feature)) + continue; + + newClusterNames = getClustersFromFeature(feature); + } + + Wt::Dbo::Transaction transaction(UpdaterDboSession()); + + auto track = Track::getById(UpdaterDboSession(), trackId); + + auto clusters = track->getClusters(); + for (auto cluster : clusters) + { + // Check if removed + if (cluster->getType() != "high_level") + continue; + + auto it = std::find(newClusterNames.begin(), newClusterNames.end(), cluster->getName()); + if (it == newClusterNames.end()) + cluster.remove(); + else + newClusterNames.erase(it); + + } + + // Add previsouly missing clusters + for (auto newName : newClusterNames) + { + auto cluster = getCluster("high_level", newName); + cluster.modify()->addTrack(track); + } + } + + LMS_LOG(DBUPDATER, INFO) << "High level based clusters processed"; +} + + +} // namespace Database diff --git a/src/database/cluster/DatabaseHighLevelCluster.hpp b/src/database/cluster/DatabaseHighLevelCluster.hpp new file mode 100644 index 00000000..25f91d86 --- /dev/null +++ b/src/database/cluster/DatabaseHighLevelCluster.hpp @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2016 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 . + */ + +#pragma once + +#include "database/DatabaseUpdater.hpp" + +namespace Database { + +class HighLevelCluster +{ + public: + void processDatabaseUpdate(Updater::Stats stats); + +}; + +} // namespace Database diff --git a/src/database/cluster/DatabaseSimilarityCluster.hpp b/src/database/cluster/DatabaseSimilarityCluster.hpp new file mode 100644 index 00000000..e69de29b diff --git a/src/database/tagger/GenreTagger.cpp b/src/database/tagger/GenreTagger.cpp new file mode 100644 index 00000000..ec40da6d --- /dev/null +++ b/src/database/tagger/GenreTagger.cpp @@ -0,0 +1,40 @@ + +/* + * Copyright (C) 2016 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 . + */ + +#include "GenreTagger.hpp" + +namespace Database { + +void +GenreTagger::processTrackUpdate(bool added, Track::id_type trackId, std::string mbid, boost::filesystem::path p) +{ + + +} + +void +GenreTagger::processDatabaseUpdate(Updater::Stats stats) +{ + + // TODO: if disabled, delete all the genre tags + // TODO: if enabled, reconstruct all the genre tags from tracks +} + +} // namespace Database diff --git a/src/database/tagger/GenreTagger.hpp b/src/database/tagger/GenreTagger.hpp new file mode 100644 index 00000000..16118c48 --- /dev/null +++ b/src/database/tagger/GenreTagger.hpp @@ -0,0 +1,37 @@ + +/* + * Copyright (C) 2016 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 . + */ + +#pragma once + +#include "database/DatabaseUpdater.hpp" + +namespace Database { + +class GenreTagger +{ + public: + void processTrackUpdate(bool added, Track::id_type trackId, std::string mbid, boost::filesystem::path p); + void processDatabaseUpdate(Updater::Stats stats); + + private: + +}; + +} // namespace Database diff --git a/src/feature/FeatureStore.cpp b/src/feature/FeatureStore.cpp index e5fff4dc..f58c51ab 100644 --- a/src/feature/FeatureStore.cpp +++ b/src/feature/FeatureStore.cpp @@ -21,6 +21,7 @@ #include "logger/Logger.hpp" #include "config/Config.hpp" +#include "utils/Path.hpp" #include "FeatureStore.hpp" @@ -40,12 +41,20 @@ Store::instance(void) void Store::reload(void) { - _storePath = Config::instance().getString("features-dir-path", ""); + boost::filesystem::path cacheDir = _storePath = Config::instance().getString("cache-dir-path", ""); - if (!boost::filesystem::is_directory(_storePath)) + if (!ensureDirectory(cacheDir)) { - LMS_LOG(DBUPDATER, ERROR) << "Feature directory '" << _storePath << "' not valid!"; - throw std::runtime_error("Invalid feature directory '" + _storePath.string()); + LMS_LOG(DBUPDATER, ERROR) << "Cache directory '" << cacheDir << "' not valid"; + throw std::runtime_error("Cache directory '" + cacheDir.string() + "' not valid!"); + } + + _storePath = cacheDir / "features"; + + if (!ensureDirectory(_storePath)) + { + LMS_LOG(DBUPDATER, ERROR) << "Features directory '" << _storePath << "' not valid"; + throw std::runtime_error("Features directory '" + _storePath.string() + "' not valid"); } } diff --git a/src/main/main.cpp b/src/main/main.cpp index 67671166..f22afc8f 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -26,10 +26,12 @@ #include "av/AvTranscoder.hpp" #include "logger/Logger.hpp" #include "image/Image.hpp" +#include "feature/FeatureExtractor.hpp" #include "database/DatabaseUpdater.hpp" -#include "database/DatabaseClassifier.hpp" -#include "feature/FeatureExtractor.hpp" +#include "database/DatabaseFeatureExtractor.hpp" +#include "database/cluster/DatabaseHighLevelCluster.hpp" + #include "ui/LmsApplication.hpp" @@ -44,16 +46,16 @@ static std::vector getWtArgs(std::string path) if (Config::instance().getBool("tls-enable", false)) { - args.push_back("--https-port=" + std::to_string( Config::instance().getULong("listen-port"))); - args.push_back("--https-address=" + Config::instance().getString("listen-addr")); + args.push_back("--https-port=" + std::to_string( Config::instance().getULong("listen-port", 5081))); + args.push_back("--https-address=" + Config::instance().getString("listen-addr", "0.0.0.0")); args.push_back("--ssl-certificate=" + Config::instance().getString("tls-cert")); args.push_back("--ssl-private-key=" + Config::instance().getString("tls-key")); args.push_back("--ssl-tmp-dh=" + Config::instance().getString("tls-dh")); } else { - args.push_back("--http-port=" + std::to_string( Config::instance().getULong("listen-port"))); - args.push_back("--http-address=" + Config::instance().getString("listen-addr")); + args.push_back("--http-port=" + std::to_string( Config::instance().getULong("listen-port", 5081))); + args.push_back("--http-address=" + Config::instance().getString("listen-addr", "0.0.0.0")); } return args; @@ -104,13 +106,12 @@ int main(int argc, char* argv[]) Database::Updater& dbUpdater = Database::Updater::instance(); dbUpdater.setConnectionPool(*connectionPool); - Database::Classifier dbClassifier(*connectionPool); + Database::FeatureExtractor dbFeatureExtractor; + Database::HighLevelCluster dbHighLevelCluster; - // Connect the classifier to the update events - dbUpdater.trackChanged().connect(std::bind(&Database::Classifier::processTrackUpdate, &dbClassifier, - std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); - dbUpdater.scanComplete().connect(std::bind(&Database::Classifier::processDatabaseUpdate, &dbClassifier, - std::placeholders::_1)); + // Connect to the update events + dbUpdater.scanComplete().connect(std::bind(&Database::HighLevelCluster::processDatabaseUpdate, &dbHighLevelCluster, std::placeholders::_1)); + dbUpdater.scanComplete().connect(std::bind(&Database::FeatureExtractor::processDatabaseUpdate, &dbFeatureExtractor, std::placeholders::_1)); // bind entry point server.addEntryPoint(Wt::Application, boost::bind(UserInterface::LmsApplication::create, diff --git a/src/ui/settings/Settings.cpp b/src/ui/settings/Settings.cpp index 3c68c6fc..308664c8 100644 --- a/src/ui/settings/Settings.cpp +++ b/src/ui/settings/Settings.cpp @@ -31,6 +31,7 @@ #include "logger/Logger.hpp" #include "database/DatabaseUpdater.hpp" +#include "database/Setting.hpp" #include "LmsApplication.hpp" @@ -39,6 +40,8 @@ namespace UserInterface { namespace Settings { +using namespace Database; + Settings::Settings(Wt::WContainerWidget* parent) : Wt::WContainerWidget(parent) { @@ -65,7 +68,7 @@ Settings::Settings(Wt::WContainerWidget* parent) { Wt::Dbo::Transaction transaction(DboSession()); - userId = Database::User::getId(CurrentUser()); + userId = User::getId(CurrentUser()); userIsAdmin = CurrentUser()->isAdmin(); } @@ -77,24 +80,19 @@ Settings::Settings(Wt::WContainerWidget* parent) { LMS_LOG(UI, INFO) << "Media directories have changed: requesting imediate scan"; - // On directory add or delete, request an immediate scan - // - { - Wt::Dbo::Transaction transaction(DboSession()); - Database::MediaDirectorySettings::get(DboSession()).modify()->setManualScanRequested(true); - } - { - std::lock_guard lock(Database::Updater::instance().getMutex()); - Database::Updater::instance().restart(); - } + // On directory change, request an immediate scan + std::lock_guard lock(Updater::instance().getMutex()); + + Setting::setBool(DboSession(), "manual_scan_requested", true); + Updater::instance().restart(); })); menu->addItem("Media Folders", mediaDirectories)->setPathComponent("mediadirectories"); DatabaseFormView* databaseFormView = new DatabaseFormView(); databaseFormView->changed().connect(std::bind([=] { - std::lock_guard lock(Database::Updater::instance().getMutex()); - Database::Updater::instance().restart(); + std::lock_guard lock(Updater::instance().getMutex()); + Updater::instance().restart(); })); menu->addItem("Database", databaseFormView)->setPathComponent("database"); diff --git a/src/ui/settings/SettingsDatabaseFormView.cpp b/src/ui/settings/SettingsDatabaseFormView.cpp index f9ad0ccf..1c93f1d8 100644 --- a/src/ui/settings/SettingsDatabaseFormView.cpp +++ b/src/ui/settings/SettingsDatabaseFormView.cpp @@ -29,7 +29,7 @@ #include #include "logger/Logger.hpp" -#include "database/MediaDirectory.hpp" +#include "database/Setting.hpp" #include "common/DirectoryValidator.hpp" #include "LmsApplication.hpp" @@ -49,6 +49,8 @@ class DatabaseFormModel : public Wt::WFormModel static const Field UpdateStartTimeField; static const Field AudioFileExtensionsField; static const Field VideoFileExtensionsField; + static const Field TagsHighLevelAcousticBrainz; + static const Field TagsSimilarityAcousticBrainz; DatabaseFormModel(Wt::WObject *parent = 0) : Wt::WFormModel(parent) @@ -59,6 +61,8 @@ class DatabaseFormModel : public Wt::WFormModel addField(UpdateStartTimeField); addField(AudioFileExtensionsField); addField(VideoFileExtensionsField); + addField(TagsHighLevelAcousticBrainz); + addField(TagsSimilarityAcousticBrainz); setValidator(UpdatePeriodField, createUpdatePeriodValidator()); setValidator(UpdateStartTimeField, createStartTimeValidator()); @@ -74,117 +78,70 @@ class DatabaseFormModel : public Wt::WFormModel void loadData() { - Wt::Dbo::Transaction transaction(DboSession()); - - // Get refresh settings - MediaDirectorySettings::pointer settings = MediaDirectorySettings::get(DboSession()); - - int periodRow = getUpdatePeriodModelRow( settings->getUpdatePeriod() ); + int periodRow = getUpdatePeriodModelRow( Setting::getString(DboSession(), "update_period")); if (periodRow != -1) - setValue(UpdatePeriodField, updatePeriod(periodRow)); + setValue(UpdatePeriodField, updatePeriodDisplay(periodRow)); - int startTimeRow = getUpdateStartTimeModelRow( settings->getUpdateStartTime() ); + int startTimeRow = getUpdateStartTimeModelRow( Setting::getDuration(DboSession(), "update_start_time") ); if (startTimeRow != -1) setValue(UpdateStartTimeField, updateStartTime( startTimeRow ) ); - std::vector audioFileExtensions = settings->getAudioFileExtensions(); - { - std::ostringstream oss; - for (auto& fileExtension : audioFileExtensions) - oss << fileExtension.string() << " "; + setValue(AudioFileExtensionsField, Setting::getString(DboSession(), "audio_file_extensions")); + setValue(VideoFileExtensionsField, Setting::getString(DboSession(), "video_file_extensions")); - setValue(AudioFileExtensionsField, oss.str()); - } - - std::vector videoFileExtensions = settings->getVideoFileExtensions(); - { - std::ostringstream oss; - for (auto& fileExtension : videoFileExtensions) - oss << fileExtension.string() << " "; - - setValue(VideoFileExtensionsField, oss.str()); - } + setValue(TagsHighLevelAcousticBrainz, Setting::getBool(DboSession(), "tags_highlevel_acousticbrainz")); + setValue(TagsSimilarityAcousticBrainz, Setting::getBool(DboSession(), "tags_similarity_acousticbrainz")); } void saveData() { - Wt::Dbo::Transaction transaction(DboSession()); - - MediaDirectorySettings::pointer settings = MediaDirectorySettings::get(DboSession()); - int periodRow = getUpdatePeriodModelRow( boost::any_cast(value(UpdatePeriodField))); assert(periodRow != -1); - settings.modify()->setUpdatePeriod( updatePeriodDuration( periodRow ) ); + Setting::setString(DboSession(), "update_period", updatePeriodSetting( periodRow ) ); int startTimeRow = getUpdateStartTimeModelRow( boost::any_cast(value(UpdateStartTimeField))); assert(startTimeRow != -1); - settings.modify()->setUpdateStartTime( updateStartTimeDuration( startTimeRow ) ); + Setting::setDuration(DboSession(), "update_start_time", updateStartTimeDuration( startTimeRow ) ); - { - std::vector res; - std::istringstream iss(boost::any_cast(value(AudioFileExtensionsField)).toUTF8()); + Setting::setString(DboSession(), "audio_file_extensions", boost::any_cast(value(AudioFileExtensionsField)).toUTF8()); + Setting::setString(DboSession(), "video_file_extensions", boost::any_cast(value(VideoFileExtensionsField)).toUTF8()); - std::copy(std::istream_iterator(iss), std::istream_iterator(), std::back_inserter(res)); - - settings.modify()->setAudioFileExtensions( std::vector(res.begin(), res.end()) ); - } - - { - std::vector res; - std::istringstream iss(boost::any_cast(value(VideoFileExtensionsField)).toUTF8()); - - std::copy(std::istream_iterator(iss), std::istream_iterator(), std::back_inserter(res)); - - settings.modify()->setVideoFileExtensions( std::vector(res.begin(), res.end()) ); - } + Setting::setBool(DboSession(), "tags_highlevel_acousticbrainz", boost::any_cast(value(TagsHighLevelAcousticBrainz))); + Setting::setBool(DboSession(), "tags_similarity_acousticbrainz", boost::any_cast(value(TagsSimilarityAcousticBrainz))); } - bool setImmediateScan(Wt::WString& error) + void setImmediateScan() { - try - { - Wt::Dbo::Transaction transaction( DboSession()); - - MediaDirectorySettings::pointer settings = MediaDirectorySettings::get(DboSession() ); - - settings.modify()->setManualScanRequested( true ); - } - catch(Wt::Dbo::Exception& exception) - { - LMS_LOG(UI, ERROR) << "Dbo exception: " << exception.what(); - return false; - } - - return true; + Setting::setBool(DboSession(), "manual_scan_requested", true); } int getUpdatePeriodModelRow(Wt::WString value) { for (int i = 0; i < _updatePeriodModel->rowCount(); ++i) { - if (updatePeriod(i) == value) + if (updatePeriodDisplay(i) == value) return i; } return -1; } - int getUpdatePeriodModelRow(MediaDirectorySettings::UpdatePeriod duration) + int getUpdatePeriodModelRow(std::string value) { for (int i = 0; i < _updatePeriodModel->rowCount(); ++i) { - if (updatePeriodDuration(i) == duration) + if (updatePeriodSetting(i) == value) return i; } return -1; } - MediaDirectorySettings::UpdatePeriod updatePeriodDuration(int row) { - return boost::any_cast + std::string updatePeriodSetting(int row) { + return boost::any_cast (_updatePeriodModel->data(_updatePeriodModel->index(row, 0), Wt::UserRole)); } - Wt::WString updatePeriod(int row) { + Wt::WString updatePeriodDisplay(int row) { return boost::any_cast (_updatePeriodModel->data(_updatePeriodModel->index(row, 0), Wt::DisplayRole)); } @@ -230,16 +187,16 @@ class DatabaseFormModel : public Wt::WFormModel _updatePeriodModel = new Wt::WStringListModel(this); _updatePeriodModel->addString("Never"); - _updatePeriodModel->setData(0, 0, MediaDirectorySettings::Never, Wt::UserRole); + _updatePeriodModel->setData(0, 0, std::string("never"), Wt::UserRole); _updatePeriodModel->addString("Daily"); - _updatePeriodModel->setData(1, 0, MediaDirectorySettings::Daily, Wt::UserRole); + _updatePeriodModel->setData(1, 0, std::string("daily"), Wt::UserRole); _updatePeriodModel->addString("Weekly"); - _updatePeriodModel->setData(2, 0, MediaDirectorySettings::Weekly, Wt::UserRole); + _updatePeriodModel->setData(2, 0, std::string("weekly"), Wt::UserRole); _updatePeriodModel->addString("Monthly"); - _updatePeriodModel->setData(3, 0, MediaDirectorySettings::Monthly, Wt::UserRole); + _updatePeriodModel->setData(3, 0, std::string("monthly"), Wt::UserRole); _updateStartTimeModel = new Wt::WStringListModel(this); @@ -290,6 +247,8 @@ const Wt::WFormModel::Field DatabaseFormModel::UpdatePeriodField = "update-peri const Wt::WFormModel::Field DatabaseFormModel::UpdateStartTimeField = "update-start-time"; const Wt::WFormModel::Field DatabaseFormModel::AudioFileExtensionsField = "audio-file-extensions"; const Wt::WFormModel::Field DatabaseFormModel::VideoFileExtensionsField = "video-file-extensions"; +const Wt::WFormModel::Field DatabaseFormModel::TagsHighLevelAcousticBrainz = "tags-highlevel-acousticbrainz"; +const Wt::WFormModel::Field DatabaseFormModel::TagsSimilarityAcousticBrainz = "tags-similarity-acousticbrainz"; DatabaseFormView::DatabaseFormView(Wt::WContainerWidget *parent) @@ -328,6 +287,12 @@ DatabaseFormView::DatabaseFormView(Wt::WContainerWidget *parent) setFormWidget(DatabaseFormModel::VideoFileExtensionsField, videoFileExtensionsEdit); videoFileExtensionsEdit->changed().connect(_applyInfo, &Wt::WWidget::hide); + // Tags from AB high level + setFormWidget(DatabaseFormModel::TagsHighLevelAcousticBrainz, new Wt::WCheckBox()); + + // Tags from AB similarity + setFormWidget(DatabaseFormModel::TagsSimilarityAcousticBrainz, new Wt::WCheckBox()); + // Title & Buttons bindString("title", "Database settings"); @@ -353,21 +318,14 @@ DatabaseFormView::DatabaseFormView(Wt::WContainerWidget *parent) void DatabaseFormView::processImmediateScan() { - Wt::WString error; + + _model->setImmediateScan(); + + _applyInfo->setText( Wt::WString::fromUTF8("Media folder scan has been started!" ) ); + _applyInfo->setStyleClass("alert alert-warning"); _applyInfo->show(); - if (_model->setImmediateScan(error)) - { - _applyInfo->setText( Wt::WString::fromUTF8("Media folder scan has been started!" ) ); - _applyInfo->setStyleClass("alert alert-warning"); - - _sigChanged.emit(); - } - else - { - _applyInfo->setText( error ); - _applyInfo->setStyleClass("alert alert-danger"); - } + _sigChanged.emit(); } void diff --git a/src/utils/Path.cpp b/src/utils/Path.cpp index 9921bd65..b7685416 100644 --- a/src/utils/Path.cpp +++ b/src/utils/Path.cpp @@ -87,3 +87,12 @@ void computeCrc(const boost::filesystem::path& p, std::vector& cr crc.push_back(data[i]); } } + +bool ensureDirectory(boost::filesystem::path dir) +{ + if (boost::filesystem::exists(dir)) + return boost::filesystem::is_directory(dir); + else + return boost::filesystem::create_directory(dir); +} + diff --git a/src/utils/Path.hpp b/src/utils/Path.hpp index 96e4726e..8ebcf95a 100644 --- a/src/utils/Path.hpp +++ b/src/utils/Path.hpp @@ -27,3 +27,8 @@ boost::filesystem::path searchExecPath(std::string filename); void computeCrc(const boost::filesystem::path& p, std::vector& checksum); + +// Make sure the given path is a directory +// Create it if needed +bool ensureDirectory(boost::filesystem::path dir); + diff --git a/src/utils/Utils.cpp b/src/utils/Utils.cpp index 3f968730..8b71586a 100644 --- a/src/utils/Utils.cpp +++ b/src/utils/Utils.cpp @@ -112,6 +112,14 @@ stringTrim(const std::string& str, const std::string& whitespace) return str.substr(strBegin, strRange); } +std::string +stringTrimEnd(const std::string& str, const std::string& whitespace) +{ + return str.substr(0, str.find_last_not_of(whitespace)+1); +} + + + std::string stringToUTF8(const std::string& str) { diff --git a/src/utils/Utils.hpp b/src/utils/Utils.hpp index e8c71ea6..a7e89054 100644 --- a/src/utils/Utils.hpp +++ b/src/utils/Utils.hpp @@ -39,7 +39,10 @@ std::vector splitString(std::string string, std::string separators); std::string -stringTrim(const std::string& str, const std::string& whitespace = " \t"); +stringTrim(const std::string& str, const std::string& whitespaces = " \t"); + +std::string +stringTrimEnd(const std::string& str, const std::string& whitespaces = " \t"); std::string stringToUTF8(const std::string& str); From 445f4596549885d8495054137dd8ba75a255edef Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 28 May 2016 23:07:28 +0200 Subject: [PATCH 018/195] [DB] Acousticbrainz tagging now use new setting system --- src/database/DatabaseUpdater.cpp | 2 +- src/database/cluster/DatabaseHighLevelCluster.cpp | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/database/DatabaseUpdater.cpp b/src/database/DatabaseUpdater.cpp index 7a1fa6b7..0ba96f85 100644 --- a/src/database/DatabaseUpdater.cpp +++ b/src/database/DatabaseUpdater.cpp @@ -124,7 +124,7 @@ Updater& Updater::instance(void) } Updater::Updater() - : _running(false), + : _running(true), _scheduleTimer(_ioService) { _ioService.setThreadCount(1); diff --git a/src/database/cluster/DatabaseHighLevelCluster.cpp b/src/database/cluster/DatabaseHighLevelCluster.cpp index c517da3d..81ce53f9 100644 --- a/src/database/cluster/DatabaseHighLevelCluster.cpp +++ b/src/database/cluster/DatabaseHighLevelCluster.cpp @@ -17,12 +17,13 @@ * along with LMS. If not, see . */ -#include "DatabaseHighLevelCluster.hpp" - -#include "config/Config.hpp" #include "logger/Logger.hpp" + +#include "database/Setting.hpp" #include "feature/FeatureStore.hpp" +#include "DatabaseHighLevelCluster.hpp" + namespace Database { static Cluster::pointer getCluster(std::string type, std::string value) @@ -166,8 +167,7 @@ static std::list getClustersFromFeature(Feature::Type& feature) void HighLevelCluster::processDatabaseUpdate(Updater::Stats stats) { - bool createTags = Config::instance().getBool("tag-highlevel-acousticbrainz", false); - + bool createTags = Setting::getBool(UpdaterDboSession(), "tags_highlevel_acousticbrainz", false); LMS_LOG(DBUPDATER, INFO) << "Creating high level based clusters..."; @@ -179,8 +179,6 @@ HighLevelCluster::processDatabaseUpdate(Updater::Stats stats) if (UpdaterQuitRequested()) return; - LMS_LOG(DBUPDATER, DEBUG) << "Processing track " << trackId; - // Get current cluster names std::list newClusterNames; if (createTags) From fd9fdb8e87d39192944fa5478b4685fc9fbbc8c8 Mon Sep 17 00:00:00 2001 From: emeric Date: Mon, 30 May 2016 13:57:18 +0200 Subject: [PATCH 019/195] [UI] Added option to parametrize the min probability for creating high level tags --- approot/templates.xml | 23 +++++++++++--- src/database/DatabaseHandler.cpp | 3 ++ .../cluster/DatabaseHighLevelCluster.cpp | 5 ++-- src/main/main.cpp | 2 ++ src/ui/settings/SettingsDatabaseFormView.cpp | 30 +++++++++++++++++-- 5 files changed, 55 insertions(+), 8 deletions(-) diff --git a/approot/templates.xml b/approot/templates.xml index 80afcd58..007c9824 100644 --- a/approot/templates.xml +++ b/approot/templates.xml @@ -297,7 +297,7 @@ - ${title} + ${scan-title}
- +
+ ${tags-title} +
${tags-highlevel-acousticbrainz} @@ -359,9 +361,22 @@
+
+ +
+ ${tags-highlevel-acousticbrainz-min-probability} +
+
+ ${tags-highlevel-acousticbrainz-min-probability-info} +
+
+ +
${tags-similarity-acousticbrainz} diff --git a/src/database/DatabaseHandler.cpp b/src/database/DatabaseHandler.cpp index 270e4fe1..f9c46053 100644 --- a/src/database/DatabaseHandler.cpp +++ b/src/database/DatabaseHandler.cpp @@ -126,6 +126,9 @@ Handler::Handler(Wt::Dbo::SqlConnectionPool& connectionPool) if (!Setting::exists(_session, "tags_highlevel_acousticbrainz")) Setting::setBool(_session, "tags_highlevel_acousticbrainz", true); + if (!Setting::exists(_session, "tags_highlevel_acousticbrainz_min_probability")) + Setting::setInt(_session, "tags_highlevel_acousticbrainz_min_probability", 90); + if (!Setting::exists(_session, "tags_similarity_acousticbrainz")) Setting::setBool(_session, "tags_similarity_acousticbrain", false); } diff --git a/src/database/cluster/DatabaseHighLevelCluster.cpp b/src/database/cluster/DatabaseHighLevelCluster.cpp index 81ce53f9..84af0d28 100644 --- a/src/database/cluster/DatabaseHighLevelCluster.cpp +++ b/src/database/cluster/DatabaseHighLevelCluster.cpp @@ -35,7 +35,7 @@ static Cluster::pointer getCluster(std::string type, std::string value) return cluster; } -static std::list getClustersFromFeature(Feature::Type& feature) +static std::list getClustersFromFeature(Feature::Type& feature, double minProb) { struct HighLevelNodeDesc { @@ -168,6 +168,7 @@ void HighLevelCluster::processDatabaseUpdate(Updater::Stats stats) { bool createTags = Setting::getBool(UpdaterDboSession(), "tags_highlevel_acousticbrainz", false); + double minProb = Setting::getInt(UpdaterDboSession(), "tags_highlevel_acousticbrainz_min_probability", false) / 100.; LMS_LOG(DBUPDATER, INFO) << "Creating high level based clusters..."; @@ -187,7 +188,7 @@ HighLevelCluster::processDatabaseUpdate(Updater::Stats stats) if (!Feature::Store::instance().get(UpdaterDboSession(), trackId, "high_level", feature)) continue; - newClusterNames = getClustersFromFeature(feature); + newClusterNames = getClustersFromFeature(feature, minProb); } Wt::Dbo::Transaction transaction(UpdaterDboSession()); diff --git a/src/main/main.cpp b/src/main/main.cpp index f22afc8f..1c8eecc5 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -113,6 +113,8 @@ int main(int argc, char* argv[]) dbUpdater.scanComplete().connect(std::bind(&Database::HighLevelCluster::processDatabaseUpdate, &dbHighLevelCluster, std::placeholders::_1)); dbUpdater.scanComplete().connect(std::bind(&Database::FeatureExtractor::processDatabaseUpdate, &dbFeatureExtractor, std::placeholders::_1)); +// dbHighLevelCluster.processDatabaseUpdate(Database::Updater::Stats()); + // bind entry point server.addEntryPoint(Wt::Application, boost::bind(UserInterface::LmsApplication::create, _1, boost::ref(*connectionPool))); diff --git a/src/ui/settings/SettingsDatabaseFormView.cpp b/src/ui/settings/SettingsDatabaseFormView.cpp index 1c93f1d8..ffc15630 100644 --- a/src/ui/settings/SettingsDatabaseFormView.cpp +++ b/src/ui/settings/SettingsDatabaseFormView.cpp @@ -23,7 +23,9 @@ #include #include #include +#include #include +#include #include #include @@ -50,6 +52,7 @@ class DatabaseFormModel : public Wt::WFormModel static const Field AudioFileExtensionsField; static const Field VideoFileExtensionsField; static const Field TagsHighLevelAcousticBrainz; + static const Field TagsHighLevelAcousticBrainzMinProbability; static const Field TagsSimilarityAcousticBrainz; DatabaseFormModel(Wt::WObject *parent = 0) @@ -62,12 +65,14 @@ class DatabaseFormModel : public Wt::WFormModel addField(AudioFileExtensionsField); addField(VideoFileExtensionsField); addField(TagsHighLevelAcousticBrainz); + addField(TagsHighLevelAcousticBrainzMinProbability); addField(TagsSimilarityAcousticBrainz); setValidator(UpdatePeriodField, createUpdatePeriodValidator()); setValidator(UpdateStartTimeField, createStartTimeValidator()); setValidator(AudioFileExtensionsField, createFileExtensionValidator()); setValidator(VideoFileExtensionsField, createFileExtensionValidator()); + setValidator(TagsHighLevelAcousticBrainzMinProbability, createMinProbabilityValidator()); // populate the model with initial data loadData(); @@ -90,6 +95,7 @@ class DatabaseFormModel : public Wt::WFormModel setValue(VideoFileExtensionsField, Setting::getString(DboSession(), "video_file_extensions")); setValue(TagsHighLevelAcousticBrainz, Setting::getBool(DboSession(), "tags_highlevel_acousticbrainz")); + setValue(TagsHighLevelAcousticBrainzMinProbability, Setting::getInt(DboSession(), "tags_highlevel_acousticbrainz_min_probability")); setValue(TagsSimilarityAcousticBrainz, Setting::getBool(DboSession(), "tags_similarity_acousticbrainz")); } @@ -107,6 +113,7 @@ class DatabaseFormModel : public Wt::WFormModel Setting::setString(DboSession(), "video_file_extensions", boost::any_cast(value(VideoFileExtensionsField)).toUTF8()); Setting::setBool(DboSession(), "tags_highlevel_acousticbrainz", boost::any_cast(value(TagsHighLevelAcousticBrainz))); + Setting::setInt(DboSession(), "tags_highlevel_acousticbrainz_min_probability", std::stoi(boost::any_cast(value(TagsHighLevelAcousticBrainzMinProbability)).toUTF8())); Setting::setBool(DboSession(), "tags_similarity_acousticbrainz", boost::any_cast(value(TagsSimilarityAcousticBrainz))); } @@ -238,6 +245,13 @@ class DatabaseFormModel : public Wt::WFormModel return v; } + Wt::WValidator *createMinProbabilityValidator() + { + Wt::WIntValidator *v = new Wt::WIntValidator(50, 100); + v->setMandatory(true); + return v; + } + Wt::WStringListModel* _updatePeriodModel; Wt::WStringListModel* _updateStartTimeModel; @@ -248,6 +262,7 @@ const Wt::WFormModel::Field DatabaseFormModel::UpdateStartTimeField = "update-s const Wt::WFormModel::Field DatabaseFormModel::AudioFileExtensionsField = "audio-file-extensions"; const Wt::WFormModel::Field DatabaseFormModel::VideoFileExtensionsField = "video-file-extensions"; const Wt::WFormModel::Field DatabaseFormModel::TagsHighLevelAcousticBrainz = "tags-highlevel-acousticbrainz"; +const Wt::WFormModel::Field DatabaseFormModel::TagsHighLevelAcousticBrainzMinProbability = "tags-highlevel-acousticbrainz-min-probability"; const Wt::WFormModel::Field DatabaseFormModel::TagsSimilarityAcousticBrainz = "tags-similarity-acousticbrainz"; @@ -288,13 +303,24 @@ DatabaseFormView::DatabaseFormView(Wt::WContainerWidget *parent) videoFileExtensionsEdit->changed().connect(_applyInfo, &Wt::WWidget::hide); // Tags from AB high level - setFormWidget(DatabaseFormModel::TagsHighLevelAcousticBrainz, new Wt::WCheckBox()); + Wt::WCheckBox* highLevel = new Wt::WCheckBox(); + setFormWidget(DatabaseFormModel::TagsHighLevelAcousticBrainz, highLevel); + + setFormWidget(DatabaseFormModel::TagsHighLevelAcousticBrainzMinProbability, new Wt::WSpinBox()); + + highLevel->changed().connect(std::bind([=] { + _model->setReadOnly(DatabaseFormModel::TagsHighLevelAcousticBrainzMinProbability, + !(highLevel->checkState() == Wt::Checked)); + updateModel(_model); + updateViewField(_model, DatabaseFormModel::TagsHighLevelAcousticBrainzMinProbability); + })); // Tags from AB similarity setFormWidget(DatabaseFormModel::TagsSimilarityAcousticBrainz, new Wt::WCheckBox()); // Title & Buttons - bindString("title", "Database settings"); + bindString("scan-title", "Scan settings"); + bindString("tags-title", "Tag settings"); Wt::WPushButton *saveButton = new Wt::WPushButton("Apply"); bindWidget("apply-button", saveButton); From 147c0d752610f8c4ce0e1ee6f548becfce2ba622 Mon Sep 17 00:00:00 2001 From: emeric Date: Mon, 30 May 2016 14:04:02 +0200 Subject: [PATCH 020/195] [DOC] updated TODO --- TODO | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/TODO b/TODO index 1a37b681..f2173cb6 100644 --- a/TODO +++ b/TODO @@ -7,18 +7,18 @@ [Cover] - Handle preferred cover file names ("front.xxx", "cover.xxx", ...) -- Implement a cache and a grabber from some web service (mandatory for artists?) +- Implement a cache and a grabber from some web service (mandatory for artists?) coverartarchive.org [Database] +- Make filters using tags (support AND) + optim - Optim, use SQL query to get the "genre" orphans - Use Inotify like system to watch modified/added files? - handle access rights problems (instead of aborting) - add a global play counter for tracks. This will help people to spot most popular files -- rework the exception process in av/metadata/updater in case of bad files - Use the WServer::post method to notify the end of the database scan? (with results?) [Metadata] -- OGG metadata -> properly handle metadata nested in the audio stream +- WMA covers: add support [Playlist] - Make public playlists so that users can see what other people are listening to @@ -59,6 +59,7 @@ - TrackView: Reselect the current selected item when displaying the updated search results - Add keyboard shortcuts - playqueue: try to get a single div to display the cover / artist / track name in order to improve style + - Add a hint for the user to get the nature of tag (created from genre ? created from audio features?) [mobile] - Reorganize the views to ease internal paths - Covers not always the same height @@ -67,7 +68,6 @@ - Implement a play queue - Implement a decent player - Make the release/artist clickable when displayed to the user - [Video] - implement a decent mediaplayer - remove codec choice from parameters From f2b635d2633015721305320cfb245f468f3a4b18 Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 4 Jun 2016 18:17:30 +0200 Subject: [PATCH 021/195] [DB] Source reorg --- src/Makefile.am | 6 +- src/database/DatabaseClassifier.cpp | 878 ------------------ src/database/DatabaseClassifier.hpp | 55 -- .../cluster/DatabaseSimilarityCluster.hpp | 0 src/database/tagger/GenreTagger.cpp | 40 - src/database/tagger/GenreTagger.hpp | 37 - .../DatabaseFeatureExtractor.cpp | 4 +- .../DatabaseFeatureExtractor.hpp | 6 +- .../DatabaseHighLevelCluster.cpp | 2 +- .../DatabaseHighLevelCluster.hpp | 6 +- .../{ => updater}/DatabaseUpdater.cpp | 5 +- .../{ => updater}/DatabaseUpdater.hpp | 23 +- src/main/main.cpp | 17 +- src/ui/settings/Settings.cpp | 2 +- 14 files changed, 44 insertions(+), 1037 deletions(-) delete mode 100644 src/database/DatabaseClassifier.cpp delete mode 100644 src/database/DatabaseClassifier.hpp delete mode 100644 src/database/cluster/DatabaseSimilarityCluster.hpp delete mode 100644 src/database/tagger/GenreTagger.cpp delete mode 100644 src/database/tagger/GenreTagger.hpp rename src/database/{ => updater}/DatabaseFeatureExtractor.cpp (96%) rename src/database/{ => updater}/DatabaseFeatureExtractor.hpp (86%) rename src/database/{cluster => updater}/DatabaseHighLevelCluster.cpp (98%) rename src/database/{cluster => updater}/DatabaseHighLevelCluster.hpp (86%) rename src/database/{ => updater}/DatabaseUpdater.cpp (99%) rename src/database/{ => updater}/DatabaseUpdater.hpp (90%) diff --git a/src/Makefile.am b/src/Makefile.am index 2e541383..568fb488 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -7,9 +7,7 @@ lms_SOURCES = \ $(srcdir)/config/Config.cpp \ $(srcdir)/cover/CoverArtGrabber.cpp \ $(srcdir)/database/Artist.cpp \ - $(srcdir)/database/DatabaseFeatureExtractor.cpp \ $(srcdir)/database/DatabaseHandler.cpp \ - $(srcdir)/database/DatabaseUpdater.cpp \ $(srcdir)/database/MediaDirectory.cpp \ $(srcdir)/database/Playlist.cpp \ $(srcdir)/database/Release.cpp \ @@ -19,7 +17,9 @@ lms_SOURCES = \ $(srcdir)/database/Track.cpp \ $(srcdir)/database/User.cpp \ $(srcdir)/database/Video.cpp \ - $(srcdir)/database/cluster/DatabaseHighLevelCluster.cpp \ + $(srcdir)/database/updater/DatabaseUpdater.cpp \ + $(srcdir)/database/updater/DatabaseFeatureExtractor.cpp \ + $(srcdir)/database/updater/DatabaseHighLevelCluster.cpp \ $(srcdir)/feature/FeatureExtractor.cpp \ $(srcdir)/feature/FeatureStore.cpp \ $(srcdir)/image/Image.cpp \ diff --git a/src/database/DatabaseClassifier.cpp b/src/database/DatabaseClassifier.cpp deleted file mode 100644 index 5aff1b00..00000000 --- a/src/database/DatabaseClassifier.cpp +++ /dev/null @@ -1,878 +0,0 @@ -/* - * Copyright (C) 2016 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 . - */ - -//#define BOOST_SPIRIT_THREADSAFE -#include - -#include "logger/Logger.hpp" -#include "feature/FeatureExtractor.hpp" - -#include "knnl/neural_net_headers.hpp" - -#include "DatabaseClassifier.hpp" - -namespace Database { - -Classifier::Classifier(Wt::Dbo::SqlConnectionPool& connectionPool) -: _db(connectionPool) -{} - -void -Classifier::processTrackUpdate(bool added, Track::id_type trackId, std::string mbid, boost::filesystem::path path) -{ - if (!added) - return; - - if (mbid.empty()) - { - // TODO compute from file - LMS_LOG(DBUPDATER, INFO) << "File '" << path << "' has no MBID: skipping feature extraction"; - return; - } - - boost::property_tree::ptree pt; - if (::Feature::Extractor::getLowLevel(pt, mbid)) - { - std::ostringstream oss; - boost::property_tree::write_json(oss, pt); - - { - Wt::Dbo::Transaction transaction(_db.getSession()); - - Track::pointer track = Database::Track::getById(_db.getSession(), trackId); - Feature::create( _db.getSession(), track, "low_level", oss.str()); - } - } - - pt.clear(); - if (::Feature::Extractor::getHighLevel(pt, mbid)) - { - std::ostringstream oss; - boost::property_tree::write_json(oss, pt); - - { - Wt::Dbo::Transaction transaction(_db.getSession()); - - Track::pointer track = Database::Track::getById(_db.getSession(), trackId); - Feature::create( _db.getSession(), track, "high_level", oss.str()); - } - } -} - -typedef std::vector entry_t; -typedef std::vector Entries; - -static bool entryAddData(entry_t& entry, const boost::property_tree::ptree& pt, std::size_t nbDimensions) -{ - if (pt.empty() && nbDimensions == 1) - { - double value = std::stod(pt.data()); - entry.push_back(value); - - return true; - } - - if (pt.size() == nbDimensions) - { - for (auto it : pt) - { - double value = std::stod(it.second.data()); - entry.push_back(value); - } - - return true; - } - - LMS_LOG(DBUPDATER, DEBUG) << "Bad entry"; - - return false; -} - -struct FeatureDesc -{ - std::string lowLevelName; - std::size_t nbDimensions; - double coeff; -}; - -static std::vector features = -{ -// { "lowlevel.average_loudness", 1, 1.0 }, -// { "lowlevel.barkbands.dmean", 27, 1.0 }, -// { "lowlevel.barkbands.dmean2", 27, 1.0 }, -// { "lowlevel.barkbands.dvar", 27, 1.0 }, -// { "lowlevel.barkbands.dvar2", 27, 1.0 }, -// { "lowlevel.barkbands.max", 27, 1.0 }, -// { "lowlevel.barkbands.mean", 27, 1.0 }, -// { "lowlevel.barkbands.median", 27, 1.0 }, -// { "lowlevel.barkbands.min", 27, 1.0 }, -// { "lowlevel.barkbands.var", 27, 1.0 }, -// { "lowlevel.barkbands_crest.dmean", 1, 1.0 }, -// { "lowlevel.barkbands_crest.dmean2", 1, 1.0 }, -// { "lowlevel.barkbands_crest.dvar", 1, 1.0 }, -// { "lowlevel.barkbands_crest.dvar2", 1, 1.0 }, -// { "lowlevel.barkbands_crest.max", 1, 1.0 }, -// { "lowlevel.barkbands_crest.mean", 1, 1.0 }, -// { "lowlevel.barkbands_crest.median", 1, 1.0 }, -// { "lowlevel.barkbands_crest.min", 1, 1.0 }, -// { "lowlevel.barkbands_crest.var", 1, 1.0 }, -// { "lowlevel.barkbands_flatness_db.dmean", 1, 1.0 }, -// { "lowlevel.barkbands_flatness_db.dmean2", 1, 1.0 }, -// { "lowlevel.barkbands_flatness_db.dvar", 1, 1.0 }, -// { "lowlevel.barkbands_flatness_db.dvar2", 1, 1.0 }, -// { "lowlevel.barkbands_flatness_db.max", 1, 1.0 }, -// { "lowlevel.barkbands_flatness_db.mean", 1, 1.0 }, -// { "lowlevel.barkbands_flatness_db.median", 1, 1.0 }, -// { "lowlevel.barkbands_flatness_db.min", 1, 1.0 }, -// { "lowlevel.barkbands_flatness_db.var", 1, 1.0 }, -// { "lowlevel.barkbands_kurtosis.dmean", 1, 1.0 }, -// { "lowlevel.barkbands_kurtosis.dmean2", 1, 1.0 }, -// { "lowlevel.barkbands_kurtosis.dvar", 1, 1.0 }, -// { "lowlevel.barkbands_kurtosis.dvar2", 1, 1.0 }, -// { "lowlevel.barkbands_kurtosis.max", 1, 1.0 }, -// { "lowlevel.barkbands_kurtosis.mean", 1, 1.0 }, -// { "lowlevel.barkbands_kurtosis.median", 1, 1.0 }, -// { "lowlevel.barkbands_kurtosis.min", 1, 1.0 }, -// { "lowlevel.barkbands_kurtosis.var", 1, 1.0 }, -// { "lowlevel.barkbands_skewness.dmean", 1, 1.0 }, -// { "lowlevel.barkbands_skewness.dmean2", 1, 1.0 }, -// { "lowlevel.barkbands_skewness.dvar", 1, 1.0 }, -// { "lowlevel.barkbands_skewness.dvar2", 1, 1.0 }, -// { "lowlevel.barkbands_skewness.max", 1, 1.0 }, -// { "lowlevel.barkbands_skewness.mean", 1, 1.0 }, -// { "lowlevel.barkbands_skewness.median", 1, 1.0 }, -// { "lowlevel.barkbands_skewness.min", 1, 1.0 }, -// { "lowlevel.barkbands_skewness.var", 1, 1.0 }, -// { "lowlevel.barkbands_spread.dmean", 1, 1.0 }, -// { "lowlevel.barkbands_spread.dmean2", 1, 1.0 }, -// { "lowlevel.barkbands_spread.dvar", 1, 1.0 }, -// { "lowlevel.barkbands_spread.dvar2", 1, 1.0 }, -// { "lowlevel.barkbands_spread.max", 1, 1.0 }, -// { "lowlevel.barkbands_spread.mean", 1, 1.0 }, -// { "lowlevel.barkbands_spread.median", 1, 1.0 }, -// { "lowlevel.barkbands_spread.min", 1, 1.0 }, -// { "lowlevel.barkbands_spread.var", 1, 1.0 }, -// { "lowlevel.dissonance.dmean", 1, 1.0 }, -// { "lowlevel.dissonance.dmean2", 1, 1.0 }, -// { "lowlevel.dissonance.dvar", 1, 1.0 }, -// { "lowlevel.dissonance.dvar2", 1, 1.0 }, -// { "lowlevel.dissonance.max", 1, 1.0 }, -// { "lowlevel.dissonance.mean", 1, 1.0 }, -// { "lowlevel.dissonance.median", 1, 1.0 }, -// { "lowlevel.dissonance.min", 1, 1.0 }, -// { "lowlevel.dissonance.var", 1, 1.0 }, -// { "lowlevel.dynamic_complexity", 1, 1.0 }, -// { "lowlevel.erbbands.dmean", 40, 1.0 }, -// { "lowlevel.erbbands.dmean2", 40, 1.0 }, -// { "lowlevel.erbbands.dvar", 40, 1.0 }, -// { "lowlevel.erbbands.dvar2", 40, 1.0 }, -// { "lowlevel.erbbands.max", 40, 1.0 }, -// { "lowlevel.erbbands.mean", 40, 1.0 }, -// { "lowlevel.erbbands.median", 40, 1.0 }, -// { "lowlevel.erbbands.min", 40, 1.0 }, -// { "lowlevel.erbbands.var", 40, 1.0 }, -// { "lowlevel.erbbands_crest.dmean", 1, 1.0 }, -// { "lowlevel.erbbands_crest.dmean2", 1, 1.0 }, -// { "lowlevel.erbbands_crest.dvar", 1, 1.0 }, -// { "lowlevel.erbbands_crest.dvar2", 1, 1.0 }, -// { "lowlevel.erbbands_crest.max", 1, 1.0 }, -// { "lowlevel.erbbands_crest.mean", 1, 1.0 }, -// { "lowlevel.erbbands_crest.median", 1, 1.0 }, -// { "lowlevel.erbbands_crest.min", 1, 1.0 }, -// { "lowlevel.erbbands_crest.var", 1, 1.0 }, -// { "lowlevel.erbbands_flatness_db.dmean", 1, 1.0 }, -// { "lowlevel.erbbands_flatness_db.dmean2", 1, 1.0 }, -// { "lowlevel.erbbands_flatness_db.dvar", 1, 1.0 }, -// { "lowlevel.erbbands_flatness_db.dvar2", 1, 1.0 }, -// { "lowlevel.erbbands_flatness_db.max", 1, 1.0 }, -// { "lowlevel.erbbands_flatness_db.mean", 1, 1.0 }, -// { "lowlevel.erbbands_flatness_db.median", 1, 1.0 }, -// { "lowlevel.erbbands_flatness_db.min", 1, 1.0 }, -// { "lowlevel.erbbands_flatness_db.var", 1, 1.0 }, -// { "lowlevel.erbbands_kurtosis.dmean", 1, 1.0 }, -// { "lowlevel.erbbands_kurtosis.dmean2", 1, 1.0 }, -// { "lowlevel.erbbands_kurtosis.dvar", 1, 1.0 }, -// { "lowlevel.erbbands_kurtosis.dvar2", 1, 1.0 }, -// { "lowlevel.erbbands_kurtosis.max", 1, 1.0 }, -// { "lowlevel.erbbands_kurtosis.mean", 1, 1.0 }, -// { "lowlevel.erbbands_kurtosis.median", 1, 1.0 }, -// { "lowlevel.erbbands_kurtosis.min", 1, 1.0 }, -// { "lowlevel.erbbands_kurtosis.var", 1, 1.0 }, -// { "lowlevel.erbbands_skewness.dmean", 1, 1.0 }, -// { "lowlevel.erbbands_skewness.dmean2", 1, 1.0 }, -// { "lowlevel.erbbands_skewness.dvar", 1, 1.0 }, -// { "lowlevel.erbbands_skewness.dvar2", 1, 1.0 }, -// { "lowlevel.erbbands_skewness.max", 1, 1.0 }, -// { "lowlevel.erbbands_skewness.mean", 1, 1.0 }, -// { "lowlevel.erbbands_skewness.median", 1, 1.0 }, -// { "lowlevel.erbbands_skewness.min", 1, 1.0 }, -// { "lowlevel.erbbands_skewness.var", 1, 1.0 }, -// { "lowlevel.erbbands_spread.dmean", 1, 1.0 }, -// { "lowlevel.erbbands_spread.dmean2", 1, 1.0 }, -// { "lowlevel.erbbands_spread.dvar", 1, 1.0 }, -// { "lowlevel.erbbands_spread.dvar2", 1, 1.0 }, -// { "lowlevel.erbbands_spread.max", 1, 1.0 }, -// { "lowlevel.erbbands_spread.mean", 1, 1.0 }, -// { "lowlevel.erbbands_spread.median", 1, 1.0 }, -// { "lowlevel.erbbands_spread.min", 1, 1.0 }, -// { "lowlevel.erbbands_spread.var", 1, 1.0 }, -// { "lowlevel.gfcc.mean", 13, 1.0 }, -// { "lowlevel.hfc.dmean", 1, 1.0 }, -// { "lowlevel.hfc.dmean2", 1, 1.0 }, -// { "lowlevel.hfc.dvar", 1, 1.0 }, -// { "lowlevel.hfc.dvar2", 1, 1.0 }, -// { "lowlevel.hfc.max", 1, 1.0 }, -// { "lowlevel.hfc.mean", 1, 1.0 }, -// { "lowlevel.hfc.median", 1, 1.0 }, -// { "lowlevel.hfc.min", 1, 1.0 }, -// { "lowlevel.hfc.var", 1, 1.0 }, -// { "lowlevel.melbands.dmean", 40, 1.0 }, -// { "lowlevel.melbands.dmean2", 40, 1.0 }, -// { "lowlevel.melbands.dvar", 40, 1.0 }, -// { "lowlevel.melbands.dvar2", 40, 1.0 }, -// { "lowlevel.melbands.max", 40, 1.0 }, -// { "lowlevel.melbands.mean", 40, 1.0 }, -// { "lowlevel.melbands.median", 40, 1.0 }, -// { "lowlevel.melbands.min", 40, 1.0 }, -// { "lowlevel.melbands.var", 40, 1.0 }, -// { "lowlevel.melbands_crest.dmean", 1, 1.0 }, -// { "lowlevel.melbands_crest.dmean2", 1, 1.0 }, -// { "lowlevel.melbands_crest.dvar", 1, 1.0 }, -// { "lowlevel.melbands_crest.dvar2", 1, 1.0 }, -// { "lowlevel.melbands_crest.max", 1, 1.0 }, -// { "lowlevel.melbands_crest.mean", 1, 1.0 }, -// { "lowlevel.melbands_crest.median", 1, 1.0 }, -// { "lowlevel.melbands_crest.min", 1, 1.0 }, -// { "lowlevel.melbands_crest.var", 1, 1.0 }, -// { "lowlevel.melbands_flatness_db.dmean", 1, 1.0 }, -// { "lowlevel.melbands_flatness_db.dmean2", 1, 1.0 }, -// { "lowlevel.melbands_flatness_db.dvar", 1, 1.0 }, -// { "lowlevel.melbands_flatness_db.dvar2", 1, 1.0 }, -// { "lowlevel.melbands_flatness_db.max", 1, 1.0 }, -// { "lowlevel.melbands_flatness_db.mean", 1, 1.0 }, -// { "lowlevel.melbands_flatness_db.median", 1, 1.0 }, -// { "lowlevel.melbands_flatness_db.min", 1, 1.0 }, -// { "lowlevel.melbands_flatness_db.var", 1, 1.0 }, -// { "lowlevel.melbands_kurtosis.dmean", 1, 1.0 }, -// { "lowlevel.melbands_kurtosis.dmean2", 1, 1.0 }, -// { "lowlevel.melbands_kurtosis.dvar", 1, 1.0 }, -// { "lowlevel.melbands_kurtosis.dvar2", 1, 1.0 }, -// { "lowlevel.melbands_kurtosis.max", 1, 1.0 }, -// { "lowlevel.melbands_kurtosis.mean", 1, 1.0 }, -// { "lowlevel.melbands_kurtosis.median", 1, 1.0 }, -// { "lowlevel.melbands_kurtosis.min", 1, 1.0 }, -// { "lowlevel.melbands_kurtosis.var", 1, 1.0 }, -// { "lowlevel.melbands_spread.dmean", 1, 1.0 }, -// { "lowlevel.melbands_spread.dmean2", 1, 1.0 }, -// { "lowlevel.melbands_spread.dvar", 1, 1.0 }, -// { "lowlevel.melbands_spread.dvar2", 1, 1.0 }, -// { "lowlevel.melbands_spread.max", 1, 1.0 }, -// { "lowlevel.melbands_spread.mean", 1, 1.0 }, -// { "lowlevel.melbands_spread.median", 1, 1.0 }, -// { "lowlevel.melbands_spread.min", 1, 1.0 }, -// { "lowlevel.melbands_spread.var", 1, 1.0 }, -// { "lowlevel.mfcc.mean", 13, 1.0 }, -// { "lowlevel.pitch_salience.dmean", 1, 1.0 }, -// { "lowlevel.pitch_salience.dmean2", 1, 1.0 }, -// { "lowlevel.pitch_salience.dvar", 1, 1.0 }, -// { "lowlevel.pitch_salience.dvar2", 1, 1.0 }, -// { "lowlevel.pitch_salience.max", 1, 1.0 }, -// { "lowlevel.pitch_salience.mean", 1, 1.0 }, -// { "lowlevel.pitch_salience.median", 1, 1.0 }, -// { "lowlevel.pitch_salience.min", 1, 1.0 }, -// { "lowlevel.pitch_salience.var", 1, 1.0 }, -// { "lowlevel.silence_rate_20dB.dmean", 1, 1.0 }, -// { "lowlevel.silence_rate_20dB.dmean2", 1, 1.0 }, -// { "lowlevel.silence_rate_20dB.dvar", 1, 1.0 }, -// { "lowlevel.silence_rate_20dB.dvar2", 1, 1.0 }, -// { "lowlevel.silence_rate_20dB.max", 1, 1.0 }, -// { "lowlevel.silence_rate_20dB.mean", 1, 1.0 }, -// { "lowlevel.silence_rate_20dB.median", 1, 1.0 }, -// { "lowlevel.silence_rate_20dB.min", 1, 1.0 }, -// { "lowlevel.silence_rate_20dB.var", 1, 1.0 }, -// { "lowlevel.silence_rate_30dB.dmean", 1, 1.0 }, -// { "lowlevel.silence_rate_30dB.dmean2", 1, 1.0 }, -// { "lowlevel.silence_rate_30dB.dvar", 1, 1.0 }, -// { "lowlevel.silence_rate_30dB.dvar2", 1, 1.0 }, -// { "lowlevel.silence_rate_30dB.max", 1, 1.0 }, -// { "lowlevel.silence_rate_30dB.mean", 1, 1.0 }, -// { "lowlevel.silence_rate_30dB.median", 1, 1.0 }, -// { "lowlevel.silence_rate_30dB.min", 1, 1.0 }, -// { "lowlevel.silence_rate_30dB.var", 1, 1.0 }, -// { "lowlevel.silence_rate_60dB.dmean", 1, 1.0 }, -// { "lowlevel.silence_rate_60dB.dmean2", 1, 1.0 }, -// { "lowlevel.silence_rate_60dB.dvar", 1, 1.0 }, -// { "lowlevel.silence_rate_60dB.dvar2", 1, 1.0 }, -// { "lowlevel.silence_rate_60dB.max", 1, 1.0 }, -// { "lowlevel.silence_rate_60dB.mean", 1, 1.0 }, -// { "lowlevel.silence_rate_60dB.median", 1, 1.0 }, -// { "lowlevel.silence_rate_60dB.min", 1, 1.0 }, -// { "lowlevel.silence_rate_60dB.var", 1, 1.0 }, -// { "lowlevel.spectral_centroid.dmean", 1, 1.0 }, -// { "lowlevel.spectral_centroid.dmean2", 1, 1.0 }, -// { "lowlevel.spectral_centroid.dvar", 1, 1.0 }, -// { "lowlevel.spectral_centroid.dvar2", 1, 1.0 }, -// { "lowlevel.spectral_centroid.max", 1, 1.0 }, - { "lowlevel.spectral_centroid.mean", 1, 1.0 }, -// { "lowlevel.spectral_centroid.median", 1, 1.0 }, -// { "lowlevel.spectral_centroid.min", 1, 1.0 }, - { "lowlevel.spectral_centroid.var", 1, 1.0 }, -// { "lowlevel.spectral_complexity.dmean", 1, 1.0 }, -// { "lowlevel.spectral_complexity.dmean2", 1, 1.0 }, -// { "lowlevel.spectral_complexity.dvar", 1, 1.0 }, -// { "lowlevel.spectral_complexity.dvar2", 1, 1.0 }, -// { "lowlevel.spectral_complexity.max", 1, 1.0 }, - { "lowlevel.spectral_complexity.mean", 1, 1.0 }, -// { "lowlevel.spectral_complexity.median", 1, 1.0 }, -// { "lowlevel.spectral_complexity.min", 1, 1.0 }, - { "lowlevel.spectral_complexity.var", 1, 1.0 }, -// { "lowlevel.spectral_decrease.dmean", 1, 1.0 }, -// { "lowlevel.spectral_decrease.dmean2", 1, 1.0 }, -// { "lowlevel.spectral_decrease.dvar", 1, 1.0 }, -// { "lowlevel.spectral_decrease.dvar2", 1, 1.0 }, -// { "lowlevel.spectral_decrease.max", 1, 1.0 }, - { "lowlevel.spectral_decrease.mean", 1, 1.0 }, -// { "lowlevel.spectral_decrease.median", 1, 1.0 }, -// { "lowlevel.spectral_decrease.min", 1, 1.0 }, - { "lowlevel.spectral_decrease.var", 1, 1.0 }, -// { "lowlevel.spectral_energy.dmean", 1, 1.0 }, -// { "lowlevel.spectral_energy.dmean2", 1, 1.0 }, -// { "lowlevel.spectral_energy.dvar", 1, 1.0 }, -// { "lowlevel.spectral_energy.dvar2", 1, 1.0 }, -// { "lowlevel.spectral_energy.max", 1, 1.0 }, - { "lowlevel.spectral_energy.mean", 1, 1.0 }, -// { "lowlevel.spectral_energy.median", 1, 1.0 }, -// { "lowlevel.spectral_energy.min", 1, 1.0 }, - { "lowlevel.spectral_energy.var", 1, 1.0 }, -// { "lowlevel.spectral_energyband_low.dmean", 1, 1.0 }, -// { "lowlevel.spectral_energyband_low.dmean2", 1, 1.0 }, -// { "lowlevel.spectral_energyband_low.dvar", 1, 1.0 }, -// { "lowlevel.spectral_energyband_low.dvar2", 1, 1.0 }, -// { "lowlevel.spectral_energyband_low.max", 1, 1.0 }, - { "lowlevel.spectral_energyband_low.mean", 1, 1.0 }, -// { "lowlevel.spectral_energyband_low.median", 1, 1.0 }, -// { "lowlevel.spectral_energyband_low.min", 1, 1.0 }, - { "lowlevel.spectral_energyband_low.var", 1, 1.0 }, -// { "lowlevel.spectral_energyband_middle_high.dmean", 1, 1.0 }, -// { "lowlevel.spectral_energyband_middle_high.dmean2", 1, 1.0 }, -// { "lowlevel.spectral_energyband_middle_high.dvar", 1, 1.0 }, -// { "lowlevel.spectral_energyband_middle_high.dvar2", 1, 1.0 }, -// { "lowlevel.spectral_energyband_middle_high.max", 1, 1.0 }, - { "lowlevel.spectral_energyband_middle_high.mean", 1, 1.0 }, -// { "lowlevel.spectral_energyband_middle_high.median", 1, 1.0 }, -// { "lowlevel.spectral_energyband_middle_high.min", 1, 1.0 }, - { "lowlevel.spectral_energyband_middle_high.var", 1, 1.0 }, -// { "lowlevel.spectral_energyband_middle_low.dmean", 1, 1.0 }, -// { "lowlevel.spectral_energyband_middle_low.dmean2", 1, 1.0 }, -// { "lowlevel.spectral_energyband_middle_low.dvar", 1, 1.0 }, -// { "lowlevel.spectral_energyband_middle_low.dvar2", 1, 1.0 }, -// { "lowlevel.spectral_energyband_middle_low.max", 1, 1.0 }, - { "lowlevel.spectral_energyband_middle_low.mean", 1, 1.0 }, -// { "lowlevel.spectral_energyband_middle_low.median", 1, 1.0 }, -// { "lowlevel.spectral_energyband_middle_low.min", 1, 1.0 }, - { "lowlevel.spectral_energyband_middle_low.var", 1, 1.0 }, -// { "lowlevel.spectral_entropy.dmean", 1, 1.0 }, -// { "lowlevel.spectral_entropy.dmean2", 1, 1.0 }, -// { "lowlevel.spectral_entropy.dvar", 1, 1.0 }, -// { "lowlevel.spectral_entropy.dvar2", 1, 1.0 }, -// { "lowlevel.spectral_entropy.max", 1, 1.0 }, - { "lowlevel.spectral_entropy.mean", 1, 1.0 }, -// { "lowlevel.spectral_entropy.median", 1, 1.0 }, -// { "lowlevel.spectral_entropy.min", 1, 1.0 }, - { "lowlevel.spectral_entropy.var", 1, 1.0 }, -// { "lowlevel.spectral_flux.dmean", 1, 1.0 }, -// { "lowlevel.spectral_flux.dmean2", 1, 1.0 }, -// { "lowlevel.spectral_flux.dvar", 1, 1.0 }, -// { "lowlevel.spectral_flux.dvar2", 1, 1.0 }, -// { "lowlevel.spectral_flux.max", 1, 1.0 }, -// { "lowlevel.spectral_flux.mean", 1, 1.0 }, -// { "lowlevel.spectral_flux.median", 1, 1.0 }, -// { "lowlevel.spectral_flux.min", 1, 1.0 }, -// { "lowlevel.spectral_flux.var", 1, 1.0 }, -// { "lowlevel.spectral_kurtosis.dmean", 1, 1.0 }, -// { "lowlevel.spectral_kurtosis.dmean2", 1, 1.0 }, -// { "lowlevel.spectral_kurtosis.dvar", 1, 1.0 }, -// { "lowlevel.spectral_kurtosis.dvar2", 1, 1.0 }, -// { "lowlevel.spectral_kurtosis.max", 1, 1.0 }, -// { "lowlevel.spectral_kurtosis.mean", 1, 1.0 }, -// { "lowlevel.spectral_kurtosis.median", 1, 1.0 }, -// { "lowlevel.spectral_kurtosis.min", 1, 1.0 }, -// { "lowlevel.spectral_kurtosis.var", 1, 1.0 }, -// { "lowlevel.spectral_rms.dmean", 1, 1.0 }, -// { "lowlevel.spectral_rms.dmean2", 1, 1.0 }, -// { "lowlevel.spectral_rms.dvar", 1, 1.0 }, -// { "lowlevel.spectral_rms.dvar2", 1, 1.0 }, -// { "lowlevel.spectral_rms.max", 1, 1.0 }, - { "lowlevel.spectral_rms.mean", 1, 1.0 }, -// { "lowlevel.spectral_rms.median", 1, 1.0 }, -// { "lowlevel.spectral_rms.min", 1, 1.0 }, - { "lowlevel.spectral_rms.var", 1, 1.0 }, -// { "lowlevel.spectral_rolloff.dmean", 1, 1.0 }, -// { "lowlevel.spectral_rolloff.dmean2", 1, 1.0 }, -// { "lowlevel.spectral_rolloff.dvar", 1, 1.0 }, -// { "lowlevel.spectral_rolloff.dvar2", 1, 1.0 }, -// { "lowlevel.spectral_rolloff.max", 1, 1.0 }, -// { "lowlevel.spectral_rolloff.mean", 1, 1.0 }, -// { "lowlevel.spectral_rolloff.median", 1, 1.0 }, -// { "lowlevel.spectral_rolloff.min", 1, 1.0 }, -// { "lowlevel.spectral_rolloff.var", 1, 1.0 }, -// { "lowlevel.spectral_skewness.dmean", 1, 1.0 }, -// { "lowlevel.spectral_skewness.dmean2", 1, 1.0 }, -// { "lowlevel.spectral_skewness.dvar", 1, 1.0 }, -// { "lowlevel.spectral_skewness.dvar2", 1, 1.0 }, -// { "lowlevel.spectral_skewness.max", 1, 1.0 }, -// { "lowlevel.spectral_skewness.mean", 1, 1.0 }, -// { "lowlevel.spectral_skewness.median", 1, 1.0 }, -// { "lowlevel.spectral_skewness.min", 1, 1.0 }, -// { "lowlevel.spectral_skewness.var", 1, 1.0 }, -// { "lowlevel.spectral_spread.dmean", 1, 1.0 }, -// { "lowlevel.spectral_spread.dmean2", 1, 1.0 }, -// { "lowlevel.spectral_spread.dvar", 1, 1.0 }, -// { "lowlevel.spectral_spread.dvar2", 1, 1.0 }, -// { "lowlevel.spectral_spread.max", 1, 1.0 }, - { "lowlevel.spectral_spread.mean", 1, 1.0 }, -// { "lowlevel.spectral_spread.median", 1, 1.0 }, -// { "lowlevel.spectral_spread.min", 1, 1.0 }, - { "lowlevel.spectral_spread.var", 1, 1.0 }, -// { "lowlevel.spectral_strongpeak.dmean", 1, 1.0 }, -// { "lowlevel.spectral_strongpeak.dmean2", 1, 1.0 }, -// { "lowlevel.spectral_strongpeak.dvar", 1, 1.0 }, -// { "lowlevel.spectral_strongpeak.dvar2", 1, 1.0 }, -// { "lowlevel.spectral_strongpeak.max", 1, 1.0 }, - { "lowlevel.spectral_strongpeak.mean", 1, 1.0 }, -// { "lowlevel.spectral_strongpeak.median", 1, 1.0 }, -// { "lowlevel.spectral_strongpeak.min", 1, 1.0 }, - { "lowlevel.spectral_strongpeak.var", 1, 1.0 }, -// { "lowlevel.zerocrossingrate.dmean", 1, 1.0 }, -// { "lowlevel.zerocrossingrate.dmean2", 1, 1.0 }, -// { "lowlevel.zerocrossingrate.dvar", 1, 1.0 }, -// { "lowlevel.zerocrossingrate.dvar2", 1, 1.0 }, -// { "lowlevel.zerocrossingrate.max", 1, 1.0 }, - { "lowlevel.zerocrossingrate.mean", 1, 1.0 }, -// { "lowlevel.zerocrossingrate.median", 1, 1.0 }, -// { "lowlevel.zerocrossingrate.min", 1, 1.0 }, - { "lowlevel.zerocrossingrate.var", 1, 1.0 }, -// { "rhythm.beats_count", 1, 1.0 }, -// { "rhythm.beats_loudness.dmean", 1, 1.0 }, -// { "rhythm.beats_loudness.dmean2", 1, 1.0 }, -// { "rhythm.beats_loudness.dvar", 1, 1.0 }, -// { "rhythm.beats_loudness.dvar2", 1, 1.0 }, -// { "rhythm.beats_loudness.max", 1, 1.0 }, - { "rhythm.beats_loudness.mean", 1, 1.0 }, -// { "rhythm.beats_loudness.median", 1, 1.0 }, -// { "rhythm.beats_loudness.min", 1, 1.0 }, - { "rhythm.beats_loudness.var", 1, 1.0 }, -// { "rhythm.bpm", 1, 1.0 }, -// { "rhythm.bpm_histogram_first_peak_bpm.dmean", 1, 1.0 }, -// { "rhythm.bpm_histogram_first_peak_bpm.dmean2", 1, 1.0 }, -// { "rhythm.bpm_histogram_first_peak_bpm.dvar", 1, 1.0 }, -// { "rhythm.bpm_histogram_first_peak_bpm.dvar2", 1, 1.0 }, -// { "rhythm.bpm_histogram_first_peak_bpm.max", 1, 1.0 }, -// { "rhythm.bpm_histogram_first_peak_bpm.mean", 1, 1.0 }, -// { "rhythm.bpm_histogram_first_peak_bpm.median", 1, 1.0 }, -// { "rhythm.bpm_histogram_first_peak_bpm.min", 1, 1.0 }, -// { "rhythm.bpm_histogram_first_peak_bpm.var", 1, 1.0 }, -// { "rhythm.bpm_histogram_first_peak_spread.dmean", 1, 1.0 }, -// { "rhythm.bpm_histogram_first_peak_spread.dmean2", 1, 1.0 }, -// { "rhythm.bpm_histogram_first_peak_spread.dvar", 1, 1.0 }, -// { "rhythm.bpm_histogram_first_peak_spread.dvar2", 1, 1.0 }, -// { "rhythm.bpm_histogram_first_peak_spread.max", 1, 1.0 }, -// { "rhythm.bpm_histogram_first_peak_spread.mean", 1, 1.0 }, -// { "rhythm.bpm_histogram_first_peak_spread.median", 1, 1.0 }, -// { "rhythm.bpm_histogram_first_peak_spread.min", 1, 1.0 }, -// { "rhythm.bpm_histogram_first_peak_spread.var", 1, 1.0 }, -// { "rhythm.bpm_histogram_first_peak_weight.dmean", 1, 1.0 }, -// { "rhythm.bpm_histogram_first_peak_weight.dmean2", 1, 1.0 }, -// { "rhythm.bpm_histogram_first_peak_weight.dvar", 1, 1.0 }, -// { "rhythm.bpm_histogram_first_peak_weight.dvar2", 1, 1.0 }, -// { "rhythm.bpm_histogram_first_peak_weight.max", 1, 1.0 }, -// { "rhythm.bpm_histogram_first_peak_weight.mean", 1, 1.0 }, -// { "rhythm.bpm_histogram_first_peak_weight.median", 1, 1.0 }, -// { "rhythm.bpm_histogram_first_peak_weight.min", 1, 1.0 }, -// { "rhythm.bpm_histogram_first_peak_weight.var", 1, 1.0 }, -// { "rhythm.bpm_histogram_second_peak_bpm.dmean", 1, 1.0 }, -// { "rhythm.bpm_histogram_second_peak_bpm.dmean2", 1, 1.0 }, -// { "rhythm.bpm_histogram_second_peak_bpm.dvar", 1, 1.0 }, -// { "rhythm.bpm_histogram_second_peak_bpm.dvar2", 1, 1.0 }, -// { "rhythm.bpm_histogram_second_peak_bpm.max", 1, 1.0 }, -// { "rhythm.bpm_histogram_second_peak_bpm.mean", 1, 1.0 }, -// { "rhythm.bpm_histogram_second_peak_bpm.median", 1, 1.0 }, -// { "rhythm.bpm_histogram_second_peak_bpm.min", 1, 1.0 }, -// { "rhythm.bpm_histogram_second_peak_bpm.var", 1, 1.0 }, -// { "rhythm.bpm_histogram_second_peak_spread.dmean", 1, 1.0 }, -// { "rhythm.bpm_histogram_second_peak_spread.dmean2", 1, 1.0 }, -// { "rhythm.bpm_histogram_second_peak_spread.dvar", 1, 1.0 }, -// { "rhythm.bpm_histogram_second_peak_spread.dvar2", 1, 1.0 }, -// { "rhythm.bpm_histogram_second_peak_spread.max", 1, 1.0 }, -// { "rhythm.bpm_histogram_second_peak_spread.mean", 1, 1.0 }, -// { "rhythm.bpm_histogram_second_peak_spread.median", 1, 1.0 }, -// { "rhythm.bpm_histogram_second_peak_spread.min", 1, 1.0 }, -// { "rhythm.bpm_histogram_second_peak_spread.var", 1, 1.0 }, -// { "rhythm.bpm_histogram_second_peak_weight.dmean", 1, 1.0 }, -// { "rhythm.bpm_histogram_second_peak_weight.dmean2", 1, 1.0 }, -// { "rhythm.bpm_histogram_second_peak_weight.dvar", 1, 1.0 }, -// { "rhythm.bpm_histogram_second_peak_weight.dvar2", 1, 1.0 }, -// { "rhythm.bpm_histogram_second_peak_weight.max", 1, 1.0 }, -// { "rhythm.bpm_histogram_second_peak_weight.mean", 1, 1.0 }, -// { "rhythm.bpm_histogram_second_peak_weight.median", 1, 1.0 }, -// { "rhythm.bpm_histogram_second_peak_weight.min", 1, 1.0 }, -// { "rhythm.bpm_histogram_second_peak_weight.var", 1, 1.0 }, -// { "rhythm.danceability", 1, 1.0}, -// { "rhythm.onset_rate", 1, 1.0}, -// -// { "tonal.chords_changes_rate", 1, 1.0 }, -// { "tonal.chords_histogram", 24, 1.0 }, -// { "tonal.chords_number_rate", 1, 1.0 }, -// { "tonal.hpcp_entropy.dmean", 1, 1.0 }, -// { "tonal.hpcp_entropy.dmean2", 1, 1.0 }, -// { "tonal.hpcp_entropy.dvar", 1, 1.0 }, -// { "tonal.hpcp_entropy.dvar2", 1, 1.0 }, -// { "tonal.hpcp_entropy.max", 1, 1.0 }, -// { "tonal.hpcp_entropy.mean", 1, 1.0 }, -// { "tonal.hpcp_entropy.median", 1, 1.0 }, -// { "tonal.hpcp_entropy.min", 1, 1.0 }, -// { "tonal.hpcp_entropy.var", 1, 1.0 }, -// { "tonal.tuning_frequency", 1, 1.0 }, -}; - -static bool entryConstructFromJSON(entry_t& entry, const std::string& jsonData) -{ - std::istringstream iss(jsonData); - boost::property_tree::ptree pt; - boost::property_tree::json_parser::read_json(iss, pt); - - for (auto& elem : features) - { - auto child = pt.get_child_optional(elem.lowLevelName); - - if (!child) - { - LMS_LOG(DBUPDATER, DEBUG) << "Cannot get '" << elem.lowLevelName << "'"; - return false; - } - - if (!entryAddData(entry, *child, elem.nbDimensions)) - return false; - } - - return true; -} - -static entry_t computeWeightCoeffs(const Entries& entries) -{ - std::vector expandedFeatures; - std::vector userCoeffs; - - for (auto& feature : features) - { - for (std::size_t i = 0; i < feature.nbDimensions; ++i) - { - expandedFeatures.push_back( { feature.lowLevelName + std::to_string(i), 1, feature.coeff / (double)feature.nbDimensions} ); - } - } - - entry_t coeffs; - - ::neural_net::Ranges ranges (*entries.begin()); - ranges( entries ); - - entry_t::const_iterator pos_max = ranges.get_max().begin(); - entry_t::const_iterator pos_min = ranges.get_min().begin(); - for (std::size_t i = 0; pos_max != ranges.get_max().end(); ++pos_max, ++pos_min, ++i) - { - coeffs.push_back( expandedFeatures.at(i).coeff * - ::operators::inverse ( - ( *pos_max - *pos_min ) * ( *pos_max - *pos_min ) - ) ); // weight for i-th axis - std::cout << "Feature = " << expandedFeatures.at(i).lowLevelName << std::endl; - std::cout << "min = " << *pos_min << ", max = " << *pos_max << std::endl; - std::cout << "Coeff = " << coeffs.back() << std::endl; - } - return coeffs; -} - template - using Matrix = std::array, ROW>; - -void -Classifier::processDatabaseUpdate(Updater::Stats stats) -{ - LMS_LOG(DBUPDATER, DEBUG) << "Database complete Called!"; - -// typedef ::neural_net::Cauchy_function < entry_t::value_type, entry_t::value_type, ::boost::int32_t> CauchyFunction; - - typedef ::neural_net::Gauss_function < entry_t::value_type, entry_t::value_type, ::boost::int32_t > GaussFunction; - -// typedef ::distance::Euclidean_distance_function < entry_t > EuclideanDistFunction; - typedef ::distance::Weighted_euclidean_distance_function < entry_t, entry_t > WeightedEuclideanDistFunction; - - typedef ::neural_net::Basic_neuron < GaussFunction, WeightedEuclideanDistFunction > KohonenNeuron; - typedef ::neural_net::Rectangular_container < KohonenNeuron > KohonenNetwork; - -// typedef ::neural_net::Hexagonal_topology < ::boost::int32_t > HexagonalTopology; - typedef ::neural_net::Max_topology < ::boost::int32_t > MaxTopology; - - typedef GaussFunction GaussFunctionSpace; - typedef ::neural_net::Gauss_function < ::boost::int32_t, entry_t::value_type, ::boost::int32_t > GaussFunctionNet; -// typedef ::neural_net::Constant_function < entry_t::value_type, entry_t::value_type > ConstFuncSpace; - - typedef ::neural_net::Classic_training_weight - < - entry_t, - ::boost::int32_t, - GaussFunctionNet, - GaussFunctionSpace, - MaxTopology, - WeightedEuclideanDistFunction, - ::boost::int32_t - > ClassicWeight; - - typedef ::neural_net::Wtm_classical_training_functional - < - entry_t, - double, - ::boost::int32_t, - ::boost::int32_t, - ClassicWeight - > WtmTrainingFunc; - - typedef ::neural_net::Wtm_training_algorithm - < - KohonenNetwork, - entry_t, - Entries::iterator, - WtmTrainingFunc, - ::boost::int32_t - > WtmTrainingAlg; - - Entries entries; - - LMS_LOG(DBUPDATER, DEBUG) << "Getting track ids"; - - std::vector trackIdsAll = Database::Track::getAllIds(_db.getSession()); - - ::std::random_shuffle ( trackIdsAll.begin(), trackIdsAll.end() ); - - LMS_LOG(DBUPDATER, DEBUG) << "Getting JSON data"; - std::vector trackIds; - for (Track::id_type trackId : trackIdsAll) - { - std::string jsonData; - - { - Wt::Dbo::Transaction transaction(_db.getSession()); - - std::vector features = Feature::getByTrack(_db.getSession(), trackId, "low_level"); - - if (features.empty()) - { - LMS_LOG(DBUPDATER, DEBUG) << "No JSON data for track " << trackId;; - continue; - } - - jsonData = features.front()->getValue(); - } - - entry_t entry; - if (entryConstructFromJSON(entry, jsonData)) - { - trackIds.push_back(trackId); - entries.push_back(entry); - } - else - { - LMS_LOG(DBUPDATER, ERROR) << "Skipping track " << trackId; - continue; - } - -// if (entries.size() > 100) -// break; - } - std::cout << "Feature vector dimension = " << entries.front().size() << std::endl; - - if (entries.empty()) - return; - - // TODO compute rows / columns from the DBB - const std::size_t nbRows = 32; - const std::size_t nbColumns = 32; - - // CauchyFunction cauchyFunc(2.0, 1); - GaussFunction gaussFunc(2.0, 1 ); - - LMS_LOG(DBUPDATER, DEBUG) << "Computing coeffs"; - entry_t coeffs = computeWeightCoeffs(entries); - LMS_LOG(DBUPDATER, DEBUG) << "Initializing distfunc"; - WeightedEuclideanDistFunction weightedEuclideanDistFunc (&coeffs); - - // prepare randomization policy - ::neural_net::Internal_randomize internalRandomize; - - KohonenNetwork network; - - // generate networks initialized by data - LMS_LOG(DBUPDATER, DEBUG) << "Generating network..."; - ::neural_net::generate_kohonen_network(nbRows, nbColumns, gaussFunc, weightedEuclideanDistFunc, entries, network, internalRandomize); - - ::std::cout << "Network weights:" << ::std::endl; - ::neural_net::print_network_weights ( ::std::cout, network ); - ::std::cout << ::std::endl; - - GaussFunctionNet gaussFuncNetwork(10, 1); - GaussFunctionSpace gaussFuncSpace(10, 1); - MaxTopology maxTopology; -// EuclideanDistFunction euclideanDistFunc; - - ClassicWeight classicWeight(gaussFuncNetwork, gaussFuncSpace, maxTopology, weightedEuclideanDistFunc); - WtmTrainingFunc trainingFunc(classicWeight, 0.3); - - WtmTrainingAlg wtmTrainAlg( trainingFunc ); - - LMS_LOG(DBUPDATER, DEBUG) << "Training..."; - // tricky training - std::size_t nbPass = 20; - for (std::size_t i = 0; i < nbPass; ++i ) - { - LMS_LOG(DBUPDATER, DEBUG) << "Training pass " << i << " / " << nbPass; - // train network using data - auto entries_copy = entries; - ::std::random_shuffle ( entries_copy.begin(), entries_copy.end() ); - - wtmTrainAlg(entries_copy.begin(), entries_copy.end(), &network); - - // decrease sigma parameter in network will make training proces more sharpen with each epoch, - // but it have to be done slowly :-) - wtmTrainAlg.training_functional.generalized_training_weight.network_function.sigma *= 2.0/3.0; -// wtmTrainAlg.training_functional.generalized_training_weight.network_function.sigma *= 9.0/10.0; - - // shuffle data -// ::std::random_shuffle ( entries.begin(), entries.end() ); - } - LMS_LOG(DBUPDATER, DEBUG) << "Training DONE..."; - - ::std::cout << "Network weights:" << ::std::endl; - ::neural_net::print_network_weights ( ::std::cout, network ); - ::std::cout << ::std::endl; - - struct ClusterEntry - { - Track::id_type trackId; - entry_t entry; - double distance; - }; - - Matrix, nbRows, nbColumns> trackClusters; - - for (std::size_t id = 0; id < entries.size(); ++id) - { - std::cout << "Entry " << id << " : " << std::endl; - // Display input vector - for (auto& value : entries[id]) - { - std::cout << value << " "; - } - std::cout << std::endl; - - auto entry = entries[id]; - std::pair coordinates = {0,0}; - double maxValue = 0.0; - - for (std::size_t i = 0; i < network.objects.size(); ++i) - { - for (std::size_t j = 0; j < network.objects[0].size(); ++j) - { - double value = network.objects[i][j]( entry); - if (value > maxValue) - { - coordinates = {i, j}; - maxValue = value; - } - } - } - -/* ::std::cout << "Network for entry " << id << std::endl; - ::neural_net::print_network ( ::std::cout, network, entry ); - ::std::cout << ::std::endl; - - std::cout << "Max is in {" << coordinates.first << ", " << coordinates.second << "}" << std::endl; - */ - trackClusters[coordinates.first][coordinates.second].push_back({trackIds[id], entry, maxValue}); - } - - // Create clusters - LMS_LOG(DBUPDATER, DEBUG) << "Erasing old clusters"; - { - Wt::Dbo::Transaction transaction(_db.getSession()); - Cluster::removeByType(_db.getSession(), "similarity"); - } - - LMS_LOG(DBUPDATER, DEBUG) << "Creating new cluster..."; - for (std::size_t i = 0; i < nbRows; i++) - { - for (std::size_t j = 0; j < nbColumns; j++) - { - LMS_LOG(DBUPDATER, DEBUG) << "Creating cluster " << i << " " << j; - - Wt::Dbo::Transaction transaction(_db.getSession()); - - Cluster::pointer cluster = Cluster::create(_db.getSession(), "similarity", "cluster_" + std::to_string(i) + "_" + std::to_string(j)); - - for (auto track : trackClusters[i][j]) - { - Track::pointer t = Database::Track::getById(_db.getSession(), track.trackId); - cluster.modify()->addTrack(t); - } - } - } - - - for (std::size_t i = 0; i < nbRows; i++) - { - for (std::size_t j = 0; j < nbColumns; j++) - { - - - std::cout << "Cluster [" << i << "," << j << "] - "; - - // Display the neuron for this cluster - for (auto& value : network.objects[i][j].weights) - std::cout << value << " "; - std::cout << std::endl; - - for (auto track : trackClusters[i][j]) - { - Wt::Dbo::Transaction transaction(_db.getSession()); - - std::cout << "- " << track.distance; - - Track::pointer t = Database::Track::getById(_db.getSession(), track.trackId); - std::cout << " - " << track.trackId << " - " << t->getArtist()->getName() << " - " << t->getName() << " - (" ; - - // Display input vector - for (auto& value : track.entry) - std::cout << value << " "; - std::cout << ")" << std::endl; - } - } - } - - -} - -} // namespace Database - diff --git a/src/database/DatabaseClassifier.hpp b/src/database/DatabaseClassifier.hpp deleted file mode 100644 index c8e382b8..00000000 --- a/src/database/DatabaseClassifier.hpp +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2016 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 . - */ - -#pragma once - -#include - -#include "DatabaseHandler.hpp" -#include "DatabaseUpdater.hpp" - -namespace Database { - -class ClusterClassifier -{ - public: - ClusterClassifier(); - - - private: - -}; - - -class Classifier -{ - public: - Classifier(Wt::Dbo::SqlConnectionPool& connectionPool); - - void processTrackUpdate(bool added, Track::id_type trackId, std::string mbid, boost::filesystem::path p); - void processDatabaseUpdate(Updater::Stats stats); - - private: - - Database::Handler _db; -}; - - -} // namespace Database - diff --git a/src/database/cluster/DatabaseSimilarityCluster.hpp b/src/database/cluster/DatabaseSimilarityCluster.hpp deleted file mode 100644 index e69de29b..00000000 diff --git a/src/database/tagger/GenreTagger.cpp b/src/database/tagger/GenreTagger.cpp deleted file mode 100644 index ec40da6d..00000000 --- a/src/database/tagger/GenreTagger.cpp +++ /dev/null @@ -1,40 +0,0 @@ - -/* - * Copyright (C) 2016 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 . - */ - -#include "GenreTagger.hpp" - -namespace Database { - -void -GenreTagger::processTrackUpdate(bool added, Track::id_type trackId, std::string mbid, boost::filesystem::path p) -{ - - -} - -void -GenreTagger::processDatabaseUpdate(Updater::Stats stats) -{ - - // TODO: if disabled, delete all the genre tags - // TODO: if enabled, reconstruct all the genre tags from tracks -} - -} // namespace Database diff --git a/src/database/tagger/GenreTagger.hpp b/src/database/tagger/GenreTagger.hpp deleted file mode 100644 index 16118c48..00000000 --- a/src/database/tagger/GenreTagger.hpp +++ /dev/null @@ -1,37 +0,0 @@ - -/* - * Copyright (C) 2016 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 . - */ - -#pragma once - -#include "database/DatabaseUpdater.hpp" - -namespace Database { - -class GenreTagger -{ - public: - void processTrackUpdate(bool added, Track::id_type trackId, std::string mbid, boost::filesystem::path p); - void processDatabaseUpdate(Updater::Stats stats); - - private: - -}; - -} // namespace Database diff --git a/src/database/DatabaseFeatureExtractor.cpp b/src/database/updater/DatabaseFeatureExtractor.cpp similarity index 96% rename from src/database/DatabaseFeatureExtractor.cpp rename to src/database/updater/DatabaseFeatureExtractor.cpp index c4239b39..8b44ecce 100644 --- a/src/database/DatabaseFeatureExtractor.cpp +++ b/src/database/updater/DatabaseFeatureExtractor.cpp @@ -21,8 +21,8 @@ #include "feature/FeatureExtractor.hpp" #include "feature/FeatureStore.hpp" +#include "database/Setting.hpp" -#include "Setting.hpp" #include "DatabaseFeatureExtractor.hpp" namespace Database { @@ -35,7 +35,7 @@ static std::string getMBID(Track::id_type trackId) } void -FeatureExtractor::processDatabaseUpdate(Updater::Stats stats) +FeatureExtractor::handleFilesUpdated(void) { bool fetchHighLevel = Setting::getBool(UpdaterDboSession(), "tags_highlevel_acousticbrainz"); bool fetchLowLevel = Setting::getBool(UpdaterDboSession(), "tags_similarity_acousticbrainz"); diff --git a/src/database/DatabaseFeatureExtractor.hpp b/src/database/updater/DatabaseFeatureExtractor.hpp similarity index 86% rename from src/database/DatabaseFeatureExtractor.hpp rename to src/database/updater/DatabaseFeatureExtractor.hpp index 2a7c268c..c0ff092a 100644 --- a/src/database/DatabaseFeatureExtractor.hpp +++ b/src/database/updater/DatabaseFeatureExtractor.hpp @@ -19,14 +19,14 @@ #pragma once -#include "database/DatabaseUpdater.hpp" +#include "DatabaseUpdater.hpp" namespace Database { -class FeatureExtractor +class FeatureExtractor : public UpdaterEventHandler { public: - void processDatabaseUpdate(Updater::Stats stats); + void handleFilesUpdated(void); }; diff --git a/src/database/cluster/DatabaseHighLevelCluster.cpp b/src/database/updater/DatabaseHighLevelCluster.cpp similarity index 98% rename from src/database/cluster/DatabaseHighLevelCluster.cpp rename to src/database/updater/DatabaseHighLevelCluster.cpp index 84af0d28..412e354f 100644 --- a/src/database/cluster/DatabaseHighLevelCluster.cpp +++ b/src/database/updater/DatabaseHighLevelCluster.cpp @@ -165,7 +165,7 @@ static std::list getClustersFromFeature(Feature::Type& feature, dou } void -HighLevelCluster::processDatabaseUpdate(Updater::Stats stats) +HighLevelCluster::handleFilesUpdated(void) { bool createTags = Setting::getBool(UpdaterDboSession(), "tags_highlevel_acousticbrainz", false); double minProb = Setting::getInt(UpdaterDboSession(), "tags_highlevel_acousticbrainz_min_probability", false) / 100.; diff --git a/src/database/cluster/DatabaseHighLevelCluster.hpp b/src/database/updater/DatabaseHighLevelCluster.hpp similarity index 86% rename from src/database/cluster/DatabaseHighLevelCluster.hpp rename to src/database/updater/DatabaseHighLevelCluster.hpp index 25f91d86..20adb3a8 100644 --- a/src/database/cluster/DatabaseHighLevelCluster.hpp +++ b/src/database/updater/DatabaseHighLevelCluster.hpp @@ -19,14 +19,14 @@ #pragma once -#include "database/DatabaseUpdater.hpp" +#include "DatabaseUpdater.hpp" namespace Database { -class HighLevelCluster +class HighLevelCluster : public UpdaterEventHandler { public: - void processDatabaseUpdate(Updater::Stats stats); + void handleFilesUpdated(void); }; diff --git a/src/database/DatabaseUpdater.cpp b/src/database/updater/DatabaseUpdater.cpp similarity index 99% rename from src/database/DatabaseUpdater.cpp rename to src/database/updater/DatabaseUpdater.cpp index 0ba96f85..7e5afb44 100644 --- a/src/database/DatabaseUpdater.cpp +++ b/src/database/updater/DatabaseUpdater.cpp @@ -28,8 +28,9 @@ #include "utils/Utils.hpp" #include "utils/Path.hpp" -#include "Setting.hpp" -#include "Types.hpp" +#include "database/Setting.hpp" +#include "database/Types.hpp" + #include "DatabaseUpdater.hpp" namespace { diff --git a/src/database/DatabaseUpdater.hpp b/src/database/updater/DatabaseUpdater.hpp similarity index 90% rename from src/database/DatabaseUpdater.hpp rename to src/database/updater/DatabaseUpdater.hpp index 3d032c4e..5683b50c 100644 --- a/src/database/DatabaseUpdater.hpp +++ b/src/database/updater/DatabaseUpdater.hpp @@ -20,6 +20,7 @@ #pragma once #include +#include #include @@ -32,6 +33,9 @@ namespace Database { + +class UpdaterEventHandler; + class Updater { public: @@ -60,7 +64,7 @@ class Updater std::size_t nbChanges() const { return nbAdded + nbRemoved + nbModified;} }; - // Emitted when the whole database has been scanned + // Emitted when the whole database has been scanned (and all the event handlers have been called) Wt::Signal& scanComplete() { return _sigScanComplete; } // Emitted when a track changed @@ -76,6 +80,9 @@ class Updater Database::Handler& getDb(void) { return *_db; } bool quitRequested(void) const { return !_running;} + + void registerEventHandler(std::shared_ptr handler) { _eventHandlers.push_back(handler); } + private: Updater(); @@ -136,6 +143,8 @@ class Updater MetaData::TagLibParser _metadataParser; + std::list > _eventHandlers; + }; // class Updater // Helper to get the updater session data @@ -149,5 +158,17 @@ static inline bool UpdaterQuitRequested() return Updater::instance().quitRequested(); } + +class UpdaterEventHandler +{ + public: + + // called when all the files have been scanned by the updater + virtual void handleFilesUpdated(void) = 0; + + private: + +}; + } // Database diff --git a/src/main/main.cpp b/src/main/main.cpp index 1c8eecc5..5e9f4082 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -28,9 +28,9 @@ #include "image/Image.hpp" #include "feature/FeatureExtractor.hpp" -#include "database/DatabaseUpdater.hpp" -#include "database/DatabaseFeatureExtractor.hpp" -#include "database/cluster/DatabaseHighLevelCluster.hpp" +#include "database/updater/DatabaseUpdater.hpp" +#include "database/updater/DatabaseFeatureExtractor.hpp" +#include "database/updater/DatabaseHighLevelCluster.hpp" #include "ui/LmsApplication.hpp" @@ -106,14 +106,9 @@ int main(int argc, char* argv[]) Database::Updater& dbUpdater = Database::Updater::instance(); dbUpdater.setConnectionPool(*connectionPool); - Database::FeatureExtractor dbFeatureExtractor; - Database::HighLevelCluster dbHighLevelCluster; - - // Connect to the update events - dbUpdater.scanComplete().connect(std::bind(&Database::HighLevelCluster::processDatabaseUpdate, &dbHighLevelCluster, std::placeholders::_1)); - dbUpdater.scanComplete().connect(std::bind(&Database::FeatureExtractor::processDatabaseUpdate, &dbFeatureExtractor, std::placeholders::_1)); - -// dbHighLevelCluster.processDatabaseUpdate(Database::Updater::Stats()); + // Instanciate the updater's event handler. Order is important + dbUpdater.registerEventHandler(std::make_shared()); + dbUpdater.registerEventHandler(std::make_shared()); // bind entry point server.addEntryPoint(Wt::Application, boost::bind(UserInterface::LmsApplication::create, diff --git a/src/ui/settings/Settings.cpp b/src/ui/settings/Settings.cpp index 308664c8..78d3838b 100644 --- a/src/ui/settings/Settings.cpp +++ b/src/ui/settings/Settings.cpp @@ -30,8 +30,8 @@ #include "SettingsUsers.hpp" #include "logger/Logger.hpp" -#include "database/DatabaseUpdater.hpp" #include "database/Setting.hpp" +#include "database/updater/DatabaseUpdater.hpp" #include "LmsApplication.hpp" From 7c475ee2d32c3295dbbc80ec1ce393a5b82a1aee Mon Sep 17 00:00:00 2001 From: emeric Date: Sun, 5 Jun 2016 13:44:43 +0200 Subject: [PATCH 022/195] [DB] Faster scan thanks to index on file path + stats improvements + bugfix --- src/database/DatabaseHandler.cpp | 1 + .../updater/DatabaseHighLevelCluster.cpp | 9 +++------ src/database/updater/DatabaseUpdater.cpp | 16 ++++++++-------- src/database/updater/DatabaseUpdater.hpp | 14 +++++++------- 4 files changed, 19 insertions(+), 21 deletions(-) diff --git a/src/database/DatabaseHandler.cpp b/src/database/DatabaseHandler.cpp index f9c46053..c8d160ed 100644 --- a/src/database/DatabaseHandler.cpp +++ b/src/database/DatabaseHandler.cpp @@ -110,6 +110,7 @@ Handler::Handler(Wt::Dbo::SqlConnectionPool& connectionPool) // Indexes _session.execute("PRAGMA journal_mode=WAL"); + _session.execute("CREATE INDEX IF NOT EXISTS track_path_idx ON track(file_path)"); _session.execute("CREATE INDEX IF NOT EXISTS artist_name_idx ON artist(name)"); _session.execute("CREATE INDEX IF NOT EXISTS release_name_idx ON release(name)"); _session.execute("CREATE INDEX IF NOT EXISTS track_artist_idx ON track(artist_id)"); diff --git a/src/database/updater/DatabaseHighLevelCluster.cpp b/src/database/updater/DatabaseHighLevelCluster.cpp index 412e354f..608963b1 100644 --- a/src/database/updater/DatabaseHighLevelCluster.cpp +++ b/src/database/updater/DatabaseHighLevelCluster.cpp @@ -142,19 +142,16 @@ static std::list getClustersFromFeature(Feature::Type& feature, dou if (!probability || !value) { - LMS_LOG(DBUPDATER, DEBUG) << "Missing " << node.node; + LMS_LOG(DBUPDATER, ERROR) << "Missing " << node.node; continue; } - if (std::stod(probability->data()) < 0.90) - { - LMS_LOG(DBUPDATER, DEBUG) << "Probability too low for " << node.node << "(" << std::stod(probability->data()) << ")"; + if (std::stod(probability->data()) < minProb) continue; - } if (node.valueMapping[value->data()] == "") { - LMS_LOG(DBUPDATER, DEBUG) << "Unknown value '" << value->data() << "'"; + LMS_LOG(DBUPDATER, ERROR) << "Unknown value '" << value->data() << "'"; continue; } diff --git a/src/database/updater/DatabaseUpdater.cpp b/src/database/updater/DatabaseUpdater.cpp index 7e5afb44..36bc51e5 100644 --- a/src/database/updater/DatabaseUpdater.cpp +++ b/src/database/updater/DatabaseUpdater.cpp @@ -263,10 +263,11 @@ Updater::process(boost::system::error_code err) checkDuplicatedAudioFiles(stats); LMS_LOG(DBUPDATER, INFO) << "Processed all files, now calling listeners..."; - scanComplete().emit(stats); + for (auto eventHandler : _eventHandlers) + eventHandler->handleFilesUpdated(); } - LMS_LOG(DBUPDATER, INFO) << "Scan complete. Scanned = " << stats.nbScanned << ", Skipped = " << stats.nbSkipped << ", Changes = " << stats.nbChanges() << " (added = " << stats.nbAdded << ", nbRemoved = " << stats.nbRemoved << ", nbModified = " << stats.nbModified << "), Scan errors = " << stats.nbScanErrors << ", Not imported = " << stats.nbNotImported; + LMS_LOG(DBUPDATER, INFO) << "Scan " << (_running ? "complete" : "aborted") << ". Changes = " << stats.nbChanges() << " (added = " << stats.nbAdded << ", removed = " << stats.nbRemoved << ", updated = " << stats.nbUpdated << "), Not changed = " << stats.nbNoChange << ", Scanned = " << stats.nbScanned << " (errors = " << stats.nbScanErrors << ", not imported = " << stats.nbNotImported << ")"; // Update database stats boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); @@ -280,6 +281,8 @@ Updater::process(boost::system::error_code err) Setting::setBool(_db->getSession(), "manual_scan_requested", false); processNextJob(); + + scanComplete().emit(stats); } } @@ -404,12 +407,9 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) if (track && track->getLastWriteTime() == lastWriteTime) { - stats.nbSkipped++; - transaction.rollback(); + stats.nbNoChange++; return; } - - transaction.rollback(); } MetaData::Items items; @@ -535,7 +535,7 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) for (auto cluster : track->getClusters()) cluster.remove(); - stats.nbModified++; + stats.nbUpdated++; } assert(track); @@ -868,7 +868,7 @@ Updater::processVideoFile( const boost::filesystem::path& file, Stats& stats) else { LMS_LOG(DBUPDATER, DEBUG) << "Updating '" << file << "'"; - stats.nbModified++; + stats.nbUpdated++; } assert(video); diff --git a/src/database/updater/DatabaseUpdater.hpp b/src/database/updater/DatabaseUpdater.hpp index 5683b50c..f9945b70 100644 --- a/src/database/updater/DatabaseUpdater.hpp +++ b/src/database/updater/DatabaseUpdater.hpp @@ -53,15 +53,15 @@ class Updater struct Stats { - std::size_t nbSkipped = 0; // no change since last scan - std::size_t nbScanned = 0; + std::size_t nbNoChange = 0; // no change since last scan + std::size_t nbScanned = 0; // total scanned filed std::size_t nbScanErrors = 0; // cannot scan file - std::size_t nbNotImported = 0; // Not imported (criteria not filled) - std::size_t nbAdded = 0; - std::size_t nbRemoved = 0; - std::size_t nbModified = 0; + std::size_t nbNotImported = 0; // Scanned, but not imported (criteria not filled) + std::size_t nbAdded = 0; // Added in DB + std::size_t nbRemoved = 0; // removed from DB + std::size_t nbUpdated = 0; // updated file in DB - std::size_t nbChanges() const { return nbAdded + nbRemoved + nbModified;} + std::size_t nbChanges() const { return nbAdded + nbRemoved + nbUpdated;} }; // Emitted when the whole database has been scanned (and all the event handlers have been called) From 85a61e46eef978864402f9ebcf8f2bd42324677a Mon Sep 17 00:00:00 2001 From: emeric Date: Sun, 5 Jun 2016 14:27:34 +0200 Subject: [PATCH 023/195] [DOC] Updated README --- README.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 13774a2c..dd3cf954 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,21 @@ # LMS - Lightweight Media Server LMS is a self-hosted media streaming software, released under the GPLv3 license. -It allows you to access your music and your videos using an https web interface. +It allows you to access your music and your videos using an http(s) web interface. ## Features - Winamp-like interface, suited for large media collections - Audio/Video transcode for maximum interoperability and low bandwith requirements - User management - - Multi genre - - MusicBrainzID support + - Playlist support + - MusicBrainzID support to handle duplicated artist and release names + +Tags are automatically created to facilitate music searching, using: + - Multi genre information from metadata + - High-level information from AcousticBrainz Please note LMS is still under development and will gain more features in the future: - - Radio function (using AcousticBrainz) + - Radio function - Interface suited for mobile devices - Video support - Subsonic and/or Ampache API @@ -58,7 +62,7 @@ By default, LMS will read its configuration in the file '/etc/lms.conf' ## Running ```sh -$ /usr/bin/lms [/path/to/config/file] +$ /usr/bin/lms [config_file] ``` LMS needs write access to the directory used for the database. It is highly recommended to run LMS as a non root user. From f825afa37a0ce8fa94636c74d9365802b3f9c64b Mon Sep 17 00:00:00 2001 From: emeric Date: Tue, 23 Aug 2016 13:33:01 +0200 Subject: [PATCH 024/195] Moved util functions into utils --- src/Makefile.am | 4 ++-- src/av/AvInfo.cpp | 2 +- src/av/AvTranscoder.cpp | 3 +-- src/cover/CoverArtGrabber.cpp | 3 ++- src/database/Artist.cpp | 2 +- src/database/DatabaseHandler.cpp | 2 +- src/database/SearchFilter.cpp | 2 +- src/database/Track.cpp | 2 +- src/database/updater/DatabaseFeatureExtractor.cpp | 5 +++-- src/database/updater/DatabaseHighLevelCluster.cpp | 4 ++-- src/database/updater/DatabaseUpdater.cpp | 8 +++++--- src/feature/FeatureExtractor.cpp | 2 +- src/feature/FeatureStore.cpp | 4 ++-- src/image/Image.cpp | 3 ++- src/main/main.cpp | 4 ++-- src/metadata/AvFormat.cpp | 9 +++++---- src/metadata/TagLibParser.cpp | 2 +- src/ui/LmsApplication.cpp | 2 +- src/ui/audio/AudioPlayer.cpp | 4 ++-- src/ui/audio/desktop/DesktopAudio.cpp | 3 ++- src/ui/audio/desktop/PlayQueue.cpp | 2 +- src/ui/audio/desktop/TableFilter.cpp | 3 ++- src/ui/audio/desktop/TrackView.cpp | 2 +- src/ui/audio/mobile/ArtistSearchView.cpp | 3 ++- src/ui/audio/mobile/MobileAudio.cpp | 7 ++++--- src/ui/audio/mobile/MobilePlayQueue.cpp | 2 +- src/ui/audio/mobile/PreviewSearchView.cpp | 3 ++- src/ui/audio/mobile/ReleaseSearch.cpp | 3 ++- src/ui/audio/mobile/ReleaseView.cpp | 3 ++- src/ui/audio/mobile/TrackSearch.cpp | 3 ++- src/ui/audio/mobile/TrackSearchView.cpp | 3 ++- src/ui/resource/AvConvTranscodeStreamResource.cpp | 2 +- src/ui/resource/ImageResource.cpp | 3 ++- src/ui/resource/TranscodeResource.cpp | 3 ++- src/ui/settings/Settings.cpp | 3 ++- src/ui/settings/SettingsAccountFormView.cpp | 2 +- src/ui/settings/SettingsAudioFormView.cpp | 2 +- src/ui/settings/SettingsDatabaseFormView.cpp | 3 ++- src/ui/settings/SettingsFirstConnectionFormView.cpp | 2 +- src/ui/settings/SettingsMediaDirectoryFormView.cpp | 3 ++- src/ui/settings/SettingsUserFormView.cpp | 2 +- src/ui/settings/SettingsUsers.cpp | 2 +- src/{config => utils}/Config.cpp | 6 ++++++ src/{config => utils}/Config.hpp | 1 + src/{logger => utils}/Logger.cpp | 0 src/{logger => utils}/Logger.hpp | 0 src/utils/Path.cpp | 2 +- 47 files changed, 83 insertions(+), 57 deletions(-) rename src/{config => utils}/Config.cpp (96%) rename src/{config => utils}/Config.hpp (99%) rename src/{logger => utils}/Logger.cpp (100%) rename src/{logger => utils}/Logger.hpp (100%) diff --git a/src/Makefile.am b/src/Makefile.am index 568fb488..6de2fd19 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -4,7 +4,6 @@ lms_SOURCES = \ $(srcdir)/main/main.cpp \ $(srcdir)/av/AvInfo.cpp \ $(srcdir)/av/AvTranscoder.cpp \ - $(srcdir)/config/Config.cpp \ $(srcdir)/cover/CoverArtGrabber.cpp \ $(srcdir)/database/Artist.cpp \ $(srcdir)/database/DatabaseHandler.cpp \ @@ -23,7 +22,6 @@ lms_SOURCES = \ $(srcdir)/feature/FeatureExtractor.cpp \ $(srcdir)/feature/FeatureStore.cpp \ $(srcdir)/image/Image.cpp \ - $(srcdir)/logger/Logger.cpp \ $(srcdir)/metadata/AvFormat.cpp \ $(srcdir)/metadata/TagLibParser.cpp \ $(srcdir)/ui/LmsApplication.cpp \ @@ -61,6 +59,8 @@ lms_SOURCES = \ $(srcdir)/ui/settings/SettingsMediaDirectoryFormView.cpp \ $(srcdir)/ui/settings/SettingsUserFormView.cpp \ $(srcdir)/ui/settings/SettingsUsers.cpp \ + $(srcdir)/utils/Config.cpp \ + $(srcdir)/utils/Logger.cpp \ $(srcdir)/utils/Path.cpp \ $(srcdir)/utils/Utils.cpp diff --git a/src/av/AvInfo.cpp b/src/av/AvInfo.cpp index ba320c0e..90aa10d4 100644 --- a/src/av/AvInfo.cpp +++ b/src/av/AvInfo.cpp @@ -19,7 +19,7 @@ #include -#include "logger/Logger.hpp" +#include "utils/Logger.hpp" #include "AvInfo.hpp" diff --git a/src/av/AvTranscoder.cpp b/src/av/AvTranscoder.cpp index c970d334..3a82fcf8 100644 --- a/src/av/AvTranscoder.cpp +++ b/src/av/AvTranscoder.cpp @@ -20,8 +20,7 @@ #include #include "utils/Path.hpp" - -#include "logger/Logger.hpp" +#include "utils/Logger.hpp" #include "AvTranscoder.hpp" diff --git a/src/cover/CoverArtGrabber.cpp b/src/cover/CoverArtGrabber.cpp index 5eed27fb..a43f3626 100644 --- a/src/cover/CoverArtGrabber.cpp +++ b/src/cover/CoverArtGrabber.cpp @@ -17,9 +17,10 @@ * along with LMS. If not, see . */ -#include "logger/Logger.hpp" #include "av/AvInfo.hpp" +#include "utils/Logger.hpp" + #include "CoverArtGrabber.hpp" namespace { diff --git a/src/database/Artist.cpp b/src/database/Artist.cpp index b141986d..67f4f340 100644 --- a/src/database/Artist.cpp +++ b/src/database/Artist.cpp @@ -20,7 +20,7 @@ #include "Types.hpp" #include "SqlQuery.hpp" -#include "logger/Logger.hpp" +#include "utils/Logger.hpp" namespace Database { diff --git a/src/database/DatabaseHandler.cpp b/src/database/DatabaseHandler.cpp index c8d160ed..c159d378 100644 --- a/src/database/DatabaseHandler.cpp +++ b/src/database/DatabaseHandler.cpp @@ -30,7 +30,7 @@ #include "Setting.hpp" -#include "logger/Logger.hpp" +#include "utils/Logger.hpp" #include "DatabaseHandler.hpp" diff --git a/src/database/SearchFilter.cpp b/src/database/SearchFilter.cpp index eafd86d5..194296e4 100644 --- a/src/database/SearchFilter.cpp +++ b/src/database/SearchFilter.cpp @@ -17,7 +17,7 @@ * along with LMS. If not, see . */ -#include "logger/Logger.hpp" +#include "utils/Logger.hpp" #include "SearchFilter.hpp" diff --git a/src/database/Track.cpp b/src/database/Track.cpp index a423abbf..cfb3f8ea 100644 --- a/src/database/Track.cpp +++ b/src/database/Track.cpp @@ -21,7 +21,7 @@ #include -#include "logger/Logger.hpp" +#include "utils/Logger.hpp" #include "SqlQuery.hpp" diff --git a/src/database/updater/DatabaseFeatureExtractor.cpp b/src/database/updater/DatabaseFeatureExtractor.cpp index 8b44ecce..1052c6cc 100644 --- a/src/database/updater/DatabaseFeatureExtractor.cpp +++ b/src/database/updater/DatabaseFeatureExtractor.cpp @@ -17,11 +17,12 @@ * along with LMS. If not, see . */ -#include "logger/Logger.hpp" +#include "database/Setting.hpp" #include "feature/FeatureExtractor.hpp" #include "feature/FeatureStore.hpp" -#include "database/Setting.hpp" + +#include "utils/Logger.hpp" #include "DatabaseFeatureExtractor.hpp" diff --git a/src/database/updater/DatabaseHighLevelCluster.cpp b/src/database/updater/DatabaseHighLevelCluster.cpp index 608963b1..995e106b 100644 --- a/src/database/updater/DatabaseHighLevelCluster.cpp +++ b/src/database/updater/DatabaseHighLevelCluster.cpp @@ -17,11 +17,11 @@ * along with LMS. If not, see . */ -#include "logger/Logger.hpp" - #include "database/Setting.hpp" #include "feature/FeatureStore.hpp" +#include "utils/Logger.hpp" + #include "DatabaseHighLevelCluster.hpp" namespace Database { diff --git a/src/database/updater/DatabaseUpdater.cpp b/src/database/updater/DatabaseUpdater.cpp index 36bc51e5..cf7920fa 100644 --- a/src/database/updater/DatabaseUpdater.cpp +++ b/src/database/updater/DatabaseUpdater.cpp @@ -23,14 +23,16 @@ #include #include -#include "logger/Logger.hpp" #include "cover/CoverArtGrabber.hpp" -#include "utils/Utils.hpp" -#include "utils/Path.hpp" #include "database/Setting.hpp" #include "database/Types.hpp" +#include "utils/Logger.hpp" +#include "utils/Path.hpp" +#include "utils/Utils.hpp" + + #include "DatabaseUpdater.hpp" namespace { diff --git a/src/feature/FeatureExtractor.cpp b/src/feature/FeatureExtractor.cpp index 914757af..7f9560cb 100644 --- a/src/feature/FeatureExtractor.cpp +++ b/src/feature/FeatureExtractor.cpp @@ -25,7 +25,7 @@ #include -#include "logger/Logger.hpp" +#include "utils/Logger.hpp" #include "utils/Path.hpp" #include "FeatureExtractor.hpp" diff --git a/src/feature/FeatureStore.cpp b/src/feature/FeatureStore.cpp index f58c51ab..3856bcf8 100644 --- a/src/feature/FeatureStore.cpp +++ b/src/feature/FeatureStore.cpp @@ -19,8 +19,8 @@ #include -#include "logger/Logger.hpp" -#include "config/Config.hpp" +#include "utils/Config.hpp" +#include "utils/Logger.hpp" #include "utils/Path.hpp" #include "FeatureStore.hpp" diff --git a/src/image/Image.cpp b/src/image/Image.cpp index d121d860..4ba865f2 100644 --- a/src/image/Image.cpp +++ b/src/image/Image.cpp @@ -16,7 +16,8 @@ * You should have received a copy of the GNU General Public License * along with LMS. If not, see . */ -#include "logger/Logger.hpp" + +#include "utils/Logger.hpp" #include "Image.hpp" diff --git a/src/main/main.cpp b/src/main/main.cpp index 5e9f4082..0282a8e1 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -21,10 +21,10 @@ #include -#include "config/Config.hpp" +#include "utils/Config.hpp" #include "av/AvInfo.hpp" #include "av/AvTranscoder.hpp" -#include "logger/Logger.hpp" +#include "utils/Logger.hpp" #include "image/Image.hpp" #include "feature/FeatureExtractor.hpp" diff --git a/src/metadata/AvFormat.cpp b/src/metadata/AvFormat.cpp index 81a95b26..da5fc3bb 100644 --- a/src/metadata/AvFormat.cpp +++ b/src/metadata/AvFormat.cpp @@ -17,14 +17,15 @@ * along with LMS. If not, see . */ -#include "AvFormat.hpp" - -#include "logger/Logger.hpp" -#include "utils/Utils.hpp" #include #include "av/AvInfo.hpp" +#include "utils/Logger.hpp" +#include "utils/Utils.hpp" + +#include "AvFormat.hpp" + namespace MetaData { diff --git a/src/metadata/TagLibParser.cpp b/src/metadata/TagLibParser.cpp index 28f455e6..7afa6140 100644 --- a/src/metadata/TagLibParser.cpp +++ b/src/metadata/TagLibParser.cpp @@ -23,7 +23,7 @@ #include #include -#include "logger/Logger.hpp" +#include "utils/Logger.hpp" #include "utils/Utils.hpp" #include "TagLibParser.hpp" diff --git a/src/ui/LmsApplication.cpp b/src/ui/LmsApplication.cpp index faa87640..d68c2f02 100644 --- a/src/ui/LmsApplication.cpp +++ b/src/ui/LmsApplication.cpp @@ -28,7 +28,7 @@ #include #include "config/config.h" -#include "logger/Logger.hpp" +#include "utils/Logger.hpp" #include "utils/Utils.hpp" #include "settings/Settings.hpp" diff --git a/src/ui/audio/AudioPlayer.cpp b/src/ui/audio/AudioPlayer.cpp index a1bd6f6e..bb487af9 100644 --- a/src/ui/audio/AudioPlayer.cpp +++ b/src/ui/audio/AudioPlayer.cpp @@ -17,7 +17,6 @@ * along with LMS. If not, see . */ - #include #include #include @@ -25,7 +24,8 @@ #include #include "common/InputRange.hpp" -#include "logger/Logger.hpp" + +#include "utils/Logger.hpp" #include "utils/Utils.hpp" #include "LmsApplication.hpp" diff --git a/src/ui/audio/desktop/DesktopAudio.cpp b/src/ui/audio/desktop/DesktopAudio.cpp index 08c857a2..3241c623 100644 --- a/src/ui/audio/desktop/DesktopAudio.cpp +++ b/src/ui/audio/desktop/DesktopAudio.cpp @@ -31,7 +31,8 @@ #include #include -#include "logger/Logger.hpp" +#include "utils/Logger.hpp" + #include "LmsApplication.hpp" #include "TableFilter.hpp" diff --git a/src/ui/audio/desktop/PlayQueue.cpp b/src/ui/audio/desktop/PlayQueue.cpp index 1d4c9c51..8eea4231 100644 --- a/src/ui/audio/desktop/PlayQueue.cpp +++ b/src/ui/audio/desktop/PlayQueue.cpp @@ -27,7 +27,7 @@ #include #include -#include "logger/Logger.hpp" +#include "utils/Logger.hpp" #include "LmsApplication.hpp" #include "PlayQueue.hpp" diff --git a/src/ui/audio/desktop/TableFilter.cpp b/src/ui/audio/desktop/TableFilter.cpp index 3ef75edd..bedb27ab 100644 --- a/src/ui/audio/desktop/TableFilter.cpp +++ b/src/ui/audio/desktop/TableFilter.cpp @@ -20,7 +20,8 @@ #include #include "database/Types.hpp" -#include "logger/Logger.hpp" + +#include "utils/Logger.hpp" #include "LmsApplication.hpp" #include "TableFilter.hpp" diff --git a/src/ui/audio/desktop/TrackView.cpp b/src/ui/audio/desktop/TrackView.cpp index 3c3e9cc6..8695d858 100644 --- a/src/ui/audio/desktop/TrackView.cpp +++ b/src/ui/audio/desktop/TrackView.cpp @@ -22,7 +22,7 @@ #include #include -#include "logger/Logger.hpp" +#include "utils/Logger.hpp" #include "utils/Utils.hpp" #include "LmsApplication.hpp" diff --git a/src/ui/audio/mobile/ArtistSearchView.cpp b/src/ui/audio/mobile/ArtistSearchView.cpp index 1fda1e0d..d05f00dd 100644 --- a/src/ui/audio/mobile/ArtistSearchView.cpp +++ b/src/ui/audio/mobile/ArtistSearchView.cpp @@ -20,7 +20,8 @@ #include #include -#include "logger/Logger.hpp" +#include "utils/Logger.hpp" + #include "SearchUtils.hpp" #include "ArtistSearch.hpp" diff --git a/src/ui/audio/mobile/MobileAudio.cpp b/src/ui/audio/mobile/MobileAudio.cpp index f1078acb..ab08aa30 100644 --- a/src/ui/audio/mobile/MobileAudio.cpp +++ b/src/ui/audio/mobile/MobileAudio.cpp @@ -23,10 +23,8 @@ #include #include -#include "logger/Logger.hpp" -#include "utils/Utils.hpp" - #include "audio/AudioPlayer.hpp" + #include "LmsApplication.hpp" #include "PreviewSearchView.hpp" @@ -39,6 +37,9 @@ #include "MobilePlayQueue.hpp" +#include "utils/Logger.hpp" +#include "utils/Utils.hpp" + #include "MobileAudio.hpp" diff --git a/src/ui/audio/mobile/MobilePlayQueue.cpp b/src/ui/audio/mobile/MobilePlayQueue.cpp index e74ddb9a..c14d0ff7 100644 --- a/src/ui/audio/mobile/MobilePlayQueue.cpp +++ b/src/ui/audio/mobile/MobilePlayQueue.cpp @@ -22,7 +22,7 @@ #include #include -#include "logger/Logger.hpp" +#include "utils/Logger.hpp" #include "LmsApplication.hpp" diff --git a/src/ui/audio/mobile/PreviewSearchView.cpp b/src/ui/audio/mobile/PreviewSearchView.cpp index 38cb7a33..8a602e24 100644 --- a/src/ui/audio/mobile/PreviewSearchView.cpp +++ b/src/ui/audio/mobile/PreviewSearchView.cpp @@ -20,7 +20,8 @@ #include #include -#include "logger/Logger.hpp" +#include "utils/Logger.hpp" + #include "SearchUtils.hpp" #include "ArtistSearch.hpp" diff --git a/src/ui/audio/mobile/ReleaseSearch.cpp b/src/ui/audio/mobile/ReleaseSearch.cpp index 07b34f2a..c45c83cc 100644 --- a/src/ui/audio/mobile/ReleaseSearch.cpp +++ b/src/ui/audio/mobile/ReleaseSearch.cpp @@ -22,7 +22,8 @@ #include #include -#include "logger/Logger.hpp" +#include "utils/Logger.hpp" + #include "LmsApplication.hpp" #include "ReleaseSearch.hpp" diff --git a/src/ui/audio/mobile/ReleaseView.cpp b/src/ui/audio/mobile/ReleaseView.cpp index 27a6350c..93c1e5fc 100644 --- a/src/ui/audio/mobile/ReleaseView.cpp +++ b/src/ui/audio/mobile/ReleaseView.cpp @@ -22,8 +22,9 @@ #include #include -#include "logger/Logger.hpp" +#include "utils/Logger.hpp" #include "utils/Utils.hpp" + #include "LmsApplication.hpp" #include "ReleaseView.hpp" diff --git a/src/ui/audio/mobile/TrackSearch.cpp b/src/ui/audio/mobile/TrackSearch.cpp index 79a2db33..db5d4427 100644 --- a/src/ui/audio/mobile/TrackSearch.cpp +++ b/src/ui/audio/mobile/TrackSearch.cpp @@ -22,8 +22,9 @@ #include #include -#include "logger/Logger.hpp" +#include "utils/Logger.hpp" #include "utils/Utils.hpp" + #include "LmsApplication.hpp" #include "TrackSearch.hpp" diff --git a/src/ui/audio/mobile/TrackSearchView.cpp b/src/ui/audio/mobile/TrackSearchView.cpp index c7138452..bff89ee0 100644 --- a/src/ui/audio/mobile/TrackSearchView.cpp +++ b/src/ui/audio/mobile/TrackSearchView.cpp @@ -20,7 +20,8 @@ #include #include -#include "logger/Logger.hpp" +#include "utils/Logger.hpp" + #include "SearchUtils.hpp" #include "TrackSearch.hpp" diff --git a/src/ui/resource/AvConvTranscodeStreamResource.cpp b/src/ui/resource/AvConvTranscodeStreamResource.cpp index b4e0b4f4..528d9cba 100644 --- a/src/ui/resource/AvConvTranscodeStreamResource.cpp +++ b/src/ui/resource/AvConvTranscodeStreamResource.cpp @@ -21,7 +21,7 @@ #include #include -#include "logger/Logger.hpp" +#include "utils/Logger.hpp" #include "AvConvTranscodeStreamResource.hpp" diff --git a/src/ui/resource/ImageResource.cpp b/src/ui/resource/ImageResource.cpp index e192dd0d..9b56846a 100644 --- a/src/ui/resource/ImageResource.cpp +++ b/src/ui/resource/ImageResource.cpp @@ -20,8 +20,9 @@ #include #include -#include "logger/Logger.hpp" +#include "utils/Logger.hpp" #include "utils/Utils.hpp" + #include "LmsApplication.hpp" #include "cover/CoverArtGrabber.hpp" diff --git a/src/ui/resource/TranscodeResource.cpp b/src/ui/resource/TranscodeResource.cpp index 8acef15c..f12c6fad 100644 --- a/src/ui/resource/TranscodeResource.cpp +++ b/src/ui/resource/TranscodeResource.cpp @@ -19,7 +19,8 @@ #include -#include "logger/Logger.hpp" +#include "utils/Logger.hpp" + #include "LmsApplication.hpp" #include "TranscodeResource.hpp" diff --git a/src/ui/settings/Settings.cpp b/src/ui/settings/Settings.cpp index 78d3838b..52e75376 100644 --- a/src/ui/settings/Settings.cpp +++ b/src/ui/settings/Settings.cpp @@ -29,7 +29,8 @@ #include "SettingsMediaDirectories.hpp" #include "SettingsUsers.hpp" -#include "logger/Logger.hpp" +#include "utils/Logger.hpp" + #include "database/Setting.hpp" #include "database/updater/DatabaseUpdater.hpp" diff --git a/src/ui/settings/SettingsAccountFormView.cpp b/src/ui/settings/SettingsAccountFormView.cpp index c7f265e8..bbffbd2a 100644 --- a/src/ui/settings/SettingsAccountFormView.cpp +++ b/src/ui/settings/SettingsAccountFormView.cpp @@ -26,7 +26,7 @@ #include #include -#include "logger/Logger.hpp" +#include "utils/Logger.hpp" #include "common/Validators.hpp" #include "LmsApplication.hpp" diff --git a/src/ui/settings/SettingsAudioFormView.cpp b/src/ui/settings/SettingsAudioFormView.cpp index e86e171d..91c49015 100644 --- a/src/ui/settings/SettingsAudioFormView.cpp +++ b/src/ui/settings/SettingsAudioFormView.cpp @@ -24,7 +24,7 @@ #include #include -#include "logger/Logger.hpp" +#include "utils/Logger.hpp" #include "common/Validators.hpp" #include "LmsApplication.hpp" diff --git a/src/ui/settings/SettingsDatabaseFormView.cpp b/src/ui/settings/SettingsDatabaseFormView.cpp index ffc15630..1379d0d1 100644 --- a/src/ui/settings/SettingsDatabaseFormView.cpp +++ b/src/ui/settings/SettingsDatabaseFormView.cpp @@ -30,7 +30,8 @@ #include #include -#include "logger/Logger.hpp" +#include "utils/Logger.hpp" + #include "database/Setting.hpp" #include "common/DirectoryValidator.hpp" diff --git a/src/ui/settings/SettingsFirstConnectionFormView.cpp b/src/ui/settings/SettingsFirstConnectionFormView.cpp index 7f0628a0..c6873079 100644 --- a/src/ui/settings/SettingsFirstConnectionFormView.cpp +++ b/src/ui/settings/SettingsFirstConnectionFormView.cpp @@ -24,7 +24,7 @@ #include #include -#include "logger/Logger.hpp" +#include "utils/Logger.hpp" #include "common/Validators.hpp" #include "LmsApplication.hpp" diff --git a/src/ui/settings/SettingsMediaDirectoryFormView.cpp b/src/ui/settings/SettingsMediaDirectoryFormView.cpp index 8d9c485a..a9e4a2f9 100644 --- a/src/ui/settings/SettingsMediaDirectoryFormView.cpp +++ b/src/ui/settings/SettingsMediaDirectoryFormView.cpp @@ -25,7 +25,8 @@ #include #include -#include "logger/Logger.hpp" +#include "utils/Logger.hpp" + #include "database/MediaDirectory.hpp" #include "common/DirectoryValidator.hpp" diff --git a/src/ui/settings/SettingsUserFormView.cpp b/src/ui/settings/SettingsUserFormView.cpp index f3c45b69..fc3224dd 100644 --- a/src/ui/settings/SettingsUserFormView.cpp +++ b/src/ui/settings/SettingsUserFormView.cpp @@ -27,7 +27,7 @@ #include #include -#include "logger/Logger.hpp" +#include "utils/Logger.hpp" #include "common/Validators.hpp" #include "LmsApplication.hpp" diff --git a/src/ui/settings/SettingsUsers.cpp b/src/ui/settings/SettingsUsers.cpp index 00eff561..314a2a59 100644 --- a/src/ui/settings/SettingsUsers.cpp +++ b/src/ui/settings/SettingsUsers.cpp @@ -25,7 +25,7 @@ #include -#include "logger/Logger.hpp" +#include "utils/Logger.hpp" #include "SettingsUserFormView.hpp" #include "LmsApplication.hpp" diff --git a/src/config/Config.cpp b/src/utils/Config.cpp similarity index 96% rename from src/config/Config.cpp rename to src/utils/Config.cpp index 61c2fda2..9ff3a95e 100644 --- a/src/config/Config.cpp +++ b/src/utils/Config.cpp @@ -30,6 +30,12 @@ Config::Config() { } +Config::~Config() +{ + if (_config) + delete _config; +} + Config& Config::instance() { diff --git a/src/config/Config.hpp b/src/utils/Config.hpp similarity index 99% rename from src/config/Config.hpp rename to src/utils/Config.hpp index afa0863d..bd52e732 100644 --- a/src/config/Config.hpp +++ b/src/utils/Config.hpp @@ -43,6 +43,7 @@ class Config private: Config(); + ~Config(); libconfig::Config *_config; }; diff --git a/src/logger/Logger.cpp b/src/utils/Logger.cpp similarity index 100% rename from src/logger/Logger.cpp rename to src/utils/Logger.cpp diff --git a/src/logger/Logger.hpp b/src/utils/Logger.hpp similarity index 100% rename from src/logger/Logger.hpp rename to src/utils/Logger.hpp diff --git a/src/utils/Path.cpp b/src/utils/Path.cpp index b7685416..dfc49ba4 100644 --- a/src/utils/Path.cpp +++ b/src/utils/Path.cpp @@ -23,7 +23,7 @@ #include // for boost::crc_32_type #include -#include "logger/Logger.hpp" +#include "utils/Logger.hpp" #include "Path.hpp" From 3af320e42be1a542cd360aaa16fff833300a5cca Mon Sep 17 00:00:00 2001 From: emeric Date: Wed, 24 Aug 2016 16:54:05 +0200 Subject: [PATCH 025/195] [CONF] Made a single configuration file --- README.md | 42 ++++++++-------------------------- conf/lms.conf | 12 ++++------ src/feature/FeatureStore.cpp | 40 +++++--------------------------- src/feature/FeatureStore.hpp | 3 --- src/main/main.cpp | 44 +++++++++++++++++++++++++++--------- src/utils/Config.cpp | 13 +++++++++++ src/utils/Config.hpp | 1 + 7 files changed, 66 insertions(+), 89 deletions(-) diff --git a/README.md b/README.md index dd3cf954..d60a242d 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,8 @@ $ apt-get install g++ autoconf automake libboost-dev libboost-locale-dev libboos ## Build ```sh +$ git clone https://github.com/epoupon/lms.git lms +$ cd lms $ autoreconf -vfi $ mkdir build $ cd build @@ -52,51 +54,25 @@ $ make install This command requires root privileges ## Configuration -LMS uses a config file for low level settings: -- database file path, default is /var/lms/lms.db -- TLS settings, default is none -- listen address/port, default is 0.0.0.0 port 5081 +LMS uses a configuration file, installed in '/etc/lms.conf' +It is recommended to edit this file and change the relevant settings (working directory, listen port, etc.) Other settings are set using the web interface. -By default, LMS will read its configuration in the file '/etc/lms.conf' + +It is highly recommended to run LMS as a non root user. Therefore make sure the user has write permissions on the working directory. ## Running ```sh -$ /usr/bin/lms [config_file] +$ lms [config_file] ``` -LMS needs write access to the directory used for the database. -It is highly recommended to run LMS as a non root user. +Logs are output in the working directory, in the file 'lms.log' To connect to LMS, just open your favorite browser and go to http://localhost:5081 -## Mobile - -Add the following code in your /etc/wt/wt_config.xml file: -``` - - true - -``` - -## Setting up SSL materials (optional) -Here is just a self signed certificate example, you could do use a CA if you want. - -Generate a dh file: -```sh -$ openssl dhparam -out dh2048.pem 2048 -``` -Generate a self signed certificate: -```sh -$ openssl req -x509 -out cert.pem -keyout privkey.pem -newkey rsa:4096 -``` -Depending on your SSL parameters, you may be asked for the PEM passphrase to unlock the private key. - -To connect to LMS, just open your favorite browser and go to https://localhost:5081 - ## Credits - Wt (http://www.webtoolkit.eu/) - bootstrap3 (http://getbootstrap.com/) -- libav project (https://www.libav.org/) +- ffmpeg project (https://ffmpeg.org/) - Magick++ (http://www.imagemagick.org/Magick++/) - MetaBrainz (https://metabrainz.org/) diff --git a/conf/lms.conf b/conf/lms.conf index b482a6f1..e457c84a 100644 --- a/conf/lms.conf +++ b/conf/lms.conf @@ -1,15 +1,13 @@ # LMS Sample configuration file -# Path to the DB file. -# Must have write privileges in order to create and modify the file -db-path = "/storage/emeric/lms/lms.dev.db"; - -# Cache directory for downloaded contents -cache-dir-path = "/var/lms/cache"; +# Path to the working directory +# Must have write privileges in order to create and modify this directory +working-dir = "/var/lms/"; # Listen port/addr of the web server listen-port = 5082; listen-addr = "0.0.0.0"; +behind-reverse-proxy = false; # If enabled, these files have to exist and have correct permissions tls-enable = false; @@ -21,6 +19,4 @@ tls-dh = "/var/lms/dh2048.pem"; docroot = "/usr/share/lms/docroot/;/resources,/css,/images,/favicon.ico"; approot = "/usr/share/lms/approot"; -# Path to the wt_config.xml file -wt-config = "/etc/wt/wt_config.xml"; diff --git a/src/feature/FeatureStore.cpp b/src/feature/FeatureStore.cpp index 3856bcf8..4322ac7e 100644 --- a/src/feature/FeatureStore.cpp +++ b/src/feature/FeatureStore.cpp @@ -38,37 +38,15 @@ Store::instance(void) return instance; } -void -Store::reload(void) +static boost::filesystem::path +getPath(std::string mbid, std::string type) { - boost::filesystem::path cacheDir = _storePath = Config::instance().getString("cache-dir-path", ""); - - if (!ensureDirectory(cacheDir)) - { - LMS_LOG(DBUPDATER, ERROR) << "Cache directory '" << cacheDir << "' not valid"; - throw std::runtime_error("Cache directory '" + cacheDir.string() + "' not valid!"); - } - - _storePath = cacheDir / "features"; - - if (!ensureDirectory(_storePath)) - { - LMS_LOG(DBUPDATER, ERROR) << "Features directory '" << _storePath << "' not valid"; - throw std::runtime_error("Features directory '" + _storePath.string() + "' not valid"); - } -} - -boost::filesystem::path getPath(boost::filesystem::path root, std::string mbid, std::string type) -{ - return root / (mbid + "_" + type); + return Config::instance().getPath("working-dir") / "features" / (mbid + "_" + type); } bool Store::exists(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::string type) { - if (_storePath.empty()) - reload(); - Wt::Dbo::Transaction transaction(session); auto track = Database::Track::getById(session, trackId); @@ -79,16 +57,13 @@ Store::exists(Wt::Dbo::Session& session, Database::Track::id_type trackId, std:: if (mbid.empty()) return false; - boost::filesystem::path path = getPath(_storePath, mbid, type); + boost::filesystem::path path = getPath(mbid, type); return boost::filesystem::exists(path); } bool Store::get(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::string type, Type& feature) { - if (_storePath.empty()) - reload(); - Wt::Dbo::Transaction transaction(session); auto track = Database::Track::getById(session, trackId); @@ -99,7 +74,7 @@ Store::get(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::str if (mbid.empty()) return false; - boost::filesystem::path path = getPath(_storePath, mbid, type); + boost::filesystem::path path = getPath(mbid, type); if (!boost::filesystem::exists(path)) return false; @@ -121,9 +96,6 @@ Store::get(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::str bool Store::set(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::string type, const Type& feature) { - if (_storePath.empty()) - reload(); - Wt::Dbo::Transaction transaction(session); auto track = Database::Track::getById(session, trackId); @@ -134,7 +106,7 @@ Store::set(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::str if (mbid.empty()) return false; - boost::filesystem::path path = getPath(_storePath, mbid, type); + boost::filesystem::path path = getPath(mbid, type); try { diff --git a/src/feature/FeatureStore.hpp b/src/feature/FeatureStore.hpp index 7716d9d7..e1f70bea 100644 --- a/src/feature/FeatureStore.hpp +++ b/src/feature/FeatureStore.hpp @@ -39,11 +39,8 @@ class Store bool set(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::string type, const Type& feature); - void reload(); private: Store(); - - boost::filesystem::path _storePath; }; } // namespace CoverArt diff --git a/src/main/main.cpp b/src/main/main.cpp index 0282a8e1..7cfafa8a 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -18,6 +18,7 @@ */ #include +#include #include @@ -34,13 +35,15 @@ #include "ui/LmsApplication.hpp" - -static std::vector getWtArgs(std::string path) +std::vector generateWtConfig(std::string execPath) { std::vector args; - args.push_back(path); - args.push_back("-c" + Config::instance().getString("wt-config")); + boost::filesystem::path wtConfigPath = Config::instance().getPath("working-dir") / "wt_config.xml"; + boost::filesystem::path wtLogFilePath = Config::instance().getPath("working-dir") / "lms.log"; + + args.push_back(execPath); + args.push_back("--config=" + wtConfigPath.string()); args.push_back("--docroot=" + Config::instance().getString("docroot")); args.push_back("--approot=" + Config::instance().getString("approot")); @@ -58,9 +61,21 @@ static std::vector getWtArgs(std::string path) args.push_back("--http-address=" + Config::instance().getString("listen-addr", "0.0.0.0")); } + // Generate the wt_config.xml file + boost::property_tree::ptree pt; + + pt.put("server.application-settings..location", "*"); + pt.put("server.application-settings.log-file", wtLogFilePath.string()); + pt.put("server.application-settings.behind-reverse-proxy", Config::instance().getBool("behind-reverse-proxy", false)); + pt.put("server.application-settings.progressive-bootstrap", true); + + std::ofstream oss(wtConfigPath.string().c_str(), std::ios::out); + boost::property_tree::xml_parser::write_xml(oss, pt); + return args; } + int main(int argc, char* argv[]) { boost::filesystem::path configFilePath = "/etc/lms.conf"; @@ -77,18 +92,25 @@ int main(int argc, char* argv[]) // Make pstream work with ffmpeg close(STDIN_FILENO); - Config::instance().setFile(configFilePath); - std::vector wtArgs = getWtArgs(argv[0]); + // Make sure the working directory exists + // TODO check with boost::system::error_code ec; + boost::filesystem::create_directories(Config::instance().getPath("working-dir")); + boost::filesystem::create_directories(Config::instance().getPath("working-dir") / "features"); - // Construct argc/argv for Wt - const char* wtArgv[wtArgs.size()]; - for (std::size_t i = 0; i < wtArgs.size(); ++i) - wtArgv[i] = wtArgs[i].c_str(); + // Construct WT configuration and get the argc/argv back + std::vector wtServerArgs = generateWtConfig(argv[0]); + + const char* wtArgv[wtServerArgs.size()]; + for (std::size_t i = 0; i < wtServerArgs.size(); ++i) + { + std::cout << "ARG = " << wtServerArgs[i] << std::endl; + wtArgv[i] = wtServerArgs[i].c_str(); + } Wt::WServer server(argv[0]); - server.setServerConfiguration (wtArgs.size(), const_cast(wtArgv)); + server.setServerConfiguration (wtServerArgs.size(), const_cast(wtArgv)); Wt::WServer::instance()->logger().configure("*"); // log everything, TODO configure this diff --git a/src/utils/Config.cpp b/src/utils/Config.cpp index 9ff3a95e..abc20d89 100644 --- a/src/utils/Config.cpp +++ b/src/utils/Config.cpp @@ -66,6 +66,19 @@ Config::getString(std::string setting, std::string def) } } +boost::filesystem::path +Config::getPath(std::string setting, boost::filesystem::path path) +{ + try { + const char* res = _config->lookup(setting); + return boost::filesystem::path(std::string(res)); + } + catch (std::exception &e) + { + return path; + } +} + unsigned long Config::getULong(std::string setting, unsigned long def) { diff --git a/src/utils/Config.hpp b/src/utils/Config.hpp index bd52e732..799e772a 100644 --- a/src/utils/Config.hpp +++ b/src/utils/Config.hpp @@ -36,6 +36,7 @@ class Config // Default values are returned in case of setting not found std::string getString(std::string setting, std::string def = ""); + boost::filesystem::path getPath(std::string setting, boost::filesystem::path def = boost::filesystem::path()); unsigned long getULong(std::string setting, unsigned long def = 0); long getLong(std::string setting, long def = 0); bool getBool(std::string setting, bool def = false); From 50cd4ed131a6e283e9d8f26bb72122bcfbe03932 Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 24 Sep 2016 16:40:17 +0200 Subject: [PATCH 026/195] Minor cleanup --- src/database/updater/DatabaseHighLevelCluster.cpp | 4 ++-- src/feature/FeatureExtractor.cpp | 3 ++- src/feature/FeatureExtractor.hpp | 2 -- src/feature/FeatureStore.cpp | 4 ++-- src/feature/FeatureStore.hpp | 6 +++--- src/main/main.cpp | 2 +- 6 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/database/updater/DatabaseHighLevelCluster.cpp b/src/database/updater/DatabaseHighLevelCluster.cpp index 995e106b..60d54918 100644 --- a/src/database/updater/DatabaseHighLevelCluster.cpp +++ b/src/database/updater/DatabaseHighLevelCluster.cpp @@ -35,7 +35,7 @@ static Cluster::pointer getCluster(std::string type, std::string value) return cluster; } -static std::list getClustersFromFeature(Feature::Type& feature, double minProb) +static std::list getClustersFromFeature(boost::property_tree::ptree& feature, double minProb) { struct HighLevelNodeDesc { @@ -181,7 +181,7 @@ HighLevelCluster::handleFilesUpdated(void) std::list newClusterNames; if (createTags) { - Feature::Type feature; + boost::property_tree::ptree feature; if (!Feature::Store::instance().get(UpdaterDboSession(), trackId, "high_level", feature)) continue; diff --git a/src/feature/FeatureExtractor.cpp b/src/feature/FeatureExtractor.cpp index 7f9560cb..48a0a888 100644 --- a/src/feature/FeatureExtractor.cpp +++ b/src/feature/FeatureExtractor.cpp @@ -112,7 +112,8 @@ Extractor::getLowLevel(boost::property_tree::ptree& pt, std::string mbid) return false; } - res.erase("metadata"); + // Keep metadata to ease debugging +// res.erase("metadata"); pt = res; diff --git a/src/feature/FeatureExtractor.hpp b/src/feature/FeatureExtractor.hpp index 7696c968..ed7169c2 100644 --- a/src/feature/FeatureExtractor.hpp +++ b/src/feature/FeatureExtractor.hpp @@ -25,8 +25,6 @@ namespace Feature { -typedef boost::property_tree::ptree Type; - class Extractor { public: diff --git a/src/feature/FeatureStore.cpp b/src/feature/FeatureStore.cpp index 4322ac7e..2f52eff8 100644 --- a/src/feature/FeatureStore.cpp +++ b/src/feature/FeatureStore.cpp @@ -62,7 +62,7 @@ Store::exists(Wt::Dbo::Session& session, Database::Track::id_type trackId, std:: } bool -Store::get(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::string type, Type& feature) +Store::get(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::string type, boost::property_tree::ptree& feature) { Wt::Dbo::Transaction transaction(session); @@ -94,7 +94,7 @@ Store::get(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::str bool -Store::set(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::string type, const Type& feature) +Store::set(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::string type, const boost::property_tree::ptree& feature) { Wt::Dbo::Transaction transaction(session); diff --git a/src/feature/FeatureStore.hpp b/src/feature/FeatureStore.hpp index e1f70bea..a02e1400 100644 --- a/src/feature/FeatureStore.hpp +++ b/src/feature/FeatureStore.hpp @@ -20,9 +20,9 @@ #pragma once #include +#include #include "database/Types.hpp" -#include "FeatureExtractor.hpp" namespace Feature { @@ -35,9 +35,9 @@ class Store static Store& instance(); bool exists(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::string type); - bool get(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::string type, Type& feature); + bool get(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::string type, boost::property_tree::ptree& feature); - bool set(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::string type, const Type& feature); + bool set(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::string type, const boost::property_tree::ptree& feature); private: Store(); diff --git a/src/main/main.cpp b/src/main/main.cpp index 7cfafa8a..29830222 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -23,9 +23,9 @@ #include #include "utils/Config.hpp" +#include "utils/Logger.hpp" #include "av/AvInfo.hpp" #include "av/AvTranscoder.hpp" -#include "utils/Logger.hpp" #include "image/Image.hpp" #include "feature/FeatureExtractor.hpp" From a9c835a190a969a35fba705438064388c0055c9d Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 24 Sep 2016 16:44:56 +0200 Subject: [PATCH 027/195] Corrected db path --- src/main/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/main.cpp b/src/main/main.cpp index 29830222..bcd80006 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -123,7 +123,7 @@ int main(int argc, char* argv[]) // Initializing a connection pool to the database that will be shared along services std::unique_ptr - connectionPool( Database::Handler::createConnectionPool(Config::instance().getString("db-path"))); + connectionPool( Database::Handler::createConnectionPool(Config::instance().getPath("working-dir") / "lms.db")); Database::Updater& dbUpdater = Database::Updater::instance(); dbUpdater.setConnectionPool(*connectionPool); From 0a70552f8d348f29556667c3a091390358fe1809 Mon Sep 17 00:00:00 2001 From: emeric Date: Fri, 11 Nov 2016 14:42:13 +0100 Subject: [PATCH 028/195] [UI] Fixed build using older versions of Wt --- src/ui/audio/desktop/DesktopAudio.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/ui/audio/desktop/DesktopAudio.cpp b/src/ui/audio/desktop/DesktopAudio.cpp index 3241c623..63e73c53 100644 --- a/src/ui/audio/desktop/DesktopAudio.cpp +++ b/src/ui/audio/desktop/DesktopAudio.cpp @@ -150,16 +150,34 @@ _playQueue(nullptr) playlistBtn->setMenu(popupMain); } +#if WT_VERSION >= 0X03030400 Wt::WPushButton *upBtn = new Wt::WPushButton("", Wt::XHTMLText); +#else + Wt::WPushButton *upBtn = new Wt::WPushButton("UP"); +#endif upBtn->setStyleClass("btn-sm"); playlistControls->addWidget(upBtn); + +#if WT_VERSION >= 0X03030400 Wt::WPushButton *downBtn = new Wt::WPushButton("", Wt::XHTMLText); +#else + Wt::WPushButton *downBtn = new Wt::WPushButton("DOWN"); +#endif downBtn->setStyleClass("btn-sm"); playlistControls->addWidget(downBtn); +#if WT_VERSION >= 0X03030400 Wt::WPushButton *delBtn = new Wt::WPushButton("", Wt::XHTMLText); +#else + Wt::WPushButton *delBtn = new Wt::WPushButton("DEL"); +#endif + delBtn->setStyleClass("btn-sm btn-warning"); playlistControls->addWidget(delBtn); +#if WT_VERSION >= 0X03030400 Wt::WPushButton *clearBtn = new Wt::WPushButton("", Wt::XHTMLText); +#else + Wt::WPushButton *clearBtn = new Wt::WPushButton("CLR"); +#endif clearBtn->setStyleClass("btn-sm btn-danger"); playlistControls->addWidget(clearBtn); From d8f8c77c07eeb5c35352aaac36d142b380200955 Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 12 Nov 2016 12:23:23 +0100 Subject: [PATCH 029/195] [UI] Fixed build using older versions of Wt. Playback is unfortunately still broken --- src/ui/audio/desktop/TableFilter.cpp | 7 +++++++ src/ui/audio/desktop/TrackView.cpp | 3 +++ src/ui/common/InputRange.cpp | 27 +++++++++++++++++---------- src/ui/common/InputRange.hpp | 16 +++++++++++++++- 4 files changed, 42 insertions(+), 11 deletions(-) diff --git a/src/ui/audio/desktop/TableFilter.cpp b/src/ui/audio/desktop/TableFilter.cpp index bedb27ab..6af45e97 100644 --- a/src/ui/audio/desktop/TableFilter.cpp +++ b/src/ui/audio/desktop/TableFilter.cpp @@ -17,6 +17,7 @@ * along with LMS. If not, see . */ +#include #include #include "database/Types.hpp" @@ -86,7 +87,9 @@ TableFilterCluster::layoutSizeChanged (int width, int height) void TableFilterCluster::refresh(SearchFilter& filter) { +#if WT_VERSION >= 0X03030500 this->clearSelection(); +#endif Cluster::updateUIQueryModel(DboSession(), _queryModel, filter); } @@ -165,7 +168,9 @@ TableFilterArtist::layoutSizeChanged (int width, int height) void TableFilterArtist::refresh(SearchFilter& filter) { +#if WT_VERSION >= 0X03030500 this->clearSelection(); +#endif Artist::updateUIQueryModel(DboSession(), _queryModel, filter); } @@ -249,7 +254,9 @@ TableFilterRelease::layoutSizeChanged (int width, int height) void TableFilterRelease::refresh(SearchFilter& filter) { +#if WT_VERSION >= 0X03030500 this->clearSelection(); +#endif Release::updateUIQueryModel(DboSession(), _queryModel, filter); } diff --git a/src/ui/audio/desktop/TrackView.cpp b/src/ui/audio/desktop/TrackView.cpp index 8695d858..aaec7939 100644 --- a/src/ui/audio/desktop/TrackView.cpp +++ b/src/ui/audio/desktop/TrackView.cpp @@ -19,6 +19,7 @@ #include +#include #include #include @@ -160,7 +161,9 @@ TrackView::emitStats(const SearchFilter& filter) void TrackView::refresh(SearchFilter& filter) { +#if WT_VERSION >= 0X03030500 this->clearSelection(); +#endif Track::updateUIQueryModel(DboSession(), _queryModel, filter); emitStats(filter); diff --git a/src/ui/common/InputRange.cpp b/src/ui/common/InputRange.cpp index 3ff0da7b..cb1ce054 100644 --- a/src/ui/common/InputRange.cpp +++ b/src/ui/common/InputRange.cpp @@ -21,29 +21,36 @@ #if WT_VERSION < 0X03030500 #include +#include #endif #include "InputRange.hpp" +#if WT_VERSION >= 0X03030500 InputRange::InputRange(Wt::WContainerWidget *parent) : Wt::WWebWidget(parent) { - //TODO woraround for older Wt versions -#if WT_VERSION >= 0X03030500 setHtmlTagName("input"); setAttributeValue("type", "range"); -#else - Wt::WTemplate *t = new Wt::WTemplate("", parent); -#endif } - std::string InputRange::jsRef(void) const { -#if WT_VERSION >= 0X03030500 return Wt::WWebWidget::jsRef(); -#else - return this->jsRef() + ".getElementsByTagName(\"input\")[0]"; -#endif } + +#else +InputRange::InputRange(Wt::WContainerWidget *parent) +: Wt::WContainerWidget(parent) +{ + addWidget(new Wt::WTemplate("")); +} + +std::string +InputRange::jsRef(void) const +{ + return Wt::WWebWidget::jsRef() + ".getElementsByTagName(\"input\")[0]"; +} +#endif + diff --git a/src/ui/common/InputRange.hpp b/src/ui/common/InputRange.hpp index 7f03abb4..9ccb8fa8 100644 --- a/src/ui/common/InputRange.hpp +++ b/src/ui/common/InputRange.hpp @@ -22,11 +22,12 @@ #include #include +#include +#if WT_VERSION >= 0X03030500 class InputRange : public Wt::WWebWidget { public: InputRange(Wt::WContainerWidget *parent = 0); - Wt::DomElementType domElementType() const { return Wt::DomElement_INPUT; @@ -35,3 +36,16 @@ class InputRange : public Wt::WWebWidget std::string jsRef() const; }; +#else + +class InputRange : public Wt::WContainerWidget +{ + public: + InputRange(Wt::WContainerWidget *parent = 0); + + std::string jsRef() const; +}; + +#endif + + From 6591006fcc70b303c9261078d5e52b2454a80725 Mon Sep 17 00:00:00 2001 From: emeric Date: Sun, 28 Jan 2018 14:15:50 +0100 Subject: [PATCH 030/195] New UI: first step --- Makefile.am | 8 +- README.md | 12 +- approot/artist.xml | 25 + approot/artists.xml | 25 + approot/messages.xml | 13 + approot/release.xml | 25 + approot/releases.xml | 31 + approot/templates.xml | 26 +- approot/tracks.xml | 33 + docroot/css/lms.css | 32 - src/Makefile.am | 37 +- src/cover/CoverArtGrabber.cpp | 6 +- src/database/DatabaseHandler.cpp | 2 +- src/database/DatabaseHandler.hpp | 1 + src/database/DbArtist.cpp | 202 ++++++ src/database/DbArtist.hpp | 98 +++ src/database/Release.cpp | 146 ++-- src/database/Release.hpp | 38 +- src/database/SearchFilter.cpp | 91 +-- src/database/SearchFilter.hpp | 62 +- src/database/Track.cpp | 133 ++-- src/database/Track.hpp | 48 +- src/database/Types.hpp | 2 +- src/ui/ArtistView.cpp | 122 ++++ .../TrackSearchView.hpp => ArtistView.hpp} | 14 +- src/ui/ArtistsView.cpp | 92 +++ .../MobileAudio.hpp => ArtistsView.hpp} | 20 +- src/ui/Explore.cpp | 98 +++ .../ArtistSearchView.hpp => Explore.hpp} | 10 +- src/ui/{audio/Audio.hpp => Filters.cpp} | 28 +- .../mobile/ArtistView.hpp => Filters.hpp} | 21 +- ...MobilePlayQueueEvents.hpp => HomeView.cpp} | 24 +- .../ReleaseSearchView.hpp => HomeView.hpp} | 12 +- src/ui/LmsApplication.cpp | 203 +++--- src/ui/LmsApplication.hpp | 4 + src/ui/ReleaseView.cpp | 151 +++++ .../Settings.hpp => ReleaseView.hpp} | 12 +- src/ui/ReleasesView.cpp | 103 +++ src/ui/ReleasesView.hpp | 38 ++ src/ui/TracksView.cpp | 109 +++ .../PreviewSearchView.hpp => TracksView.hpp} | 13 +- src/ui/audio/AudioPlayer.cpp | 357 ---------- src/ui/audio/AudioPlayer.hpp | 76 --- src/ui/audio/desktop/DesktopAudio.cpp | 556 --------------- src/ui/audio/desktop/DesktopAudio.hpp | 81 --- src/ui/audio/desktop/Filter.hpp | 57 -- src/ui/audio/desktop/FilterChain.cpp | 75 --- src/ui/audio/desktop/FilterChain.hpp | 59 -- src/ui/audio/desktop/KeywordSearchFilter.cpp | 64 -- src/ui/audio/desktop/KeywordSearchFilter.hpp | 54 -- src/ui/audio/desktop/PlayQueue.cpp | 634 ------------------ src/ui/audio/desktop/PlayQueue.hpp | 92 --- src/ui/audio/desktop/TableFilter.cpp | 282 -------- src/ui/audio/desktop/TableFilter.hpp | 112 ---- src/ui/audio/desktop/TrackView.cpp | 234 ------- src/ui/audio/desktop/TrackView.hpp | 80 --- src/ui/audio/mobile/ArtistSearch.cpp | 104 --- src/ui/audio/mobile/ArtistSearch.hpp | 59 -- src/ui/audio/mobile/ArtistSearchView.cpp | 61 -- src/ui/audio/mobile/ArtistView.cpp | 66 -- src/ui/audio/mobile/MobileAudio.cpp | 158 ----- src/ui/audio/mobile/MobilePlayQueue.cpp | 206 ------ src/ui/audio/mobile/MobilePlayQueue.hpp | 75 --- src/ui/audio/mobile/PreviewSearchView.cpp | 88 --- src/ui/audio/mobile/ReleaseSearch.cpp | 135 ---- src/ui/audio/mobile/ReleaseSearch.hpp | 58 -- src/ui/audio/mobile/ReleaseSearchView.cpp | 58 -- src/ui/audio/mobile/ReleaseView.cpp | 214 ------ src/ui/audio/mobile/ReleaseView.hpp | 66 -- src/ui/audio/mobile/SearchUtils.hpp | 32 - src/ui/audio/mobile/TrackSearch.cpp | 123 ---- src/ui/audio/mobile/TrackSearch.hpp | 59 -- src/ui/audio/mobile/TrackSearchView.cpp | 61 -- src/ui/settings/Settings.cpp | 110 --- src/ui/settings/SettingsAccountFormView.cpp | 285 -------- src/ui/settings/SettingsAccountFormView.hpp | 49 -- src/ui/settings/SettingsAudioFormView.cpp | 258 ------- src/ui/settings/SettingsAudioFormView.hpp | 49 -- src/ui/settings/SettingsDatabaseFormView.cpp | 400 ----------- src/ui/settings/SettingsDatabaseFormView.hpp | 58 -- .../SettingsFirstConnectionFormView.cpp | 229 ------- .../SettingsFirstConnectionFormView.hpp | 52 -- src/ui/settings/SettingsMediaDirectories.cpp | 172 ----- src/ui/settings/SettingsMediaDirectories.hpp | 59 -- .../SettingsMediaDirectoryFormView.cpp | 185 ----- .../SettingsMediaDirectoryFormView.hpp | 54 -- src/ui/settings/SettingsUserFormView.cpp | 411 ------------ src/ui/settings/SettingsUserFormView.hpp | 55 -- src/ui/settings/SettingsUsers.cpp | 202 ------ src/ui/settings/SettingsUsers.hpp | 52 -- src/ui/video/VideoDatabaseWidget.cpp | 143 ---- src/ui/video/VideoDatabaseWidget.hpp | 52 -- src/ui/video/VideoMediaPlayerWidget.cpp | 200 ------ src/ui/video/VideoMediaPlayerWidget.hpp | 85 --- src/ui/video/VideoParametersDialog.cpp | 143 ---- src/ui/video/VideoParametersDialog.hpp | 64 -- src/ui/video/VideoWidget.cpp | 91 --- src/ui/video/VideoWidget.hpp | 53 -- 98 files changed, 1602 insertions(+), 8515 deletions(-) create mode 100644 approot/artist.xml create mode 100644 approot/artists.xml create mode 100644 approot/messages.xml create mode 100644 approot/release.xml create mode 100644 approot/releases.xml create mode 100644 approot/tracks.xml create mode 100644 src/database/DbArtist.cpp create mode 100644 src/database/DbArtist.hpp create mode 100644 src/ui/ArtistView.cpp rename src/ui/{audio/mobile/TrackSearchView.hpp => ArtistView.hpp} (77%) create mode 100644 src/ui/ArtistsView.cpp rename src/ui/{audio/mobile/MobileAudio.hpp => ArtistsView.hpp} (71%) create mode 100644 src/ui/Explore.cpp rename src/ui/{audio/mobile/ArtistSearchView.hpp => Explore.hpp} (80%) rename src/ui/{audio/Audio.hpp => Filters.cpp} (57%) rename src/ui/{audio/mobile/ArtistView.hpp => Filters.hpp} (68%) rename src/ui/{audio/mobile/MobilePlayQueueEvents.hpp => HomeView.cpp} (71%) rename src/ui/{audio/mobile/ReleaseSearchView.hpp => HomeView.hpp} (80%) create mode 100644 src/ui/ReleaseView.cpp rename src/ui/{settings/Settings.hpp => ReleaseView.hpp} (81%) create mode 100644 src/ui/ReleasesView.cpp create mode 100644 src/ui/ReleasesView.hpp create mode 100644 src/ui/TracksView.cpp rename src/ui/{audio/mobile/PreviewSearchView.hpp => TracksView.hpp} (74%) delete mode 100644 src/ui/audio/AudioPlayer.cpp delete mode 100644 src/ui/audio/AudioPlayer.hpp delete mode 100644 src/ui/audio/desktop/DesktopAudio.cpp delete mode 100644 src/ui/audio/desktop/DesktopAudio.hpp delete mode 100644 src/ui/audio/desktop/Filter.hpp delete mode 100644 src/ui/audio/desktop/FilterChain.cpp delete mode 100644 src/ui/audio/desktop/FilterChain.hpp delete mode 100644 src/ui/audio/desktop/KeywordSearchFilter.cpp delete mode 100644 src/ui/audio/desktop/KeywordSearchFilter.hpp delete mode 100644 src/ui/audio/desktop/PlayQueue.cpp delete mode 100644 src/ui/audio/desktop/PlayQueue.hpp delete mode 100644 src/ui/audio/desktop/TableFilter.cpp delete mode 100644 src/ui/audio/desktop/TableFilter.hpp delete mode 100644 src/ui/audio/desktop/TrackView.cpp delete mode 100644 src/ui/audio/desktop/TrackView.hpp delete mode 100644 src/ui/audio/mobile/ArtistSearch.cpp delete mode 100644 src/ui/audio/mobile/ArtistSearch.hpp delete mode 100644 src/ui/audio/mobile/ArtistSearchView.cpp delete mode 100644 src/ui/audio/mobile/ArtistView.cpp delete mode 100644 src/ui/audio/mobile/MobileAudio.cpp delete mode 100644 src/ui/audio/mobile/MobilePlayQueue.cpp delete mode 100644 src/ui/audio/mobile/MobilePlayQueue.hpp delete mode 100644 src/ui/audio/mobile/PreviewSearchView.cpp delete mode 100644 src/ui/audio/mobile/ReleaseSearch.cpp delete mode 100644 src/ui/audio/mobile/ReleaseSearch.hpp delete mode 100644 src/ui/audio/mobile/ReleaseSearchView.cpp delete mode 100644 src/ui/audio/mobile/ReleaseView.cpp delete mode 100644 src/ui/audio/mobile/ReleaseView.hpp delete mode 100644 src/ui/audio/mobile/SearchUtils.hpp delete mode 100644 src/ui/audio/mobile/TrackSearch.cpp delete mode 100644 src/ui/audio/mobile/TrackSearch.hpp delete mode 100644 src/ui/audio/mobile/TrackSearchView.cpp delete mode 100644 src/ui/settings/Settings.cpp delete mode 100644 src/ui/settings/SettingsAccountFormView.cpp delete mode 100644 src/ui/settings/SettingsAccountFormView.hpp delete mode 100644 src/ui/settings/SettingsAudioFormView.cpp delete mode 100644 src/ui/settings/SettingsAudioFormView.hpp delete mode 100644 src/ui/settings/SettingsDatabaseFormView.cpp delete mode 100644 src/ui/settings/SettingsDatabaseFormView.hpp delete mode 100644 src/ui/settings/SettingsFirstConnectionFormView.cpp delete mode 100644 src/ui/settings/SettingsFirstConnectionFormView.hpp delete mode 100644 src/ui/settings/SettingsMediaDirectories.cpp delete mode 100644 src/ui/settings/SettingsMediaDirectories.hpp delete mode 100644 src/ui/settings/SettingsMediaDirectoryFormView.cpp delete mode 100644 src/ui/settings/SettingsMediaDirectoryFormView.hpp delete mode 100644 src/ui/settings/SettingsUserFormView.cpp delete mode 100644 src/ui/settings/SettingsUserFormView.hpp delete mode 100644 src/ui/settings/SettingsUsers.cpp delete mode 100644 src/ui/settings/SettingsUsers.hpp delete mode 100644 src/ui/video/VideoDatabaseWidget.cpp delete mode 100644 src/ui/video/VideoDatabaseWidget.hpp delete mode 100644 src/ui/video/VideoMediaPlayerWidget.cpp delete mode 100644 src/ui/video/VideoMediaPlayerWidget.hpp delete mode 100644 src/ui/video/VideoParametersDialog.cpp delete mode 100644 src/ui/video/VideoParametersDialog.hpp delete mode 100644 src/ui/video/VideoWidget.cpp delete mode 100644 src/ui/video/VideoWidget.hpp diff --git a/Makefile.am b/Makefile.am index a82a941d..ae035de4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -17,7 +17,13 @@ lms_images_DATA = \ docroot/images/unknown-artist.jpg lms_approot_DATA = \ - approot/templates.xml + approot/artists.xml \ + approot/artist.xml \ + approot/messages.xml \ + approot/releases.xml \ + approot/release.xml \ + approot/templates.xml \ + approot/tracks.xml sysconf_DATA = \ $(top_srcdir)/conf/lms.conf diff --git a/README.md b/README.md index d60a242d..8f985bb4 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,7 @@ LMS is a self-hosted media streaming software, released under the GPLv3 license. It allows you to access your music and your videos using an http(s) web interface. ## Features - - Winamp-like interface, suited for large media collections - - Audio/Video transcode for maximum interoperability and low bandwith requirements + - Audio transcode for maximum interoperability and low bandwith requirements - User management - Playlist support - MusicBrainzID support to handle duplicated artist and release names @@ -14,20 +13,13 @@ Tags are automatically created to facilitate music searching, using: - Multi genre information from metadata - High-level information from AcousticBrainz -Please note LMS is still under development and will gain more features in the future: - - Radio function - - Interface suited for mobile devices - - Video support - - Subsonic and/or Ampache API - LMS is written entirely in C++. Therefore, it is suitable to run on embedded devices, where space and memory are limited. -Please note some media files may require significant CPU usage to be transcoded. ## Dependencies ### Debian ```sh -$ apt-get install g++ autoconf automake libboost-dev libboost-locale-dev libboost-iostreams-dev libavcodec-dev libwtdbosqlite-dev libwthttp-dev libwtdbo-dev libwt-dev libmagick++-dev libavcodec-dev libavformat-dev libav-tools libpstreams-dev libcurl-dev libcurlpp-dev libconfig++-dev +$ apt-get install g++ autoconf automake libboost-dev libboost-locale-dev libboost-iostreams-dev libavcodec-dev libwtdbosqlite-dev libwthttp-dev libwtdbo-dev libwt-dev libmagick++-dev libavcodec-dev libavformat-dev libav-tools libpstreams-dev libcurl-dev libcurlpp-dev libconfig++-dev ffmpeg ``` ## Build diff --git a/approot/artist.xml b/approot/artist.xml new file mode 100644 index 00000000..a877c111 --- /dev/null +++ b/approot/artist.xml @@ -0,0 +1,25 @@ + + + + + + + ${releases class="list-group"} + + + +
+
+ ${cover class="media-object"} +
+
+

${name}

+
+ ${}${tr:msg-various-artists}${} +

${}${year}${}${} (${orig-year})${}

+
+
+ +
diff --git a/approot/artists.xml b/approot/artists.xml new file mode 100644 index 00000000..92696a37 --- /dev/null +++ b/approot/artists.xml @@ -0,0 +1,25 @@ + + + + + + +
+
+ + ${search} +
+
+ ${artists class="list-group"} +
+ + +
+ ${nb-release} + ${name} +
+
+ +
diff --git a/approot/messages.xml b/approot/messages.xml new file mode 100644 index 00000000..196a7296 --- /dev/null +++ b/approot/messages.xml @@ -0,0 +1,13 @@ + + + + +Various artists +Artists +Albums +Tracks +Playlist +Add filter +Search... + + diff --git a/approot/release.xml b/approot/release.xml new file mode 100644 index 00000000..e92c6289 --- /dev/null +++ b/approot/release.xml @@ -0,0 +1,25 @@ + + + + + + + ${cover} + + ${tracks} + + + + ${}${disc-number}-${}${}${track-number}. ${} + ${}${artist-name} - ${} + ${name} + ${play-btn}${add-btn} + + + + diff --git a/approot/releases.xml b/approot/releases.xml new file mode 100644 index 00000000..a7cca098 --- /dev/null +++ b/approot/releases.xml @@ -0,0 +1,31 @@ + + + + + + + +
+
+ + ${search} +
+
+ ${releases class="list-group"} +
+ + +
+
+ ${cover class="media-object"} +
+
+

${release-name}

+ ${artist-name} +
+
+
+ +
diff --git a/approot/templates.xml b/approot/templates.xml index 007c9824..9894b02e 100644 --- a/approot/templates.xml +++ b/approot/templates.xml @@ -2,15 +2,23 @@ - -
- ${collapse-button} - - ${contents} + + ${navbar-top class="main-nav"} + ${contents class="container"} + + + + ${tr:msg-home-welcome} + + + + ${filters} + ${contents} + + + +
+ ${add-filter}${filters}
diff --git a/approot/tracks.xml b/approot/tracks.xml new file mode 100644 index 00000000..047796e8 --- /dev/null +++ b/approot/tracks.xml @@ -0,0 +1,33 @@ + + + + + + + +
+
+ + ${search} +
+
+ ${tracks class="list-group"} +
+ + +
+
+ ${cover class="media-object"} +
+
+

${name}

+
+ ${}${release-name}${} + ${}${artist-name}${} + ${play-btn}${add-btn} +
+
+ +
diff --git a/docroot/css/lms.css b/docroot/css/lms.css index 814622af..9c47a5dc 100644 --- a/docroot/css/lms.css +++ b/docroot/css/lms.css @@ -1,14 +1,5 @@ .main-nav { margin-bottom: 0px; - box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.4); -} - -.Wt-hrh2 { - background-color: white; -} - -.Wt-vrh2 { - background-color: white; } .playqueue { @@ -198,26 +189,3 @@ box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.5) } -.release_truncated-name { - white-space: nowrap; - overflow: hidden; - overflow-x: hidden; - overflow-y: hidden; - text-overflow: ellipsis; -} - -.release_img-responsive { - display: block; - max-width: 100%; - height: auto; -} - -.release-search-cover { - width: 512px; - min-width: 512px; - height: 512px; - min-height: 512px; - display: flex; - justify-content: space-between; - align-items: center; -} diff --git a/src/Makefile.am b/src/Makefile.am index 6de2fd19..02bd8c3b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -5,7 +5,7 @@ lms_SOURCES = \ $(srcdir)/av/AvInfo.cpp \ $(srcdir)/av/AvTranscoder.cpp \ $(srcdir)/cover/CoverArtGrabber.cpp \ - $(srcdir)/database/Artist.cpp \ + $(srcdir)/database/DbArtist.cpp \ $(srcdir)/database/DatabaseHandler.cpp \ $(srcdir)/database/MediaDirectory.cpp \ $(srcdir)/database/Playlist.cpp \ @@ -24,41 +24,22 @@ lms_SOURCES = \ $(srcdir)/image/Image.cpp \ $(srcdir)/metadata/AvFormat.cpp \ $(srcdir)/metadata/TagLibParser.cpp \ + $(srcdir)/ui/ArtistView.cpp \ + $(srcdir)/ui/ArtistsView.cpp \ + $(srcdir)/ui/Explore.cpp \ + $(srcdir)/ui/Filters.cpp \ + $(srcdir)/ui/HomeView.cpp \ $(srcdir)/ui/LmsApplication.cpp \ + $(srcdir)/ui/ReleasesView.cpp \ + $(srcdir)/ui/ReleaseView.cpp \ + $(srcdir)/ui/TracksView.cpp \ $(srcdir)/ui/auth/LmsAuth.cpp \ - $(srcdir)/ui/audio/AudioPlayer.cpp \ - $(srcdir)/ui/audio/desktop/DesktopAudio.cpp \ - $(srcdir)/ui/audio/desktop/FilterChain.cpp \ - $(srcdir)/ui/audio/desktop/KeywordSearchFilter.cpp \ - $(srcdir)/ui/audio/desktop/PlayQueue.cpp \ - $(srcdir)/ui/audio/desktop/TableFilter.cpp \ - $(srcdir)/ui/audio/desktop/TrackView.cpp \ - $(srcdir)/ui/audio/mobile/ArtistSearch.cpp \ - $(srcdir)/ui/audio/mobile/ArtistSearchView.cpp \ - $(srcdir)/ui/audio/mobile/ArtistView.cpp \ - $(srcdir)/ui/audio/mobile/MobileAudio.cpp \ - $(srcdir)/ui/audio/mobile/MobilePlayQueue.cpp \ - $(srcdir)/ui/audio/mobile/PreviewSearchView.cpp \ - $(srcdir)/ui/audio/mobile/ReleaseSearch.cpp \ - $(srcdir)/ui/audio/mobile/ReleaseSearchView.cpp \ - $(srcdir)/ui/audio/mobile/ReleaseView.cpp \ - $(srcdir)/ui/audio/mobile/TrackSearch.cpp \ - $(srcdir)/ui/audio/mobile/TrackSearchView.cpp \ $(srcdir)/ui/common/DirectoryValidator.cpp \ $(srcdir)/ui/common/InputRange.cpp \ $(srcdir)/ui/common/LineEdit.cpp \ $(srcdir)/ui/resource/AvConvTranscodeStreamResource.cpp \ $(srcdir)/ui/resource/ImageResource.cpp \ $(srcdir)/ui/resource/TranscodeResource.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 \ $(srcdir)/utils/Config.cpp \ $(srcdir)/utils/Logger.cpp \ $(srcdir)/utils/Path.cpp \ diff --git a/src/cover/CoverArtGrabber.cpp b/src/cover/CoverArtGrabber.cpp index a43f3626..a73a502c 100644 --- a/src/cover/CoverArtGrabber.cpp +++ b/src/cover/CoverArtGrabber.cpp @@ -178,11 +178,7 @@ Grabber::getFromRelease(Wt::Dbo::Session& session, Database::Release::id_type re if (!release || release->isNone()) return std::vector(); - // Get the first track of the release - std::vector tracks = Track::getByFilter(session, - SearchFilter::ById(SearchFilter::Field::Release, releaseId), - -1, 1 /* limit result size */); - + std::vector tracks = release->getTracks(); if (tracks.empty()) return std::vector(); diff --git a/src/database/DatabaseHandler.cpp b/src/database/DatabaseHandler.cpp index c159d378..1aedb3b8 100644 --- a/src/database/DatabaseHandler.cpp +++ b/src/database/DatabaseHandler.cpp @@ -187,7 +187,7 @@ Handler::createConnectionPool(boost::filesystem::path p) connection->executeSql("pragma journal_mode=WAL"); - // connection->setProperty("show-queries", "true"); + connection->setProperty("show-queries", "true"); return new Wt::Dbo::FixedSqlConnectionPool(connection, 1); } diff --git a/src/database/DatabaseHandler.hpp b/src/database/DatabaseHandler.hpp index 38978325..6b07984e 100644 --- a/src/database/DatabaseHandler.hpp +++ b/src/database/DatabaseHandler.hpp @@ -33,6 +33,7 @@ namespace Database { +typedef Wt::Dbo::dbo_default_traits::IdType id_type; typedef Wt::Auth::Dbo::UserDatabase UserDatabase; // Session living class handling the database and the login diff --git a/src/database/DbArtist.cpp b/src/database/DbArtist.cpp new file mode 100644 index 00000000..7dc341f6 --- /dev/null +++ b/src/database/DbArtist.cpp @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2015 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 . + */ + +#include "Types.hpp" +#include "SqlQuery.hpp" + +#include "utils/Logger.hpp" + +namespace Database +{ + +Artist::Artist(const std::string& name, const std::string& MBID) +: _name(std::string(name, 0 , _maxNameLength)), +_MBID(MBID) +{ + +} + +std::vector +Artist::getByName(Wt::Dbo::Session& session, const std::string& name) +{ + Wt::Dbo::collection res = session.find().where("name = ?").bind( std::string(name, 0, _maxNameLength) ); + return std::vector(res.begin(), res.end()); +} + +Artist::pointer +Artist::getByMBID(Wt::Dbo::Session& session, const std::string& mbid) +{ + return session.find().where("mbid = ?").bind(mbid); +} + +Artist::pointer +Artist::getById(Wt::Dbo::Session& session, Artist::id_type id) +{ + return session.find().where("id = ?").bind(id); +} + +Artist::pointer +Artist::create(Wt::Dbo::Session& session, const std::string& name, const std::string& MBID) +{ + return session.add(new Artist(name, MBID)); +} + +Artist::pointer +Artist::getNone(Wt::Dbo::Session& session) +{ + std::vector res = getByName(session, ""); + if (res.empty()) + return create(session, ""); + + return res.front(); +} + +bool +Artist::isNone() const +{ + return _name == ""; +} + +std::vector +Artist::getAll(Wt::Dbo::Session& session, int offset, int size) +{ + Wt::Dbo::collection res = session.find().orderBy("LOWER(name)").offset(offset).limit(size); + return std::vector(res.begin(), res.end()); +} + +std::vector +Artist::getAllOrphans(Wt::Dbo::Session& session) +{ + Wt::Dbo::collection res = session.query< Wt::Dbo::ptr >("SELECT DISTINCT a FROM artist a LEFT OUTER JOIN Track t ON a.id = t.artist_id WHERE t.id IS NULL"); + + return std::vector(res.begin(), res.end()); +} + +static +Wt::Dbo::Query +getQuery(Wt::Dbo::Session& session, + const std::vector& clusterIds, + const std::vector& keywords) +{ + WhereClause where; + + std::ostringstream oss; + oss << "SELECT DISTINCT a FROM artist a"; + + for (auto keyword : keywords) + where.And(WhereClause("a.name LIKE ?")).bind("%%" + keyword + "%%"); + + if (!clusterIds.empty()) + { + oss << " INNER JOIN track t ON t.artist_id = a.id INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id"; + + WhereClause clusterClause; + + for (auto id : clusterIds) + clusterClause.Or(WhereClause("c.id = ?")).bind(std::to_string(id)); + + where.And(clusterClause); + } + oss << " " << where.get(); + + if (!clusterIds.empty()) + oss << " GROUP BY t.id HAVING COUNT(*) = " << clusterIds.size(); + + oss << " ORDER BY a.name"; + + Wt::Dbo::Query query = session.query( oss.str() ); + + for (const std::string& bindArg : where.getBindArgs()) + { + query.bind(bindArg); + } + + return query; +} + +std::vector +Artist::getByFilter(Wt::Dbo::Session& session, + const std::vector& clusters, + const std::vector keywords, + int offset, int size, bool& moreResults) +{ + Wt::Dbo::collection collection = getQuery(session, clusters, keywords).limit(size).offset(offset); + + auto res = std::vector(collection.begin(), collection.end()); + + if (size != -1 && res.size() == static_cast(size) + 1) + { + moreResults = true; + res.pop_back(); + } + else + moreResults = false; + + return res; +} + +std::vector > +Artist::getReleases(const std::vector& clusterIds) const +{ + assert(self()); + assert(self()->id() != Wt::Dbo::dbo_traits::invalidId() ); + assert(session()); + + WhereClause where; + + std::ostringstream oss; + oss << "SELECT DISTINCT r FROM release r INNER JOIN artist a ON t.artist_id = a.id INNER JOIN track t ON t.release_id = r.id"; + + if (!clusterIds.empty()) + { + oss << " INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id"; + + WhereClause clusterClause; + + for (auto id : clusterIds) + clusterClause.Or(WhereClause("c.id = ?")).bind(std::to_string(id)); + + where.And(clusterClause); + } + + where.And(WhereClause("a.id = ?")).bind(std::to_string(id())); + + oss << " " << where.get(); + + if (!clusterIds.empty()) + oss << " GROUP BY t.id HAVING COUNT(*) = " << clusterIds.size(); + + // TODO order + oss << " ORDER BY t.date,r.name"; + + Wt::Dbo::Query query = session()->query( oss.str() ); + + for (const std::string& bindArg : where.getBindArgs()) + { + query.bind(bindArg); + } + + Wt::Dbo::collection< Wt::Dbo::ptr > res = query; + + return std::vector< Wt::Dbo::ptr > (res.begin(), res.end()); +} + + + +} // namespace Database diff --git a/src/database/DbArtist.hpp b/src/database/DbArtist.hpp new file mode 100644 index 00000000..7b45bcb6 --- /dev/null +++ b/src/database/DbArtist.hpp @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2015 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 . + */ + +#ifndef _DB_ARTIST_HPP_ +#define _DB_ARTIST_HPP_ + +#include +#include + +#include +#include + +#include "SearchFilter.hpp" + +namespace Database +{ + +class Track; +class Cluster; +class Release; + +class Artist : public Wt::Dbo::Dbo +{ + public: + + typedef Wt::Dbo::ptr pointer; + typedef Wt::Dbo::dbo_traits::IdType id_type; + + Artist() {} + Artist(const std::string& name, const std::string& MBID = ""); + + // Accessors + static pointer getByMBID(Wt::Dbo::Session& session, const std::string& MBID); + static pointer getById(Wt::Dbo::Session& session, id_type id); + static pointer getNone(Wt::Dbo::Session& session); // Special entry + static std::vector getByName(Wt::Dbo::Session& session, const std::string& name); + static std::vector getByFilter(Wt::Dbo::Session& session, + const std::vector& clusters, // at least one track that belongs to these clusters + const std::vector keywords, // name must match all of these keywords + int offset, + int size, + bool& moreExpected); + + static std::vector getAll(Wt::Dbo::Session& session, int offset = -1, int size = -1); + static std::vector getAllOrphans(Wt::Dbo::Session& session); // No track related + + // Accessors + std::string getName(void) const { return _name; } + std::string getMBID(void) const { return _MBID; } + + // Get the releases that have at least one track for this artist + belongs to optonal cluster filters + std::vector> getReleases(const std::vector& clusterIds = std::vector()) const; + + void setMBID(std::string mbid) { _MBID = mbid; } + + // Create + static pointer create(Wt::Dbo::Session& session, const std::string& name, const std::string& MBID = ""); + + bool isNone(void) const; + + template + void persist(Action& a) + { + Wt::Dbo::field(a, _name, "name"); + Wt::Dbo::field(a, _MBID, "mbid"); + + Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToOne, "artist"); + } + + private: + + static const std::size_t _maxNameLength = 128; + + std::string _name; + std::string _MBID; // Musicbrainz Identifier + + Wt::Dbo::collection> _tracks; // Tracks of this artist +}; + +} // namespace Database + +#endif diff --git a/src/database/Release.cpp b/src/database/Release.cpp index d82cbcef..ca9e1fb5 100644 --- a/src/database/Release.cpp +++ b/src/database/Release.cpp @@ -82,68 +82,61 @@ Release::getAll(Wt::Dbo::Session& session, int offset, int size) std::vector Release::getAllOrphans(Wt::Dbo::Session& session) { - Wt::Dbo::collection res = session.query< Wt::Dbo::ptr >("select r from release r LEFT OUTER JOIN Track t ON r.id = t.release_id WHERE t.id IS NULL"); + Wt::Dbo::collection res = session.query>("select r from release r LEFT OUTER JOIN Track t ON r.id = t.release_id WHERE t.id IS NULL"); return std::vector(res.begin(), res.end()); } +static Wt::Dbo::Query -Release::getQuery(Wt::Dbo::Session& session, SearchFilter filter) +getQuery(Wt::Dbo::Session& session, + const std::vector& clusterIds, + const std::vector keywords) { - SqlQuery sqlQuery = generatePartialQuery(filter); + WhereClause where; - Wt::Dbo::Query query - = session.query("SELECT r FROM release r INNER JOIN artist a ON a.id = t.artist_id INNER JOIN track t ON t.release_id = r.id INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id " + sqlQuery.where().get()).groupBy("r.id").orderBy("r.name"); + std::ostringstream oss; + oss << "SELECT DISTINCT r FROM release r"; - for (const std::string& bindArg : sqlQuery.where().getBindArgs()) - query.bind(bindArg); + for (auto keyword : keywords) + where.And(WhereClause("r.name LIKE ?")).bind("%%" + keyword + "%%"); - return query; -} - -Wt::Dbo::Query -Release::getUIQuery(Wt::Dbo::Session& session, SearchFilter filter) -{ - SqlQuery sqlQuery = generatePartialQuery(filter); - - // TODO DATE of RELEASE - Wt::Dbo::Query query - = session.query("SELECT r.id, r.name, t.date, COUNT(DISTINCT t.id) FROM release r INNER JOIN track t ON t.release_id = r.id INNER JOIN artist a ON a.id = t.artist_id INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id " + sqlQuery.where().get()).groupBy("r.id").orderBy("r.name"); - - for (const std::string& bindArg : sqlQuery.where().getBindArgs()) - query.bind(bindArg); - - return query; -} - -void -Release::updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel& model, SearchFilter filter, const std::vector& columnNames) -{ - Wt::Dbo::Query query = getUIQuery(session, filter); - - model.setQuery(query, columnNames.empty() ? true : false); - - // TODO do something better - if (columnNames.size() == 3) + if (!clusterIds.empty()) { - model.addColumn( "r.name", columnNames[0]); - model.addColumn( "t.date", columnNames[1]); - model.addColumn( "COUNT(DISTINCT t.id)", columnNames[2]); + oss << " INNER JOIN track t ON t.release_id = r.id INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id"; + + WhereClause clusterClause; + + for (auto id : clusterIds) + clusterClause.And(WhereClause("c.id = ?")).bind(std::to_string(id)); + + where.And(clusterClause); } + + oss << " " << where.get(); + + if (!clusterIds.empty()) + oss << " GROUP BY t.id HAVING COUNT(*) = " << clusterIds.size(); + + oss << " ORDER BY r.name"; + + Wt::Dbo::Query query = session.query( oss.str() ); + + for (const std::string& bindArg : where.getBindArgs()) + query.bind(bindArg); + + return query; } std::vector -Release::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size) +Release::getByFilter(Wt::Dbo::Session& session, + const std::vector& clusterIds, + const std::vector keywords, + int offset, int size, bool& moreResults) { - Wt::Dbo::collection res = getQuery(session, filter).limit(size).offset(offset); + Wt::Dbo::collection collection = getQuery(session, clusterIds, keywords).limit(size).offset(offset); - return std::vector(res.begin(), res.end()); -} - -std::vector -Release::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size, bool& moreResults) -{ - auto res = getByFilter(session, filter, offset, size + 1); + auto res = std::vector(collection.begin(), collection.end()); if (size != -1 && res.size() == static_cast(size) + 1) { @@ -156,28 +149,63 @@ Release::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, return res; } -int +boost::optional Release::getReleaseYear(bool original) const { assert(session()); - // TODO something better - auto tracks = Track::getByFilter(*session(), SearchFilter::ById(SearchFilter::Field::Release, this->id()), -1, 1); + Wt::Dbo::collection times = session()->query( + std::string("SELECT ") + (original ? "t.original_date" : "t.date") + " FROM track t INNER JOIN release r ON r.id = t.release_id") + .where("r.id = ?") + .groupBy("t.date") + .bind(this->id()); - if (tracks.empty()) - return 0; + /* various dates, no date */ + if (times.empty() || times.size() > 1) + return boost::none; - boost::gregorian::date date; - - if (original) - date = tracks.front()->getOriginalDate().date(); - else - date = tracks.front()->getDate().date(); + boost::gregorian::date date = times.front().date(); if (date.is_special()) - return 0; + return boost::none; - return date.year(); + return boost::make_optional(date.year()); } +std::vector> +Release::getArtists() const +{ + assert(self()); + assert(self()->id() != Wt::Dbo::dbo_traits::invalidId() ); + assert(session()); + + Wt::Dbo::collection> res = session()->query>( + "SELECT DISTINCT a FROM artist a INNER JOIN release r ON t.artist_id = a.id INNER JOIN track t ON t.release_id = r.id") + .where("r.id = ?") + .bind(id()); + + return std::vector>(res.begin(), res.end()); +} + +bool +Release::hasVariousArtists() const +{ + // TODO optimize + return getArtists().size() > 1; +} + +std::vector> +Release::getTracks() const +{ + assert(self()); + assert(session()); + + Wt::Dbo::collection> res = session()->query>( + "SELECT t FROM track t INNER JOIN release r ON t.release_id = r.id") + .where("r.id = ?") + .orderBy("t.disc_number,t.track_number") + .bind(id()); + + return std::vector>(res.begin(), res.end()); +} } // namespace Database diff --git a/src/database/Release.hpp b/src/database/Release.hpp index f92b6c0c..fc6fedcb 100644 --- a/src/database/Release.hpp +++ b/src/database/Release.hpp @@ -17,11 +17,11 @@ * along with LMS. If not, see . */ -#ifndef _DB_RELEASE_HPP_ -#define _DB_RELEASE_HPP_ +#pragma once + +#include #include -#include #include "SearchFilter.hpp" @@ -30,6 +30,7 @@ namespace Database class Track; class Release; +class Artist; class Release : public Wt::Dbo::Dbo { @@ -46,30 +47,34 @@ class Release : public Wt::Dbo::Dbo static std::vector 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); // Special entry - static std::vector getAllOrphans(Wt::Dbo::Session& session); + static std::vector getAllOrphans(Wt::Dbo::Session& session); // no track related static std::vector getAll(Wt::Dbo::Session& session, int offset, int size); - static std::vector getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1); - static std::vector getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size, bool& moreExpected); + + static std::vector getByFilter(Wt::Dbo::Session& session, + const std::vector& clusters, // at least one track that belongs to these clusters + const std::vector keywords, // name must match all of these keywords + int offset, + int size, + bool& moreExpected); + + std::vector> getTracks() const; // Create static pointer create(Wt::Dbo::Session& session, const std::string& name, const std::string& MBID = ""); // Utility functions - int getReleaseYear(bool originalDate = false) const; // 0 if unknown or various + boost::optional getReleaseYear(bool originalDate = false) const; // 0 if unknown or various - // MVC models for the user interface - // ID, Release name, year, track counts - typedef boost::tuple UIQueryResult; - static Wt::Dbo::Query getUIQuery(Wt::Dbo::Session& session, SearchFilter filter); - static void updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel< UIQueryResult >& model, SearchFilter filter, const std::vector& columnNames = std::vector()); - - - // Accessosrs + // Accessors std::string getName() const { return _name; } std::string getMBID() const { return _MBID; } bool isNone(void) const; boost::posix_time::time_duration getDuration(void) const; + // Get the artists of this release + std::vector > getArtists() const; + bool hasVariousArtists() const; + void setMBID(std::string mbid) { _MBID = mbid; } template @@ -82,8 +87,6 @@ class Release : public Wt::Dbo::Dbo } private: - static Wt::Dbo::Query getQuery(Wt::Dbo::Session& session, SearchFilter filter); - static const std::size_t _maxNameLength = 128; std::string _name; @@ -94,5 +97,4 @@ class Release : public Wt::Dbo::Dbo } // namespace Database -#endif diff --git a/src/database/SearchFilter.cpp b/src/database/SearchFilter.cpp index 194296e4..5fb23e94 100644 --- a/src/database/SearchFilter.cpp +++ b/src/database/SearchFilter.cpp @@ -24,98 +24,15 @@ namespace Database { -static std::ostream& operator<<(std::ostream& ost, const std::vector< Wt::Dbo::dbo_default_traits::IdType>& ids) +void +SearchFilter::operator+=(const SearchFilter& filter) { - const char *sep = ""; - - for (auto id : ids) - { - ost << sep << std::to_string(id); - sep = ","; - } - - return ost; } -SqlQuery generatePartialQuery(SearchFilter& filter) +SqlQuery +SearchFilter::generatePartialQuery() { SqlQuery sqlQuery; - - // Process name like parameters - for (auto nameLikeMatches : filter.nameLikeMatch) - { - WhereClause likeWhereClause; - - for (auto nameLikeMatch : nameLikeMatches) - { - switch (nameLikeMatch.first) - { - case SearchFilter::Field::Artist: - for (const std::string& name : nameLikeMatch.second) - likeWhereClause.Or( WhereClause("a.name LIKE ?") ).bind("%%" + name + "%%"); - break; - - case SearchFilter::Field::Release: - for (const std::string& name : nameLikeMatch.second) - likeWhereClause.Or( WhereClause("r.name LIKE ?") ).bind("%%" + name + "%%"); - break; - - case SearchFilter::Field::Cluster: - for (const std::string& name : nameLikeMatch.second) - likeWhereClause.Or( WhereClause("c.name LIKE ?") ).bind("%%" + name + "%%"); - break; - - case SearchFilter::Field::Track: - for (const std::string& name : nameLikeMatch.second) - likeWhereClause.Or( WhereClause("t.name LIKE ?") ).bind("%%" + name + "%%"); - break; - } - } - sqlQuery.where().And( likeWhereClause ); - } - - // Process id exact match parameters - // Id list may be long, we do not bind the parameters - // Safe since we already known these are just ints - for (auto idMatch : filter.idMatch) - { - WhereClause idWhereClause; - - switch (idMatch.first) - { - case SearchFilter::Field::Artist: - { - std::ostringstream oss; - oss << "a.id IN (" << idMatch.second << ")"; - idWhereClause.Or(oss.str()); - } - break; - case SearchFilter::Field::Release: - { - std::ostringstream oss; - oss << "r.id IN (" << idMatch.second << ")"; - idWhereClause.Or(oss.str()); - } - break; - case SearchFilter::Field::Cluster: - { - std::ostringstream oss; - oss << "c.id IN (" << idMatch.second << ")"; - idWhereClause.Or(oss.str()); - } - break; - case SearchFilter::Field::Track: - { - std::ostringstream oss; - oss << "t.id IN (" << idMatch.second << ")"; - idWhereClause.Or(oss.str()); - } - break; - } - - sqlQuery.where().And( idWhereClause ); - } - return sqlQuery; } diff --git a/src/database/SearchFilter.hpp b/src/database/SearchFilter.hpp index 2156ecfe..01bbcb06 100644 --- a/src/database/SearchFilter.hpp +++ b/src/database/SearchFilter.hpp @@ -31,71 +31,37 @@ namespace Database { +typedef Wt::Dbo::dbo_default_traits::IdType id_type; + class SearchFilter { public: - enum class Field { - Artist, // artist - Release, // release - Track, // track - Cluster, // cluster - }; - - typedef std::vector > > NameLikeMatchType; - typedef std::map > IdMatchType; + typedef int id_type; SearchFilter() {} - // Helpers - static SearchFilter IdMatch( const IdMatchType& _idMatch ) - { - return SearchFilter(_idMatch); - } + static SearchFilter Artist(std::string name) {return SearchFilter();} + static SearchFilter Artist(id_type id) {return SearchFilter();} - static SearchFilter NameLikeMatch( const NameLikeMatchType& _nameLikeMatch ) - { - return SearchFilter(_nameLikeMatch); - } + static SearchFilter Release(std::string name) {return SearchFilter();} + static SearchFilter Release(id_type id) {return SearchFilter();} - // Single Field ID match - static SearchFilter ById(Field field, Wt::Dbo::dbo_default_traits::IdType id) - { - return SearchFilter({{field, {id} }}); - } + static SearchFilter Track(std::string name) {return SearchFilter();} - // Single Field Name match OR - static SearchFilter ByNameOr(Field field, std::vector keywords) - { - return NameLikeMatch({{{field, keywords}}}); - } + static SearchFilter Cluster(id_type id) {return SearchFilter();} - // Single Field Name match AND - static SearchFilter ByNameAnd(Field field, std::vector keywords) - { - NameLikeMatchType nameLikeMatch; + // Combine search filters by add operation + // Caution: multiple filters on different artist/release/track + // values may lead to empty results + void operator+=(const SearchFilter& filter); - for (auto keyword : keywords) - nameLikeMatch.push_back({{{field, {keyword}}}}); - - return NameLikeMatch(nameLikeMatch); - } - - // The filter is a AND of the following conditions: - - // ((Field1.name LIKE STR1-1 OR Field1.name LIKE STR1-2 ...) OR (Field2.name LIKE STR2-1 OR Field2.name LIKE STR2-2 ...) ... - NameLikeMatchType nameLikeMatch; - - // (Field1.id IN (ID1-1,ID1-2 ... ) AND (Field2.id IN (ID2-1,ID2-2 ... ) ... - IdMatchType idMatch; + SqlQuery generatePartialQuery(); private: - SearchFilter(const NameLikeMatchType& _nameLikeMatch) : nameLikeMatch(_nameLikeMatch) {} - SearchFilter(const IdMatchType& _idMatch) : idMatch(_idMatch) {} }; -SqlQuery generatePartialQuery(SearchFilter& filter); } // namespace Database diff --git a/src/database/Track.cpp b/src/database/Track.cpp index cfb3f8ea..ac7faa18 100644 --- a/src/database/Track.cpp +++ b/src/database/Track.cpp @@ -108,29 +108,42 @@ Track::getClusters(void) const return clusters; } +static Wt::Dbo::Query< Track::pointer > -Track::getQuery(Wt::Dbo::Session& session, SearchFilter filter) +getQuery(Wt::Dbo::Session& session, + const std::vector& clusterIds, + const std::vector keywords) { - SqlQuery sqlQuery = generatePartialQuery(filter); + WhereClause where; - Wt::Dbo::Query query - = session.query( "SELECT t FROM track t INNER JOIN artist a ON t.artist_id = a.id INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN release r ON r.id = t.release_id " + sqlQuery.where().get()).groupBy("t.id").orderBy("a.name,t.date,r.name,t.disc_number,t.track_number"); + std::ostringstream oss; + oss << "SELECT t FROM track t"; - for (const std::string& bindArg : sqlQuery.where().getBindArgs()) - query.bind(bindArg); + for (auto keyword : keywords) + where.And(WhereClause("t.name LIKE ?")).bind("%%" + keyword + "%%"); - return query; -} + if (!clusterIds.empty()) + { + oss << " INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id"; -Wt::Dbo::Query< Track::UIQueryResult > -Track::getUIQuery(Wt::Dbo::Session& session, SearchFilter filter) -{ - SqlQuery sqlQuery = generatePartialQuery(filter); + WhereClause clusterClause; - Wt::Dbo::Query query - = session.query( "SELECT t.id, a.name, r.name, t.disc_number, t.track_number, t.name, t.duration, t.date, t.original_date, t.genre_list FROM track t INNER JOIN artist a ON t.artist_id = a.id INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN release r ON r.id = t.release_id " + sqlQuery.where().get()).groupBy("t.id").orderBy("a.name,t.date,r.name,t.disc_number,t.track_number"); + for (auto id : clusterIds) + clusterClause.And(WhereClause("c.id = ?")).bind(std::to_string(id)); - for (const std::string& bindArg : sqlQuery.where().getBindArgs()) + where.And(clusterClause); + } + + oss << " " << where.get(); + + if (!clusterIds.empty()) + oss << " GROUP BY t.id HAVING COUNT(*) = " << clusterIds.size(); + + oss << " ORDER BY t.name"; + + Wt::Dbo::Query query = session.query( oss.str() ); + + for (const std::string& bindArg : where.getBindArgs()) query.bind(bindArg); return query; @@ -139,7 +152,7 @@ Track::getUIQuery(Wt::Dbo::Session& session, SearchFilter filter) Track::StatsQueryResult Track::getStats(Wt::Dbo::Session& session, SearchFilter filter) { - SqlQuery sqlQuery = generatePartialQuery(filter); + SqlQuery sqlQuery = filter.generatePartialQuery(); Wt::Dbo::Query query = session.query( "SELECT COUNT(\"id\"), SUM(\"dur\") FROM (SELECT t.id as \"id\", t.duration as \"dur\" FROM track t INNER JOIN artist a ON t.artist_id = a.id INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN release r ON r.id = t.release_id " + sqlQuery.where().get() + " GROUP BY t.id)"); @@ -151,17 +164,14 @@ Track::getStats(Wt::Dbo::Session& session, SearchFilter filter) std::vector -Track::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size) +Track::getByFilter(Wt::Dbo::Session& session, + const std::vector& clusterIds, + const std::vector keywords, + int offset, int size, bool& moreResults) { - Wt::Dbo::collection res = getQuery(session, filter).limit(size).offset(offset); + Wt::Dbo::collection collection = getQuery(session, clusterIds, keywords).limit(size).offset(offset); - return std::vector(res.begin(), res.end()); -} - -std::vector -Track::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size, bool& moreResults) -{ - auto res = getByFilter(session, filter, offset, size + 1); + auto res = std::vector(collection.begin(), collection.end()); if (size != -1 && res.size() == static_cast(size) + 1) { @@ -174,26 +184,28 @@ Track::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, i return res; } -void -Track::updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel< UIQueryResult >& model, SearchFilter filter, const std::vector& columnNames) +boost::optional +Track::getTrackNumber(void) const { - Wt::Dbo::Query< UIQueryResult > query = getUIQuery(session, filter); - model.setQuery(query, columnNames.empty() ? true : false); + return (_trackNumber > 0) ? boost::make_optional(_trackNumber) : boost::none; +} - // TODO do something better - if (columnNames.size() == 9) - { - model.addColumn( "a.name", columnNames[0] ); - model.addColumn( "r.name", columnNames[1] ); - model.addColumn( "t.disc_number", columnNames[2] ); - model.addColumn( "t.track_number", columnNames[3] ); - model.addColumn( "t.name", columnNames[4] ); - model.addColumn( "t.duration", columnNames[5] ); - model.addColumn( "t.date", columnNames[6] ); - model.addColumn( "t.original_date", columnNames[7] ); - model.addColumn( "t.genre_list", columnNames[8] ); - } +boost::optional +Track::getTotalTrackNumber(void) const +{ + return (_totalTrackNumber > 0) ? boost::make_optional(_totalTrackNumber) : boost::none; +} +boost::optional +Track::getDiscNumber(void) const +{ + return (_discNumber > 0) ? boost::make_optional(_discNumber) : boost::none; +} + +boost::optional +Track::getTotalDiscNumber(void) const +{ + return (_totalDiscNumber > 0) ? boost::make_optional(_totalDiscNumber) : boost::none; } @@ -221,6 +233,13 @@ Cluster::get(Wt::Dbo::Session& session, std::string type, std::string name) return session.find().where("type = ?").where("name = ?").bind( std::string(type, 0, _maxTypeLength)).bind( std::string(name, 0, _maxNameLength)); } +std::vector +Cluster::getByType(Wt::Dbo::Session& session, std::string type) +{ + Wt::Dbo::collection res = session.find().where("type = ?").bind( std::string(type, 0, _maxTypeLength)).orderBy("name"); + return std::vector(res.begin(), res.end()); +} + Cluster::pointer Cluster::getNone(Wt::Dbo::Session& session) { @@ -252,7 +271,7 @@ Cluster::remove(Wt::Dbo::Session& session, std::string type) Wt::Dbo::Query Cluster::getQuery(Wt::Dbo::Session& session, SearchFilter filter) { - SqlQuery sqlQuery = generatePartialQuery(filter); + SqlQuery sqlQuery = filter.generatePartialQuery(); Wt::Dbo::Query query = session.query( "SELECT g FROM cluster c INNER JOIN track_cluster t_c ON t_c.cluster_id = c.id INNER JOIN artist a ON t.artist_id = a.id INNER JOIN release r ON r.id = t.release_id INNER JOIN track t ON t.id = t_c.track_id " + sqlQuery.where().get()).groupBy("c.name").orderBy("c.name"); @@ -263,34 +282,6 @@ Cluster::getQuery(Wt::Dbo::Session& session, SearchFilter filter) return query; } -Wt::Dbo::Query -Cluster::getUIQuery(Wt::Dbo::Session& session, SearchFilter filter) -{ - SqlQuery sqlQuery = generatePartialQuery(filter); - - Wt::Dbo::Query query - = session.query( "SELECT c.id, c.name, COUNT(DISTINCT t.id) FROM cluster c INNER JOIN track_cluster t_c ON t_c.cluster_id = c.id INNER JOIN artist a ON t.artist_id = a.id INNER JOIN release r ON r.id = t.release_id INNER JOIN track t ON t.id = t_c.track_id " + sqlQuery.where().get()).groupBy("c.name").orderBy("c.name"); - - for (const std::string& bindArg : sqlQuery.where().getBindArgs()) - query.bind(bindArg); - - return query; -} - -void -Cluster::updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel& model, SearchFilter filter, const std::vector& columnNames) -{ - Wt::Dbo::Query query = getUIQuery(session, filter); - model.setQuery(query, columnNames.empty() ? true : false); - - // TODO do something better - if (columnNames.size() == 2) - { - model.addColumn( "c.name", columnNames[0] ); - model.addColumn( "COUNT(DISTINCT t.id)", columnNames[1] ); - } -} - std::vector Cluster::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size) { diff --git a/src/database/Track.hpp b/src/database/Track.hpp index 3d523814..d1af053c 100644 --- a/src/database/Track.hpp +++ b/src/database/Track.hpp @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -44,6 +45,12 @@ class Cluster { public: + enum class Type + { + Genre = 1, + Mood = 2, + }; + typedef Wt::Dbo::ptr pointer; typedef Wt::Dbo::dbo_traits::IdType id_type; @@ -57,12 +64,6 @@ class Cluster static Wt::Dbo::collection getAll(Wt::Dbo::Session& session); static std::vector getByType(Wt::Dbo::Session& session, std::string type); - // MVC models for the user interface - // ClusterID, type, name, track count - typedef boost::tuple UIQueryResult; - static Wt::Dbo::Query getUIQuery(Wt::Dbo::Session& session, SearchFilter filter); - static void updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel& model, SearchFilter filter, const std::vector& columnNames = std::vector()); - // Create utility static pointer create(Wt::Dbo::Session& session, std::string type, std::string name); @@ -118,8 +119,13 @@ class Track static pointer getByPath(Wt::Dbo::Session& session, const boost::filesystem::path& p); static pointer getById(Wt::Dbo::Session& session, id_type id); static pointer getByMBID(Wt::Dbo::Session& session, const std::string& MBID); - static std::vector getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1); - static std::vector getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size, bool &moreResults); + static std::vector getByFilter(Wt::Dbo::Session& session, + const std::vector& clusters, // tracks that belong to these clusters + const std::vector keywords, // name must match all of these keywords + int offset, + int size, + bool& moreExpected); + static Wt::Dbo::collection< pointer > getAll(Wt::Dbo::Session& session); static std::vector getAllIds(Wt::Dbo::Session& session); // nested transaction static std::vector getAllPaths(Wt::Dbo::Session& session); // nested transaction @@ -127,22 +133,6 @@ class Track static std::vector getChecksumDuplicates(Wt::Dbo::Session& session); // Utility fonctions - // MVC models for the user interface - // ID, Artist name, Release Name, DiscNumber, TrackNumber, Name, duration, date, original date, genre list - typedef boost::tuple // genre list - UIQueryResult; - static Wt::Dbo::Query< UIQueryResult > getUIQuery(Wt::Dbo::Session& session, SearchFilter filter); - static void updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel< UIQueryResult >& model, SearchFilter filter, const std::vector& columnNames = std::vector()); - // Stats for a given search filter typedef boost::tuple< int, // Total tracks @@ -174,10 +164,10 @@ class Track void setArtist(Wt::Dbo::ptr artist) { _artist = artist; } void setRelease(Wt::Dbo::ptr release) { _release = release; } - int getTrackNumber(void) const { return _trackNumber; } - int getTotalTrackNumber(void) const { return _totalTrackNumber; } - int getDiscNumber(void) const { return _discNumber; } - int getTotalDiscNumber(void) const { return _totalDiscNumber; } + boost::optional getTrackNumber(void) const; + boost::optional getTotalTrackNumber(void) const; + boost::optional getDiscNumber(void) const; + boost::optional getTotalDiscNumber(void) const; std::string getName(void) const { return _name; } boost::filesystem::path getPath(void) const { return _filePath; } boost::posix_time::time_duration getDuration(void) const { return _duration; } @@ -218,8 +208,6 @@ class Track private: - static Wt::Dbo::Query< pointer > getQuery(Wt::Dbo::Session& session, SearchFilter filter); - static const std::size_t _maxNameLength = 128; int _trackNumber; diff --git a/src/database/Types.hpp b/src/database/Types.hpp index a04b978b..681e4c97 100644 --- a/src/database/Types.hpp +++ b/src/database/Types.hpp @@ -19,7 +19,7 @@ // header file aimed to ease database class declarations -#include "Artist.hpp" +#include "DbArtist.hpp" #include "Track.hpp" #include "Playlist.hpp" #include "Release.hpp" diff --git a/src/ui/ArtistView.cpp b/src/ui/ArtistView.cpp new file mode 100644 index 00000000..5327ced6 --- /dev/null +++ b/src/ui/ArtistView.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2018 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 . + */ + +#include +#include +#include +#include +#include + +#include "database/Types.hpp" + +#include "utils/Logger.hpp" +#include "utils/Utils.hpp" + +#include "LmsApplication.hpp" +#include "ArtistView.hpp" + +namespace UserInterface { + +Artist::Artist(Filters* filters, Wt::WContainerWidget* parent) +: Wt::WContainerWidget(parent), + _filters(filters) +{ + wApp->internalPathChanged().connect(std::bind([=] + { + refresh(); + })); + + refresh(); +} + +void +Artist::refresh() +{ + if (!wApp->internalPathMatches("/artist/")) + return; + + clear(); + + Database::Artist::id_type artistId; + if (!readAs(wApp->internalPathNextPart("/artist/"), artistId)) + return; + + Wt::Dbo::Transaction transaction(DboSession()); + auto artist = Database::Artist::getById(DboSession(), artistId); + + if (!artist) + { + LmsApp->goHome(); + return; + } + + auto t = new Wt::WTemplate(Wt::WString::tr("template-artist"), this); + t->addFunction("tr", &Wt::WTemplate::Functions::tr); + + t->bindString("name", Wt::WString::fromUTF8(artist->getName()), Wt::PlainText); + + auto releasesContainer = new Wt::WContainerWidget(); + t->bindWidget("releases", releasesContainer); + + auto releases = artist->getReleases(_filters->getClusterIds()); + for (auto release : releases) + { + if (release->isNone()) + continue; + + Wt::WTemplate* entry = new Wt::WTemplate(Wt::WString::tr("template-artist-entry"), releasesContainer); + entry->addFunction("tr", Wt::WTemplate::Functions::tr); + + { + Wt::WAnchor *coverAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/release/" + std::to_string(release.id()))); + Wt::WImage *cover = new Wt::WImage(coverAnchor); + cover->setImageLink(SessionImageResource()->getReleaseUrl(release.id(), 128)); + // Some images may not be square + cover->setWidth(128); + entry->bindWidget("cover", coverAnchor); + } + + { + Wt::WAnchor *releaseAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/release/" + std::to_string(release.id()))); + Wt::WText *releaseName = new Wt::WText(releaseAnchor); + releaseName->setText(Wt::WString::fromUTF8(release->getName(), Wt::PlainText)); + entry->bindWidget("name", releaseAnchor); + } + + if (release->hasVariousArtists()) + entry->setCondition("if-has-various-artists", true); + + boost::optional year = release->getReleaseYear(); + if (year) + { + entry->setCondition("if-has-year", true); + entry->bindInt("year", *year); + + boost::optional originalYear = release->getReleaseYear(true); + if (originalYear && *originalYear != *year) + { + entry->setCondition("if-has-orig-year", true); + entry->bindInt("orig-year", *originalYear); + } + } + } +} + +} // namespace UserInterface + diff --git a/src/ui/audio/mobile/TrackSearchView.hpp b/src/ui/ArtistView.hpp similarity index 77% rename from src/ui/audio/mobile/TrackSearchView.hpp rename to src/ui/ArtistView.hpp index 602d9c56..eb7262d7 100644 --- a/src/ui/audio/mobile/TrackSearchView.hpp +++ b/src/ui/ArtistView.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 Emeric Poupon + * Copyright (C) 2018 Emeric Poupon * * This file is part of LMS. * @@ -20,19 +20,21 @@ #pragma once #include -#include + +#include "Filters.hpp" namespace UserInterface { -namespace Mobile { -class TrackSearchView : public Wt::WContainerWidget +class Artist : public Wt::WContainerWidget { public: + Artist(Filters* filters, Wt::WContainerWidget* parent = 0); - TrackSearchView(PlayQueueEvents& events, Wt::WContainerWidget* parent = 0); + private: + void refresh(); + Filters* _filters; }; -} // namespace Mobile } // namespace UserInterface diff --git a/src/ui/ArtistsView.cpp b/src/ui/ArtistsView.cpp new file mode 100644 index 00000000..e684db2d --- /dev/null +++ b/src/ui/ArtistsView.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2018 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 . + */ + +#include +#include +#include +#include + +#include "database/Types.hpp" + +#include "utils/Logger.hpp" +#include "utils/Utils.hpp" + +#include "LmsApplication.hpp" +#include "ArtistsView.hpp" + +namespace UserInterface { + +using namespace Database; + +Artists::Artists(Filters* filters, Wt::WContainerWidget* parent) +: Wt::WContainerWidget(parent), + _filters(filters) +{ + auto artists = new Wt::WTemplate(Wt::WString::tr("template-artists"), this); + artists->addFunction("tr", &Wt::WTemplate::Functions::tr); + + auto search = new Wt::WLineEdit(); + artists->bindWidget("search", search); + search->setPlaceholderText(Wt::WString::tr("msg-search-placeholder")); + search->textInput().connect(std::bind([this, search] + { + auto keywords = splitString(search->text().toUTF8(), " "); + refresh(keywords); + })); + + _artistsContainer = new Wt::WContainerWidget(); + artists->bindWidget("artists", _artistsContainer); + + refresh(); +} + + +void +Artists::refresh(std::vector searchKeywords) +{ + _artistsContainer->clear(); + + auto clusterIds = _filters->getClusterIds(); + + Wt::Dbo::Transaction transaction(DboSession()); + + bool moreResults; + auto artists = Artist::getByFilter(DboSession(), + clusterIds, + searchKeywords, + 0, 40, moreResults); + + for (auto artist : artists) + { + Wt::WTemplate* entry = new Wt::WTemplate(Wt::WString::tr("template-artists-entry"), _artistsContainer); + + entry->bindInt("nb-release", artist->getReleases(clusterIds).size()); + + { + Wt::WAnchor *artistAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/artist/" + std::to_string(artist.id()))); + Wt::WText *artistText = new Wt::WText(artistAnchor); + artistText->setText(Wt::WString::fromUTF8(artist->getName(), Wt::PlainText)); + entry->bindWidget("name", artistAnchor); + } + } + +} + +} // namespace UserInterface + diff --git a/src/ui/audio/mobile/MobileAudio.hpp b/src/ui/ArtistsView.hpp similarity index 71% rename from src/ui/audio/mobile/MobileAudio.hpp rename to src/ui/ArtistsView.hpp index c49b24ae..2cc29bfc 100644 --- a/src/ui/audio/mobile/MobileAudio.hpp +++ b/src/ui/ArtistsView.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Emeric Poupon + * Copyright (C) 2018 Emeric Poupon * * This file is part of LMS. * @@ -21,27 +21,21 @@ #include -#include "audio/Audio.hpp" - -#include "MobilePlayQueueEvents.hpp" - +#include "Filters.hpp" namespace UserInterface { -namespace Mobile { -class Audio : public UserInterface::Audio +class Artists : public Wt::WContainerWidget { public: - Audio(Wt::WContainerWidget *parent = 0); - - void search(std::string text); + Artists(Filters* filters, Wt::WContainerWidget* parent = 0); private: + void refresh(std::vector searchKeywords = std::vector()); - PlayQueueEvents _playQueueEvents; - + Filters* _filters; + Wt::WContainerWidget* _artistsContainer; }; -} // namespace Mobile } // namespace UserInterface diff --git a/src/ui/Explore.cpp b/src/ui/Explore.cpp new file mode 100644 index 00000000..46cfc5ca --- /dev/null +++ b/src/ui/Explore.cpp @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2018 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 . + */ + +#include +#include +#include + +#include "utils/Logger.hpp" + +#include "LmsApplication.hpp" + +#include "ArtistsView.hpp" +#include "ArtistView.hpp" +#include "ReleasesView.hpp" +#include "ReleaseView.hpp" +#include "TracksView.hpp" + +#include "Explore.hpp" + +namespace UserInterface { + + +enum IdxRoot +{ + IdxArtists = 0, + IdxArtist, + IdxReleases, + IdxRelease, + IdxTracks, +}; + +static void +handlePathChange(Wt::WStackedWidget* stack) +{ + static const std::map indexes = + { + { "/artists", IdxArtists }, + { "/artist", IdxArtist }, + { "/releases", IdxReleases }, + { "/release", IdxRelease }, + { "/tracks", IdxTracks }, + }; + + LMS_LOG(UI, DEBUG) << "Internal path changed to '" << wApp->internalPath() << "'"; + + for (auto index : indexes) + { + if (wApp->internalPathMatches(index.first)) + { + stack->setCurrentIndex(index.second); + return; + } + } +} + +Explore::Explore(Wt::WContainerWidget* parent) +: Wt::WContainerWidget(parent) +{ + auto container = new Wt::WTemplate(Wt::WString::tr("template-explore"), this); + + auto filters = new Filters(); + container->bindWidget("filters", filters); + + // Contents + Wt::WStackedWidget* stack = new Wt::WStackedWidget(); + container->bindWidget("contents", stack); + + stack->addWidget(new Artists(filters)); + stack->addWidget(new Artist(filters)); + stack->addWidget(new Releases(/*filters*/)); + stack->addWidget(new Release(/*filters*/)); + stack->addWidget(new Tracks(/*filters*/)); + + wApp->internalPathChanged().connect(std::bind([=] + { + handlePathChange(stack); + })); + + handlePathChange(stack); +} +} // namespace UserInterface + diff --git a/src/ui/audio/mobile/ArtistSearchView.hpp b/src/ui/Explore.hpp similarity index 80% rename from src/ui/audio/mobile/ArtistSearchView.hpp rename to src/ui/Explore.hpp index 29971950..ac360a83 100644 --- a/src/ui/audio/mobile/ArtistSearchView.hpp +++ b/src/ui/Explore.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Emeric Poupon + * Copyright (C) 2018 Emeric Poupon * * This file is part of LMS. * @@ -22,16 +22,12 @@ #include namespace UserInterface { -namespace Mobile { -class ArtistSearchView : public Wt::WContainerWidget +class Explore : public Wt::WContainerWidget { public: - - ArtistSearchView(Wt::WContainerWidget* parent = 0); - + Explore(Wt::WContainerWidget *parent = 0); }; -} // namespace Mobile } // namespace UserInterface diff --git a/src/ui/audio/Audio.hpp b/src/ui/Filters.cpp similarity index 57% rename from src/ui/audio/Audio.hpp rename to src/ui/Filters.cpp index 422b0cca..8dc3f131 100644 --- a/src/ui/audio/Audio.hpp +++ b/src/ui/Filters.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Emeric Poupon + * Copyright (C) 2018 Emeric Poupon * * This file is part of LMS. * @@ -17,25 +17,29 @@ * along with LMS. If not, see . */ -#ifndef UI_AUDIO_HPP -#define UI_AUDIO_HPP +#include +#include -#include -#include +#include "Filters.hpp" namespace UserInterface { -class Audio : public Wt::WContainerWidget +Filters::Filters(Wt::WContainerWidget *parent) +: Wt::WContainerWidget(parent) { - public: + auto container = new Wt::WTemplate(Wt::WString::tr("template-filters"), this); - Audio(Wt::WContainerWidget *parent) : Wt::WContainerWidget(parent) {} - virtual ~Audio() {} + // Filters + Wt::WPushButton *addFilterBtn = new Wt::WPushButton(Wt::WText::tr("msg-filter-add")); + container->bindWidget("add-filter", addFilterBtn); + + _filters = new Wt::WContainerWidget(); + container->bindWidget("filters", _filters); + +// _filterIds = {108, 419}; +} - virtual void search(std::string text) = 0; -}; } // namespace UserInterface -#endif diff --git a/src/ui/audio/mobile/ArtistView.hpp b/src/ui/Filters.hpp similarity index 68% rename from src/ui/audio/mobile/ArtistView.hpp rename to src/ui/Filters.hpp index 089e6f53..98d2e4c0 100644 --- a/src/ui/audio/mobile/ArtistView.hpp +++ b/src/ui/Filters.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Emeric Poupon + * Copyright (C) 2018 Emeric Poupon * * This file is part of LMS. * @@ -20,24 +20,25 @@ #pragma once #include -#include #include "database/Types.hpp" -namespace UserInterface { -namespace Mobile { +#include "Filters.hpp" -class ArtistView : public Wt::WContainerWidget +namespace UserInterface { + +class Filters : public Wt::WContainerWidget { public: + Filters(Wt::WContainerWidget *parent = 0); - static Wt::WLink getLink(Database::Artist::id_type id); + std::vector getClusterIds() const { return _filterIds; } - ArtistView(Wt::WContainerWidget *parent = 0); + private: + Wt::WContainerWidget *_filters; + std::vector _filterIds; }; - -} //namespace Mobile -} //namespace UserInterface +} // namespace UserInterface diff --git a/src/ui/audio/mobile/MobilePlayQueueEvents.hpp b/src/ui/HomeView.cpp similarity index 71% rename from src/ui/audio/mobile/MobilePlayQueueEvents.hpp rename to src/ui/HomeView.cpp index 61775642..730f981e 100644 --- a/src/ui/audio/mobile/MobilePlayQueueEvents.hpp +++ b/src/ui/HomeView.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 Emeric Poupon + * Copyright (C) 2018 Emeric Poupon * * This file is part of LMS. * @@ -17,20 +17,22 @@ * along with LMS. If not, see . */ -#pragma once +#include -#include -#include "database/Types.hpp" +#include "utils/Logger.hpp" + +#include "LmsApplication.hpp" + +#include "HomeView.hpp" namespace UserInterface { -namespace Mobile { -struct PlayQueueEvents + +Home::Home(Wt::WContainerWidget *parent) +: Wt::WContainerWidget(parent) { - Wt::Signal trackPlay; - Wt::Signal trackAdd; -}; + addWidget(new Wt::WTemplate(Wt::WString::tr("template-home"))); +} - -} // namespace Mobile } // namespace UserInterface + diff --git a/src/ui/audio/mobile/ReleaseSearchView.hpp b/src/ui/HomeView.hpp similarity index 80% rename from src/ui/audio/mobile/ReleaseSearchView.hpp rename to src/ui/HomeView.hpp index 8427fe68..33f65d5e 100644 --- a/src/ui/audio/mobile/ReleaseSearchView.hpp +++ b/src/ui/HomeView.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 Emeric Poupon + * Copyright (C) 2018 Emeric Poupon * * This file is part of LMS. * @@ -22,16 +22,18 @@ #include namespace UserInterface { -namespace Mobile { -class ReleaseSearchView : public Wt::WContainerWidget +class Home : public Wt::WContainerWidget { public: + Home(Wt::WContainerWidget *parent = 0); - ReleaseSearchView(Wt::WContainerWidget* parent = 0); + private: + + void displayFilter(void); + void displayContents(void); }; -} // namespace Mobile } // namespace UserInterface diff --git a/src/ui/LmsApplication.cpp b/src/ui/LmsApplication.cpp index d68c2f02..063e97be 100644 --- a/src/ui/LmsApplication.cpp +++ b/src/ui/LmsApplication.cpp @@ -19,28 +19,19 @@ #include #include -#include #include #include #include -#include -#include -#include +#include #include "config/config.h" #include "utils/Logger.hpp" #include "utils/Utils.hpp" -#include "settings/Settings.hpp" -#include "settings/SettingsFirstConnectionFormView.hpp" - #include "auth/LmsAuth.hpp" -#include "audio/desktop/DesktopAudio.hpp" -#include "audio/mobile/MobileAudio.hpp" -#if HAVE_VIDEO -#include "video/VideoWidget.hpp" -#endif -#include "common/LineEdit.hpp" +#include "Explore.hpp" +#include "HomeView.hpp" + #include "LmsApplication.hpp" @@ -48,19 +39,6 @@ namespace skeletons { extern const char *AuthStrings_xml1; } -namespace { - -bool agentIsMobile() -{ - const Wt::WEnvironment& env = Wt::WApplication::instance()->environment(); - return (env.agentIsIEMobile() - || env.agentIsMobileWebKit() - || env.userAgent().find("Mobile") != std::string::npos // Workaround for firefox - || env.userAgent().find("Tablet") != std::string::npos // Workaround for firefox - ); -} -} - namespace UserInterface { Wt::WApplication* @@ -100,7 +78,13 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnecti useStyleSheet("resources/font-awesome/css/font-awesome.min.css"); // Add a resource bundle + messageResourceBundle().use(appRoot() + "artist"); + messageResourceBundle().use(appRoot() + "artists"); + messageResourceBundle().use(appRoot() + "messages"); messageResourceBundle().use(appRoot() + "templates"); + messageResourceBundle().use(appRoot() + "release"); + messageResourceBundle().use(appRoot() + "releases"); + messageResourceBundle().use(appRoot() + "tracks"); setTitle("LMS"); @@ -114,9 +98,9 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnecti LMS_LOG(UI, DEBUG) << "Creating root widget. First connection = " << std::boolalpha << firstConnection; // If here is no account in the database, launch the first connection wizard - if (firstConnection) - createFirstConnectionUI(); - else +// if (firstConnection) +// createFirstConnectionUI(); +// else createLmsUI(); } @@ -149,13 +133,19 @@ TranscodeResource* SessionTranscodeResource() return LmsApplication::instance()->getTranscodeResource(); } +void +LmsApplication::goHome() +{ + setInternalPath("/home", true); +} + void LmsApplication::createFirstConnectionUI() { // Hack, use the auth widget builtin strings builtinLocalizedStrings().useBuiltin(skeletons::AuthStrings_xml1); - root()->addWidget( new Settings::FirstConnectionFormView()); +// root()->addWidget( new Settings::FirstConnectionFormView()); } void @@ -164,6 +154,8 @@ LmsApplication::createLmsUI() _imageResource = new ImageResource(_db, root()); _transcodeResource = new TranscodeResource(_db, root()); + handleAuthEvent(); + /* DbHandler().getLogin().changed().connect(this, &LmsApplication::handleAuthEvent); LmsAuth *authWidget = new LmsAuth(); @@ -174,11 +166,53 @@ LmsApplication::createLmsUI() authWidget->processEnvironment(); root()->addWidget(authWidget); + */ +} + + +enum IdxRoot +{ + IdxHome = 0, + IdxExplore, + IdxPlaylist, + IdxSettings, +}; + + + +static void +handlePathChange(Wt::WStackedWidget* stack) +{ + static const std::map indexes = + { + { "/home", IdxHome }, + { "/artists", IdxExplore }, + { "/artist", IdxExplore }, + { "/releases", IdxExplore }, + { "/release", IdxExplore }, + { "/tracks", IdxExplore }, + { "/playlist", IdxPlaylist }, + { "/settings", IdxSettings }, + }; + + LMS_LOG(UI, DEBUG) << "Internal path changed to '" << wApp->internalPath() << "'"; + + for (auto index : indexes) + { + if (wApp->internalPathMatches(index.first)) + { + stack->setCurrentIndex(index.second); + return; + } + } + + wApp->setInternalPath("/home", true); } void LmsApplication::handleAuthEvent(void) { + /* if (!DbHandler().getLogin().loggedIn()) { LMS_LOG(UI, INFO) << "User logged out, session = " << Wt::WApplication::instance()->sessionId(); @@ -190,76 +224,61 @@ LmsApplication::handleAuthEvent(void) } LMS_LOG(UI, INFO) << "User '" << CurrentAuthUser().identity(Wt::Auth::Identity::LoginName) << "' logged in from '" << Wt::WApplication::instance()->environment().clientAddress() << "', user agent = " << Wt::WApplication::instance()->environment().agent() << ", session = " << Wt::WApplication::instance()->sessionId(); +*/ + setConfirmCloseMessage(Wt::WString::tr("msg-quit-confirm")); - this->root()->setOverflow(Wt::WContainerWidget::OverflowHidden); - setConfirmCloseMessage("Closing LMS. Are you sure?"); + auto main = new Wt::WTemplate(Wt::WString::tr("template-main"), root()); - // Create a Vertical layout: top is the nav bar, bottom is the contents - Wt::WVBoxLayout *layout = new Wt::WVBoxLayout(this->root()); + // Navbar + auto navbar = new Wt::WNavigationBar(); + navbar->setTitle("LMS", Wt::WLink(Wt::WLink::InternalPath, "/home")); + navbar->setResponsive(true); +// navbar->setStyleClass("main-nav"); - Wt::WNavigationBar *navigation = new Wt::WNavigationBar(); - navigation->setTitle("LMS", "https://github.com/epoupon/lms"); - navigation->setResponsive(true); - navigation->addStyleClass("main-nav"); + main->bindWidget("navbar-top", navbar); - Wt::WStackedWidget *contentsStack = new Wt::WStackedWidget(); - - contentsStack->setOverflow(Wt::WContainerWidget::OverflowAuto); - contentsStack->addStyleClass("contents"); - - Wt::WMenu *menu = new Wt::WMenu(contentsStack); - navigation->addMenu(menu, Wt::AlignRight); - - LineEdit *searchEdit = new LineEdit(500); - navigation->bindWidget("search", searchEdit); - searchEdit->setEmptyText("Search..."); - searchEdit->addStyleClass("navbar-form navbar-nav"); - searchEdit->setWidth(150); - // TODO add a span with a search icon - - menu->setInternalPathEnabled(); - menu->setInternalBasePath("/"); - - Audio *audio; - if (agentIsMobile()) - audio = new Mobile::Audio(); - else - audio = new Desktop::Audio(); - - menu->addItem("Audio", audio)->setPathComponent("audio");; -#if defined HAVE_VIDEO - menu->addItem("Video", new VideoWidget())->setPathComponent("video"); -#endif - menu->addItem("Settings", new Settings::Settings())->setPathComponent("settings"); - - Wt::WPopupMenu *popup = new Wt::WPopupMenu(); - popup->addItem("Logout"); - - Wt::WMenuItem *item = new Wt::WMenuItem( CurrentAuthUser().identity(Wt::Auth::Identity::LoginName) ); - item->setMenu(popup); - menu->addItem(item); - - popup->itemSelected().connect(std::bind([=] (Wt::WMenuItem* item) + auto menu = new Wt::WMenu(); { - if (item && item->text() == "Logout") - DbHandler().getLogin().logout(); - }, std::placeholders::_1)); - - searchEdit->timedChanged().connect(std::bind([=] () + auto menuItem = menu->insertItem(0, Wt::WString::tr("msg-artists")); + menuItem->setLink(Wt::WLink(Wt::WLink::InternalPath, "/artists")); + menuItem->setSelectable(false); + } { - // TODO: check which view is activated and search into it - audio->search(searchEdit->text().toUTF8()); + auto menuItem = menu->insertItem(1, Wt::WString::tr("msg-releases")); + menuItem->setLink(Wt::WLink(Wt::WLink::InternalPath, "/releases")); + menuItem->setSelectable(false); + } + { + auto menuItem = menu->insertItem(2, Wt::WString::tr("msg-tracks")); + menuItem->setLink(Wt::WLink(Wt::WLink::InternalPath, "/tracks")); + menuItem->setSelectable(false); + } + { + auto menuItem = menu->insertItem(3, Wt::WString::tr("msg-playlist")); + menuItem->setLink(Wt::WLink(Wt::WLink::InternalPath, "/playlist")); + menuItem->setSelectable(false); + } + navbar->addMenu(menu); + + // Contents + Wt::WStackedWidget* mainStack = new Wt::WStackedWidget(); + main->bindWidget("contents", mainStack); + + mainStack->addWidget(new Home()); + mainStack->addWidget(new Explore()); + mainStack->addWidget(new Wt::WText("PLAYLIST")); + mainStack->addWidget(new Wt::WText("SETTINGS")); + + // MediaPlayer +// auto player = new AudioPlayer(AudioPlayer::ControlPlayqueue); +// main->bindWidget("player", player); + + internalPathChanged().connect(std::bind([=] + { + handlePathChange(mainStack); })); - layout->addWidget(navigation); - layout->addWidget(contentsStack, 1); - layout->setContentsMargins(0, 0, 0, 0); - - if (agentIsMobile()) - setInternalPath("/audio/search/preview", true); - else - setInternalPath("/audio"); - + handlePathChange(mainStack); } } // namespace UserInterface diff --git a/src/ui/LmsApplication.hpp b/src/ui/LmsApplication.hpp index ee0cfe52..de460e4e 100644 --- a/src/ui/LmsApplication.hpp +++ b/src/ui/LmsApplication.hpp @@ -44,6 +44,9 @@ class LmsApplication : public Wt::WApplication TranscodeResource* getTranscodeResource() { return _transcodeResource; } Database::Handler& getDbHandler() { return _db;} + // Utils + void goHome(); + private: void handleAuthEvent(void); @@ -67,6 +70,7 @@ Database::User::pointer CurrentUser(); ImageResource *SessionImageResource(); TranscodeResource *SessionTranscodeResource(); + } // namespace UserInterface #endif diff --git a/src/ui/ReleaseView.cpp b/src/ui/ReleaseView.cpp new file mode 100644 index 00000000..098b12a5 --- /dev/null +++ b/src/ui/ReleaseView.cpp @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2018 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 . + */ + +#include +#include +#include +#include +#include + +#include "database/Types.hpp" + +#include "utils/Logger.hpp" +#include "utils/Utils.hpp" + +#include "LmsApplication.hpp" +#include "ReleaseView.hpp" + +namespace UserInterface { + +Release::Release(Wt::WContainerWidget* parent) +: Wt::WContainerWidget(parent) +{ + wApp->internalPathChanged().connect(std::bind([=] + { + refresh(); + })); + + refresh(); +} + +void +Release::refresh() +{ + if (!wApp->internalPathMatches("/release/")) + return; + + clear(); + + Database::Release::id_type releaseId; + + if (!readAs(wApp->internalPathNextPart("/release/"), releaseId)) + return; + + Wt::Dbo::Transaction transaction(DboSession()); + + auto release = Database::Release::getById(DboSession(), releaseId); + if (!release) + { + LmsApp->goHome(); + return; + } + + auto t = new Wt::WTemplate(Wt::WString::tr("template-release"), this); + t->addFunction("tr", &Wt::WTemplate::Functions::tr); + + t->bindString("name", Wt::WString::fromUTF8(release->getName()), Wt::PlainText); + + boost::optional year = release->getReleaseYear(); + if (year) + { + t->setCondition("if-has-year", true); + t->bindInt("year", *year); + + boost::optional originalYear = release->getReleaseYear(true); + if (originalYear && *originalYear != *year) + { + t->setCondition("if-has-orig-year", true); + t->bindInt("orig-year", *originalYear); + } + } + + { + auto artists = release->getArtists(); + if (artists.size() > 1) + { + t->bindString("artist-name", Wt::WString::tr("msg-various-artists")); + } + else + { + Wt::WAnchor *artistAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/artist/" + std::to_string(artists.front().id()))); + Wt::WText *artist = new Wt::WText(artistAnchor); + artist->setText(Wt::WString::fromUTF8(artists.front()->getName(), Wt::PlainText)); + t->bindWidget("artist-name", artistAnchor); + } + } + + Wt::WImage *cover = new Wt::WImage(); + cover->setImageLink(Wt::WLink (SessionImageResource()->getReleaseUrl(release.id(), 512))); + t->bindWidget("cover", cover); + + // TODO play/add button + + auto tracksContainer = new Wt::WContainerWidget(); + t->bindWidget("tracks", tracksContainer); + + std::vector clusterIds; // TODO fill + +// auto tracks = release->getTracks(clusterIds); + auto tracks = release->getTracks(); + bool variousArtists = release->hasVariousArtists(); + + for (auto track : tracks) + { + Wt::WTemplate* entry = new Wt::WTemplate(Wt::WString::tr("template-release-entry"), tracksContainer); + + entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::PlainText); + + if (variousArtists && !track->getArtist()->isNone()) + { + entry->setCondition("if-has-artist", true); + Wt::WAnchor *artistAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/artist/" + std::to_string(track->getArtist().id()))); + Wt::WText *artistText = new Wt::WText(artistAnchor); + artistText->setText(Wt::WString::fromUTF8(track->getArtist()->getName(), Wt::PlainText)); + entry->bindWidget("artist-name", artistAnchor); + } + + auto trackNumber = track->getTrackNumber(); + if (trackNumber) + { + entry->setCondition("if-has-track-number", true); + entry->bindInt("track-number", *trackNumber); + } + + auto discNumber = track->getDiscNumber(); + auto totalDiscNumber = track->getTotalDiscNumber(); + if (discNumber && totalDiscNumber && *totalDiscNumber > 1) + { + entry->setCondition("if-has-disc-number", true); + entry->bindInt("disc-number", *discNumber); + } + } +} + +} // namespace UserInterface + diff --git a/src/ui/settings/Settings.hpp b/src/ui/ReleaseView.hpp similarity index 81% rename from src/ui/settings/Settings.hpp rename to src/ui/ReleaseView.hpp index 30ffa0b8..0a68a9e9 100644 --- a/src/ui/settings/Settings.hpp +++ b/src/ui/ReleaseView.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Emeric Poupon + * Copyright (C) 2018 Emeric Poupon * * This file is part of LMS. * @@ -17,18 +17,20 @@ * along with LMS. If not, see . */ +#pragma once + #include namespace UserInterface { -namespace Settings { -class Settings : public Wt::WContainerWidget +class Release : public Wt::WContainerWidget { public: - Settings(Wt::WContainerWidget* parent = 0); + Release(Wt::WContainerWidget* parent = 0); private: + void refresh(); }; -} // namespace Settings } // namespace UserInterface + diff --git a/src/ui/ReleasesView.cpp b/src/ui/ReleasesView.cpp new file mode 100644 index 00000000..7c7da4de --- /dev/null +++ b/src/ui/ReleasesView.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2018 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 . + */ + +#include +#include +#include +#include +#include +#include + +#include "database/Types.hpp" + +#include "utils/Logger.hpp" +#include "utils/Utils.hpp" + +#include "LmsApplication.hpp" +#include "ReleasesView.hpp" + +namespace UserInterface { + +using namespace Database; + +Releases::Releases(Wt::WContainerWidget* parent) +: Wt::WContainerWidget(parent) +{ + auto releases = new Wt::WTemplate(Wt::WString::tr("template-releases"), this); + releases->addFunction("tr", &Wt::WTemplate::Functions::tr); + + auto search = new Wt::WLineEdit(); + releases->bindWidget("search", search); + search->setPlaceholderText(Wt::WString::tr("msg-search-placeholder")); + search->textInput().connect(std::bind([this, search] + { + auto keywords = splitString(search->text().toUTF8(), " "); + refresh(keywords); + })); + + _releasesContainer = new Wt::WContainerWidget(); + releases->bindWidget("releases", _releasesContainer); + + refresh(); +} + + +void +Releases::refresh(std::vector searchKeywords) +{ + _releasesContainer->clear(); + + std::vector clusterIds; // TODO fill + + Wt::Dbo::Transaction transaction(DboSession()); + + bool moreResults; + auto releases = Release::getByFilter(DboSession(), clusterIds, searchKeywords, 0, 40, moreResults); + + for (auto release : releases) + { + Wt::WTemplate* entry = new Wt::WTemplate(Wt::WString::tr("template-releases-entry"), _releasesContainer); + entry->addFunction("tr", Wt::WTemplate::Functions::tr); + + Wt::WAnchor *coverAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/release/" + std::to_string(release.id()))); + Wt::WImage *cover = new Wt::WImage(coverAnchor); + cover->setImageLink(SessionImageResource()->getReleaseUrl(release.id(), 128)); + // Some images may not be square + cover->setWidth(128); + entry->bindWidget("cover", coverAnchor); + + entry->bindString("release-name", Wt::WString::fromUTF8(release->getName()), Wt::PlainText); + + auto artists = release->getArtists(); + if (artists.size() > 1) + { + entry->bindString("artist-name", Wt::WString::tr("msg-various-artists")); + } + else + { + Wt::WAnchor *artistAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/artist/" + std::to_string(artists.front().id()))); + Wt::WText *artist = new Wt::WText(artistAnchor); + artist->setText(Wt::WString::fromUTF8(artists.front()->getName(), Wt::PlainText)); + entry->bindWidget("artist-name", artistAnchor); + } + } +} + +} // namespace UserInterface + diff --git a/src/ui/ReleasesView.hpp b/src/ui/ReleasesView.hpp new file mode 100644 index 00000000..7a4325cd --- /dev/null +++ b/src/ui/ReleasesView.hpp @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2018 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 . + */ + +#pragma once + +#include + +namespace UserInterface { + +class Releases : public Wt::WContainerWidget +{ + public: + Releases(Wt::WContainerWidget* parent = 0); + + private: + void refresh(std::vector searchKeywords = std::vector()); + + Wt::WContainerWidget *_releasesContainer; +}; + +} // namespace UserInterface + diff --git a/src/ui/TracksView.cpp b/src/ui/TracksView.cpp new file mode 100644 index 00000000..bc834de5 --- /dev/null +++ b/src/ui/TracksView.cpp @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2018 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 . + */ + +#include +#include +#include +#include +#include + +#include "database/Types.hpp" + +#include "utils/Logger.hpp" +#include "utils/Utils.hpp" + +#include "LmsApplication.hpp" +#include "TracksView.hpp" + +namespace UserInterface { + +using namespace Database; + +Tracks::Tracks(Wt::WContainerWidget* parent) +: Wt::WContainerWidget(parent) +{ + auto tracks = new Wt::WTemplate(Wt::WString::tr("template-tracks"), this); + tracks->addFunction("tr", &Wt::WTemplate::Functions::tr); + + auto search = new Wt::WLineEdit(); + tracks->bindWidget("search", search); + search->setPlaceholderText(Wt::WString::tr("msg-search-placeholder")); + search->textInput().connect(std::bind([this, search] + { + auto keywords = splitString(search->text().toUTF8(), " "); + refresh(keywords); + })); + + _tracksContainer = new Wt::WContainerWidget(); + tracks->bindWidget("tracks", _tracksContainer); + + refresh(); +} + + +void +Tracks::refresh(std::vector searchKeywords) +{ + _tracksContainer->clear(); + + std::vector clusterIds; // TODO fill + + Wt::Dbo::Transaction transaction(DboSession()); + + bool moreResults; + auto tracks = Track::getByFilter(DboSession(), clusterIds, searchKeywords, 0, 40, moreResults); + + for (auto track : tracks) + { + Wt::WTemplate* entry = new Wt::WTemplate(Wt::WString::tr("template-tracks-entry"), _tracksContainer); + + entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::PlainText); + + auto artist = track->getArtist(); + if (!artist->isNone()) + { + entry->setCondition("if-has-artist", true); + Wt::WAnchor *artistAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/artist/" + std::to_string(track->getArtist().id()))); + Wt::WText *artistText = new Wt::WText(artistAnchor); + artistText->setText(Wt::WString::fromUTF8(artist->getName(), Wt::PlainText)); + entry->bindWidget("artist-name", artistAnchor); + } + + auto release = track->getRelease(); + if (!release->isNone()) + { + entry->setCondition("if-has-release", true); + // TODO anchor + Wt::WAnchor *releaseAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/release/" + std::to_string(track->getRelease().id()))); + Wt::WText *releaseText = new Wt::WText(releaseAnchor); + releaseText->setText(Wt::WString::fromUTF8(release->getName(), Wt::PlainText)); + entry->bindWidget("release-name", releaseAnchor); + } + + Wt::WImage *cover = new Wt::WImage(); + cover->setImageLink(SessionImageResource()->getTrackUrl(track.id(), 64)); + // Some images may not be square + cover->setWidth(64); + entry->bindWidget("cover", cover); + } + +} + +} // namespace UserInterface + diff --git a/src/ui/audio/mobile/PreviewSearchView.hpp b/src/ui/TracksView.hpp similarity index 74% rename from src/ui/audio/mobile/PreviewSearchView.hpp rename to src/ui/TracksView.hpp index fab0b4e9..7ba2c61c 100644 --- a/src/ui/audio/mobile/PreviewSearchView.hpp +++ b/src/ui/TracksView.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Emeric Poupon + * Copyright (C) 2018 Emeric Poupon * * This file is part of LMS. * @@ -21,19 +21,18 @@ #include -#include "MobilePlayQueueEvents.hpp" - namespace UserInterface { -namespace Mobile { -class PreviewSearchView : public Wt::WContainerWidget +class Tracks : public Wt::WContainerWidget { public: + Tracks(Wt::WContainerWidget* parent = 0); - PreviewSearchView(PlayQueueEvents& events, Wt::WContainerWidget* parent = 0); + private: + void refresh(std::vector searchKeywords = std::vector()); + Wt::WContainerWidget *_tracksContainer; }; -} // namespace Mobile } // namespace UserInterface diff --git a/src/ui/audio/AudioPlayer.cpp b/src/ui/audio/AudioPlayer.cpp deleted file mode 100644 index bb487af9..00000000 --- a/src/ui/audio/AudioPlayer.cpp +++ /dev/null @@ -1,357 +0,0 @@ -/* - * Copyright (C) 2015 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 . - */ - -#include -#include -#include -#include -#include - -#include "common/InputRange.hpp" - -#include "utils/Logger.hpp" -#include "utils/Utils.hpp" - -#include "LmsApplication.hpp" - -#include "AudioPlayer.hpp" - -namespace UserInterface { - - -Av::Encoding -AudioPlayer::getBestEncoding() const -{ - // TODO get the supported formats of the player and pick one - return Av::Encoding::MP3; -} - -bool -AudioPlayer::loadTrack(Database::Track::id_type trackId) -{ - // TODO reduce this scope - Wt::Dbo::Transaction transaction(DboSession()); - - Database::Track::pointer track = Database::Track::getById(DboSession(), trackId); - if (!track) - { - LMS_LOG(UI, INFO) << "No track found for id " << trackId; - return false; - } - - Database::User::pointer user = CurrentUser(); - if (!user) - { - LMS_LOG(UI, ERROR) << "No user found!"; - return false; - } - - Av::Encoding encoding; - switch (user->getAudioEncoding()) - { - case Database::AudioEncoding::MP3: encoding = Av::Encoding::MP3; break; - case Database::AudioEncoding::OGA: encoding = Av::Encoding::OGA; break; - case Database::AudioEncoding::WEBMA: encoding = Av::Encoding::WEBMA; break; - case Database::AudioEncoding::AUTO: - default: - encoding = getBestEncoding(); - break; - } - - bindString("track", Wt::WString::fromUTF8(track->getName())); - bindString("artist", Wt::WString::fromUTF8(track->getArtist()->getName())); - _cover->setImageLink(SessionImageResource()->getTrackUrl(trackId, 64)); - - std::string durationFormat = track->getDuration().total_seconds() < 3600 ? "%M:%S" : "%H:%M:%S"; - - _trackDuration->setText(durationToString(track->getDuration(), durationFormat)); - _trackCurTime->setText(durationToString(boost::posix_time::seconds(0), durationFormat)); - - // Analyse track, select the best media stream - Av::MediaFile mediaFile(track->getPath()); - - if (!mediaFile.open() || !mediaFile.scan()) - { - LMS_LOG(UI, ERROR) << "Cannot open file '" << track->getPath(); - return false; - } - - - int audioBestStreamId = mediaFile.getBestStreamId(Av::Stream::Type::Audio); - std::vector streams; - if (audioBestStreamId != -1) - streams.push_back(audioBestStreamId); - - this->doJavaScript("\ - document.lms.audio.state = \"loaded\";\ - document.lms.audio.seekbar.min = " + std::to_string(0) + ";\ - document.lms.audio.seekbar.max = " + std::to_string(track->getDuration().total_seconds()) + ";\ - document.lms.audio.seekbar.value = 0;\ - document.lms.audio.seekbar.disabled = false;\ - document.lms.audio.offset = 0;\ - document.lms.audio.curTime = 0;\ - "); - - LMS_LOG(UI, DEBUG) << "Loading, URL = '" << SessionTranscodeResource()->getUrl(trackId, encoding, 0, streams) << "'"; - - //TODO, try to load everything in JS in order to prevent the WriteError bug? - _audio->pause(); - _audio->clearSources(); - _audio->addSource(SessionTranscodeResource()->getUrl(trackId, encoding, 0, streams)); - _audio->setPreloadMode(Wt::WAudio::PreloadNone); - _audio->play(); - - return true; -} - -AudioPlayer::AudioPlayer(ControlFlags controls, Wt::WContainerWidget *parent) -: Wt::WTemplate(parent) -{ - setTemplateText(Wt::WString::tr("wa-audio-player")); - addStyleClass("mediaplayer"); - - // TODO potential leak here - _audio = new Wt::WAudio(); - _audio->setOptions(Wt::WAudio::Autoplay); - _audio->setPreloadMode(Wt::WAudio::PreloadNone); - bindWidget("audio", _audio); - - _audio->ended().connect(std::bind([=] () - { - _playbackEnded.emit(); - })); - - _cover = new Wt::WImage(); - bindWidget("cover", _cover); - _cover->setImageLink(SessionImageResource()->getUnknownTrackUrl(64)); - - InputRange *seekbar = new InputRange(); - seekbar->addStyleClass("mediaplayer-seekbar"); - bindWidget("seekbar", seekbar); - - bindString("track", "Track"); - bindString("artist", "Artist"); - - InputRange *volumeSlider = new InputRange(); - volumeSlider->addStyleClass("mediaplayer-volume"); - volumeSlider->setAttributeValue("orient", "vertical"); // firefox - bindWidget("volume", volumeSlider); - - if (controls & ControlRepeat) - { - setCondition("if-has-repeat", true); - Wt::WText *repeatBtn = new Wt::WText("", Wt::XHTMLText); - repeatBtn->addStyleClass("mediaplayer-btn hidden-xs"); - bindWidget("repeat", repeatBtn); - repeatBtn->clicked().connect(std::bind([=] () - { - if (repeatBtn->hasStyleClass("mediaplayer-btn-active")) - { - repeatBtn->removeStyleClass("mediaplayer-btn-active"); - _loop.emit(false); - } - else - { - repeatBtn->addStyleClass("mediaplayer-btn-active"); - _loop.emit(true); - } - })); - } - - if (controls & ControlPlayqueue) - { - setCondition("if-has-playqueue", true); - Wt::WText *playQueueBtn = new Wt::WText("", Wt::XHTMLText); - bindWidget("playqueue", playQueueBtn); - playQueueBtn->addStyleClass("mediaplayer-btn"); - playQueueBtn->clicked().connect(std::bind([=] () - { - _playQueue.emit(); - })); - } - - if (controls & ControlShuffle) - { - setCondition("if-has-shuffle", true); - Wt::WText *shuffleBtn = new Wt::WText("", Wt::XHTMLText); - shuffleBtn->addStyleClass("mediaplayer-btn hidden-xs"); - bindWidget("shuffle", shuffleBtn); - shuffleBtn->clicked().connect(std::bind([=] () - { - if (shuffleBtn->hasStyleClass("mediaplayer-btn-active")) - { - shuffleBtn->removeStyleClass("mediaplayer-btn-active"); - _shuffle.emit(false); - } - else - { - shuffleBtn->addStyleClass("mediaplayer-btn-active"); - _shuffle.emit(true); - } - })); - } - - Wt::WText *prevBtn = new Wt::WText("", Wt::XHTMLText); - prevBtn->addStyleClass("mediaplayer-btn"); - bindWidget("prev", prevBtn); - prevBtn->clicked().connect(std::bind([=] () - { - _playPrevious.emit(); - })); - - Wt::WText *nextBtn = new Wt::WText("", Wt::XHTMLText); - nextBtn->addStyleClass("mediaplayer-btn"); - bindWidget("next", nextBtn); - nextBtn->clicked().connect(std::bind([=] () - { - _playNext.emit(); - })); - - Wt::WText *playPauseBtn = new Wt::WText("", Wt::XHTMLText); - playPauseBtn->addStyleClass("mediaplayer-btn"); - bindWidget("play-pause", playPauseBtn); - - _trackCurTime = new Wt::WText("--:--"); - _trackCurTime->addStyleClass("hidden-xs badge mediaplayer-badge mediaplayer-current-duration"); - bindWidget("curtime", _trackCurTime); - - _trackDuration = new Wt::WText("--:--"); - _trackDuration->addStyleClass("hidden-xs badge mediaplayer-badge mediaplayer-total-duration"); - bindWidget("duration", _trackDuration); - - this->doJavaScript( - "\ - document.lms = {};\ - document.lms.audio = {};\ - document.lms.audio.audio = " + _audio->jsRef() + ";\ - document.lms.audio.seekbar = " + seekbar->jsRef() +";\ - document.lms.audio.volumeSlider = " + volumeSlider->jsRef() + ";\ - document.lms.audio.curTimeText = " + _trackCurTime->jsRef() + ";\ - document.lms.audio.playPause = " + playPauseBtn->jsRef() + ";\ - \ - document.lms.audio.offset = 0;\ - document.lms.audio.curTime = 0;\ - document.lms.audio.state = \"init\";\ - document.lms.audio.volume = 1;\ - \ - document.lms.audio.seekbar.value = 0;\ - document.lms.audio.seekbar.disabled = true;\ - \ - document.lms.audio.volumeSlider.min = 0;\ - document.lms.audio.volumeSlider.max = 100;\ - document.lms.audio.volumeSlider.value = 100;\ - \ - function updateUIPlaying() { \ - var icon = document.lms.audio.playPause.getElementsByTagName(\"i\")[0]; \ - icon.className = \"fa fa-pause fa-3x fa-fw\"; \ - } \ - \ - function updateUIStopped() { \ - var icon = document.lms.audio.playPause.getElementsByTagName(\"i\")[0]; \ - icon.className = \"fa fa-play fa-3x fa-fw\"; \ - } \ - \ - function durationToString(duration, displayHours) { \ - var hours = parseInt( duration / 3600 ) % 24; \ - var minutes = parseInt( duration / 60) % 60; \ - var seconds = duration % 60; \ - \ - var res = \"\"; \ - if (displayHours) \ - res = (hours < 10 ? \"0\" + hours : hours) + \":\"; \ - \ - res += (minutes < 10 ? \"0\" + minutes : minutes) + \":\"; \ - res += (seconds < 10 ? \"0\" + seconds : seconds); \ - return res; \ - } \ - \ - function updateUI() {\ - document.lms.audio.curTimeText.innerHTML = durationToString(document.lms.audio.curTime, document.lms.audio.seekbar.max > 3600); \ - document.lms.audio.seekbar.value = document.lms.audio.curTime;\ - }\ - \ - var mouseDown = 0;\ - function seekMouseDown(e) {\ - ++mouseDown;\ - }\ - function seekMouseUp(e) {\ - --mouseDown;\ - }\ - \ - function seeking(e) {\ - if (document.lms.audio.state == \"init\")\ - return;\ - \ - document.lms.audio.curTimeText.innerHTML = durationToString(document.lms.audio.seekbar.value, document.lms.audio.seekbar.max > 3600); \ - }\ - \ - function seek(e) {\ - if (document.lms.audio.state == \"init\")\ - return;\ - \ - document.lms.audio.audio.pause(); \ - document.lms.audio.offset = parseInt(document.lms.audio.seekbar.value);\ - document.lms.audio.curTime = document.lms.audio.seekbar.value;\ - var audioSource = document.lms.audio.audio.getElementsByTagName(\"source\")[0];\ - var src = audioSource.src;\ - src = src.slice(0, src.lastIndexOf(\"=\") + 1);\ - audioSource.src = src + document.lms.audio.seekbar.value;\ - document.lms.audio.audio.load(); \ - document.lms.audio.audio.play(); \ - document.lms.audio.curTimeText.innerHTML = durationToString(document.lms.audio.curTime, document.lms.audio.seekbar.max > 3600); \ - }\ - \ - function volumeChanged() {\ - document.lms.audio.audio.volume = document.lms.audio.volumeSlider.value / 100;\ - }\ - \ - function updateCurTime() {\ - document.lms.audio.curTime = document.lms.audio.offset + ~~document.lms.audio.audio.currentTime; \ - if (mouseDown == 0)\ - updateUI();\ - } \ - \ - function playPause() { \ - if (document.lms.audio.state == \"init\") \ - return;\ - \ - if (document.lms.audio.audio.paused) \ - document.lms.audio.audio.play(); \ - else \ - document.lms.audio.audio.pause();\ - \ - }\ - \ - document.lms.audio.audio.addEventListener('timeupdate', updateCurTime); \ - document.lms.audio.audio.addEventListener('playing', updateUIPlaying); \ - document.lms.audio.audio.addEventListener('play', updateUIPlaying); \ - document.lms.audio.audio.addEventListener('pause', updateUIStopped); \ - document.lms.audio.audio.addEventListener('ended', updateUIStopped); \ - document.lms.audio.seekbar.addEventListener('change', seek);\ - document.lms.audio.seekbar.addEventListener('input', seeking);\ - document.lms.audio.seekbar.addEventListener('mousedown', seekMouseDown);\ - document.lms.audio.seekbar.addEventListener('mouseup', seekMouseUp);\ - document.lms.audio.volumeSlider.addEventListener('input', volumeChanged);\ - document.lms.audio.playPause.addEventListener('click', playPause);\ - " - ); -} - - -} // namespaceUserInterface diff --git a/src/ui/audio/AudioPlayer.hpp b/src/ui/audio/AudioPlayer.hpp deleted file mode 100644 index 669091e7..00000000 --- a/src/ui/audio/AudioPlayer.hpp +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2015 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 . - */ - -#pragma once - -#include -#include - -#include "common/InputRange.hpp" -#include "database/Types.hpp" -#include "av/AvTranscoder.hpp" - -namespace UserInterface { - -class AudioPlayer : public Wt::WTemplate -{ - public: - - enum ControlFlags - { - ControlNone = 0, - ControlShuffle = 1 << 0, - ControlRepeat = 1 << 1, - ControlPlayqueue = 1 << 2, - }; - - AudioPlayer(ControlFlags controls, Wt::WContainerWidget *parent = 0); - - Av::Encoding getBestEncoding() const; - bool loadTrack(Database::Track::id_type trackId); - - // Signals - Wt::Signal& playbackEnded() {return _playbackEnded;} - Wt::Signal& playNext() {return _playNext;} - Wt::Signal& playPrevious() {return _playPrevious;} - Wt::Signal& shuffle() {return _shuffle;} - Wt::Signal& loop() {return _loop;} - Wt::Signal& showPlayQueue() {return _playQueue;} - - private: - - // Signals - Wt::Signal _playbackEnded; - Wt::Signal _playNext; - Wt::Signal _playPrevious; - Wt::Signal _shuffle; - Wt::Signal _loop; - Wt::Signal _playQueue; - - Wt::WAudio* _audio; - Wt::WText* _trackCurTime; - Wt::WText* _trackDuration; - Wt::WText* _trackName; - Wt::WImage* _cover; -}; - -constexpr AudioPlayer::ControlFlags operator|(AudioPlayer::ControlFlags f1, AudioPlayer::ControlFlags f2) { return AudioPlayer::ControlFlags(int(f1)|int(f2)); } - - -} // namespace UserInterface diff --git a/src/ui/audio/desktop/DesktopAudio.cpp b/src/ui/audio/desktop/DesktopAudio.cpp deleted file mode 100644 index 63e73c53..00000000 --- a/src/ui/audio/desktop/DesktopAudio.cpp +++ /dev/null @@ -1,556 +0,0 @@ -/* - * 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 . - */ - -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "utils/Logger.hpp" - -#include "LmsApplication.hpp" - -#include "TableFilter.hpp" -#include "KeywordSearchFilter.hpp" - -#include "DesktopAudio.hpp" - -namespace { - -void WPopupMenuClear(Wt::WPopupMenu* menu) -{ - while(menu->count() > 0) - menu->removeItem(menu->itemAt(0)); -} - -} - -namespace UserInterface { -namespace Desktop { - -using namespace Database; - -// Special playlist generated each time the playque gets changed -// Restored at the beginning of the session -static const std::string CurrentQueuePlaylistName = "__current__"; - -Audio::Audio(Wt::WContainerWidget* parent) -: UserInterface::Audio(parent), -_mediaPlayer(nullptr), -_trackView(nullptr), -_playQueue(nullptr) -{ - Wt::WGridLayout *mainLayout = new Wt::WGridLayout(); - this->setLayout(mainLayout); - mainLayout->setContentsMargins(9,4,9,9); - - // Filters - Wt::WHBoxLayout *filterLayout = new Wt::WHBoxLayout(); - - TableFilterCluster *filterCluster = new TableFilterCluster(); - filterLayout->addWidget(filterCluster); - _filterChain.addFilter(filterCluster); - - TableFilterArtist *filterArtist = new TableFilterArtist(); - filterLayout->addWidget(filterArtist); - _filterChain.addFilter(filterArtist); - - TableFilterRelease *filterRelease = new TableFilterRelease(); - filterLayout->addWidget(filterRelease); - _filterChain.addFilter(filterRelease); - - mainLayout->addLayout(filterLayout, 0, 1); - // ENDOF(Filters) - - // TODO ADD some stats (nb files, total duration, etc.) - - Wt::WVBoxLayout* trackLayout = new Wt::WVBoxLayout(); - - _trackView = new TrackView(); - trackLayout->addWidget(_trackView, 1); - - Wt::WHBoxLayout* trackControls = new Wt::WHBoxLayout(); - - Wt::WPushButton* playBtn = new Wt::WPushButton("Play"); - playBtn->setStyleClass("btn-sm"); - trackControls->addWidget(playBtn); - - Wt::WPushButton* addBtn = new Wt::WPushButton("Add"); - addBtn->setStyleClass("btn-sm"); - trackControls->addWidget(addBtn); - - Wt::WText *statsText = new Wt::WText(); - statsText->setStyleClass("vertical-align"); - trackControls->addWidget(statsText, 1); - _trackView->statsUpdated().connect(std::bind([=] (Wt::WString stats) { - statsText->setText(stats); - }, std::placeholders::_1)); - - trackLayout->addLayout(trackControls); - - mainLayout->addLayout(trackLayout, 1, 1); - - _filterChain.addFilter(_trackView); - - - _playQueue = new PlayQueue(); - - // Playlist/PlayQueue - Wt::WContainerWidget* playQueueContainer = new Wt::WContainerWidget(); - playQueueContainer->setStyleClass("playqueue"); - Wt::WVBoxLayout* playQueueLayout = new Wt::WVBoxLayout(); - playQueueContainer->setLayout(playQueueLayout); - - playQueueLayout->addWidget( _playQueue, 1); - - Wt::WHBoxLayout* playlistControls = new Wt::WHBoxLayout(); - - Wt::WPushButton *playlistBtn = new Wt::WPushButton("Playlist"); - playlistBtn->setStyleClass("btn-sm btn-primary"); - playlistControls->addWidget(playlistBtn); - - // Playlist menu - { - Wt::WPopupMenu *popupMain = new Wt::WPopupMenu(); - - _popupMenuSave = new Wt::WPopupMenu(); - popupMain->addMenu("Save", _popupMenuSave); - - _popupMenuLoad = new Wt::WPopupMenu(); - popupMain->addMenu("Load", _popupMenuLoad); - - _popupMenuDelete = new Wt::WPopupMenu(); - popupMain->addMenu("Delete", _popupMenuDelete); - - playlistBtn->setMenu(popupMain); - } - -#if WT_VERSION >= 0X03030400 - Wt::WPushButton *upBtn = new Wt::WPushButton("", Wt::XHTMLText); -#else - Wt::WPushButton *upBtn = new Wt::WPushButton("UP"); -#endif - upBtn->setStyleClass("btn-sm"); - playlistControls->addWidget(upBtn); - -#if WT_VERSION >= 0X03030400 - Wt::WPushButton *downBtn = new Wt::WPushButton("", Wt::XHTMLText); -#else - Wt::WPushButton *downBtn = new Wt::WPushButton("DOWN"); -#endif - downBtn->setStyleClass("btn-sm"); - playlistControls->addWidget(downBtn); -#if WT_VERSION >= 0X03030400 - Wt::WPushButton *delBtn = new Wt::WPushButton("", Wt::XHTMLText); -#else - Wt::WPushButton *delBtn = new Wt::WPushButton("DEL"); -#endif - - delBtn->setStyleClass("btn-sm btn-warning"); - playlistControls->addWidget(delBtn); -#if WT_VERSION >= 0X03030400 - Wt::WPushButton *clearBtn = new Wt::WPushButton("", Wt::XHTMLText); -#else - Wt::WPushButton *clearBtn = new Wt::WPushButton("CLR"); -#endif - clearBtn->setStyleClass("btn-sm btn-danger"); - playlistControls->addWidget(clearBtn); - - delBtn->clicked().connect(_playQueue, &PlayQueue::delSelected); - upBtn->clicked().connect(_playQueue, &PlayQueue::moveSelectedUp); - downBtn->clicked().connect(_playQueue, &PlayQueue::moveSelectedDown); - clearBtn->clicked().connect(std::bind([=] - { - Wt::WMessageBox *messageBox = new Wt::WMessageBox - ("Clear playqueue", - Wt::WString( "Are you sure?"), - Wt::Question, Wt::Yes | Wt::No); - - messageBox->setModal(true); - - messageBox->buttonClicked().connect(std::bind([=] () - { - if (messageBox->buttonResult() == Wt::Yes) - _playQueue->delAll(); - - delete messageBox; - })); - - messageBox->show(); - })); - - playQueueLayout->addLayout(playlistControls); - - mainLayout->addWidget(playQueueContainer, 0, 0, 2, 1); - - // Load the last known queue - playlistLoadToPlayqueue(CurrentQueuePlaylistName); - - // Select the last known playing track - { - Wt::Dbo::Transaction transaction(DboSession()); - _playQueue->select(CurrentUser()->getCurPlayingTrackPos()); - } - - _mediaPlayer = new AudioPlayer(AudioPlayer::ControlShuffle | AudioPlayer::ControlRepeat); - mainLayout->addWidget(_mediaPlayer, 2, 0, 1, 4); - - mainLayout->setRowStretch(1, 1); - mainLayout->setRowResizable(0, true, Wt::WLength(250, Wt::WLength::Pixel)); - mainLayout->setColumnResizable(0, true, Wt::WLength(400, Wt::WLength::Pixel)); - - // Double click on track - // Set the selected tracks to the play queue - _trackView->trackDoubleClicked().connect(boost::bind(&Audio::playSelectedTracks, this, PlayQueueAddSelectedTracks)); - - // Double click on artist - // Set the selected tracks to the play queue - filterArtist->sigDoubleClicked().connect(boost::bind(&Audio::playSelectedTracks, this, PlayQueueAddAllTracks)); - filterRelease->sigDoubleClicked().connect(boost::bind(&Audio::playSelectedTracks, this, PlayQueueAddAllTracks)); - filterCluster->sigDoubleClicked().connect(boost::bind(&Audio::playSelectedTracks, this, PlayQueueAddAllTracks)); - - // Play button - // Set the selected tracks to the play queue - playBtn->clicked().connect(boost::bind(&Audio::playSelectedTracks, this, PlayQueueAddSelectedTracks)); - - // Add Button - // Add the selected tracks at the end of the play queue - addBtn->clicked().connect(this, &Audio::addSelectedTracks); - - _playQueue->playTrack().connect(this, &Audio::playTrack); - - _mediaPlayer->playbackEnded().connect(_playQueue, &PlayQueue::handlePlaybackComplete); - _mediaPlayer->playNext().connect(_playQueue, &PlayQueue::playNext); - _mediaPlayer->playPrevious().connect(_playQueue, &PlayQueue::playPrevious); - _mediaPlayer->shuffle().connect(boost::bind(&PlayQueue::setShuffle, _playQueue, _1)); - _mediaPlayer->loop().connect(boost::bind(&PlayQueue::setLoop,_playQueue, _1)); - - _playQueue->tracksUpdated().connect(std::bind([=] () { - LMS_LOG(UI, INFO) << "Playqueue updated!"; - - playlistSaveFromPlayqueue(CurrentQueuePlaylistName); - })); - - - playlistRefreshMenus(); - - // Initially, search for everything - _filterChain.searchKeyword(""); -} - -void -Audio::playlistShowSaveNewDialog() -{ - Wt::WDialog *dialog = new Wt::WDialog("New playlist"); - - Wt::WLabel *label = new Wt::WLabel("Name", dialog->contents()); - Wt::WLineEdit *edit = new Wt::WLineEdit(dialog->contents()); - label->setBuddy(edit); - - Wt::WLengthValidator* validator = new Wt::WLengthValidator(); - validator->setMinimumLength(3); - validator->setMandatory(true); - edit->setValidator(validator); - - Wt::WPushButton *save = new Wt::WPushButton("Save", dialog->footer()); - save->setStyleClass("btn-success"); - save->setDefault(true); - save->disable(); - - Wt::WPushButton *cancel = new Wt::WPushButton("Cancel", dialog->footer()); - dialog->rejectWhenEscapePressed(); - - edit->keyWentUp().connect(std::bind([=] () { - save->setDisabled(edit->validate() != Wt::WValidator::Valid); - })); - - save->clicked().connect(std::bind([=] () - { - if (edit->validate()) - dialog->accept(); - })); - - cancel->clicked().connect(dialog, &Wt::WDialog::reject); - - dialog->finished().connect(std::bind([=] () - { - if (dialog->result() == Wt::WDialog::Accepted) - { - playlistShowSaveDialog(edit->text().toUTF8()); - } - - delete dialog; - })); - - dialog->show(); -} - -void -Audio::playlistShowSaveDialog(std::string playlistName) -{ - Wt::Dbo::Transaction transaction(DboSession()); - - // Actually create the dialog only if the given list already exists - if (Playlist::get(DboSession(), playlistName, CurrentUser())) - { - Wt::WMessageBox *messageBox = new Wt::WMessageBox - ("Overwrite playlist", - Wt::WString( "Overwrite playlist '{1}'?").arg(playlistName), - Wt::Question, Wt::Yes | Wt::No); - - messageBox->setModal(true); - - messageBox->buttonClicked().connect(std::bind([=] () { - if (messageBox->buttonResult() == Wt::Yes) - playlistSaveFromPlayqueue(playlistName); - - delete messageBox; - })); - - messageBox->show(); - } - else - { - playlistSaveFromPlayqueue(playlistName); - playlistRefreshMenus(); - } -} - -void -Audio::playlistSaveFromPlayqueue(std::string playlistName) -{ - LMS_LOG(UI, INFO) << "Saving playqueue to playlist '" << playlistName << "'"; - - Wt::Dbo::Transaction transaction(DboSession()); - - Playlist::pointer playlist = Playlist::get(DboSession(), playlistName, CurrentUser()); - if (playlist) - { - LMS_LOG(UI, INFO) << "Erasing playlist '" << playlistName << "'"; - playlist.remove(); - } - - playlist = Playlist::create(DboSession(), playlistName, false, CurrentUser()); - - std::vector trackIds; - _playQueue->getTracks(trackIds); - - int pos = 0; - for (Track::id_type trackId : trackIds) - { - Track::pointer track = Track::getById(DboSession(), trackId); - - if (track) - PlaylistEntry::create(DboSession(), track, playlist, pos++); - } - - LMS_LOG(UI, INFO) << "Saving playqueue to playlist '" << playlistName << "' done. Contains " << pos << " entries"; -} - -void -Audio::playlistLoadToPlayqueue(std::string playlistName) -{ - LMS_LOG(UI, DEBUG) << "Loading playlist '" << playlistName << "' to playqueue"; - - std::vector entries; - - { - Wt::Dbo::Transaction transaction(DboSession()); - - Playlist::pointer playlist = Playlist::get(DboSession(), playlistName, CurrentUser()); - if (!playlist) - return; - - entries = PlaylistEntry::getEntries(DboSession(), playlist); - } - - _playQueue->clear(); - _playQueue->addTracks(entries); - - LMS_LOG(UI, DEBUG) << "Loading playlist '" << playlistName << "' to playqueue done. " << entries.size() << " entries"; -} - - -void -Audio::playlistShowDeleteDialog(std::string name) -{ - - Wt::WMessageBox *messageBox = new Wt::WMessageBox - ("Delete playlist", - Wt::WString( "Deleting playlist '{1}'?").arg(name), - Wt::Question, Wt::Yes | Wt::No); - - messageBox->setModal(true); - - messageBox->buttonClicked().connect(std::bind([=] () { - if (messageBox->buttonResult() == Wt::Yes) - { - Wt::Dbo::Transaction transaction(DboSession()); - - Playlist::pointer playlist = Playlist::get(DboSession(), name, CurrentUser()); - if (playlist) - playlist.remove(); - - playlistRefreshMenus(); - } - - delete messageBox; - })); - - messageBox->show(); -} - -void -Audio::playlistRefreshMenus() -{ - Wt::Dbo::Transaction transaction(DboSession()); - - // Clear playlists in each menu - LMS_LOG(UI, DEBUG) << "Save item count: " << _popupMenuSave->count(); - - WPopupMenuClear(_popupMenuDelete); - WPopupMenuClear(_popupMenuLoad); - WPopupMenuClear(_popupMenuSave); - - _popupMenuSave->addItem("New")->triggered().connect(std::bind([=] () - { - playlistShowSaveNewDialog(); - })); - _popupMenuSave->addSeparator(); - - std::vector playlists = Playlist::get(DboSession(), CurrentUser()); - - for (Playlist::pointer playlist : playlists) - { - if (playlist->getName() == CurrentQueuePlaylistName) - continue; - - // Add playlists in each menu - _popupMenuDelete->addItem(playlist->getName())->triggered().connect(std::bind([=] () - { - playlistShowDeleteDialog(playlist->getName()); - })); - - _popupMenuLoad->addItem(playlist->getName())->triggered().connect(std::bind([=] () - { - playlistLoadToPlayqueue(playlist->getName()); - playlistRefreshMenus(); // in case deleted in other session - })); - - _popupMenuSave->addItem(playlist->getName())->triggered().connect(std::bind([=] () - { - playlistShowSaveDialog(playlist->getName()); - })); - } -} - - -void -Audio::search(std::string searchText) -{ - _filterChain.searchKeyword(searchText); -} - -void -Audio::addSelectedTracks(void) -{ - std::vector trackIds; - _trackView->getSelectedTracks(trackIds); - - // If nothing is selected, get the whole track list - if (trackIds.empty()) - _trackView->getTracks(trackIds); - - _playQueue->addTracks(trackIds); -} - -void -Audio::playSelectedTracks(PlayQueueAddType addType) -{ - std::vector trackIds; - - LMS_LOG(UI, DEBUG) << "Playing selected tracks... nb selected = " << _trackView->getNbSelectedTracks() << ", add type = " << (addType == PlayQueueAddAllTracks ? "AddAll" : "AddSelected"); - - _playQueue->clear(); - - switch(addType) - { - case PlayQueueAddAllTracks: - // Play all the tracks - _trackView->getTracks(trackIds); - _playQueue->addTracks(trackIds); - _playQueue->play(); - - break; - case PlayQueueAddSelectedTracks: - - LMS_LOG(UI, DEBUG) << "Adding selected tracks..."; - - // If nothing selected, get all the track and play everything - if (_trackView->getNbSelectedTracks() == 0) - { - _trackView->getTracks(trackIds); - _playQueue->addTracks(trackIds); - _playQueue->play(); - } - // If only ONE selected, get them all and pre select the track - else if (_trackView->getNbSelectedTracks() == 1) - { - // Start to play at the selected track, if any - _trackView->getTracks(trackIds); - _playQueue->addTracks(trackIds); - _playQueue->play(_trackView->getFirstSelectedTrackPosition()); - } - else - { - // Play all the selected tracks - _trackView->getSelectedTracks(trackIds); - _playQueue->addTracks(trackIds); - _playQueue->play(); - } - break; - } -} - -void -Audio::playTrack(Track::id_type trackId, int pos) -{ - { - Wt::Dbo::Transaction transaction(DboSession()); - - // Update user current track position - CurrentUser().modify()->setCurPlayingTrackPos(pos); - } - - if (!_mediaPlayer->loadTrack(trackId)) - _playQueue->playNext(); -} - -} // namespace Desktop -} // namespace UserInterface - diff --git a/src/ui/audio/desktop/DesktopAudio.hpp b/src/ui/audio/desktop/DesktopAudio.hpp deleted file mode 100644 index ac7bda65..00000000 --- a/src/ui/audio/desktop/DesktopAudio.hpp +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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 . - */ - -#pragma once - -#include - -#include - -#include "audio/AudioPlayer.hpp" -#include "TrackView.hpp" -#include "PlayQueue.hpp" - -#include "FilterChain.hpp" - -#include "audio/Audio.hpp" - -namespace UserInterface { -namespace Desktop { - -class Audio : public UserInterface::Audio -{ - - public: - - Audio(Wt::WContainerWidget* parent = 0); - - void search(std::string searchText); - - private: - - void playlistSaveFromPlayqueue(std::string name); - void playlistLoadToPlayqueue(std::string name); - void playlistShowSaveNewDialog(); - void playlistShowSaveDialog(std::string name); - void playlistShowDeleteDialog(std::string name); - void playlistRefreshMenus(); - - void playTrack(Database::Track::id_type id, int pos); - - enum PlayQueueAddType - { - PlayQueueAddAllTracks, - PlayQueueAddSelectedTracks, - }; - - void playSelectedTracks(PlayQueueAddType addType); - void addSelectedTracks(); - - void handlePlaylistSelected(Wt::WString name); - - AudioPlayer* _mediaPlayer; - TrackView* _trackView; - PlayQueue* _playQueue; - - FilterChain _filterChain; - - Wt::WPopupMenu* _popupMenuSave; - Wt::WPopupMenu* _popupMenuLoad; - Wt::WPopupMenu* _popupMenuDelete; -}; - -} // namespace Desktop -} // namespace UserInterface - diff --git a/src/ui/audio/desktop/Filter.hpp b/src/ui/audio/desktop/Filter.hpp deleted file mode 100644 index 5feb120a..00000000 --- a/src/ui/audio/desktop/Filter.hpp +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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 . - */ - -#ifndef FILTER_HPP -#define FILTER_HPP - -#include - -#include "database/Types.hpp" - -namespace UserInterface { -namespace Desktop { - -class Filter -{ - public: - Filter() {} - virtual ~Filter() {} - - // Refresh filter using constraints created by parent filters - virtual void refresh(Database::SearchFilter& filter) = 0; - - // Update constraints for next filters - virtual void getConstraint(Database::SearchFilter& filter) = 0; - - // Emitted when a constraint has changed - Wt::Signal& update() { return _update; }; - - protected: - - void emitUpdate() { _update.emit(); } - - private: - - Wt::Signal _update; -}; - -} // namespace Dekstop -} // namespace UserInterface - -#endif diff --git a/src/ui/audio/desktop/FilterChain.cpp b/src/ui/audio/desktop/FilterChain.cpp deleted file mode 100644 index d94ddea5..00000000 --- a/src/ui/audio/desktop/FilterChain.cpp +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2014 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 . - */ - -#include "FilterChain.hpp" - -namespace UserInterface { -namespace Desktop { - - -FilterChain::FilterChain() -: _refreshingFilters(false) -{ - addFilter(&_keywordSearchFilter); -} - -void -FilterChain::addFilter(Filter* filter) -{ - filter->update().connect(boost::bind(&FilterChain::updateFilters, this, _filters.size())); - _filters.push_back(filter); -} - -void -FilterChain::searchKeyword(const std::string& text) -{ - _keywordSearchFilter.setText(text); -} - -void -FilterChain::updateFilters(std::size_t startIdx) -{ - // Prevent loops - if (_refreshingFilters) - return; - - _refreshingFilters = true; - - Database::SearchFilter searchFilter; - - for (std::size_t idFilter = 0; idFilter < _filters.size(); ++idFilter) - { - Filter* filter = _filters.at(idFilter); - - // Apply contraints created by previous filters - if (idFilter > startIdx) { - filter->refresh(searchFilter); - } - - // Get constraints generated by this filter - // (Note: adding accross successive calls) - filter->getConstraint(searchFilter); - } - - _refreshingFilters = false; -} - -} // namespace Desktop -} // namespace UserInterface - diff --git a/src/ui/audio/desktop/FilterChain.hpp b/src/ui/audio/desktop/FilterChain.hpp deleted file mode 100644 index c9625941..00000000 --- a/src/ui/audio/desktop/FilterChain.hpp +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2014 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 . - */ - -#ifndef FILTER_CHAIN_HPP -#define FILTER_CHAIN_HPP - -#include - -#include "Filter.hpp" -#include "KeywordSearchFilter.hpp" - -namespace UserInterface { -namespace Desktop { - -// FilterChain -class FilterChain -{ - public: - - FilterChain(); - - void addFilter(Filter* filter); - - // First filter is a keywork search - void searchKeyword(const std::string& text); - - // Update filters from filter @ startIdx - void updateFilters(std::size_t startIdx); - - private: - - KeywordSearchFilter _keywordSearchFilter; - - // No ownership - std::vector _filters; - - bool _refreshingFilters; -}; - -} // namespace Desktop -} // namespace UserInterface - -#endif diff --git a/src/ui/audio/desktop/KeywordSearchFilter.cpp b/src/ui/audio/desktop/KeywordSearchFilter.cpp deleted file mode 100644 index 3749960a..00000000 --- a/src/ui/audio/desktop/KeywordSearchFilter.cpp +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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 . - */ - -#include -#include -#include - -#include "KeywordSearchFilter.hpp" - -namespace UserInterface { -namespace Desktop { - -KeywordSearchFilter::KeywordSearchFilter() -{ -} - -void -KeywordSearchFilter::setText(const std::string& text) -{ - _lastEmittedText = text; - emitUpdate(); -} - -// Get constraints created by this filter -void -KeywordSearchFilter::getConstraint(Database::SearchFilter& filter) -{ - // No active search means no constaint! - if (!_lastEmittedText.empty()) { - std::vector values; - boost::algorithm::split(values, _lastEmittedText, boost::is_any_of(" "), boost::token_compress_on); - - // For each part, do a global search on all searchable fields - for (const std::string& value : values) - { - filter.nameLikeMatch.push_back({ - {Database::SearchFilter::Field::Artist, {value}}, - {Database::SearchFilter::Field::Release, {value}}, - {Database::SearchFilter::Field::Cluster, {value}}, - {Database::SearchFilter::Field::Track, {value}} - }); - } - } -} - -} // namespace Desktop -} // namespace UserInterface - diff --git a/src/ui/audio/desktop/KeywordSearchFilter.hpp b/src/ui/audio/desktop/KeywordSearchFilter.hpp deleted file mode 100644 index acf62b0b..00000000 --- a/src/ui/audio/desktop/KeywordSearchFilter.hpp +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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 . - */ - -#ifndef KEYWORD_SEARCH_FILTER_HPP -#define KEYWORD_SEARCH_FILTER_HPP - -#include - -#include "Filter.hpp" - -namespace UserInterface { -namespace Desktop { - -class KeywordSearchFilter : public Filter -{ - public: - KeywordSearchFilter(); - - void setText(const std::string& text); - - // Set constraint on this filter - void refresh(Database::SearchFilter& filter) {} - - // Get constraints created by this filter - void getConstraint(Database::SearchFilter& filter); - - private: - - void handleKeyWentUp(void); - - std::string _lastEmittedText; -}; - -} // namespace Desktop -} // namespace UserInterface - -#endif - diff --git a/src/ui/audio/desktop/PlayQueue.cpp b/src/ui/audio/desktop/PlayQueue.cpp deleted file mode 100644 index 8eea4231..00000000 --- a/src/ui/audio/desktop/PlayQueue.cpp +++ /dev/null @@ -1,634 +0,0 @@ -/* - * Copyright (C) 2014 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 . - */ - -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "utils/Logger.hpp" - -#include "LmsApplication.hpp" -#include "PlayQueue.hpp" - -static const int TrackInfoRole = Wt::UserRole; - -namespace { - - void swapRows(Wt::WStandardItemModel *model, int row1, int row2) - { - // Swap data column by column - for (int i = 0; i < model->columnCount(); ++i) - { - Wt::WModelIndex index1 = model->index(row1, i); - Wt::WModelIndex index2 = model->index(row2, i); - - // swap data associated with standard roles - { - auto tmp = model->itemData(index1); // 1 -> tmp - model->setItemData(index1, model->itemData(index2)); // 2 -> 1 - model->setItemData(index2, tmp); // tmp-> 2 - } - // caution: swap data associated with our custom roles if any!! - } - } -} - -namespace UserInterface { -namespace Desktop { - -enum ColumnId -{ - COLUMN_ID_TRACK_ID = 0, - COLUMN_ID_COVER = 1, - COLUMN_ID_NAME = 2, -}; - -static const int trackPosInvalid = -1; - -class TrackSelector -{ - public: - TrackSelector(); - - void setShuffle(bool enable); - void setLoop(bool enable) { _loop = enable; } - - int previous(void); - int next(void); - int getCurrent(void); - - // Set the internal pos thanks to the track pos - void setPosByRowId(int rowId); - void setPos(int pos) { _curPos = pos; } - - void setSize(std::size_t size); - std::size_t getSize(void) const { return _size;} - - private: - - void refreshPositions(); - - bool _loop; - bool _shuffle; - - std::size_t _size; - std::size_t _curPos; - - std::vector _trackPos; -}; - -TrackSelector::TrackSelector() -: _loop(false), -_shuffle(false), -_size(0), -_curPos(0) -{ -} - -void -TrackSelector::refreshPositions() -{ - _trackPos.clear(); - - if (_size == 0) - return; - - for (std::size_t i = 0; i < _size; ++i) - _trackPos.push_back(i); - - // Now shuffle - // Source: http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle - boost::random::mt19937 rng; // produces randomness out of thin air - rng.seed(static_cast(std::time(0))); - - for (std::size_t i = 0; i < _size; ++i) - { - boost::random::uniform_int_distribution<> distribution(i, _size - 1); - int val1 = distribution(rng); - int tmp = _trackPos[i]; - _trackPos[i] = _trackPos[val1]; - _trackPos[val1] = tmp; - } -} - -int -TrackSelector::getCurrent() -{ - return _shuffle ? _trackPos[_curPos] : _curPos; -} - -void -TrackSelector::setShuffle(bool enable) -{ - _shuffle = enable; - - if (_size != 0) - { - // Update the current index we are playing if we switch shuffle - if (_shuffle) - setPosByRowId(_curPos); - else - setPos(_trackPos[_curPos]); - } -} - -int -TrackSelector::next() -{ - if (_size == 0) - return trackPosInvalid; - - if (_curPos == _size - 1) - { - if (!_loop) - return trackPosInvalid; - else - _curPos = 0; - } - else - ++_curPos; - - return _shuffle ? _trackPos[_curPos] : _curPos; -} - -int -TrackSelector::previous() -{ - if (_size == 0) - return trackPosInvalid; - - if (_curPos == 0) - { - if (!_loop) - return trackPosInvalid; - else - _curPos = _size - 1; - } - else - --_curPos; - - return _shuffle ? _trackPos[_curPos] : _curPos; -} - -void -TrackSelector::setPosByRowId(int rowId) -{ - if (_shuffle) - { - for (std::size_t i = 0; i < _trackPos.size(); ++i) - { - if (rowId == _trackPos[i]) - { - _curPos = i; - break; - } - } - } - else - _curPos = rowId; -} - -void -TrackSelector::setSize(std::size_t size) -{ - _size = size; - _curPos = 0; - refreshPositions(); -} - -struct TrackInfo -{ - Wt::WString track; - Wt::WString artist; - Wt::WString release; -}; - -class PlayQueueItemDelegate : public Wt::WItemDelegate -{ - public: - PlayQueueItemDelegate(Wt::WObject *parent = 0) : Wt::WItemDelegate(parent) {} - - Wt::WWidget* update(Wt::WWidget *widget, const Wt::WModelIndex &index, Wt::WFlags< Wt::ViewItemRenderFlag > flags) - { - Wt::WWidget* res; - - if (!index.data(TrackInfoRole).empty()) - { - TrackInfo trackInfo = boost::any_cast(index.data(TrackInfoRole)); - - Wt::WContainerWidget *container = new Wt::WContainerWidget(); - Wt::WText* track = new Wt::WText(trackInfo.track, Wt::PlainText, container); - Wt::WText* artist = new Wt::WText(trackInfo.artist + " - " + trackInfo.release, Wt::PlainText, container); - - artist->setInline(false); - track->setInline(false); - - artist->setStyleClass("playqueue-artist"); - track->setStyleClass("playqueue-track"); - - // Apply style if any - Wt::WString styleClass = Wt::asString(index.data(Wt::StyleClassRole)); - - // Apply selection style if any - if (flags & Wt::RenderSelected) - styleClass += " " + Wt::WApplication::instance()->theme()->activeClass(); - - container->setStyleClass(styleClass); - - res = container; - } - else - res = Wt::WItemDelegate::update(widget, index, flags); - - return res; - } - - private: - -}; - - -PlayQueue::PlayQueue(Wt::WContainerWidget* parent) -: Wt::WTableView(parent), -_curPlayedTrackPos(trackPosInvalid), -_trackSelector(new TrackSelector()) -{ - _model = new Wt::WStandardItemModel(0, 3, this); - - // 0 Column is hidden (track id) - - this->setHeaderHeight(0); - this->setModel(_model); - this->setSelectionMode(Wt::ExtendedSelection); - this->setSortingEnabled(false); - this->setAlternatingRowColors(true); - this->setRowHeight(64); - this->setColumnWidth(COLUMN_ID_COVER, 64); - this->setColumnWidth(COLUMN_ID_NAME, 240); - - this->setLayoutSizeAware(true); -#if WT_VERSION >= 0X03030400 - this->setOverflow(Wt::WContainerWidget::OverflowHidden, Wt::Horizontal); - this->setOverflow(Wt::WContainerWidget::OverflowScroll, Wt::Vertical); -#endif - this->setColumnHidden(COLUMN_ID_TRACK_ID, true); - - _itemDelegate = new PlayQueueItemDelegate(); - this->setItemDelegate(_itemDelegate); - - this->doubleClicked().connect( std::bind([=] (Wt::WModelIndex idx, Wt::WMouseEvent evt) - { - if (!idx.isValid()) - return; - - // Update selection - Wt::WModelIndexSet indexSet; - indexSet.insert(idx); - - this->setSelectedIndexes( indexSet ); - - // Read the requested track - play(idx.row()); - - }, std::placeholders::_1, std::placeholders::_2)); - -} - -void -PlayQueue::layoutSizeChanged (int width, int height) -{ - std::size_t coverColumnSize = this->columnWidth(COLUMN_ID_COVER).toPixels(); - // Set the remaining size for the name column - this->setColumnWidth(COLUMN_ID_NAME, width - coverColumnSize - (7 * 2) - 2); -} - -void -PlayQueue::setShuffle(bool enable) -{ - _trackSelector->setShuffle(enable); -} - -void -PlayQueue::setLoop(bool enable) -{ - _trackSelector->setLoop(enable); -} - -void -PlayQueue::play() -{ - _trackSelector->setPos(0); - - readTrack(_trackSelector->getCurrent()); -} - -void -PlayQueue::play(int rowId) -{ - // Update the track selector to use the requested track as current position - _trackSelector->setPosByRowId(rowId); - - readTrack(_trackSelector->getCurrent()); -} - -void -PlayQueue::select(int rowId) -{ - // Update the track selector to use the requested track as current position - _trackSelector->setPosByRowId(rowId); - - Wt::WTableView::select(_model->index(_trackSelector->getCurrent(), 0)); - - this->scrollTo( _model->index(_trackSelector->getCurrent(), 0)); -} - -void -PlayQueue::addTracks(const std::vector& trackIds) -{ - using namespace Database; - - LMS_LOG(UI, DEBUG) << "Adding " << trackIds.size() << " tracks to play queue"; - - // Add tracks to model - for (Track::id_type trackId : trackIds) - { - Wt::Dbo::Transaction transaction(DboSession()); - - Track::pointer track (Track::getById(DboSession(), trackId)); - - if (track) - { - int dataRow = _model->rowCount(); - - _model->insertRows(dataRow, 1); - - _model->setData(dataRow, COLUMN_ID_TRACK_ID, track.id(), Wt::UserRole); - - std::string coverUrl; - coverUrl = SessionImageResource()->getTrackUrl(track.id(), 64); - - _model->setData(dataRow, COLUMN_ID_COVER, coverUrl, Wt::DecorationRole); - _model->setData(dataRow, COLUMN_ID_COVER, std::string("playqueue-cover"), Wt::StyleClassRole); - - TrackInfo trackInfo; - trackInfo.track = Wt::WString::fromUTF8(track->getName()); - trackInfo.artist = Wt::WString::fromUTF8(track->getArtist()->getName()); - trackInfo.release = Wt::WString::fromUTF8(track->getRelease()->getName()); - _model->setData(dataRow, COLUMN_ID_NAME, trackInfo, TrackInfoRole); - } - } - - _trackSelector->setSize( _model->rowCount() ); - - _sigTracksUpdated.emit(); -} - -void -PlayQueue::clear(void) -{ - _model->removeRows(0, _model->rowCount()); - - // Reset play id - _curPlayedTrackPos = trackPosInvalid; - _trackSelector->setSize( 0 ); - - _sigTracksUpdated.emit(); -} - -void -PlayQueue::handlePlaybackComplete(void) -{ - playNext(); -} - -void -PlayQueue::playNext(void) -{ - std::size_t nbTries = _trackSelector->getSize(); - - while (nbTries > 0) - { - int pos = _trackSelector->next(); - if (pos == trackPosInvalid) - break; - - readTrack(pos); - - break; - } -} - -void -PlayQueue::playPrevious(void) -{ - std::size_t nbTries = _trackSelector->getSize(); - - while (nbTries > 0) - { - int pos = _trackSelector->previous(); - if (pos == trackPosInvalid) - break; - - readTrack(pos); - break; - - } -} - -void -PlayQueue::readTrack(int rowPos) -{ - LMS_LOG(UI, DEBUG) << "Reading track at pos " << rowPos << ", row count = " << _model->rowCount(); - - if (rowPos < _model->rowCount()) - { - Database::Track::id_type trackId = boost::any_cast(_model->data(rowPos, COLUMN_ID_TRACK_ID, Wt::UserRole)); - - setPlayingTrackPos(rowPos); - - _sigTrackPlay.emit(trackId, rowPos); - - this->scrollTo( _model->index(_trackSelector->getCurrent(), 0)); - } -} - -void -PlayQueue::setPlayingTrackPos(int newRowPos) -{ - int oldRowPos = _curPlayedTrackPos; - _curPlayedTrackPos = newRowPos; - - // Hack re-set the data in order to trigger the rerending of the widget - // calling the update method of our custom item delegate gives bad results - if (oldRowPos >= 0) - { - _model->setData(oldRowPos, COLUMN_ID_NAME, boost::any(), Wt::StyleClassRole); - } - - if (newRowPos >= 0) - { - _model->setData(newRowPos, COLUMN_ID_NAME, std::string("playqueue-playing"), Wt::StyleClassRole); - } -} - -void -PlayQueue::delSelected(void) -{ - int minId = _model->rowCount(); - Wt::WModelIndexSet indexSet = this->selectedIndexes(); - - // Make sure to delete entries in the reverse order - std::vector rowIds; - for (Wt::WModelIndex index : indexSet) - rowIds.push_back(index.row()); - - std::sort(rowIds.begin(), rowIds.end(), std::greater()); - if (!rowIds.empty()) - minId = rowIds.back(); - - for (int rowId : rowIds) - _model->removeRow(rowId); - - // If the current played track is removed, make sure to unselect it - _trackSelector->setSize(_model->rowCount()); - - renumber(minId, _model->rowCount() - 1); - _sigTracksUpdated.emit(); -} - -void -PlayQueue::delAll(void) -{ - _model->removeRows(0, _model->rowCount()); - _trackSelector->setSize(0); - _sigTracksUpdated.emit(); -} - -void -PlayQueue::moveSelectedUp(void) -{ - int minId = _model->rowCount(); - int maxId = 0; - - Wt::WModelIndexSet indexSet = this->selectedIndexes(); - Wt::WModelIndexSet newIndexSet; - - // ordered from up to down - for (Wt::WModelIndex index : indexSet) - { - // Do nothing if the first selected index is on the top - if (index.row() == 0) - return; - - // TODO optimize for blocks - swapRows(_model, index.row() - 1, index.row()); - - if (index.row() - 1 < minId) - minId = index.row() - 1; - - if (index.row() > maxId) - maxId = index.row(); - - // Update playing status - if (_curPlayedTrackPos == index.row()) - setPlayingTrackPos(_curPlayedTrackPos - 1); - else if (_curPlayedTrackPos == index.row() - 1) - setPlayingTrackPos(_curPlayedTrackPos + 1); - - newIndexSet.insert( _model->index(index.row() - 1, 0)); - } - - _trackSelector->setPosByRowId(_curPlayedTrackPos); - this->setSelectedIndexes( newIndexSet ); - - renumber(minId, maxId); - - _sigTracksUpdated.emit(); -} - - -void -PlayQueue::moveSelectedDown(void) -{ - int minId = _model->rowCount(); - int maxId = 0; - - Wt::WModelIndexSet indexSet = this->selectedIndexes(); - Wt::WModelIndexSet newIndexSet; - - // ordered from up to down - for (Wt::WModelIndex index : indexSet) - { - // Do nothing if the last selected index is the last one - if (index.row() == _model->rowCount() - 1) - return; - - // TODO optimize for blocks - swapRows(_model, index.row(), index.row() + 1); - - if (index.row() < minId) - minId = index.row(); - - if (index.row() + 1 > maxId) - maxId = index.row() + 1; - - // Update playing status - if (_curPlayedTrackPos == index.row()) - setPlayingTrackPos(_curPlayedTrackPos + 1); - else if (_curPlayedTrackPos == index.row() + 1) - setPlayingTrackPos(_curPlayedTrackPos - 1); - - newIndexSet.insert( _model->index(index.row() + 1, 0)); - } - - _trackSelector->setPosByRowId(_curPlayedTrackPos); - this->setSelectedIndexes( newIndexSet ); - - renumber(minId, maxId); - - _sigTracksUpdated.emit(); -} - -void -PlayQueue::renumber(int firstId, int lastId) -{ - for (int i = firstId; i <= lastId; ++i) - _model->setData( i, 1, i + 1); -} - -void -PlayQueue::getTracks(std::vector& trackIds) const -{ - // Now add each entry in the playlist - for (int i = 0; i < _model->rowCount(); ++i) - { - Database::Track::id_type trackId = boost::any_cast(_model->data(i, COLUMN_ID_TRACK_ID, Wt::UserRole)); - trackIds.push_back(trackId); - } -} - -} // namespace Desktop -} // namespace UserInterface - diff --git a/src/ui/audio/desktop/PlayQueue.hpp b/src/ui/audio/desktop/PlayQueue.hpp deleted file mode 100644 index 0f0ca22d..00000000 --- a/src/ui/audio/desktop/PlayQueue.hpp +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2014 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 . - */ - -#ifndef PLAY_QUEUE_HPP -#define PLAY_QUEUE_HPP - -#include -#include - -#include "database/Types.hpp" - -namespace UserInterface { -namespace Desktop { - -class PlayQueueItemDelegate; -class TrackSelector; - -class PlayQueue : public Wt::WTableView -{ - public: - PlayQueue(Wt::WContainerWidget* parent = 0); - - void addTracks(const std::vector& trackIds); - void getTracks(std::vector& trackIds) const; - - void clear(void); - - void setShuffle(bool enable); // true to enable shuffle - void setLoop(bool enable); // true to enable loop - - // Play functions - void play(void); // Play the queue from the beginning - void play(int rowId); // Play the queue from the given rowId - void select(int rowId); // Select the given rowId - void playNext(void); // Play the next track - void playPrevious(void); // Play the previous track - - // List manipulations - void delSelected(void); - void delAll(void); - void moveSelectedUp(void); - void moveSelectedDown(void); - - // Signals - // Emitted when a song has to be played - Wt::Signal< Database::Track::id_type, int >& playTrack() { return _sigTrackPlay; } - // Emitted when the list has changed - Wt::Signal< void >& tracksUpdated() { return _sigTracksUpdated; } - - // Slots - void handlePlaybackComplete(void); - - private: - - void layoutSizeChanged (int width, int height); - - void readTrack(int rowId); - void setPlayingTrackPos(int newRowPos); - void renumber(int firstId, int lastId); - - Wt::Signal< Database::Track::id_type, int > _sigTrackPlay; - Wt::Signal< void > _sigTracksUpdated; - - Wt::WStandardItemModel* _model; - - PlayQueueItemDelegate* _itemDelegate; - - int _curPlayedTrackPos; - std::unique_ptr _trackSelector; - -}; - -} // namespace Desktop -} // namespace UserInterface - -#endif diff --git a/src/ui/audio/desktop/TableFilter.cpp b/src/ui/audio/desktop/TableFilter.cpp deleted file mode 100644 index 6af45e97..00000000 --- a/src/ui/audio/desktop/TableFilter.cpp +++ /dev/null @@ -1,282 +0,0 @@ -/* - * 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 . - */ - -#include -#include - -#include "database/Types.hpp" - -#include "utils/Logger.hpp" - -#include "LmsApplication.hpp" -#include "TableFilter.hpp" - -namespace UserInterface { -namespace Desktop { - -using namespace Database; - -TableFilterCluster::TableFilterCluster(Wt::WContainerWidget* parent) -: Wt::WTableView( parent ), Filter() -{ - const std::vector columnNames = {"Tag", "Tracks"}; - - SearchFilter filter; - - Cluster::updateUIQueryModel(DboSession(), _queryModel, filter, columnNames); - - this->setSelectionMode(Wt::ExtendedSelection); - this->setSortingEnabled(true); - this->setAlternatingRowColors(true); - this->setModel(&_queryModel); - - this->setColumnWidth(1, 80); - - this->selectionChanged().connect(this, &TableFilterCluster::emitUpdate); - -#if WT_VERSION >= 0X03030400 - this->setOverflow(Wt::WContainerWidget::OverflowHidden, Wt::Horizontal); - this->setOverflow(Wt::WContainerWidget::OverflowScroll, Wt::Vertical); -#endif - - this->setLayoutSizeAware(true); - - _queryModel.setBatchSize(150); - - // If an item is double clicked, select and emit signal - this->doubleClicked().connect( std::bind([=] (Wt::WModelIndex idx, Wt::WMouseEvent evt) - { - if (!idx.isValid()) - return; - - Wt::WModelIndexSet indexSet; - indexSet.insert(idx); - - this->setSelectedIndexes( indexSet ); - _sigDoubleClicked.emit( ); - - }, std::placeholders::_1, std::placeholders::_2)); - -} - -void -TableFilterCluster::layoutSizeChanged (int width, int height) -{ - std::size_t trackColumnSize = this->columnWidth(1).toPixels(); - // Set the remaining size for the name column - this->setColumnWidth(0, width - trackColumnSize - (7 * 2) - 2); -} - -// Set constraints on this filter -void -TableFilterCluster::refresh(SearchFilter& filter) -{ -#if WT_VERSION >= 0X03030500 - this->clearSelection(); -#endif - - Cluster::updateUIQueryModel(DboSession(), _queryModel, filter); -} - -// Get constraint created by this filter -void -TableFilterCluster::getConstraint(SearchFilter& filter) -{ - Wt::WModelIndexSet indexSet = this->selectedIndexes(); - - for (Wt::WModelIndex index : indexSet) - { - if (!index.isValid()) - continue; - - // TODO set invisible track id - Cluster::id_type id = _queryModel.resultRow( index.row() ).get<0>(); - - filter.idMatch[Database::SearchFilter::Field::Cluster].push_back(id); - } -} - -TableFilterArtist::TableFilterArtist(Wt::WContainerWidget* parent) -: Wt::WTableView( parent ), Filter() -{ - const std::vector columnNames = {"Artist", "Releases", "Tracks"}; - - SearchFilter filter; - - Artist::updateUIQueryModel(DboSession(), _queryModel, filter, columnNames); - - this->setSelectionMode(Wt::ExtendedSelection); - this->setSortingEnabled(true); - this->setAlternatingRowColors(true); - this->setModel(&_queryModel); - - this->setColumnWidth(1, 80); - this->setColumnWidth(2, 80); - - this->selectionChanged().connect(this, &TableFilterArtist::emitUpdate); - -#if WT_VERSION >= 0X03030400 - this->setOverflow(Wt::WContainerWidget::OverflowHidden, Wt::Horizontal); - this->setOverflow(Wt::WContainerWidget::OverflowScroll, Wt::Vertical); -#endif - - this->setLayoutSizeAware(true); - - _queryModel.setBatchSize(150); - - // If an item is double clicked, select and emit signal - this->doubleClicked().connect( std::bind([=] (Wt::WModelIndex idx, Wt::WMouseEvent evt) - { - if (!idx.isValid()) - return; - - Wt::WModelIndexSet indexSet; - indexSet.insert(idx); - - this->setSelectedIndexes( indexSet ); - _sigDoubleClicked.emit( ); - - }, std::placeholders::_1, std::placeholders::_2)); - -} - -void -TableFilterArtist::layoutSizeChanged (int width, int height) -{ - std::size_t otherColumnsSize = this->columnWidth(1).toPixels() + this->columnWidth(2).toPixels(); - // Set the remaining size for the name column - this->setColumnWidth(0, width - otherColumnsSize - (7 * 3) - 2); -} - -// Set constraints on this filter -void -TableFilterArtist::refresh(SearchFilter& filter) -{ -#if WT_VERSION >= 0X03030500 - this->clearSelection(); -#endif - Artist::updateUIQueryModel(DboSession(), _queryModel, filter); -} - -// Get constraint created by this filter -void -TableFilterArtist::getConstraint(SearchFilter& filter) -{ - Wt::WModelIndexSet indexSet = this->selectedIndexes(); - - for (Wt::WModelIndex index : indexSet) - { - if (!index.isValid()) - continue; - - Artist::id_type id = _queryModel.resultRow( index.row() ).get<0>(); - - filter.idMatch[SearchFilter::Field::Artist].push_back(id); - } -} - -TableFilterRelease::TableFilterRelease(Wt::WContainerWidget* parent) -: Wt::WTableView( parent ), Filter() -{ - const std::vector columnNames = {"Release", "Date", "Tracks"}; - - SearchFilter filter; - - Release::updateUIQueryModel(DboSession(), _queryModel, filter, columnNames); - - this->setSelectionMode(Wt::ExtendedSelection); - this->setSortingEnabled(true); - this->setAlternatingRowColors(true); - this->setModel(&_queryModel); - - this->setColumnWidth(1, 60); - this->setColumnWidth(2, 80); - - // Date display, just the year - { - Wt::WItemDelegate *delegate = new Wt::WItemDelegate(this); - delegate->setTextFormat("yyyy"); - this->setItemDelegateForColumn(1, delegate); - } - - this->selectionChanged().connect(this, &TableFilterRelease::emitUpdate); - -#if WT_VERSION >= 0X03030400 - this->setOverflow(Wt::WContainerWidget::OverflowHidden, Wt::Horizontal); - this->setOverflow(Wt::WContainerWidget::OverflowScroll, Wt::Vertical); -#endif - - this->setLayoutSizeAware(true); - - _queryModel.setBatchSize(150); - - // If an item is double clicked, select and emit signal - this->doubleClicked().connect( std::bind([=] (Wt::WModelIndex idx, Wt::WMouseEvent evt) - { - if (!idx.isValid()) - return; - - Wt::WModelIndexSet indexSet; - indexSet.insert(idx); - - this->setSelectedIndexes( indexSet ); - _sigDoubleClicked.emit( ); - - }, std::placeholders::_1, std::placeholders::_2)); - -} - -void -TableFilterRelease::layoutSizeChanged (int width, int height) -{ - std::size_t otherColumnSizes = this->columnWidth(1).toPixels() + this->columnWidth(2).toPixels(); - // Set the remaining size for the name column - this->setColumnWidth(0, width - otherColumnSizes - (7 * 3) - 2); -} - -// Set constraints on this filter -void -TableFilterRelease::refresh(SearchFilter& filter) -{ -#if WT_VERSION >= 0X03030500 - this->clearSelection(); -#endif - Release::updateUIQueryModel(DboSession(), _queryModel, filter); -} - -// Get constraint created by this filter -void -TableFilterRelease::getConstraint(SearchFilter& filter) -{ - Wt::WModelIndexSet indexSet = this->selectedIndexes(); - - for (Wt::WModelIndex index : indexSet) - { - if (!index.isValid()) - continue; - - Release::id_type id = _queryModel.resultRow( index.row() ).get<0>(); - - filter.idMatch[Database::SearchFilter::Field::Release].push_back(id); - } -} - -} // namespace Desktop -} // namespace UserInterface - diff --git a/src/ui/audio/desktop/TableFilter.hpp b/src/ui/audio/desktop/TableFilter.hpp deleted file mode 100644 index 243b17f2..00000000 --- a/src/ui/audio/desktop/TableFilter.hpp +++ /dev/null @@ -1,112 +0,0 @@ -/* - * 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 . - */ - -#ifndef TABLE_FILTER_HPP -#define TABLE_FILTER_HPP - - -#include -#include - -#include "Filter.hpp" -#include "database/Types.hpp" - -namespace UserInterface { -namespace Desktop { - -class TableFilterCluster : public Wt::WTableView, public Filter -{ - public: - TableFilterCluster(Wt::WContainerWidget* parent = 0); - - // Set constraints on this filter - void refresh(Database::SearchFilter& filter); - - // Get constraints created by this filter - void getConstraint(Database::SearchFilter& filter); - - void layoutSizeChanged (int width, int height); - - typedef Wt::Signal SigDoubleClicked; - - SigDoubleClicked& sigDoubleClicked() { return _sigDoubleClicked; } - - protected: - - SigDoubleClicked _sigDoubleClicked; - - Wt::Dbo::QueryModel _queryModel; -}; - -class TableFilterArtist : public Wt::WTableView, public Filter -{ - public: - TableFilterArtist(Wt::WContainerWidget* parent = 0); - - // Set constraints on this filter - void refresh(Database::SearchFilter& filter); - - // Get constraints created by this filter - void getConstraint(Database::SearchFilter& filter); - - void layoutSizeChanged (int width, int height); - - typedef Wt::Signal SigDoubleClicked; - - SigDoubleClicked& sigDoubleClicked() { return _sigDoubleClicked; } - - protected: - - SigDoubleClicked _sigDoubleClicked; - - // Name, track count - Wt::Dbo::QueryModel _queryModel; -}; - -class TableFilterRelease : public Wt::WTableView, public Filter -{ - public: - TableFilterRelease(Wt::WContainerWidget* parent = 0); - - // Set constraints on this filter - void refresh(Database::SearchFilter& filter); - - // Get constraints created by this filter - void getConstraint(Database::SearchFilter& filter); - - void layoutSizeChanged (int width, int height); - - typedef Wt::Signal SigDoubleClicked; - - SigDoubleClicked& sigDoubleClicked() { return _sigDoubleClicked; } - - protected: - - SigDoubleClicked _sigDoubleClicked; - - // Name, track count - Wt::Dbo::QueryModel _queryModel; -}; - - -} // namespace Desktop -} // namespace UserInterface - -#endif - diff --git a/src/ui/audio/desktop/TrackView.cpp b/src/ui/audio/desktop/TrackView.cpp deleted file mode 100644 index aaec7939..00000000 --- a/src/ui/audio/desktop/TrackView.cpp +++ /dev/null @@ -1,234 +0,0 @@ -/* - * 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 . - */ - -#include - -#include -#include -#include - -#include "utils/Logger.hpp" -#include "utils/Utils.hpp" - -#include "LmsApplication.hpp" -#include "TrackView.hpp" - -namespace UserInterface { -namespace Desktop { - -using namespace Database; - -class DurationItemDelegate : public Wt::WItemDelegate -{ - public: - DurationItemDelegate(Wt::WObject *parent = 0) : Wt::WItemDelegate(parent) {} - - Wt::WWidget* update(Wt::WWidget *widget, const Wt::WModelIndex &index, Wt::WFlags< Wt::ViewItemRenderFlag > flags) - { - boost::posix_time::time_duration duration = boost::any_cast(index.data(Wt::DisplayRole)); - - // TODO bug when redrawn - std::string format = duration.total_seconds() < 3600 ? "%M:%S" : "%H:%M:%S"; - - return new Wt::WText(durationToString(duration, format), Wt::PlainText); - } -}; - -TrackView::TrackView(Wt::WContainerWidget* parent) -: Wt::WTableView( parent ) -{ - - static const std::vector columnNames = - { - "Artist", - "Album", - "Disc #", - "Track #", - "Track", - "Duration", - "Date", - "Original Date", - "Genres", - }; - - SearchFilter filter; - - Track::updateUIQueryModel(DboSession(), _queryModel, filter, columnNames); - - _queryModel.setBatchSize(500); - - this->setSortingEnabled(true); - this->setSelectionMode(Wt::ExtendedSelection); - this->setAlternatingRowColors(true); - this->setModel(&_queryModel); - - this->setColumnWidth(0, 180); // Artist - this->setColumnWidth(1, 180); // Album - this->setColumnWidth(2, 70); // Disc Number - this->setColumnWidth(3, 70); // Track Number - this->setColumnWidth(4, 180); // Track - this->setColumnWidth(5, 70); // Duration - this->setColumnWidth(6, 70); // Date - this->setColumnWidth(7, 70); // Original Date - this->setColumnWidth(8, 180); // Genres -#if WT_VERSION >= 0X03030400 - this->setOverflow(Wt::WContainerWidget::OverflowScroll, Wt::Vertical); -#endif - - // Duration display - { - // TODO better handle 1 hour+ files! - DurationItemDelegate *delegate = new DurationItemDelegate(this); - this->setItemDelegateForColumn(5, delegate); - } - - // Date display, just the year - { - Wt::WItemDelegate *delegate = new Wt::WItemDelegate(this); - delegate->setTextFormat("yyyy"); - this->setItemDelegateForColumn(6, delegate); - } - { - Wt::WItemDelegate *delegate = new Wt::WItemDelegate(this); - delegate->setTextFormat("yyyy"); - this->setItemDelegateForColumn(7, delegate); - } - - // If an item is double clicked, select the track and emit signal - this->doubleClicked().connect( std::bind([=] (Wt::WModelIndex idx, Wt::WMouseEvent evt) - { - if (!idx.isValid()) - return; - - Wt::WModelIndexSet indexSet; - indexSet.insert(idx); - - this->setSelectedIndexes( indexSet ); - - _sigTrackDoubleClicked.emit( ); - - }, std::placeholders::_1, std::placeholders::_2)); - -} - -void -TrackView::emitStats(const SearchFilter& filter) -{ - Wt::Dbo::Transaction transaction (DboSession()); - - // Update stats on the view - Track::StatsQueryResult stats = Track::getStats(DboSession(), filter); - - transaction.commit(); - - int nbTracks = stats.get<0>(); - boost::posix_time::time_duration totalDuration = stats.get<1>(); - - std::ostringstream oss; - - oss << nbTracks << " track" << (nbTracks > 1 ? "s" : "") << ", "; - - if (totalDuration.hours() >= 24) - { - auto days = totalDuration.hours() / 24; - oss << days << " day" << (days > 1 ? "s " : " "); - } - - oss << std::setw(2) << std::setfill('0') << totalDuration.hours() % 24 - << ":" << std::setw(2) << std::setfill('0') << totalDuration.minutes() - << ":" << std::setw(2) << std::setfill('0') << totalDuration.seconds(); - - _sigStatsUpdated.emit(Wt::WString(oss.str())); -} - -// Set constraints created by parent filters -void -TrackView::refresh(SearchFilter& filter) -{ -#if WT_VERSION >= 0X03030500 - this->clearSelection(); -#endif - Track::updateUIQueryModel(DboSession(), _queryModel, filter); - - emitStats(filter); -} - -void -TrackView::getSelectedTracks(std::vector& track_ids) -{ - LMS_LOG(UI, DEBUG) << "Getting selected tracks..."; - - Wt::WModelIndexSet indexSet = this->selectedIndexes(); - - for (Wt::WModelIndex index : indexSet) - { - if (!index.isValid()) - continue; - - Track::id_type id = _queryModel.resultRow( index.row() ).get<0>(); - - track_ids.push_back(id); - } - - LMS_LOG(UI, DEBUG) << "Getting all selected tracks: " << track_ids.size(); -} - -std::size_t -TrackView::getNbSelectedTracks(void) -{ - return this->selectedIndexes().size(); -} - -int -TrackView::getFirstSelectedTrackPosition(void) -{ - Wt::WModelIndexSet indexSet = this->selectedIndexes(); - - for (Wt::WModelIndex index : indexSet) - { - if (!index.isValid()) - continue; - - return index.row(); - } - - return 0; -} - -void -TrackView::getTracks(std::vector& trackIds) -{ - LMS_LOG(UI, DEBUG) << "Getting all tracks..."; - - Wt::Dbo::Transaction transaction(DboSession()); - Wt::Dbo::Query query = _queryModel.query(); - Wt::Dbo::collection results = query.limit(-1).offset(-1); - - for (auto it = results.begin(); it != results.end(); ++it) - { - Track::id_type id = it->get<0>(); - trackIds.push_back(id); - } - - LMS_LOG(UI, DEBUG) << "Getting all tracks done! " << trackIds.size() << " tracks!"; -} - -} // namespace Desktop -} // namespace UserInterface - diff --git a/src/ui/audio/desktop/TrackView.hpp b/src/ui/audio/desktop/TrackView.hpp deleted file mode 100644 index 2fef0556..00000000 --- a/src/ui/audio/desktop/TrackView.hpp +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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 . - */ - -#ifndef TRACK_HPP -#define TRACK_HPP - -#include -#include - -#include "database/DatabaseHandler.hpp" - -#include "Filter.hpp" - -namespace UserInterface { -namespace Desktop { - -class TrackView : public Wt::WTableView, public Filter -{ - public: - - TrackView(Wt::WContainerWidget* parent = 0); - - // Filter interface - // Set constraints created by parent filters - void refresh(Database::SearchFilter& filter); - - // Create constraints for child filters (N/A) - void getConstraint(Database::SearchFilter& filter) {} - - // Get all the tracks that are currently selected - void getSelectedTracks(std::vector& trackIds); - - std::size_t getNbSelectedTracks(void); - - // Get the first position of the selected tracks (0 if nothing selected) - int getFirstSelectedTrackPosition(void); - - // Get all the tracks - void getTracks(std::vector& track_ids); - - typedef Wt::Signal SigTrackDoubleClicked; - typedef Wt::Signal SigStatsUpdated; - - SigTrackDoubleClicked& trackDoubleClicked() { return _sigTrackDoubleClicked; } - SigStatsUpdated& statsUpdated() { return _sigStatsUpdated; } - - private: - - SigTrackDoubleClicked _sigTrackDoubleClicked; - SigStatsUpdated _sigStatsUpdated; - - void emitStats(const Database::SearchFilter& filter); - - typedef Database::Track::UIQueryResult ResultType; - Wt::Dbo::QueryModel< ResultType > _queryModel; - Wt::WTableView* _tableView; - -}; - -} // namespace Desktop -} // namespace UserInterface - -#endif - diff --git a/src/ui/audio/mobile/ArtistSearch.cpp b/src/ui/audio/mobile/ArtistSearch.cpp deleted file mode 100644 index 0f9762a8..00000000 --- a/src/ui/audio/mobile/ArtistSearch.cpp +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2015 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 . - */ - -#include -#include -#include -#include - -#include "LmsApplication.hpp" - -#include "ArtistSearch.hpp" - -namespace UserInterface { -namespace Mobile { - -ArtistSearch::ArtistSearch(Wt::WString title, Wt::WContainerWidget *parent) -: Wt::WContainerWidget(parent) -{ - Wt::WTemplate* t = new Wt::WTemplate(this); - t->setTemplateText(Wt::WString::tr("wa-artist-search")); - - Wt::WTemplate *titleTemplate = new Wt::WTemplate(this); - titleTemplate->setTemplateText(Wt::WString::tr("mobile-search-title")); - titleTemplate->bindString("text", title); - t->bindWidget("title", titleTemplate); - - _contents = new Wt::WContainerWidget(); - t->bindWidget("contents", _contents ); - - _showMore = new Wt::WTemplate(); - _showMore->setTemplateText(Wt::WString::tr("mobile-search-more")); - _showMore->bindString("text", "Tap to show more results..."); - _showMore->hide(); - _showMore->clicked().connect(std::bind([=] { - _sigShowMore.emit(); - })); - t->bindWidget("show-more", _showMore); -} - -void -ArtistSearch::clear() -{ - _contents->clear(); - _showMore->hide(); -} - -void -ArtistSearch::search(Database::SearchFilter filter, size_t nb) -{ - _filter = filter; - - clear(); - addResults(nb); -} - -void -ArtistSearch::addResults(std::size_t nb) -{ - using namespace Database; - - Wt::Dbo::Transaction transaction(DboSession()); - - bool moreResults; - std::vector artists = Artist::getByFilter(DboSession(), _filter, _contents->count(), nb, moreResults); - - for (Artist::pointer artist : artists) - { - Wt::WTemplate* res = new Wt::WTemplate(_contents); - res->setTemplateText(Wt::WString::tr("wa-artist-search-res")); - - Wt::WAnchor *coverAnchor = new Wt::WAnchor(Wt::WLink(Wt:: WLink::InternalPath, "/audio/artist/" + std::to_string(artist.id()))); - Wt::WImage *artistImg = new Wt::WImage(coverAnchor); - artistImg->setImageLink( SessionImageResource()->getArtistUrl(artist.id(), 512)); - artistImg->setStyleClass("center-block"); // TODO move in css? - artistImg->setStyleClass("release_res_shadow release_img-responsive"); // TODO move in css? - - res->bindWidget("gif", coverAnchor); - res->bindString("name", Wt::WString::fromUTF8(artist->getName(), Wt::PlainText)); - } - - if (moreResults) - _showMore->show(); - else - _showMore->hide(); -} - -} // namespace Mobile -} // namespace UserInterface diff --git a/src/ui/audio/mobile/ArtistSearch.hpp b/src/ui/audio/mobile/ArtistSearch.hpp deleted file mode 100644 index 50dc22de..00000000 --- a/src/ui/audio/mobile/ArtistSearch.hpp +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2015 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 . - */ - -#ifndef UI_MOBILE_ARTIST_SEARCH_HPP -#define UI_MOBILE_ARTIST_SEARCH_HPP - -#include - -#include "database/Types.hpp" -#include "database/SearchFilter.hpp" - -namespace UserInterface { -namespace Mobile { - -class ArtistSearch : public Wt::WContainerWidget -{ - public: - - ArtistSearch(Wt::WString title, Wt::WContainerWidget *parent = 0); - - void search(Database::SearchFilter filter, std::size_t nb); - void addResults(std::size_t nb); - - // Slots - Wt::Signal& showMore() { return _sigShowMore;} - - private: - - Wt::Signal _sigShowMore; - - void clear(void); - - Wt::WTemplate* _showMore; - - Database::SearchFilter _filter; - Wt::WContainerWidget* _contents; - std::size_t _count; -}; - -} // namespace Mobile -} // namespace UserInterface - -#endif diff --git a/src/ui/audio/mobile/ArtistSearchView.cpp b/src/ui/audio/mobile/ArtistSearchView.cpp deleted file mode 100644 index d05f00dd..00000000 --- a/src/ui/audio/mobile/ArtistSearchView.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2015 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 . - */ - -#include -#include - -#include "utils/Logger.hpp" - -#include "SearchUtils.hpp" - -#include "ArtistSearch.hpp" - -#include "ArtistSearchView.hpp" - -namespace UserInterface { -namespace Mobile { - -#define SEARCH_NB_ITEMS 20 - -using namespace Database; - -ArtistSearchView::ArtistSearchView(Wt::WContainerWidget* parent) -{ - ArtistSearch* artistSearch = new ArtistSearch("Artists", this); - artistSearch->showMore().connect(std::bind([=] { - artistSearch->addResults(SEARCH_NB_ITEMS); - })); - - wApp->internalPathChanged().connect(std::bind([=] (std::string path) - { - const std::string pathPrefix = "/audio/search/artist"; - - if (!wApp->internalPathMatches(pathPrefix)) - return; - - std::vector keywords = searchPathToSearchKeywords(path.substr(pathPrefix.length())); - - artistSearch->search(SearchFilter::ByNameAnd(SearchFilter::Field::Artist, keywords), SEARCH_NB_ITEMS); - - }, std::placeholders::_1)); -} - -} // namespace Mobile -} // namespace UserInterface - diff --git a/src/ui/audio/mobile/ArtistView.cpp b/src/ui/audio/mobile/ArtistView.cpp deleted file mode 100644 index 0d365624..00000000 --- a/src/ui/audio/mobile/ArtistView.cpp +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2015 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 . - */ - -#include "LmsApplication.hpp" - -#include "utils/Utils.hpp" - -#include "ReleaseSearch.hpp" -#include "ArtistView.hpp" - -namespace UserInterface { -namespace Mobile { - -using namespace Database; - -ArtistView::ArtistView(Wt::WContainerWidget *parent) -: Wt::WContainerWidget(parent) -{ - ReleaseSearch *releases = new ReleaseSearch(this); - - wApp->internalPathChanged().connect(std::bind([=] (std::string path) - { - const std::string pathPrefix = "/audio/artist/"; - - if (!wApp->internalPathMatches(pathPrefix)) - return; - - std::string strId = path.substr(pathPrefix.length()); - - Artist::id_type id; - if (readAs(strId, id)) - { - Wt::Dbo::Transaction transaction(DboSession()); - - auto artist = Artist::getById(DboSession(), id); - releases->search(SearchFilter::ById(SearchFilter::Field::Artist, id), 20, artist ? Wt::WString::fromUTF8(artist->getName()) : "Unknown artist"); - } - - }, std::placeholders::_1)); -} - -Wt::WLink -ArtistView::getLink(Database::Artist::id_type id) -{ - return Wt::WLink(Wt::WLink::InternalPath, "/audio/artist/" + std::to_string(id)); -} - -} //namespace Mobile -} //namespace UserInterface - diff --git a/src/ui/audio/mobile/MobileAudio.cpp b/src/ui/audio/mobile/MobileAudio.cpp deleted file mode 100644 index ab08aa30..00000000 --- a/src/ui/audio/mobile/MobileAudio.cpp +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (C) 2015 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 . - */ - -#include - -#include -#include -#include - -#include "audio/AudioPlayer.hpp" - -#include "LmsApplication.hpp" - -#include "PreviewSearchView.hpp" -#include "ArtistSearchView.hpp" -#include "ReleaseSearchView.hpp" -#include "TrackSearchView.hpp" - -#include "ArtistView.hpp" -#include "ReleaseView.hpp" - -#include "MobilePlayQueue.hpp" - -#include "utils/Logger.hpp" -#include "utils/Utils.hpp" - -#include "MobileAudio.hpp" - - -namespace UserInterface { -namespace Mobile { - -enum WidgetIdx -{ - WidgetIdxSearchPreview = 0, - WidgetIdxSearchArtist = 1, - WidgetIdxSearchRelease = 2, - WidgetIdxSearchTrack = 3, - WidgetIdxArtist = 4, - WidgetIdxRelease = 5, - WidgetIdxPlayQueue = 6, -}; - -using namespace Database; - -void -Audio::search(std::string text) -{ - wApp->setInternalPath("/audio/search/preview/" + stringToUTF8(text), true); -} - -Audio::Audio(Wt::WContainerWidget *parent) -: UserInterface::Audio(parent) -{ - // Root div has to be a "container" - this->setStyleClass("container-fluid"); - this->setPadding(60, Wt::Bottom); - - Wt::WStackedWidget *stack = new Wt::WStackedWidget(this); - - PlayQueue *playQueue = new PlayQueue(); - - // Same order as WidgetIdxXXX - stack->addWidget(new PreviewSearchView(_playQueueEvents)); - stack->addWidget(new ArtistSearchView()); - stack->addWidget(new ReleaseSearchView()); - stack->addWidget(new TrackSearchView(_playQueueEvents)); - stack->addWidget(new ArtistView()); - stack->addWidget(new ReleaseView(_playQueueEvents)); - stack->addWidget(playQueue); - - wApp->internalPathChanged().connect(std::bind([=] (std::string path) - { - // order is important: most specific path first - static std::map indexes = - { - { "/audio/search/preview", WidgetIdxSearchPreview}, - { "/audio/search/artist", WidgetIdxSearchArtist}, - { "/audio/search/release", WidgetIdxSearchRelease}, - { "/audio/search/track", WidgetIdxSearchTrack}, - { "/audio/artist", WidgetIdxArtist}, - { "/audio/release", WidgetIdxRelease}, - { "/audio/playqueue", WidgetIdxPlayQueue}, - }; - - for (auto index : indexes) - { - if (wApp->internalPathMatches(index.first)) - stack->setCurrentIndex(index.second); - } - - }, std::placeholders::_1)); - - Wt::WTemplate* footer = new Wt::WTemplate(this); - footer->setTemplateText(Wt::WString::tr("mobile-audio-footer")); - - AudioPlayer* audioPlayer = new AudioPlayer(AudioPlayer::ControlPlayqueue); - footer->bindWidget("player", audioPlayer); - - audioPlayer->showPlayQueue().connect(std::bind([=] - { - wApp->setInternalPath("/audio/playqueue", true); - })); - - // Connect the audio player events to the playqueue - playQueue->playTrack().connect(std::bind([=] (Database::Track::id_type id) - { - audioPlayer->loadTrack(id); - }, std::placeholders::_1)); - audioPlayer->playNext().connect(std::bind([=] - { - playQueue->playNext(); - })); - audioPlayer->playPrevious().connect(std::bind([=] - { - playQueue->playPrevious(); - })); - audioPlayer->playbackEnded().connect(std::bind([=] - { - playQueue->playNext(); - })); - audioPlayer->shuffle().connect(std::bind(&PlayQueue::setShuffle, playQueue, std::placeholders::_1)); - audioPlayer->loop().connect(std::bind(&PlayQueue::setLoop, playQueue, std::placeholders::_1)); - - // Connect the events to the player - _playQueueEvents.trackPlay.connect(std::bind([=] (Database::Track::id_type id) - { - std::size_t pos = playQueue->addTrack(id); - playQueue->play(pos); - }, std::placeholders::_1)); - - _playQueueEvents.trackAdd.connect(std::bind([=] (Database::Track::id_type id) - { - playQueue->addTrack(id); - }, std::placeholders::_1)); - - -} - -} // namespace Mobile -} // namespace UserInterface - diff --git a/src/ui/audio/mobile/MobilePlayQueue.cpp b/src/ui/audio/mobile/MobilePlayQueue.cpp deleted file mode 100644 index c14d0ff7..00000000 --- a/src/ui/audio/mobile/MobilePlayQueue.cpp +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright (C) 2016 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 . - */ - -#include -#include -#include -#include - -#include "utils/Logger.hpp" - -#include "LmsApplication.hpp" - -#include "MobilePlayQueue.hpp" - - -namespace UserInterface { -namespace Mobile { - - -PlayQueue::PlayQueue(Wt::WContainerWidget* parent) -: Wt::WContainerWidget(parent) -{ - Wt::WTemplate* t = new Wt::WTemplate(this); - t->setTemplateText(Wt::WString::tr("wa-playqueue-view")); - - _trackContainer = new Wt::WContainerWidget(); - t->bindWidget("contents", _trackContainer); - - Wt::WTemplate* controls = new Wt::WTemplate(); - t->bindWidget("controls", controls); - controls->setTemplateText(Wt::WString::tr("wa-playqueue-controls")); - - Wt::WText *loadBtn = new Wt::WText("", Wt::XHTMLText); - controls->bindWidget("load", loadBtn); - loadBtn->setStyleClass("mobile-btn"); - - Wt::WText *saveBtn = new Wt::WText("", Wt::XHTMLText); - controls->bindWidget("save", saveBtn); - saveBtn->setStyleClass("mobile-btn"); - - Wt::WText *clearBtn = new Wt::WText("", Wt::XHTMLText); - controls->bindWidget("clear", clearBtn); - clearBtn->setStyleClass("mobile-btn"); - clearBtn->clicked().connect(std::bind([=] - { - clear(); - })); - - _shuffleBtn = new Wt::WText("", Wt::XHTMLText); - controls->bindWidget("shuffle", _shuffleBtn); - _shuffleBtn->setStyleClass("mobile-btn"); - _shuffleBtn->clicked().connect(std::bind([=] () - { - setShuffle(!_shuffle); - })); - - _loopBtn = new Wt::WText("", Wt::XHTMLText); - controls->bindWidget("repeat", _loopBtn); - _loopBtn->setStyleClass("mobile-btn"); - _loopBtn->clicked().connect(std::bind([=] () - { - setLoop(!_loop); - })); - -} - -std::size_t -PlayQueue::addTrack(Database::Track::id_type id) -{ - Wt::Dbo::Transaction transaction(DboSession()); - - Database::Track::pointer track = Database::Track::getById(DboSession(), id); - if (!track) - { - LMS_LOG(UI, INFO) << "No track found for id " << id; - return 0; - } - - std::size_t trackPos = _trackIds.size(); - _trackIds.push_back(id); - - Wt::WTemplate* t = new Wt::WTemplate(_trackContainer); - t->setTemplateText(Wt::WString::tr("wa-playqueue-track")); - - Wt::WImage *cover = new Wt::WImage(); - t->bindWidget("cover", cover); - cover->setStyleClass ("center-block img-responsive"); - cover->setImageLink(SessionImageResource()->getTrackUrl(track.id(), 64)); - t->bindString("track-name", Wt::WString::fromUTF8(track->getName()), Wt::PlainText); - t->bindString("artist-name", Wt::WString::fromUTF8(track->getArtist()->getName()), Wt::PlainText); - - Wt::WText *playBtn = new Wt::WText("", Wt::XHTMLText); - t->bindWidget("play-btn", playBtn); - playBtn->addStyleClass("mobile-btn"); - playBtn->clicked().connect(std::bind([=] - { - int pos = _trackContainer->indexOf(t); - play(pos); - })); - - Wt::WText *delBtn = new Wt::WText("", Wt::XHTMLText); - t->bindWidget("del-btn", delBtn); - delBtn->addStyleClass("mobile-btn"); - delBtn->clicked().connect(std::bind([=] - { - int pos = _trackContainer->indexOf(t); - _trackContainer->removeWidget(t); - _trackIds.erase(_trackIds.begin() + pos); - })); - - return trackPos; -} - -void -PlayQueue::clear(void) -{ - _trackIds.clear(); - _trackContainer->clear(); - _currentPos = 0; -} - -void -PlayQueue::play(size_t pos) -{ - if (_trackIds.empty() || pos >= _trackIds.size()) - return; - - _trackContainer->widget(_currentPos)->removeStyleClass("playqueue-playing"); - - _currentPos = pos; - _trackContainer->widget(_currentPos)->addStyleClass("playqueue-playing"); - - _sigTrackPlay.emit(_trackIds[pos]); -} - -void -PlayQueue::playNext(void) -{ - if (_currentPos == (_trackIds.size() - 1) && _loop) - play(0); - else - play(_currentPos + 1); -} - -void -PlayQueue::playPrevious(void) -{ - if (_currentPos == 0) - { - if (_loop) - { - play(_trackIds.size() - 1); - } - else - return; - } - else - play(_currentPos - 1); -} - -void -PlayQueue::handlePlaybackComplete(void) -{ - playNext(); -} - -void -PlayQueue::setShuffle(bool value) -{ - _shuffle = value; - - if (value) - _shuffleBtn->addStyleClass("mobile-btn-active"); - else - _shuffleBtn->removeStyleClass("mobile-btn-active"); -} - -void -PlayQueue::setLoop(bool value) -{ - _loop = value; - - if (value) - _loopBtn->addStyleClass("mobile-btn-active"); - else - _loopBtn->removeStyleClass("mobile-btn-active"); -} - -} // namespace Mobile -} // namespace UserInterface diff --git a/src/ui/audio/mobile/MobilePlayQueue.hpp b/src/ui/audio/mobile/MobilePlayQueue.hpp deleted file mode 100644 index ab8a1dfc..00000000 --- a/src/ui/audio/mobile/MobilePlayQueue.hpp +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2016 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 . - */ - -#pragma once - -#include - -#include -#include - -#include "database/Types.hpp" - -namespace UserInterface { -namespace Mobile { - -class PlayQueue : public Wt::WContainerWidget -{ - public: - PlayQueue(Wt::WContainerWidget* parent = 0); - - // Contents - std::size_t addArtist(Database::Artist::id_type id); - std::size_t addRelease(Database::Release::id_type id); - std::size_t addTrack(Database::Track::id_type id); - void clear(void); - - // Controls - void play(size_t pos); // Play the track at a given position - void playNext(void); - void playPrevious(void); - - void setShuffle(bool enable); // true to enable shuffle - void setLoop(bool enable); // true to enable loop - - // Signals - // Emitted when a song has to be played - Wt::Signal& playTrack() { return _sigTrackPlay; } - - // Slots - // Song has finished - void handlePlaybackComplete(void); - - private: - - bool _loop = false; - bool _shuffle = false; - std::size_t _currentPos = 0; - - Wt::WText* _shuffleBtn = nullptr; - Wt::WText* _loopBtn = nullptr; - - Wt::Signal _sigTrackPlay; - - std::vector _trackIds; - Wt::WContainerWidget* _trackContainer; -}; - -} // namespace Mobile -} // namespace UserInterface diff --git a/src/ui/audio/mobile/PreviewSearchView.cpp b/src/ui/audio/mobile/PreviewSearchView.cpp deleted file mode 100644 index 8a602e24..00000000 --- a/src/ui/audio/mobile/PreviewSearchView.cpp +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2015 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 . - */ - -#include -#include - -#include "utils/Logger.hpp" - -#include "SearchUtils.hpp" - -#include "ArtistSearch.hpp" -#include "ReleaseSearch.hpp" -#include "TrackSearch.hpp" - -#include "PreviewSearchView.hpp" - -namespace UserInterface { -namespace Mobile { - -#define SEARCH_NB_ITEMS 4 - -using namespace Database; - -PreviewSearchView::PreviewSearchView(PlayQueueEvents& events, Wt::WContainerWidget* parent) -{ - const std::string pathPrefix = "/audio/search/preview"; - - ArtistSearch* artistSearch = new ArtistSearch("Artists", this); - ReleaseSearch* releaseSearch = new ReleaseSearch(this); - TrackSearch* trackSearch = new TrackSearch(events, this); - - artistSearch->showMore().connect(std::bind([=] - { - std::string path = wApp->internalPath(); - path.replace(0, pathPrefix.length(), "/audio/search/artist"); - - wApp->setInternalPath(path, true); - })); - - releaseSearch->showMore().connect(std::bind([=] - { - std::string path = wApp->internalPath(); - path.replace(0, pathPrefix.length(), "/audio/search/release"); - - wApp->setInternalPath(path, true); - })); - - trackSearch->showMore().connect(std::bind([=] - { - std::string path = wApp->internalPath(); - path.replace(0, pathPrefix.length(), "/audio/search/track"); - - wApp->setInternalPath(path, true); - })); - - wApp->internalPathChanged().connect(std::bind([=] (std::string path) - { - if (!wApp->internalPathMatches(pathPrefix)) - return; - - std::vector keywords = searchPathToSearchKeywords(path.substr(pathPrefix.length())); - - artistSearch->search(SearchFilter::ByNameAnd(SearchFilter::Field::Artist, keywords), SEARCH_NB_ITEMS); - releaseSearch->search(SearchFilter::ByNameAnd(SearchFilter::Field::Release, keywords), SEARCH_NB_ITEMS, "Releases"); - trackSearch->search(SearchFilter::ByNameAnd(SearchFilter::Field::Track, keywords), SEARCH_NB_ITEMS); - - }, std::placeholders::_1)); -} - -} // namespace Mobile -} // namespace UserInterface - diff --git a/src/ui/audio/mobile/ReleaseSearch.cpp b/src/ui/audio/mobile/ReleaseSearch.cpp deleted file mode 100644 index c45c83cc..00000000 --- a/src/ui/audio/mobile/ReleaseSearch.cpp +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (C) 2015 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 . - */ - -#include -#include -#include -#include - -#include "utils/Logger.hpp" - -#include "LmsApplication.hpp" - -#include "ReleaseSearch.hpp" -#include "ReleaseView.hpp" -#include "ArtistView.hpp" - -namespace UserInterface { -namespace Mobile { - -using namespace Database; - -ReleaseSearch::ReleaseSearch(Wt::WContainerWidget *parent) -: Wt::WContainerWidget(parent) -{ - Wt::WTemplate* t = new Wt::WTemplate(this); - t->setTemplateText(Wt::WString::tr("wa-release-search")); - - Wt::WTemplate *titleTemplate = new Wt::WTemplate(this); - titleTemplate->setTemplateText(Wt::WString::tr("mobile-search-title")); - - _title = new Wt::WText(); - titleTemplate->bindWidget("text", _title); - - t->bindWidget("title", titleTemplate); - - _contents = new Wt::WContainerWidget(); - t->bindWidget("contents", _contents ); - - _showMore = new Wt::WTemplate(); - _showMore->setTemplateText(Wt::WString::tr("mobile-search-more")); - _showMore->bindString("text", "Tap to show more results..."); - _showMore->hide(); - _showMore->clicked().connect(std::bind([=] { - _sigShowMore.emit(); - })); - - t->bindWidget("show-more", _showMore); -} - -void -ReleaseSearch::clear() -{ - _contents->clear(); - _showMore->hide(); -} - -void -ReleaseSearch::search(Database::SearchFilter filter, size_t max, Wt::WString title) -{ - _filter = filter; - _title->setText(title); - - clear(); - addResults(max); -} - -void -ReleaseSearch::addResults(size_t nb) -{ - using namespace Database; - - Wt::Dbo::Transaction transaction(DboSession()); - - bool moreResults; - std::vector releases = Release::getByFilter(DboSession(), _filter, _contents->count(), nb, moreResults); - - for (Release::pointer release : releases) - { - Wt::WTemplate* res = new Wt::WTemplate(_contents); - res->setTemplateText(Wt::WString::tr("wa-release-search-res")); - - Wt::WAnchor *coverAnchor = new Wt::WAnchor(ReleaseView::getLink(release.id())); - Wt::WImage *cover = new Wt::WImage(coverAnchor); - cover->setStyleClass("center-block"); - cover->setImageLink( SessionImageResource()->getReleaseUrl(release.id(), 512)); - cover->setStyleClass("release_res_shadow release_img-responsive"); // TODO move? - - res->bindWidget("cover", coverAnchor); - res->bindWidget("name", new Wt::WText(Wt::WString::fromUTF8(release->getName()), Wt::PlainText)); - res->bindString("release_name", Wt::WString::fromUTF8(release->getName()), Wt::PlainText); - - bool variousArtists; - auto artists = Artist::getByFilter(DboSession(), - SearchFilter::ById(SearchFilter::Field::Release, release.id()), 0, 1, variousArtists); - if (!artists.empty()) - { - if (variousArtists) - { - res->bindWidget("artist", new Wt::WText(Wt::WString::fromUTF8("Various Artists", Wt::PlainText))); - } - else - { - Wt::WAnchor *artistAnchor = new Wt::WAnchor(ArtistView::getLink(artists.front().id())); - Wt::WText *artist = new Wt::WText(artistAnchor); - artist->setText(Wt::WString::fromUTF8(artists.front()->getName(), Wt::PlainText)); - res->bindWidget("artist", artistAnchor); - } - } - } - - if (moreResults) - _showMore->show(); - else - _showMore->hide(); - -} - -} // namespace Mobile -} // namespace UserInterface diff --git a/src/ui/audio/mobile/ReleaseSearch.hpp b/src/ui/audio/mobile/ReleaseSearch.hpp deleted file mode 100644 index 3e82eb57..00000000 --- a/src/ui/audio/mobile/ReleaseSearch.hpp +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2015 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 . - */ - -#pragma once - -#include -#include - -#include "database/SearchFilter.hpp" -#include "database/Types.hpp" - -namespace UserInterface { -namespace Mobile { - -class ReleaseSearch : public Wt::WContainerWidget -{ - public: - - ReleaseSearch(Wt::WContainerWidget *parent = 0); - - void search(Database::SearchFilter filter, std::size_t nb, Wt::WString title); - void addResults(std::size_t nb); - - // Slots - Wt::Signal& showMore() { return _sigShowMore;} - - private: - - Wt::Signal _sigShowMore; - - void clear(void); - - Wt::WTemplate* _showMore; - Database::SearchFilter _filter; - Wt::WContainerWidget* _contents; - Wt::WText* _title; - std::size_t _count; -}; - -} // namespace Mobile -} // namespace UserInterface - diff --git a/src/ui/audio/mobile/ReleaseSearchView.cpp b/src/ui/audio/mobile/ReleaseSearchView.cpp deleted file mode 100644 index 09bfaf9f..00000000 --- a/src/ui/audio/mobile/ReleaseSearchView.cpp +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2015 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 . - */ - -#include - -#include "SearchUtils.hpp" - -#include "ReleaseSearch.hpp" - -#include "ReleaseSearchView.hpp" - -namespace UserInterface { -namespace Mobile { - -#define SEARCH_NB_ITEMS 20 - -using namespace Database; - -ReleaseSearchView::ReleaseSearchView(Wt::WContainerWidget* parent) -{ - ReleaseSearch* artistSearch = new ReleaseSearch(this); - artistSearch->showMore().connect(std::bind([=] { - artistSearch->addResults(SEARCH_NB_ITEMS); - })); - - wApp->internalPathChanged().connect(std::bind([=] (std::string path) - { - const std::string pathPrefix = "/audio/search/release"; - - if (!wApp->internalPathMatches(pathPrefix)) - return; - - std::vector keywords = searchPathToSearchKeywords(path.substr(pathPrefix.length())); - - artistSearch->search(SearchFilter::ByNameAnd(SearchFilter::Field::Release, keywords), SEARCH_NB_ITEMS, "Releases"); - - }, std::placeholders::_1)); -} - -} // namespace Mobile -} // namespace UserInterface - diff --git a/src/ui/audio/mobile/ReleaseView.cpp b/src/ui/audio/mobile/ReleaseView.cpp deleted file mode 100644 index 93c1e5fc..00000000 --- a/src/ui/audio/mobile/ReleaseView.cpp +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright (C) 2015 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 . - */ - -#include -#include -#include -#include - -#include "utils/Logger.hpp" -#include "utils/Utils.hpp" - -#include "LmsApplication.hpp" - -#include "ReleaseView.hpp" - -namespace UserInterface { -namespace Mobile { - -using namespace Database; - -ReleaseView::ReleaseView(PlayQueueEvents& events, Wt::WContainerWidget *parent) -: Wt::WContainerWidget(parent), - _events(events) -{ - Wt::WTemplate* wrapper = new Wt::WTemplate(this); - wrapper->setTemplateText(Wt::WString::tr("wa-trackview-wrapper")); - - Wt::WTemplate* title = new Wt::WTemplate(this); - wrapper->bindWidget("title", title); - - title->setTemplateText(Wt::WString::tr("mobile-search-title")); - title->bindString("text", "Releases", Wt::PlainText); - - _releaseContainer = new Wt::WContainerWidget(); - wrapper->bindWidget("release-container", _releaseContainer); - - _showMore = new Wt::WTemplate(); - wrapper->bindWidget("show-more", _showMore); - - _showMore->setTemplateText(Wt::WString::tr("mobile-search-more")); - _showMore->bindString("text", "Tap to show more results..."); - _showMore->hide(); - _showMore->clicked().connect(std::bind([=] { - addResults(20); - })); - - wApp->internalPathChanged().connect(std::bind([=] (std::string path) - { - const std::string pathPrefix = "/audio/release/"; - - if (!wApp->internalPathMatches(pathPrefix)) - return; - - std::string strId = path.substr(pathPrefix.length()); - - Release::id_type id; - if (readAs(strId, id)) - { - clear(); - search(SearchFilter::ById(SearchFilter::Field::Release, id), 20); - } - }, std::placeholders::_1)); -} - -void -ReleaseView::clear() -{ - // Flush the release container - _releaseContainer->clear(); - - // Flush the current context - _currentTrackContainer = nullptr; - _nbTracks = 0; - _showMore->hide(); -} - -void -ReleaseView::search(SearchFilter filter, size_t nb) -{ - _filter = filter; - - clear(); - addResults(nb); -} - -static Wt::WString -getArtistNameFromRelease(Release::pointer release) -{ - auto artists = Artist::getByFilter(DboSession(), - SearchFilter::ById(SearchFilter::Field::Release, release.id()), -1, 2); - - if (artists.size() > 1) - return Wt::WString::fromUTF8("Various artists", Wt::PlainText); - else if (artists.size() == 1) - return Wt::WString::fromUTF8(artists.front()->getName(), Wt::PlainText); - else - return Wt::WString(); -} - -void -ReleaseView::addResults(size_t nb) -{ - Wt::Dbo::Transaction transaction(DboSession()); - - bool moreResults; - std::vector tracks = Track::getByFilter(DboSession(), _filter, _nbTracks, nb, moreResults); - - for (Track::pointer track : tracks) - { - // First check if we need to create a new track container - - // New container if it is the first one or if the release has changed - if (!_currentTrackContainer - || _currentReleaseId != track->getRelease().id()) - { - Release::pointer release = track->getRelease(); - - Wt::WTemplate *releaseContainer = new Wt::WTemplate(_releaseContainer); - releaseContainer->setTemplateText(Wt::WString::tr("wa-trackview-release-container")); - _currentReleaseId = release.id(); - - Wt::WImage *cover = new Wt::WImage(); - cover->setStyleClass ("center-block img-responsive"); // TODO move to CSS? - cover->setImageLink(Wt::WLink (SessionImageResource()->getReleaseUrl(release.id(), 512))); - - releaseContainer->bindWidget("cover", cover); - releaseContainer->bindString("artist-name", getArtistNameFromRelease(release), Wt::PlainText); - releaseContainer->bindString("release-name", release->getName(), Wt::PlainText); - int year = release->getReleaseYear(); - if (year > 0) - { - releaseContainer->setCondition("if-has-year", true); - releaseContainer->bindInt("year", year); - - int originalYear = release->getReleaseYear(true); - if (originalYear > 0 && originalYear != year) - { - releaseContainer->setCondition("if-has-orig-year", true); - releaseContainer->bindInt("orig-year", originalYear); - } - } - - _currentTrackContainer = new Wt::WContainerWidget(); - releaseContainer->bindWidget("track-container", _currentTrackContainer); - } - - assert(_currentTrackContainer != nullptr); - - Wt::WTemplate* trackRes = new Wt::WTemplate(_currentTrackContainer); - trackRes->setTemplateText(Wt::WString::tr("wa-trackview-track")); - - if (track->getTrackNumber() > 0 && !track->getRelease()->isNone()) - { - trackRes->setCondition("if-has-track-num", true); - trackRes->bindInt("track-num", track->getTrackNumber()); - - if (track->getDiscNumber() > 0 && track->getTotalDiscNumber() > 1) - { trackRes->setCondition("if-has-disc-num", true); - trackRes->bindInt("disc-num", track->getDiscNumber()); - } - } - trackRes->bindString("track-name", Wt::WString::fromUTF8(track->getName()), Wt::PlainText); - // TODO, display artist name for compilation releases? - - Wt::WText *playBtn = new Wt::WText("", Wt::XHTMLText); - trackRes->bindWidget("play-btn", playBtn); - playBtn->setStyleClass("mobile-btn"); - playBtn->clicked().connect(std::bind([=] { - _events.trackPlay.emit(track.id()); - })); - - Wt::WText *addBtn = new Wt::WText("", Wt::XHTMLText); - trackRes->bindWidget("add-btn", addBtn); - addBtn->setStyleClass("mobile-btn"); - addBtn->clicked().connect(std::bind([=] { - _events.trackAdd.emit(track.id()); - })); - - - - - _nbTracks++; - } - - if (moreResults) - _showMore->show(); - else - _showMore->hide(); -} - -Wt::WLink -ReleaseView::getLink(Database::Release::id_type id) -{ - return Wt::WLink(Wt::WLink::InternalPath, "/audio/release/" + std::to_string(id)); -} - -} // namespace Mobile -} // namespace UserInterface diff --git a/src/ui/audio/mobile/ReleaseView.hpp b/src/ui/audio/mobile/ReleaseView.hpp deleted file mode 100644 index bb590702..00000000 --- a/src/ui/audio/mobile/ReleaseView.hpp +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2015 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 . - */ - -#pragma once - -#include -#include - -#include "MobilePlayQueueEvents.hpp" - -namespace UserInterface { -namespace Mobile { - -class ReleaseView : public Wt::WContainerWidget -{ - public: - - static Wt::WLink getLink(Database::Release::id_type id); - - ReleaseView(PlayQueueEvents& events, Wt::WContainerWidget *parent = 0); - - void search(Database::SearchFilter filter, size_t nb); - - // Slots - Wt::Signal& moreTracksSelected() { return _sigMoreTracksSelected;} - - private: - - Wt::Signal _sigMoreTracksSelected; - - void clear(void); - void addResults(size_t nb); - - PlayQueueEvents& _events; - Wt::WTemplate* _showMore; - - Database::SearchFilter _filter; - std::size_t _nbTracks; - - // Main container that holds the releases - Wt::WContainerWidget* _releaseContainer; - - // Used to add more results on the fly - Database::Release::id_type _currentReleaseId; - Wt::WContainerWidget* _currentTrackContainer; -}; - -} // namespace Mobile -} // namespace UserInterface - diff --git a/src/ui/audio/mobile/SearchUtils.hpp b/src/ui/audio/mobile/SearchUtils.hpp deleted file mode 100644 index 04743b15..00000000 --- a/src/ui/audio/mobile/SearchUtils.hpp +++ /dev/null @@ -1,32 +0,0 @@ - -/* - * Copyright (C) 2015 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 . - */ - -#pragma once - -#include "utils/Utils.hpp" - -static inline std::vector searchPathToSearchKeywords(std::string path) -{ - if (!path.empty() && path[0] == '/') - path.erase(path.begin()); - - return splitString(path, " \t"); -} - diff --git a/src/ui/audio/mobile/TrackSearch.cpp b/src/ui/audio/mobile/TrackSearch.cpp deleted file mode 100644 index db5d4427..00000000 --- a/src/ui/audio/mobile/TrackSearch.cpp +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2015 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 . - */ - -#include -#include -#include -#include - -#include "utils/Logger.hpp" -#include "utils/Utils.hpp" - -#include "LmsApplication.hpp" - -#include "TrackSearch.hpp" - -namespace UserInterface { -namespace Mobile { - -using namespace Database; - -TrackSearch::TrackSearch(PlayQueueEvents& events, Wt::WContainerWidget *parent) -: Wt::WContainerWidget(parent), - _events(events) -{ - Wt::WTemplate* t = new Wt::WTemplate(this); - t->setTemplateText(Wt::WString::tr("wa-track-search")); - - Wt::WTemplate* title = new Wt::WTemplate(this); - title->setTemplateText(Wt::WString::tr("mobile-search-title")); - title->bindString("text", "Tracks", Wt::PlainText); - t->bindWidget("title", title); - - _contents = new Wt::WContainerWidget(); - t->bindWidget("contents", _contents); - - _showMore = new Wt::WTemplate(); - _showMore->setTemplateText(Wt::WString::tr("mobile-search-more")); - _showMore->bindString("text", "Tap to show more results..."); - _showMore->hide(); - _showMore->clicked().connect(std::bind([=] { - _sigShowMore.emit(); - addResults(20); - })); - t->bindWidget("show-more", _showMore); -} - -void -TrackSearch::clear() -{ - _contents->clear(); - _showMore->hide(); -} - -void -TrackSearch::search(SearchFilter filter, size_t nb) -{ - _filter = filter; - - clear(); - addResults(nb); -} - -void -TrackSearch::addResults(size_t nb) -{ - Wt::Dbo::Transaction transaction(DboSession()); - - bool moreResults; - std::vector tracks = Track::getByFilter(DboSession(), _filter, _contents->count(), nb, moreResults); - - for (Track::pointer track : tracks) - { - Wt::WTemplate* res = new Wt::WTemplate(_contents); - res->setTemplateText(Wt::WString::tr("wa-track-search-res")); - - Wt::WImage *cover = new Wt::WImage(); - res->bindWidget("cover", cover); - cover->setStyleClass ("center-block img-responsive"); - cover->setImageLink(SessionImageResource()->getTrackUrl(track.id(), 64)); - res->bindString("track-name", Wt::WString::fromUTF8(track->getName()), Wt::PlainText); - res->bindString("artist-name", Wt::WString::fromUTF8(track->getArtist()->getName()), Wt::PlainText); - - Wt::WText *playBtn = new Wt::WText("", Wt::XHTMLText); - res->bindWidget("play-btn", playBtn); - playBtn->setStyleClass("mobile-btn"); - playBtn->clicked().connect(std::bind([=] { - _events.trackPlay.emit(track.id()); - })); - - Wt::WText *addBtn = new Wt::WText("", Wt::XHTMLText); - res->bindWidget("add-btn", addBtn); - addBtn->setStyleClass("mobile-btn"); - addBtn->clicked().connect(std::bind([=] { - _events.trackAdd.emit(track.id()); - })); - - - } - - if (moreResults) - _showMore->show(); - else - _showMore->hide(); -} - -} // namespace Mobile -} // namespace UserInterface diff --git a/src/ui/audio/mobile/TrackSearch.hpp b/src/ui/audio/mobile/TrackSearch.hpp deleted file mode 100644 index 0a98d886..00000000 --- a/src/ui/audio/mobile/TrackSearch.hpp +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2015 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 . - */ - -#pragma once - -#include -#include - -#include "database/SearchFilter.hpp" -#include "database/Types.hpp" - -#include "MobilePlayQueueEvents.hpp" - -namespace UserInterface { -namespace Mobile { - -class TrackSearch : public Wt::WContainerWidget -{ - public: - - TrackSearch(PlayQueueEvents& events, Wt::WContainerWidget *parent = 0); - - void addResults(size_t nb); - void search(Database::SearchFilter filter, size_t nb); - - // Slots - Wt::Signal& showMore() { return _sigShowMore;} - - private: - - Wt::Signal _sigShowMore; - - void clear(void); - - PlayQueueEvents& _events; - Wt::WTemplate* _showMore; - Database::SearchFilter _filter; - Wt::WContainerWidget* _contents; -}; - -} // namespace Mobile -} // namespace UserInterface - diff --git a/src/ui/audio/mobile/TrackSearchView.cpp b/src/ui/audio/mobile/TrackSearchView.cpp deleted file mode 100644 index bff89ee0..00000000 --- a/src/ui/audio/mobile/TrackSearchView.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2016 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 . - */ - -#include -#include - -#include "utils/Logger.hpp" - -#include "SearchUtils.hpp" - -#include "TrackSearch.hpp" - -#include "TrackSearchView.hpp" - -namespace UserInterface { -namespace Mobile { - -#define SEARCH_NB_ITEMS 20 - -using namespace Database; - -TrackSearchView::TrackSearchView(PlayQueueEvents& events, Wt::WContainerWidget* parent) -{ - TrackSearch* trackSearch = new TrackSearch(events, this); - trackSearch->showMore().connect(std::bind([=] { - trackSearch->addResults(SEARCH_NB_ITEMS); - })); - - wApp->internalPathChanged().connect(std::bind([=] (std::string path) - { - const std::string pathPrefix = "/audio/search/track"; - - if (!wApp->internalPathMatches(pathPrefix)) - return; - - std::vector keywords = searchPathToSearchKeywords(path.substr(pathPrefix.length())); - - trackSearch->search(SearchFilter::ByNameAnd(SearchFilter::Field::Track, keywords), SEARCH_NB_ITEMS); - - }, std::placeholders::_1)); -} - -} // namespace Mobile -} // namespace UserInterface - diff --git a/src/ui/settings/Settings.cpp b/src/ui/settings/Settings.cpp deleted file mode 100644 index 52e75376..00000000 --- a/src/ui/settings/Settings.cpp +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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 . - */ - -#include -#include -#include -#include - -#include "SettingsAudioFormView.hpp" -#include "SettingsUserFormView.hpp" -#include "SettingsAccountFormView.hpp" -#include "SettingsDatabaseFormView.hpp" -#include "SettingsMediaDirectories.hpp" -#include "SettingsUsers.hpp" - -#include "utils/Logger.hpp" - -#include "database/Setting.hpp" -#include "database/updater/DatabaseUpdater.hpp" - -#include "LmsApplication.hpp" - -#include "Settings.hpp" - -namespace UserInterface { -namespace Settings { - -using namespace Database; - -Settings::Settings(Wt::WContainerWidget* parent) -: Wt::WContainerWidget(parent) -{ - Wt::WHBoxLayout* hLayout = new Wt::WHBoxLayout(this); - - // Create a stack where the contents will be located. - Wt::WStackedWidget *contents = new Wt::WStackedWidget(); - - contents->setStyleClass("contents"); - contents->setOverflow(WContainerWidget::OverflowHidden); - - // TODO menu style on hover - Wt::WMenu *menu = new Wt::WMenu(contents, Wt::Vertical); - menu->setStyleClass("nav nav-pills nav-stacked submenu"); - menu->setWidth(150); - menu->setInternalPathEnabled(); - menu->setInternalBasePath("/settings/"); - - hLayout->addWidget(menu); - hLayout->addWidget(contents, 1); - - std::string userId; - bool userIsAdmin; - { - Wt::Dbo::Transaction transaction(DboSession()); - - userId = User::getId(CurrentUser()); - userIsAdmin = CurrentUser()->isAdmin(); - } - - menu->addItem("Audio", new AudioFormView())->setPathComponent("audio"); - if (userIsAdmin) - { - MediaDirectories* mediaDirectories = new MediaDirectories(); - mediaDirectories->changed().connect(std::bind([=] - { - LMS_LOG(UI, INFO) << "Media directories have changed: requesting imediate scan"; - - // On directory change, request an immediate scan - std::lock_guard lock(Updater::instance().getMutex()); - - Setting::setBool(DboSession(), "manual_scan_requested", true); - Updater::instance().restart(); - })); - menu->addItem("Media Folders", mediaDirectories)->setPathComponent("mediadirectories"); - - DatabaseFormView* databaseFormView = new DatabaseFormView(); - databaseFormView->changed().connect(std::bind([=] - { - std::lock_guard lock(Updater::instance().getMutex()); - Updater::instance().restart(); - })); - menu->addItem("Database", databaseFormView)->setPathComponent("database"); - - menu->addItem("Users", new Users())->setPathComponent("users"); - } - else - { - menu->addItem("Account", new AccountFormView(userId))->setPathComponent("account"); - } - -} - -} // namespace Settings -} // namespace UserInterface diff --git a/src/ui/settings/SettingsAccountFormView.cpp b/src/ui/settings/SettingsAccountFormView.cpp deleted file mode 100644 index bbffbd2a..00000000 --- a/src/ui/settings/SettingsAccountFormView.cpp +++ /dev/null @@ -1,285 +0,0 @@ -/* - * 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 . - */ - -#include - -#include -#include -#include -#include -#include -#include - -#include "utils/Logger.hpp" - -#include "common/Validators.hpp" -#include "LmsApplication.hpp" - -#include "SettingsAccountFormView.hpp" - -namespace UserInterface { -namespace Settings { - -class AccountFormModel : public Wt::WFormModel -{ - public: - - // Associate each field with a unique string literal. - static const Field NameField; - static const Field EmailField; - static const Field PasswordField; - static const Field PasswordConfirmField; - - AccountFormModel(std::string userId, Wt::WObject *parent = 0) - : Wt::WFormModel(parent), - _userId(userId) - { - addField(NameField); - addField(EmailField); - addField(PasswordField); - addField(PasswordConfirmField); - - setValidator(NameField, createNameValidator()); - setValidator(EmailField, createEmailValidator()); - - // populate the model with initial data - loadData(); - } - - void loadData() - { - Wt::Dbo::Transaction transaction(DboSession()); - - Wt::Auth::User authUser = DbHandler().getUserDatabase().findWithId( _userId ); - Database::User::pointer user = DbHandler().getUser(authUser); - - if (user && authUser.isValid()) - { - setValue(NameField, authUser.identity(Wt::Auth::Identity::LoginName)); - if (!authUser.email().empty()) - setValue(EmailField, authUser.email()); - else - setValue(EmailField, authUser.unverifiedEmail()); - } - setValue(PasswordField, Wt::WString()); - setValue(PasswordConfirmField, Wt::WString()); - } - - bool saveData(Wt::WString& error) - { - // DBO transaction active here - try { - Wt::Dbo::Transaction transaction(DboSession()); - - // Update user - Wt::Auth::User authUser = DbHandler().getUserDatabase().findWithId(_userId); - Database::User::pointer user = DbHandler().getUser( authUser ); - - // user may have been deleted by someone else - if (!authUser.isValid()) { - error = Wt::WString("User identity does not exist"); - return false; - } - else if(!user) - { - error = Wt::WString("User not found"); - return false; - } - - // Account - authUser.setIdentity(Wt::Auth::Identity::LoginName, valueText(NameField)); - authUser.setEmail(valueText(EmailField).toUTF8()); - - // Password - if (!valueText(PasswordField).empty()) - Database::Handler::getPasswordService().updatePassword(authUser, valueText(PasswordField)); - - setValue(PasswordField, Wt::WString()); - setValue(PasswordConfirmField, Wt::WString()); - - } - catch(Wt::Dbo::Exception& exception) - { - LMS_LOG(UI, ERROR) << "Dbo exception: " << exception.what(); - return false; - } - - return true; - } - - bool validateField(Field field) - { - // DBO transaction active here - - Wt::WString error; - - if (field == NameField) - { - Wt::Dbo::Transaction transaction(DboSession()); - // Must be unique since used as LoginIdentity - Wt::Auth::User user = DbHandler().getUserDatabase().findWithIdentity(Wt::Auth::Identity::LoginName, valueText(field)); - if (user.isValid() && user.id() != _userId) - error = "Already exists"; - else - return Wt::WFormModel::validateField(field); - } - else if (field == PasswordField) - { - // Password is mandatory if we create the user - if (!valueText(PasswordField).empty()) - { - // Evaluate the strength of the password - Wt::Auth::AbstractPasswordService::StrengthValidatorResult res - = Database::Handler::getPasswordService().strengthValidator()->evaluateStrength(valueText(PasswordField), - valueText(NameField), - valueText(EmailField).toUTF8()); - - if (!res.isValid()) - error = res.message(); - } - else - return Wt::WFormModel::validateField(field); - } - else if (field == PasswordConfirmField) - { - if (validation(PasswordField).state() == Wt::WValidator::Valid) - { - if (valueText(PasswordField) != valueText(PasswordConfirmField)) - error = Wt::WString::tr("Wt.Auth.passwords-dont-match"); - } - } - else - { - // Apply validators - return Wt::WFormModel::validateField(field); - } - - setValidation(field, Wt::WValidator::Result( error.empty() ? Wt::WValidator::Valid : Wt::WValidator::Invalid, error)); - - return validation(field).state() == Wt::WValidator::Valid; - } - - - private: - - std::string _userId; -}; - -const Wt::WFormModel::Field AccountFormModel::NameField = "name"; -const Wt::WFormModel::Field AccountFormModel::EmailField = "email"; -const Wt::WFormModel::Field AccountFormModel::PasswordField = "password"; -const Wt::WFormModel::Field AccountFormModel::PasswordConfirmField = "password-confirm"; - -AccountFormView::AccountFormView(std::string userId, Wt::WContainerWidget *parent) -: Wt::WTemplateFormView(parent) -{ - - _model = new AccountFormModel(userId, this); - - setTemplateText(tr("settings-account")); - addFunction("id", &WTemplate::Functions::id); - addFunction("block", &WTemplate::Functions::id); - - _applyInfo = new Wt::WText(); - _applyInfo->setInline(false); - _applyInfo->hide(); - bindWidget("apply-info", _applyInfo); - - // Name - Wt::WLineEdit* accountEdit = new Wt::WLineEdit(); - setFormWidget(AccountFormModel::NameField, accountEdit); - accountEdit->changed().connect(_applyInfo, &Wt::WWidget::hide); - - // Email - Wt::WLineEdit* emailEdit = new Wt::WLineEdit(); - setFormWidget(AccountFormModel::EmailField, emailEdit); - emailEdit->changed().connect(_applyInfo, &Wt::WWidget::hide); - - // Password - Wt::WLineEdit* passwordEdit = new Wt::WLineEdit(); - setFormWidget(AccountFormModel::PasswordField, passwordEdit ); - passwordEdit->setEchoMode(Wt::WLineEdit::Password); - passwordEdit->changed().connect(_applyInfo, &Wt::WWidget::hide); - - // Password confirmation - Wt::WLineEdit* passwordConfirmEdit = new Wt::WLineEdit(); - setFormWidget(AccountFormModel::PasswordConfirmField, passwordConfirmEdit); - passwordConfirmEdit->setEchoMode(Wt::WLineEdit::Password); - passwordConfirmEdit->changed().connect(_applyInfo, &Wt::WWidget::hide); - - // Title & Buttons - bindString("title", "Account settings"); - - Wt::WPushButton *saveButton = new Wt::WPushButton("Apply"); - saveButton->setStyleClass("btn-primary"); - bindWidget("save-button", saveButton); - saveButton->clicked().connect(this, &AccountFormView::processSave); - - Wt::WPushButton *cancelButton = new Wt::WPushButton("Discard"); - bindWidget("cancel-button", cancelButton); - cancelButton->clicked().connect(this, &AccountFormView::processCancel); - - updateView(_model); - -} - - void -AccountFormView::processCancel() -{ - _applyInfo->show(); - - _applyInfo->setText( Wt::WString::fromUTF8("Parameters reverted!")); - _applyInfo->setStyleClass("alert alert-info"); - _model->loadData(); - - _model->validate(); - updateView(_model); -} - -void -AccountFormView::processSave() -{ - updateModel(_model); - - _applyInfo->show(); - if (_model->validate()) - { - Wt::WString error; - // commit model into DB - if (_model->saveData(error) ) - { - _applyInfo->setText( Wt::WString::fromUTF8("New parameters successfully applied!")); - _applyInfo->setStyleClass("alert alert-success"); - } - else - { - _applyInfo->setText( Wt::WString::fromUTF8("Cannot apply new parameters: ") + error); - _applyInfo->setStyleClass("alert alert-danger"); - } - } - else - { - _applyInfo->setText( Wt::WString::fromUTF8("Cannot apply new parameters!")); - _applyInfo->setStyleClass("alert alert-danger"); - } - updateView(_model); -} - -} // namespace Settings -} // namespace UserInterface diff --git a/src/ui/settings/SettingsAccountFormView.hpp b/src/ui/settings/SettingsAccountFormView.hpp deleted file mode 100644 index ebe02e5e..00000000 --- a/src/ui/settings/SettingsAccountFormView.hpp +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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 . - */ - -#ifndef UI_SETTINGS_ACCOUNT_FORM_VIEW_HPP -#define UI_SETTINGS_ACCOUNT_FORM_VIEW_HPP - -#include -#include -#include - -namespace UserInterface { -namespace Settings { - -class AccountFormModel; - -class AccountFormView : public Wt::WTemplateFormView -{ - public: - AccountFormView(std::string userId, Wt::WContainerWidget *parent = 0); - - private: - void processCancel(); // reload from DB - void processSave(); // commit into DB - - AccountFormModel* _model; - Wt::WText* _applyInfo; - -}; - -} // namespace Settings -} // namespace UserInterface - -#endif diff --git a/src/ui/settings/SettingsAudioFormView.cpp b/src/ui/settings/SettingsAudioFormView.cpp deleted file mode 100644 index 91c49015..00000000 --- a/src/ui/settings/SettingsAudioFormView.cpp +++ /dev/null @@ -1,258 +0,0 @@ -/* - * 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 . - */ - -#include - -#include -#include -#include -#include - -#include "utils/Logger.hpp" - -#include "common/Validators.hpp" -#include "LmsApplication.hpp" - -#include "SettingsAudioFormView.hpp" - -namespace { -std::string encodingToString(Database::AudioEncoding encoding) -{ - - switch (encoding) - { - case Database::AudioEncoding::AUTO: return "Automatic"; - case Database::AudioEncoding::MP3: return "MP3"; - case Database::AudioEncoding::OGA: return "OGG"; - case Database::AudioEncoding::WEBMA: return "WebM"; - } - - return "?"; -} -} - -namespace UserInterface { -namespace Settings { - -class AudioFormModel : public Wt::WFormModel -{ - public: - - // Associate each field with a unique string literal. - static const Field BitrateField; - static const Field EncodingField; - - AudioFormModel(Wt::WObject *parent = 0) - : Wt::WFormModel(parent) - { - initializeModels(); - - addField(BitrateField); - addField(EncodingField); - - setValidator(BitrateField, new Wt::WValidator(true)); // mandatory - setValidator(EncodingField, new Wt::WValidator(true)); // mandatory - - // populate the model with initial data - loadData(); - } - - Wt::WAbstractItemModel *bitrateModel() { return _bitrateModel; } - Wt::WAbstractItemModel *encodingModel() { return _encodingModel; } - - void loadData() - { - Wt::Dbo::Transaction transaction(DboSession()); - - setValue(BitrateField, std::min(CurrentUser()->getMaxAudioBitrate(), CurrentUser()->getAudioBitrate()) / 1000); // in kps - int encodingRow = getAudioEncodingRow( CurrentUser()->getAudioEncoding()); - setValue(EncodingField, getAudioEncodingString(encodingRow)); - } - - bool saveData(Wt::WString& error) - { - // DBO transaction active here - try { - Wt::Dbo::Transaction transaction(DboSession()); - - // user may have been deleted by someone else - CurrentUser().modify()->setAudioBitrate( Wt::asNumber(value(BitrateField)) * 1000); // in kbps - - int encodingRow = getAudioEncodingRow( boost::any_cast(value((EncodingField)))); - CurrentUser().modify()->setAudioEncoding( getAudioEncodingValue(encodingRow)); - } - catch(Wt::Dbo::Exception& exception) - { - LMS_LOG(UI, ERROR) << "Dbo exception: " << exception.what(); - return false; - } - - return true; - } - - int getAudioEncodingRow( Database::AudioEncoding encoding) - { - for (int i = 0; i < _encodingModel->rowCount(); ++i) - { - if (getAudioEncodingValue(i) == encoding) - return i; - } - return -1; - } - - int getAudioEncodingRow( Wt::WString encoding) - { - for (int i = 0; i < _encodingModel->rowCount(); ++i) - { - if (getAudioEncodingString(i) == encoding) - return i; - } - return -1; - } - - Database::AudioEncoding getAudioEncodingValue(int row) - { - return boost::any_cast - (_encodingModel->data(_encodingModel->index(row, 0), Wt::UserRole)); - } - - Wt::WString getAudioEncodingString(int row) - { - return boost::any_cast - (_encodingModel->data(_encodingModel->index(row, 0), Wt::DisplayRole)); - } - - - private: - void initializeModels() - { - Wt::Dbo::Transaction transaction(DboSession()); - - _bitrateModel = new Wt::WStringListModel(this); - BOOST_FOREACH(std::size_t bitrate, Database::User::audioBitrates) - { - if (bitrate <= CurrentUser()->getMaxAudioBitrate()) - _bitrateModel->addString( Wt::WString("{1}").arg( bitrate / 1000 ) ); // in kbps - } - - _encodingModel = new Wt::WStringListModel(this); - int row = 0; - BOOST_FOREACH(Database::AudioEncoding encoding, Database::User::audioEncodings) - { - _encodingModel->addString(encodingToString(encoding)); - _encodingModel->setData(row, 0, encoding, Wt::UserRole); - row++; - } - } - - std::string _userId; - Wt::WStringListModel* _bitrateModel; - Wt::WStringListModel* _encodingModel; -}; - -const Wt::WFormModel::Field AudioFormModel::BitrateField = "bitrate"; -const Wt::WFormModel::Field AudioFormModel::EncodingField = "encoding"; - -AudioFormView::AudioFormView(Wt::WContainerWidget *parent) -: Wt::WTemplateFormView(parent) -{ - - _model = new AudioFormModel(this); - - setTemplateText(tr("settings-audio")); - addFunction("id", &WTemplate::Functions::id); - addFunction("block", &WTemplate::Functions::id); - - _applyInfo = new Wt::WText(); - _applyInfo->setInline(false); - _applyInfo->hide(); - bindWidget("apply-info", _applyInfo); - - // Bitrate - Wt::WComboBox *bitrateCB = new Wt::WComboBox(); - setFormWidget(AudioFormModel::BitrateField, bitrateCB); - bitrateCB->setStyleClass("span2"); - bitrateCB->setModel(_model->bitrateModel()); - bitrateCB->changed().connect(_applyInfo, &Wt::WWidget::hide); - - // Encoding - Wt::WComboBox *encodingCB = new Wt::WComboBox(); - setFormWidget(AudioFormModel::EncodingField, encodingCB); - encodingCB->setModel(_model->encodingModel()); - encodingCB->changed().connect(_applyInfo, &Wt::WWidget::hide); - - // Title & Buttons - bindString("title", "Audio settings"); - - Wt::WPushButton *saveButton = new Wt::WPushButton("Apply"); - saveButton->setStyleClass("btn-primary"); - bindWidget("save-button", saveButton); - saveButton->clicked().connect(this, &AudioFormView::processSave); - - Wt::WPushButton *cancelButton = new Wt::WPushButton("Discard"); - bindWidget("cancel-button", cancelButton); - cancelButton->clicked().connect(this, &AudioFormView::processCancel); - - updateView(_model); -} - -void -AudioFormView::processCancel() -{ - _applyInfo->show(); - - _applyInfo->setText( Wt::WString::fromUTF8("Parameters reverted!")); - _applyInfo->setStyleClass("alert alert-info"); - _model->loadData(); - - _model->validate(); - updateView(_model); -} - -void -AudioFormView::processSave() -{ - updateModel(_model); - - _applyInfo->show(); - if (_model->validate()) - { - Wt::WString error; - // commit model into DB - if (_model->saveData(error) ) - { - _applyInfo->setText( Wt::WString::fromUTF8("New parameters successfully applied!")); - _applyInfo->setStyleClass("alert alert-success"); - } - else - { - _applyInfo->setText( Wt::WString::fromUTF8("Cannot apply new parameters: ") + error); - _applyInfo->setStyleClass("alert alert-danger"); - } - } - else - { - _applyInfo->setText( Wt::WString::fromUTF8("Cannot apply new parameters!")); - _applyInfo->setStyleClass("alert alert-danger"); - } - updateView(_model); -} - -} // namespace Settings -} // namespace UserInterface diff --git a/src/ui/settings/SettingsAudioFormView.hpp b/src/ui/settings/SettingsAudioFormView.hpp deleted file mode 100644 index deed1ea6..00000000 --- a/src/ui/settings/SettingsAudioFormView.hpp +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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 . - */ - -#ifndef UI_SETTINGS_AUDIO_FORM_VIEW_HPP -#define UI_SETTINGS_AUDIO_ACCOUNT_FORM_VIEW_HPP - -#include -#include -#include - -namespace UserInterface { -namespace Settings { - -class AudioFormModel; - -class AudioFormView : public Wt::WTemplateFormView -{ - public: - AudioFormView(Wt::WContainerWidget *parent = 0); - - private: - void processCancel(); // reload from DB - void processSave(); // commit into DB - - AudioFormModel* _model; - Wt::WText* _applyInfo; - -}; - -} // namespace Settings -} // namespace UserInterface - -#endif diff --git a/src/ui/settings/SettingsDatabaseFormView.cpp b/src/ui/settings/SettingsDatabaseFormView.cpp deleted file mode 100644 index 1379d0d1..00000000 --- a/src/ui/settings/SettingsDatabaseFormView.cpp +++ /dev/null @@ -1,400 +0,0 @@ -/* - * 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 . - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "utils/Logger.hpp" - -#include "database/Setting.hpp" - -#include "common/DirectoryValidator.hpp" -#include "LmsApplication.hpp" - -#include "SettingsDatabaseFormView.hpp" - -namespace UserInterface { -namespace Settings { - -using namespace Database; - -class DatabaseFormModel : public Wt::WFormModel -{ - public: - // Associate each field with a unique string literal. - static const Field UpdatePeriodField; - static const Field UpdateStartTimeField; - static const Field AudioFileExtensionsField; - static const Field VideoFileExtensionsField; - static const Field TagsHighLevelAcousticBrainz; - static const Field TagsHighLevelAcousticBrainzMinProbability; - static const Field TagsSimilarityAcousticBrainz; - - DatabaseFormModel(Wt::WObject *parent = 0) - : Wt::WFormModel(parent) - { - initializeModels(); - - addField(UpdatePeriodField); - addField(UpdateStartTimeField); - addField(AudioFileExtensionsField); - addField(VideoFileExtensionsField); - addField(TagsHighLevelAcousticBrainz); - addField(TagsHighLevelAcousticBrainzMinProbability); - addField(TagsSimilarityAcousticBrainz); - - setValidator(UpdatePeriodField, createUpdatePeriodValidator()); - setValidator(UpdateStartTimeField, createStartTimeValidator()); - setValidator(AudioFileExtensionsField, createFileExtensionValidator()); - setValidator(VideoFileExtensionsField, createFileExtensionValidator()); - setValidator(TagsHighLevelAcousticBrainzMinProbability, createMinProbabilityValidator()); - - // populate the model with initial data - loadData(); - } - - Wt::WAbstractItemModel *updatePeriodModel() { return _updatePeriodModel; } - Wt::WAbstractItemModel *updateStartTimeModel() { return _updateStartTimeModel; } - - void loadData() - { - int periodRow = getUpdatePeriodModelRow( Setting::getString(DboSession(), "update_period")); - if (periodRow != -1) - setValue(UpdatePeriodField, updatePeriodDisplay(periodRow)); - - int startTimeRow = getUpdateStartTimeModelRow( Setting::getDuration(DboSession(), "update_start_time") ); - if (startTimeRow != -1) - setValue(UpdateStartTimeField, updateStartTime( startTimeRow ) ); - - setValue(AudioFileExtensionsField, Setting::getString(DboSession(), "audio_file_extensions")); - setValue(VideoFileExtensionsField, Setting::getString(DboSession(), "video_file_extensions")); - - setValue(TagsHighLevelAcousticBrainz, Setting::getBool(DboSession(), "tags_highlevel_acousticbrainz")); - setValue(TagsHighLevelAcousticBrainzMinProbability, Setting::getInt(DboSession(), "tags_highlevel_acousticbrainz_min_probability")); - setValue(TagsSimilarityAcousticBrainz, Setting::getBool(DboSession(), "tags_similarity_acousticbrainz")); - } - - void saveData() - { - int periodRow = getUpdatePeriodModelRow( boost::any_cast(value(UpdatePeriodField))); - assert(periodRow != -1); - Setting::setString(DboSession(), "update_period", updatePeriodSetting( periodRow ) ); - - int startTimeRow = getUpdateStartTimeModelRow( boost::any_cast(value(UpdateStartTimeField))); - assert(startTimeRow != -1); - Setting::setDuration(DboSession(), "update_start_time", updateStartTimeDuration( startTimeRow ) ); - - Setting::setString(DboSession(), "audio_file_extensions", boost::any_cast(value(AudioFileExtensionsField)).toUTF8()); - Setting::setString(DboSession(), "video_file_extensions", boost::any_cast(value(VideoFileExtensionsField)).toUTF8()); - - Setting::setBool(DboSession(), "tags_highlevel_acousticbrainz", boost::any_cast(value(TagsHighLevelAcousticBrainz))); - Setting::setInt(DboSession(), "tags_highlevel_acousticbrainz_min_probability", std::stoi(boost::any_cast(value(TagsHighLevelAcousticBrainzMinProbability)).toUTF8())); - Setting::setBool(DboSession(), "tags_similarity_acousticbrainz", boost::any_cast(value(TagsSimilarityAcousticBrainz))); - } - - void setImmediateScan() - { - Setting::setBool(DboSession(), "manual_scan_requested", true); - } - - int getUpdatePeriodModelRow(Wt::WString value) - { - for (int i = 0; i < _updatePeriodModel->rowCount(); ++i) - { - if (updatePeriodDisplay(i) == value) - return i; - } - return -1; - } - - int getUpdatePeriodModelRow(std::string value) - { - for (int i = 0; i < _updatePeriodModel->rowCount(); ++i) - { - if (updatePeriodSetting(i) == value) - return i; - } - - return -1; - } - - std::string updatePeriodSetting(int row) { - return boost::any_cast - (_updatePeriodModel->data(_updatePeriodModel->index(row, 0), Wt::UserRole)); - } - - Wt::WString updatePeriodDisplay(int row) { - return boost::any_cast - (_updatePeriodModel->data(_updatePeriodModel->index(row, 0), Wt::DisplayRole)); - } - - - int getUpdateStartTimeModelRow(Wt::WString value) - { - for (int i = 0; i < _updateStartTimeModel->rowCount(); ++i) - { - if (updateStartTime(i) == value) - return i; - } - - return -1; - } - - int getUpdateStartTimeModelRow(boost::posix_time::time_duration duration) - { - for (int i = 0; i < _updateStartTimeModel->rowCount(); ++i) - { - if (updateStartTimeDuration(i) == duration) - return i; - } - - return -1; - } - - boost::posix_time::time_duration updateStartTimeDuration(int row) { - return boost::any_cast - (_updateStartTimeModel->data(_updateStartTimeModel->index(row, 0), Wt::UserRole)); - } - - Wt::WString updateStartTime(int row) { - return boost::any_cast - (_updateStartTimeModel->data(_updateStartTimeModel->index(row, 0), Wt::DisplayRole)); - } - - - private: - - void initializeModels() { - - _updatePeriodModel = new Wt::WStringListModel(this); - - _updatePeriodModel->addString("Never"); - _updatePeriodModel->setData(0, 0, std::string("never"), Wt::UserRole); - - _updatePeriodModel->addString("Daily"); - _updatePeriodModel->setData(1, 0, std::string("daily"), Wt::UserRole); - - _updatePeriodModel->addString("Weekly"); - _updatePeriodModel->setData(2, 0, std::string("weekly"), Wt::UserRole); - - _updatePeriodModel->addString("Monthly"); - _updatePeriodModel->setData(3, 0, std::string("monthly"), Wt::UserRole); - - _updateStartTimeModel = new Wt::WStringListModel(this); - - for (std::size_t i = 0; i < 24; ++i) - { - boost::posix_time::time_duration dur = boost::posix_time::hours(i); - - boost::posix_time::time_facet* facet = new boost::posix_time::time_facet("%H:%M"); - facet->time_duration_format("%H:%M"); - - std::ostringstream oss; - oss.imbue(std::locale(oss.getloc(), facet)); - - oss << dur; - - _updateStartTimeModel->addString( oss.str() ); - _updateStartTimeModel->setData(i, 0, dur, Wt::UserRole); - } - - } - - Wt::WValidator *createUpdatePeriodValidator() - { - Wt::WValidator* v = new Wt::WValidator(); - v->setMandatory(true); - return v; - } - - Wt::WValidator *createStartTimeValidator() - { - Wt::WValidator* v = new Wt::WValidator(); - v->setMandatory(true); - return v; - } - - Wt::WValidator *createFileExtensionValidator() - { - Wt::WRegExpValidator *v = new Wt::WRegExpValidator("(?:\\.\\w+(?:\\s*))+"); - return v; - } - - Wt::WValidator *createMinProbabilityValidator() - { - Wt::WIntValidator *v = new Wt::WIntValidator(50, 100); - v->setMandatory(true); - return v; - } - - Wt::WStringListModel* _updatePeriodModel; - Wt::WStringListModel* _updateStartTimeModel; - -}; - -const Wt::WFormModel::Field DatabaseFormModel::UpdatePeriodField = "update-period"; -const Wt::WFormModel::Field DatabaseFormModel::UpdateStartTimeField = "update-start-time"; -const Wt::WFormModel::Field DatabaseFormModel::AudioFileExtensionsField = "audio-file-extensions"; -const Wt::WFormModel::Field DatabaseFormModel::VideoFileExtensionsField = "video-file-extensions"; -const Wt::WFormModel::Field DatabaseFormModel::TagsHighLevelAcousticBrainz = "tags-highlevel-acousticbrainz"; -const Wt::WFormModel::Field DatabaseFormModel::TagsHighLevelAcousticBrainzMinProbability = "tags-highlevel-acousticbrainz-min-probability"; -const Wt::WFormModel::Field DatabaseFormModel::TagsSimilarityAcousticBrainz = "tags-similarity-acousticbrainz"; - - -DatabaseFormView::DatabaseFormView(Wt::WContainerWidget *parent) -: Wt::WTemplateFormView(parent) -{ - _model = new DatabaseFormModel(this); - - setTemplateText(tr("settings-database")); - addFunction("id", &WTemplate::Functions::id); - addFunction("block", &WTemplate::Functions::id); - - _applyInfo = new Wt::WText(); - _applyInfo->setInline(false); - _applyInfo->hide(); - bindWidget("apply-info", _applyInfo); - - // Update Period - Wt::WComboBox *updatePeriodCB = new Wt::WComboBox(); - setFormWidget(DatabaseFormModel::UpdatePeriodField, updatePeriodCB); - updatePeriodCB->setModel(_model->updatePeriodModel()); - updatePeriodCB->changed().connect(_applyInfo, &Wt::WWidget::hide); - - // Update Start Time - Wt::WComboBox *updateStartTimeCB = new Wt::WComboBox(); - setFormWidget(DatabaseFormModel::UpdateStartTimeField, updateStartTimeCB); - updateStartTimeCB->setModel(_model->updateStartTimeModel()); - updateStartTimeCB->changed().connect(_applyInfo, &Wt::WWidget::hide); - - // Audio file extensions - Wt::WLineEdit *audioFileExtensionsEdit = new Wt::WLineEdit(); - setFormWidget(DatabaseFormModel::AudioFileExtensionsField, audioFileExtensionsEdit); - audioFileExtensionsEdit->changed().connect(_applyInfo, &Wt::WWidget::hide); - - // Video file extensions - Wt::WLineEdit *videoFileExtensionsEdit = new Wt::WLineEdit(); - setFormWidget(DatabaseFormModel::VideoFileExtensionsField, videoFileExtensionsEdit); - videoFileExtensionsEdit->changed().connect(_applyInfo, &Wt::WWidget::hide); - - // Tags from AB high level - Wt::WCheckBox* highLevel = new Wt::WCheckBox(); - setFormWidget(DatabaseFormModel::TagsHighLevelAcousticBrainz, highLevel); - - setFormWidget(DatabaseFormModel::TagsHighLevelAcousticBrainzMinProbability, new Wt::WSpinBox()); - - highLevel->changed().connect(std::bind([=] { - _model->setReadOnly(DatabaseFormModel::TagsHighLevelAcousticBrainzMinProbability, - !(highLevel->checkState() == Wt::Checked)); - updateModel(_model); - updateViewField(_model, DatabaseFormModel::TagsHighLevelAcousticBrainzMinProbability); - })); - - // Tags from AB similarity - setFormWidget(DatabaseFormModel::TagsSimilarityAcousticBrainz, new Wt::WCheckBox()); - - // Title & Buttons - bindString("scan-title", "Scan settings"); - bindString("tags-title", "Tag settings"); - - Wt::WPushButton *saveButton = new Wt::WPushButton("Apply"); - bindWidget("apply-button", saveButton); - saveButton->setStyleClass("btn-primary"); - - Wt::WPushButton *discardButton = new Wt::WPushButton("Discard"); - bindWidget("discard-button", discardButton); - - saveButton->clicked().connect(this, &DatabaseFormView::processSave); - discardButton->clicked().connect(this, &DatabaseFormView::processDiscard); - - Wt::WPushButton *immediateScanButton = new Wt::WPushButton("Immediate scan"); - immediateScanButton->setStyleClass("btn-warning"); - bindWidget("immediate-scan-button", immediateScanButton); - immediateScanButton->clicked().connect(this, &DatabaseFormView::processImmediateScan); - - updateView(_model); - -} - -void -DatabaseFormView::processImmediateScan() -{ - - _model->setImmediateScan(); - - _applyInfo->setText( Wt::WString::fromUTF8("Media folder scan has been started!" ) ); - _applyInfo->setStyleClass("alert alert-warning"); - _applyInfo->show(); - - _sigChanged.emit(); -} - -void -DatabaseFormView::processDiscard() -{ - _applyInfo->show(); - - _applyInfo->setText( Wt::WString::fromUTF8("Parameters reverted!")); - _applyInfo->setStyleClass("alert alert-info"); - - _model->loadData(); - - _model->validate(); - updateView(_model); -} - -void -DatabaseFormView::processSave() -{ - updateModel(_model); - - _applyInfo->show(); - - if (_model->validate()) { - // Make the model to commit data into DB - _model->saveData(); - - _sigChanged.emit(); - - _applyInfo->setText( Wt::WString::fromUTF8("New parameters successfully applied!")); - _applyInfo->setStyleClass("alert alert-success"); - } - else { - _applyInfo->setText( Wt::WString::fromUTF8("Cannot apply new parameters!")); - _applyInfo->setStyleClass("alert alert-danger"); - } - // Udate the view: Delete any validation message in the view, etc. - updateView(_model); -} - - -} // namespace Settings -} // namespace UserInterface - - diff --git a/src/ui/settings/SettingsDatabaseFormView.hpp b/src/ui/settings/SettingsDatabaseFormView.hpp deleted file mode 100644 index 23585fdb..00000000 --- a/src/ui/settings/SettingsDatabaseFormView.hpp +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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 . - */ - -#ifndef UI_SETTINGS_DB_FORM_VIEW_HPP -#define UI_SETTINGS_DB_FORM_VIEW_HPP - -#include -#include -#include -#include - -namespace UserInterface { -namespace Settings { - -class DatabaseFormModel; - -class DatabaseFormView : public Wt::WTemplateFormView -{ - public: - DatabaseFormView(Wt::WContainerWidget *parent = 0); - - Wt::Signal& changed() { return _sigChanged; } - - private: - - Wt::Signal _sigChanged; - - void processSave(); - void processDiscard(); - void processImmediateScan(); - - Wt::WText *_applyInfo; - DatabaseFormModel *_model; - -}; - - -} // namespace Settings -} // namespace UserInterface - -#endif - diff --git a/src/ui/settings/SettingsFirstConnectionFormView.cpp b/src/ui/settings/SettingsFirstConnectionFormView.cpp deleted file mode 100644 index c6873079..00000000 --- a/src/ui/settings/SettingsFirstConnectionFormView.cpp +++ /dev/null @@ -1,229 +0,0 @@ -/* - * 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 . - */ - -#include - -#include -#include -#include -#include - -#include "utils/Logger.hpp" - -#include "common/Validators.hpp" -#include "LmsApplication.hpp" - -#include "SettingsFirstConnectionFormView.hpp" - -namespace UserInterface { -namespace Settings { - -class FirstConnectionFormModel : public Wt::WFormModel -{ - public: - - // Associate each field with a unique string literal. - static const Field NameField; - static const Field EmailField; - static const Field PasswordField; - static const Field PasswordConfirmField; - - FirstConnectionFormModel(Wt::WObject *parent = 0) - : Wt::WFormModel(parent) - { - addField(NameField); - addField(EmailField); - addField(PasswordField); - addField(PasswordConfirmField); - - setValidator(NameField, createNameValidator()); - setValidator(EmailField, createEmailValidator()); - - } - - bool saveData(Wt::WString& error) - { - // DBO transaction active here - try { - Wt::Dbo::Transaction transaction(DboSession()); - - // Check if a user already exist - // If it's the case, just do nothing - if (!Database::User::getAll(DboSession()).empty()) - { - LMS_LOG(UI, ERROR) << "Admin user already created"; - error = Wt::WString("Admin user already created!"); - return false; - } - - // Create user - Wt::Auth::User authUser = DbHandler().getUserDatabase().registerNew(); - Database::User::pointer user = DbHandler().getUser(authUser); - - // Account - authUser.setIdentity(Wt::Auth::Identity::LoginName, valueText(NameField)); - authUser.setEmail(valueText(EmailField).toUTF8()); - Database::Handler::getPasswordService().updatePassword(authUser, valueText(PasswordField)); - - user.modify()->setAdmin( true ); - - } - catch(Wt::Dbo::Exception& exception) - { - LMS_LOG(UI, ERROR) << "Dbo exception: " << exception.what(); - error = Wt::WString(exception.what()); - return false; - } - - return true; - } - - bool validateField(Field field) - { - // DBO transaction active here - - Wt::WString error; - - if (field == PasswordField) - { - // Password is mandatory if we create the user - if (!valueText(PasswordField).empty()) - { - // Evaluate the strength of the password - Wt::Auth::AbstractPasswordService::StrengthValidatorResult res - = Database::Handler::getPasswordService().strengthValidator()->evaluateStrength(valueText(PasswordField), - valueText(NameField), - valueText(EmailField).toUTF8()); - - if (!res.isValid()) - error = res.message(); - } - else - return Wt::WFormModel::validateField(field); - } - else if (field == PasswordConfirmField) - { - if (validation(PasswordField).state() == Wt::WValidator::Valid) - { - if (valueText(PasswordField) != valueText(PasswordConfirmField)) - error = Wt::WString::tr("Wt.Auth.passwords-dont-match"); - } - } - else - { - // Apply validators - return Wt::WFormModel::validateField(field); - } - - setValidation(field, Wt::WValidator::Result( error.empty() ? Wt::WValidator::Valid : Wt::WValidator::Invalid, error)); - - return validation(field).state() == Wt::WValidator::Valid; - } - - - private: - -}; - -const Wt::WFormModel::Field FirstConnectionFormModel::NameField = "name"; -const Wt::WFormModel::Field FirstConnectionFormModel::EmailField = "email"; -const Wt::WFormModel::Field FirstConnectionFormModel::PasswordField = "password"; -const Wt::WFormModel::Field FirstConnectionFormModel::PasswordConfirmField = "password-confirm"; - -FirstConnectionFormView::FirstConnectionFormView(Wt::WContainerWidget *parent) -: Wt::WTemplateFormView(parent) -{ - - _model = new FirstConnectionFormModel(this); - - setTemplateText(tr("settings-first-connection")); - addFunction("id", &WTemplate::Functions::id); - addFunction("block", &WTemplate::Functions::id); - - _applyInfo = new Wt::WText(); - _applyInfo->setInline(false); - _applyInfo->hide(); - bindWidget("apply-info", _applyInfo); - - // Name - Wt::WLineEdit* accountEdit = new Wt::WLineEdit(); - setFormWidget(FirstConnectionFormModel::NameField, accountEdit); - accountEdit->changed().connect(_applyInfo, &Wt::WWidget::hide); - - // Email - Wt::WLineEdit* emailEdit = new Wt::WLineEdit(); - setFormWidget(FirstConnectionFormModel::EmailField, emailEdit); - emailEdit->changed().connect(_applyInfo, &Wt::WWidget::hide); - - // Password - Wt::WLineEdit* passwordEdit = new Wt::WLineEdit(); - setFormWidget(FirstConnectionFormModel::PasswordField, passwordEdit ); - passwordEdit->setEchoMode(Wt::WLineEdit::Password); - passwordEdit->changed().connect(_applyInfo, &Wt::WWidget::hide); - - // Password confirmation - Wt::WLineEdit* passwordConfirmEdit = new Wt::WLineEdit(); - setFormWidget(FirstConnectionFormModel::PasswordConfirmField, passwordConfirmEdit); - passwordConfirmEdit->setEchoMode(Wt::WLineEdit::Password); - passwordConfirmEdit->changed().connect(_applyInfo, &Wt::WWidget::hide); - - // Title & Buttons - bindString("title", "Create Admin account"); - - _saveButton = new Wt::WPushButton("Create"); - _saveButton->setStyleClass("btn-primary"); - bindWidget("save-button", _saveButton); - _saveButton->clicked().connect(this, &FirstConnectionFormView::processSave); - - updateView(_model); - -} - -void -FirstConnectionFormView::processSave() -{ - updateModel(_model); - - _applyInfo->show(); - if (_model->validate()) - { - Wt::WString error; - // commit model into DB - if (_model->saveData(error) ) - { - _applyInfo->setText( Wt::WString::fromUTF8("New parameters successfully applied! Please refresh this page in order to login")); - _applyInfo->setStyleClass("alert alert-success"); - _saveButton->hide(); - } - else - { - _applyInfo->setText( Wt::WString::fromUTF8("Cannot apply new parameters: ") + error); - _applyInfo->setStyleClass("alert alert-danger"); - } - } - else - { - _applyInfo->setText( Wt::WString::fromUTF8("Cannot apply new parameters!")); - _applyInfo->setStyleClass("alert alert-danger"); - } - updateView(_model); -} - -} // namespace Settings -} // namespace UserInterface diff --git a/src/ui/settings/SettingsFirstConnectionFormView.hpp b/src/ui/settings/SettingsFirstConnectionFormView.hpp deleted file mode 100644 index 1761c192..00000000 --- a/src/ui/settings/SettingsFirstConnectionFormView.hpp +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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 . - */ - -#ifndef UI_SETTINGS_FIRST_CONNECTION_FORM_VIEW_HPP -#define UI_SETTINGS_FIRST_CONNECTION_FORM_VIEW_HPP - -#include -#include -#include -#include - -namespace UserInterface { -namespace Settings { - -class FirstConnectionFormModel; - -class FirstConnectionFormView : public Wt::WTemplateFormView -{ - public: - FirstConnectionFormView(Wt::WContainerWidget *parent = 0); - - private: - - void processSave(); - - Wt::WPushButton* _saveButton; - FirstConnectionFormModel* _model; - Wt::WText* _applyInfo; - -}; - - -} // namespace Settings -} // namespace UserInterface - -#endif diff --git a/src/ui/settings/SettingsMediaDirectories.cpp b/src/ui/settings/SettingsMediaDirectories.cpp deleted file mode 100644 index d9b75a20..00000000 --- a/src/ui/settings/SettingsMediaDirectories.cpp +++ /dev/null @@ -1,172 +0,0 @@ -/* - * 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 . - */ - -#include -#include -#include -#include - -#include "database/MediaDirectory.hpp" - -#include "LmsApplication.hpp" - -#include "SettingsMediaDirectoryFormView.hpp" - -#include "SettingsMediaDirectories.hpp" - -namespace UserInterface { -namespace Settings { - -MediaDirectories::MediaDirectories(Wt::WContainerWidget *parent) -: Wt::WContainerWidget(parent) -{ - // Stack two widgets: - _stack = new Wt::WStackedWidget(this); - - // 1/ the media directory table view - { - Wt::WGroupBox *container = new Wt::WGroupBox("Media Folders", _stack); - - _table = new Wt::WTable(container); - - _table->addStyleClass("table form-inline"); - - _table->toggleStyleClass("table-hover", true); - _table->toggleStyleClass("table-striped", true); - - _table->setHeaderCount(1); - - _table->elementAt(0, 0)->addWidget(new Wt::WText("#")); - _table->elementAt(0, 1)->addWidget(new Wt::WText("Path")); - _table->elementAt(0, 2)->addWidget(new Wt::WText("Type")); - - Wt::WPushButton* addBtn = new Wt::WPushButton("Add Folder"); - addBtn->setStyleClass("btn-success"); - container->addWidget( addBtn ); - addBtn->clicked().connect(boost::bind(&MediaDirectories::handleCreateMediaDirectory, this)); - } - - refresh(); -} - -void -MediaDirectories::refresh(void) -{ - - assert(_table->rowCount() > 0); - for (int i = _table->rowCount() - 1; i > 0; --i) - _table->deleteRow(i); - - Wt::Dbo::Transaction transaction(DboSession()); - - std::vector mediaDirectories = Database::MediaDirectory::getAll(DboSession()); - - std::size_t id = 1; - BOOST_FOREACH(Database::MediaDirectory::pointer mediaDirectory, mediaDirectories) - { - _table->elementAt(id, 0)->addWidget(new Wt::WText( Wt::WString::fromUTF8("{1}").arg(id))); - _table->elementAt(id, 1)->addWidget(new Wt::WText( Wt::WString::fromUTF8(mediaDirectory->getPath().string()) )); - - Wt::WString dirType; - switch(mediaDirectory->getType()) - { - case Database::MediaDirectory::Video: dirType = "Video"; break; - case Database::MediaDirectory::Audio: dirType = "Audio"; break; - } - - _table->elementAt(id, 2)->addWidget( new Wt::WText( dirType )); - - Wt::WPushButton* delBtn = new Wt::WPushButton("Delete"); - delBtn->setStyleClass("btn-danger"); - _table->elementAt(id, 3)->addWidget(delBtn); - delBtn->clicked().connect(boost::bind( &MediaDirectories::handleDelMediaDirectory, this, mediaDirectory->getPath(), mediaDirectory->getType() ) ); - - ++id; - } -} - -void -MediaDirectories::handleDelMediaDirectory(boost::filesystem::path p, Database::MediaDirectory::Type type) -{ - Wt::WMessageBox *messageBox = new Wt::WMessageBox - ("Delete Folder", - Wt::WString( "Deleting folder '{1}'?").arg(p.string()), - Wt::Question, Wt::Yes | Wt::No); - - messageBox->setModal(true); - - messageBox->buttonClicked().connect(std::bind([=] () { - if (messageBox->buttonResult() == Wt::Yes) - { - { - Wt::Dbo::Transaction transaction(DboSession()); - - // Delete the media diretory - Database::MediaDirectory::pointer mediaDirectory = Database::MediaDirectory::get(DboSession(), p, type); - if (mediaDirectory) - mediaDirectory.remove(); - } - - refresh(); - - // Emit something changed in the settings - _sigChanged.emit(); - } - - delete messageBox; - })); - - messageBox->show(); - -} - -void -MediaDirectories::handleCreateMediaDirectory(void) -{ - assert(_stack->count() == 1); - - MediaDirectoryFormView* formView = new MediaDirectoryFormView(_stack); - formView->completed().connect(this, &MediaDirectories::handleMediaDirectoryFormCompleted); - - _stack->setCurrentIndex(1); -} - -void -MediaDirectories::handleMediaDirectoryFormCompleted(bool changed) -{ - _stack->setCurrentIndex(0); - - if (changed) - { - // Refresh the user table if a change has been made - refresh(); - - // Emit something changed in the settings - _sigChanged.emit(); - } - - // Delete the form view - delete _stack->widget(1); - -} - -} // namespace UserInterface -} // namespace Settings - - diff --git a/src/ui/settings/SettingsMediaDirectories.hpp b/src/ui/settings/SettingsMediaDirectories.hpp deleted file mode 100644 index 4fb2bdf8..00000000 --- a/src/ui/settings/SettingsMediaDirectories.hpp +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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 . - */ - -#ifndef UI_SETTINGS_MEDIA_DIRECTORIES_HPP -#define UI_SETTINGS_MEDIA_DIRECTORIES_HPP - -#include -#include -#include -#include - -#include "database/MediaDirectory.hpp" - -namespace UserInterface { -namespace Settings { - -class MediaDirectories : public Wt::WContainerWidget -{ - public: - MediaDirectories(Wt::WContainerWidget *parent = 0); - - void refresh(); - - Wt::Signal& changed() { return _sigChanged; } - - private: - - Wt::Signal _sigChanged; - - void handleMediaDirectoryFormCompleted(bool changed); - - void handleDelMediaDirectory(boost::filesystem::path p, Database::MediaDirectory::Type type); - void handleCreateMediaDirectory(void); - - Wt::WStackedWidget* _stack; - Wt::WTable* _table; -}; - -} // namespace UserInterface -} // namespace Settings - -#endif - diff --git a/src/ui/settings/SettingsMediaDirectoryFormView.cpp b/src/ui/settings/SettingsMediaDirectoryFormView.cpp deleted file mode 100644 index a9e4a2f9..00000000 --- a/src/ui/settings/SettingsMediaDirectoryFormView.cpp +++ /dev/null @@ -1,185 +0,0 @@ -/* - * 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 . - */ - -#include - -#include -#include -#include -#include -#include - -#include "utils/Logger.hpp" - -#include "database/MediaDirectory.hpp" - -#include "common/DirectoryValidator.hpp" -#include "LmsApplication.hpp" - -#include "SettingsMediaDirectoryFormView.hpp" - -namespace UserInterface { -namespace Settings { - -// TODO validate if directory already exists -class MediaDirectoryFormModel : public Wt::WFormModel -{ - public: - - // Associate each field with a unique string literal. - static const Field PathField; - static const Field TypeField; - - MediaDirectoryFormModel(Wt::WObject *parent = 0) - : Wt::WFormModel(parent) - { - initializeModels(); - - addField(PathField); - addField(TypeField); - - DirectoryValidator* dirValidator = new DirectoryValidator(); - dirValidator->setMandatory(true); - setValidator(PathField, dirValidator); - setValidator(TypeField, new Wt::WValidator(true)); // mandatory - - } - - Wt::WAbstractItemModel *typeModel() { return _typeModel; } - - bool saveData(Wt::WString& error) - { - try { - Wt::Dbo::Transaction transaction(DboSession()); - - Database::MediaDirectory::Type type - = (valueText(TypeField) == "Audio") ? Database::MediaDirectory::Audio : Database::MediaDirectory::Video; - - if (Database::MediaDirectory::get(DboSession(), valueText(PathField).toUTF8(), type)) - { - error = "This Path/Type already exists!"; - return false; - } - - Database::MediaDirectory::create(DboSession(), valueText(PathField).toUTF8(), type); - - } - catch(Wt::Dbo::Exception& exception) - { - LMS_LOG(UI, ERROR) << "Dbo exception: " << exception.what(); - return false; - } - - return true; - } - - private: - void initializeModels() - { - _typeModel = new Wt::WStringListModel(this); - _typeModel->addString("Audio"); - _typeModel->addString("Video"); - } - - std::string _userId; - Wt::WStringListModel* _typeModel; -}; - -const Wt::WFormModel::Field MediaDirectoryFormModel::PathField = "path"; -const Wt::WFormModel::Field MediaDirectoryFormModel::TypeField = "type"; - -MediaDirectoryFormView::MediaDirectoryFormView(Wt::WContainerWidget *parent) -: Wt::WTemplateFormView(parent) -{ - - _model = new MediaDirectoryFormModel(this); - - setTemplateText(tr("settings-new-media-directory")); - addFunction("id", &WTemplate::Functions::id); - addFunction("block", &WTemplate::Functions::id); - - _applyInfo = new Wt::WText(); - _applyInfo->setInline(false); - _applyInfo->hide(); - bindWidget("apply-info", _applyInfo); - - // Path - Wt::WLineEdit* pathEdit = new Wt::WLineEdit(); - setFormWidget(MediaDirectoryFormModel::PathField, pathEdit); - pathEdit->changed().connect(_applyInfo, &Wt::WWidget::hide); - - // Type - Wt::WComboBox *typeCB = new Wt::WComboBox(); - setFormWidget(MediaDirectoryFormModel::TypeField, typeCB); - typeCB->setModel(_model->typeModel()); - typeCB->changed().connect(_applyInfo, &Wt::WWidget::hide); - - // Title & Buttons - bindString("title", "Add media folder"); - - Wt::WPushButton *saveButton = new Wt::WPushButton("Add"); - saveButton->setStyleClass("btn-primary"); - bindWidget("save-button", saveButton); - saveButton->clicked().connect(this, &MediaDirectoryFormView::processSave); - - Wt::WPushButton *cancelButton = new Wt::WPushButton("Cancel"); - bindWidget("cancel-button", cancelButton); - cancelButton->clicked().connect(this, &MediaDirectoryFormView::processCancel); - - updateView(_model); -} - -void -MediaDirectoryFormView::processCancel() -{ - // parent widget will delete this widget - completed().emit(false); -} - -void -MediaDirectoryFormView::processSave() -{ - updateModel(_model); - - _applyInfo->show(); - if (_model->validate()) - { - Wt::WString error; - // commit model into DB - if (_model->saveData(error) ) { - completed().emit(true); - return; - } - else - { - _applyInfo->setText( Wt::WString::fromUTF8("Cannot apply new parameters: ") + error); - _applyInfo->setStyleClass("alert alert-danger"); - } - } - else - { - _applyInfo->setText( Wt::WString::fromUTF8("Cannot apply new parameters!")); - _applyInfo->setStyleClass("alert alert-danger"); - } - - updateView(_model); -} - -} // namespace Settings -} // namespace UserInterface diff --git a/src/ui/settings/SettingsMediaDirectoryFormView.hpp b/src/ui/settings/SettingsMediaDirectoryFormView.hpp deleted file mode 100644 index 29554c74..00000000 --- a/src/ui/settings/SettingsMediaDirectoryFormView.hpp +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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 . - */ - -#ifndef UI_SETTINGS_MEDIA_DIR_FORM_VIEW_HPP -#define UI_SETTINGS_MEDIA_DIR_FORM_VIEW_HPP - -#include -#include -#include -#include - -namespace UserInterface { -namespace Settings { - -class MediaDirectoryFormModel; - -class MediaDirectoryFormView : public Wt::WTemplateFormView -{ - public: - MediaDirectoryFormView(Wt::WContainerWidget *parent = 0); - - // Signal emitted once the form is completed - Wt::Signal& completed() { return _sigCompleted; } - - private: - void processCancel(); // reload from DB - void processSave(); // commit into DB - - Wt::Signal _sigCompleted; - MediaDirectoryFormModel* _model; - Wt::WText* _applyInfo; - -}; - -} // namespace Settings -} // namespace UserInterface - -#endif diff --git a/src/ui/settings/SettingsUserFormView.cpp b/src/ui/settings/SettingsUserFormView.cpp deleted file mode 100644 index fc3224dd..00000000 --- a/src/ui/settings/SettingsUserFormView.cpp +++ /dev/null @@ -1,411 +0,0 @@ -/* - * 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 . - */ - -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "utils/Logger.hpp" - -#include "common/Validators.hpp" -#include "LmsApplication.hpp" - -#include "SettingsUserFormView.hpp" - - -namespace UserInterface { -namespace Settings { - -class UserFormModel : public Wt::WFormModel -{ - public: - - // Associate each field with a unique string literal. - static const Field NameField; - static const Field EmailField; - static const Field PasswordField; - static const Field PasswordConfirmField; - static const Field AdminField; - static const Field AudioBitrateLimitField; - static const Field VideoBitrateLimitField; - - UserFormModel(std::string userId, Wt::WObject *parent = 0) - : Wt::WFormModel(parent), - _userId(userId) - { - initializeModels(); - - addField(NameField); - addField(EmailField); - addField(PasswordField); - addField(PasswordConfirmField); - addField(AdminField); - addField(AudioBitrateLimitField); - addField(VideoBitrateLimitField); - - setValidator(NameField, createNameValidator()); - setValidator(EmailField, createEmailValidator()); - // If creating a user, passwords are mandatory - if (_userId.empty()) - { - setValidator(PasswordField, new Wt::WValidator(true)); // mandatory - setValidator(PasswordConfirmField, new Wt::WValidator(true)); // mandatory - } - setValidator(AudioBitrateLimitField, new Wt::WValidator(true)); // mandatory - setValidator(VideoBitrateLimitField, new Wt::WValidator(true)); // mandatory - - // populate the model with initial data - loadData(userId); - } - - Wt::WAbstractItemModel *audioBitrateModel() { return _audioBitrateModel; } - Wt::WAbstractItemModel *videoBitrateModel() { return _videoBitrateModel; } - - void loadData(std::string userId) - { - - if (!userId.empty()) - { - Wt::Dbo::Transaction transaction(DboSession()); - - Wt::Auth::User authUser = DbHandler().getUserDatabase().findWithId( userId ); - Database::User::pointer user = DbHandler().getUser(authUser); - - Wt::Auth::User currentUser = CurrentAuthUser(); - - if (user && authUser.isValid()) - { - if (user->isAdmin()) - { - setValue(AdminField, true); - - // We can cannot remove admin rights to ourselves - if (currentUser == authUser) - setReadOnly(AdminField, true); - - // if the user is admin, no need to limit it - setReadOnly(AudioBitrateLimitField, true); - setValidator(AudioBitrateLimitField, nullptr); - - setReadOnly(VideoBitrateLimitField, true); - setValidator(VideoBitrateLimitField, nullptr); - } - else - { - setValue(AudioBitrateLimitField, user->getMaxAudioBitrate() / 1000); // in kbps - setValue(VideoBitrateLimitField, user->getMaxVideoBitrate() / 1000); // in kbps - } - - setValue(NameField, authUser.identity(Wt::Auth::Identity::LoginName)); - if (!authUser.email().empty()) - setValue(EmailField, authUser.email()); - else - setValue(EmailField, authUser.unverifiedEmail()); - } - } - } - - bool saveData() - { - try { - Wt::Dbo::Transaction transaction(DboSession()); - - if (_userId.empty()) - { - // Create user - Wt::Auth::User authUser = DbHandler().getUserDatabase().registerNew(); - Database::User::pointer user = DbHandler().getUser(authUser); - - // Account - authUser.setIdentity(Wt::Auth::Identity::LoginName, valueText(NameField)); - authUser.setEmail(valueText(EmailField).toUTF8()); - Database::Handler::getPasswordService().updatePassword(authUser, valueText(PasswordField)); - - // Access - { - boost::any v = value(AdminField); - if (!v.empty() && boost::any_cast(v) == true) - user.modify()->setAdmin( true ); - else - user.modify()->setAdmin( false ); - } - - if (!isReadOnly(AudioBitrateLimitField)) - user.modify()->setMaxAudioBitrate( Wt::asNumber(value(AudioBitrateLimitField)) * 1000); // in bps - - if (!isReadOnly(VideoBitrateLimitField)) - user.modify()->setMaxVideoBitrate( Wt::asNumber(value(VideoBitrateLimitField)) * 1000); // in bps - - transaction.commit(); - } - else - { - // Update user - Wt::Auth::User authUser = DbHandler().getUserDatabase().findWithId(_userId); - Database::User::pointer user = DbHandler().getUser( authUser ); - - // user may have been deleted by someone else - if (!authUser.isValid()) { - LMS_LOG(UI, ERROR) << "user identity does not exist!"; - return false; - } - else if(!user) - { - LMS_LOG(UI, ERROR) << "User not found!"; - return false; - } - - // Account - authUser.setIdentity(Wt::Auth::Identity::LoginName, valueText(NameField)); - authUser.setEmail(valueText(EmailField).toUTF8()); - - // Password - if (!valueText(PasswordField).empty()) - Database::Handler::getPasswordService().updatePassword(authUser, valueText(PasswordField)); - - // Access - if (!isReadOnly(AdminField)) - { - boost::any v = value(AdminField); - if (!v.empty() && boost::any_cast(v) == true) - user.modify()->setAdmin( true ); - else - user.modify()->setAdmin( false ); - } - - if (!isReadOnly(AudioBitrateLimitField)) - user.modify()->setMaxAudioBitrate( Wt::asNumber(value(AudioBitrateLimitField)) * 1000); // in bps - - if (!isReadOnly(VideoBitrateLimitField)) - user.modify()->setMaxVideoBitrate( Wt::asNumber(value(VideoBitrateLimitField)) * 1000); // in bps - - transaction.commit(); - } - } - catch(Wt::Dbo::Exception& exception) - { - LMS_LOG(UI, ERROR) << "Dbo exception: " << exception.what(); - return false; - } - - return true; - } - - bool validateField(Field field) - { - Wt::WString error; - - if (field == NameField) - { - Wt::Dbo::Transaction transaction(DboSession()); - // Must be unique since used as LoginIdentity - Wt::Auth::User user = DbHandler().getUserDatabase().findWithIdentity(Wt::Auth::Identity::LoginName, valueText(field)); - if (user.isValid() && user.id() != _userId) - error = "Already exists"; - else - return Wt::WFormModel::validateField(field); - } - else if (field == PasswordField) - { - // Password is mandatory if we create the user - if (!valueText(PasswordField).empty()) - { - // Evaluate the strength of the password - Wt::Auth::AbstractPasswordService::StrengthValidatorResult res - = Database::Handler::getPasswordService().strengthValidator()->evaluateStrength(valueText(PasswordField), - valueText(NameField), - valueText(EmailField).toUTF8()); - - if (!res.isValid()) - error = res.message(); - } - else - return Wt::WFormModel::validateField(field); - } - else if (field == PasswordConfirmField) - { - if (validation(PasswordField).state() == Wt::WValidator::Valid) - { - if (valueText(PasswordField) != valueText(PasswordConfirmField)) - error = Wt::WString::tr("Wt.Auth.passwords-dont-match"); - } - } - else - { - // Apply validators - return Wt::WFormModel::validateField(field); - } - - setValidation(field, Wt::WValidator::Result( error.empty() ? Wt::WValidator::Valid : Wt::WValidator::Invalid, error)); - - return validation(field).state() == Wt::WValidator::Valid; - } - - - private: - - void initializeModels() - { - // AUDIO - _audioBitrateModel = new Wt::WStringListModel(this); - BOOST_FOREACH(std::size_t bitrate, Database::User::audioBitrates) - _audioBitrateModel->addString( Wt::WString("{1}").arg( bitrate / 1000 ) ); // in kbps - - // VIDEO - _videoBitrateModel = new Wt::WStringListModel(this); - BOOST_FOREACH(std::size_t bitrate, Database::User::videoBitrates) - _videoBitrateModel->addString( Wt::WString("{1}").arg( bitrate / 1000 ) ); // in kbps - - } - - std::string _userId; - Wt::WStringListModel* _audioBitrateModel; - Wt::WStringListModel* _videoBitrateModel; -}; - -const Wt::WFormModel::Field UserFormModel::NameField = "name"; -const Wt::WFormModel::Field UserFormModel::EmailField = "email"; -const Wt::WFormModel::Field UserFormModel::PasswordField = "password"; -const Wt::WFormModel::Field UserFormModel::PasswordConfirmField = "password-confirm"; -const Wt::WFormModel::Field UserFormModel::AdminField = "admin"; -const Wt::WFormModel::Field UserFormModel::AudioBitrateLimitField = "audio-bitrate-limit"; -const Wt::WFormModel::Field UserFormModel::VideoBitrateLimitField = "video-bitrate-limit"; - - -UserFormView::UserFormView(std::string userId, Wt::WContainerWidget *parent) -: Wt::WTemplateFormView(parent) -{ - - _model = new UserFormModel(userId, this); - - setTemplateText(tr("settings-new-user")); - addFunction("id", &WTemplate::Functions::id); - addFunction("block", &WTemplate::Functions::id); - - // Name - setFormWidget(UserFormModel::NameField, new Wt::WLineEdit()); - - // Email - setFormWidget(UserFormModel::EmailField, new Wt::WLineEdit()); - - // Password - Wt::WLineEdit* passwordEdit = new Wt::WLineEdit(); - setFormWidget(UserFormModel::PasswordField, passwordEdit ); - passwordEdit->setEchoMode(Wt::WLineEdit::Password); - - // Password confirmation - Wt::WLineEdit* passwordConfirmEdit = new Wt::WLineEdit(); - setFormWidget(UserFormModel::PasswordConfirmField, passwordConfirmEdit); - passwordConfirmEdit->setEchoMode(Wt::WLineEdit::Password); - - bindString("access", "Access"); - - // Admin Field - setFormWidget(UserFormModel::AdminField, new Wt::WCheckBox()); - - // AudioBitrate - Wt::WComboBox *audioBitrateCB = new Wt::WComboBox(); - setFormWidget(UserFormModel::AudioBitrateLimitField, audioBitrateCB); - audioBitrateCB->setStyleClass("span2"); - audioBitrateCB->setModel(_model->audioBitrateModel()); - - // VideoBitrate - Wt::WComboBox *videoBitrateCB = new Wt::WComboBox(); - setFormWidget(UserFormModel::VideoBitrateLimitField, videoBitrateCB); - videoBitrateCB->setStyleClass("span2"); - videoBitrateCB->setModel(_model->videoBitrateModel()); - - // Title & Buttons - Wt::WString title; - if (userId.empty()) { - title = Wt::WString("Create user"); - } - else { - Wt::Dbo::Transaction transaction (DboSession()); - Wt::Auth::User authUser = DbHandler().getUserDatabase().findWithId( userId ); - - Wt::WString userName; - if (authUser.isValid()) - userName = authUser.identity(Wt::Auth::Identity::LoginName); - else - ; // TODO display user deleted and close the widget - - title = Wt::WString("Edit user {1}").arg(userName); - } - - bindString("title", title); - - Wt::WPushButton *saveButton = new Wt::WPushButton(); - if (userId.empty()) { - saveButton->setText("Create user"); - saveButton->setStyleClass("btn-success"); - } - else - { - saveButton->setText("Save"); - saveButton->setStyleClass("btn-primary"); - } - bindWidget("save-button", saveButton); - saveButton->clicked().connect(this, &UserFormView::processSave); - - Wt::WPushButton *cancelButton = new Wt::WPushButton("Cancel"); - bindWidget("cancel-button", cancelButton); - cancelButton->clicked().connect(this, &UserFormView::processCancel); - - updateView(_model); - -} - - void -UserFormView::processCancel() -{ - // parent widget will delete this widget - completed().emit(false); -} - -void -UserFormView::processSave() -{ - updateModel(_model); - - - if (_model->validate()) - { - // commit model into DB - if (_model->saveData() ) - { - // parent widget will delete this widget - completed().emit(true); - } - // else TODO display a nice error message - } - else - { - updateView(_model); - } -} - -} // namespace Settings -} // namespace UserInterface diff --git a/src/ui/settings/SettingsUserFormView.hpp b/src/ui/settings/SettingsUserFormView.hpp deleted file mode 100644 index 1e495784..00000000 --- a/src/ui/settings/SettingsUserFormView.hpp +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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 . - */ - -#ifndef UI_SETTINGS_USER_FORM_VIEW_HPP -#define UI_SETTINGS_USER_FORM_VIEW_HPP - -#include -#include -#include - -namespace UserInterface { -namespace Settings { - -class UserFormModel; - -class UserFormView : public Wt::WTemplateFormView -{ - public: - - UserFormView(std::string userId, Wt::WContainerWidget *parent = 0); - - // Signal emitted once the form is completed - Wt::Signal& completed() { return _sigCompleted; } - - private: - - Wt::Signal _sigCompleted; - - void processSave(); - void processCancel(); - - UserFormModel* _model; - -}; - -} // namespace Settings -} // namespace UserInterface - -#endif diff --git a/src/ui/settings/SettingsUsers.cpp b/src/ui/settings/SettingsUsers.cpp deleted file mode 100644 index 314a2a59..00000000 --- a/src/ui/settings/SettingsUsers.cpp +++ /dev/null @@ -1,202 +0,0 @@ -/* - * 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 . - */ - - -#include -#include -#include -#include - -#include - -#include "utils/Logger.hpp" - -#include "SettingsUserFormView.hpp" -#include "LmsApplication.hpp" - -#include "SettingsUsers.hpp" - -namespace UserInterface { -namespace Settings { - -Users::Users(Wt::WContainerWidget *parent) -: Wt::WContainerWidget(parent) -{ - // Stack two widgets: - _stack = new Wt::WStackedWidget(this); - - // 1/ the user table view - { - Wt::WGroupBox *container = new Wt::WGroupBox("Users", _stack); - - _table = new Wt::WTable(container); - - _table->addStyleClass("table form-inline"); - - _table->toggleStyleClass("table-hover", true); - _table->toggleStyleClass("table-striped", true); - - _table->setHeaderCount(1); - - _table->elementAt(0, 0)->addWidget(new Wt::WText("#")); - _table->elementAt(0, 1)->addWidget(new Wt::WText("Name")); - _table->elementAt(0, 2)->addWidget(new Wt::WText("e-Mail")); - _table->elementAt(0, 3)->addWidget(new Wt::WText("Admin")); - - Wt::WPushButton* addBtn = new Wt::WPushButton("Add User"); - addBtn->setStyleClass("btn-success"); - container->addWidget( addBtn ); - addBtn->clicked().connect(boost::bind(&Users::handleCreateUser, this, "")); - } - - refresh(); -} - -void -Users::refresh(void) -{ - - assert(_table->rowCount() > 0); - for (int i = _table->rowCount() - 1; i > 0; --i) - _table->deleteRow(i); - - Wt::Dbo::Transaction transaction(DboSession()); - - const Wt::Auth::User& currentUser = CurrentAuthUser(); - - std::vector users = Database::User::getAll(DboSession()); - - std::size_t userIndex = 1; - for (std::size_t i = 0; i < users.size(); ++i) - { - - std::string userId = Database::User::getId(users[i]); - - Wt::Auth::User authUser; - - // Hack try/catch here since it may fail! - try { - authUser = DbHandler().getUserDatabase().findWithId( userId ); - } - catch(Wt::Dbo::Exception& e) - { - LMS_LOG(UI, ERROR) << "Caught exception when getting userId=" << userId << ": " << e.code(); - continue; - } - - if (!authUser.isValid()) { - LMS_LOG(UI, ERROR) << "Users::refresh: skipping invalid userId = " << userId; - continue; - } - - _table->elementAt(userIndex, 0)->addWidget(new Wt::WText( Wt::WString::fromUTF8("{1}").arg(userIndex))); - _table->elementAt(userIndex, 1)->addWidget(new Wt::WText( authUser.identity(Wt::Auth::Identity::LoginName)) ); - - Wt::WText* email = new Wt::WText(); - if (!authUser.email().empty()) { - email->setText(authUser.email()); - } - else { - email->setStyleClass("alert-danger"); - email->setText(authUser.unverifiedEmail()); - } - _table->elementAt(userIndex, 2)->addWidget( email ) ; - _table->elementAt(userIndex, 3)->addWidget(new Wt::WText( users[i]->isAdmin() ? "Yes" : "No" )); - - Wt::WPushButton* editBtn = new Wt::WPushButton("Edit"); - _table->elementAt(userIndex, 4)->addWidget(editBtn); - editBtn->clicked().connect(boost::bind( &Users::handleCreateUser, this, userId)); - - if (currentUser != authUser) - { - Wt::WPushButton* delBtn = new Wt::WPushButton("Delete"); - delBtn->setStyleClass("btn-danger"); - delBtn->setMargin(5, Wt::Left); - _table->elementAt(userIndex, 4)->addWidget(delBtn); - delBtn->clicked().connect(boost::bind( &Users::handleDelUser, this, authUser.identity(Wt::Auth::Identity::LoginName), userId)); - } - - ++userIndex; - } - -} - -void -Users::handleDelUser(Wt::WString loginNameIdentity, std::string id) -{ - Wt::WMessageBox *messageBox = new Wt::WMessageBox - ("Delete User", - Wt::WString( "Deleting user '{1}'?").arg(loginNameIdentity), - Wt::Question, Wt::Yes | Wt::No); - - messageBox->setModal(true); - - messageBox->buttonClicked().connect(std::bind([=] () { - if (messageBox->buttonResult() == Wt::Yes) - { - Wt::Dbo::Transaction transaction(DboSession()); - - // Delete the user - Wt::Auth::User authUser = DbHandler().getUserDatabase().findWithId( id ); - - DbHandler().getUserDatabase().deleteUser( authUser ); - - Database::User::pointer user = Database::User::getById(DboSession(), id); - if (user) - user.remove(); - - refresh(); - } - - delete messageBox; - })); - - messageBox->show(); - -} - -void -Users::handleCreateUser(std::string id) -{ - assert(_stack->count() == 1); - - UserFormView* userFormView = new UserFormView(id, _stack); - userFormView->completed().connect(this, &Users::handleUserFormCompleted); - - _stack->setCurrentIndex(1); -} - -void -Users::handleUserFormCompleted(bool changed) -{ - _stack->setCurrentIndex(0); - - // Refresh the user table if a change has been made - if (changed) - refresh(); - - // Delete the form view - delete _stack->widget(1); - -} - -} // namespace UserInterface -} // namespace Settings - - diff --git a/src/ui/settings/SettingsUsers.hpp b/src/ui/settings/SettingsUsers.hpp deleted file mode 100644 index 4f67f672..00000000 --- a/src/ui/settings/SettingsUsers.hpp +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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 . - */ - -#ifndef UI_SETTINGS_USERS_HPP -#define UI_SETTINGS_USERS_HPP - -#include -#include -#include - -namespace UserInterface { -namespace Settings { - -class Users : public Wt::WContainerWidget -{ - public: - Users(Wt::WContainerWidget *parent = 0); - - void refresh(); - - private: - - void handleUserFormCompleted(bool changed); - - void handleDelUser(Wt::WString loginNameIdentity, std::string id); - void handleCreateUser(std::string id); // set the id in order to edit the user - - Wt::WStackedWidget* _stack; - Wt::WTable* _table; -}; - -} // namespace UserInterface -} // namespace Settings - -#endif - diff --git a/src/ui/video/VideoDatabaseWidget.cpp b/src/ui/video/VideoDatabaseWidget.cpp deleted file mode 100644 index 5e25e8bc..00000000 --- a/src/ui/video/VideoDatabaseWidget.cpp +++ /dev/null @@ -1,143 +0,0 @@ -/* - * 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 . - */ - -#include - -#include -#include - -#include -#include - -#include "LmsApplication.hpp" - -#include "VideoDatabaseWidget.hpp" - -namespace UserInterface { - -VideoDatabaseWidget::VideoDatabaseWidget(Wt::WContainerWidget *parent) -: Wt::WContainerWidget(parent) -{ - _table = new Wt::WTable( this ); - _table->setHeaderCount(1); - - _table->addStyleClass("table form-inline"); - - _table->toggleStyleClass("table-hover", true); - _table->toggleStyleClass("table-striped", true); - - updateView( boost::filesystem::path(), 0); -} - -void -VideoDatabaseWidget::addHeader(void) -{ - _table->elementAt(0, 0)->addWidget(new Wt::WText("Name")); - _table->elementAt(0, 1)->addWidget(new Wt::WText("Duration")); - _table->elementAt(0, 2)->addWidget(new Wt::WText("Action")); -} - -void -VideoDatabaseWidget::addDirectory(const std::string& name, boost::filesystem::path path, size_t depth) -{ - - int row = _table->rowCount(); - - new Wt::WText(Wt::WString::fromUTF8( name ), _table->elementAt(row, 0)); - new Wt::WText(Wt::WString::fromUTF8( " " ), _table->elementAt(row, 1)); - - Wt::WPushButton* btn = new Wt::WPushButton(Wt::WString::fromUTF8( "Open"), _table->elementAt(row, 2)); - - btn->clicked().connect(std::bind([=] () { - updateView(path, depth); - })); - -} - - -void -VideoDatabaseWidget::addVideo(const std::string& name, const boost::posix_time::time_duration& duration, const boost::filesystem::path& path) -{ - - int row = _table->rowCount(); - - new Wt::WText(Wt::WString::fromUTF8( name ), _table->elementAt(row, 0)); - new Wt::WText(Wt::WString::fromUTF8( boost::posix_time::to_simple_string( duration )), _table->elementAt(row, 1)); - - Wt::WPushButton* btn = new Wt::WPushButton(Wt::WString::fromUTF8( "Play"), _table->elementAt(row, 2)); - - btn->clicked().connect(std::bind([=] () { - playVideo().emit(path); - })); -} - - -void -VideoDatabaseWidget::updateView(boost::filesystem::path directory, size_t depth) -{ - _table->clear(); - addHeader(); - - // If directory is not valid, add the root Media Directories - if (depth == 0) - { - Wt::Dbo::Transaction transaction ( DboSession() ); - - std::vector dirs - = Database::MediaDirectory::getByType(DboSession(), Database::MediaDirectory::Video); - - BOOST_FOREACH(Database::MediaDirectory::pointer dir, dirs) - { - addDirectory( dir->getPath().filename().string(), - dir->getPath(), - depth + 1); - } - } - else - { - addDirectory( "..", directory.parent_path(), depth - 1); - - // Iterators over files in the directory - // If directory, add a directory entry - // If video, add a video entry - std::vector paths; - std::copy(boost::filesystem::directory_iterator(directory), boost::filesystem::directory_iterator(), std::back_inserter(paths)); - - std::sort(paths.begin(), paths.end()); - - BOOST_FOREACH(const boost::filesystem::path& path, paths) - { - if (boost::filesystem::is_directory(path)) - addDirectory( path.filename().string(), path, depth + 1); - else if (boost::filesystem::is_regular(path) ) - { - Wt::Dbo::Transaction transaction ( DboSession() ); - - Database::Video::pointer video = Database::Video::getByPath( DboSession(), path); - if (video) - addVideo( video->getName(), video->getDuration(), path); - } - } - } - -} - - -} // namespace UserInterface - diff --git a/src/ui/video/VideoDatabaseWidget.hpp b/src/ui/video/VideoDatabaseWidget.hpp deleted file mode 100644 index 8ab399d4..00000000 --- a/src/ui/video/VideoDatabaseWidget.hpp +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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 . - */ - -#ifndef VIDEO_DB_WIDGET_HPP -#define VIDEO_DB_WIDGET_HPP - -#include -#include - -namespace UserInterface { - -class VideoDatabaseWidget : public Wt::WContainerWidget -{ - public: - VideoDatabaseWidget(Wt::WContainerWidget *parent = 0); - - // Signals - Wt::Signal< boost::filesystem::path >& playVideo() { return _playVideo; } - - private: - - void addHeader(void); - void addDirectory(const std::string& name, boost::filesystem::path path, size_t depth); - void addVideo(const std::string& name, const boost::posix_time::time_duration& duration, const boost::filesystem::path& path); - - void updateView(boost::filesystem::path directory, size_t depth); - - Wt::Signal< boost::filesystem::path > _playVideo; - - Wt::WTable* _table; -}; - -} // namespace UserInterface - -#endif - diff --git a/src/ui/video/VideoMediaPlayerWidget.cpp b/src/ui/video/VideoMediaPlayerWidget.cpp deleted file mode 100644 index 2062cd4d..00000000 --- a/src/ui/video/VideoMediaPlayerWidget.cpp +++ /dev/null @@ -1,200 +0,0 @@ -/* - * 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 . - */ - -#include //include all types plus i/o - -#include - -#include "VideoParametersDialog.hpp" - -#include "VideoMediaPlayerWidget.hpp" - -namespace UserInterface { - -Wt::WMediaPlayer::Encoding -AvEncoding_to_WtEncoding(Av::Encoding encoding) -{ - switch( encoding ) - { - case Av::Encoding::OGA: return Wt::WMediaPlayer::OGA; - case Av::Encoding::OGV: return Wt::WMediaPlayer::OGV; - case Av::Encoding::MP3: return Wt::WMediaPlayer::MP3; - case Av::Encoding::WEBMA: return Wt::WMediaPlayer::WEBMA; - case Av::Encoding::WEBMV: return Wt::WMediaPlayer::WEBMV; - case Av::Encoding::FLA: return Wt::WMediaPlayer::FLA; - case Av::Encoding::FLV: return Wt::WMediaPlayer::FLV; - case Av::Encoding::M4A: return Wt::WMediaPlayer::M4A; - case Av::Encoding::M4V: return Wt::WMediaPlayer::M4V; - } - assert(0); -} - - -VideoMediaPlayerWidget::VideoMediaPlayerWidget( const Av::MediaFile& mediaFile, Av::TranscodeParameters parameters, Wt::WContainerWidget *parent) - : Wt::WContainerWidget(parent), - _mediaResource(nullptr), - _currentParameters(parameters), - _dialog(nullptr) -{ - _mediaPlayer = new Wt::WMediaPlayer( Wt::WMediaPlayer::Video, this ); -// _mediaPlayer->setAlternativeContent (new Wt::WText("You don't have HTML5 audio support!")); -// _mediaPlayer->setOptions( Wt::WMediaPlayer::Autoplay ); -// _mediaPlayer->addSource( Wt::WMediaPlayer::WEBMV, "" ); - - { - Wt::WContainerWidget *container = new Wt::WContainerWidget(this); - - _playBtn = new Wt::WPushButton("Play", container ); - _pauseBtn = new Wt::WPushButton("Pause", 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, &VideoMediaPlayerWidget::handleTimeUpdated); - - Wt::WPushButton* fullScreenButton = new Wt::WPushButton("Fullscreen", container); - _mediaPlayer->setButton(Wt::WMediaPlayer::FullScreen, fullScreenButton); - - Wt::WPushButton* restoreScreenButton = new Wt::WPushButton("Restore screen", container); - _mediaPlayer->setButton(Wt::WMediaPlayer::RestoreScreen, restoreScreenButton); - - _mediaPlayer->timeUpdated().connect(this, &VideoMediaPlayerWidget::handleTimeUpdated); - - _timeSlider->valueChanged().connect(this, &VideoMediaPlayerWidget::handlePlayOffset); - _timeSlider->sliderMoved().connect(this, &VideoMediaPlayerWidget::handleSliderMoved); - - _volumeSlider->sliderMoved().connect(this, &VideoMediaPlayerWidget::handleVolumeSliderMoved); - } - - - Wt::WPushButton* closeButton = new Wt::WPushButton("Close", this); - closeButton->clicked().connect( this, &VideoMediaPlayerWidget::handleClose ); - - Wt::WPushButton* parametersButton = new Wt::WPushButton("Parameters", this); - parametersButton->clicked().connect( this, &VideoMediaPlayerWidget::handleParametersEdit ); - - _currentFile = mediaFile.getPath(); - - load(parameters); - - _timeSlider->setRange(0, mediaFile.getDuration().total_seconds() ); - _duration->setText( boost::posix_time::to_simple_string( mediaFile.getDuration() )); -} - -void -VideoMediaPlayerWidget::load(Av::TranscodeParameters parameters) -{ - _mediaPlayer->clearSources(); - - _currentParameters = parameters; - - _mediaInternalLink.setResource( nullptr ); - if (_mediaResource) - delete _mediaResource; - - _mediaResource = new AvConvTranscodeStreamResource( _currentFile, parameters, this ); - _mediaInternalLink.setResource( _mediaResource ); - - _mediaPlayer->addSource( AvEncoding_to_WtEncoding(parameters.getEncoding()), _mediaInternalLink ); - - _timeSlider->setValue( 0 ); - - - _mediaPlayer->play(); -} - -void -VideoMediaPlayerWidget::handlePlayOffset(int offsetSecs) -{ - _currentParameters.setOffset( boost::posix_time::seconds(offsetSecs) ); - load( _currentParameters ); - - _timeSlider->setValue( offsetSecs ); -} - -void -VideoMediaPlayerWidget::handleSliderMoved(int value) -{ - _curTime->setText( boost::posix_time::to_simple_string( boost::posix_time::seconds( value ) ) ); -} - -void -VideoMediaPlayerWidget::handleTimeUpdated(void) -{ - - 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 -VideoMediaPlayerWidget::handleVolumeSliderMoved(int value) -{ - _mediaPlayer->setVolume( value / 100. ); -} - -void -VideoMediaPlayerWidget::handleClose(void) -{ - _close.emit(); -} - -void -VideoMediaPlayerWidget::handleParametersEdit(void) -{ -/* - _dialog = new VideoParametersDialog("Parameters"); - _dialog->load(_currentParameters); - - _dialog->show(); - - _dialog->finished().connect(this, &VideoMediaPlayerWidget::handleParametersDone); - */ -} - -void -VideoMediaPlayerWidget::handleParametersDone(Wt::WDialog::DialogCode code) -{ - assert(_dialog != nullptr); - - if (code == Wt::WDialog::Accepted) - { - _dialog->save( _currentParameters ); - - // TODO SYNC current offset with player? - // HACK use slider current value - handlePlayOffset( _timeSlider->value()); - } - -} - -} // namespace UserInterface diff --git a/src/ui/video/VideoMediaPlayerWidget.hpp b/src/ui/video/VideoMediaPlayerWidget.hpp deleted file mode 100644 index ef292c52..00000000 --- a/src/ui/video/VideoMediaPlayerWidget.hpp +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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 . - */ - -#ifndef __VIDEO_MEDIA_PLAYER_WIDGET_HPP -#define __VIDEO_MEDIA_PLAYER_WIDGET_HPP - -#include -#include -#include -#include -#include - -#include "VideoParametersDialog.hpp" - -#include "av/AvTranscoder.hpp" -#include "resource/AvConvTranscodeStreamResource.hpp" - -namespace UserInterface { - -class VideoMediaPlayerWidget : public Wt::WContainerWidget -{ - public: - - VideoMediaPlayerWidget( const Av::MediaFile& mediaFile, Av::TranscodeParameters parameters, Wt::WContainerWidget *parent = 0); - - Wt::Signal& close() { return _close; }; - - private: - - // Signals - Wt::Signal _close; - - void load(Av::TranscodeParameters parameters); - - // Player controls - void handlePlayOffset(int offsetSecs); - void handleTimeUpdated(void); - void handleSliderMoved(int value); - void handleFullscreen(void); - void handleVolumeSliderMoved(int value); - - void handleClose(void); - - void handleParametersEdit(void); - void handleParametersDone(Wt::WDialog::DialogCode); - - boost::filesystem::path _currentFile; - - // Core - Wt::WMediaPlayer* _mediaPlayer; - AvConvTranscodeStreamResource* _mediaResource; - Wt::WLink _mediaInternalLink; - - // Controls - Av::TranscodeParameters _currentParameters; - Wt::WPushButton* _playBtn; - Wt::WPushButton* _pauseBtn; - Wt::WSlider* _timeSlider; - Wt::WSlider* _volumeSlider; - Wt::WText* _curTime; - Wt::WText* _duration; - - VideoParametersDialog* _dialog; -}; - -} // namespace UserInterface - -#endif - diff --git a/src/ui/video/VideoParametersDialog.cpp b/src/ui/video/VideoParametersDialog.cpp deleted file mode 100644 index 88b578c2..00000000 --- a/src/ui/video/VideoParametersDialog.cpp +++ /dev/null @@ -1,143 +0,0 @@ -/* - * 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 . - */ - -#include -#include -#include - -#include "VideoParametersDialog.hpp" - -namespace UserInterface { - -static const std::list streamTypes = {Av::Stream::Type::Video, Av::Stream::Type::Audio, Av::Stream::Type::Subtitle}; - -VideoParametersDialog::VideoParametersDialog(Wt::WString windowTitle, Wt::WDialog* parent) -: Wt::WDialog(windowTitle, parent) -{ - - Wt::WTable* layout = new Wt::WTable(contents()); - - createStreamWidgets("Video", Av::Stream::Type::Video, layout); - createStreamWidgets("Audio", Av::Stream::Type::Audio, layout); - createStreamWidgets("Subtitles", Av::Stream::Type::Subtitle, layout); - - Wt::WPushButton *ok = new Wt::WPushButton("Apply", contents()); - ok->clicked().connect(this, &Wt::WDialog::accept); - - Wt::WPushButton *cancel = new Wt::WPushButton("Cancel", contents()); - cancel->clicked().connect(this, &Wt::WDialog::reject); -} - -void -VideoParametersDialog::createStreamWidgets(const Wt::WString& labelString, Av::Stream::Type type, Wt::WTable* layout) -{ - int row = layout->rowCount(); - - Wt::WLabel* label = new Wt::WLabel(labelString); - Wt::WComboBox *combo = new Wt::WComboBox(); - label->setBuddy(combo); - - layout->elementAt(row, 0)->addWidget(label); - layout->elementAt(row, 1)->addWidget(combo); - - Wt::WStringListModel* model = new Wt::WStringListModel(combo); - - combo->setModel(model); - - _streamSelection.insert( std::make_pair(type, std::make_pair(combo, model) ) ); -} - - -void -VideoParametersDialog::handleApply() -{ - _apply.emit(); -} - -void -VideoParametersDialog::addStreams(Wt::WStringListModel* model, const std::vector& streams) -{ - for (const Av::Stream& stream : streams) - { - model->addString(stream.desc); - model->setData(model->rowCount(), 0, stream.id, Wt::UserRole); - } -} - -void -VideoParametersDialog::selectStream(const Wt::WStringListModel* model, int streamId, Wt::WComboBox* combo) -{ - for (int idStream = 0; idStream < model->rowCount(); ++idStream) - { - int id = boost::any_cast( model->data( model->index(idStream, 0), Wt::UserRole)); - if (id == streamId) - { - combo->setCurrentIndex(idStream); - break; - } - } -} - - -void -VideoParametersDialog::load(const Av::MediaFile& mediaFile, Av::TranscodeParameters currentParameters) -{ - // Populate the combox with the available streams - // And then show the selected one - for (Av::Stream::Type streamType : streamTypes) - { - Wt::WComboBox* combo = _streamSelection[streamType].first; - Wt::WStringListModel* model = _streamSelection[streamType].second; - - addStreams(model, mediaFile.getStreams(streamType) ); - - // Pre Select if necessary - std::set selectedStreams = currentParameters.getSelectedStreamIds(); - for (Av::Stream stream : mediaFile.getStreams(streamType)) - { - if (selectedStreams.find(stream.id) != selectedStreams.end()) - { - selectStream(model, stream.id, combo); - break; - } - } - } -} - -void -VideoParametersDialog::save(Av::TranscodeParameters& parameters) -{ - // Get stream selected, if any - for (Av::Stream::Type streamType : streamTypes) - { - Wt::WComboBox* combo = _streamSelection[streamType].first; - Wt::WStringListModel* model = _streamSelection[streamType].second; - - if (combo->currentIndex() >= 0) - { - int streamId = boost::any_cast( model->data(model->index(combo->currentIndex(), 0), Wt::UserRole)); - - parameters.addStream(streamId); - } - - } -} - -} // namespace UserInterface - diff --git a/src/ui/video/VideoParametersDialog.hpp b/src/ui/video/VideoParametersDialog.hpp deleted file mode 100644 index 01219c01..00000000 --- a/src/ui/video/VideoParametersDialog.hpp +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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 . - */ - -#pragma once - -#include - -#include -#include -#include -#include -#include - -#include "av/AvTranscoder.hpp" - -namespace UserInterface { - -class VideoParametersDialog : public Wt::WDialog -{ - public: - // parameters to be edited - VideoParametersDialog(Wt::WString windowTitle, Wt::WDialog* parent = 0); - - // Populates widget contents using these paramaters - void load(const Av::MediaFile& mediaFile, Av::TranscodeParameters currentParameters); - - // Save widgets contents into these parameters - void save(Av::TranscodeParameters& parameters); - - // Signal to be emitted if parameters are changed - Wt::Signal& apply() { return _apply; } - - private: - - void handleApply(void); - - // Stream handling - void createStreamWidgets(const Wt::WString& label, Av::Stream::Type type, Wt::WTable* layout); - void addStreams(Wt::WStringListModel* model, const std::vector& streams); - void selectStream(const Wt::WStringListModel* model, int streamId, Wt::WComboBox* combo); - - Wt::Signal _apply; - - std::map > _streamSelection; -}; - -} // namespace UserInterface - diff --git a/src/ui/video/VideoWidget.cpp b/src/ui/video/VideoWidget.cpp deleted file mode 100644 index bfa38d7f..00000000 --- a/src/ui/video/VideoWidget.cpp +++ /dev/null @@ -1,91 +0,0 @@ -/* - * 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 . - */ - -#include -#include - -#include "logger/Logger.hpp" - -#include "LmsApplication.hpp" - -#include "VideoWidget.hpp" - -namespace UserInterface { - -VideoWidget::VideoWidget(Wt::WContainerWidget* parent ) -: Wt::WContainerWidget(parent) -{ - - _videoDbWidget = new VideoDatabaseWidget(this); - - _videoDbWidget->playVideo().connect(this, &VideoWidget::playVideo); - - -} - - -void -VideoWidget::search(const std::string& searchText) -{ - //TODO -} - -void -VideoWidget::playVideo(boost::filesystem::path p) -{ - LMS_LOG(UI, DEBUG) << "Want to play video " << p << "'"; - - std::size_t audioBitrate = 0; - std::size_t videoBitrate = 0; - - // Get user preferences - { - Wt::Dbo::Transaction transaction(DboSession()); - - audioBitrate = CurrentUser()->getMaxAudioBitrate(); - videoBitrate = CurrentUser()->getMaxVideoBitrate(); - } - - LMS_LOG(UI, DEBUG) << "Max bitrate set to " << videoBitrate << "/" << audioBitrate; - - Av::MediaFile mediaFile(p); - - if (!mediaFile.open()) - return; - - if (!mediaFile.scan()) - return; - - Av::TranscodeParameters parameters; - - parameters.setEncoding( Av::Encoding::WEBMV ); - - VideoMediaPlayerWidget *mediaPlayer = new VideoMediaPlayerWidget(mediaFile, parameters, this); - - mediaPlayer->close().connect(std::bind([=] () - { - _videoDbWidget->setHidden(false); - delete mediaPlayer; - })); - - _videoDbWidget->setHidden(true); -} - -} // namespace UserInterface - diff --git a/src/ui/video/VideoWidget.hpp b/src/ui/video/VideoWidget.hpp deleted file mode 100644 index 68b2109a..00000000 --- a/src/ui/video/VideoWidget.hpp +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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 . - */ - -#ifndef VIDEO_WIDGET_HPP -#define VIDEO_WIDGET_HPP - -#include - -#include - -#include "video/VideoMediaPlayerWidget.hpp" -#include "video/VideoDatabaseWidget.hpp" - -namespace UserInterface { - -class VideoWidget : public Wt::WContainerWidget -{ - public: - - VideoWidget(Wt::WContainerWidget* parent = 0); - - void search(const std::string& searchText); - - private: - - void backToList(void); - void playVideo(boost::filesystem::path p); - - VideoDatabaseWidget* _videoDbWidget; - VideoMediaPlayerWidget* _mediaPlayer; - -}; - -} // namespace UserInterface - -#endif - From 0da8d83288ed9fcd2983016c71e6f6707ce196c9 Mon Sep 17 00:00:00 2001 From: emeric Date: Wed, 31 Jan 2018 13:06:59 +0100 Subject: [PATCH 031/195] New UI: filters now operational on all views --- README.md | 2 +- src/database/Release.cpp | 47 +++++++++++++++++++++++++++++++++------- src/database/Release.hpp | 2 +- src/database/Track.cpp | 2 +- src/ui/ArtistView.cpp | 8 +++---- src/ui/Explore.cpp | 6 ++--- src/ui/ReleaseView.cpp | 10 ++++----- src/ui/ReleaseView.hpp | 6 ++++- src/ui/ReleasesView.cpp | 15 +++++++------ src/ui/ReleasesView.hpp | 7 ++++-- src/ui/TracksView.cpp | 7 +++--- src/ui/TracksView.hpp | 7 ++++-- 12 files changed, 81 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 8f985bb4..923266e5 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ LMS is written entirely in C++. Therefore, it is suitable to run on embedded dev ### Debian ```sh -$ apt-get install g++ autoconf automake libboost-dev libboost-locale-dev libboost-iostreams-dev libavcodec-dev libwtdbosqlite-dev libwthttp-dev libwtdbo-dev libwt-dev libmagick++-dev libavcodec-dev libavformat-dev libav-tools libpstreams-dev libcurl-dev libcurlpp-dev libconfig++-dev ffmpeg +$ apt-get install g++ autoconf automake libboost-dev libboost-locale-dev libboost-iostreams-dev libavcodec-dev libavutil-dev libavformat-dev libav-tools libwtdbosqlite-dev libwthttp-dev libwtdbo-dev libwt-dev libmagick++-dev libpstreams-dev libcurlpp-dev libconfig++-dev libpstreams-dev ffmpeg libtag1-dev ``` ## Build diff --git a/src/database/Release.cpp b/src/database/Release.cpp index ca9e1fb5..a7f15dea 100644 --- a/src/database/Release.cpp +++ b/src/database/Release.cpp @@ -108,7 +108,7 @@ getQuery(Wt::Dbo::Session& session, WhereClause clusterClause; for (auto id : clusterIds) - clusterClause.And(WhereClause("c.id = ?")).bind(std::to_string(id)); + clusterClause.Or(WhereClause("c.id = ?")).bind(std::to_string(id)); where.And(clusterClause); } @@ -195,17 +195,48 @@ Release::hasVariousArtists() const } std::vector> -Release::getTracks() const +Release::getTracks(const std::vector& clusterIds) const { assert(self()); + assert(self()->id() != Wt::Dbo::dbo_traits::invalidId() ); assert(session()); - Wt::Dbo::collection> res = session()->query>( - "SELECT t FROM track t INNER JOIN release r ON t.release_id = r.id") - .where("r.id = ?") - .orderBy("t.disc_number,t.track_number") - .bind(id()); + WhereClause where; - return std::vector>(res.begin(), res.end()); + std::ostringstream oss; + oss << "SELECT t FROM track t INNER JOIN release r ON t.release_id = r.id"; + + if (!clusterIds.empty()) + { + oss << " INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id"; + + WhereClause clusterClause; + + for (auto id : clusterIds) + clusterClause.Or(WhereClause("c.id = ?")).bind(std::to_string(id)); + + where.And(clusterClause); + } + + where.And(WhereClause("r.id = ?")).bind(std::to_string(id())); + + oss << " " << where.get(); + + if (!clusterIds.empty()) + oss << " GROUP BY t.id HAVING COUNT(*) = " << clusterIds.size(); + + oss << " ORDER BY t.disc_number,t.track_number"; + + Wt::Dbo::Query query = session()->query( oss.str() ); + + for (const std::string& bindArg : where.getBindArgs()) + { + query.bind(bindArg); + } + + Wt::Dbo::collection< Wt::Dbo::ptr > res = query; + + return std::vector< Wt::Dbo::ptr > (res.begin(), res.end()); } + } // namespace Database diff --git a/src/database/Release.hpp b/src/database/Release.hpp index fc6fedcb..390d60a0 100644 --- a/src/database/Release.hpp +++ b/src/database/Release.hpp @@ -57,7 +57,7 @@ class Release : public Wt::Dbo::Dbo int size, bool& moreExpected); - std::vector> getTracks() const; + std::vector> getTracks(const std::vector& clusters = std::vector()) const; // Create static pointer create(Wt::Dbo::Session& session, const std::string& name, const std::string& MBID = ""); diff --git a/src/database/Track.cpp b/src/database/Track.cpp index ac7faa18..65e3319e 100644 --- a/src/database/Track.cpp +++ b/src/database/Track.cpp @@ -129,7 +129,7 @@ getQuery(Wt::Dbo::Session& session, WhereClause clusterClause; for (auto id : clusterIds) - clusterClause.And(WhereClause("c.id = ?")).bind(std::to_string(id)); + clusterClause.Or(WhereClause("c.id = ?")).bind(std::to_string(id)); where.And(clusterClause); } diff --git a/src/ui/ArtistView.cpp b/src/ui/ArtistView.cpp index 5327ced6..9c98dd93 100644 --- a/src/ui/ArtistView.cpp +++ b/src/ui/ArtistView.cpp @@ -84,8 +84,8 @@ Artist::refresh() entry->addFunction("tr", Wt::WTemplate::Functions::tr); { - Wt::WAnchor *coverAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/release/" + std::to_string(release.id()))); - Wt::WImage *cover = new Wt::WImage(coverAnchor); + Wt::WAnchor* coverAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/release/" + std::to_string(release.id()))); + Wt::WImage* cover = new Wt::WImage(coverAnchor); cover->setImageLink(SessionImageResource()->getReleaseUrl(release.id(), 128)); // Some images may not be square cover->setWidth(128); @@ -93,8 +93,8 @@ Artist::refresh() } { - Wt::WAnchor *releaseAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/release/" + std::to_string(release.id()))); - Wt::WText *releaseName = new Wt::WText(releaseAnchor); + Wt::WAnchor* releaseAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/release/" + std::to_string(release.id()))); + Wt::WText* releaseName = new Wt::WText(releaseAnchor); releaseName->setText(Wt::WString::fromUTF8(release->getName(), Wt::PlainText)); entry->bindWidget("name", releaseAnchor); } diff --git a/src/ui/Explore.cpp b/src/ui/Explore.cpp index 46cfc5ca..8686f0f2 100644 --- a/src/ui/Explore.cpp +++ b/src/ui/Explore.cpp @@ -83,9 +83,9 @@ Explore::Explore(Wt::WContainerWidget* parent) stack->addWidget(new Artists(filters)); stack->addWidget(new Artist(filters)); - stack->addWidget(new Releases(/*filters*/)); - stack->addWidget(new Release(/*filters*/)); - stack->addWidget(new Tracks(/*filters*/)); + stack->addWidget(new Releases(filters)); + stack->addWidget(new Release(filters)); + stack->addWidget(new Tracks(filters)); wApp->internalPathChanged().connect(std::bind([=] { diff --git a/src/ui/ReleaseView.cpp b/src/ui/ReleaseView.cpp index 098b12a5..69b5078f 100644 --- a/src/ui/ReleaseView.cpp +++ b/src/ui/ReleaseView.cpp @@ -33,8 +33,9 @@ namespace UserInterface { -Release::Release(Wt::WContainerWidget* parent) -: Wt::WContainerWidget(parent) +Release::Release(Filters* filters, Wt::WContainerWidget* parent) +: Wt::WContainerWidget(parent), + _filters(filters) { wApp->internalPathChanged().connect(std::bind([=] { @@ -109,10 +110,9 @@ Release::refresh() auto tracksContainer = new Wt::WContainerWidget(); t->bindWidget("tracks", tracksContainer); - std::vector clusterIds; // TODO fill + auto clusterIds = _filters->getClusterIds(); + auto tracks = release->getTracks(clusterIds); -// auto tracks = release->getTracks(clusterIds); - auto tracks = release->getTracks(); bool variousArtists = release->hasVariousArtists(); for (auto track : tracks) diff --git a/src/ui/ReleaseView.hpp b/src/ui/ReleaseView.hpp index 0a68a9e9..b51568cd 100644 --- a/src/ui/ReleaseView.hpp +++ b/src/ui/ReleaseView.hpp @@ -21,15 +21,19 @@ #include +#include "Filters.hpp" + namespace UserInterface { class Release : public Wt::WContainerWidget { public: - Release(Wt::WContainerWidget* parent = 0); + Release(Filters* filters, Wt::WContainerWidget* parent = 0); private: void refresh(); + + Filters* _filters; }; } // namespace UserInterface diff --git a/src/ui/ReleasesView.cpp b/src/ui/ReleasesView.cpp index 7c7da4de..414c1418 100644 --- a/src/ui/ReleasesView.cpp +++ b/src/ui/ReleasesView.cpp @@ -36,8 +36,9 @@ namespace UserInterface { using namespace Database; -Releases::Releases(Wt::WContainerWidget* parent) -: Wt::WContainerWidget(parent) +Releases::Releases(Filters* filters, Wt::WContainerWidget* parent) +: Wt::WContainerWidget(parent), + _filters(filters) { auto releases = new Wt::WTemplate(Wt::WString::tr("template-releases"), this); releases->addFunction("tr", &Wt::WTemplate::Functions::tr); @@ -63,7 +64,7 @@ Releases::refresh(std::vector searchKeywords) { _releasesContainer->clear(); - std::vector clusterIds; // TODO fill + auto clusterIds = _filters->getClusterIds(); Wt::Dbo::Transaction transaction(DboSession()); @@ -75,8 +76,8 @@ Releases::refresh(std::vector searchKeywords) Wt::WTemplate* entry = new Wt::WTemplate(Wt::WString::tr("template-releases-entry"), _releasesContainer); entry->addFunction("tr", Wt::WTemplate::Functions::tr); - Wt::WAnchor *coverAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/release/" + std::to_string(release.id()))); - Wt::WImage *cover = new Wt::WImage(coverAnchor); + Wt::WAnchor* coverAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/release/" + std::to_string(release.id()))); + Wt::WImage* cover = new Wt::WImage(coverAnchor); cover->setImageLink(SessionImageResource()->getReleaseUrl(release.id(), 128)); // Some images may not be square cover->setWidth(128); @@ -91,8 +92,8 @@ Releases::refresh(std::vector searchKeywords) } else { - Wt::WAnchor *artistAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/artist/" + std::to_string(artists.front().id()))); - Wt::WText *artist = new Wt::WText(artistAnchor); + Wt::WAnchor* artistAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/artist/" + std::to_string(artists.front().id()))); + Wt::WText* artist = new Wt::WText(artistAnchor); artist->setText(Wt::WString::fromUTF8(artists.front()->getName(), Wt::PlainText)); entry->bindWidget("artist-name", artistAnchor); } diff --git a/src/ui/ReleasesView.hpp b/src/ui/ReleasesView.hpp index 7a4325cd..bf741678 100644 --- a/src/ui/ReleasesView.hpp +++ b/src/ui/ReleasesView.hpp @@ -21,17 +21,20 @@ #include +#include "Filters.hpp" + namespace UserInterface { class Releases : public Wt::WContainerWidget { public: - Releases(Wt::WContainerWidget* parent = 0); + Releases(Filters* filters, Wt::WContainerWidget* parent = 0); private: void refresh(std::vector searchKeywords = std::vector()); - Wt::WContainerWidget *_releasesContainer; + Filters* _filters; + Wt::WContainerWidget* _releasesContainer; }; } // namespace UserInterface diff --git a/src/ui/TracksView.cpp b/src/ui/TracksView.cpp index bc834de5..c904c6ae 100644 --- a/src/ui/TracksView.cpp +++ b/src/ui/TracksView.cpp @@ -35,8 +35,9 @@ namespace UserInterface { using namespace Database; -Tracks::Tracks(Wt::WContainerWidget* parent) -: Wt::WContainerWidget(parent) +Tracks::Tracks(Filters* filters, Wt::WContainerWidget* parent) +: Wt::WContainerWidget(parent), + _filters(filters) { auto tracks = new Wt::WTemplate(Wt::WString::tr("template-tracks"), this); tracks->addFunction("tr", &Wt::WTemplate::Functions::tr); @@ -62,7 +63,7 @@ Tracks::refresh(std::vector searchKeywords) { _tracksContainer->clear(); - std::vector clusterIds; // TODO fill + auto clusterIds = _filters->getClusterIds(); Wt::Dbo::Transaction transaction(DboSession()); diff --git a/src/ui/TracksView.hpp b/src/ui/TracksView.hpp index 7ba2c61c..3210e666 100644 --- a/src/ui/TracksView.hpp +++ b/src/ui/TracksView.hpp @@ -21,17 +21,20 @@ #include +#include "Filters.hpp" + namespace UserInterface { class Tracks : public Wt::WContainerWidget { public: - Tracks(Wt::WContainerWidget* parent = 0); + Tracks(Filters* filters, Wt::WContainerWidget* parent = 0); private: void refresh(std::vector searchKeywords = std::vector()); - Wt::WContainerWidget *_tracksContainer; + Wt::WContainerWidget* _tracksContainer; + Filters* _filters; }; } // namespace UserInterface From 1e470a34b1f69151ffb091f919751703ca190a88 Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 3 Feb 2018 15:50:03 +0100 Subject: [PATCH 032/195] Removed unused html templates --- approot/templates.xml | 552 ------------------------------------------ 1 file changed, 552 deletions(-) diff --git a/approot/templates.xml b/approot/templates.xml index 9894b02e..1780e53d 100644 --- a/approot/templates.xml +++ b/approot/templates.xml @@ -22,556 +22,4 @@
- - ${title} -
-
- -
- ${name} -
-
- ${name-info} -
-
-
- -
- ${email} -
-
- ${email-info} -
-
-
- -
- ${password} -
- ${password-info} -
-
- -
- ${password-confirm} -
-
- ${password-confirm-info} -
-
-
-
- ${save-button} -
-
- ${apply-info} -
-
- - - - ${title} -
-
- -
- ${name} -
-
- ${name-info} -
-
-
- -
- ${email} -
-
- ${email-info} -
-
-
- -
- ${password} -
-
- ${password-info} -
-
-
- -
- ${password-confirm} -
-
- ${password-confirm-info} -
-
-
- ${access} -
-
- -
- ${admin} -
-
- ${admin-info} -
-
-
- -
-
- ${audio-bitrate-limit} - kbps -
-
-
- ${audio-bitrate-limit-info} -
-
-
- -
-
- ${video-bitrate-limit} - kbps -
-
-
- ${video-bitrate-limit-info} -
-
-
-
-
-
- ${save-button} ${cancel-button} -
-
-
- -
- - - ${title} -
-
- -
- ${name} -
-
- ${name-info} -
-
-
- -
- ${email} -
-
- ${email-info} -
-
-
- -
- ${password} -
-
- ${password-info} -
-
-
- -
- ${password-confirm} -
-
- ${password-confirm-info} -
-
-
-
- ${save-button} ${cancel-button} -
-
- ${apply-info} -
-
- - - ${title} -
-
- -
-
- ${bitrate} - kbps -
-
-
- ${bitrate-info} -
-
-
- -
- ${encoding} -
-
- ${encoding-info} -
-
-
-
- ${save-button} ${cancel-button} -
-
- ${apply-info} -
-
- - - ${title} -
-
- -
- ${path} -
-
- ${path-info} -
-
-
- -
- ${type} -
-
- ${type-info} -
-
-
-
- ${save-button} ${cancel-button} -
-
- ${apply-info} -
-
- - - ${scan-title} -
-
- -
- ${update-period} -
-
- ${update-period-info} -
-
- -
- -
- ${update-start-time} -
-
- ${update-start-time-info} -
-
- -
- -
- ${audio-file-extensions} -
-
- ${audio-file-extensions-info} -
-
- -
- -
- ${video-file-extensions} -
-
- ${video-file-extensions-info} -
-
-
- ${tags-title} -
-
- -
- ${tags-highlevel-acousticbrainz} -
-
- ${tags-highlevel-acousticbrainz-info} -
-
- -
- -
- ${tags-highlevel-acousticbrainz-min-probability} -
-
- ${tags-highlevel-acousticbrainz-min-probability-info} -
-
- - -
- -
- ${tags-similarity-acousticbrainz} -
-
- ${tags-similarity-acousticbrainz-info} -
-
- -
-
- ${apply-button} ${discard-button} ${immediate-scan-button} -
-
- ${apply-info} -
-
- - -
-
-
${text}
-
-
-
- -
-
-
${text}
-
-
-
- - - - - - - - - ${title} - ${contents} - ${show-more} - - - -
-
${gif} -

${name}

-
-
- - - - ${title} - ${contents} - ${show-more} - - - -
-
${cover} -

${name}

${artist}

-
-
- - - - ${title} - - ${show-more} - - - - - - - - - - ${title} - ${release-container} - ${show-more} - - - -
-
-
-
- ${cover} -
-

${release-name}
${artist-name}${} - ${year}${}${} (${orig-year})${}

-
- -
-
- - - - - - - - ${controls} - - - - - ${load}${save}${clear}${shuffle}${repeat} - - - -
-
-
${cover}
-
${artist-name}
${track-name}
-
${play-btn}${del-btn}
-
-
-
- - - ${audio} - ${prev}${play-pause}${next} - - -
-
- ${track} ${artist} -
-
- ${curtime}${seekbar}${duration} -
-
- ${}${shuffle}${}${}${repeat}${}${}${playqueue}${} -
-
From cea01d06b72e22e7b8fdea8e4aec4e86da790724 Mon Sep 17 00:00:00 2001 From: emeric Date: Mon, 5 Feb 2018 13:39:45 +0100 Subject: [PATCH 033/195] Make filters really work --- approot/messages.xml | 5 ++ approot/templates.xml | 26 +++++++++ src/database/DbArtist.cpp | 6 +-- src/database/DbArtist.hpp | 4 +- src/database/Release.cpp | 6 +-- src/database/Release.hpp | 4 +- src/database/Track.cpp | 13 ++++- src/database/Track.hpp | 3 +- src/ui/ArtistView.cpp | 4 ++ src/ui/ArtistsView.cpp | 5 +- src/ui/Filters.cpp | 111 +++++++++++++++++++++++++++++++++++++- src/ui/Filters.hpp | 10 +++- src/ui/ReleasesView.cpp | 4 ++ src/ui/TracksView.cpp | 4 ++ 14 files changed, 187 insertions(+), 18 deletions(-) diff --git a/approot/messages.xml b/approot/messages.xml index 196a7296..44479447 100644 --- a/approot/messages.xml +++ b/approot/messages.xml @@ -10,4 +10,9 @@ Add filter Search... +Add +Cancel + +Type +Value diff --git a/approot/templates.xml b/approot/templates.xml index 1780e53d..5f93b41d 100644 --- a/approot/templates.xml +++ b/approot/templates.xml @@ -22,4 +22,30 @@
+ +
+
+ +
+ ${type} +
+
+
+ +
+ ${value} +
+
+
+
+ ${add class="btn-primary"} ${cancel} +
+
+
+
+ diff --git a/src/database/DbArtist.cpp b/src/database/DbArtist.cpp index 7dc341f6..413e40c4 100644 --- a/src/database/DbArtist.cpp +++ b/src/database/DbArtist.cpp @@ -91,7 +91,7 @@ Artist::getAllOrphans(Wt::Dbo::Session& session) static Wt::Dbo::Query getQuery(Wt::Dbo::Session& session, - const std::vector& clusterIds, + const std::set& clusterIds, const std::vector& keywords) { WhereClause where; @@ -132,7 +132,7 @@ getQuery(Wt::Dbo::Session& session, std::vector Artist::getByFilter(Wt::Dbo::Session& session, - const std::vector& clusters, + const std::set& clusters, const std::vector keywords, int offset, int size, bool& moreResults) { @@ -152,7 +152,7 @@ Artist::getByFilter(Wt::Dbo::Session& session, } std::vector > -Artist::getReleases(const std::vector& clusterIds) const +Artist::getReleases(const std::set& clusterIds) const { assert(self()); assert(self()->id() != Wt::Dbo::dbo_traits::invalidId() ); diff --git a/src/database/DbArtist.hpp b/src/database/DbArtist.hpp index 7b45bcb6..47c7d24f 100644 --- a/src/database/DbArtist.hpp +++ b/src/database/DbArtist.hpp @@ -51,7 +51,7 @@ class Artist : public Wt::Dbo::Dbo static pointer getNone(Wt::Dbo::Session& session); // Special entry static std::vector getByName(Wt::Dbo::Session& session, const std::string& name); static std::vector getByFilter(Wt::Dbo::Session& session, - const std::vector& clusters, // at least one track that belongs to these clusters + const std::set& clusters, // at least one track that belongs to these clusters const std::vector keywords, // name must match all of these keywords int offset, int size, @@ -65,7 +65,7 @@ class Artist : public Wt::Dbo::Dbo std::string getMBID(void) const { return _MBID; } // Get the releases that have at least one track for this artist + belongs to optonal cluster filters - std::vector> getReleases(const std::vector& clusterIds = std::vector()) const; + std::vector> getReleases(const std::set& clusterIds = std::set()) const; void setMBID(std::string mbid) { _MBID = mbid; } diff --git a/src/database/Release.cpp b/src/database/Release.cpp index a7f15dea..2b338e34 100644 --- a/src/database/Release.cpp +++ b/src/database/Release.cpp @@ -90,7 +90,7 @@ Release::getAllOrphans(Wt::Dbo::Session& session) static Wt::Dbo::Query getQuery(Wt::Dbo::Session& session, - const std::vector& clusterIds, + const std::set& clusterIds, const std::vector keywords) { WhereClause where; @@ -130,7 +130,7 @@ getQuery(Wt::Dbo::Session& session, std::vector Release::getByFilter(Wt::Dbo::Session& session, - const std::vector& clusterIds, + const std::set& clusterIds, const std::vector keywords, int offset, int size, bool& moreResults) { @@ -195,7 +195,7 @@ Release::hasVariousArtists() const } std::vector> -Release::getTracks(const std::vector& clusterIds) const +Release::getTracks(const std::set& clusterIds) const { assert(self()); assert(self()->id() != Wt::Dbo::dbo_traits::invalidId() ); diff --git a/src/database/Release.hpp b/src/database/Release.hpp index 390d60a0..3feb0100 100644 --- a/src/database/Release.hpp +++ b/src/database/Release.hpp @@ -51,13 +51,13 @@ class Release : public Wt::Dbo::Dbo static std::vector getAll(Wt::Dbo::Session& session, int offset, int size); static std::vector getByFilter(Wt::Dbo::Session& session, - const std::vector& clusters, // at least one track that belongs to these clusters + const std::set& clusters, // at least one track that belongs to these clusters const std::vector keywords, // name must match all of these keywords int offset, int size, bool& moreExpected); - std::vector> getTracks(const std::vector& clusters = std::vector()) const; + std::vector> getTracks(const std::set& clusters = std::set()) const; // Create static pointer create(Wt::Dbo::Session& session, const std::string& name, const std::string& MBID = ""); diff --git a/src/database/Track.cpp b/src/database/Track.cpp index 65e3319e..50c16a4d 100644 --- a/src/database/Track.cpp +++ b/src/database/Track.cpp @@ -111,7 +111,7 @@ Track::getClusters(void) const static Wt::Dbo::Query< Track::pointer > getQuery(Wt::Dbo::Session& session, - const std::vector& clusterIds, + const std::set& clusterIds, const std::vector keywords) { WhereClause where; @@ -165,7 +165,7 @@ Track::getStats(Wt::Dbo::Session& session, SearchFilter filter) std::vector Track::getByFilter(Wt::Dbo::Session& session, - const std::vector& clusterIds, + const std::set& clusterIds, const std::vector keywords, int offset, int size, bool& moreResults) { @@ -282,6 +282,15 @@ Cluster::getQuery(Wt::Dbo::Session& session, SearchFilter filter) return query; } +std::vector +Cluster::getAllTypes(Wt::Dbo::Session& session) +{ + Wt::Dbo::collection res + = session.query("SELECT type from cluster").groupBy("type").orderBy("type"); + + return std::vector(res.begin(), res.end()); +} + std::vector Cluster::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size) { diff --git a/src/database/Track.hpp b/src/database/Track.hpp index d1af053c..0d81a36d 100644 --- a/src/database/Track.hpp +++ b/src/database/Track.hpp @@ -62,6 +62,7 @@ class Cluster static pointer getNone(Wt::Dbo::Session& session); static std::vector getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1); static Wt::Dbo::collection getAll(Wt::Dbo::Session& session); + static std::vector getAllTypes(Wt::Dbo::Session& session); static std::vector getByType(Wt::Dbo::Session& session, std::string type); // Create utility @@ -120,7 +121,7 @@ class Track static pointer getById(Wt::Dbo::Session& session, id_type id); static pointer getByMBID(Wt::Dbo::Session& session, const std::string& MBID); static std::vector getByFilter(Wt::Dbo::Session& session, - const std::vector& clusters, // tracks that belong to these clusters + const std::set& clusters, // tracks that belong to these clusters const std::vector keywords, // name must match all of these keywords int offset, int size, diff --git a/src/ui/ArtistView.cpp b/src/ui/ArtistView.cpp index 9c98dd93..0e17096b 100644 --- a/src/ui/ArtistView.cpp +++ b/src/ui/ArtistView.cpp @@ -43,6 +43,10 @@ Artist::Artist(Filters* filters, Wt::WContainerWidget* parent) })); refresh(); + + filters->updated().connect(std::bind([=] { + refresh(); + })); } void diff --git a/src/ui/ArtistsView.cpp b/src/ui/ArtistsView.cpp index e684db2d..be50e252 100644 --- a/src/ui/ArtistsView.cpp +++ b/src/ui/ArtistsView.cpp @@ -54,8 +54,11 @@ Artists::Artists(Filters* filters, Wt::WContainerWidget* parent) artists->bindWidget("artists", _artistsContainer); refresh(); -} + filters->updated().connect(std::bind([=] { + refresh(); + })); +} void Artists::refresh(std::vector searchKeywords) diff --git a/src/ui/Filters.cpp b/src/ui/Filters.cpp index 8dc3f131..872ba5ce 100644 --- a/src/ui/Filters.cpp +++ b/src/ui/Filters.cpp @@ -17,13 +17,116 @@ * along with LMS. If not, see . */ -#include +#include +#include #include +#include #include "Filters.hpp" +#include "LmsApplication.hpp" + namespace UserInterface { +void +Filters::showDialog() +{ + auto dialog = new Wt::WDialog("Go to cell"); + + auto container = new Wt::WTemplate(Wt::WString::tr("template-filter-add")); + container->addFunction("tr", &Wt::WTemplate::Functions::tr); + + dialog->contents()->addWidget(container); + + auto typeCombo = new Wt::WComboBox(); + container->bindWidget("type", typeCombo); + + auto valueCombo = new Wt::WComboBox(); + container->bindWidget("value", valueCombo); + + auto addBtn = new Wt::WPushButton(Wt::WString::tr("msg-add")); + container->bindWidget("add", addBtn); + addBtn->clicked().connect(dialog, &Wt::WDialog::accept); + + auto cancelBtn = new Wt::WPushButton(Wt::WString::tr("msg-cancel")); + container->bindWidget("cancel", cancelBtn); + cancelBtn->clicked().connect(dialog, &Wt::WDialog::reject); + + // Populate data + { + Wt::Dbo::Transaction transaction(DboSession()); + + auto types = Database::Cluster::getAllTypes(DboSession()); + + for (auto type : types) + typeCombo->addItem(Wt::WString::fromUTF8(type)); + + if (!types.empty()) + { + auto values = Database::Cluster::getByType(DboSession(), types.front()); + + for (auto value : values) + { + if (_filterIds.find(value.id()) == _filterIds.end()) + valueCombo->addItem(Wt::WString::fromUTF8(value->getName())); + } + } + + } + + typeCombo->changed().connect(std::bind([=] + { + auto type = typeCombo->valueText().toUTF8(); + + valueCombo->clear(); + + Wt::Dbo::Transaction transaction(DboSession()); + + auto values = Database::Cluster::getByType(DboSession(), type); + for (auto value : values) + { + if (_filterIds.find(value.id()) == _filterIds.end()) + valueCombo->addItem(Wt::WString::fromUTF8(value->getName())); + } + })); + + dialog->setModal(true); + dialog->setMovable(false); + dialog->setResizable(false); + dialog->setClosable(false); + + dialog->finished().connect(std::bind([=] + { + if (dialog->result() != Wt::WDialog::Accepted) + return; + + auto type = typeCombo->valueText().toUTF8(); + auto value = valueCombo->valueText().toUTF8(); + + Wt::Dbo::Transaction transaction(DboSession()); + + auto cluster = Database::Cluster::get(DboSession(), type, value); + if (!cluster) + return; + + auto clusterId = cluster.id(); + _filterIds.insert(clusterId); + _sigUpdated.emit(); + + auto filterBtn = new Wt::WPushButton(Wt::WString::fromUTF8(value)); + _filters->addWidget(filterBtn); + + filterBtn->clicked().connect(std::bind([=] + { + _filters->removeWidget(filterBtn); + _filterIds.erase(clusterId); + _sigUpdated.emit(); + })); + })); + + dialog->show(); +} + Filters::Filters(Wt::WContainerWidget *parent) : Wt::WContainerWidget(parent) { @@ -36,7 +139,11 @@ Filters::Filters(Wt::WContainerWidget *parent) _filters = new Wt::WContainerWidget(); container->bindWidget("filters", _filters); -// _filterIds = {108, 419}; + addFilterBtn->clicked().connect(std::bind([this] + { + showDialog(); + })); + } diff --git a/src/ui/Filters.hpp b/src/ui/Filters.hpp index 98d2e4c0..d0753915 100644 --- a/src/ui/Filters.hpp +++ b/src/ui/Filters.hpp @@ -20,6 +20,7 @@ #pragma once #include +#include #include "database/Types.hpp" @@ -32,12 +33,17 @@ class Filters : public Wt::WContainerWidget public: Filters(Wt::WContainerWidget *parent = 0); - std::vector getClusterIds() const { return _filterIds; } + std::set getClusterIds() const { return _filterIds; } + + Wt::Signal& updated() { return _sigUpdated; } private: + void showDialog(); + Wt::WContainerWidget *_filters; - std::vector _filterIds; + Wt::Signal _sigUpdated; + std::set _filterIds; }; } // namespace UserInterface diff --git a/src/ui/ReleasesView.cpp b/src/ui/ReleasesView.cpp index 414c1418..270abe89 100644 --- a/src/ui/ReleasesView.cpp +++ b/src/ui/ReleasesView.cpp @@ -56,6 +56,10 @@ Releases::Releases(Filters* filters, Wt::WContainerWidget* parent) releases->bindWidget("releases", _releasesContainer); refresh(); + + filters->updated().connect(std::bind([=] { + refresh(); + })); } diff --git a/src/ui/TracksView.cpp b/src/ui/TracksView.cpp index c904c6ae..7ae1b3ae 100644 --- a/src/ui/TracksView.cpp +++ b/src/ui/TracksView.cpp @@ -55,6 +55,10 @@ Tracks::Tracks(Filters* filters, Wt::WContainerWidget* parent) tracks->bindWidget("tracks", _tracksContainer); refresh(); + + filters->updated().connect(std::bind([=] { + refresh(); + })); } From f24e63444732906f665821592042b6819f538a12 Mon Sep 17 00:00:00 2001 From: emeric Date: Tue, 6 Feb 2018 20:14:27 +0100 Subject: [PATCH 034/195] Fix build with Wt < 3.3.7 --- src/ui/Filters.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ui/Filters.cpp b/src/ui/Filters.cpp index 872ba5ce..4040f723 100644 --- a/src/ui/Filters.cpp +++ b/src/ui/Filters.cpp @@ -91,7 +91,10 @@ Filters::showDialog() })); dialog->setModal(true); +#if WT_VERSION >= 0x030307 dialog->setMovable(false); +#endif + dialog->setResizable(false); dialog->setClosable(false); From 5cc2a29b59a458664d682fc56853db6d139839ca Mon Sep 17 00:00:00 2001 From: emeric Date: Tue, 6 Feb 2018 20:29:19 +0100 Subject: [PATCH 035/195] Really fix build with Wt < 3.3.7 --- src/ui/Filters.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/Filters.cpp b/src/ui/Filters.cpp index 4040f723..9ecdc704 100644 --- a/src/ui/Filters.cpp +++ b/src/ui/Filters.cpp @@ -91,7 +91,7 @@ Filters::showDialog() })); dialog->setModal(true); -#if WT_VERSION >= 0x030307 +#if WT_VERSION >= 0x03030700 dialog->setMovable(false); #endif From 18a446a0a2be9547b97389f616979c2ec5811e66 Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 10 Feb 2018 16:30:19 +0100 Subject: [PATCH 036/195] Remove special None handling and video --- configure.ac | 8 -- src/Makefile.am | 9 -- src/cover/CoverArtGrabber.cpp | 4 +- src/database/Artist.cpp | 168 ----------------------- src/database/Artist.hpp | 102 -------------- src/database/DatabaseHandler.cpp | 1 - src/database/DbArtist.cpp | 16 --- src/database/DbArtist.hpp | 2 - src/database/MediaDirectory.hpp | 1 - src/database/Release.cpp | 16 --- src/database/Release.hpp | 2 - src/database/Track.cpp | 15 -- src/database/Track.hpp | 2 - src/database/Types.hpp | 1 - src/database/Video.cpp | 59 -------- src/database/Video.hpp | 84 ------------ src/database/updater/DatabaseUpdater.cpp | 121 +--------------- src/ui/ArtistView.cpp | 3 - src/ui/ReleaseView.cpp | 2 +- src/ui/TracksView.cpp | 4 +- 20 files changed, 11 insertions(+), 609 deletions(-) delete mode 100644 src/database/Artist.cpp delete mode 100644 src/database/Artist.hpp delete mode 100644 src/database/Video.cpp delete mode 100644 src/database/Video.hpp diff --git a/configure.ac b/configure.ac index fe932bb4..41173572 100644 --- a/configure.ac +++ b/configure.ac @@ -8,14 +8,6 @@ AC_LANG_CPLUSPLUS # Checks for programs AC_PROG_CXX -AC_ARG_ENABLE([video], AS_HELP_STRING([--enable-video], - [Enable Video support. Default to no.])], - [enable_video="$enableval"], - [enable_video="no"]) - -AM_CONDITIONAL([VIDEO], [test x$enable_video = xyes]) -AS_IF([test x$enable_video = xyes], [AC_DEFINE(HAVE_VIDEO, [1], [Enable Video support])]) - PKG_CHECK_MODULES(IMAGEMAGICKXX, "ImageMagick++", [ HAVE_IMAGEMAGICKXX=yes ], [ ]) if test -n "$HAVE_IMAGEMAGICKXX"; then MAGICKXX_CFLAGS="$IMAGEMAGICKXX_CFLAGS" diff --git a/src/Makefile.am b/src/Makefile.am index 02bd8c3b..5bcbc8a6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -15,7 +15,6 @@ lms_SOURCES = \ $(srcdir)/database/SqlQuery.cpp \ $(srcdir)/database/Track.cpp \ $(srcdir)/database/User.cpp \ - $(srcdir)/database/Video.cpp \ $(srcdir)/database/updater/DatabaseUpdater.cpp \ $(srcdir)/database/updater/DatabaseFeatureExtractor.cpp \ $(srcdir)/database/updater/DatabaseHighLevelCluster.cpp \ @@ -45,14 +44,6 @@ lms_SOURCES = \ $(srcdir)/utils/Path.cpp \ $(srcdir)/utils/Utils.cpp -if VIDEO -lms_SOURCES += \ - $(srcdir)/ui/video/VideoWidget.cpp \ - $(srcdir)/ui/video/VideoDatabaseWidget.cpp \ - $(srcdir)/ui/video/VideoMediaPlayerWidget.cpp \ - $(srcdir)/ui/video/VideoParametersDialog.cpp -endif - lms_CXXFLAGS=-std=c++11 -Wall -I$(srcdir)/third-party -I$(srcdir)/ui $(MAGICKXX_CFLAGS) -D_REENTRANT -DBOOST_SPIRIT_THREADSAFE lms_LDADD=$(MAGICKXX_LIBS) diff --git a/src/cover/CoverArtGrabber.cpp b/src/cover/CoverArtGrabber.cpp index a73a502c..56967cda 100644 --- a/src/cover/CoverArtGrabber.cpp +++ b/src/cover/CoverArtGrabber.cpp @@ -173,9 +173,9 @@ Grabber::getFromRelease(Wt::Dbo::Session& session, Database::Release::id_type re Wt::Dbo::Transaction transaction(session); - // If the release does not exist or is the special release "None", do nothing + // If the release does not exist, do nothing Release::pointer release = Release::getById(session, releaseId); - if (!release || release->isNone()) + if (!release) return std::vector(); std::vector tracks = release->getTracks(); diff --git a/src/database/Artist.cpp b/src/database/Artist.cpp deleted file mode 100644 index 67f4f340..00000000 --- a/src/database/Artist.cpp +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright (C) 2015 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 . - */ - -#include "Types.hpp" -#include "SqlQuery.hpp" - -#include "utils/Logger.hpp" - -namespace Database -{ - -Artist::Artist(const std::string& name, const std::string& MBID) -: _name(std::string(name, 0 , _maxNameLength)), -_MBID(MBID) -{ - -} - -std::vector -Artist::getByName(Wt::Dbo::Session& session, const std::string& name) -{ - Wt::Dbo::collection res = session.find().where("name = ?").bind( std::string(name, 0, _maxNameLength) ); - return std::vector(res.begin(), res.end()); -} - -Artist::pointer -Artist::getByMBID(Wt::Dbo::Session& session, const std::string& mbid) -{ - return session.find().where("mbid = ?").bind(mbid); -} - -Artist::pointer -Artist::getById(Wt::Dbo::Session& session, Artist::id_type id) -{ - return session.find().where("id = ?").bind(id); -} - -Artist::pointer -Artist::create(Wt::Dbo::Session& session, const std::string& name, const std::string& MBID) -{ - return session.add(new Artist(name, MBID)); -} - -Artist::pointer -Artist::getNone(Wt::Dbo::Session& session) -{ - std::vector res = getByName(session, ""); - if (res.empty()) - return create(session, ""); - - return res.front(); -} - -std::vector -Artist::getAll(Wt::Dbo::Session& session, int offset, int size) -{ - Wt::Dbo::collection res = session.find().offset(offset).limit(size); - return std::vector(res.begin(), res.end()); -} - -std::vector -Artist::getAllOrphans(Wt::Dbo::Session& session) -{ - Wt::Dbo::collection res = session.query< Wt::Dbo::ptr >("select a from artist a LEFT OUTER JOIN Track t ON a.id = t.artist_id WHERE t.id IS NULL"); - - return std::vector(res.begin(), res.end()); -} - -std::vector -Artist::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size) -{ - Wt::Dbo::collection res = getQuery(session, filter).limit(size).offset(offset); - - return std::vector(res.begin(), res.end()); -} - -std::vector -Artist::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size, bool& moreResults) -{ - auto res = getByFilter(session, filter, offset, size + 1); - - if (size != -1 && res.size() == static_cast(size) + 1) - { - moreResults = true; - res.pop_back(); - } - else - moreResults = false; - - return res; -} - - - -std::vector > -Artist::getReleases() const -{ - assert(self()); - assert(self()->id() != Wt::Dbo::dbo_traits::invalidId() ); - assert(session()); - - Wt::Dbo::collection< Wt::Dbo::ptr > res = session()->query >("SELECT r FROM release r INNER JOIN artist a ON t.artist_id = a.id INNER JOIN track t ON t.release_id = r.id").where("a.id = ?").bind(id()); - - return std::vector< Wt::Dbo::ptr > (res.begin(), res.end()); -} - -Wt::Dbo::Query -Artist::getQuery(Wt::Dbo::Session& session, SearchFilter filter) -{ - SqlQuery sqlQuery = generatePartialQuery(filter); - - Wt::Dbo::Query query - = session.query( "SELECT a FROM artist a INNER JOIN track t ON t.artist_id = a.id INNER JOIN release r ON r.id = t.release_id INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id " + sqlQuery.where().get()).groupBy("a.id").orderBy("a.name"); - - for (const std::string& bindArg : sqlQuery.where().getBindArgs()) - query.bind(bindArg); - - return query; -} - -Wt::Dbo::Query -Artist::getUIQuery(Wt::Dbo::Session& session, SearchFilter filter) -{ - SqlQuery sqlQuery = generatePartialQuery(filter); - - Wt::Dbo::Query query - = session.query( "SELECT a.id, a.name, COUNT(DISTINCT r.id), COUNT(DISTINCT t.id) FROM artist a INNER JOIN track t ON t.artist_id = a.id INNER JOIN release r ON r.id = t.release_id INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id " + sqlQuery.where().get()).groupBy("a.id").orderBy("a.name"); - - for (const std::string& bindArg : sqlQuery.where().getBindArgs()) - query.bind(bindArg); - - return query; -} - -void -Artist::updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel& model, SearchFilter filter, const std::vector& columnNames) -{ - Wt::Dbo::Query query = getUIQuery(session, filter); - - model.setQuery(query, columnNames.empty() ? true : false); - - // TODO do something better - if (columnNames.size() == 3) - { - model.addColumn( "a.name", columnNames.at(0)); - model.addColumn( "COUNT(DISTINCT r.id)", columnNames.at(1) ); - model.addColumn( "COUNT(DISTINCT t.id)", columnNames.at(2) ); - } -} - - -} // namespace Database diff --git a/src/database/Artist.hpp b/src/database/Artist.hpp deleted file mode 100644 index 638a0847..00000000 --- a/src/database/Artist.hpp +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2015 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 . - */ - -#ifndef _DB_ARTIST_HPP_ -#define _DB_ARTIST_HPP_ - -#include -#include - -#include -#include - -#include "SearchFilter.hpp" - -namespace Database -{ - -class Track; -class Cluster; -class Release; - -class Artist : public Wt::Dbo::Dbo -{ - public: - - typedef Wt::Dbo::ptr pointer; - typedef Wt::Dbo::dbo_traits::IdType id_type; - - Artist() {} - Artist(const std::string& name, const std::string& MBID = ""); - - // Accessors - static pointer getByMBID(Wt::Dbo::Session& session, const std::string& MBID); - static pointer getById(Wt::Dbo::Session& session, id_type id); - static pointer getNone(Wt::Dbo::Session& session); // Special entry - static std::vector getByName(Wt::Dbo::Session& session, const std::string& name); - static std::vector getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1); - static std::vector getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size, bool& moreExpected); - - static std::vector getAll(Wt::Dbo::Session& session, int offset = -1, int size = -1); - static std::vector getAllOrphans(Wt::Dbo::Session& session); - - // Accessors - std::string getName(void) const { return _name; } - std::string getMBID(void) const { return _MBID; } - - // Get the releases that have at least one track for this artist - std::vector > getReleases() const; - - void setMBID(std::string mbid) { _MBID = mbid; } - - // Create - static pointer create(Wt::Dbo::Session& session, const std::string& name, const std::string& MBID = ""); - - // MVC models for the user interface - // ID, Artist name, albums, tracks - typedef boost::tuple UIQueryResult; - static Wt::Dbo::Query getUIQuery(Wt::Dbo::Session& session, SearchFilter filter); - static void updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel& model, SearchFilter filter, const std::vector& columnNames = std::vector()); - - bool isNone(void) const; - - template - void persist(Action& a) - { - Wt::Dbo::field(a, _name, "name"); - Wt::Dbo::field(a, _MBID, "mbid"); - - Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToOne, "artist"); - } - - private: - - static Wt::Dbo::Query getQuery(Wt::Dbo::Session& session, SearchFilter filter); - - static const std::size_t _maxNameLength = 128; - - std::string _name; - std::string _MBID; // Musicbrainz Identifier - - Wt::Dbo::collection< Wt::Dbo::ptr > _tracks; // Tracks of this artist -}; - -} // namespace Database - -#endif diff --git a/src/database/DatabaseHandler.cpp b/src/database/DatabaseHandler.cpp index 1aedb3b8..85164449 100644 --- a/src/database/DatabaseHandler.cpp +++ b/src/database/DatabaseHandler.cpp @@ -87,7 +87,6 @@ Handler::Handler(Wt::Dbo::SqlConnectionPool& connectionPool) _session.mapClass("playlist"); _session.mapClass("playlist_entry"); _session.mapClass("release"); - _session.mapClass("video"); _session.mapClass("media_directory"); _session.mapClass("setting"); diff --git a/src/database/DbArtist.cpp b/src/database/DbArtist.cpp index 413e40c4..7c65b231 100644 --- a/src/database/DbArtist.cpp +++ b/src/database/DbArtist.cpp @@ -57,22 +57,6 @@ Artist::create(Wt::Dbo::Session& session, const std::string& name, const std::st return session.add(new Artist(name, MBID)); } -Artist::pointer -Artist::getNone(Wt::Dbo::Session& session) -{ - std::vector res = getByName(session, ""); - if (res.empty()) - return create(session, ""); - - return res.front(); -} - -bool -Artist::isNone() const -{ - return _name == ""; -} - std::vector Artist::getAll(Wt::Dbo::Session& session, int offset, int size) { diff --git a/src/database/DbArtist.hpp b/src/database/DbArtist.hpp index 47c7d24f..a22360dc 100644 --- a/src/database/DbArtist.hpp +++ b/src/database/DbArtist.hpp @@ -48,7 +48,6 @@ class Artist : public Wt::Dbo::Dbo // Accessors static pointer getByMBID(Wt::Dbo::Session& session, const std::string& MBID); static pointer getById(Wt::Dbo::Session& session, id_type id); - static pointer getNone(Wt::Dbo::Session& session); // Special entry static std::vector getByName(Wt::Dbo::Session& session, const std::string& name); static std::vector getByFilter(Wt::Dbo::Session& session, const std::set& clusters, // at least one track that belongs to these clusters @@ -72,7 +71,6 @@ class Artist : public Wt::Dbo::Dbo // Create static pointer create(Wt::Dbo::Session& session, const std::string& name, const std::string& MBID = ""); - bool isNone(void) const; template void persist(Action& a) diff --git a/src/database/MediaDirectory.hpp b/src/database/MediaDirectory.hpp index c77b6b0c..e00892dd 100644 --- a/src/database/MediaDirectory.hpp +++ b/src/database/MediaDirectory.hpp @@ -36,7 +36,6 @@ class MediaDirectory enum Type { Audio = 1, - Video = 2, }; MediaDirectory() {} diff --git a/src/database/Release.cpp b/src/database/Release.cpp index 2b338e34..d3d73732 100644 --- a/src/database/Release.cpp +++ b/src/database/Release.cpp @@ -56,22 +56,6 @@ Release::create(Wt::Dbo::Session& session, const std::string& name, const std::s return session.add(new Release(name, MBID)); } -bool -Release::isNone() const -{ - return _name == ""; -} - -Release::pointer -Release::getNone(Wt::Dbo::Session& session) -{ - std::vector res = getByName(session, ""); - if (res.empty()) - return create(session, ""); - - return res.front(); -} - std::vector Release::getAll(Wt::Dbo::Session& session, int offset, int size) { diff --git a/src/database/Release.hpp b/src/database/Release.hpp index 3feb0100..b9d8acac 100644 --- a/src/database/Release.hpp +++ b/src/database/Release.hpp @@ -46,7 +46,6 @@ class Release : public Wt::Dbo::Dbo static pointer getByMBID(Wt::Dbo::Session& session, const std::string& MBID); static std::vector 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); // Special entry static std::vector getAllOrphans(Wt::Dbo::Session& session); // no track related static std::vector getAll(Wt::Dbo::Session& session, int offset, int size); @@ -68,7 +67,6 @@ class Release : public Wt::Dbo::Dbo // Accessors std::string getName() const { return _name; } std::string getMBID() const { return _MBID; } - bool isNone(void) const; boost::posix_time::time_duration getDuration(void) const; // Get the artists of this release diff --git a/src/database/Track.cpp b/src/database/Track.cpp index 50c16a4d..a7d5ab9c 100644 --- a/src/database/Track.cpp +++ b/src/database/Track.cpp @@ -240,21 +240,6 @@ Cluster::getByType(Wt::Dbo::Session& session, std::string type) return std::vector(res.begin(), res.end()); } -Cluster::pointer -Cluster::getNone(Wt::Dbo::Session& session) -{ - pointer res = get(session, "", ""); - if (!res) - res = create(session, "", ""); - return res; -} - -bool -Cluster::isNone(void) const -{ - return (_type == "" && _name == ""); -} - Cluster::pointer Cluster::create(Wt::Dbo::Session& session, std::string type, std::string name) { diff --git a/src/database/Track.hpp b/src/database/Track.hpp index 0d81a36d..01a1566c 100644 --- a/src/database/Track.hpp +++ b/src/database/Track.hpp @@ -59,7 +59,6 @@ class Cluster // Find utility static pointer get(Wt::Dbo::Session& session, std::string type, std::string name); - static pointer getNone(Wt::Dbo::Session& session); static std::vector getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1); static Wt::Dbo::collection getAll(Wt::Dbo::Session& session); static std::vector getAllTypes(Wt::Dbo::Session& session); @@ -74,7 +73,6 @@ class Cluster // Accessors const std::string& getName(void) const { return _name; } const std::string& getType(void) const { return _type; } - bool isNone(void) const; const Wt::Dbo::collection< Wt::Dbo::ptr >& getTracks() const { return _tracks;} void addTrack(Wt::Dbo::Session& session, Wt::Dbo::dbo_traits::IdType trackId); diff --git a/src/database/Types.hpp b/src/database/Types.hpp index 681e4c97..0518cbe5 100644 --- a/src/database/Types.hpp +++ b/src/database/Types.hpp @@ -23,7 +23,6 @@ #include "Track.hpp" #include "Playlist.hpp" #include "Release.hpp" -#include "Video.hpp" #include "MediaDirectory.hpp" #include "User.hpp" diff --git a/src/database/Video.cpp b/src/database/Video.cpp deleted file mode 100644 index 6568604d..00000000 --- a/src/database/Video.cpp +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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 . - */ - -#include "Types.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
${}${tr:msg-various-artists}${}

${}${year}${}${} (${orig-year})${}

+ ${play-btn}${add-btn}
diff --git a/approot/release.xml b/approot/release.xml index e92c6289..1748c3cd 100644 --- a/approot/release.xml +++ b/approot/release.xml @@ -3,6 +3,14 @@ + + + + + + + + ${cover} ${artists class="list-group"} + ${show-more} diff --git a/approot/messages.xml b/approot/messages.xml index 44479447..38d2396f 100644 --- a/approot/messages.xml +++ b/approot/messages.xml @@ -15,4 +15,6 @@ Type Value + +Show more diff --git a/approot/releases.xml b/approot/releases.xml index 374bcd91..23957a5f 100644 --- a/approot/releases.xml +++ b/approot/releases.xml @@ -14,6 +14,7 @@
${releases class="list-group"} + ${show-more}
diff --git a/approot/templates.xml b/approot/templates.xml index a6581c0f..d0b47ac9 100644 --- a/approot/templates.xml +++ b/approot/templates.xml @@ -17,6 +17,10 @@ ${contents} + + ${tr:msg-show-more} + +
${add-filter}${filters} diff --git a/approot/tracks.xml b/approot/tracks.xml index 62091789..823e6fb9 100644 --- a/approot/tracks.xml +++ b/approot/tracks.xml @@ -14,6 +14,7 @@
${tracks class="list-group"} + ${show-more}
diff --git a/src/database/DbArtist.cpp b/src/database/DbArtist.cpp index 7c65b231..bce519e2 100644 --- a/src/database/DbArtist.cpp +++ b/src/database/DbArtist.cpp @@ -120,7 +120,7 @@ Artist::getByFilter(Wt::Dbo::Session& session, const std::vector keywords, int offset, int size, bool& moreResults) { - Wt::Dbo::collection collection = getQuery(session, clusters, keywords).limit(size).offset(offset); + Wt::Dbo::collection collection = getQuery(session, clusters, keywords).limit(size + 1).offset(offset); auto res = std::vector(collection.begin(), collection.end()); diff --git a/src/database/Release.cpp b/src/database/Release.cpp index d3d73732..0e902da8 100644 --- a/src/database/Release.cpp +++ b/src/database/Release.cpp @@ -118,7 +118,7 @@ Release::getByFilter(Wt::Dbo::Session& session, const std::vector keywords, int offset, int size, bool& moreResults) { - Wt::Dbo::collection collection = getQuery(session, clusterIds, keywords).limit(size).offset(offset); + Wt::Dbo::collection collection = getQuery(session, clusterIds, keywords).limit(size + 1).offset(offset); auto res = std::vector(collection.begin(), collection.end()); diff --git a/src/database/Track.cpp b/src/database/Track.cpp index a7d5ab9c..efd5944a 100644 --- a/src/database/Track.cpp +++ b/src/database/Track.cpp @@ -169,7 +169,7 @@ Track::getByFilter(Wt::Dbo::Session& session, const std::vector keywords, int offset, int size, bool& moreResults) { - Wt::Dbo::collection collection = getQuery(session, clusterIds, keywords).limit(size).offset(offset); + Wt::Dbo::collection collection = getQuery(session, clusterIds, keywords).limit(size + 1).offset(offset); auto res = std::vector(collection.begin(), collection.end()); diff --git a/src/ui/ArtistsView.cpp b/src/ui/ArtistsView.cpp index 7373c39b..3038b861 100644 --- a/src/ui/ArtistsView.cpp +++ b/src/ui/ArtistsView.cpp @@ -39,16 +39,25 @@ Artists::Artists(Filters* filters, Wt::WContainerWidget* parent) : Wt::WContainerWidget(parent), _filters(filters) { - auto artists = new Wt::WTemplate(Wt::WString::tr("template-artists"), this); - artists->addFunction("tr", &Wt::WTemplate::Functions::tr); + auto container = new Wt::WTemplate(Wt::WString::tr("template-artists"), this); + container->addFunction("tr", &Wt::WTemplate::Functions::tr); _search = new Wt::WLineEdit(); - artists->bindWidget("search", _search); + container->bindWidget("search", _search); _search->setPlaceholderText(Wt::WString::tr("msg-search-placeholder")); _search->textInput().connect(this, &Artists::refresh); _artistsContainer = new Wt::WContainerWidget(); - artists->bindWidget("artists", _artistsContainer); + container->bindWidget("artists", _artistsContainer); + + _showMore = new Wt::WTemplate(Wt::WString::tr("template-show-more")); + _showMore->addFunction("tr", &Wt::WTemplate::Functions::tr); + container->bindWidget("show-more", _showMore); + + _showMore->clicked().connect(std::bind([=] + { + add_some(); + })); refresh(); @@ -58,9 +67,14 @@ Artists::Artists(Filters* filters, Wt::WContainerWidget* parent) void Artists::refresh() { - auto searchKeywords = splitString(_search->text().toUTF8(), " "); - _artistsContainer->clear(); + add_some(); +} + +void +Artists::add_some() +{ + auto searchKeywords = splitString(_search->text().toUTF8(), " "); auto clusterIds = _filters->getClusterIds(); @@ -70,7 +84,7 @@ Artists::refresh() auto artists = Artist::getByFilter(DboSession(), clusterIds, searchKeywords, - 0, 40, moreResults); + _artistsContainer->count(), 20, moreResults); for (auto artist : artists) { @@ -86,6 +100,7 @@ Artists::refresh() } } + _showMore->setHidden(!moreResults); } } // namespace UserInterface diff --git a/src/ui/ArtistsView.hpp b/src/ui/ArtistsView.hpp index cbf9c26a..c8b42a39 100644 --- a/src/ui/ArtistsView.hpp +++ b/src/ui/ArtistsView.hpp @@ -37,8 +37,10 @@ class Artists : public Wt::WContainerWidget private: void refresh(); + void add_some(); Filters* _filters; + Wt::WTemplate* _showMore; Wt::WLineEdit* _search; Wt::WContainerWidget* _artistsContainer; }; diff --git a/src/ui/ReleasesView.cpp b/src/ui/ReleasesView.cpp index dce1b8eb..1353a2b8 100644 --- a/src/ui/ReleasesView.cpp +++ b/src/ui/ReleasesView.cpp @@ -40,36 +40,49 @@ Releases::Releases(Filters* filters, Wt::WContainerWidget* parent) : Wt::WContainerWidget(parent), _filters(filters) { - auto releases = new Wt::WTemplate(Wt::WString::tr("template-releases"), this); - releases->addFunction("tr", &Wt::WTemplate::Functions::tr); + auto container = new Wt::WTemplate(Wt::WString::tr("template-releases"), this); + container->addFunction("tr", &Wt::WTemplate::Functions::tr); _search = new Wt::WLineEdit(); - releases->bindWidget("search", _search); + container->bindWidget("search", _search); _search->setPlaceholderText(Wt::WString::tr("msg-search-placeholder")); _search->textInput().connect(this, &Releases::refresh); _releasesContainer = new Wt::WContainerWidget(); - releases->bindWidget("releases", _releasesContainer); + container->bindWidget("releases", _releasesContainer); + + _showMore = new Wt::WTemplate(Wt::WString::tr("template-show-more")); + _showMore->addFunction("tr", &Wt::WTemplate::Functions::tr); + container->bindWidget("show-more", _showMore); + + _showMore->clicked().connect(std::bind([=] + { + add_some(); + })); refresh(); filters->updated().connect(this, &Releases::refresh); } - void Releases::refresh() { - auto searchKeywords = splitString(_search->text().toUTF8(), " "); - _releasesContainer->clear(); + add_some(); +} + +void +Releases::add_some() +{ + auto searchKeywords = splitString(_search->text().toUTF8(), " "); auto clusterIds = _filters->getClusterIds(); Wt::Dbo::Transaction transaction(DboSession()); bool moreResults; - auto releases = Release::getByFilter(DboSession(), clusterIds, searchKeywords, 0, 40, moreResults); + auto releases = Release::getByFilter(DboSession(), clusterIds, searchKeywords, _releasesContainer->count(), 20, moreResults); for (auto release : releases) { @@ -120,6 +133,10 @@ Releases::refresh() releaseAdd.emit(releaseId); })); } + + std::cerr << "if-show-more = " << moreResults << "\n"; + + _showMore->setHidden(!moreResults); } } // namespace UserInterface diff --git a/src/ui/ReleasesView.hpp b/src/ui/ReleasesView.hpp index 9bc9c996..b14fcdf9 100644 --- a/src/ui/ReleasesView.hpp +++ b/src/ui/ReleasesView.hpp @@ -21,6 +21,7 @@ #include #include +#include namespace UserInterface { @@ -36,8 +37,10 @@ class Releases : public Wt::WContainerWidget private: void refresh(); + void add_some(); Filters* _filters; + Wt::WTemplate* _showMore; Wt::WLineEdit* _search; Wt::WContainerWidget* _releasesContainer; }; diff --git a/src/ui/TracksView.cpp b/src/ui/TracksView.cpp index a4414f2f..31ae4233 100644 --- a/src/ui/TracksView.cpp +++ b/src/ui/TracksView.cpp @@ -20,7 +20,6 @@ #include #include #include -#include #include #include "database/Types.hpp" @@ -40,16 +39,25 @@ Tracks::Tracks(Filters* filters, Wt::WContainerWidget* parent) : Wt::WContainerWidget(parent), _filters(filters) { - auto tracks = new Wt::WTemplate(Wt::WString::tr("template-tracks"), this); - tracks->addFunction("tr", &Wt::WTemplate::Functions::tr); + auto container = new Wt::WTemplate(Wt::WString::tr("template-tracks"), this); + container->addFunction("tr", &Wt::WTemplate::Functions::tr); _search = new Wt::WLineEdit(); - tracks->bindWidget("search", _search); + container->bindWidget("search", _search); _search->setPlaceholderText(Wt::WString::tr("msg-search-placeholder")); _search->textInput().connect(this, &Tracks::refresh); _tracksContainer = new Wt::WContainerWidget(); - tracks->bindWidget("tracks", _tracksContainer); + container->bindWidget("tracks", _tracksContainer); + + _showMore = new Wt::WTemplate(Wt::WString::tr("template-show-more")); + _showMore->addFunction("tr", &Wt::WTemplate::Functions::tr); + container->bindWidget("show-more", _showMore); + + _showMore->clicked().connect(std::bind([=] + { + add_some(); + })); refresh(); @@ -60,16 +68,21 @@ Tracks::Tracks(Filters* filters, Wt::WContainerWidget* parent) void Tracks::refresh() { - auto searchKeywords = splitString(_search->text().toUTF8(), " "); - _tracksContainer->clear(); + add_some(); +} + +void +Tracks::add_some() +{ + auto searchKeywords = splitString(_search->text().toUTF8(), " "); auto clusterIds = _filters->getClusterIds(); Wt::Dbo::Transaction transaction(DboSession()); bool moreResults; - auto tracks = Track::getByFilter(DboSession(), clusterIds, searchKeywords, 0, 40, moreResults); + auto tracks = Track::getByFilter(DboSession(), clusterIds, searchKeywords, _tracksContainer->count(), 20, moreResults); for (auto track : tracks) { @@ -120,6 +133,7 @@ Tracks::refresh() })); } + _showMore->setHidden(!moreResults); } } // namespace UserInterface diff --git a/src/ui/TracksView.hpp b/src/ui/TracksView.hpp index 4f82cfe9..a6b52b42 100644 --- a/src/ui/TracksView.hpp +++ b/src/ui/TracksView.hpp @@ -21,6 +21,7 @@ #include #include +#include namespace UserInterface { @@ -35,8 +36,10 @@ class Tracks : public Wt::WContainerWidget private: void refresh(); + void add_some(); Wt::WContainerWidget* _tracksContainer; + Wt::WTemplate* _showMore; Wt::WLineEdit* _search; Filters* _filters; }; From 86077c1e44242784cdb62467386a558f51184c13 Mon Sep 17 00:00:00 2001 From: emeric Date: Thu, 22 Feb 2018 13:54:32 +0100 Subject: [PATCH 049/195] Added show more to playlist --- approot/playlist.xml | 1 + src/database/Playlist.cpp | 12 ++++++++++-- src/database/Playlist.hpp | 2 +- src/ui/ArtistsView.cpp | 6 +++--- src/ui/ArtistsView.hpp | 2 +- src/ui/PlaylistView.cpp | 37 ++++++++++++++++++++++++++++--------- src/ui/PlaylistView.hpp | 3 +++ src/ui/ReleasesView.cpp | 6 +++--- src/ui/ReleasesView.hpp | 2 +- src/ui/TracksView.cpp | 6 +++--- src/ui/TracksView.hpp | 2 +- 11 files changed, 55 insertions(+), 24 deletions(-) diff --git a/approot/playlist.xml b/approot/playlist.xml index 5b097e83..2543d616 100644 --- a/approot/playlist.xml +++ b/approot/playlist.xml @@ -20,6 +20,7 @@ ${save-btn} ${load-btn} ${clear-btn}
${entries} + ${show-more}
diff --git a/src/database/Playlist.cpp b/src/database/Playlist.cpp index 0094fa7b..24e8e68b 100644 --- a/src/database/Playlist.cpp +++ b/src/database/Playlist.cpp @@ -83,16 +83,24 @@ PlaylistEntry::create(Wt::Dbo::Session& session, Wt::Dbo::ptr track, Wt:: std::vector> -Playlist::getTracks(int offset, int size) const +Playlist::getTracks(int offset, int size, bool& moreResults) const { assert(session()); - Wt::Dbo::collection> entries = session()->find().where("playlist_id = ?").bind(self().id()).orderBy("pos").offset(offset).limit(size); + moreResults = false; + + Wt::Dbo::collection> entries = session()->find().where("playlist_id = ?").bind(self().id()).orderBy("pos").offset(offset).limit(size + 1); std::vector> res; for (auto entry : entries) { + if (size != -1 && res.size() == static_cast(size)) + { + moreResults = true; + break; + } + res.push_back(entry->getTrack()); } diff --git a/src/database/Playlist.hpp b/src/database/Playlist.hpp index b8f62474..816e4098 100644 --- a/src/database/Playlist.hpp +++ b/src/database/Playlist.hpp @@ -54,7 +54,7 @@ class Playlist : public Wt::Dbo::Dbo void clear() { _entries.clear(); } // Get tracks, ordered by position - std::vector> getTracks(int offset = -1, int size = -1) const; + std::vector> getTracks(int offset, int size, bool& moreResults) const; template void persist(Action& a) diff --git a/src/ui/ArtistsView.cpp b/src/ui/ArtistsView.cpp index 3038b861..f801fa05 100644 --- a/src/ui/ArtistsView.cpp +++ b/src/ui/ArtistsView.cpp @@ -56,7 +56,7 @@ Artists::Artists(Filters* filters, Wt::WContainerWidget* parent) _showMore->clicked().connect(std::bind([=] { - add_some(); + addSome(); })); refresh(); @@ -68,11 +68,11 @@ void Artists::refresh() { _artistsContainer->clear(); - add_some(); + addSome(); } void -Artists::add_some() +Artists::addSome() { auto searchKeywords = splitString(_search->text().toUTF8(), " "); diff --git a/src/ui/ArtistsView.hpp b/src/ui/ArtistsView.hpp index c8b42a39..f51c8706 100644 --- a/src/ui/ArtistsView.hpp +++ b/src/ui/ArtistsView.hpp @@ -37,7 +37,7 @@ class Artists : public Wt::WContainerWidget private: void refresh(); - void add_some(); + void addSome(); Filters* _filters; Wt::WTemplate* _showMore; diff --git a/src/ui/PlaylistView.cpp b/src/ui/PlaylistView.cpp index 57f8474c..d3644bc2 100644 --- a/src/ui/PlaylistView.cpp +++ b/src/ui/PlaylistView.cpp @@ -19,7 +19,6 @@ #include #include -#include #include #include "LmsApplication.hpp" @@ -32,20 +31,29 @@ static const std::string currentPlaylistName = "__current__playlist__"; Playlist::Playlist(Wt::WContainerWidget* parent) : Wt::WContainerWidget(parent) { - auto t = new Wt::WTemplate(Wt::WString::tr("template-playlist"), this); - t->addFunction("tr", &Wt::WTemplate::Functions::tr); + auto container = new Wt::WTemplate(Wt::WString::tr("template-playlist"), this); + container->addFunction("tr", &Wt::WTemplate::Functions::tr); auto saveBtn = new Wt::WText(Wt::WString::tr("btn-playlist-save-btn"), Wt::XHTMLText); - t->bindWidget("save-btn", saveBtn); + container->bindWidget("save-btn", saveBtn); auto loadBtn = new Wt::WText(Wt::WString::tr("btn-playlist-load-btn"), Wt::XHTMLText); - t->bindWidget("load-btn", loadBtn); + container->bindWidget("load-btn", loadBtn); auto clearBtn = new Wt::WText(Wt::WString::tr("btn-playlist-clear-btn"), Wt::XHTMLText); - t->bindWidget("clear-btn", clearBtn); + container->bindWidget("clear-btn", clearBtn); _entriesContainer = new Wt::WContainerWidget(); - t->bindWidget("entries", _entriesContainer); + container->bindWidget("entries", _entriesContainer); + + _showMore = new Wt::WTemplate(Wt::WString::tr("template-show-more")); + _showMore->addFunction("tr", &Wt::WTemplate::Functions::tr); + container->bindWidget("show-more", _showMore); + + _showMore->clicked().connect(std::bind([=] + { + addSome(); + })); refresh(); } @@ -67,7 +75,7 @@ Playlist::addTracks(const std::vector& tracks) playlist.modify()->addTrack(track); } - refresh(); + addSome(); } void @@ -86,8 +94,16 @@ Playlist::playTracks(const std::vector& tracks) // TODO Immediate play } + void Playlist::refresh() +{ + _entriesContainer->clear(); + addSome(); +} + +void +Playlist::addSome() { Wt::Dbo::Transaction transaction (DboSession()); @@ -95,7 +111,8 @@ Playlist::refresh() if (!playlist) return; - auto tracks = playlist->getTracks(_entriesContainer->count(), 20); // TODO + bool moreResults; + auto tracks = playlist->getTracks(_entriesContainer->count(), 20, moreResults); for (auto track : tracks) { Wt::WTemplate* entry = new Wt::WTemplate(Wt::WString::tr("template-playlist-entry"), _entriesContainer); @@ -136,6 +153,8 @@ Playlist::refresh() entry->bindWidget("del-btn", addBtn); // TODO } + + _showMore->setHidden(!moreResults); } } // namespace UserInterface diff --git a/src/ui/PlaylistView.hpp b/src/ui/PlaylistView.hpp index 7a0dc595..03db9226 100644 --- a/src/ui/PlaylistView.hpp +++ b/src/ui/PlaylistView.hpp @@ -20,6 +20,7 @@ #pragma once #include +#include #include "database/Types.hpp" @@ -35,8 +36,10 @@ class Playlist : public Wt::WContainerWidget private: void refresh(); + void addSome(); Wt::WContainerWidget* _entriesContainer; + Wt::WTemplate* _showMore; }; } // namespace UserInterface diff --git a/src/ui/ReleasesView.cpp b/src/ui/ReleasesView.cpp index 1353a2b8..f8b9df0e 100644 --- a/src/ui/ReleasesView.cpp +++ b/src/ui/ReleasesView.cpp @@ -57,7 +57,7 @@ Releases::Releases(Filters* filters, Wt::WContainerWidget* parent) _showMore->clicked().connect(std::bind([=] { - add_some(); + addSome(); })); refresh(); @@ -69,11 +69,11 @@ void Releases::refresh() { _releasesContainer->clear(); - add_some(); + addSome(); } void -Releases::add_some() +Releases::addSome() { auto searchKeywords = splitString(_search->text().toUTF8(), " "); diff --git a/src/ui/ReleasesView.hpp b/src/ui/ReleasesView.hpp index b14fcdf9..6626656b 100644 --- a/src/ui/ReleasesView.hpp +++ b/src/ui/ReleasesView.hpp @@ -37,7 +37,7 @@ class Releases : public Wt::WContainerWidget private: void refresh(); - void add_some(); + void addSome(); Filters* _filters; Wt::WTemplate* _showMore; diff --git a/src/ui/TracksView.cpp b/src/ui/TracksView.cpp index 31ae4233..cbfd45a8 100644 --- a/src/ui/TracksView.cpp +++ b/src/ui/TracksView.cpp @@ -56,7 +56,7 @@ Tracks::Tracks(Filters* filters, Wt::WContainerWidget* parent) _showMore->clicked().connect(std::bind([=] { - add_some(); + addSome(); })); refresh(); @@ -69,11 +69,11 @@ void Tracks::refresh() { _tracksContainer->clear(); - add_some(); + addSome(); } void -Tracks::add_some() +Tracks::addSome() { auto searchKeywords = splitString(_search->text().toUTF8(), " "); diff --git a/src/ui/TracksView.hpp b/src/ui/TracksView.hpp index a6b52b42..3f949770 100644 --- a/src/ui/TracksView.hpp +++ b/src/ui/TracksView.hpp @@ -36,7 +36,7 @@ class Tracks : public Wt::WContainerWidget private: void refresh(); - void add_some(); + void addSome(); Wt::WContainerWidget* _tracksContainer; Wt::WTemplate* _showMore; From 373d7ff12bedb8e5aff104b61f9e8ace93506a46 Mon Sep 17 00:00:00 2001 From: emeric Date: Thu, 22 Feb 2018 13:59:43 +0100 Subject: [PATCH 050/195] When adding an artist to the playlist, add only the tracks he actually made (and not whole compilations for example) --- src/ui/Explore.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ui/Explore.cpp b/src/ui/Explore.cpp index d11df72a..fcd41278 100644 --- a/src/ui/Explore.cpp +++ b/src/ui/Explore.cpp @@ -138,7 +138,11 @@ static std::vector getArtistTracks(Wt::Dbo::Session& s for (auto release : releases) { auto tracks = release->getTracks(clusters); - res.insert( res.end(), tracks.begin(), tracks.end() ); + for (auto track : tracks) + { + if (track->getArtist() && track->getArtist().id() == artistId) + res.push_back(track); + } } return res; From fe53103445d532e9492adabcc0ceca7065c561bc Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 24 Feb 2018 14:52:54 +0100 Subject: [PATCH 051/195] Do not display covers in playlist --- approot/playlist.xml | 14 +------------- src/ui/PlaylistView.cpp | 7 +------ 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/approot/playlist.xml b/approot/playlist.xml index 2543d616..266baa33 100644 --- a/approot/playlist.xml +++ b/approot/playlist.xml @@ -32,19 +32,7 @@ -
-
- ${cover class="media-object"} -
-
-

${name}

-
- - ${}${release-name}${} - ${}${artist-name}${} - - ${play-btn} ${del-btn} -
+ ${pos} ${name} ${}${release-name}${} ${}${artist-name}${} ${play-btn} ${del-btn}
diff --git a/src/ui/PlaylistView.cpp b/src/ui/PlaylistView.cpp index d3644bc2..a5fef657 100644 --- a/src/ui/PlaylistView.cpp +++ b/src/ui/PlaylistView.cpp @@ -117,6 +117,7 @@ Playlist::addSome() { Wt::WTemplate* entry = new Wt::WTemplate(Wt::WString::tr("template-playlist-entry"), _entriesContainer); + entry->bindString("pos", std::to_string(_entriesContainer->count())); entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::PlainText); auto artist = track->getArtist(); @@ -139,12 +140,6 @@ Playlist::addSome() entry->bindWidget("release-name", releaseAnchor); } - Wt::WImage *cover = new Wt::WImage(); - cover->setImageLink(SessionImageResource()->getTrackUrl(track.id(), 64)); - // Some images may not be square - cover->setWidth(64); - entry->bindWidget("cover", cover); - auto playBtn = new Wt::WText(Wt::WString::tr("btn-playlist-entry-play-btn"), Wt::XHTMLText); entry->bindWidget("play-btn", playBtn); // TODO From aa54b5b5993df89c7b6dcfbef34da6e624199230 Mon Sep 17 00:00:00 2001 From: emeric Date: Sun, 25 Feb 2018 08:57:05 +0100 Subject: [PATCH 052/195] Added a play/add all on tracks view --- approot/tracks.xml | 1 + src/database/DbArtist.cpp | 4 +++- src/database/Playlist.cpp | 12 ++++++------ src/database/Playlist.hpp | 2 +- src/database/Release.cpp | 4 +++- src/database/Track.cpp | 13 ++++++++++++- src/database/Track.hpp | 2 ++ src/ui/Explore.cpp | 14 ++++++++++++++ src/ui/Explore.hpp | 2 ++ src/ui/PlaylistView.cpp | 4 ++-- src/ui/TracksView.cpp | 38 ++++++++++++++++++++++++++++++++------ src/ui/TracksView.hpp | 6 ++++++ 12 files changed, 84 insertions(+), 18 deletions(-) diff --git a/approot/tracks.xml b/approot/tracks.xml index 823e6fb9..ef3d7320 100644 --- a/approot/tracks.xml +++ b/approot/tracks.xml @@ -6,6 +6,7 @@
diff --git a/src/database/DbArtist.cpp b/src/database/DbArtist.cpp index bce519e2..32654f5e 100644 --- a/src/database/DbArtist.cpp +++ b/src/database/DbArtist.cpp @@ -120,7 +120,9 @@ Artist::getByFilter(Wt::Dbo::Session& session, const std::vector keywords, int offset, int size, bool& moreResults) { - Wt::Dbo::collection collection = getQuery(session, clusters, keywords).limit(size + 1).offset(offset); + Wt::Dbo::collection collection = getQuery(session, clusters, keywords) + .limit(size != -1 ? size + 1 : -1) + .offset(offset); auto res = std::vector(collection.begin(), collection.end()); diff --git a/src/database/Playlist.cpp b/src/database/Playlist.cpp index 24e8e68b..47e259b3 100644 --- a/src/database/Playlist.cpp +++ b/src/database/Playlist.cpp @@ -61,12 +61,6 @@ Playlist::getAll(Wt::Dbo::Session& session, Wt::Dbo::ptr user) return std::vector(res.begin(), res.end()); } -void -Playlist::addTrack(Wt::Dbo::ptr track) -{ - _entries.insert( PlaylistEntry::create(*session(), track, self(), _entries.size()) ); -} - PlaylistEntry::PlaylistEntry(Wt::Dbo::ptr track, Wt::Dbo::ptr playlist, int pos) : _pos(pos), _track(track), @@ -107,4 +101,10 @@ Playlist::getTracks(int offset, int size, bool& moreResults) const return res; } +std::size_t +Playlist::getCount() const +{ + return _entries.size(); +} + } // namespace Database diff --git a/src/database/Playlist.hpp b/src/database/Playlist.hpp index 816e4098..0acc41b8 100644 --- a/src/database/Playlist.hpp +++ b/src/database/Playlist.hpp @@ -50,10 +50,10 @@ class Playlist : public Wt::Dbo::Dbo bool isPublic() const { return _isPublic; } // Modifiers - void addTrack(Wt::Dbo::ptr track); void clear() { _entries.clear(); } // Get tracks, ordered by position + std::size_t getCount() const; std::vector> getTracks(int offset, int size, bool& moreResults) const; template diff --git a/src/database/Release.cpp b/src/database/Release.cpp index 0e902da8..134db031 100644 --- a/src/database/Release.cpp +++ b/src/database/Release.cpp @@ -118,7 +118,9 @@ Release::getByFilter(Wt::Dbo::Session& session, const std::vector keywords, int offset, int size, bool& moreResults) { - Wt::Dbo::collection collection = getQuery(session, clusterIds, keywords).limit(size + 1).offset(offset); + Wt::Dbo::collection collection = getQuery(session, clusterIds, keywords) + .limit(size != -1 ? size + 1 : -1) + .offset(offset); auto res = std::vector(collection.begin(), collection.end()); diff --git a/src/database/Track.cpp b/src/database/Track.cpp index efd5944a..5853a293 100644 --- a/src/database/Track.cpp +++ b/src/database/Track.cpp @@ -169,7 +169,9 @@ Track::getByFilter(Wt::Dbo::Session& session, const std::vector keywords, int offset, int size, bool& moreResults) { - Wt::Dbo::collection collection = getQuery(session, clusterIds, keywords).limit(size + 1).offset(offset); + Wt::Dbo::collection collection = getQuery(session, clusterIds, keywords) + .limit(size != -1 ? size + 1 : -1) + .offset(offset); auto res = std::vector(collection.begin(), collection.end()); @@ -184,6 +186,15 @@ Track::getByFilter(Wt::Dbo::Session& session, return res; } +std::vector +Track::getByFilter(Wt::Dbo::Session& session, + const std::set& clusters) +{ + bool moreResults; + + return getByFilter(session, clusters, std::vector(), -1, -1, moreResults); +} + boost::optional Track::getTrackNumber(void) const { diff --git a/src/database/Track.hpp b/src/database/Track.hpp index 01a1566c..7c0f1bc2 100644 --- a/src/database/Track.hpp +++ b/src/database/Track.hpp @@ -118,6 +118,8 @@ class Track static pointer getByPath(Wt::Dbo::Session& session, const boost::filesystem::path& p); static pointer getById(Wt::Dbo::Session& session, id_type id); static pointer getByMBID(Wt::Dbo::Session& session, const std::string& MBID); + static std::vector getByFilter(Wt::Dbo::Session& session, + const std::set& clusters); // tracks that belong to these clusters static std::vector getByFilter(Wt::Dbo::Session& session, const std::set& clusters, // tracks that belong to these clusters const std::vector keywords, // name must match all of these keywords diff --git a/src/ui/Explore.cpp b/src/ui/Explore.cpp index fcd41278..7a8d463c 100644 --- a/src/ui/Explore.cpp +++ b/src/ui/Explore.cpp @@ -115,6 +115,8 @@ Explore::Explore(Wt::WContainerWidget* parent) tracks->trackAdd.connect(this, &Explore::handleTrackAdd); tracks->trackPlay.connect(this, &Explore::handleTrackPlay); + tracks->tracksAdd.connect(this, &Explore::handleTracksAdd); + tracks->tracksPlay.connect(this, &Explore::handleTracksPlay); wApp->internalPathChanged().connect(std::bind([=] @@ -218,5 +220,17 @@ Explore::handleTrackPlay(Database::id_type id) tracksPlay.emit(getTrack(DboSession(), id)); } +void +Explore::handleTracksAdd(std::vector tracks) +{ + tracksAdd.emit(tracks); +} + +void +Explore::handleTracksPlay(std::vector tracks) +{ + tracksPlay.emit(tracks); +} + } // namespace UserInterface diff --git a/src/ui/Explore.hpp b/src/ui/Explore.hpp index 0e04a967..3c0dd136 100644 --- a/src/ui/Explore.hpp +++ b/src/ui/Explore.hpp @@ -43,6 +43,8 @@ class Explore : public Wt::WContainerWidget void handleReleasePlay(Database::id_type id); void handleTrackAdd(Database::id_type id); void handleTrackPlay(Database::id_type id); + void handleTracksAdd(std::vector tracks); + void handleTracksPlay(std::vector tracks); Filters* _filters; }; diff --git a/src/ui/PlaylistView.cpp b/src/ui/PlaylistView.cpp index a5fef657..b7a74384 100644 --- a/src/ui/PlaylistView.cpp +++ b/src/ui/PlaylistView.cpp @@ -70,9 +70,10 @@ Playlist::addTracks(const std::vector& tracks) playlist = Database::Playlist::create(DboSession(), currentPlaylistName, false, CurrentUser()); } + std::size_t pos = playlist->getCount(); for (auto track : tracks) { - playlist.modify()->addTrack(track); + Database::PlaylistEntry::create(DboSession(), track, playlist, pos++); } addSome(); @@ -133,7 +134,6 @@ Playlist::addSome() if (release) { entry->setCondition("if-has-release", true); - // TODO anchor Wt::WAnchor *releaseAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/release/" + std::to_string(track->getRelease().id()))); Wt::WText *releaseText = new Wt::WText(releaseAnchor); releaseText->setText(Wt::WString::fromUTF8(release->getName(), Wt::PlainText)); diff --git a/src/ui/TracksView.cpp b/src/ui/TracksView.cpp index cbfd45a8..8240986d 100644 --- a/src/ui/TracksView.cpp +++ b/src/ui/TracksView.cpp @@ -47,6 +47,20 @@ Tracks::Tracks(Filters* filters, Wt::WContainerWidget* parent) _search->setPlaceholderText(Wt::WString::tr("msg-search-placeholder")); _search->textInput().connect(this, &Tracks::refresh); + auto playBtn = new Wt::WText(Wt::WString::tr("btn-tracks-play-btn"), Wt::XHTMLText); + container->bindWidget("play-btn", playBtn); + playBtn->clicked().connect(std::bind([=] + { + tracksPlay.emit(getTracks()); + })); + + auto addBtn = new Wt::WText(Wt::WString::tr("btn-tracks-add-btn"), Wt::XHTMLText); + container->bindWidget("add-btn", addBtn); + addBtn->clicked().connect(std::bind([=] + { + tracksAdd.emit(getTracks()); + })); + _tracksContainer = new Wt::WContainerWidget(); container->bindWidget("tracks", _tracksContainer); @@ -64,6 +78,23 @@ Tracks::Tracks(Filters* filters, Wt::WContainerWidget* parent) filters->updated().connect(this, &Tracks::refresh); } +std::vector +Tracks::getTracks(int offset, int size, bool& moreResults) +{ + auto searchKeywords = splitString(_search->text().toUTF8(), " "); + auto clusterIds = _filters->getClusterIds(); + + Wt::Dbo::Transaction transaction(DboSession()); + + return Track::getByFilter(DboSession(), clusterIds, searchKeywords, offset, size, moreResults); +} + +std::vector +Tracks::getTracks() +{ + bool moreResults; + return getTracks(-1, -1, moreResults); +} void Tracks::refresh() @@ -75,14 +106,10 @@ Tracks::refresh() void Tracks::addSome() { - auto searchKeywords = splitString(_search->text().toUTF8(), " "); - - auto clusterIds = _filters->getClusterIds(); - Wt::Dbo::Transaction transaction(DboSession()); bool moreResults; - auto tracks = Track::getByFilter(DboSession(), clusterIds, searchKeywords, _tracksContainer->count(), 20, moreResults); + auto tracks = getTracks(_tracksContainer->count(), 20, moreResults); for (auto track : tracks) { @@ -105,7 +132,6 @@ Tracks::addSome() if (release) { entry->setCondition("if-has-release", true); - // TODO anchor Wt::WAnchor *releaseAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/release/" + std::to_string(track->getRelease().id()))); Wt::WText *releaseText = new Wt::WText(releaseAnchor); releaseText->setText(Wt::WString::fromUTF8(release->getName(), Wt::PlainText)); diff --git a/src/ui/TracksView.hpp b/src/ui/TracksView.hpp index 3f949770..9b7e1c5c 100644 --- a/src/ui/TracksView.hpp +++ b/src/ui/TracksView.hpp @@ -34,10 +34,16 @@ class Tracks : public Wt::WContainerWidget Wt::Signal trackAdd; Wt::Signal trackPlay; + Wt::Signal> tracksAdd; + Wt::Signal> tracksPlay; + private: void refresh(); void addSome(); + std::vector getTracks(int offset, int size, bool& moreResults); + std::vector getTracks(); + Wt::WContainerWidget* _tracksContainer; Wt::WTemplate* _showMore; Wt::WLineEdit* _search; From e9da3adbf6f2a619ad219595868b9881ee51ab2b Mon Sep 17 00:00:00 2001 From: emeric Date: Sun, 25 Feb 2018 12:14:34 +0100 Subject: [PATCH 053/195] Added artist tag cloud --- approot/artist.xml | 5 +++++ src/database/DbArtist.cpp | 21 ++++++++++++++++++++- src/database/DbArtist.hpp | 5 ++++- src/ui/ArtistView.cpp | 14 ++++++++++++++ 4 files changed, 43 insertions(+), 2 deletions(-) diff --git a/approot/artist.xml b/approot/artist.xml index 434e056b..070c46c7 100644 --- a/approot/artist.xml +++ b/approot/artist.xml @@ -13,11 +13,16 @@ ${releases class="list-group"} + + ${name} + +
diff --git a/src/database/DbArtist.cpp b/src/database/DbArtist.cpp index 32654f5e..bd871e7f 100644 --- a/src/database/DbArtist.cpp +++ b/src/database/DbArtist.cpp @@ -137,7 +137,7 @@ Artist::getByFilter(Wt::Dbo::Session& session, return res; } -std::vector > +std::vector> Artist::getReleases(const std::set& clusterIds) const { assert(self()); @@ -183,6 +183,25 @@ Artist::getReleases(const std::set& clusterIds) const return std::vector< Wt::Dbo::ptr > (res.begin(), res.end()); } +std::vector> +Artist::getClusters(int size) const +{ + assert(self()); + assert(self()->id() != Wt::Dbo::dbo_traits::invalidId() ); + assert(session()); + + Wt::Dbo::Query query = session()->query + ("select c from cluster c INNER JOIN track t ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN artist a ON t.artist_id = a.id") + .where("a.id = ?").bind(self()->id()) + .groupBy("c.id") + .orderBy("COUNT(c.id) DESC") + .limit(size); + + Wt::Dbo::collection res = query; + + return std::vector(res.begin(), res.end()); +} + } // namespace Database diff --git a/src/database/DbArtist.hpp b/src/database/DbArtist.hpp index a22360dc..6ee5b283 100644 --- a/src/database/DbArtist.hpp +++ b/src/database/DbArtist.hpp @@ -63,9 +63,12 @@ class Artist : public Wt::Dbo::Dbo std::string getName(void) const { return _name; } std::string getMBID(void) const { return _MBID; } - // Get the releases that have at least one track for this artist + belongs to optonal cluster filters + // Get the releases that have at least one track for this artist that belongs to optional cluster filters std::vector> getReleases(const std::set& clusterIds = std::set()) const; + // Get the cluster of the tracks made by this artist + std::vector> getClusters(int size) const; + void setMBID(std::string mbid) { _MBID = mbid; } // Create diff --git a/src/ui/ArtistView.cpp b/src/ui/ArtistView.cpp index fc529a4f..6b770af0 100644 --- a/src/ui/ArtistView.cpp +++ b/src/ui/ArtistView.cpp @@ -74,6 +74,20 @@ Artist::refresh() auto t = new Wt::WTemplate(Wt::WString::tr("template-artist"), this); t->addFunction("tr", &Wt::WTemplate::Functions::tr); + auto clusterContainers = new Wt::WContainerWidget(); + t->bindWidget("tags", clusterContainers); + + { + auto clusters = artist->getClusters(3); + + for (auto cluster : clusters) + { + auto entry = new Wt::WTemplate(Wt::WString::tr("template-artist-tag-entry"), clusterContainers); + + entry->bindString("name", Wt::WString::fromUTF8(cluster->getName()), Wt::PlainText); + } + } + t->bindString("name", Wt::WString::fromUTF8(artist->getName()), Wt::PlainText); { auto playBtn = new Wt::WText(Wt::WString::tr("btn-artist-play-btn"), Wt::XHTMLText); From d6f0e34b622c5ceeec999b1515fe9544d0a2d69e Mon Sep 17 00:00:00 2001 From: emeric Date: Sun, 25 Feb 2018 12:27:16 +0100 Subject: [PATCH 054/195] Added release tag cloud --- approot/artist.xml | 4 ++-- approot/release.xml | 5 +++++ src/database/DbArtist.cpp | 2 -- src/database/Release.cpp | 19 +++++++++++++++++++ src/database/Release.hpp | 1 + src/ui/ArtistView.cpp | 4 ++-- src/ui/ReleaseView.cpp | 14 ++++++++++++++ 7 files changed, 43 insertions(+), 6 deletions(-) diff --git a/approot/artist.xml b/approot/artist.xml index 070c46c7..b3252bf0 100644 --- a/approot/artist.xml +++ b/approot/artist.xml @@ -13,13 +13,13 @@ ${releases class="list-group"} - + ${name} diff --git a/approot/release.xml b/approot/release.xml index 68def396..dcefa70c 100644 --- a/approot/release.xml +++ b/approot/release.xml @@ -16,11 +16,16 @@

${name}

${artist-name}

${}${year}${}${} (${orig-year})${}

+ ${clusters} ${play-btn}${add-btn}
${tracks} + + ${name} + + ${}${disc-number}-${}${}${track-number}. ${} ${}${artist-name} - ${} diff --git a/src/database/DbArtist.cpp b/src/database/DbArtist.cpp index bd871e7f..ec08bc52 100644 --- a/src/database/DbArtist.cpp +++ b/src/database/DbArtist.cpp @@ -202,6 +202,4 @@ Artist::getClusters(int size) const return std::vector(res.begin(), res.end()); } - - } // namespace Database diff --git a/src/database/Release.cpp b/src/database/Release.cpp index 134db031..73c65bdf 100644 --- a/src/database/Release.cpp +++ b/src/database/Release.cpp @@ -225,4 +225,23 @@ Release::getTracks(const std::set& clusterIds) const return std::vector< Wt::Dbo::ptr > (res.begin(), res.end()); } +std::vector> +Release::getClusters(int size) const +{ + assert(self()); + assert(self()->id() != Wt::Dbo::dbo_traits::invalidId() ); + assert(session()); + + Wt::Dbo::Query query = session()->query + ("select c from cluster c INNER JOIN track t ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN release r ON t.release_id = r.id") + .where("r.id = ?").bind(self()->id()) + .groupBy("c.id") + .orderBy("COUNT(c.id) DESC") + .limit(size); + + Wt::Dbo::collection res = query; + + return std::vector(res.begin(), res.end()); +} + } // namespace Database diff --git a/src/database/Release.hpp b/src/database/Release.hpp index b9d8acac..7e46795e 100644 --- a/src/database/Release.hpp +++ b/src/database/Release.hpp @@ -57,6 +57,7 @@ class Release : public Wt::Dbo::Dbo bool& moreExpected); std::vector> getTracks(const std::set& clusters = std::set()) const; + std::vector> getClusters(int size) const; // Create static pointer create(Wt::Dbo::Session& session, const std::string& name, const std::string& MBID = ""); diff --git a/src/ui/ArtistView.cpp b/src/ui/ArtistView.cpp index 6b770af0..eefc62a6 100644 --- a/src/ui/ArtistView.cpp +++ b/src/ui/ArtistView.cpp @@ -75,14 +75,14 @@ Artist::refresh() t->addFunction("tr", &Wt::WTemplate::Functions::tr); auto clusterContainers = new Wt::WContainerWidget(); - t->bindWidget("tags", clusterContainers); + t->bindWidget("clusters", clusterContainers); { auto clusters = artist->getClusters(3); for (auto cluster : clusters) { - auto entry = new Wt::WTemplate(Wt::WString::tr("template-artist-tag-entry"), clusterContainers); + auto entry = new Wt::WTemplate(Wt::WString::tr("template-artist-cluster-entry"), clusterContainers); entry->bindString("name", Wt::WString::fromUTF8(cluster->getName()), Wt::PlainText); } diff --git a/src/ui/ReleaseView.cpp b/src/ui/ReleaseView.cpp index bd224e47..8493da75 100644 --- a/src/ui/ReleaseView.cpp +++ b/src/ui/ReleaseView.cpp @@ -110,6 +110,20 @@ Release::refresh() cover->setImageLink(Wt::WLink (SessionImageResource()->getReleaseUrl(release.id(), 512))); t->bindWidget("cover", cover); + auto clusterContainers = new Wt::WContainerWidget(); + t->bindWidget("clusters", clusterContainers); + + { + auto clusters = release->getClusters(3); + + for (auto cluster : clusters) + { + auto entry = new Wt::WTemplate(Wt::WString::tr("template-release-cluster-entry"), clusterContainers); + + entry->bindString("name", Wt::WString::fromUTF8(cluster->getName()), Wt::PlainText); + } + } + { auto playBtn = new Wt::WText(Wt::WString::tr("btn-release-play-btn"), Wt::XHTMLText); t->bindWidget("play-btn", playBtn); From 9cf89893e0d7e40b19b1bb433e1a07c655da976d Mon Sep 17 00:00:00 2001 From: emeric Date: Thu, 1 Mar 2018 22:20:53 +0100 Subject: [PATCH 055/195] Prepare multi cluster tag parsing --- TODO | 4 + src/Makefile.am | 4 +- src/database/DatabaseHandler.cpp | 25 +- src/database/DatabaseHandler.hpp | 1 + src/database/MediaDirectory.cpp | 23 +- src/database/MediaDirectory.hpp | 16 +- src/database/Track.cpp | 97 +++--- src/database/Track.hpp | 81 +++-- .../updater/DatabaseHighLevelCluster.cpp | 12 +- src/main/main.cpp | 19 +- src/metadata/AvFormat.cpp | 149 ++++----- src/metadata/AvFormat.hpp | 20 +- src/metadata/MetaData.hpp | 26 +- src/metadata/TagLibParser.cpp | 64 ++-- src/metadata/TagLibParser.hpp | 16 +- .../MediaScanner.cpp} | 312 ++++++++---------- .../MediaScanner.hpp} | 110 ++---- src/ui/Filters.cpp | 19 +- test/Makefile.am | 4 +- test/TestAvMetadata.cpp | 24 +- 20 files changed, 504 insertions(+), 522 deletions(-) rename src/{database/updater/DatabaseUpdater.cpp => scanner/MediaScanner.cpp} (59%) rename src/{database/updater/DatabaseUpdater.hpp => scanner/MediaScanner.hpp} (50%) diff --git a/TODO b/TODO index 15b32001..d55ee917 100644 --- a/TODO +++ b/TODO @@ -12,6 +12,8 @@ - handle access rights problems (instead of aborting) - add a global play counter for tracks. This will help people to spot most popular files - Use the WServer::post method to notify the end of the database scan? (with results?) +- Make clusters based on different metadata (albumgrouping, genre, mood, etc.). Make it configurable somewhere as it is very difficult to handle them all? +- Use albumartist when available (useful for compilations) [Metadata] - WMA covers: add support @@ -31,6 +33,8 @@ - Add a hint for the user to get the nature of tag (created from genre ? created from audio features?) - Implement a play queue - Implement a decent player + - Tags cloud for artist/release + - Dedicate a color for each tag type [REST API] - Make a dedicated REST API. Maybe use the SubSonic API or Ampache API? diff --git a/src/Makefile.am b/src/Makefile.am index 54f36b85..7842e2dd 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -15,14 +15,12 @@ lms_SOURCES = \ $(srcdir)/database/SqlQuery.cpp \ $(srcdir)/database/Track.cpp \ $(srcdir)/database/User.cpp \ - $(srcdir)/database/updater/DatabaseUpdater.cpp \ - $(srcdir)/database/updater/DatabaseFeatureExtractor.cpp \ - $(srcdir)/database/updater/DatabaseHighLevelCluster.cpp \ $(srcdir)/feature/FeatureExtractor.cpp \ $(srcdir)/feature/FeatureStore.cpp \ $(srcdir)/image/Image.cpp \ $(srcdir)/metadata/AvFormat.cpp \ $(srcdir)/metadata/TagLibParser.cpp \ + $(srcdir)/scanner/MediaScanner.cpp \ $(srcdir)/ui/ArtistView.cpp \ $(srcdir)/ui/ArtistsView.cpp \ $(srcdir)/ui/Explore.cpp \ diff --git a/src/database/DatabaseHandler.cpp b/src/database/DatabaseHandler.cpp index 85164449..c063bbf0 100644 --- a/src/database/DatabaseHandler.cpp +++ b/src/database/DatabaseHandler.cpp @@ -17,6 +17,8 @@ * along with LMS. If not, see . */ +#include + #include #include @@ -83,22 +85,25 @@ Handler::Handler(Wt::Dbo::SqlConnectionPool& connectionPool) _session.mapClass("artist"); _session.mapClass("cluster"); - _session.mapClass("track"); + _session.mapClass("cluster_type"); + _session.mapClass("media_directory"); _session.mapClass("playlist"); _session.mapClass("playlist_entry"); _session.mapClass("release"); - _session.mapClass("media_directory"); _session.mapClass("setting"); + _session.mapClass("track"); - _session.mapClass("user"); _session.mapClass("auth_info"); _session.mapClass("auth_identity"); _session.mapClass("auth_token"); + _session.mapClass("user"); try { Wt::Dbo::Transaction transaction(_session); _session.createTables(); + + LMS_LOG(DB, INFO) << "Tables created"; } catch(std::exception& e) { LMS_LOG(DB, ERROR) << "Cannot create tables: " << e.what(); @@ -108,21 +113,17 @@ Handler::Handler(Wt::Dbo::SqlConnectionPool& connectionPool) Wt::Dbo::Transaction transaction(_session); // Indexes - _session.execute("PRAGMA journal_mode=WAL"); + // _session.execute("PRAGMA journal_mode=WAL"); _session.execute("CREATE INDEX IF NOT EXISTS track_path_idx ON track(file_path)"); _session.execute("CREATE INDEX IF NOT EXISTS artist_name_idx ON artist(name)"); _session.execute("CREATE INDEX IF NOT EXISTS release_name_idx ON release(name)"); _session.execute("CREATE INDEX IF NOT EXISTS track_artist_idx ON track(artist_id)"); _session.execute("CREATE INDEX IF NOT EXISTS track_release_idx ON track(release_id)"); - _session.execute("CREATE INDEX IF NOT EXISTS cluster_type_idx ON cluster(type)"); + _session.execute("CREATE INDEX IF NOT EXISTS cluster_name_idx ON cluster(name)"); + _session.execute("CREATE INDEX IF NOT EXISTS cluster_type_name_idx ON cluster_type(name)"); + // TODO move this // Default values - if (!Setting::exists(_session, "audio_file_extensions")) - Setting::setString(_session, "audio_file_extensions", ".mp3 .ogg .oga .aac .m4a .flac .wav .wma .aif .aiff .ape .mpc .shn" ); - - if (!Setting::exists(_session, "video_file_extensions")) - Setting::setString(_session, "video_file_extensions", ".flv .avi .mpg .mpeg .mp4 .m4v .mkv .mov .wmv .ogv .divx .m2ts"); - if (!Setting::exists(_session, "tags_highlevel_acousticbrainz")) Setting::setBool(_session, "tags_highlevel_acousticbrainz", true); @@ -184,7 +185,7 @@ Handler::createConnectionPool(boost::filesystem::path p) Wt::Dbo::backend::Sqlite3 *connection = new Wt::Dbo::backend::Sqlite3(p.string()); - connection->executeSql("pragma journal_mode=WAL"); +// connection->executeSql("pragma journal_mode=WAL"); connection->setProperty("show-queries", "true"); return new Wt::Dbo::FixedSqlConnectionPool(connection, 1); diff --git a/src/database/DatabaseHandler.hpp b/src/database/DatabaseHandler.hpp index 6b07984e..28f5600a 100644 --- a/src/database/DatabaseHandler.hpp +++ b/src/database/DatabaseHandler.hpp @@ -21,6 +21,7 @@ #define DATABASE_HANDLER_HPP #include +#include #include #include diff --git a/src/database/MediaDirectory.cpp b/src/database/MediaDirectory.cpp index cd2a0cc2..88b30e88 100644 --- a/src/database/MediaDirectory.cpp +++ b/src/database/MediaDirectory.cpp @@ -23,16 +23,15 @@ namespace Database { -MediaDirectory::MediaDirectory(boost::filesystem::path p, Type type) -: _type(type), - _path(stringTrimEnd(p.string(), "/\\")) +MediaDirectory::MediaDirectory(boost::filesystem::path p) +: _path(stringTrimEnd(p.string(), "/\\")) { } MediaDirectory::pointer -MediaDirectory::create(Wt::Dbo::Session& session, boost::filesystem::path p, Type type) +MediaDirectory::create(Wt::Dbo::Session& session, boost::filesystem::path p) { - return session.add( new MediaDirectory( p, type ) ); + return session.add( new MediaDirectory(p) ); } void @@ -50,20 +49,6 @@ MediaDirectory::getAll(Wt::Dbo::Session& session) return std::vector(res.begin(), res.end()); } -std::vector -MediaDirectory::getByType(Wt::Dbo::Session& session, Type type) -{ - Wt::Dbo::collection< MediaDirectory::pointer > res = session.find().where("type = ?").bind (type); - - return std::vector(res.begin(), res.end()); -} - -MediaDirectory::pointer -MediaDirectory::get(Wt::Dbo::Session& session, boost::filesystem::path p, Type type) -{ - return session.find().where("path = ?").where("type = ?").bind( p.string()).bind(type); -} - boost::filesystem::path MediaDirectory::getPath(void) const { diff --git a/src/database/MediaDirectory.hpp b/src/database/MediaDirectory.hpp index e00892dd..3bc2e497 100644 --- a/src/database/MediaDirectory.hpp +++ b/src/database/MediaDirectory.hpp @@ -31,39 +31,29 @@ namespace Database { class MediaDirectory { public: - typedef Wt::Dbo::ptr pointer; - enum Type { - Audio = 1, - }; - MediaDirectory() {} - MediaDirectory(boost::filesystem::path p, Type type); + MediaDirectory(boost::filesystem::path p); // Accessors - static pointer create(Wt::Dbo::Session& session, boost::filesystem::path p, Type type); + static pointer create(Wt::Dbo::Session& session, boost::filesystem::path p); static std::vector getAll(Wt::Dbo::Session& session); - static std::vector 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); + static void eraseByPath(Wt::Dbo::Session& session, boost::filesystem::path p); - Type getType(void) const { return _type; } boost::filesystem::path getPath(void) const; template void persist(Action& a) { - Wt::Dbo::field(a, _type, "type"); Wt::Dbo::field(a, _path, "path"); } private: - Type _type; std::string _path; - }; } // namespace Database diff --git a/src/database/Track.cpp b/src/database/Track.cpp index 5853a293..d913980f 100644 --- a/src/database/Track.cpp +++ b/src/database/Track.cpp @@ -224,44 +224,24 @@ Cluster::Cluster() { } -Cluster::Cluster(std::string type, std::string name) -: -_type( std::string(type, 0, _maxTypeLength)), -_name( std::string(name, 0, _maxNameLength)) +Cluster::Cluster(Wt::Dbo::ptr type, std::string name) + : _name(std::string(name, 0, _maxNameLength)), + _clusterType(type) { } -Wt::Dbo::collection -Cluster::getAll(Wt::Dbo::Session& session) -{ - return session.find(); -} - Cluster::pointer -Cluster::get(Wt::Dbo::Session& session, std::string type, std::string name) -{ - // TODO use like search - return session.find().where("type = ?").where("name = ?").bind( std::string(type, 0, _maxTypeLength)).bind( std::string(name, 0, _maxNameLength)); -} - -std::vector -Cluster::getByType(Wt::Dbo::Session& session, std::string type) -{ - Wt::Dbo::collection res = session.find().where("type = ?").bind( std::string(type, 0, _maxTypeLength)).orderBy("name"); - return std::vector(res.begin(), res.end()); -} - -Cluster::pointer -Cluster::create(Wt::Dbo::Session& session, std::string type, std::string name) +Cluster::create(Wt::Dbo::Session& session, Wt::Dbo::ptr type, std::string name) { return session.add(new Cluster(type, name)); } -void -Cluster::remove(Wt::Dbo::Session& session, std::string type) +std::vector +Cluster::getAll(Wt::Dbo::Session& session) { - Wt::Dbo::Transaction transaction(session); - session.execute("DELETE FROM cluster WHERE type = ?").bind(type); + Wt::Dbo::collection res = session.find(); + + return std::vector(res.begin(), res.end()); } Wt::Dbo::Query @@ -278,15 +258,6 @@ Cluster::getQuery(Wt::Dbo::Session& session, SearchFilter filter) return query; } -std::vector -Cluster::getAllTypes(Wt::Dbo::Session& session) -{ - Wt::Dbo::collection res - = session.query("SELECT type from cluster").groupBy("type").orderBy("type"); - - return std::vector(res.begin(), res.end()); -} - std::vector Cluster::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size) { @@ -295,6 +266,56 @@ Cluster::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, return std::vector(res.begin(), res.end()); } +ClusterType::ClusterType(std::string name) + : _name(name) +{ +} + +ClusterType::pointer +ClusterType::getByName(Wt::Dbo::Session& session, std::string name) +{ + return session.find().where("name = ?").bind(name); +} + +std::vector +ClusterType::getAll(Wt::Dbo::Session& session) +{ + Wt::Dbo::collection res = session.find(); + + return std::vector(res.begin(), res.end()); +} + +ClusterType::pointer +ClusterType::create(Wt::Dbo::Session& session, std::string name) +{ + return session.add(new ClusterType(name)); +} + +Cluster::pointer +ClusterType::getCluster(std::string name) const +{ + assert(self()); + assert(self()->id() != Wt::Dbo::dbo_traits::invalidId() ); + assert(session()); + + return session()->find() + .where("name = ?").bind(name) + .where("cluster_type_id = ").bind(self()->id()); +} + +std::vector +ClusterType::getClusters() const +{ + assert(self()); + assert(self()->id() != Wt::Dbo::dbo_traits::invalidId() ); + assert(session()); + + Wt::Dbo::collection res = session()->find() + .where("cluster_type_id = ").bind(self()->id()) + .orderBy("name"); + + return std::vector(res.begin(), res.end()); +} } // namespace Database diff --git a/src/database/Track.hpp b/src/database/Track.hpp index 7c0f1bc2..878ef796 100644 --- a/src/database/Track.hpp +++ b/src/database/Track.hpp @@ -40,64 +40,89 @@ class Artist; class Release; class Track; class PlaylistEntry; +class ClusterType; -class Cluster +class Cluster : public Wt::Dbo::Dbo { public: - - enum class Type - { - Genre = 1, - Mood = 2, - }; - typedef Wt::Dbo::ptr pointer; typedef Wt::Dbo::dbo_traits::IdType id_type; Cluster(); - Cluster(std::string type, std::string name); + Cluster(Wt::Dbo::ptr type, std::string name); // Find utility - static pointer get(Wt::Dbo::Session& session, std::string type, std::string name); static std::vector getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1); - static Wt::Dbo::collection getAll(Wt::Dbo::Session& session); - static std::vector getAllTypes(Wt::Dbo::Session& session); - static std::vector getByType(Wt::Dbo::Session& session, std::string type); + static std::vector getAll(Wt::Dbo::Session& session); // Create utility - static pointer create(Wt::Dbo::Session& session, std::string type, std::string name); - - // Remove utility - static void remove(Wt::Dbo::Session& session, std::string type); // nested transaction + static pointer create(Wt::Dbo::Session& session, Wt::Dbo::ptr type, std::string name); // Accessors const std::string& getName(void) const { return _name; } - const std::string& getType(void) const { return _type; } - const Wt::Dbo::collection< Wt::Dbo::ptr >& getTracks() const { return _tracks;} + Wt::Dbo::ptr getType() const { return _clusterType; } + const Wt::Dbo::collection>& getTracks() const { return _tracks; } - void addTrack(Wt::Dbo::Session& session, Wt::Dbo::dbo_traits::IdType trackId); void addTrack(Wt::Dbo::ptr track) { _tracks.insert(track); } template - void persist(Action& a) - { - Wt::Dbo::field(a, _name, "name"); - Wt::Dbo::field(a, _type, "type"); - Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToMany, "track_cluster", "", Wt::Dbo::OnDeleteCascade); - } + void persist(Action& a) + { + Wt::Dbo::field(a, _name, "name"); + + Wt::Dbo::belongsTo(a, _clusterType, "cluster_type", Wt::Dbo::OnDeleteCascade); + Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToMany, "track_cluster", "", Wt::Dbo::OnDeleteCascade); + } private: static Wt::Dbo::Query getQuery(Wt::Dbo::Session& session, SearchFilter filter); static const std::size_t _maxNameLength = 128; - static const std::size_t _maxTypeLength = 128; - std::string _type; std::string _name; + Wt::Dbo::ptr _clusterType; Wt::Dbo::collection< Wt::Dbo::ptr > _tracks; }; + +class ClusterType : public Wt::Dbo::Dbo +{ + public: + + using pointer = Wt::Dbo::ptr; + using id_type = Wt::Dbo::dbo_traits::IdType; + + ClusterType() {} + ClusterType(std::string name); + + static pointer getByName(Wt::Dbo::Session& session, std::string name); + static std::vector getAll(Wt::Dbo::Session& session); + + static pointer create(Wt::Dbo::Session& session, std::string name); + static void remove(Wt::Dbo::Session& session, std::string name); + + // Accessors + const std::string& getName(void) const { return _name; } + std::vector getClusters() const; + Cluster::pointer getCluster(std::string name) const; + + template + void persist(Action& a) + { + Wt::Dbo::field(a, _name, "name"); + Wt::Dbo::hasMany(a, _clusters, Wt::Dbo::ManyToOne, "cluster_type"); + } + + private: + + static const std::size_t _maxNameLength = 128; + + std::string _name; + Wt::Dbo::collection< Wt::Dbo::ptr > _clusters; +}; + + class Track { public: diff --git a/src/database/updater/DatabaseHighLevelCluster.cpp b/src/database/updater/DatabaseHighLevelCluster.cpp index 60d54918..5cc37607 100644 --- a/src/database/updater/DatabaseHighLevelCluster.cpp +++ b/src/database/updater/DatabaseHighLevelCluster.cpp @@ -26,11 +26,15 @@ namespace Database { -static Cluster::pointer getCluster(std::string type, std::string value) +static Cluster::pointer getCluster(std::string type, std::string name) { - Cluster::pointer cluster = ( Cluster::get(UpdaterDboSession(), type, value) ); + ClusterType::pointer clusterType = ClusterType::getByName(UpdaterDboSession(), type); + if (!clusterType) + clusterType = ClusterType::create(UpdaterDboSession(), type); + + auto cluster = clusterType->getCluster(name); if (!cluster) - cluster = Cluster::create(UpdaterDboSession(), type, value); + cluster = Cluster::create(UpdaterDboSession(), clusterType, name); return cluster; } @@ -196,7 +200,7 @@ HighLevelCluster::handleFilesUpdated(void) for (auto cluster : clusters) { // Check if removed - if (cluster->getType() != "high_level") + if (cluster->getType()->getName() != "high_level") continue; auto it = std::find(newClusterNames.begin(), newClusterNames.end(), cluster->getName()); diff --git a/src/main/main.cpp b/src/main/main.cpp index bcd80006..c2b24416 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -29,9 +29,9 @@ #include "image/Image.hpp" #include "feature/FeatureExtractor.hpp" -#include "database/updater/DatabaseUpdater.hpp" -#include "database/updater/DatabaseFeatureExtractor.hpp" -#include "database/updater/DatabaseHighLevelCluster.hpp" +#include "scanner/MediaScanner.hpp" +//#include "database/updater/DatabaseFeatureExtractor.hpp" +//#include "database/updater/DatabaseHighLevelCluster.hpp" #include "ui/LmsApplication.hpp" @@ -125,20 +125,19 @@ int main(int argc, char* argv[]) std::unique_ptr connectionPool( Database::Handler::createConnectionPool(Config::instance().getPath("working-dir") / "lms.db")); - Database::Updater& dbUpdater = Database::Updater::instance(); - dbUpdater.setConnectionPool(*connectionPool); + Scanner::MediaScanner scanner(*connectionPool); // Instanciate the updater's event handler. Order is important - dbUpdater.registerEventHandler(std::make_shared()); - dbUpdater.registerEventHandler(std::make_shared()); +// dbUpdater.registerEventHandler(std::make_shared()); +// dbUpdater.registerEventHandler(std::make_shared()); // bind entry point server.addEntryPoint(Wt::Application, boost::bind(UserInterface::LmsApplication::create, _1, boost::ref(*connectionPool))); // Start - LMS_LOG(MAIN, INFO) << "Starting database updater..."; - dbUpdater.start(); + LMS_LOG(MAIN, INFO) << "Starting Media scanner..."; + scanner.start(); LMS_LOG(MAIN, INFO) << "Starting server..."; server.start(); @@ -152,7 +151,7 @@ int main(int argc, char* argv[]) server.stop(); LMS_LOG(MAIN, INFO) << "Stopping database updater..."; - dbUpdater.stop(); + scanner.stop(); res = EXIT_SUCCESS; } diff --git a/src/metadata/AvFormat.cpp b/src/metadata/AvFormat.cpp index da5fc3bb..9b13a731 100644 --- a/src/metadata/AvFormat.cpp +++ b/src/metadata/AvFormat.cpp @@ -30,19 +30,23 @@ namespace MetaData { -bool -AvFormat::parse(const boost::filesystem::path& p, Items& items) +AvFormat::AvFormat(const std::map& clusterMap) +: _clusterMap(clusterMap) { +} + +boost::optional +AvFormat::parse(const boost::filesystem::path& p) +{ + Items items; Av::MediaFile mediaFile(p); if (!mediaFile.open()) - return false; + return boost::none; if (!mediaFile.scan()) - return false; - - std::map metadata = mediaFile.getMetaData(); + return boost::none; // Stream info { @@ -63,41 +67,6 @@ AvFormat::parse(const boost::filesystem::path& p, Items& items) items.insert( std::make_pair(MetaData::Type::AudioStreams, audioStreams)); } - { - std::vector videoStreams; - - std::vector streams = mediaFile.getStreams(Av::Stream::Type::Video); - - for (Av::Stream& stream : streams) - { - VideoStream videoStream; - videoStream.desc = stream.desc; - videoStream.bitRate = stream.bitrate; - - videoStreams.push_back(videoStream); - } - - if (!videoStreams.empty()) - items.insert( std::make_pair(MetaData::Type::VideoStreams, videoStreams)); - } - - { - std::vector subtitleStreams; - - std::vector streams = mediaFile.getStreams(Av::Stream::Type::Subtitle); - - for (Av::Stream& stream : streams) - { - SubtitleStream subtitleStream; - subtitleStream.desc = stream.desc; - - subtitleStreams.push_back(subtitleStream); - } - - if (!subtitleStreams.empty()) - items.insert( std::make_pair(MetaData::Type::SubtitleStreams, subtitleStreams)); - } - // Duration items.insert( std::make_pair(MetaData::Type::Duration, mediaFile.getDuration() )); @@ -106,19 +75,27 @@ AvFormat::parse(const boost::filesystem::path& p, Items& items) // Embedded MetaData // Make sure to convert strings into UTF-8 - std::map::const_iterator it; - for (it = metadata.begin(); it != metadata.end(); ++it) + + MetaData::Clusters clusters; + + std::map metadataMap = mediaFile.getMetaData(); + for (auto metadata : metadataMap) { - if (boost::iequals(it->first, "artist")) - items.insert( std::make_pair(MetaData::Type::Artist, stringTrim( stringToUTF8(it->second)) )); - else if (boost::iequals(it->first, "album")) - items.insert( std::make_pair(MetaData::Type::Album, stringTrim( stringToUTF8(it->second)) )); - else if (boost::iequals(it->first, "title")) - items.insert( std::make_pair(MetaData::Type::Title, stringTrim( stringToUTF8(it->second)) )); - else if (boost::iequals(it->first, "track")) + const std::string tag = boost::to_upper_copy(metadata.first); + const std::string value = metadata.second; +#if 0 + std::cout << "TAG = " << tag << ", VAL = " << value << std::endl; +#endif + if (tag == "ARTIST") + items.insert( std::make_pair(MetaData::Type::Artist, stringTrim( stringToUTF8(value)) )); + else if (tag == "ALBUM") + items.insert( std::make_pair(MetaData::Type::Album, stringTrim( stringToUTF8(value)) )); + else if (tag == "TITLE") + items.insert( std::make_pair(MetaData::Type::Title, stringTrim( stringToUTF8(value)) )); + else if (tag == "TRACK") { // Expecting 'Number/Total' - auto strings = splitString(it->second, "/"); + auto strings = splitString(value, "/"); if (strings.size() > 0) { @@ -134,10 +111,10 @@ AvFormat::parse(const boost::filesystem::path& p, Items& items) } } } - else if (boost::iequals(it->first, "disc")) + else if (tag == "DISC") { // Expecting 'Number/Total' - auto strings = splitString(it->second, "/"); + auto strings = splitString(value, "/"); if (strings.size() > 0) { @@ -153,49 +130,57 @@ AvFormat::parse(const boost::filesystem::path& p, Items& items) } } } - else if (boost::iequals(it->first, "date") - || boost::iequals(it->first, "year") - || boost::iequals(it->first, "WM/Year")) + else if (tag == "DATE" + || tag == "YEAR" + || tag == "WM/Year") { boost::posix_time::ptime p; - if (readAsPosixTime(it->second, p)) + if (readAsPosixTime(value, p)) items.insert( std::make_pair(MetaData::Type::Date, p)); } - else if (boost::iequals(it->first, "TDOR") // Original release time (ID3v2 2.4) - || boost::iequals(it->first, "TORY")) // Original release year + else if (tag == "TDOR" // Original release time (ID3v2 2.4) + || tag == "TORY") // Original release year { boost::posix_time::ptime p; - if (readAsPosixTime(it->second, p)) + if (readAsPosixTime(value, p)) items.insert( std::make_pair(MetaData::Type::OriginalDate, p)); } - else if (boost::iequals(it->first, "genre")) + else if (tag == "MUSICBRAINZ ARTIST ID" + || tag == "MUSICBRAINZ_ARTISTID") { - // TODO use splitStrings - std::list genres; - if (readList(it->second, ";,\\", genres)) - items.insert( std::make_pair(MetaData::Type::Genres, genres)); + items.insert( std::make_pair(MetaData::Type::MusicBrainzArtistID, stringTrim( stringToUTF8(value)) )); + } + else if (tag == "MUSICBRAINZ ALBUM ID" + || tag == "MUSICBRAINZ_ALBUMID") + { + items.insert( std::make_pair(MetaData::Type::MusicBrainzAlbumID, stringTrim( stringToUTF8(value)) )); + } + else if (tag == "MUSICBRAINZ RELEASE TRACK ID" + || tag == "MUSICBRAINZ_RELEASETRACKID" + || tag == "MUSICBRAINZ_TRACKID") + { + items.insert( std::make_pair(MetaData::Type::MusicBrainzTrackID, stringTrim( stringToUTF8(value)) )); + } + else if (tag == "ACOUSTID ID") + { + items.insert( std::make_pair(MetaData::Type::AcoustID, stringTrim( stringToUTF8(value)) )); + } + else if (_clusterMap.find(tag) != _clusterMap.end()) + { + std::vector clusterNames = splitString(value, ";,\\"); - } - else if (boost::iequals(it->first, "MusicBrainz Artist Id") - || boost::iequals(it->first, "MUSICBRAINZ_ARTISTID")) - { - items.insert( std::make_pair(MetaData::Type::MusicBrainzArtistID, stringTrim( stringToUTF8(it->second)) )); - } - else if (boost::iequals(it->first, "MusicBrainz Album Id") - || boost::iequals(it->first, "MUSICBRAINZ_ALBUMID")) - { - items.insert( std::make_pair(MetaData::Type::MusicBrainzAlbumID, stringTrim( stringToUTF8(it->second)) )); - } - else if (boost::iequals(it->first, "MusicBrainz Release Track Id") - || boost::iequals(it->first, "MUSICBRAINZ_RELEASETRACKID") - || boost::iequals(it->first, "MUSICBRAINZ_TRACKID")) - { - items.insert( std::make_pair(MetaData::Type::MusicBrainzTrackID, stringTrim( stringToUTF8(it->second)) )); + if (!clusterNames.empty()) + { + clusters[_clusterMap[tag]] = std::set(clusterNames.begin(), clusterNames.end()); + } } } - return true; + if (!clusters.empty()) + items.insert( std::make_pair(MetaData::Type::Clusters, clusters) ); + + return items; } } // namespace MetaData diff --git a/src/metadata/AvFormat.hpp b/src/metadata/AvFormat.hpp index 4c08d943..8c3c9504 100644 --- a/src/metadata/AvFormat.hpp +++ b/src/metadata/AvFormat.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Emeric Poupon + * Copyright (C) 2018 Emeric Poupon * * This file is part of LMS. * @@ -17,8 +17,10 @@ * along with LMS. If not, see . */ -#ifndef METADATA_AVFORMAT_HPP -#define METADATA_AVFORMAT_HPP +#pragma once + +#include +#include #include "MetaData.hpp" @@ -30,13 +32,19 @@ class AvFormat : public Parser { public: - bool parse(const boost::filesystem::path& p, Items& items); + AvFormat(const std::map& clusterMap + = { + {"GENRE", "Genre" }, + {"ALBUMGROUPING", "Group" } + }); + + + boost::optional parse(const boost::filesystem::path& p); private: + std::map _clusterMap; }; - } // namespace MetaData -#endif diff --git a/src/metadata/MetaData.hpp b/src/metadata/MetaData.hpp index 019184a3..6bbea293 100644 --- a/src/metadata/MetaData.hpp +++ b/src/metadata/MetaData.hpp @@ -21,8 +21,10 @@ #define METADATA_HPP #include +#include #include +#include #include namespace MetaData @@ -30,10 +32,11 @@ namespace MetaData enum class Type { + // Name Type of the value Artist, // string Title, // string Album, // string - Genres, // list + Clusters, // Clusters, ex: { "genre", {"death metal", "brutal death"} }, { "albumgrouping", {"metal"} } Duration, // boost::posix_time::time_duration TrackNumber, // size_t DiscNumber, // size_t @@ -43,12 +46,11 @@ namespace MetaData OriginalDate, // boost::posix_time::ptime HasCover, // bool AudioStreams, // vector - VideoStreams, // vector - SubtitleStreams, // vector MusicBrainzArtistID, // string MusicBrainzAlbumID, // string MusicBrainzTrackID, // string MusicBrainzRecordingID, // string + AcoustID, // string }; // Used by Streams @@ -58,28 +60,16 @@ namespace MetaData std::size_t bitRate; }; - struct VideoStream - { - std::string desc; - std::size_t bitRate; - }; - - struct SubtitleStream - { - std::string desc; - }; - // Type and associated data // See enum Type's comments - typedef std::map Items; + using Items = std::map; + using Clusters = std::map>; class Parser { public: - typedef std::shared_ptr pointer; - - virtual bool parse(const boost::filesystem::path& p, Items& items) = 0; + virtual boost::optional parse(const boost::filesystem::path& p) = 0; }; diff --git a/src/metadata/TagLibParser.cpp b/src/metadata/TagLibParser.cpp index 7afa6140..5508ef46 100644 --- a/src/metadata/TagLibParser.cpp +++ b/src/metadata/TagLibParser.cpp @@ -32,18 +32,25 @@ namespace MetaData { -bool -TagLibParser::parse(const boost::filesystem::path& p, Items& items) +TagLibParser::TagLibParser(const std::map& clusterMap) +: _clusterMap(clusterMap) +{ +} + +boost::optional +TagLibParser::parse(const boost::filesystem::path& p) { TagLib::FileRef f(p.string().c_str(), true, // read audio properties TagLib::AudioProperties::Average); if (f.isNull()) - return false; + return boost::none; if (!f.audioProperties()) - return false; + return boost::none; + + Items items; { TagLib::AudioProperties *properties = f.audioProperties(); @@ -70,32 +77,46 @@ TagLibParser::parse(const boost::filesystem::path& p, Items& items) if (f.tag()) { - TagLib::PropertyMap tags = f.file()->properties(); + MetaData::Clusters clusters; + TagLib::PropertyMap properties = f.file()->properties(); - for(TagLib::PropertyMap::ConstIterator itElem = tags.begin(); itElem != tags.end(); ++itElem) + for(auto property : properties) { - const std::string tag = itElem->first.to8Bit(true); - const TagLib::StringList &values = itElem->second; + const std::string tag = property.first.upper().to8Bit(true); + const TagLib::StringList& values = property.second; if (tag.empty() || values.isEmpty() || values.front().isEmpty()) continue; // TODO validate MBID format +#if 0 + std::cout << "TAG = '" << tag << "'" << std::endl; + for (auto value : values) + { + std::cout << "\t'" << value.to8Bit(true) << "'" << std::endl; + } +#endif + if (tag == "ARTIST") items.insert( std::make_pair(MetaData::Type::Artist, stringTrim( values.front().to8Bit(true)))); else if (tag == "ALBUM") items.insert( std::make_pair(MetaData::Type::Album, stringTrim( values.front().to8Bit(true)))); else if (tag == "TITLE") items.insert( std::make_pair(MetaData::Type::Title, stringTrim( values.front().to8Bit(true)))); - else if (tag == "MUSICBRAINZ_RELEASETRACKID") + else if (tag == "MUSICBRAINZ_RELEASETRACKID" + || tag == "MUSICBRAINZ RELEASE TRACK ID") + { items.insert( std::make_pair(MetaData::Type::MusicBrainzTrackID, stringTrim( values.front().to8Bit(true)))); + } else if (tag == "MUSICBRAINZ_ARTISTID") items.insert( std::make_pair(MetaData::Type::MusicBrainzArtistID, stringTrim( values.front().to8Bit(true)))); else if (tag == "MUSICBRAINZ_ALBUMID") items.insert( std::make_pair(MetaData::Type::MusicBrainzAlbumID, stringTrim( values.front().to8Bit(true)))); else if (tag == "MUSICBRAINZ_TRACKID") - items.insert( std::make_pair(MetaData::Type::MusicBrainzRecordingID, stringTrim( tags["MUSICBRAINZ_TRACKID"].front().to8Bit(true)))); + items.insert( std::make_pair(MetaData::Type::MusicBrainzRecordingID, stringTrim( values.front().to8Bit(true)))); + else if (tag == "ACOUSTID_ID") + items.insert( std::make_pair(MetaData::Type::AcoustID, stringTrim( values.front().to8Bit(true)))); else if (tag == "TRACKNUMBER") { // Expecting 'Number/Total' @@ -160,24 +181,29 @@ TagLibParser::parse(const boost::filesystem::path& p, Items& items) items.insert( std::make_pair(MetaData::Type::OriginalDate, p)); } } - else if (tag == "GENRE") - { - std::list genreList; - for (TagLib::StringList::ConstIterator itGenre = values.begin(); itGenre != values.end(); ++itGenre) - genreList.push_back(itGenre->to8Bit(true)); - - items.insert( std::make_pair(MetaData::Type::Genres, genreList) ); - } else if (tag == "METADATA_BLOCK_PICTURE") { // Only add once if (items.find(MetaData::Type::HasCover) == items.end()) items.insert( std::make_pair(MetaData::Type::HasCover, true)); } + // Check if a hit a cluster tag + else if (_clusterMap.find(tag) != _clusterMap.end()) + { + std::set clusterNames; + for (const auto& value : values) + clusterNames.insert(value.to8Bit(true)); + + if (!clusterNames.empty()) + clusters[_clusterMap[tag]] = clusterNames; + } } + + if (!clusters.empty()) + items.insert( std::make_pair(MetaData::Type::Clusters, clusters) ); } - return true; + return items; } } // namespace MetaData diff --git a/src/metadata/TagLibParser.hpp b/src/metadata/TagLibParser.hpp index a05364aa..baefc112 100644 --- a/src/metadata/TagLibParser.hpp +++ b/src/metadata/TagLibParser.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 Emeric Poupon + * Copyright (C) 2018 Emeric Poupon * * This file is part of LMS. * @@ -19,6 +19,9 @@ #pragma once +#include +#include + #include "MetaData.hpp" namespace MetaData @@ -28,10 +31,19 @@ namespace MetaData class TagLibParser : public Parser { public: - bool parse(const boost::filesystem::path& p, Items& items); + + // Provide a map for TagLib name -> Cluster name + TagLibParser(const std::map& clusterMap + = { + {"GENRE", "Genre" }, + {"ALBUMGROUPING", "Group" } + }); + + boost::optional parse(const boost::filesystem::path& p); private: + std::map _clusterMap; }; } // namespace MetaData diff --git a/src/database/updater/DatabaseUpdater.cpp b/src/scanner/MediaScanner.cpp similarity index 59% rename from src/database/updater/DatabaseUpdater.cpp rename to src/scanner/MediaScanner.cpp index b0484f40..60f19f55 100644 --- a/src/database/updater/DatabaseUpdater.cpp +++ b/src/scanner/MediaScanner.cpp @@ -32,8 +32,7 @@ #include "utils/Path.hpp" #include "utils/Utils.hpp" - -#include "DatabaseUpdater.hpp" +#include "MediaScanner.hpp" namespace { @@ -85,20 +84,6 @@ isFileSupported(const boost::filesystem::path& file, const std::vector -getRootDirectoriesByType(Wt::Dbo::Session& session, Database::MediaDirectory::Type type) -{ - Wt::Dbo::Transaction transaction(session); - - std::vector rootDirs = Database::MediaDirectory::getByType(session, type); - - std::vector res; - for (auto rootDir : rootDirs) - res.push_back(rootDir->getPath()); - - return res; -} - bool isPathInParentPath(const boost::filesystem::path& path, const boost::filesystem::path& parentPath) { @@ -118,40 +103,33 @@ isPathInParentPath(const boost::filesystem::path& path, const boost::filesystem: } // namespace -namespace Database { +namespace Scanner { -Updater& Updater::instance(void) -{ - static Updater updater; - return updater; -} +using namespace Database; -Updater::Updater() - : _running(true), -_scheduleTimer(_ioService) +MediaScanner::MediaScanner(Wt::Dbo::SqlConnectionPool& connectionPool) + : _running(false), +_scheduleTimer(_ioService), +_db(connectionPool) { _ioService.setThreadCount(1); + + Wt::Dbo::Transaction transaction(_db.getSession()); + + if (!Setting::exists(_db.getSession(), "file_extensions")) + Setting::setString(_db.getSession(), "file_extensions", ".mp3 .ogg .oga .aac .m4a .flac .wav .wma .aif .aiff .ape .mpc .shn" ); } void -Updater::setConnectionPool(Wt::Dbo::SqlConnectionPool& connectionPool) -{ - _db = new Database::Handler(connectionPool); -} - -void -Updater::restart(void) +MediaScanner::restart(void) { stop(); start(); } void -Updater::start(void) +MediaScanner::start(void) { - if (_db == nullptr) - throw std::logic_error("uninitialized db!"); - _running = true; // post some jobs in the io_service @@ -161,7 +139,7 @@ Updater::start(void) } void -Updater::stop(void) +MediaScanner::stop(void) { _running = false; @@ -171,9 +149,9 @@ Updater::stop(void) } void -Updater::processNextJob(void) +MediaScanner::processNextJob(void) { - if (Setting::getBool(_db->getSession(), "manual_scan_requested", false)) + if (Setting::getBool(_db.getSession(), "manual_scan_requested", false)) { LMS_LOG(DBUPDATER, INFO) << "Manual scan requested!"; scheduleScan( boost::posix_time::seconds(0) ); @@ -181,11 +159,11 @@ Updater::processNextJob(void) else { boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); - boost::posix_time::time_duration startTime = Setting::getDuration(_db->getSession(), "update_start_time"); + boost::posix_time::time_duration startTime = Setting::getDuration(_db.getSession(), "update_start_time"); boost::gregorian::date nextScanDate; - std::string updatePeriod = Setting::getString(_db->getSession(), "update_period", "never"); + std::string updatePeriod = Setting::getString(_db.getSession(), "update_period", "never"); if (updatePeriod == "daily") { if (now.time_of_day() < startTime) @@ -214,72 +192,58 @@ Updater::processNextJob(void) } void -Updater::scheduleScan( boost::posix_time::time_duration duration) +MediaScanner::scheduleScan( boost::posix_time::time_duration duration) { LMS_LOG(DBUPDATER, INFO) << "Scheduling next scan in " << duration; _scheduleTimer.expires_from_now(duration); - _scheduleTimer.async_wait( boost::bind( &Updater::process, this, boost::asio::placeholders::error) ); + _scheduleTimer.async_wait( boost::bind( &MediaScanner::process, this, boost::asio::placeholders::error) ); } void -Updater::scheduleScan( boost::posix_time::ptime time) +MediaScanner::scheduleScan( boost::posix_time::ptime time) { LMS_LOG(DBUPDATER, INFO) << "Scheduling next scan at " << time; _scheduleTimer.expires_at(time); - _scheduleTimer.async_wait( boost::bind( &Updater::process, this, boost::asio::placeholders::error) ); + _scheduleTimer.async_wait( boost::bind( &MediaScanner::process, this, boost::asio::placeholders::error) ); } void -Updater::process(boost::system::error_code err) +MediaScanner::process(boost::system::error_code err) { if (err) return; - updateFileExtensions(); + refreshScanSettings(); Stats stats; checkAudioFiles(stats); - std::vector rootDirectories; - { - Wt::Dbo::Transaction transaction(_db->getSession()); - - for (MediaDirectory::pointer directory : MediaDirectory::getAll(_db->getSession())) - rootDirectories.push_back( RootDirectory( directory->getType(), directory->getPath() )); - } - - for (RootDirectory rootDirectory : rootDirectories) + for (auto rootDirectory : _rootDirectories) { if (!_running) break; - LMS_LOG(DBUPDATER, INFO) << "Processing root directory '" << rootDirectory.path << "'..."; + LMS_LOG(DBUPDATER, INFO) << "Processing root directory '" << rootDirectory << "'..."; processRootDirectory(rootDirectory, stats); - LMS_LOG(DBUPDATER, INFO) << "Processing root directory '" << rootDirectory.path << "' DONE"; + LMS_LOG(DBUPDATER, INFO) << "Processing root directory '" << rootDirectory << "' DONE"; } if (_running) - { checkDuplicatedAudioFiles(stats); - LMS_LOG(DBUPDATER, INFO) << "Processed all files, now calling listeners..."; - for (auto eventHandler : _eventHandlers) - eventHandler->handleFilesUpdated(); - } - LMS_LOG(DBUPDATER, INFO) << "Scan " << (_running ? "complete" : "aborted") << ". Changes = " << stats.nbChanges() << " (added = " << stats.nbAdded << ", removed = " << stats.nbRemoved << ", updated = " << stats.nbUpdated << "), Not changed = " << stats.nbNoChange << ", Scanned = " << stats.nbScanned << " (errors = " << stats.nbScanErrors << ", not imported = " << stats.nbNotImported << ")"; // Update database stats boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); if (stats.nbChanges() > 0) - Setting::setTime(_db->getSession(), "last_update", now); + Setting::setTime(_db.getSession(), "last_update", now); // Save the last scan only if it has been completed if (_running) { - Setting::setTime(_db->getSession(), "last_scan", now); - Setting::setBool(_db->getSession(), "manual_scan_requested", false); + Setting::setTime(_db.getSession(), "last_scan", now); + Setting::setBool(_db.getSession(), "manual_scan_requested", false); processNextJob(); @@ -288,26 +252,30 @@ Updater::process(boost::system::error_code err) } void -Updater::updateFileExtensions() +MediaScanner::refreshScanSettings() { - Wt::Dbo::Transaction transaction(_db->getSession()); + Wt::Dbo::Transaction transaction(_db.getSession()); - _audioFileExtensions.clear(); - for (auto extension : splitString(Setting::getString(_db->getSession(), "audio_file_extensions"), " ")) - _audioFileExtensions.push_back( extension ); + _fileExtensions.clear(); + for (auto extension : splitString(Setting::getString(_db.getSession(), "file_extensions"), " ")) + _fileExtensions.push_back( extension ); + + _rootDirectories.clear(); + for (auto rootDir : Database::MediaDirectory::getAll(_db.getSession())) + _rootDirectories.push_back(rootDir->getPath()); } Artist::pointer -Updater::getArtist( const boost::filesystem::path& file, const std::string& name, const std::string& mbid) +MediaScanner::getArtist( const boost::filesystem::path& file, const std::string& name, const std::string& mbid) { Artist::pointer artist; // First try to get by MBID if (!mbid.empty()) { - artist = Artist::getByMBID( _db->getSession(), mbid ); + artist = Artist::getByMBID( _db.getSession(), mbid ); if (!artist) - artist = Artist::create( _db->getSession(), name, mbid); + artist = Artist::create( _db.getSession(), name, mbid); return artist; } @@ -315,7 +283,7 @@ Updater::getArtist( const boost::filesystem::path& file, const std::string& name // Fall back on artist name (collisions may occur) if (!name.empty()) { - for (Artist::pointer sameNamedArtist : Artist::getByName( _db->getSession(), name )) + for (Artist::pointer sameNamedArtist : Artist::getByName( _db.getSession(), name )) { if (sameNamedArtist->getMBID().empty()) { @@ -326,7 +294,7 @@ Updater::getArtist( const boost::filesystem::path& file, const std::string& name // No Artist found with the same name and without MBID -> creating if (!artist) - artist = Artist::create( _db->getSession(), name); + artist = Artist::create( _db.getSession(), name); return artist; } @@ -335,16 +303,16 @@ Updater::getArtist( const boost::filesystem::path& file, const std::string& name } Release::pointer -Updater::getRelease( const boost::filesystem::path& file, const std::string& name, const std::string& mbid) +MediaScanner::getRelease( const boost::filesystem::path& file, const std::string& name, const std::string& mbid) { Release::pointer release; // First try to get by MBID if (!mbid.empty()) { - release = Release::getByMBID( _db->getSession(), mbid ); + release = Release::getByMBID( _db.getSession(), mbid ); if (!release) - release = Release::create( _db->getSession(), name, mbid); + release = Release::create( _db.getSession(), name, mbid); return release; } @@ -352,7 +320,7 @@ Updater::getRelease( const boost::filesystem::path& file, const std::string& nam // Fall back on release name (collisions may occur) if (!name.empty()) { - for (Release::pointer sameNamedRelease : Release::getByName( _db->getSession(), name )) + for (Release::pointer sameNamedRelease : Release::getByName( _db.getSession(), name )) { if (sameNamedRelease->getMBID().empty()) { @@ -363,7 +331,7 @@ Updater::getRelease( const boost::filesystem::path& file, const std::string& nam // No release found with the same name and without MBID -> creating if (!release) - release = Release::create( _db->getSession(), name); + release = Release::create( _db.getSession(), name); return release; } @@ -372,32 +340,39 @@ Updater::getRelease( const boost::filesystem::path& file, const std::string& nam } std::vector -Updater::getGenreClusters( const std::list& names) +MediaScanner::getClusters( const MetaData::Clusters& clustersNames) { - std::vector< Cluster::pointer > genres; + std::vector< Cluster::pointer > clusters; - for (const std::string& name : names) + for (auto clusterNames : clustersNames) { - Cluster::pointer genre ( Cluster::get(_db->getSession(), "Genre", name) ); - if (!genre) - genre = Cluster::create(_db->getSession(), "Genre", name); + ClusterType::pointer clusterType = ClusterType::getByName(_db.getSession(), clusterNames.first); + if (!clusterType) + clusterType = ClusterType::create(_db.getSession(), clusterNames.first); - genres.push_back( genre ); + for (auto clusterName : clusterNames.second) + { + Cluster::pointer cluster = clusterType->getCluster(clusterName); + if (!cluster) + cluster = Cluster::create(_db.getSession(), clusterType, clusterName); + + clusters.push_back(cluster); + } } - return genres; + return clusters; } void -Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) +MediaScanner::processAudioFile( const boost::filesystem::path& file, Stats& stats) { boost::posix_time::ptime lastWriteTime (boost::posix_time::from_time_t( boost::filesystem::last_write_time( file ) ) ); // Skip file if last write is the same { - Wt::Dbo::Transaction transaction(_db->getSession()); + Wt::Dbo::Transaction transaction(_db.getSession()); - Wt::Dbo::ptr track = Track::getByPath(_db->getSession(), file); + Wt::Dbo::ptr track = Track::getByPath(_db.getSession(), file); if (track && track->getLastWriteTime() == lastWriteTime) { @@ -406,8 +381,8 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) } } - MetaData::Items items; - if (!_metadataParser.parse(file, items)) + boost::optional items = _metadataParser.parse(file); + if (!items) { stats.nbScanErrors++; return; @@ -418,15 +393,15 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) std::vector checksum ; computeCrc(file, checksum); - Wt::Dbo::Transaction transaction(_db->getSession()); + Wt::Dbo::Transaction transaction(_db.getSession()); - Wt::Dbo::ptr track = Track::getByPath(_db->getSession(), file); + Wt::Dbo::ptr track = Track::getByPath(_db.getSession(), file); // We estimate this is a audio file if: // - we found a least one audio stream // - the duration is not null - if (items.find(MetaData::Type::AudioStreams) == items.end() - || boost::any_cast >(items[MetaData::Type::AudioStreams]).empty()) + if ((*items).find(MetaData::Type::AudioStreams) == (*items).end() + || boost::any_cast> ((*items)[MetaData::Type::AudioStreams]).empty()) { LMS_LOG(DBUPDATER, INFO) << "Skipped '" << file << "' (no audio stream found)"; @@ -439,8 +414,8 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) stats.nbNotImported++; return; } - if (items.find(MetaData::Type::Duration) == items.end() - || boost::any_cast(items[MetaData::Type::Duration]).total_seconds() <= 0) + if ((*items).find(MetaData::Type::Duration) == (*items).end() + || boost::any_cast((*items)[MetaData::Type::Duration]).total_seconds() <= 0) { LMS_LOG(DBUPDATER, INFO) << "Skipped '" << file << "' (no duration or duration <= 0)"; @@ -456,9 +431,9 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) // ***** Title std::string title; - if (items.find(MetaData::Type::Title) != items.end()) + if ((*items).find(MetaData::Type::Title) != (*items).end()) { - title = boost::any_cast(items[MetaData::Type::Title]); + title = boost::any_cast((*items)[MetaData::Type::Title]); } else { @@ -470,13 +445,15 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) // ***** Clusters std::vector< Cluster::pointer > genres; { - std::list genreList; + MetaData::Clusters clusterNames; - if (items.find(MetaData::Type::Genres) != items.end()) - genreList = boost::any_cast< std::list > (items[MetaData::Type::Genres]); + if ((*items).find(MetaData::Type::Clusters) != (*items).end()) + { + clusterNames = boost::any_cast ((*items)[MetaData::Type::Clusters]); + } // TODO rename - genres = getGenreClusters( genreList ); + genres = getClusters( clusterNames ); } // ***** Artist @@ -485,11 +462,11 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) std::string artistName; std::string artistMusicBrainzID; - if (items.find(MetaData::Type::MusicBrainzArtistID) != items.end()) - artistMusicBrainzID = boost::any_cast(items[MetaData::Type::MusicBrainzArtistID] ); + if ((*items).find(MetaData::Type::MusicBrainzArtistID) != (*items).end()) + artistMusicBrainzID = boost::any_cast((*items)[MetaData::Type::MusicBrainzArtistID] ); - if (items.find(MetaData::Type::Artist) != items.end()) - artistName = boost::any_cast(items[MetaData::Type::Artist]); + if ((*items).find(MetaData::Type::Artist) != (*items).end()) + artistName = boost::any_cast((*items)[MetaData::Type::Artist]); artist = getArtist(file, artistName, artistMusicBrainzID); } @@ -500,11 +477,11 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) std::string releaseName; std::string releaseMusicBrainzID; - if (items.find(MetaData::Type::MusicBrainzAlbumID) != items.end()) - releaseMusicBrainzID = boost::any_cast(items[MetaData::Type::MusicBrainzAlbumID] ); + if ((*items).find(MetaData::Type::MusicBrainzAlbumID) != (*items).end()) + releaseMusicBrainzID = boost::any_cast((*items)[MetaData::Type::MusicBrainzAlbumID] ); - if (items.find(MetaData::Type::Album) != items.end()) - releaseName = boost::any_cast(items[MetaData::Type::Album]); + if ((*items).find(MetaData::Type::Album) != (*items).end()) + releaseName = boost::any_cast((*items)[MetaData::Type::Album]); release = getRelease(file, releaseName, releaseMusicBrainzID); } @@ -515,7 +492,7 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) if (!track) { // Create a new song - track = Track::create(_db->getSession(), file); + track = Track::create(_db.getSession(), file); LMS_LOG(DBUPDATER, INFO) << "Adding '" << file << "'"; stats.nbAdded++; } @@ -537,7 +514,7 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) track.modify()->setRelease(release); track.modify()->setLastWriteTime(lastWriteTime); track.modify()->setName(title); - track.modify()->setDuration( boost::any_cast(items[MetaData::Type::Duration]) ); + track.modify()->setDuration( boost::any_cast((*items)[MetaData::Type::Duration]) ); track.modify()->setAddedTime( boost::posix_time::second_clock::local_time() ); { @@ -555,53 +532,54 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) track.modify()->setGenres( trackClusterList ); } - if (items.find(MetaData::Type::TrackNumber) != items.end()) - track.modify()->setTrackNumber( boost::any_cast(items[MetaData::Type::TrackNumber]) ); + if ((*items).find(MetaData::Type::TrackNumber) != (*items).end()) + track.modify()->setTrackNumber( boost::any_cast((*items)[MetaData::Type::TrackNumber]) ); - if (items.find(MetaData::Type::TotalTrack) != items.end()) - track.modify()->setTotalTrackNumber( boost::any_cast(items[MetaData::Type::TotalTrack]) ); + if ((*items).find(MetaData::Type::TotalTrack) != (*items).end()) + track.modify()->setTotalTrackNumber( boost::any_cast((*items)[MetaData::Type::TotalTrack]) ); - if (items.find(MetaData::Type::DiscNumber) != items.end()) - track.modify()->setDiscNumber( boost::any_cast(items[MetaData::Type::DiscNumber]) ); + if ((*items).find(MetaData::Type::DiscNumber) != (*items).end()) + track.modify()->setDiscNumber( boost::any_cast((*items)[MetaData::Type::DiscNumber]) ); - if (items.find(MetaData::Type::TotalDisc) != items.end()) - track.modify()->setTotalDiscNumber( boost::any_cast(items[MetaData::Type::TotalDisc]) ); + if ((*items).find(MetaData::Type::TotalDisc) != (*items).end()) + track.modify()->setTotalDiscNumber( boost::any_cast((*items)[MetaData::Type::TotalDisc]) ); - if (items.find(MetaData::Type::Date) != items.end()) - track.modify()->setDate( boost::any_cast(items[MetaData::Type::Date]) ); + if ((*items).find(MetaData::Type::Date) != (*items).end()) + track.modify()->setDate( boost::any_cast((*items)[MetaData::Type::Date]) ); - if (items.find(MetaData::Type::OriginalDate) != items.end()) + if ((*items).find(MetaData::Type::OriginalDate) != (*items).end()) { - track.modify()->setOriginalDate( boost::any_cast(items[MetaData::Type::OriginalDate]) ); + track.modify()->setOriginalDate( boost::any_cast((*items)[MetaData::Type::OriginalDate]) ); // If a file has an OriginalDate but no date, set the date to ease filtering - if (items.find(MetaData::Type::Date) == items.end()) - track.modify()->setDate( boost::any_cast(items[MetaData::Type::OriginalDate]) ); + if ((*items).find(MetaData::Type::Date) == (*items).end()) + track.modify()->setDate( boost::any_cast((*items)[MetaData::Type::OriginalDate]) ); } - if (items.find(MetaData::Type::MusicBrainzRecordingID) != items.end()) + if ((*items).find(MetaData::Type::MusicBrainzRecordingID) != (*items).end()) { - track.modify()->setMBID( boost::any_cast(items[MetaData::Type::MusicBrainzRecordingID]) ); + track.modify()->setMBID( boost::any_cast((*items)[MetaData::Type::MusicBrainzRecordingID]) ); } - if (items.find(MetaData::Type::HasCover) != items.end()) + if ((*items).find(MetaData::Type::HasCover) != (*items).end()) { - bool hasCover = boost::any_cast(items[MetaData::Type::HasCover]); + bool hasCover = boost::any_cast((*items)[MetaData::Type::HasCover]); track.modify()->setCoverType( hasCover ? Track::CoverType::Embedded : Track::CoverType::None ); } - transaction.commit(); + // TODO check added/modified + _sigAddedTrack.emit(track); - _sigTrackChanged.emit(true, track.id(), track->getMBID(), track->getPath()); + transaction.commit(); } void -Updater::processRootDirectory(RootDirectory rootDirectory, Stats& stats) +MediaScanner::processRootDirectory(boost::filesystem::path rootDirectory, Stats& stats) { boost::system::error_code ec; - boost::filesystem::recursive_directory_iterator itPath(rootDirectory.path, ec); + boost::filesystem::recursive_directory_iterator itPath(rootDirectory, ec); boost::filesystem::recursive_directory_iterator itEnd; while (!ec && itPath != itEnd) @@ -614,20 +592,15 @@ Updater::processRootDirectory(RootDirectory rootDirectory, Stats& stats) if (boost::filesystem::is_regular(path)) { - switch( rootDirectory.type ) - { - case Database::MediaDirectory::Audio: - if (isFileSupported(path, _audioFileExtensions)) - processAudioFile(path, stats ); - - break; - } + if (isFileSupported(path, _fileExtensions)) + processAudioFile(path, stats ); } } } -bool -Updater::checkFile(const boost::filesystem::path& p, const std::vector& rootDirs, const std::vector& extensions) +// Check if a file exists and is still in a root directory +static bool +checkFile(const boost::filesystem::path& p, const std::vector& rootDirs, const std::vector& extensions) { try { @@ -678,12 +651,11 @@ Updater::checkFile(const boost::filesystem::path& p, const std::vector trackPaths = Track::getAllPaths(_db->getSession());; - std::vector rootDirs = getRootDirectoriesByType(_db->getSession(), Database::MediaDirectory::Audio); + std::vector trackPaths = Track::getAllPaths(_db.getSession());; LMS_LOG(DBUPDATER, DEBUG) << "Checking tracks..."; for (auto& trackPath : trackPaths) @@ -691,11 +663,11 @@ Updater::checkAudioFiles( Stats& stats ) if (!_running) return; - if (!checkFile(trackPath, rootDirs, _audioFileExtensions)) + if (!checkFile(trackPath, _rootDirectories, _fileExtensions)) { - Wt::Dbo::Transaction transaction(_db->getSession()); + Wt::Dbo::Transaction transaction(_db.getSession()); - Track::pointer track = Track::getByPath(_db->getSession(), trackPath); + Track::pointer track = Track::getByPath(_db.getSession(), trackPath); if (track) { track.remove(); @@ -706,25 +678,25 @@ Updater::checkAudioFiles( Stats& stats ) LMS_LOG(DBUPDATER, DEBUG) << "Checking Clusters..."; { - Wt::Dbo::Transaction transaction(_db->getSession()); + Wt::Dbo::Transaction transaction(_db.getSession()); // Now process orphan Cluster (no track) - auto genres = Cluster::getAll(_db->getSession()); - for (auto genre : genres) + auto clusters = Cluster::getAll(_db.getSession()); + for (auto cluster : clusters) { - if (genre->getTracks().size() == 0) + if (cluster->getTracks().size() == 0) { - LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan genre '" << genre->getName() << "'"; - genre.remove(); + LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan cluster '" << cluster->getName() << "'"; + cluster.remove(); } } } LMS_LOG(DBUPDATER, DEBUG) << "Checking artists..."; { - Wt::Dbo::Transaction transaction(_db->getSession()); + Wt::Dbo::Transaction transaction(_db.getSession()); - auto artists = Artist::getAllOrphans(_db->getSession()); + auto artists = Artist::getAllOrphans(_db.getSession()); for (auto artist : artists) { LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan artist '" << artist->getName() << "'"; @@ -734,9 +706,9 @@ Updater::checkAudioFiles( Stats& stats ) LMS_LOG(DBUPDATER, DEBUG) << "Checking releases..."; { - Wt::Dbo::Transaction transaction(_db->getSession()); + Wt::Dbo::Transaction transaction(_db.getSession()); - auto releases = Release::getAllOrphans(_db->getSession()); + auto releases = Release::getAllOrphans(_db.getSession()); for (auto release : releases) { LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan release '" << release->getName() << "'"; @@ -748,19 +720,19 @@ Updater::checkAudioFiles( Stats& stats ) } void -Updater::checkDuplicatedAudioFiles(Stats& stats) +MediaScanner::checkDuplicatedAudioFiles(Stats& stats) { LMS_LOG(DBUPDATER, INFO) << "Checking duplicated audio files"; - Wt::Dbo::Transaction transaction(_db->getSession()); + Wt::Dbo::Transaction transaction(_db.getSession()); - std::vector tracks = Database::Track::getMBIDDuplicates(_db->getSession()); + std::vector tracks = Database::Track::getMBIDDuplicates(_db.getSession()); for (Track::pointer track : tracks) { LMS_LOG(DBUPDATER, INFO) << "Found duplicated MBID [" << track->getMBID() << "], file: " << track->getPath() << " - " << track->getArtist()->getName() << " - " << track->getName(); } - tracks = Database::Track::getChecksumDuplicates(_db->getSession()); + tracks = Database::Track::getChecksumDuplicates(_db.getSession()); for (Track::pointer track : tracks) { LMS_LOG(DBUPDATER, INFO) << "Found duplicated checksum [" << bufferToString(track->getChecksum()) << "], file: " << track->getPath() << " - " << track->getArtist()->getName() << " - " << track->getName(); @@ -771,4 +743,4 @@ Updater::checkDuplicatedAudioFiles(Stats& stats) } -} // namespace Database +} // namespace Scanner diff --git a/src/database/updater/DatabaseUpdater.hpp b/src/scanner/MediaScanner.hpp similarity index 50% rename from src/database/updater/DatabaseUpdater.hpp rename to src/scanner/MediaScanner.hpp index f9945b70..fd8d1e5f 100644 --- a/src/database/updater/DatabaseUpdater.hpp +++ b/src/scanner/MediaScanner.hpp @@ -19,9 +19,6 @@ #pragma once -#include -#include - #include #include @@ -31,21 +28,14 @@ #include "database/DatabaseHandler.hpp" -namespace Database { +namespace Scanner { -class UpdaterEventHandler; - -class Updater +class MediaScanner { public: - static Updater& instance(); - - void setConnectionPool(Wt::Dbo::SqlConnectionPool& connectionPool); - - void setAudioExtensions(const std::vector& extensions); - void setVideoExtensions(const std::vector& extensions); + MediaScanner(Wt::Dbo::SqlConnectionPool& connectionPool); void start(); void stop(); @@ -64,37 +54,21 @@ class Updater std::size_t nbChanges() const { return nbAdded + nbRemoved + nbUpdated;} }; - // Emitted when the whole database has been scanned (and all the event handlers have been called) + + // Called just after track addition + Wt::Signal& addedTrack() { return _sigAddedTrack; } + + // Called just before track removal + Wt::Signal& removedTrack() { return _sigRemovedTrack; } + + // Called just after track modification + Wt::Signal& modifiedTrack() { return _sigModifiedTrack; } + + // Called just after scan complete Wt::Signal& scanComplete() { return _sigScanComplete; } - // Emitted when a track changed - // true -> added or modified, false -> to be deleted - // id of the track - // musicbrainz recordid - // path of the track - typedef Wt::Signal SigTrackChanged; - SigTrackChanged& trackChanged() { return _sigTrackChanged; } - - std::mutex& getMutex(void) { return _mutex; } - - Database::Handler& getDb(void) { return *_db; } - - bool quitRequested(void) const { return !_running;} - - void registerEventHandler(std::shared_ptr handler) { _eventHandlers.push_back(handler); } - private: - Updater(); - - struct RootDirectory - { - Database::MediaDirectory::Type type; - boost::filesystem::path path; - - RootDirectory(Database::MediaDirectory::Type t, boost::filesystem::path p) : type(t), path(p) {} - }; - // Job handling void processNextJob(); void scheduleScan(boost::posix_time::time_duration duration); @@ -103,19 +77,13 @@ class Updater // 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& rootDirectories, - const std::vector& extensions); - - - void processRootDirectory( RootDirectory rootDirectory, Stats& stats); + void processRootDirectory( boost::filesystem::path rootDirectory, Stats& stats); // Helpers Database::Artist::pointer getArtist( const boost::filesystem::path& file, const std::string& name, const std::string& MBID); Database::Release::pointer getRelease( const boost::filesystem::path& file, const std::string& name, const std::string& MBID); - std::vector getGenreClusters( const std::list& names); - void updateFileExtensions(); + std::vector getClusters( const MetaData::Clusters& names); + void refreshScanSettings(); // Audio void checkAudioFiles( Stats& stats ); @@ -128,47 +96,23 @@ class Updater bool _running; Wt::WIOService _ioService; + Wt::Signal _sigScanComplete; - Wt::Signal _sigArtistChanged; - Wt::Signal _sigReleaseChanged; - SigTrackChanged _sigTrackChanged; - std::mutex _mutex; + Wt::Signal _sigModifiedTrack; + Wt::Signal _sigAddedTrack; + Wt::Signal _sigRemovedTrack; boost::asio::deadline_timer _scheduleTimer; - Database::Handler* _db = nullptr; + Database::Handler _db; - std::vector _audioFileExtensions; - std::vector _videoFileExtensions; + // Scan settings + std::vector _fileExtensions; + std::vector _rootDirectories; MetaData::TagLibParser _metadataParser; - std::list > _eventHandlers; +}; // class MediaScanner -}; // class Updater - -// Helper to get the updater session data -static inline Wt::Dbo::Session& UpdaterDboSession() -{ - return Updater::instance().getDb().getSession(); -} - -static inline bool UpdaterQuitRequested() -{ - return Updater::instance().quitRequested(); -} - - -class UpdaterEventHandler -{ - public: - - // called when all the files have been scanned by the updater - virtual void handleFilesUpdated(void) = 0; - - private: - -}; - -} // Database +} // Scanner diff --git a/src/ui/Filters.cpp b/src/ui/Filters.cpp index 9ecdc704..efc2ae07 100644 --- a/src/ui/Filters.cpp +++ b/src/ui/Filters.cpp @@ -56,14 +56,15 @@ Filters::showDialog() { Wt::Dbo::Transaction transaction(DboSession()); - auto types = Database::Cluster::getAllTypes(DboSession()); + auto types = Database::ClusterType::getAll(DboSession()); for (auto type : types) - typeCombo->addItem(Wt::WString::fromUTF8(type)); + typeCombo->addItem(Wt::WString::fromUTF8(type->getName())); + if (!types.empty()) { - auto values = Database::Cluster::getByType(DboSession(), types.front()); + auto values = types.front()->getClusters(); for (auto value : values) { @@ -76,13 +77,15 @@ Filters::showDialog() typeCombo->changed().connect(std::bind([=] { - auto type = typeCombo->valueText().toUTF8(); + auto name = typeCombo->valueText().toUTF8(); valueCombo->clear(); Wt::Dbo::Transaction transaction(DboSession()); - auto values = Database::Cluster::getByType(DboSession(), type); + auto clusterType = Database::ClusterType::getByName(DboSession(), name); + + auto values = clusterType->getClusters(); for (auto value : values) { if (_filterIds.find(value.id()) == _filterIds.end()) @@ -108,7 +111,11 @@ Filters::showDialog() Wt::Dbo::Transaction transaction(DboSession()); - auto cluster = Database::Cluster::get(DboSession(), type, value); + auto clusterType = Database::ClusterType::getByName(DboSession(), type); + if (!clusterType) + return; + + auto cluster = clusterType->getCluster(value); if (!cluster) return; diff --git a/test/Makefile.am b/test/Makefile.am index 0df0354b..411aef1a 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -1,7 +1,7 @@ TESTS = sql-query database-user -check_PROGRAMS = sql-query database-user test-wt test-avmetadata +check_PROGRAMS = test-avmetadata database_basics_SOURCES = \ $(srcdir)/CheckDbBasics.cpp \ @@ -88,7 +88,7 @@ test_wt_audio_LDADD=$(MAGICKXX_LIBS) test_avmetadata_SOURCES = TestAvMetadata.cpp \ - $(top_srcdir)/src/logger/Logger.cpp \ + $(top_srcdir)/src/utils/Logger.cpp \ $(top_srcdir)/src/utils/Utils.cpp \ $(top_srcdir)/src/metadata/AvFormat.cpp \ $(top_srcdir)/src/metadata/TagLibParser.cpp \ diff --git a/test/TestAvMetadata.cpp b/test/TestAvMetadata.cpp index b42d53f8..c91d0ea3 100644 --- a/test/TestAvMetadata.cpp +++ b/test/TestAvMetadata.cpp @@ -26,20 +26,20 @@ int main(int argc, char *argv[]) MetaData::AvFormat avFormatParser; MetaData::TagLibParser tagLibParser; - std::vector parsers = { &avFormatParser, &tagLibParser }; + std::vector parsers = {&avFormatParser, &tagLibParser }; for (auto& parser : parsers) { - MetaData::Items items; + boost::optional items = parser->parse(argv[1]); - if (!parser->parse(argv[1], items)) + if (!items) { std::cout << "Parsing failed" << std::endl; continue; } std::cout << "Items:" << std::endl; - for (auto item : items) + for (auto item : (*items)) { switch (item.first) { @@ -55,9 +55,15 @@ int main(int argc, char *argv[]) std::cout << "Album: " << boost::any_cast(item.second) << std::endl; break; - case MetaData::Type::Genres: - for (auto& genre : boost::any_cast >(item.second)) - std::cout << "Genre: " << genre << std::endl; + case MetaData::Type::Clusters: + for (const auto& cluster : boost::any_cast(item.second)) + { + std::cout << "Cluster: " << cluster.first << std::endl; + for (const auto name : cluster.second) + { + std::cout << "\t" << name << std::endl; + } + } break; case MetaData::Type::Duration: @@ -113,6 +119,10 @@ int main(int argc, char *argv[]) std::cout << "MusicBrainzRecordingID: " << boost::any_cast(item.second) << std::endl; break; + case MetaData::Type::AcoustID: + std::cout << "AcoustID: " << boost::any_cast(item.second) << std::endl; + break; + default: break; } From 4b20f713f3ce5a99f76404274ec7d209f062e907 Mon Sep 17 00:00:00 2001 From: emeric Date: Fri, 2 Mar 2018 13:05:30 +0100 Subject: [PATCH 056/195] Do not display show more for playlist if no playlist is found --- src/database/Playlist.cpp | 7 ++++++- src/ui/PlaylistView.cpp | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/database/Playlist.cpp b/src/database/Playlist.cpp index 47e259b3..c5b2e125 100644 --- a/src/database/Playlist.cpp +++ b/src/database/Playlist.cpp @@ -83,7 +83,12 @@ Playlist::getTracks(int offset, int size, bool& moreResults) const moreResults = false; - Wt::Dbo::collection> entries = session()->find().where("playlist_id = ?").bind(self().id()).orderBy("pos").offset(offset).limit(size + 1); + Wt::Dbo::collection> entries + = session()->find() + .where("playlist_id = ?").bind(self().id()) + .orderBy("pos") + .limit(size != -1 ? size + 1 : -1) + .offset(offset); std::vector> res; diff --git a/src/ui/PlaylistView.cpp b/src/ui/PlaylistView.cpp index b7a74384..4ee8f58f 100644 --- a/src/ui/PlaylistView.cpp +++ b/src/ui/PlaylistView.cpp @@ -48,6 +48,7 @@ Playlist::Playlist(Wt::WContainerWidget* parent) _showMore = new Wt::WTemplate(Wt::WString::tr("template-show-more")); _showMore->addFunction("tr", &Wt::WTemplate::Functions::tr); + _showMore->setHidden(true); container->bindWidget("show-more", _showMore); _showMore->clicked().connect(std::bind([=] From 529055d432b32580248ad1770bc0fc3df27512d4 Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 3 Mar 2018 17:23:28 +0100 Subject: [PATCH 057/195] Added database settings view --- Makefile.am | 1 + approot/messages.xml | 15 ++ approot/settings.xml | 54 +++++ src/Makefile.am | 1 + src/main/main.cpp | 4 - src/scanner/MediaScanner.cpp | 68 ++++-- src/scanner/MediaScanner.hpp | 12 ++ src/ui/LmsApplication.cpp | 25 ++- src/ui/common/DirectoryValidator.cpp | 5 +- src/ui/settings/DatabaseView.cpp | 298 +++++++++++++++++++++++++++ src/ui/settings/DatabaseView.hpp | 54 +++++ 11 files changed, 506 insertions(+), 31 deletions(-) create mode 100644 approot/settings.xml create mode 100644 src/ui/settings/DatabaseView.cpp create mode 100644 src/ui/settings/DatabaseView.hpp diff --git a/Makefile.am b/Makefile.am index 7153c82b..95bf74bc 100644 --- a/Makefile.am +++ b/Makefile.am @@ -24,6 +24,7 @@ lms_approot_DATA = \ approot/playlist.xml \ approot/releases.xml \ approot/release.xml \ + approot/settings.xml \ approot/templates.xml \ approot/tracks.xml diff --git a/approot/messages.xml b/approot/messages.xml index 38d2396f..6bf0010a 100644 --- a/approot/messages.xml +++ b/approot/messages.xml @@ -17,4 +17,19 @@ Value Show more + +Settings +Database +Media root directory +Update period +Update start time +Never +Daily +Weekly +Monthly +Not a directory +Apply +Discard + + diff --git a/approot/settings.xml b/approot/settings.xml new file mode 100644 index 00000000..75f4c661 --- /dev/null +++ b/approot/settings.xml @@ -0,0 +1,54 @@ + + + + + + + ${tr:msg-settings-database} +
+ +
+ +
+ ${media-directory} +
+
+ ${media-directory-info} +
+
+ +
+ +
+ ${update-period} +
+
+ ${update-period-info} +
+
+ +
+ +
+ ${update-start-time} +
+
+ ${update-start-time-info} +
+
+ +
+
+ ${apply-btn class="btn-primary"} ${discard-btn} +
+
+
+
+ +
diff --git a/src/Makefile.am b/src/Makefile.am index 7842e2dd..ec57c4a9 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -39,6 +39,7 @@ lms_SOURCES = \ $(srcdir)/ui/resource/AvConvTranscodeStreamResource.cpp \ $(srcdir)/ui/resource/ImageResource.cpp \ $(srcdir)/ui/resource/TranscodeResource.cpp \ + $(srcdir)/ui/settings/DatabaseView.cpp \ $(srcdir)/utils/Config.cpp \ $(srcdir)/utils/Logger.cpp \ $(srcdir)/utils/Path.cpp \ diff --git a/src/main/main.cpp b/src/main/main.cpp index c2b24416..2901d976 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -127,10 +127,6 @@ int main(int argc, char* argv[]) Scanner::MediaScanner scanner(*connectionPool); - // Instanciate the updater's event handler. Order is important -// dbUpdater.registerEventHandler(std::make_shared()); -// dbUpdater.registerEventHandler(std::make_shared()); - // bind entry point server.addEntryPoint(Wt::Application, boost::bind(UserInterface::LmsApplication::create, _1, boost::ref(*connectionPool))); diff --git a/src/scanner/MediaScanner.cpp b/src/scanner/MediaScanner.cpp index 60f19f55..7f1239d8 100644 --- a/src/scanner/MediaScanner.cpp +++ b/src/scanner/MediaScanner.cpp @@ -107,6 +107,28 @@ namespace Scanner { using namespace Database; +UpdatePeriod +getUpdatePeriod(Wt::Dbo::Session& session) +{ + return static_cast(Setting::getInt(session, "update_period", static_cast(UpdatePeriod::Never))); +} + +void +setUpdatePeriod(Wt::Dbo::Session& session, UpdatePeriod updatePeriod) +{ + Setting::setInt(session, "update_period", static_cast(updatePeriod)); +} + +boost::posix_time::time_duration getUpdateStartTime(Wt::Dbo::Session& session) +{ + return Setting::getDuration(session, "update_start_time"); +} + +void setUpdateStartTime(Wt::Dbo::Session& session, boost::posix_time::time_duration startTime) +{ + Setting::setDuration(session, "update_start_time", startTime); +} + MediaScanner::MediaScanner(Wt::Dbo::SqlConnectionPool& connectionPool) : _running(false), _scheduleTimer(_ioService), @@ -159,31 +181,35 @@ MediaScanner::processNextJob(void) else { boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); - boost::posix_time::time_duration startTime = Setting::getDuration(_db.getSession(), "update_start_time"); + boost::posix_time::time_duration startTime = getUpdateStartTime(_db.getSession()); boost::gregorian::date nextScanDate; - std::string updatePeriod = Setting::getString(_db.getSession(), "update_period", "never"); - if (updatePeriod == "daily") + switch ( getUpdatePeriod(_db.getSession()) ) { - if (now.time_of_day() < startTime) - nextScanDate = now.date(); - else - nextScanDate = getNextDay(now.date()); - } - else if (updatePeriod == "weekly") - { - if (now.time_of_day() < startTime && now.date().day_of_week() == 1) - nextScanDate = now.date(); - else - nextScanDate = getNextMonday(now.date()); - } - else if (updatePeriod == "monthly") - { - if (now.time_of_day() < startTime && now.date().day() == 1) - nextScanDate = now.date(); - else - nextScanDate = getNextFirstOfMonth(now.date()); + case UpdatePeriod::Daily: + if (now.time_of_day() < startTime) + nextScanDate = now.date(); + else + nextScanDate = getNextDay(now.date()); + break; + + case UpdatePeriod::Weekly: + if (now.time_of_day() < startTime && now.date().day_of_week() == 1) + nextScanDate = now.date(); + else + nextScanDate = getNextMonday(now.date()); + break; + + case UpdatePeriod::Monthly: + if (now.time_of_day() < startTime && now.date().day() == 1) + nextScanDate = now.date(); + else + nextScanDate = getNextFirstOfMonth(now.date()); + break; + + case UpdatePeriod::Never: + break; } if (!nextScanDate.is_special()) diff --git a/src/scanner/MediaScanner.hpp b/src/scanner/MediaScanner.hpp index fd8d1e5f..6d319f0b 100644 --- a/src/scanner/MediaScanner.hpp +++ b/src/scanner/MediaScanner.hpp @@ -30,6 +30,18 @@ namespace Scanner { +enum class UpdatePeriod { + Never = 0, + Daily, + Weekly, + Monthly +}; + +UpdatePeriod getUpdatePeriod(Wt::Dbo::Session& session); +void setUpdatePeriod(Wt::Dbo::Session& session, UpdatePeriod updatePeriod); + +boost::posix_time::time_duration getUpdateStartTime(Wt::Dbo::Session& session); +void setUpdateStartTime(Wt::Dbo::Session& session, boost::posix_time::time_duration); class MediaScanner { diff --git a/src/ui/LmsApplication.cpp b/src/ui/LmsApplication.cpp index ba1997ab..dcfd42ff 100644 --- a/src/ui/LmsApplication.cpp +++ b/src/ui/LmsApplication.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include "config/config.h" @@ -34,6 +35,8 @@ #include "MediaPlayer.hpp" #include "PlaylistView.hpp" +#include "settings/DatabaseView.hpp" + #include "LmsApplication.hpp" namespace skeletons { @@ -86,6 +89,7 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnecti messageResourceBundle().use(appRoot() + "playlist"); messageResourceBundle().use(appRoot() + "release"); messageResourceBundle().use(appRoot() + "releases"); + messageResourceBundle().use(appRoot() + "settings"); messageResourceBundle().use(appRoot() + "tracks"); messageResourceBundle().use(appRoot() + "templates"); @@ -179,7 +183,7 @@ enum IdxRoot IdxHome = 0, IdxExplore, IdxPlaylist, - IdxSettings, + IdxSettingsDatabase, }; @@ -196,7 +200,7 @@ handlePathChange(Wt::WStackedWidget* stack) { "/release", IdxExplore }, { "/tracks", IdxExplore }, { "/playlist", IdxPlaylist }, - { "/settings", IdxSettings }, + { "/settings/database", IdxSettingsDatabase }, }; LMS_LOG(UI, DEBUG) << "Internal path changed to '" << wApp->internalPath() << "'"; @@ -237,7 +241,6 @@ LmsApplication::handleAuthEvent(void) auto navbar = new Wt::WNavigationBar(); navbar->setTitle("LMS", Wt::WLink(Wt::WLink::InternalPath, "/home")); navbar->setResponsive(true); -// navbar->setStyleClass("main-nav"); main->bindWidget("navbar-top", navbar); @@ -262,6 +265,18 @@ LmsApplication::handleAuthEvent(void) menuItem->setLink(Wt::WLink(Wt::WLink::InternalPath, "/playlist")); menuItem->setSelectable(false); } + { + auto menuItem = menu->insertItem(4, Wt::WString::tr("msg-settings")); + menuItem->setSelectable(false); + + Wt::WPopupMenu *settings = new Wt::WPopupMenu(); + auto dbSettings = settings->insertItem(0, Wt::WString::tr("msg-settings-database")); + dbSettings->setLink(Wt::WLink(Wt::WLink::InternalPath, "/settings/database")); + dbSettings->setSelectable(false); + + menuItem->setMenu(settings); + } + navbar->addMenu(menu); // Contents @@ -275,7 +290,9 @@ LmsApplication::handleAuthEvent(void) auto playlist = new Playlist(); mainStack->addWidget(playlist); - mainStack->addWidget(new Wt::WText("SETTINGS")); + + auto databaseSettings = new Settings::DatabaseView(); + mainStack->addWidget(databaseSettings); explore->tracksAdd.connect(std::bind([=] (std::vector tracks) { diff --git a/src/ui/common/DirectoryValidator.cpp b/src/ui/common/DirectoryValidator.cpp index c0093f1d..cf6c16fd 100644 --- a/src/ui/common/DirectoryValidator.cpp +++ b/src/ui/common/DirectoryValidator.cpp @@ -38,13 +38,14 @@ DirectoryValidator::validate(const Wt::WString& input) const boost::filesystem::path p(input.toUTF8()); boost::system::error_code ec; + // TODO check rights bool res = boost::filesystem::is_directory(p, ec); if (ec) - return Wt::WValidator::Result(Wt::WValidator::Invalid, ec.message()); + return Wt::WValidator::Result(Wt::WValidator::Invalid, ec.message()); // TODO translate common errors else if (res) return Wt::WValidator::Result(Wt::WValidator::Valid); else - return Wt::WValidator::Result(Wt::WValidator::Invalid, "Not a directory"); + return Wt::WValidator::Result(Wt::WValidator::Invalid, Wt::WString::tr("msg-error-not-a-directory")); } diff --git a/src/ui/settings/DatabaseView.cpp b/src/ui/settings/DatabaseView.cpp new file mode 100644 index 00000000..355b3b25 --- /dev/null +++ b/src/ui/settings/DatabaseView.cpp @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2018 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 . + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include "common/DirectoryValidator.hpp" +#include "database/MediaDirectory.hpp" +#include "scanner/MediaScanner.hpp" +#include "utils/Logger.hpp" + +#include "LmsApplication.hpp" + +#include "DatabaseView.hpp" + +namespace UserInterface { +namespace Settings { + +using namespace Database; + +class DatabaseModel : public Wt::WFormModel +{ + public: + // Associate each field with a unique string literal. + static const Field MediaDirectoryField; + static const Field UpdatePeriodField; + static const Field UpdateStartTimeField; + + DatabaseModel(Wt::WObject *parent = 0) + : Wt::WFormModel(parent) + { + initializeModels(); + + addField(MediaDirectoryField); + addField(UpdatePeriodField); + addField(UpdateStartTimeField); + + DirectoryValidator* dirValidator = new DirectoryValidator(); + dirValidator->setMandatory(true); + setValidator(MediaDirectoryField, dirValidator); + + setValidator(UpdatePeriodField, createMandatoryValidator()); + setValidator(UpdateStartTimeField, createMandatoryValidator()); + + // populate the model with initial data + loadData(); + } + + Wt::WAbstractItemModel *updatePeriodModel() { return _updatePeriodModel; } + Wt::WAbstractItemModel *updateStartTimeModel() { return _updateStartTimeModel; } + + void loadData() + { + using namespace Database; + + Wt::Dbo::Transaction transaction(DboSession()); + + std::vector mediaDirectories = MediaDirectory::getAll(DboSession()); + if (!mediaDirectories.empty()) + setValue(MediaDirectoryField, mediaDirectories.front()->getPath().string()); + + auto periodRow = getUpdatePeriodModelRow( Scanner::getUpdatePeriod(DboSession()) ); + if (periodRow) + setValue(UpdatePeriodField, updatePeriodString(*periodRow)); + + auto startTimeRow = getUpdateStartTimeModelRow( Scanner::getUpdateStartTime(DboSession()) ); + if (startTimeRow) + setValue(UpdateStartTimeField, updateStartTimeString(*startTimeRow) ); + } + + void saveData() + { + Wt::Dbo::Transaction transaction(DboSession()); + + MediaDirectory::eraseAll(DboSession()); + MediaDirectory::create(DboSession(), boost::any_cast(value(MediaDirectoryField)).toUTF8()); + + auto updatePeriodRow = getUpdatePeriodModelRow( boost::any_cast(value(UpdatePeriodField))); + assert(updatePeriodRow); + Scanner::setUpdatePeriod(DboSession(), updatePeriod(*updatePeriodRow)); + + auto startTimeRow = getUpdateStartTimeModelRow( boost::any_cast(value(UpdateStartTimeField))); + assert(startTimeRow); + Scanner::setUpdateStartTime(DboSession(), updateStartTime(*startTimeRow)); + } + + boost::optional getUpdatePeriodModelRow(Wt::WString value) + { + for (int i = 0; i < _updatePeriodModel->rowCount(); ++i) + { + if (updatePeriodString(i) == value) + return i; + } + + return boost::none; + } + + boost::optional getUpdatePeriodModelRow(Scanner::UpdatePeriod period) + { + for (int i = 0; i < _updatePeriodModel->rowCount(); ++i) + { + if (updatePeriod(i) == period) + return i; + } + + return boost::none; + } + + Scanner::UpdatePeriod updatePeriod(int row) + { + return boost::any_cast + (_updatePeriodModel->data(_updatePeriodModel->index(row, 0), Wt::UserRole)); + } + + Wt::WString updatePeriodString(int row) + { + return boost::any_cast + (_updatePeriodModel->data(_updatePeriodModel->index(row, 0), Wt::DisplayRole)); + } + + + boost::optional getUpdateStartTimeModelRow(Wt::WString value) + { + for (int i = 0; i < _updateStartTimeModel->rowCount(); ++i) + { + if (updateStartTimeString(i) == value) + return i; + } + + return boost::none; + } + + boost::optional getUpdateStartTimeModelRow(boost::posix_time::time_duration startTime) + { + for (int i = 0; i < _updateStartTimeModel->rowCount(); ++i) + { + if (updateStartTime(i) == startTime) + return i; + } + + return boost::none; + } + + boost::posix_time::time_duration updateStartTime(int row) + { + return boost::any_cast + (_updateStartTimeModel->data(_updateStartTimeModel->index(row, 0), Wt::UserRole)); + } + + Wt::WString updateStartTimeString(int row) + { + return boost::any_cast + (_updateStartTimeModel->data(_updateStartTimeModel->index(row, 0), Wt::DisplayRole)); + } + + + private: + + void initializeModels() { + + _updatePeriodModel = new Wt::WStringListModel(this); + + _updatePeriodModel->addString(Wt::WString::tr("msg-update-period-never")); + _updatePeriodModel->setData(0, 0, Scanner::UpdatePeriod::Never, Wt::UserRole); + + _updatePeriodModel->addString(Wt::WString::tr("msg-update-period-daily")); + _updatePeriodModel->setData(1, 0, Scanner::UpdatePeriod::Daily, Wt::UserRole); + + _updatePeriodModel->addString(Wt::WString::tr("msg-update-period-weekly")); + _updatePeriodModel->setData(2, 0, Scanner::UpdatePeriod::Weekly, Wt::UserRole); + + _updatePeriodModel->addString(Wt::WString::tr("msg-update-period-monthly")); + _updatePeriodModel->setData(3, 0, Scanner::UpdatePeriod::Monthly, Wt::UserRole); + + _updateStartTimeModel = new Wt::WStringListModel(this); + + for (std::size_t i = 0; i < 24; ++i) + { + boost::posix_time::time_duration dur = boost::posix_time::hours(i); + + boost::posix_time::time_facet* facet = new boost::posix_time::time_facet("%H:%M"); + facet->time_duration_format("%H:%M"); + + std::ostringstream oss; + oss.imbue(std::locale(oss.getloc(), facet)); + + oss << dur; + + _updateStartTimeModel->addString( oss.str() ); + _updateStartTimeModel->setData(i, 0, dur, Wt::UserRole); + } + + } + + Wt::WValidator *createMandatoryValidator() + { + Wt::WValidator* v = new Wt::WValidator(); + v->setMandatory(true); + return v; + } + + Wt::WStringListModel* _updatePeriodModel; + Wt::WStringListModel* _updateStartTimeModel; + +}; + +const Wt::WFormModel::Field DatabaseModel::MediaDirectoryField = "media-directory"; +const Wt::WFormModel::Field DatabaseModel::UpdatePeriodField = "update-period"; +const Wt::WFormModel::Field DatabaseModel::UpdateStartTimeField = "update-start-time"; + + +DatabaseView::DatabaseView(Wt::WContainerWidget *parent) +: Wt::WTemplateFormView(parent) +{ + _model = new DatabaseModel(this); + + setTemplateText(tr("template-settings-database")); + addFunction("tr", &WTemplate::Functions::tr); + addFunction("id", &WTemplate::Functions::id); + + // Media Directory + Wt::WLineEdit *mediaDirectoryEdit = new Wt::WLineEdit(); + setFormWidget(DatabaseModel::MediaDirectoryField, mediaDirectoryEdit); + + // Update Period + Wt::WComboBox *updatePeriodCB = new Wt::WComboBox(); + setFormWidget(DatabaseModel::UpdatePeriodField, updatePeriodCB); + updatePeriodCB->setModel(_model->updatePeriodModel()); + + // Update Start Time + Wt::WComboBox *updateStartTimeCB = new Wt::WComboBox(); + setFormWidget(DatabaseModel::UpdateStartTimeField, updateStartTimeCB); + updateStartTimeCB->setModel(_model->updateStartTimeModel()); + + // Buttons + + Wt::WPushButton *saveBtn = new Wt::WPushButton(Wt::WString::tr("msg-btn-apply")); + bindWidget("apply-btn", saveBtn); + saveBtn->setStyleClass("btn-primary"); + + Wt::WPushButton *discardBtn = new Wt::WPushButton(Wt::WString::tr("msg-btn-discard")); + bindWidget("discard-btn", discardBtn); + + saveBtn->clicked().connect(this, &DatabaseView::processSave); + discardBtn->clicked().connect(this, &DatabaseView::processDiscard); + + updateView(_model); +} + +void +DatabaseView::processDiscard() +{ + _model->loadData(); + _model->validate(); + updateView(_model); +} + +void +DatabaseView::processSave() +{ + updateModel(_model); + + if (_model->validate()) { + _model->saveData(); + +// _sigChanged.emit(); + } + // Udate the view: Delete any validation message in the view, etc. + updateView(_model); +} + + +} // namespace Settings +} // namespace UserInterface + + diff --git a/src/ui/settings/DatabaseView.hpp b/src/ui/settings/DatabaseView.hpp new file mode 100644 index 00000000..ad82178c --- /dev/null +++ b/src/ui/settings/DatabaseView.hpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2018 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 . + */ + +#pragma once + +#include +#include +#include +#include + +namespace UserInterface { +namespace Settings { + +class DatabaseModel; + +class DatabaseView : public Wt::WTemplateFormView +{ + public: + DatabaseView(Wt::WContainerWidget *parent = 0); + + Wt::Signal& changed() { return _sigChanged; } + + private: + + Wt::Signal _sigChanged; + + void processSave(); + void processDiscard(); + void processImmediateScan(); + + Wt::WText *_applyInfo; + DatabaseModel *_model; +}; + + +} // namespace Settings +} // namespace UserInterface + From 2db46260ab99f37b827ac37408c38a47a2845986 Mon Sep 17 00:00:00 2001 From: emeric Date: Sun, 4 Mar 2018 15:19:44 +0100 Subject: [PATCH 058/195] Made database scan work, added immediate scan option --- approot/messages.xml | 2 +- approot/settings.xml | 2 +- src/database/Track.cpp | 6 +- src/main/main.cpp | 3 +- src/scanner/MediaScanner.cpp | 111 ++++++++++++++++++------------- src/scanner/MediaScanner.hpp | 15 ++--- src/ui/LmsApplication.cpp | 12 +++- src/ui/LmsApplication.hpp | 12 +++- src/ui/settings/DatabaseView.cpp | 10 ++- 9 files changed, 105 insertions(+), 68 deletions(-) diff --git a/approot/messages.xml b/approot/messages.xml index 6bf0010a..b3e1146e 100644 --- a/approot/messages.xml +++ b/approot/messages.xml @@ -30,6 +30,6 @@ Not a directory Apply Discard - +Scan now! diff --git a/approot/settings.xml b/approot/settings.xml index 75f4c661..f5107fcc 100644 --- a/approot/settings.xml +++ b/approot/settings.xml @@ -45,7 +45,7 @@
- ${apply-btn class="btn-primary"} ${discard-btn} + ${apply-btn class="btn-primary"} ${discard-btn} ${immediate-scan-btn class="btn-info"}
diff --git a/src/database/Track.cpp b/src/database/Track.cpp index d913980f..d66327f0 100644 --- a/src/database/Track.cpp +++ b/src/database/Track.cpp @@ -295,12 +295,12 @@ Cluster::pointer ClusterType::getCluster(std::string name) const { assert(self()); - assert(self()->id() != Wt::Dbo::dbo_traits::invalidId() ); + assert(self()->id() != Wt::Dbo::dbo_traits::invalidId() ); assert(session()); return session()->find() .where("name = ?").bind(name) - .where("cluster_type_id = ").bind(self()->id()); + .where("cluster_type_id = ?").bind(self()->id()); } std::vector @@ -311,7 +311,7 @@ ClusterType::getClusters() const assert(session()); Wt::Dbo::collection res = session()->find() - .where("cluster_type_id = ").bind(self()->id()) + .where("cluster_type_id = ?").bind(self()->id()) .orderBy("name"); return std::vector(res.begin(), res.end()); diff --git a/src/main/main.cpp b/src/main/main.cpp index 2901d976..4715640c 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -129,7 +129,7 @@ int main(int argc, char* argv[]) // bind entry point server.addEntryPoint(Wt::Application, boost::bind(UserInterface::LmsApplication::create, - _1, boost::ref(*connectionPool))); + _1, boost::ref(*connectionPool), boost::ref(scanner))); // Start LMS_LOG(MAIN, INFO) << "Starting Media scanner..."; @@ -149,6 +149,7 @@ int main(int argc, char* argv[]) LMS_LOG(MAIN, INFO) << "Stopping database updater..."; scanner.stop(); + LMS_LOG(MAIN, INFO) << "Clean stop!"; res = EXIT_SUCCESS; } catch( libconfig::FileIOException& e) diff --git a/src/scanner/MediaScanner.cpp b/src/scanner/MediaScanner.cpp index 7f1239d8..2e56944f 100644 --- a/src/scanner/MediaScanner.cpp +++ b/src/scanner/MediaScanner.cpp @@ -155,7 +155,7 @@ MediaScanner::start(void) _running = true; // post some jobs in the io_service - processNextJob(); + scheduleScan(); _ioService.start(); } @@ -171,50 +171,63 @@ MediaScanner::stop(void) } void -MediaScanner::processNextJob(void) +MediaScanner::scheduleImmediateScan() { - if (Setting::getBool(_db.getSession(), "manual_scan_requested", false)) + _ioService.post([=]() { - LMS_LOG(DBUPDATER, INFO) << "Manual scan requested!"; + LMS_LOG(DBUPDATER, INFO) << "Schedule immediate scan"; scheduleScan( boost::posix_time::seconds(0) ); - } - else + }); +} + +void +MediaScanner::reschedule() +{ + _ioService.post([=]() { - boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); - boost::posix_time::time_duration startTime = getUpdateStartTime(_db.getSession()); + LMS_LOG(DBUPDATER, INFO) << "Rescheduling scan"; + scheduleScan(); + }); +} - boost::gregorian::date nextScanDate; +void +MediaScanner::scheduleScan() +{ + boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); + boost::posix_time::time_duration startTime = getUpdateStartTime(_db.getSession()); - switch ( getUpdatePeriod(_db.getSession()) ) - { - case UpdatePeriod::Daily: - if (now.time_of_day() < startTime) - nextScanDate = now.date(); - else - nextScanDate = getNextDay(now.date()); - break; + boost::gregorian::date nextScanDate; - case UpdatePeriod::Weekly: - if (now.time_of_day() < startTime && now.date().day_of_week() == 1) - nextScanDate = now.date(); - else - nextScanDate = getNextMonday(now.date()); - break; + switch ( getUpdatePeriod(_db.getSession()) ) + { + case UpdatePeriod::Daily: + if (now.time_of_day() < startTime) + nextScanDate = now.date(); + else + nextScanDate = getNextDay(now.date()); + break; - case UpdatePeriod::Monthly: - if (now.time_of_day() < startTime && now.date().day() == 1) - nextScanDate = now.date(); - else - nextScanDate = getNextFirstOfMonth(now.date()); - break; + case UpdatePeriod::Weekly: + if (now.time_of_day() < startTime && now.date().day_of_week() == 1) + nextScanDate = now.date(); + else + nextScanDate = getNextMonday(now.date()); + break; - case UpdatePeriod::Never: - break; - } + case UpdatePeriod::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, startTime) ); + case UpdatePeriod::Never: + LMS_LOG(DBUPDATER, INFO) << "Auto scan disabled!"; + break; } + + if (!nextScanDate.is_special()) + scheduleScan( boost::posix_time::ptime (nextScanDate, startTime) ); } void @@ -222,7 +235,7 @@ MediaScanner::scheduleScan( boost::posix_time::time_duration duration) { LMS_LOG(DBUPDATER, INFO) << "Scheduling next scan in " << duration; _scheduleTimer.expires_from_now(duration); - _scheduleTimer.async_wait( boost::bind( &MediaScanner::process, this, boost::asio::placeholders::error) ); + _scheduleTimer.async_wait( boost::bind( &MediaScanner::scan, this, boost::asio::placeholders::error) ); } void @@ -230,11 +243,11 @@ MediaScanner::scheduleScan( boost::posix_time::ptime time) { LMS_LOG(DBUPDATER, INFO) << "Scheduling next scan at " << time; _scheduleTimer.expires_at(time); - _scheduleTimer.async_wait( boost::bind( &MediaScanner::process, this, boost::asio::placeholders::error) ); + _scheduleTimer.async_wait( boost::bind( &MediaScanner::scan, this, boost::asio::placeholders::error) ); } void -MediaScanner::process(boost::system::error_code err) +MediaScanner::scan(boost::system::error_code err) { if (err) return; @@ -250,9 +263,9 @@ MediaScanner::process(boost::system::error_code err) if (!_running) break; - LMS_LOG(DBUPDATER, INFO) << "Processing root directory '" << rootDirectory << "'..."; - processRootDirectory(rootDirectory, stats); - LMS_LOG(DBUPDATER, INFO) << "Processing root directory '" << rootDirectory << "' DONE"; + LMS_LOG(DBUPDATER, INFO) << "scaning root directory '" << rootDirectory << "'..."; + scanRootDirectory(rootDirectory, stats); + LMS_LOG(DBUPDATER, INFO) << "scaning root directory '" << rootDirectory << "' DONE"; } if (_running) @@ -269,9 +282,8 @@ MediaScanner::process(boost::system::error_code err) if (_running) { Setting::setTime(_db.getSession(), "last_scan", now); - Setting::setBool(_db.getSession(), "manual_scan_requested", false); - processNextJob(); + scheduleScan(); scanComplete().emit(stats); } @@ -373,12 +385,19 @@ MediaScanner::getClusters( const MetaData::Clusters& clustersNames) for (auto clusterNames : clustersNames) { ClusterType::pointer clusterType = ClusterType::getByName(_db.getSession(), clusterNames.first); + bool newType = !clusterType; + if (!clusterType) clusterType = ClusterType::create(_db.getSession(), clusterNames.first); + std::cout << "Cluster type = " << clusterType.id() << "\n"; for (auto clusterName : clusterNames.second) { - Cluster::pointer cluster = clusterType->getCluster(clusterName); + Cluster::pointer cluster; + + if (!newType) + cluster = clusterType->getCluster(clusterName); + if (!cluster) cluster = Cluster::create(_db.getSession(), clusterType, clusterName); @@ -390,7 +409,7 @@ MediaScanner::getClusters( const MetaData::Clusters& clustersNames) } void -MediaScanner::processAudioFile( const boost::filesystem::path& file, Stats& stats) +MediaScanner::scanAudioFile( const boost::filesystem::path& file, Stats& stats) { boost::posix_time::ptime lastWriteTime (boost::posix_time::from_time_t( boost::filesystem::last_write_time( file ) ) ); @@ -601,7 +620,7 @@ MediaScanner::processAudioFile( const boost::filesystem::path& file, Stats& stat } void -MediaScanner::processRootDirectory(boost::filesystem::path rootDirectory, Stats& stats) +MediaScanner::scanRootDirectory(boost::filesystem::path rootDirectory, Stats& stats) { boost::system::error_code ec; @@ -619,7 +638,7 @@ MediaScanner::processRootDirectory(boost::filesystem::path rootDirectory, Stats& if (boost::filesystem::is_regular(path)) { if (isFileSupported(path, _fileExtensions)) - processAudioFile(path, stats ); + scanAudioFile(path, stats ); } } } diff --git a/src/scanner/MediaScanner.hpp b/src/scanner/MediaScanner.hpp index 6d319f0b..902dc29b 100644 --- a/src/scanner/MediaScanner.hpp +++ b/src/scanner/MediaScanner.hpp @@ -53,6 +53,9 @@ class MediaScanner void stop(); void restart(); + void scheduleImmediateScan(); + void reschedule(); + struct Stats { std::size_t nbNoChange = 0; // no change since last scan @@ -82,14 +85,14 @@ class MediaScanner private: // Job handling - void processNextJob(); + void scheduleScan(); 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); + void scan(boost::system::error_code ec); - void processRootDirectory( boost::filesystem::path rootDirectory, Stats& stats); + void scanRootDirectory( boost::filesystem::path rootDirectory, Stats& stats); // Helpers Database::Artist::pointer getArtist( const boost::filesystem::path& file, const std::string& name, const std::string& MBID); @@ -100,11 +103,7 @@ class MediaScanner // Audio void checkAudioFiles( Stats& stats ); void checkDuplicatedAudioFiles( 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); + void scanAudioFile( const boost::filesystem::path& file, Stats& stats); bool _running; Wt::WIOService _ioService; diff --git a/src/ui/LmsApplication.cpp b/src/ui/LmsApplication.cpp index dcfd42ff..3e935f67 100644 --- a/src/ui/LmsApplication.cpp +++ b/src/ui/LmsApplication.cpp @@ -46,13 +46,13 @@ namespace skeletons { namespace UserInterface { Wt::WApplication* -LmsApplication::create(const Wt::WEnvironment& env, Wt::Dbo::SqlConnectionPool& connectionPool) +LmsApplication::create(const Wt::WEnvironment& env, Wt::Dbo::SqlConnectionPool& connectionPool, Scanner::MediaScanner& scanner) { /* * You could read information from the environment to decide whether * the user has permission to start a new application */ - return new LmsApplication(env, connectionPool); + return new LmsApplication(env, connectionPool, scanner); } LmsApplication* @@ -67,9 +67,10 @@ LmsApplication::instance() * constructor so it is typically also an argument for your custom * application constructor. */ -LmsApplication::LmsApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnectionPool& connectionPool) +LmsApplication::LmsApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnectionPool& connectionPool, Scanner::MediaScanner& scanner) : Wt::WApplication(env), _db(connectionPool), + _scanner(scanner), _imageResource(nullptr), _transcodeResource(nullptr) { @@ -141,6 +142,11 @@ TranscodeResource* SessionTranscodeResource() return LmsApplication::instance()->getTranscodeResource(); } +Scanner::MediaScanner& MediaScanner() +{ + return LmsApplication::instance()->getMediaScanner(); +} + void LmsApplication::goHome() { diff --git a/src/ui/LmsApplication.hpp b/src/ui/LmsApplication.hpp index de460e4e..e36fcbee 100644 --- a/src/ui/LmsApplication.hpp +++ b/src/ui/LmsApplication.hpp @@ -26,6 +26,8 @@ #include #include "database/DatabaseHandler.hpp" +#include "scanner/MediaScanner.hpp" + #include "resource/ImageResource.hpp" #include "resource/TranscodeResource.hpp" @@ -34,26 +36,29 @@ namespace UserInterface { class LmsApplication : public Wt::WApplication { public: - static Wt::WApplication *create(const Wt::WEnvironment& env, Wt::Dbo::SqlConnectionPool& connectionPool); + static Wt::WApplication *create(const Wt::WEnvironment& env, Wt::Dbo::SqlConnectionPool& connectionPool, Scanner::MediaScanner& scanner); static LmsApplication* instance(); - LmsApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnectionPool& connectionPool); - // Session application data ImageResource* getImageResource() { return _imageResource; } TranscodeResource* getTranscodeResource() { return _transcodeResource; } Database::Handler& getDbHandler() { return _db;} + Scanner::MediaScanner& getMediaScanner() { return _scanner; } + // Utils void goHome(); private: + LmsApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnectionPool& connectionPool, Scanner::MediaScanner& scanner); + void handleAuthEvent(void); void createFirstConnectionUI(); void createLmsUI(); Database::Handler _db; + Scanner::MediaScanner& _scanner; ImageResource* _imageResource; TranscodeResource* _transcodeResource; }; @@ -70,6 +75,7 @@ Database::User::pointer CurrentUser(); ImageResource *SessionImageResource(); TranscodeResource *SessionTranscodeResource(); +Scanner::MediaScanner& MediaScanner(); } // namespace UserInterface diff --git a/src/ui/settings/DatabaseView.cpp b/src/ui/settings/DatabaseView.cpp index 355b3b25..712794c8 100644 --- a/src/ui/settings/DatabaseView.cpp +++ b/src/ui/settings/DatabaseView.cpp @@ -258,13 +258,19 @@ DatabaseView::DatabaseView(Wt::WContainerWidget *parent) Wt::WPushButton *saveBtn = new Wt::WPushButton(Wt::WString::tr("msg-btn-apply")); bindWidget("apply-btn", saveBtn); - saveBtn->setStyleClass("btn-primary"); Wt::WPushButton *discardBtn = new Wt::WPushButton(Wt::WString::tr("msg-btn-discard")); bindWidget("discard-btn", discardBtn); + Wt::WPushButton *immScanBtn = new Wt::WPushButton(Wt::WString::tr("msg-btn-immediate-scan")); + bindWidget("immediate-scan-btn", immScanBtn); + saveBtn->clicked().connect(this, &DatabaseView::processSave); discardBtn->clicked().connect(this, &DatabaseView::processDiscard); + immScanBtn->clicked().connect(std::bind([=] () + { + MediaScanner().scheduleImmediateScan(); + })); updateView(_model); } @@ -285,7 +291,7 @@ DatabaseView::processSave() if (_model->validate()) { _model->saveData(); -// _sigChanged.emit(); + MediaScanner().reschedule(); } // Udate the view: Delete any validation message in the view, etc. updateView(_model); From c0c5273e9266698375918146e51278c2d4985508 Mon Sep 17 00:00:00 2001 From: emeric Date: Sun, 4 Mar 2018 15:34:33 +0100 Subject: [PATCH 059/195] Remove orphaned cluster types --- src/database/Track.cpp | 9 +++++++++ src/database/Track.hpp | 1 + src/scanner/MediaScanner.cpp | 9 +++++++++ 3 files changed, 19 insertions(+) diff --git a/src/database/Track.cpp b/src/database/Track.cpp index d66327f0..a222e77a 100644 --- a/src/database/Track.cpp +++ b/src/database/Track.cpp @@ -271,6 +271,15 @@ ClusterType::ClusterType(std::string name) { } +std::vector +ClusterType::getAllOrphans(Wt::Dbo::Session& session) +{ + Wt::Dbo::collection res = session.query>("select c_t from cluster_type c_t LEFT OUTER JOIN cluster c ON c_t.id = c.cluster_type_id WHERE c.id IS NULL"); + + return std::vector(res.begin(), res.end()); +} + + ClusterType::pointer ClusterType::getByName(Wt::Dbo::Session& session, std::string name) { diff --git a/src/database/Track.hpp b/src/database/Track.hpp index 878ef796..3bec8ee9 100644 --- a/src/database/Track.hpp +++ b/src/database/Track.hpp @@ -96,6 +96,7 @@ class ClusterType : public Wt::Dbo::Dbo ClusterType() {} ClusterType(std::string name); + static std::vector getAllOrphans(Wt::Dbo::Session& session); static pointer getByName(Wt::Dbo::Session& session, std::string name); static std::vector getAll(Wt::Dbo::Session& session); diff --git a/src/scanner/MediaScanner.cpp b/src/scanner/MediaScanner.cpp index 2e56944f..e4b12f43 100644 --- a/src/scanner/MediaScanner.cpp +++ b/src/scanner/MediaScanner.cpp @@ -725,6 +725,7 @@ MediaScanner::checkAudioFiles( Stats& stats ) { Wt::Dbo::Transaction transaction(_db.getSession()); + // TODO better query for this // Now process orphan Cluster (no track) auto clusters = Cluster::getAll(_db.getSession()); for (auto cluster : clusters) @@ -735,6 +736,14 @@ MediaScanner::checkAudioFiles( Stats& stats ) cluster.remove(); } } + + // Now process orphan cluster types + auto clusterTypes = ClusterType::getAllOrphans(_db.getSession()); + for (auto clusterType : clusterTypes) + { + LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan cluster type '" << clusterType->getName() << "'"; + clusterType.remove(); + } } LMS_LOG(DBUPDATER, DEBUG) << "Checking artists..."; From ceab623e5742dfa93394dbd659318600a1869299 Mon Sep 17 00:00:00 2001 From: emeric Date: Sun, 4 Mar 2018 15:36:39 +0100 Subject: [PATCH 060/195] Restored WAL to improve import perfs --- src/database/DatabaseHandler.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/database/DatabaseHandler.cpp b/src/database/DatabaseHandler.cpp index c063bbf0..f47fa8a8 100644 --- a/src/database/DatabaseHandler.cpp +++ b/src/database/DatabaseHandler.cpp @@ -113,7 +113,6 @@ Handler::Handler(Wt::Dbo::SqlConnectionPool& connectionPool) Wt::Dbo::Transaction transaction(_session); // Indexes - // _session.execute("PRAGMA journal_mode=WAL"); _session.execute("CREATE INDEX IF NOT EXISTS track_path_idx ON track(file_path)"); _session.execute("CREATE INDEX IF NOT EXISTS artist_name_idx ON artist(name)"); _session.execute("CREATE INDEX IF NOT EXISTS release_name_idx ON release(name)"); @@ -185,7 +184,7 @@ Handler::createConnectionPool(boost::filesystem::path p) Wt::Dbo::backend::Sqlite3 *connection = new Wt::Dbo::backend::Sqlite3(p.string()); -// connection->executeSql("pragma journal_mode=WAL"); + connection->executeSql("pragma journal_mode=WAL"); connection->setProperty("show-queries", "true"); return new Wt::Dbo::FixedSqlConnectionPool(connection, 1); From 3ac4d1b4f340fcfff2d5ed4c88a27070a64ed633 Mon Sep 17 00:00:00 2001 From: emeric Date: Sun, 4 Mar 2018 15:48:14 +0100 Subject: [PATCH 061/195] Fixed regression when adding all tracks to play queue --- src/ui/PlaylistView.cpp | 6 ++++++ src/ui/TracksView.cpp | 2 ++ 2 files changed, 8 insertions(+) diff --git a/src/ui/PlaylistView.cpp b/src/ui/PlaylistView.cpp index 4ee8f58f..c50d3925 100644 --- a/src/ui/PlaylistView.cpp +++ b/src/ui/PlaylistView.cpp @@ -21,6 +21,8 @@ #include #include +#include "utils/Logger.hpp" + #include "LmsApplication.hpp" #include "PlaylistView.hpp" @@ -65,6 +67,8 @@ Playlist::addTracks(const std::vector& tracks) // Use a "session" playlist in order to store the current playlist // so that the user can disconnect and get its playlist back + LMS_LOG(UI, DEBUG) << "Adding tracks to the current queue"; + auto playlist = Database::Playlist::get(DboSession(), currentPlaylistName, CurrentUser()); if (!playlist) { @@ -83,6 +87,8 @@ Playlist::addTracks(const std::vector& tracks) void Playlist::playTracks(const std::vector& tracks) { + LMS_LOG(UI, DEBUG) << "Emptying current queue to play new tracks"; + Wt::Dbo::Transaction transaction(DboSession()); auto playlist = Database::Playlist::get(DboSession(), currentPlaylistName, CurrentUser()); diff --git a/src/ui/TracksView.cpp b/src/ui/TracksView.cpp index 8240986d..db82be6a 100644 --- a/src/ui/TracksView.cpp +++ b/src/ui/TracksView.cpp @@ -51,6 +51,7 @@ Tracks::Tracks(Filters* filters, Wt::WContainerWidget* parent) container->bindWidget("play-btn", playBtn); playBtn->clicked().connect(std::bind([=] { + Wt::Dbo::Transaction transaction(DboSession()); tracksPlay.emit(getTracks()); })); @@ -58,6 +59,7 @@ Tracks::Tracks(Filters* filters, Wt::WContainerWidget* parent) container->bindWidget("add-btn", addBtn); addBtn->clicked().connect(std::bind([=] { + Wt::Dbo::Transaction transaction(DboSession()); tracksAdd.emit(getTracks()); })); From bbb988266def7679d3e9ae5796c16bb2611c0611 Mon Sep 17 00:00:00 2001 From: emeric Date: Sun, 4 Mar 2018 15:52:16 +0100 Subject: [PATCH 062/195] Better handler ClusterType creation --- src/scanner/MediaScanner.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/scanner/MediaScanner.cpp b/src/scanner/MediaScanner.cpp index e4b12f43..5c1b6e2e 100644 --- a/src/scanner/MediaScanner.cpp +++ b/src/scanner/MediaScanner.cpp @@ -385,18 +385,15 @@ MediaScanner::getClusters( const MetaData::Clusters& clustersNames) for (auto clusterNames : clustersNames) { ClusterType::pointer clusterType = ClusterType::getByName(_db.getSession(), clusterNames.first); - bool newType = !clusterType; - if (!clusterType) + { clusterType = ClusterType::create(_db.getSession(), clusterNames.first); + _db.getSession().flush(); // Make sure the newly createdType has an id + } - std::cout << "Cluster type = " << clusterType.id() << "\n"; for (auto clusterName : clusterNames.second) { - Cluster::pointer cluster; - - if (!newType) - cluster = clusterType->getCluster(clusterName); + Cluster::pointer cluster = clusterType->getCluster(clusterName); if (!cluster) cluster = Cluster::create(_db.getSession(), clusterType, clusterName); From a3fd11c85b3e53bd8e67442780df6963d2e74476 Mon Sep 17 00:00:00 2001 From: emeric Date: Sun, 4 Mar 2018 16:09:03 +0100 Subject: [PATCH 063/195] Create a fake user if not existing --- src/database/User.cpp | 6 ++++++ src/database/User.hpp | 3 +++ src/ui/LmsApplication.cpp | 4 ++++ src/ui/PlaylistView.cpp | 2 +- 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/database/User.cpp b/src/database/User.cpp index 751e614e..4024e2d4 100644 --- a/src/database/User.cpp +++ b/src/database/User.cpp @@ -83,6 +83,12 @@ User::getAll(Wt::Dbo::Session& session) return std::vector(res.begin(), res.end()); } +User::pointer +User::create(Wt::Dbo::Session& session) +{ + return session.add(new User()); +} + User::pointer User::getById(Wt::Dbo::Session& session, id_type id) { diff --git a/src/database/User.hpp b/src/database/User.hpp index de065e68..e772f404 100644 --- a/src/database/User.hpp +++ b/src/database/User.hpp @@ -64,6 +64,9 @@ class User typedef Wt::Dbo::ptr pointer; + // utility + static pointer create(Wt::Dbo::Session& session); + // accessors static pointer getById(Wt::Dbo::Session& session, id_type id); static std::vector getAll(Wt::Dbo::Session& session); diff --git a/src/ui/LmsApplication.cpp b/src/ui/LmsApplication.cpp index 3e935f67..db4ee7b4 100644 --- a/src/ui/LmsApplication.cpp +++ b/src/ui/LmsApplication.cpp @@ -101,6 +101,10 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnecti Wt::Dbo::Transaction transaction(DboSession()); firstConnection = (Database::User::getAll(DboSession()).size() == 0); + + // Create a fake user + // TODO settings + Database::User::create(DboSession()); } LMS_LOG(UI, DEBUG) << "Creating root widget. First connection = " << std::boolalpha << firstConnection; diff --git a/src/ui/PlaylistView.cpp b/src/ui/PlaylistView.cpp index c50d3925..5cc4350c 100644 --- a/src/ui/PlaylistView.cpp +++ b/src/ui/PlaylistView.cpp @@ -120,7 +120,7 @@ Playlist::addSome() return; bool moreResults; - auto tracks = playlist->getTracks(_entriesContainer->count(), 20, moreResults); + auto tracks = playlist->getTracks(_entriesContainer->count(), 50, moreResults); for (auto track : tracks) { Wt::WTemplate* entry = new Wt::WTemplate(Wt::WString::tr("template-playlist-entry"), _entriesContainer); From da138aa06e82295bd381efe2b24f88a77c8529e6 Mon Sep 17 00:00:00 2001 From: emeric Date: Sun, 4 Mar 2018 20:25:03 +0100 Subject: [PATCH 064/195] Renamed playlist to playqueue --- Makefile.am | 2 +- approot/messages.xml | 2 +- approot/{playlist.xml => playqueue.xml} | 16 +++---- src/Makefile.am | 2 +- src/ui/LmsApplication.cpp | 20 ++++----- .../{PlaylistView.cpp => PlayQueueView.cpp} | 44 +++++++++---------- .../{PlaylistView.hpp => PlayQueueView.hpp} | 4 +- 7 files changed, 45 insertions(+), 45 deletions(-) rename approot/{playlist.xml => playqueue.xml} (66%) rename src/ui/{PlaylistView.cpp => PlayQueueView.cpp} (75%) rename src/ui/{PlaylistView.hpp => PlayQueueView.hpp} (92%) diff --git a/Makefile.am b/Makefile.am index 95bf74bc..caa0718b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -21,7 +21,7 @@ lms_approot_DATA = \ approot/artist.xml \ approot/mediaplayer.xml \ approot/messages.xml \ - approot/playlist.xml \ + approot/playqueue.xml \ approot/releases.xml \ approot/release.xml \ approot/settings.xml \ diff --git a/approot/messages.xml b/approot/messages.xml index b3e1146e..1ebc8dcf 100644 --- a/approot/messages.xml +++ b/approot/messages.xml @@ -6,7 +6,7 @@ Artists Albums Tracks -Playlist +Play Queue Add filter Search... diff --git a/approot/playlist.xml b/approot/playqueue.xml similarity index 66% rename from approot/playlist.xml rename to approot/playqueue.xml index 266baa33..a48b1570 100644 --- a/approot/playlist.xml +++ b/approot/playqueue.xml @@ -2,36 +2,36 @@ - + - + - + - + ${entries} ${show-more} - + - + - + ${pos} ${name} ${}${release-name}${} ${}${artist-name}${} ${play-btn} ${del-btn} diff --git a/src/Makefile.am b/src/Makefile.am index ec57c4a9..b958dfca 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -28,7 +28,7 @@ lms_SOURCES = \ $(srcdir)/ui/HomeView.cpp \ $(srcdir)/ui/LmsApplication.cpp \ $(srcdir)/ui/MediaPlayer.cpp \ - $(srcdir)/ui/PlaylistView.cpp \ + $(srcdir)/ui/PlayQueueView.cpp \ $(srcdir)/ui/ReleasesView.cpp \ $(srcdir)/ui/ReleaseView.cpp \ $(srcdir)/ui/TracksView.cpp \ diff --git a/src/ui/LmsApplication.cpp b/src/ui/LmsApplication.cpp index db4ee7b4..2b0d4baa 100644 --- a/src/ui/LmsApplication.cpp +++ b/src/ui/LmsApplication.cpp @@ -33,7 +33,7 @@ #include "Explore.hpp" #include "HomeView.hpp" #include "MediaPlayer.hpp" -#include "PlaylistView.hpp" +#include "PlayQueueView.hpp" #include "settings/DatabaseView.hpp" @@ -87,7 +87,7 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnecti messageResourceBundle().use(appRoot() + "artists"); messageResourceBundle().use(appRoot() + "mediaplayer"); messageResourceBundle().use(appRoot() + "messages"); - messageResourceBundle().use(appRoot() + "playlist"); + messageResourceBundle().use(appRoot() + "playqueue"); messageResourceBundle().use(appRoot() + "release"); messageResourceBundle().use(appRoot() + "releases"); messageResourceBundle().use(appRoot() + "settings"); @@ -192,7 +192,7 @@ enum IdxRoot { IdxHome = 0, IdxExplore, - IdxPlaylist, + IdxPlayQueue, IdxSettingsDatabase, }; @@ -209,7 +209,7 @@ handlePathChange(Wt::WStackedWidget* stack) { "/releases", IdxExplore }, { "/release", IdxExplore }, { "/tracks", IdxExplore }, - { "/playlist", IdxPlaylist }, + { "/playqueue", IdxPlayQueue }, { "/settings/database", IdxSettingsDatabase }, }; @@ -271,8 +271,8 @@ LmsApplication::handleAuthEvent(void) menuItem->setSelectable(false); } { - auto menuItem = menu->insertItem(3, Wt::WString::tr("msg-playlist")); - menuItem->setLink(Wt::WLink(Wt::WLink::InternalPath, "/playlist")); + auto menuItem = menu->insertItem(3, Wt::WString::tr("msg-playqueue")); + menuItem->setLink(Wt::WLink(Wt::WLink::InternalPath, "/playqueue")); menuItem->setSelectable(false); } { @@ -298,20 +298,20 @@ LmsApplication::handleAuthEvent(void) auto explore = new Explore(); mainStack->addWidget(explore); - auto playlist = new Playlist(); - mainStack->addWidget(playlist); + auto playqueue = new PlayQueue(); + mainStack->addWidget(playqueue); auto databaseSettings = new Settings::DatabaseView(); mainStack->addWidget(databaseSettings); explore->tracksAdd.connect(std::bind([=] (std::vector tracks) { - playlist->addTracks(tracks); + playqueue->addTracks(tracks); }, std::placeholders::_1)); explore->tracksPlay.connect(std::bind([=] (std::vector tracks) { - playlist->playTracks(tracks); + playqueue->playTracks(tracks); }, std::placeholders::_1)); // MediaPlayer diff --git a/src/ui/PlaylistView.cpp b/src/ui/PlayQueueView.cpp similarity index 75% rename from src/ui/PlaylistView.cpp rename to src/ui/PlayQueueView.cpp index 5cc4350c..db265b9a 100644 --- a/src/ui/PlaylistView.cpp +++ b/src/ui/PlayQueueView.cpp @@ -24,25 +24,25 @@ #include "utils/Logger.hpp" #include "LmsApplication.hpp" -#include "PlaylistView.hpp" +#include "PlayQueueView.hpp" namespace UserInterface { -static const std::string currentPlaylistName = "__current__playlist__"; +static const std::string currentPlayQueueName = "__current__playqueue__"; -Playlist::Playlist(Wt::WContainerWidget* parent) +PlayQueue::PlayQueue(Wt::WContainerWidget* parent) : Wt::WContainerWidget(parent) { - auto container = new Wt::WTemplate(Wt::WString::tr("template-playlist"), this); + auto container = new Wt::WTemplate(Wt::WString::tr("template-playqueue"), this); container->addFunction("tr", &Wt::WTemplate::Functions::tr); - auto saveBtn = new Wt::WText(Wt::WString::tr("btn-playlist-save-btn"), Wt::XHTMLText); + auto saveBtn = new Wt::WText(Wt::WString::tr("btn-playqueue-save-btn"), Wt::XHTMLText); container->bindWidget("save-btn", saveBtn); - auto loadBtn = new Wt::WText(Wt::WString::tr("btn-playlist-load-btn"), Wt::XHTMLText); + auto loadBtn = new Wt::WText(Wt::WString::tr("btn-playqueue-load-btn"), Wt::XHTMLText); container->bindWidget("load-btn", loadBtn); - auto clearBtn = new Wt::WText(Wt::WString::tr("btn-playlist-clear-btn"), Wt::XHTMLText); + auto clearBtn = new Wt::WText(Wt::WString::tr("btn-playqueue-clear-btn"), Wt::XHTMLText); container->bindWidget("clear-btn", clearBtn); _entriesContainer = new Wt::WContainerWidget(); @@ -62,17 +62,17 @@ Playlist::Playlist(Wt::WContainerWidget* parent) } void -Playlist::addTracks(const std::vector& tracks) +PlayQueue::addTracks(const std::vector& tracks) { - // Use a "session" playlist in order to store the current playlist - // so that the user can disconnect and get its playlist back + // Use a "session" playqueue in order to store the current playqueue + // so that the user can disconnect and get its playqueue back LMS_LOG(UI, DEBUG) << "Adding tracks to the current queue"; - auto playlist = Database::Playlist::get(DboSession(), currentPlaylistName, CurrentUser()); + auto playlist = Database::Playlist::get(DboSession(), currentPlayQueueName, CurrentUser()); if (!playlist) { - playlist = Database::Playlist::create(DboSession(), currentPlaylistName, false, CurrentUser()); + playlist = Database::Playlist::create(DboSession(), currentPlayQueueName, false, CurrentUser()); } std::size_t pos = playlist->getCount(); @@ -85,15 +85,15 @@ Playlist::addTracks(const std::vector& tracks) } void -Playlist::playTracks(const std::vector& tracks) +PlayQueue::playTracks(const std::vector& tracks) { LMS_LOG(UI, DEBUG) << "Emptying current queue to play new tracks"; Wt::Dbo::Transaction transaction(DboSession()); - auto playlist = Database::Playlist::get(DboSession(), currentPlaylistName, CurrentUser()); - if (playlist) - playlist.modify()->clear(); + auto playqueue = Database::Playlist::get(DboSession(), currentPlayQueueName, CurrentUser()); + if (playqueue) + playqueue.modify()->clear(); _entriesContainer->clear(); @@ -104,18 +104,18 @@ Playlist::playTracks(const std::vector& tracks) void -Playlist::refresh() +PlayQueue::refresh() { _entriesContainer->clear(); addSome(); } void -Playlist::addSome() +PlayQueue::addSome() { Wt::Dbo::Transaction transaction (DboSession()); - auto playlist = Database::Playlist::get(DboSession(), currentPlaylistName, CurrentUser()); + auto playlist = Database::Playlist::get(DboSession(), currentPlayQueueName, CurrentUser()); if (!playlist) return; @@ -123,7 +123,7 @@ Playlist::addSome() auto tracks = playlist->getTracks(_entriesContainer->count(), 50, moreResults); for (auto track : tracks) { - Wt::WTemplate* entry = new Wt::WTemplate(Wt::WString::tr("template-playlist-entry"), _entriesContainer); + Wt::WTemplate* entry = new Wt::WTemplate(Wt::WString::tr("template-playqueue-entry"), _entriesContainer); entry->bindString("pos", std::to_string(_entriesContainer->count())); entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::PlainText); @@ -147,11 +147,11 @@ Playlist::addSome() entry->bindWidget("release-name", releaseAnchor); } - auto playBtn = new Wt::WText(Wt::WString::tr("btn-playlist-entry-play-btn"), Wt::XHTMLText); + auto playBtn = new Wt::WText(Wt::WString::tr("btn-playqueue-entry-play-btn"), Wt::XHTMLText); entry->bindWidget("play-btn", playBtn); // TODO - auto addBtn = new Wt::WText(Wt::WString::tr("btn-playlist-entry-del-btn"), Wt::XHTMLText); + auto addBtn = new Wt::WText(Wt::WString::tr("btn-playqueue-entry-del-btn"), Wt::XHTMLText); entry->bindWidget("del-btn", addBtn); // TODO } diff --git a/src/ui/PlaylistView.hpp b/src/ui/PlayQueueView.hpp similarity index 92% rename from src/ui/PlaylistView.hpp rename to src/ui/PlayQueueView.hpp index 03db9226..9f8e932a 100644 --- a/src/ui/PlaylistView.hpp +++ b/src/ui/PlayQueueView.hpp @@ -26,10 +26,10 @@ namespace UserInterface { -class Playlist : public Wt::WContainerWidget +class PlayQueue : public Wt::WContainerWidget { public: - Playlist(Wt::WContainerWidget* parent = 0); + PlayQueue(Wt::WContainerWidget* parent = 0); void addTracks(const std::vector& tracks); void playTracks(const std::vector& tracks); From 711dc613aac9623205b253fb77ba51f94f27d0a9 Mon Sep 17 00:00:00 2001 From: emeric Date: Sun, 4 Mar 2018 22:35:08 +0100 Subject: [PATCH 065/195] Added a notify system --- approot/messages.xml | 4 ++++ src/ui/LmsApplication.cpp | 27 +++++++++++++++++++++++++++ src/ui/LmsApplication.hpp | 4 ++++ src/ui/settings/DatabaseView.cpp | 8 +++++++- 4 files changed, 42 insertions(+), 1 deletion(-) diff --git a/approot/messages.xml b/approot/messages.xml index 1ebc8dcf..14e4bc54 100644 --- a/approot/messages.xml +++ b/approot/messages.xml @@ -23,6 +23,7 @@ Media root directory Update period Update start time +Update start time Never Daily Weekly @@ -31,5 +32,8 @@ Apply Discard Scan now! +Scan launched! +New settings saved! +Scan finished, {1} imported file(s), {2} removed file(s)! diff --git a/src/ui/LmsApplication.cpp b/src/ui/LmsApplication.cpp index 2b0d4baa..496df353 100644 --- a/src/ui/LmsApplication.cpp +++ b/src/ui/LmsApplication.cpp @@ -318,6 +318,21 @@ LmsApplication::handleAuthEvent(void) auto player = new MediaPlayer(); main->bindWidget("player", player); + // Events from MediaScanner + // TODO: only if admin + enableUpdates(true); + std::string sessionId = this->sessionId(); + _scanner.scanComplete().connect(std::bind([=] (Scanner::MediaScanner::Stats stats) + { + Wt::WServer::instance()->post(sessionId, [=] + { + notify(Wt::WString::tr("msg-notify-scan-complete").arg(stats.nbAdded).arg(stats.nbRemoved)); + triggerUpdate(); + }); +// notify("TEST"); + }, std::placeholders::_1)); + + internalPathChanged().connect(std::bind([=] { handlePathChange(mainStack); @@ -326,6 +341,18 @@ LmsApplication::handleAuthEvent(void) handlePathChange(mainStack); } +void +LmsApplication::notify(const Wt::WString& message) +{ + LMS_LOG(UI, INFO) << "Notifying message '" << message.toUTF8() << "'"; + root()->addWidget(new Wt::WText(message)); +} + +void notify(const Wt::WString& message) +{ + LmsApplication::instance()->notify(message); +} + } // namespace UserInterface diff --git a/src/ui/LmsApplication.hpp b/src/ui/LmsApplication.hpp index e36fcbee..f3e4dd2b 100644 --- a/src/ui/LmsApplication.hpp +++ b/src/ui/LmsApplication.hpp @@ -48,6 +48,7 @@ class LmsApplication : public Wt::WApplication // Utils void goHome(); + void notify(const Wt::WString& message); private: @@ -77,6 +78,9 @@ TranscodeResource *SessionTranscodeResource(); Scanner::MediaScanner& MediaScanner(); +void notify(const Wt::WString& message); + + } // namespace UserInterface #endif diff --git a/src/ui/settings/DatabaseView.cpp b/src/ui/settings/DatabaseView.cpp index 712794c8..05dad344 100644 --- a/src/ui/settings/DatabaseView.cpp +++ b/src/ui/settings/DatabaseView.cpp @@ -270,6 +270,8 @@ DatabaseView::DatabaseView(Wt::WContainerWidget *parent) immScanBtn->clicked().connect(std::bind([=] () { MediaScanner().scheduleImmediateScan(); + + notify(Wt::WString::tr("msg-notify-scan-launched")); })); updateView(_model); @@ -288,11 +290,15 @@ DatabaseView::processSave() { updateModel(_model); - if (_model->validate()) { + if (_model->validate()) + { _model->saveData(); MediaScanner().reschedule(); + + notify(Wt::WString::tr("msg-notify-settings-saved")); } + // Udate the view: Delete any validation message in the view, etc. updateView(_model); } From 6ef33f69b6175f142f38745a9713df623f340c69 Mon Sep 17 00:00:00 2001 From: emeric Date: Sun, 4 Mar 2018 22:46:56 +0100 Subject: [PATCH 066/195] Better home template --- approot/messages.xml | 3 +++ src/ui/HomeView.cpp | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/approot/messages.xml b/approot/messages.xml index 14e4bc54..a0c99722 100644 --- a/approot/messages.xml +++ b/approot/messages.xml @@ -2,6 +2,9 @@ + +

Welcome!

+ Various artists Artists Albums diff --git a/src/ui/HomeView.cpp b/src/ui/HomeView.cpp index 730f981e..e97d8b33 100644 --- a/src/ui/HomeView.cpp +++ b/src/ui/HomeView.cpp @@ -31,7 +31,9 @@ namespace UserInterface { Home::Home(Wt::WContainerWidget *parent) : Wt::WContainerWidget(parent) { - addWidget(new Wt::WTemplate(Wt::WString::tr("template-home"))); + auto t = new Wt::WTemplate(Wt::WString::tr("template-home"), this); + t->addFunction("tr", &Wt::WTemplate::Functions::tr); + } } // namespace UserInterface From 7d081d39454163773e939da60d511d8f99c272c1 Mon Sep 17 00:00:00 2001 From: emeric Date: Sun, 4 Mar 2018 23:02:31 +0100 Subject: [PATCH 067/195] Updated TODO list --- TODO | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/TODO b/TODO index d55ee917..bcdc9f75 100644 --- a/TODO +++ b/TODO @@ -4,16 +4,16 @@ [Cover] - Handle preferred cover file names ("front.xxx", "cover.xxx", ...) -- Implement a cache and a grabber from some web service (mandatory for artists?) coverartarchive.org +- Implement a cache to improve speed [Database] - Optim, use SQL query to get the "genre" orphans - Use Inotify like system to watch modified/added files? - handle access rights problems (instead of aborting) - add a global play counter for tracks. This will help people to spot most popular files -- Use the WServer::post method to notify the end of the database scan? (with results?) -- Make clusters based on different metadata (albumgrouping, genre, mood, etc.). Make it configurable somewhere as it is very difficult to handle them all? +- Make cluster scan configurable as it is very difficult to handle them all? - Use albumartist when available (useful for compilations) +- Add AcousticBrainz support to create new kind of clusters [Metadata] - WMA covers: add support @@ -27,14 +27,20 @@ - Limit multi login from UI and API interfaces (limit per interface is acceptable) [UI] - - Add a download button to get the current playlist in a streamed zip file + - Add a download button to get the current playqueue in a streamed zip file - Save user prefs in local storage (volume, loop, shuffle, current playqueue, etc.) - Add keyboard shortcuts - Add a hint for the user to get the nature of tag (created from genre ? created from audio features?) - Implement a play queue - Implement a decent player - - Tags cloud for artist/release - Dedicate a color for each tag type + - Display only the most popular cluster of each cluster type? + +[Settings] + - Add a audio settings: format + bitrate to be used + - Add a user management view + - Add multi root directories support + - Add a first connection wizard [REST API] - Make a dedicated REST API. Maybe use the SubSonic API or Ampache API? From db0fe53aa38f20332937176dfce7778ac913c314 Mon Sep 17 00:00:00 2001 From: emeric Date: Mon, 5 Mar 2018 23:13:44 +0100 Subject: [PATCH 068/195] Better scan stats --- approot/messages.xml | 2 +- src/scanner/MediaScanner.cpp | 24 +++++++++++++----------- src/scanner/MediaScanner.hpp | 22 ++++++++++++++-------- src/ui/LmsApplication.cpp | 7 ++++++- 4 files changed, 34 insertions(+), 21 deletions(-) diff --git a/approot/messages.xml b/approot/messages.xml index a0c99722..03322b49 100644 --- a/approot/messages.xml +++ b/approot/messages.xml @@ -37,6 +37,6 @@ Scan now! Scan launched! New settings saved! -Scan finished, {1} imported file(s), {2} removed file(s)! +Scan complete: {1} total files, {2} additions, {3} deletions, {4} duplicates, {5} errors
diff --git a/src/scanner/MediaScanner.cpp b/src/scanner/MediaScanner.cpp index 5c1b6e2e..492ad6f4 100644 --- a/src/scanner/MediaScanner.cpp +++ b/src/scanner/MediaScanner.cpp @@ -271,7 +271,7 @@ MediaScanner::scan(boost::system::error_code err) if (_running) checkDuplicatedAudioFiles(stats); - LMS_LOG(DBUPDATER, INFO) << "Scan " << (_running ? "complete" : "aborted") << ". Changes = " << stats.nbChanges() << " (added = " << stats.nbAdded << ", removed = " << stats.nbRemoved << ", updated = " << stats.nbUpdated << "), Not changed = " << stats.nbNoChange << ", Scanned = " << stats.nbScanned << " (errors = " << stats.nbScanErrors << ", not imported = " << stats.nbNotImported << ")"; + LMS_LOG(DBUPDATER, INFO) << "Scan " << (_running ? "complete" : "aborted") << ". Changes = " << stats.nbChanges() << " (added = " << stats.additions << ", removed = " << stats.deletions << ", updated = " << stats.updates << "), Not changed = " << stats.skips << ", Scanned = " << stats.scans << " (errors = " << stats.scanErrors << ", not imported = " << stats.incompleteScans << "), duplicates = " << stats.nbDuplicates() << " (hash = " << stats.duplicateHashes << ", mbid = " << stats.duplicateMBID << ")"; // Update database stats boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); @@ -418,7 +418,7 @@ MediaScanner::scanAudioFile( const boost::filesystem::path& file, Stats& stats) if (track && track->getLastWriteTime() == lastWriteTime) { - stats.nbNoChange++; + stats.skips++; return; } } @@ -426,11 +426,11 @@ MediaScanner::scanAudioFile( const boost::filesystem::path& file, Stats& stats) boost::optional items = _metadataParser.parse(file); if (!items) { - stats.nbScanErrors++; + stats.scanErrors++; return; } - stats.nbScanned++; + stats.scans++; std::vector checksum ; computeCrc(file, checksum); @@ -451,9 +451,9 @@ MediaScanner::scanAudioFile( const boost::filesystem::path& file, Stats& stats) if (track) { track.remove(); - stats.nbRemoved++; + stats.deletions++; } - stats.nbNotImported++; + stats.incompleteScans++; return; } if ((*items).find(MetaData::Type::Duration) == (*items).end() @@ -465,9 +465,9 @@ MediaScanner::scanAudioFile( const boost::filesystem::path& file, Stats& stats) if (track) { track.remove(); - stats.nbRemoved++; + stats.deletions++; } - stats.nbNotImported++; + stats.incompleteScans++; return; } @@ -536,7 +536,7 @@ MediaScanner::scanAudioFile( const boost::filesystem::path& file, Stats& stats) // Create a new song track = Track::create(_db.getSession(), file); LMS_LOG(DBUPDATER, INFO) << "Adding '" << file << "'"; - stats.nbAdded++; + stats.additions++; } else { @@ -546,7 +546,7 @@ MediaScanner::scanAudioFile( const boost::filesystem::path& file, Stats& stats) for (auto cluster : track->getClusters()) cluster.remove(); - stats.nbUpdated++; + stats.updates++; } assert(track); @@ -713,7 +713,7 @@ MediaScanner::checkAudioFiles( Stats& stats ) if (track) { track.remove(); - stats.nbRemoved++; + stats.deletions++; } } } @@ -781,12 +781,14 @@ MediaScanner::checkDuplicatedAudioFiles(Stats& stats) for (Track::pointer track : tracks) { LMS_LOG(DBUPDATER, INFO) << "Found duplicated MBID [" << track->getMBID() << "], file: " << track->getPath() << " - " << track->getArtist()->getName() << " - " << track->getName(); + stats.duplicateMBID++; } tracks = Database::Track::getChecksumDuplicates(_db.getSession()); for (Track::pointer track : tracks) { LMS_LOG(DBUPDATER, INFO) << "Found duplicated checksum [" << bufferToString(track->getChecksum()) << "], file: " << track->getPath() << " - " << track->getArtist()->getName() << " - " << track->getName(); + stats.duplicateHashes++; } diff --git a/src/scanner/MediaScanner.hpp b/src/scanner/MediaScanner.hpp index 902dc29b..cb011c1b 100644 --- a/src/scanner/MediaScanner.hpp +++ b/src/scanner/MediaScanner.hpp @@ -58,15 +58,21 @@ class MediaScanner struct Stats { - std::size_t nbNoChange = 0; // no change since last scan - std::size_t nbScanned = 0; // total scanned filed - std::size_t nbScanErrors = 0; // cannot scan file - std::size_t nbNotImported = 0; // Scanned, but not imported (criteria not filled) - std::size_t nbAdded = 0; // Added in DB - std::size_t nbRemoved = 0; // removed from DB - std::size_t nbUpdated = 0; // updated file in DB + std::size_t skips = 0; // no change since last scan + std::size_t scans = 0; // actually scanned filed + std::size_t scanErrors = 0; // cannot scan file + std::size_t incompleteScans = 0; // Scanned, but not imported (criteria not filled) + std::size_t additions = 0; // Added in DB + std::size_t deletions = 0; // removed from DB + std::size_t updates = 0; // updated file in DB - std::size_t nbChanges() const { return nbAdded + nbRemoved + nbUpdated;} + std::size_t duplicateHashes = 0; // Same file hashes + std::size_t duplicateMBID = 0; // Same MBID + + std::size_t nbFiles() const { return skips + additions + updates; } + std::size_t nbChanges() const { return additions + deletions + updates; } + std::size_t nbErrors() const { return scanErrors + incompleteScans; } + std::size_t nbDuplicates() const { return duplicateHashes + duplicateMBID; } }; diff --git a/src/ui/LmsApplication.cpp b/src/ui/LmsApplication.cpp index 496df353..ab5a6ad7 100644 --- a/src/ui/LmsApplication.cpp +++ b/src/ui/LmsApplication.cpp @@ -326,7 +326,12 @@ LmsApplication::handleAuthEvent(void) { Wt::WServer::instance()->post(sessionId, [=] { - notify(Wt::WString::tr("msg-notify-scan-complete").arg(stats.nbAdded).arg(stats.nbRemoved)); + notify(Wt::WString::tr("msg-notify-scan-complete") + .arg(stats.nbFiles()) + .arg(stats.additions) + .arg(stats.deletions) + .arg(stats.nbDuplicates()) + .arg(stats.nbErrors())); triggerUpdate(); }); // notify("TEST"); From 86fc1415d5538aed8cc25d082468d2dc052acfc9 Mon Sep 17 00:00:00 2001 From: emeric Date: Thu, 8 Mar 2018 22:57:56 +0100 Subject: [PATCH 069/195] Added first connection wizard to create the administrator account --- Makefile.am | 3 +- approot/messages.xml | 8 + .../{settings.xml => settings-database.xml} | 0 approot/settings-first-connection.xml | 57 ++++++ src/Makefile.am | 3 +- src/database/DatabaseHandler.cpp | 2 +- src/database/User.cpp | 56 ------ src/database/User.hpp | 31 +--- src/ui/LmsApplication.cpp | 18 +- ...{DirectoryValidator.cpp => Validators.cpp} | 19 +- src/ui/common/Validators.hpp | 25 ++- src/ui/settings/DatabaseView.cpp | 8 +- src/ui/settings/DatabaseView.hpp | 7 - src/ui/settings/FirstConnectionView.cpp | 164 ++++++++++++++++++ .../FirstConnectionView.hpp} | 18 +- 15 files changed, 287 insertions(+), 132 deletions(-) rename approot/{settings.xml => settings-database.xml} (100%) create mode 100644 approot/settings-first-connection.xml rename src/ui/common/{DirectoryValidator.cpp => Validators.cpp} (79%) create mode 100644 src/ui/settings/FirstConnectionView.cpp rename src/ui/{common/DirectoryValidator.hpp => settings/FirstConnectionView.hpp} (72%) diff --git a/Makefile.am b/Makefile.am index caa0718b..ddd7cc97 100644 --- a/Makefile.am +++ b/Makefile.am @@ -24,7 +24,8 @@ lms_approot_DATA = \ approot/playqueue.xml \ approot/releases.xml \ approot/release.xml \ - approot/settings.xml \ + approot/settings-database.xml \ + approot/settings-first-connection.xml \ approot/templates.xml \ approot/tracks.xml diff --git a/approot/messages.xml b/approot/messages.xml index 03322b49..ff60b3c9 100644 --- a/approot/messages.xml +++ b/approot/messages.xml @@ -21,6 +21,14 @@ Show more +Login +Password +Verify password +Create + +Administrator account +Account created. Please refresh to continue! + Settings Database Media root directory diff --git a/approot/settings.xml b/approot/settings-database.xml similarity index 100% rename from approot/settings.xml rename to approot/settings-database.xml diff --git a/approot/settings-first-connection.xml b/approot/settings-first-connection.xml new file mode 100644 index 00000000..c4fda6db --- /dev/null +++ b/approot/settings-first-connection.xml @@ -0,0 +1,57 @@ + + + + + + +
+ +
+
+ +
+ ${admin-login} +
+
+ ${admin-login-info} +
+
+ +
+ +
+ ${password} +
+
+ ${password-info} +
+
+ +
+ +
+ ${password-confirm} +
+
+ ${password-confirm-info} +
+
+ +
+
+ ${create-btn class="btn-primary"} +
+
+
+
+
+ +
diff --git a/src/Makefile.am b/src/Makefile.am index b958dfca..8aa3f025 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -33,13 +33,14 @@ lms_SOURCES = \ $(srcdir)/ui/ReleaseView.cpp \ $(srcdir)/ui/TracksView.cpp \ $(srcdir)/ui/auth/LmsAuth.cpp \ - $(srcdir)/ui/common/DirectoryValidator.cpp \ + $(srcdir)/ui/common/Validators.cpp \ $(srcdir)/ui/common/InputRange.cpp \ $(srcdir)/ui/common/LineEdit.cpp \ $(srcdir)/ui/resource/AvConvTranscodeStreamResource.cpp \ $(srcdir)/ui/resource/ImageResource.cpp \ $(srcdir)/ui/resource/TranscodeResource.cpp \ $(srcdir)/ui/settings/DatabaseView.cpp \ + $(srcdir)/ui/settings/FirstConnectionView.cpp \ $(srcdir)/utils/Config.cpp \ $(srcdir)/utils/Logger.cpp \ $(srcdir)/utils/Path.cpp \ diff --git a/src/database/DatabaseHandler.cpp b/src/database/DatabaseHandler.cpp index f47fa8a8..7334dbde 100644 --- a/src/database/DatabaseHandler.cpp +++ b/src/database/DatabaseHandler.cpp @@ -61,7 +61,7 @@ Handler::configureAuth(void) strengthValidator->setMinimumLength( Wt::Auth::PasswordStrengthValidator::OneCharClass, 4); strengthValidator->setMinimumLength( Wt::Auth::PasswordStrengthValidator::TwoCharClass, 4); strengthValidator->setMinimumLength( Wt::Auth::PasswordStrengthValidator::ThreeCharClass, 4 ); - strengthValidator->setMinimumLength( Wt::Auth::PasswordStrengthValidator::FourCharClass, 4 ); + strengthValidator->setMinimumLength( Wt::Auth::PasswordStrengthValidator::FourCharClass, 4 ); passwordService.setStrengthValidator(strengthValidator); } diff --git a/src/database/User.cpp b/src/database/User.cpp index 4024e2d4..bd1dfa2c 100644 --- a/src/database/User.cpp +++ b/src/database/User.cpp @@ -26,26 +26,9 @@ const std::vector User::audioBitrates = { 64000, - 96000, 128000, - 160000, 192000, - 224000, - 256000, 320000, - 512000 -}; - - -const std::vector -User::videoBitrates = -{ - 256000, - 512000, - 1024000, - 2048000, - 4096000, - 8192000 }; const std::vector @@ -57,20 +40,11 @@ User::audioEncodings = AudioEncoding::WEBMA, }; -const std::vector -User::videoEncodings = -{ - VideoEncoding::AUTO, -}; - User::User() : _maxAudioBitrate(maxAudioBitrate), - _maxVideoBitrate(maxVideoBitrate), _isAdmin(false), _audioBitrate(defaultAudioBitrate), _audioEncoding(AudioEncoding::AUTO), -_videoBitrate(defaultVideoBitrate), -_videoEncoding(VideoEncoding::AUTO), _curPlayingTrackPos(0) { @@ -108,24 +82,12 @@ User::setAudioBitrate(std::size_t bitrate) _audioBitrate = std::min(bitrate, std::min(static_cast(_maxAudioBitrate), audioBitrates.back())); } -void -User::setVideoBitrate(std::size_t bitrate) -{ - _videoBitrate = std::min(bitrate, std::min(static_cast(_maxVideoBitrate), videoBitrates.back())); -} - void User::setMaxAudioBitrate(std::size_t bitrate) { _maxAudioBitrate = std::min(bitrate, static_cast(_maxAudioBitrate)); } -void -User::setMaxVideoBitrate(std::size_t bitrate) -{ - _maxVideoBitrate = std::min(bitrate, static_cast(_maxVideoBitrate)); -} - std::size_t User::getAudioBitrate(void) const { @@ -135,15 +97,6 @@ User::getAudioBitrate(void) const return std::min(static_cast(_audioBitrate), audioBitrates.back()); } -std::size_t -User::getVideoBitrate(void) const -{ - if (!isAdmin()) - return std::min(static_cast(_videoBitrate), std::min(static_cast(_maxVideoBitrate), videoBitrates.back())); - else - return std::min(static_cast(_videoBitrate), videoBitrates.back()); -} - std::size_t User::getMaxAudioBitrate(void) const { @@ -153,15 +106,6 @@ User::getMaxAudioBitrate(void) const return audioBitrates.back(); } -std::size_t -User::getMaxVideoBitrate(void) const -{ - if (!isAdmin()) - return std::min(static_cast(_maxVideoBitrate), videoBitrates.back()); - else - return videoBitrates.back(); -} - } // namespace Database diff --git a/src/database/User.hpp b/src/database/User.hpp index e772f404..a422c369 100644 --- a/src/database/User.hpp +++ b/src/database/User.hpp @@ -41,29 +41,21 @@ enum class AudioEncoding WEBMA, }; -enum class VideoEncoding -{ - AUTO, -}; - class User { - public: + typedef Wt::Dbo::dbo_traits::IdType id_type; + typedef Wt::Dbo::ptr pointer; + static const std::size_t MinNameLength = 3; static const std::size_t MaxNameLength = 15; - // list of audio/video parameters + // list of audio parameters static const std::vector audioBitrates; static const std::vector audioEncodings; - static const std::vector videoBitrates; - static const std::vector videoEncodings; - User(); - typedef Wt::Dbo::ptr pointer; - // utility static pointer create(Wt::Dbo::Session& session); @@ -76,32 +68,23 @@ class User void setAdmin(bool admin) { _isAdmin = admin; } void setAudioBitrate(std::size_t bitrate); void setAudioEncoding(AudioEncoding encoding) { _audioEncoding = encoding; } - void setVideoBitrate(std::size_t bitrate); - void setVideoEncoding(VideoEncoding encoding) { _videoEncoding = encoding; } void setMaxAudioBitrate(std::size_t bitrate); - void setMaxVideoBitrate(std::size_t bitrate); void setCurPlayingTrackPos(std::size_t pos) { _curPlayingTrackPos = pos; } // read bool isAdmin() const {return _isAdmin;} std::size_t getAudioBitrate() const; AudioEncoding getAudioEncoding() const { return _audioEncoding;} - std::size_t getVideoBitrate() const; - VideoEncoding getVideoEncoding() const { return _videoEncoding;} std::size_t getMaxAudioBitrate() const; - std::size_t getMaxVideoBitrate() const; std::size_t getCurPlayingTrackPos() const { return _curPlayingTrackPos; } template 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, _audioEncoding, "audio_encoding"); - Wt::Dbo::field(a, _videoBitrate, "video_bitrate"); - Wt::Dbo::field(a, _videoEncoding, "video_encoding"); // User's dynamic data Wt::Dbo::field(a, _curPlayingTrackPos, "cur_playing_track_pos"); Wt::Dbo::hasMany(a, _playlists, Wt::Dbo::ManyToOne, "user"); @@ -110,21 +93,15 @@ class User 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; AudioEncoding _audioEncoding; - int _videoBitrate; - VideoEncoding _videoEncoding; // User's dynamic data int _curPlayingTrackPos; // Current track position in queue diff --git a/src/ui/LmsApplication.cpp b/src/ui/LmsApplication.cpp index ab5a6ad7..84dc0c46 100644 --- a/src/ui/LmsApplication.cpp +++ b/src/ui/LmsApplication.cpp @@ -36,6 +36,7 @@ #include "PlayQueueView.hpp" #include "settings/DatabaseView.hpp" +#include "settings/FirstConnectionView.hpp" #include "LmsApplication.hpp" @@ -90,29 +91,26 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnecti messageResourceBundle().use(appRoot() + "playqueue"); messageResourceBundle().use(appRoot() + "release"); messageResourceBundle().use(appRoot() + "releases"); - messageResourceBundle().use(appRoot() + "settings"); + messageResourceBundle().use(appRoot() + "settings-database"); + messageResourceBundle().use(appRoot() + "settings-first-connection"); messageResourceBundle().use(appRoot() + "tracks"); messageResourceBundle().use(appRoot() + "templates"); setTitle("LMS"); + // If here is no account in the database, launch the first connection wizard bool firstConnection; { Wt::Dbo::Transaction transaction(DboSession()); firstConnection = (Database::User::getAll(DboSession()).size() == 0); - - // Create a fake user - // TODO settings - Database::User::create(DboSession()); } LMS_LOG(UI, DEBUG) << "Creating root widget. First connection = " << std::boolalpha << firstConnection; - // If here is no account in the database, launch the first connection wizard -// if (firstConnection) -// createFirstConnectionUI(); -// else + if (firstConnection) + createFirstConnectionUI(); + else createLmsUI(); } @@ -163,7 +161,7 @@ LmsApplication::createFirstConnectionUI() // Hack, use the auth widget builtin strings builtinLocalizedStrings().useBuiltin(skeletons::AuthStrings_xml1); -// root()->addWidget( new Settings::FirstConnectionFormView()); + root()->addWidget(new Settings::FirstConnectionView()); } void diff --git a/src/ui/common/DirectoryValidator.cpp b/src/ui/common/Validators.cpp similarity index 79% rename from src/ui/common/DirectoryValidator.cpp rename to src/ui/common/Validators.cpp index cf6c16fd..2d8b2170 100644 --- a/src/ui/common/DirectoryValidator.cpp +++ b/src/ui/common/Validators.cpp @@ -19,16 +19,31 @@ #include -#include "DirectoryValidator.hpp" +#include "Validators.hpp" namespace UserInterface { +Wt::WValidator* createNameValidator() +{ + Wt::WLengthValidator *v = new Wt::WLengthValidator(); + v->setMandatory(true); + v->setMinimumLength(::Database::User::MinNameLength); + v->setMaximumLength(::Database::User::MaxNameLength); + return v; +} + +Wt::WValidator* createMandatoryValidator() +{ + auto v = new Wt::WValidator(); + v->setMandatory(true); + return v; +} + DirectoryValidator::DirectoryValidator(Wt::WObject *parent) : Wt::WValidator(parent) { } - Wt::WValidator::Result DirectoryValidator::validate(const Wt::WString& input) const { diff --git a/src/ui/common/Validators.hpp b/src/ui/common/Validators.hpp index 76e1b3a5..45f5fd99 100644 --- a/src/ui/common/Validators.hpp +++ b/src/ui/common/Validators.hpp @@ -19,26 +19,23 @@ #pragma once -#include #include #include "database/User.hpp" namespace UserInterface { -static inline Wt::WValidator *createEmailValidator() -{ - Wt::WValidator *res = new Wt::WRegExpValidator("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4}"); - res->setMandatory(true); - return res; -} +Wt::WValidator* createNameValidator(); +Wt::WValidator* createMandatoryValidator(); -static inline Wt::WValidator *createNameValidator() { - Wt::WLengthValidator *v = new Wt::WLengthValidator(); - v->setMandatory(true); - v->setMinimumLength(3); - v->setMaximumLength(::Database::User::MaxNameLength); - return v; -} +class DirectoryValidator : public Wt::WValidator +{ + public: + DirectoryValidator(Wt::WObject *parent = 0); + + Wt::WValidator::Result validate(const Wt::WString& input) const override; + +}; } // namespace UserInterface + diff --git a/src/ui/settings/DatabaseView.cpp b/src/ui/settings/DatabaseView.cpp index 05dad344..2a8940d0 100644 --- a/src/ui/settings/DatabaseView.cpp +++ b/src/ui/settings/DatabaseView.cpp @@ -26,7 +26,7 @@ #include #include -#include "common/DirectoryValidator.hpp" +#include "common/Validators.hpp" #include "database/MediaDirectory.hpp" #include "scanner/MediaScanner.hpp" #include "utils/Logger.hpp" @@ -214,12 +214,6 @@ class DatabaseModel : public Wt::WFormModel } - Wt::WValidator *createMandatoryValidator() - { - Wt::WValidator* v = new Wt::WValidator(); - v->setMandatory(true); - return v; - } Wt::WStringListModel* _updatePeriodModel; Wt::WStringListModel* _updateStartTimeModel; diff --git a/src/ui/settings/DatabaseView.hpp b/src/ui/settings/DatabaseView.hpp index ad82178c..ab930efb 100644 --- a/src/ui/settings/DatabaseView.hpp +++ b/src/ui/settings/DatabaseView.hpp @@ -21,8 +21,6 @@ #include #include -#include -#include namespace UserInterface { namespace Settings { @@ -34,17 +32,12 @@ class DatabaseView : public Wt::WTemplateFormView public: DatabaseView(Wt::WContainerWidget *parent = 0); - Wt::Signal& changed() { return _sigChanged; } - private: - Wt::Signal _sigChanged; - void processSave(); void processDiscard(); void processImmediateScan(); - Wt::WText *_applyInfo; DatabaseModel *_model; }; diff --git a/src/ui/settings/FirstConnectionView.cpp b/src/ui/settings/FirstConnectionView.cpp new file mode 100644 index 00000000..5b1dac97 --- /dev/null +++ b/src/ui/settings/FirstConnectionView.cpp @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2018 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 . + */ + +#include +#include +#include +#include + +#include "utils/Logger.hpp" + +#include "common/Validators.hpp" +#include "LmsApplication.hpp" + +#include "FirstConnectionView.hpp" + +namespace UserInterface { +namespace Settings { + +class FirstConnectionModel : public Wt::WFormModel +{ + public: + + // Associate each field with a unique string literal. + static const Field AdminLoginField; + static const Field PasswordField; + static const Field PasswordConfirmField; + + FirstConnectionModel(Wt::WObject *parent = 0) + : Wt::WFormModel(parent) + { + addField(AdminLoginField); + addField(PasswordField); + addField(PasswordConfirmField); + + setValidator(AdminLoginField, createNameValidator()); + setValidator(PasswordField, createMandatoryValidator()); + setValidator(PasswordConfirmField, createMandatoryValidator()); + } + + void saveData() + { + Wt::Dbo::Transaction transaction(DboSession()); + + // Check if a user already exist + // If it's the case, just do nothing + if (!Database::User::getAll(DboSession()).empty()) + throw std::runtime_error("Admin user already created"); + + // Create user + Wt::Auth::User authUser = DbHandler().getUserDatabase().registerNew(); + Database::User::pointer user = DbHandler().getUser(authUser); + + // Account + authUser.setIdentity(Wt::Auth::Identity::LoginName, valueText(AdminLoginField)); + Database::Handler::getPasswordService().updatePassword(authUser, valueText(PasswordField)); + + user.modify()->setAdmin( true ); + } + + bool validateField(Field field) + { + Wt::WString error; + + if (field == PasswordField) + { + // Password is mandatory if we create the user + if (!valueText(PasswordField).empty()) + { + // Evaluate the strength of the password + Wt::Auth::AbstractPasswordService::StrengthValidatorResult res + = Database::Handler::getPasswordService().strengthValidator()->evaluateStrength(valueText(PasswordField), + valueText(AdminLoginField), ""); + + if (!res.isValid()) + error = res.message(); + } + else + return Wt::WFormModel::validateField(field); + } + else if (field == PasswordConfirmField) + { + if (validation(PasswordField).state() == Wt::WValidator::Valid) + { + if (valueText(PasswordField) != valueText(PasswordConfirmField)) + error = Wt::WString::tr("Wt.Auth.passwords-dont-match"); + } + } + else + { + return Wt::WFormModel::validateField(field); + } + + setValidation(field, Wt::WValidator::Result( error.empty() ? Wt::WValidator::Valid : Wt::WValidator::Invalid, error)); + + return (validation(field).state() == Wt::WValidator::Valid); + } + +}; + +const Wt::WFormModel::Field FirstConnectionModel::AdminLoginField = "admin-login"; +const Wt::WFormModel::Field FirstConnectionModel::PasswordField = "password"; +const Wt::WFormModel::Field FirstConnectionModel::PasswordConfirmField = "password-confirm"; + +FirstConnectionView::FirstConnectionView(Wt::WContainerWidget *parent) +: Wt::WTemplateFormView(parent) +{ + auto model = new FirstConnectionModel(this); + + setTemplateText(Wt::WString::tr("template-settings-first-connection")); + addFunction("tr", &WTemplate::Functions::tr); + addFunction("id", &WTemplate::Functions::id); + + // AdminLogin + Wt::WLineEdit* accountEdit = new Wt::WLineEdit(); + setFormWidget(FirstConnectionModel::AdminLoginField, accountEdit); + + // Password + Wt::WLineEdit* passwordEdit = new Wt::WLineEdit(); + setFormWidget(FirstConnectionModel::PasswordField, passwordEdit ); + passwordEdit->setEchoMode(Wt::WLineEdit::Password); + + // Password confirmation + Wt::WLineEdit* passwordConfirmEdit = new Wt::WLineEdit(); + setFormWidget(FirstConnectionModel::PasswordConfirmField, passwordConfirmEdit); + passwordConfirmEdit->setEchoMode(Wt::WLineEdit::Password); + + auto saveButton = new Wt::WPushButton(Wt::WString::tr("msg-create")); + bindWidget("create-btn", saveButton); + saveButton->clicked().connect(std::bind([=] + { + updateModel(model); + + if (model->validate()) + { + model->saveData(); + notify(Wt::WString::tr("msg-settings-first-connection-done")); + saveButton->setEnabled(false); + } + + updateView(model); + })); + + updateView(model); +} + +} // namespace Settings +} // namespace UserInterface + diff --git a/src/ui/common/DirectoryValidator.hpp b/src/ui/settings/FirstConnectionView.hpp similarity index 72% rename from src/ui/common/DirectoryValidator.hpp rename to src/ui/settings/FirstConnectionView.hpp index 8ee161e2..2a9485e3 100644 --- a/src/ui/common/DirectoryValidator.hpp +++ b/src/ui/settings/FirstConnectionView.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Emeric Poupon + * Copyright (C) 2018 Emeric Poupon * * This file is part of LMS. * @@ -17,17 +17,23 @@ * along with LMS. If not, see . */ -#include +#pragma once + +#include +#include namespace UserInterface { +namespace Settings { -class DirectoryValidator : public Wt::WValidator +class FirstConnectionView : public Wt::WTemplateFormView { public: - DirectoryValidator(Wt::WObject *parent = 0); - - Wt::WValidator::Result validate(const Wt::WString& input) const; + FirstConnectionView(Wt::WContainerWidget *parent = 0); }; + +} // namespace Settings } // namespace UserInterface + + From 85a3e42dace06200095acc8f405a25f883cd8355 Mon Sep 17 00:00:00 2001 From: emeric Date: Thu, 8 Mar 2018 23:18:27 +0100 Subject: [PATCH 070/195] Simplified implementation --- src/ui/settings/DatabaseView.cpp | 61 +++++++++++++++----------------- src/ui/settings/DatabaseView.hpp | 10 ------ 2 files changed, 28 insertions(+), 43 deletions(-) diff --git a/src/ui/settings/DatabaseView.cpp b/src/ui/settings/DatabaseView.cpp index 2a8940d0..4986348c 100644 --- a/src/ui/settings/DatabaseView.cpp +++ b/src/ui/settings/DatabaseView.cpp @@ -228,7 +228,7 @@ const Wt::WFormModel::Field DatabaseModel::UpdateStartTimeField = "update-start DatabaseView::DatabaseView(Wt::WContainerWidget *parent) : Wt::WTemplateFormView(parent) { - _model = new DatabaseModel(this); + auto model = new DatabaseModel(this); setTemplateText(tr("template-settings-database")); addFunction("tr", &WTemplate::Functions::tr); @@ -241,12 +241,12 @@ DatabaseView::DatabaseView(Wt::WContainerWidget *parent) // Update Period Wt::WComboBox *updatePeriodCB = new Wt::WComboBox(); setFormWidget(DatabaseModel::UpdatePeriodField, updatePeriodCB); - updatePeriodCB->setModel(_model->updatePeriodModel()); + updatePeriodCB->setModel(model->updatePeriodModel()); // Update Start Time Wt::WComboBox *updateStartTimeCB = new Wt::WComboBox(); setFormWidget(DatabaseModel::UpdateStartTimeField, updateStartTimeCB); - updateStartTimeCB->setModel(_model->updateStartTimeModel()); + updateStartTimeCB->setModel(model->updateStartTimeModel()); // Buttons @@ -259,8 +259,30 @@ DatabaseView::DatabaseView(Wt::WContainerWidget *parent) Wt::WPushButton *immScanBtn = new Wt::WPushButton(Wt::WString::tr("msg-btn-immediate-scan")); bindWidget("immediate-scan-btn", immScanBtn); - saveBtn->clicked().connect(this, &DatabaseView::processSave); - discardBtn->clicked().connect(this, &DatabaseView::processDiscard); + saveBtn->clicked().connect(std::bind([=] () + { + updateModel(model); + + if (model->validate()) + { + model->saveData(); + + MediaScanner().reschedule(); + + notify(Wt::WString::tr("msg-notify-settings-saved")); + } + + // Udate the view: Delete any validation message in the view, etc. + updateView(model); + })); + + discardBtn->clicked().connect(std::bind([=] () + { + model->loadData(); + model->validate(); + updateView(model); + })); + immScanBtn->clicked().connect(std::bind([=] () { MediaScanner().scheduleImmediateScan(); @@ -268,36 +290,9 @@ DatabaseView::DatabaseView(Wt::WContainerWidget *parent) notify(Wt::WString::tr("msg-notify-scan-launched")); })); - updateView(_model); + updateView(model); } -void -DatabaseView::processDiscard() -{ - _model->loadData(); - _model->validate(); - updateView(_model); -} - -void -DatabaseView::processSave() -{ - updateModel(_model); - - if (_model->validate()) - { - _model->saveData(); - - MediaScanner().reschedule(); - - notify(Wt::WString::tr("msg-notify-settings-saved")); - } - - // Udate the view: Delete any validation message in the view, etc. - updateView(_model); -} - - } // namespace Settings } // namespace UserInterface diff --git a/src/ui/settings/DatabaseView.hpp b/src/ui/settings/DatabaseView.hpp index ab930efb..a985a69c 100644 --- a/src/ui/settings/DatabaseView.hpp +++ b/src/ui/settings/DatabaseView.hpp @@ -25,20 +25,10 @@ namespace UserInterface { namespace Settings { -class DatabaseModel; - class DatabaseView : public Wt::WTemplateFormView { public: DatabaseView(Wt::WContainerWidget *parent = 0); - - private: - - void processSave(); - void processDiscard(); - void processImmediateScan(); - - DatabaseModel *_model; }; From f91aacb0a356b001417fb4c735313fe8a043304a Mon Sep 17 00:00:00 2001 From: emeric Date: Tue, 13 Mar 2018 09:43:16 +0100 Subject: [PATCH 071/195] Added a login view --- Makefile.am | 1 + approot/login.xml | 56 ++++++++++++++ approot/messages.xml | 4 + src/Makefile.am | 2 +- src/database/DatabaseHandler.cpp | 7 +- src/ui/LmsApplication.cpp | 79 +++++++++---------- src/ui/LmsApplication.hpp | 5 +- src/ui/LoginView.cpp | 89 ++++++++++++++++++++++ src/ui/{auth/LmsAuth.hpp => LoginView.hpp} | 26 ++----- src/ui/auth/LmsAuth.cpp | 49 ------------ 10 files changed, 203 insertions(+), 115 deletions(-) create mode 100644 approot/login.xml create mode 100644 src/ui/LoginView.cpp rename src/ui/{auth/LmsAuth.hpp => LoginView.hpp} (69%) delete mode 100644 src/ui/auth/LmsAuth.cpp diff --git a/Makefile.am b/Makefile.am index ddd7cc97..3c24abc5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -19,6 +19,7 @@ lms_images_DATA = \ lms_approot_DATA = \ approot/artists.xml \ approot/artist.xml \ + approot/login.xml \ approot/mediaplayer.xml \ approot/messages.xml \ approot/playqueue.xml \ diff --git a/approot/login.xml b/approot/login.xml new file mode 100644 index 00000000..e031a36b --- /dev/null +++ b/approot/login.xml @@ -0,0 +1,56 @@ + + + + + +
+ +
+
+ +
+ ${user-name} +
+
+ ${user-name-info} +
+
+ +
+ +
+ ${password} +
+
+ ${password-info} +
+
+ +
+ +
+ ${remember-me} +
+
+ ${remember-me-info} +
+
+
+
+ ${login-btn class="btn-primary"} +
+
+
+
+
+ + +
diff --git a/approot/messages.xml b/approot/messages.xml index ff60b3c9..74ab5522 100644 --- a/approot/messages.xml +++ b/approot/messages.xml @@ -26,6 +26,10 @@ Verify password Create +Remember me +Welcome! +Logout + Administrator account Account created. Please refresh to continue! diff --git a/src/Makefile.am b/src/Makefile.am index 8aa3f025..6e998f5b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -27,12 +27,12 @@ lms_SOURCES = \ $(srcdir)/ui/Filters.cpp \ $(srcdir)/ui/HomeView.cpp \ $(srcdir)/ui/LmsApplication.cpp \ + $(srcdir)/ui/LoginView.cpp \ $(srcdir)/ui/MediaPlayer.cpp \ $(srcdir)/ui/PlayQueueView.cpp \ $(srcdir)/ui/ReleasesView.cpp \ $(srcdir)/ui/ReleaseView.cpp \ $(srcdir)/ui/TracksView.cpp \ - $(srcdir)/ui/auth/LmsAuth.cpp \ $(srcdir)/ui/common/Validators.cpp \ $(srcdir)/ui/common/InputRange.cpp \ $(srcdir)/ui/common/LineEdit.cpp \ diff --git a/src/database/DatabaseHandler.cpp b/src/database/DatabaseHandler.cpp index 7334dbde..862d6034 100644 --- a/src/database/DatabaseHandler.cpp +++ b/src/database/DatabaseHandler.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -48,7 +49,11 @@ namespace { void Handler::configureAuth(void) { - authService.setEmailVerificationEnabled(true); + authService.setEmailVerificationEnabled(false); + authService.setAuthTokensEnabled(true, "lmsauth"); + authService.setIdentityPolicy(Wt::Auth::LoginNameIdentity); + authService.setRandomTokenLength(32); + authService.setTokenHashFunction(new Wt::Auth::BCryptHashFunction(8)); Wt::Auth::PasswordVerifier *verifier = new Wt::Auth::PasswordVerifier(); verifier->addHashFunction(new Wt::Auth::BCryptHashFunction(8)); diff --git a/src/ui/LmsApplication.cpp b/src/ui/LmsApplication.cpp index 84dc0c46..dcb6f86e 100644 --- a/src/ui/LmsApplication.cpp +++ b/src/ui/LmsApplication.cpp @@ -24,12 +24,13 @@ #include #include #include +#include #include "config/config.h" #include "utils/Logger.hpp" #include "utils/Utils.hpp" -#include "auth/LmsAuth.hpp" +#include "LoginView.hpp" #include "Explore.hpp" #include "HomeView.hpp" #include "MediaPlayer.hpp" @@ -86,6 +87,7 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnecti // Add a resource bundle messageResourceBundle().use(appRoot() + "artist"); messageResourceBundle().use(appRoot() + "artists"); + messageResourceBundle().use(appRoot() + "login"); messageResourceBundle().use(appRoot() + "mediaplayer"); messageResourceBundle().use(appRoot() + "messages"); messageResourceBundle().use(appRoot() + "playqueue"); @@ -109,9 +111,19 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnecti LMS_LOG(UI, DEBUG) << "Creating root widget. First connection = " << std::boolalpha << firstConnection; if (firstConnection) - createFirstConnectionUI(); + { + // Hack, use the auth widget builtin strings + builtinLocalizedStrings().useBuiltin(skeletons::AuthStrings_xml1); + + root()->addWidget(new Settings::FirstConnectionView()); + } else - createLmsUI(); + { + DbHandler().getLogin().changed().connect(this, &LmsApplication::handleAuthEvent); + + auto auth = new LoginView(DbHandler().getLogin()); + root()->addWidget(auth); + } } Database::Handler& DbHandler() @@ -130,8 +142,7 @@ const Wt::Auth::User& CurrentAuthUser() Database::User::pointer CurrentUser() { - return Database::User::getById(DboSession(), 1); -// return DbHandler().getCurrentUser(); + return DbHandler().getCurrentUser(); } ImageResource* SessionImageResource() @@ -156,36 +167,12 @@ LmsApplication::goHome() } void -LmsApplication::createFirstConnectionUI() +LmsApplication::quit() { - // Hack, use the auth widget builtin strings - builtinLocalizedStrings().useBuiltin(skeletons::AuthStrings_xml1); - - root()->addWidget(new Settings::FirstConnectionView()); + WApplication::quit(""); + redirect("/"); } -void -LmsApplication::createLmsUI() -{ - _imageResource = new ImageResource(_db, root()); - _transcodeResource = new TranscodeResource(_db, root()); - - handleAuthEvent(); - /* - DbHandler().getLogin().changed().connect(this, &LmsApplication::handleAuthEvent); - - LmsAuth *authWidget = new LmsAuth(); - - authWidget->model()->addPasswordAuth(&Database::Handler::getPasswordService()); - authWidget->setRegistrationEnabled(false); - - authWidget->processEnvironment(); - - root()->addWidget(authWidget); - */ -} - - enum IdxRoot { IdxHome = 0, @@ -194,8 +181,6 @@ enum IdxRoot IdxSettingsDatabase, }; - - static void handlePathChange(Wt::WStackedWidget* stack) { @@ -228,19 +213,19 @@ handlePathChange(Wt::WStackedWidget* stack) void LmsApplication::handleAuthEvent(void) { - /* if (!DbHandler().getLogin().loggedIn()) { LMS_LOG(UI, INFO) << "User logged out, session = " << Wt::WApplication::instance()->sessionId(); - quit(""); - redirect("/"); - + quit(); return; } LMS_LOG(UI, INFO) << "User '" << CurrentAuthUser().identity(Wt::Auth::Identity::LoginName) << "' logged in from '" << Wt::WApplication::instance()->environment().clientAddress() << "', user agent = " << Wt::WApplication::instance()->environment().agent() << ", session = " << Wt::WApplication::instance()->sessionId(); -*/ + + _imageResource = new ImageResource(_db, root()); + _transcodeResource = new TranscodeResource(_db, root()); + setConfirmCloseMessage(Wt::WString::tr("msg-quit-confirm")); auto main = new Wt::WTemplate(Wt::WString::tr("template-main"), root()); @@ -273,8 +258,11 @@ LmsApplication::handleAuthEvent(void) menuItem->setLink(Wt::WLink(Wt::WLink::InternalPath, "/playqueue")); menuItem->setSelectable(false); } + navbar->addMenu(menu); + + auto rightMenu = new Wt::WMenu(); { - auto menuItem = menu->insertItem(4, Wt::WString::tr("msg-settings")); + auto menuItem = rightMenu->insertItem(0, Wt::WString::tr("msg-settings")); menuItem->setSelectable(false); Wt::WPopupMenu *settings = new Wt::WPopupMenu(); @@ -285,7 +273,16 @@ LmsApplication::handleAuthEvent(void) menuItem->setMenu(settings); } - navbar->addMenu(menu); + { + auto menuItem = rightMenu->insertItem(1, Wt::WString::tr("msg-logout")); + menuItem->setSelectable(true); + menuItem->triggered().connect(std::bind([=] + { + setConfirmCloseMessage(""); + DbHandler().getLogin().logout(); + })); + } + navbar->addMenu(rightMenu, Wt::AlignRight); // Contents Wt::WStackedWidget* mainStack = new Wt::WStackedWidget(); diff --git a/src/ui/LmsApplication.hpp b/src/ui/LmsApplication.hpp index f3e4dd2b..74006fee 100644 --- a/src/ui/LmsApplication.hpp +++ b/src/ui/LmsApplication.hpp @@ -20,9 +20,7 @@ #ifndef LMS_APPLICATION_HPP #define LMS_APPLICATION_HPP -#include #include - #include #include "database/DatabaseHandler.hpp" @@ -48,6 +46,7 @@ class LmsApplication : public Wt::WApplication // Utils void goHome(); + void quit(); void notify(const Wt::WString& message); private: @@ -55,8 +54,6 @@ class LmsApplication : public Wt::WApplication LmsApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnectionPool& connectionPool, Scanner::MediaScanner& scanner); void handleAuthEvent(void); - void createFirstConnectionUI(); - void createLmsUI(); Database::Handler _db; Scanner::MediaScanner& _scanner; diff --git a/src/ui/LoginView.cpp b/src/ui/LoginView.cpp new file mode 100644 index 00000000..f37fde26 --- /dev/null +++ b/src/ui/LoginView.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2018 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 . + */ + +#include +#include +#include +#include +#include + +#include "utils/Logger.hpp" + +#include "common/Validators.hpp" +#include "LmsApplication.hpp" + +#include "LoginView.hpp" + +namespace UserInterface { + +LoginView::LoginView(Wt::Auth::Login& login, Wt::WContainerWidget *parent) +: Wt::WTemplateFormView(parent) +{ + auto model = new Wt::Auth::AuthModel(DbHandler().getAuthService(), DbHandler().getUserDatabase()); + + model->addPasswordAuth(&Database::Handler::getPasswordService()); + + setTemplateText(Wt::WString::tr("template-login")); + addFunction("tr", &WTemplate::Functions::tr); + addFunction("id", &WTemplate::Functions::id); + + // LoginName + auto loginName = new Wt::WLineEdit(); + setFormWidget(Wt::Auth::AuthModel::LoginNameField, loginName); + + // Password + auto password = new Wt::WLineEdit(); + setFormWidget(Wt::Auth::AuthModel::PasswordField, password); + password->setEchoMode(Wt::WLineEdit::Password); + + // Remember Me + auto rememberMe = new Wt::WCheckBox(); + setFormWidget(Wt::Auth::AuthModel::RememberMeField, rememberMe); + + auto loginBtn = new Wt::WPushButton(Wt::WString::tr("msg-login")); + bindWidget("login-btn", loginBtn); + loginBtn->clicked().connect(std::bind([=, &login] + { + updateModel(model); + + if (model->validate()) + model->login(login); + else + updateView(model); + })); + + password->enterPressed().connect(std::bind([=] + { + loginBtn->clicked().emit(Wt::WMouseEvent()); + })); + + login.changed().connect(std::bind([=, &login] + { + if (login.loggedIn()) + this->setHidden(true); + })); + + Wt::Auth::User user = model->processAuthToken(); + model->loginUser(login, user, Wt::Auth::WeakLogin); + + updateView(model); +} + +} // namespace UserInterface + diff --git a/src/ui/auth/LmsAuth.hpp b/src/ui/LoginView.hpp similarity index 69% rename from src/ui/auth/LmsAuth.hpp rename to src/ui/LoginView.hpp index bcf8fc7f..7a7bbea2 100644 --- a/src/ui/auth/LmsAuth.hpp +++ b/src/ui/LoginView.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Emeric Poupon + * Copyright (C) 2018 Emeric Poupon * * This file is part of LMS. * @@ -17,33 +17,21 @@ * along with LMS. If not, see . */ -#ifndef UI_AUTH_HPP -#define UI_AUTH_HPP +#pragma once -#include -#include -#include -#include #include +#include +#include namespace UserInterface { -class LmsAuth : public Wt::Auth::AuthWidget +class LoginView : public Wt::WTemplateFormView { public: - - LmsAuth(); - - // LoggedInView is delegated to LmsHome - void createLoggedInView () ; - void createLoginView (); - - private: - - + LoginView(Wt::Auth::Login& login, Wt::WContainerWidget *parent = 0); }; + } // namespace UserInterface -#endif diff --git a/src/ui/auth/LmsAuth.cpp b/src/ui/auth/LmsAuth.cpp deleted file mode 100644 index eb3a8559..00000000 --- a/src/ui/auth/LmsAuth.cpp +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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 . - */ - -#include "LmsAuth.hpp" - -#include "LmsApplication.hpp" - -namespace UserInterface { - -LmsAuth::LmsAuth() -: Wt::Auth::AuthWidget(DbHandler().getAuthService(), - DbHandler().getUserDatabase(), - DbHandler().getLogin()) -{ - // Root div has to be a container - this->setStyleClass("container"); -} - - -void -LmsAuth::createLoggedInView() -{ - hide(); -} - -void -LmsAuth::createLoginView() -{ - show(); - Wt::Auth::AuthWidget::createLoginView(); -} - -} // namespace UserInterface From ba1968b02c8396f353509eb3e2f526a916e02b3a Mon Sep 17 00:00:00 2001 From: emeric Date: Tue, 13 Mar 2018 11:24:20 +0100 Subject: [PATCH 072/195] Now deleting auth cookie when logging out --- src/Makefile.am | 2 +- src/ui/{LoginView.cpp => Auth.cpp} | 35 +++++++++++++++++------------- src/ui/{LoginView.hpp => Auth.hpp} | 10 +++++++-- src/ui/LmsApplication.cpp | 8 +++---- src/ui/LmsApplication.hpp | 2 ++ 5 files changed, 35 insertions(+), 22 deletions(-) rename src/ui/{LoginView.cpp => Auth.cpp} (72%) rename src/ui/{LoginView.hpp => Auth.hpp} (83%) diff --git a/src/Makefile.am b/src/Makefile.am index 6e998f5b..70143ff1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -27,7 +27,7 @@ lms_SOURCES = \ $(srcdir)/ui/Filters.cpp \ $(srcdir)/ui/HomeView.cpp \ $(srcdir)/ui/LmsApplication.cpp \ - $(srcdir)/ui/LoginView.cpp \ + $(srcdir)/ui/Auth.cpp \ $(srcdir)/ui/MediaPlayer.cpp \ $(srcdir)/ui/PlayQueueView.cpp \ $(srcdir)/ui/ReleasesView.cpp \ diff --git a/src/ui/LoginView.cpp b/src/ui/Auth.cpp similarity index 72% rename from src/ui/LoginView.cpp rename to src/ui/Auth.cpp index f37fde26..70bd63c9 100644 --- a/src/ui/LoginView.cpp +++ b/src/ui/Auth.cpp @@ -21,23 +21,22 @@ #include #include #include -#include #include "utils/Logger.hpp" #include "common/Validators.hpp" #include "LmsApplication.hpp" -#include "LoginView.hpp" +#include "Auth.hpp" namespace UserInterface { -LoginView::LoginView(Wt::Auth::Login& login, Wt::WContainerWidget *parent) +Auth::Auth(Wt::WContainerWidget *parent) : Wt::WTemplateFormView(parent) { - auto model = new Wt::Auth::AuthModel(DbHandler().getAuthService(), DbHandler().getUserDatabase()); + _model = new Wt::Auth::AuthModel(DbHandler().getAuthService(), DbHandler().getUserDatabase()); - model->addPasswordAuth(&Database::Handler::getPasswordService()); + _model->addPasswordAuth(&Database::Handler::getPasswordService()); setTemplateText(Wt::WString::tr("template-login")); addFunction("tr", &WTemplate::Functions::tr); @@ -58,14 +57,14 @@ LoginView::LoginView(Wt::Auth::Login& login, Wt::WContainerWidget *parent) auto loginBtn = new Wt::WPushButton(Wt::WString::tr("msg-login")); bindWidget("login-btn", loginBtn); - loginBtn->clicked().connect(std::bind([=, &login] + loginBtn->clicked().connect(std::bind([=] { - updateModel(model); + updateModel(_model); - if (model->validate()) - model->login(login); + if (_model->validate()) + _model->login(DbHandler().getLogin()); else - updateView(model); + updateView(_model); })); password->enterPressed().connect(std::bind([=] @@ -73,16 +72,22 @@ LoginView::LoginView(Wt::Auth::Login& login, Wt::WContainerWidget *parent) loginBtn->clicked().emit(Wt::WMouseEvent()); })); - login.changed().connect(std::bind([=, &login] + DbHandler().getLogin().changed().connect(std::bind([=] { - if (login.loggedIn()) + if (DbHandler().getLogin().loggedIn()) this->setHidden(true); })); - Wt::Auth::User user = model->processAuthToken(); - model->loginUser(login, user, Wt::Auth::WeakLogin); + Wt::Auth::User user = _model->processAuthToken(); + _model->loginUser(DbHandler().getLogin(), user, Wt::Auth::WeakLogin); - updateView(model); + updateView(_model); +} + +void +Auth::logout() +{ + _model->logout(DbHandler().getLogin()); } } // namespace UserInterface diff --git a/src/ui/LoginView.hpp b/src/ui/Auth.hpp similarity index 83% rename from src/ui/LoginView.hpp rename to src/ui/Auth.hpp index 7a7bbea2..37e09eff 100644 --- a/src/ui/LoginView.hpp +++ b/src/ui/Auth.hpp @@ -22,13 +22,19 @@ #include #include #include +#include namespace UserInterface { -class LoginView : public Wt::WTemplateFormView +class Auth : public Wt::WTemplateFormView { public: - LoginView(Wt::Auth::Login& login, Wt::WContainerWidget *parent = 0); + Auth(Wt::WContainerWidget *parent = 0); + + void logout(); + + private: + Wt::Auth::AuthModel* _model; }; diff --git a/src/ui/LmsApplication.cpp b/src/ui/LmsApplication.cpp index dcb6f86e..75451c6c 100644 --- a/src/ui/LmsApplication.cpp +++ b/src/ui/LmsApplication.cpp @@ -30,7 +30,7 @@ #include "utils/Logger.hpp" #include "utils/Utils.hpp" -#include "LoginView.hpp" +#include "Auth.hpp" #include "Explore.hpp" #include "HomeView.hpp" #include "MediaPlayer.hpp" @@ -121,8 +121,8 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnecti { DbHandler().getLogin().changed().connect(this, &LmsApplication::handleAuthEvent); - auto auth = new LoginView(DbHandler().getLogin()); - root()->addWidget(auth); + _auth = new Auth(); + root()->addWidget(_auth); } } @@ -279,7 +279,7 @@ LmsApplication::handleAuthEvent(void) menuItem->triggered().connect(std::bind([=] { setConfirmCloseMessage(""); - DbHandler().getLogin().logout(); + _auth->logout(); })); } navbar->addMenu(rightMenu, Wt::AlignRight); diff --git a/src/ui/LmsApplication.hpp b/src/ui/LmsApplication.hpp index 74006fee..f812cad4 100644 --- a/src/ui/LmsApplication.hpp +++ b/src/ui/LmsApplication.hpp @@ -28,6 +28,7 @@ #include "resource/ImageResource.hpp" #include "resource/TranscodeResource.hpp" +#include "Auth.hpp" namespace UserInterface { @@ -56,6 +57,7 @@ class LmsApplication : public Wt::WApplication void handleAuthEvent(void); Database::Handler _db; + Auth* _auth; Scanner::MediaScanner& _scanner; ImageResource* _imageResource; TranscodeResource* _transcodeResource; From acf769c129d99976716e94a13b4236858dbe060a Mon Sep 17 00:00:00 2001 From: emeric Date: Tue, 13 Mar 2018 16:52:08 +0100 Subject: [PATCH 073/195] Minor cleanup to acces Application elements --- src/ui/ArtistView.cpp | 6 +++--- src/ui/ArtistsView.cpp | 2 +- src/ui/LmsApplication.cpp | 27 +++++++++++++++---------- src/ui/LmsApplication.hpp | 11 +++------- src/ui/ReleaseView.cpp | 6 +++--- src/ui/ReleasesView.cpp | 8 ++++---- src/ui/TracksView.cpp | 6 +++--- src/ui/settings/DatabaseView.cpp | 10 ++++----- src/ui/settings/FirstConnectionView.cpp | 2 +- 9 files changed, 38 insertions(+), 40 deletions(-) diff --git a/src/ui/ArtistView.cpp b/src/ui/ArtistView.cpp index eefc62a6..1a524ba0 100644 --- a/src/ui/ArtistView.cpp +++ b/src/ui/ArtistView.cpp @@ -121,16 +121,16 @@ Artist::refresh() entry->addFunction("tr", Wt::WTemplate::Functions::tr); { - Wt::WAnchor* coverAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/release/" + std::to_string(releaseId))); + Wt::WAnchor* coverAnchor = LmsApplication::createReleaseAnchor(releaseId); Wt::WImage* cover = new Wt::WImage(coverAnchor); - cover->setImageLink(SessionImageResource()->getReleaseUrl(release.id(), 128)); + cover->setImageLink(LmsApp->getImageResource()->getReleaseUrl(release.id(), 128)); // Some images may not be square cover->setWidth(128); entry->bindWidget("cover", coverAnchor); } { - Wt::WAnchor* releaseAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/release/" + std::to_string(releaseId))); + Wt::WAnchor* releaseAnchor = LmsApplication::createReleaseAnchor(releaseId); Wt::WText* releaseName = new Wt::WText(releaseAnchor); releaseName->setText(Wt::WString::fromUTF8(release->getName(), Wt::PlainText)); entry->bindWidget("name", releaseAnchor); diff --git a/src/ui/ArtistsView.cpp b/src/ui/ArtistsView.cpp index f801fa05..b5d1addb 100644 --- a/src/ui/ArtistsView.cpp +++ b/src/ui/ArtistsView.cpp @@ -93,7 +93,7 @@ Artists::addSome() entry->bindInt("nb-release", artist->getReleases(clusterIds).size()); { - Wt::WAnchor *artistAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/artist/" + std::to_string(artist.id()))); + Wt::WAnchor *artistAnchor = LmsApplication::createArtistAnchor(artist.id()); Wt::WText *artistText = new Wt::WText(artistAnchor); artistText->setText(Wt::WString::fromUTF8(artist->getName(), Wt::PlainText)); entry->bindWidget("name", artistAnchor); diff --git a/src/ui/LmsApplication.cpp b/src/ui/LmsApplication.cpp index 75451c6c..0db39c96 100644 --- a/src/ui/LmsApplication.cpp +++ b/src/ui/LmsApplication.cpp @@ -17,12 +17,13 @@ * along with LMS. If not, see . */ -#include +#include #include -#include -#include +#include #include +#include #include +#include #include #include @@ -30,7 +31,6 @@ #include "utils/Logger.hpp" #include "utils/Utils.hpp" -#include "Auth.hpp" #include "Explore.hpp" #include "HomeView.hpp" #include "MediaPlayer.hpp" @@ -41,10 +41,6 @@ #include "LmsApplication.hpp" -namespace skeletons { - extern const char *AuthStrings_xml1; -} - namespace UserInterface { Wt::WApplication* @@ -112,9 +108,6 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnecti if (firstConnection) { - // Hack, use the auth widget builtin strings - builtinLocalizedStrings().useBuiltin(skeletons::AuthStrings_xml1); - root()->addWidget(new Settings::FirstConnectionView()); } else @@ -160,6 +153,18 @@ Scanner::MediaScanner& MediaScanner() return LmsApplication::instance()->getMediaScanner(); } +Wt::WAnchor* +LmsApplication::createArtistAnchor(Database::id_type id) +{ + return new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/artist/" + std::to_string(id))); +} + +Wt::WAnchor* +LmsApplication::createReleaseAnchor(Database::id_type id) +{ + return new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/release/" + std::to_string(id))); +} + void LmsApplication::goHome() { diff --git a/src/ui/LmsApplication.hpp b/src/ui/LmsApplication.hpp index f812cad4..59f0875a 100644 --- a/src/ui/LmsApplication.hpp +++ b/src/ui/LmsApplication.hpp @@ -50,6 +50,9 @@ class LmsApplication : public Wt::WApplication void quit(); void notify(const Wt::WString& message); + static Wt::WAnchor* createArtistAnchor(Database::id_type id); + static Wt::WAnchor* createReleaseAnchor(Database::id_type id); + private: LmsApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnectionPool& connectionPool, Scanner::MediaScanner& scanner); @@ -72,14 +75,6 @@ Wt::Dbo::Session& DboSession(); const Wt::Auth::User& CurrentAuthUser(); Database::User::pointer CurrentUser(); -ImageResource *SessionImageResource(); -TranscodeResource *SessionTranscodeResource(); - -Scanner::MediaScanner& MediaScanner(); - -void notify(const Wt::WString& message); - - } // namespace UserInterface #endif diff --git a/src/ui/ReleaseView.cpp b/src/ui/ReleaseView.cpp index 8493da75..123b7eea 100644 --- a/src/ui/ReleaseView.cpp +++ b/src/ui/ReleaseView.cpp @@ -99,7 +99,7 @@ Release::refresh() } else { - Wt::WAnchor *artistAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/artist/" + std::to_string(artists.front().id()))); + Wt::WAnchor *artistAnchor = LmsApplication::createArtistAnchor(artists.front().id()); Wt::WText *artist = new Wt::WText(artistAnchor); artist->setText(Wt::WString::fromUTF8(artists.front()->getName(), Wt::PlainText)); t->bindWidget("artist-name", artistAnchor); @@ -107,7 +107,7 @@ Release::refresh() } Wt::WImage *cover = new Wt::WImage(); - cover->setImageLink(Wt::WLink (SessionImageResource()->getReleaseUrl(release.id(), 512))); + cover->setImageLink(Wt::WLink(LmsApp->getImageResource()->getReleaseUrl(release.id(), 512))); t->bindWidget("cover", cover); auto clusterContainers = new Wt::WContainerWidget(); @@ -161,7 +161,7 @@ Release::refresh() if (variousArtists && track->getArtist()) { entry->setCondition("if-has-artist", true); - Wt::WAnchor *artistAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/artist/" + std::to_string(track->getArtist().id()))); + Wt::WAnchor *artistAnchor = LmsApplication::createArtistAnchor(track->getArtist().id()); Wt::WText *artistText = new Wt::WText(artistAnchor); artistText->setText(Wt::WString::fromUTF8(track->getArtist()->getName(), Wt::PlainText)); entry->bindWidget("artist-name", artistAnchor); diff --git a/src/ui/ReleasesView.cpp b/src/ui/ReleasesView.cpp index f8b9df0e..1cb5a00a 100644 --- a/src/ui/ReleasesView.cpp +++ b/src/ui/ReleasesView.cpp @@ -91,15 +91,15 @@ Releases::addSome() Wt::WTemplate* entry = new Wt::WTemplate(Wt::WString::tr("template-releases-entry"), _releasesContainer); entry->addFunction("tr", Wt::WTemplate::Functions::tr); - Wt::WAnchor* coverAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/release/" + std::to_string(releaseId))); + Wt::WAnchor* coverAnchor = LmsApplication::createReleaseAnchor(releaseId); Wt::WImage* cover = new Wt::WImage(coverAnchor); - cover->setImageLink(SessionImageResource()->getReleaseUrl(releaseId, 128)); + cover->setImageLink(LmsApp->getImageResource()->getReleaseUrl(releaseId, 128)); // Some images may not be square cover->setWidth(128); entry->bindWidget("cover", coverAnchor); { - Wt::WAnchor* releaseAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/release/" + std::to_string(releaseId))); + Wt::WAnchor* releaseAnchor = LmsApplication::createReleaseAnchor(releaseId); Wt::WText* releaseName = new Wt::WText(releaseAnchor); releaseName->setText(Wt::WString::fromUTF8(release->getName(), Wt::PlainText)); entry->bindWidget("release-name", releaseAnchor); @@ -113,7 +113,7 @@ Releases::addSome() } else { - Wt::WAnchor* artistAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/artist/" + std::to_string(artists.front().id()))); + Wt::WAnchor* artistAnchor = LmsApplication::createArtistAnchor(artists.front().id()); Wt::WText* artist = new Wt::WText(artistAnchor); artist->setText(Wt::WString::fromUTF8(artists.front()->getName(), Wt::PlainText)); entry->bindWidget("artist-name", artistAnchor); diff --git a/src/ui/TracksView.cpp b/src/ui/TracksView.cpp index db82be6a..81341975 100644 --- a/src/ui/TracksView.cpp +++ b/src/ui/TracksView.cpp @@ -124,7 +124,7 @@ Tracks::addSome() if (artist) { entry->setCondition("if-has-artist", true); - Wt::WAnchor *artistAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/artist/" + std::to_string(track->getArtist().id()))); + Wt::WAnchor *artistAnchor = LmsApplication::createArtistAnchor(track->getArtist().id()); Wt::WText *artistText = new Wt::WText(artistAnchor); artistText->setText(Wt::WString::fromUTF8(artist->getName(), Wt::PlainText)); entry->bindWidget("artist-name", artistAnchor); @@ -134,14 +134,14 @@ Tracks::addSome() if (release) { entry->setCondition("if-has-release", true); - Wt::WAnchor *releaseAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/release/" + std::to_string(track->getRelease().id()))); + Wt::WAnchor *releaseAnchor = LmsApplication::createReleaseAnchor(track->getRelease().id()); Wt::WText *releaseText = new Wt::WText(releaseAnchor); releaseText->setText(Wt::WString::fromUTF8(release->getName(), Wt::PlainText)); entry->bindWidget("release-name", releaseAnchor); } Wt::WImage *cover = new Wt::WImage(); - cover->setImageLink(SessionImageResource()->getTrackUrl(track.id(), 64)); + cover->setImageLink(LmsApp->getImageResource()->getTrackUrl(track.id(), 64)); // Some images may not be square cover->setWidth(64); entry->bindWidget("cover", cover); diff --git a/src/ui/settings/DatabaseView.cpp b/src/ui/settings/DatabaseView.cpp index 4986348c..1ee1ac28 100644 --- a/src/ui/settings/DatabaseView.cpp +++ b/src/ui/settings/DatabaseView.cpp @@ -267,9 +267,8 @@ DatabaseView::DatabaseView(Wt::WContainerWidget *parent) { model->saveData(); - MediaScanner().reschedule(); - - notify(Wt::WString::tr("msg-notify-settings-saved")); + LmsApp->getMediaScanner().reschedule(); + LmsApp->notify(Wt::WString::tr("msg-notify-settings-saved")); } // Udate the view: Delete any validation message in the view, etc. @@ -285,9 +284,8 @@ DatabaseView::DatabaseView(Wt::WContainerWidget *parent) immScanBtn->clicked().connect(std::bind([=] () { - MediaScanner().scheduleImmediateScan(); - - notify(Wt::WString::tr("msg-notify-scan-launched")); + LmsApp->getMediaScanner().scheduleImmediateScan(); + LmsApp->notify(Wt::WString::tr("msg-notify-scan-launched")); })); updateView(model); diff --git a/src/ui/settings/FirstConnectionView.cpp b/src/ui/settings/FirstConnectionView.cpp index 5b1dac97..5ac4dfc9 100644 --- a/src/ui/settings/FirstConnectionView.cpp +++ b/src/ui/settings/FirstConnectionView.cpp @@ -149,7 +149,7 @@ FirstConnectionView::FirstConnectionView(Wt::WContainerWidget *parent) if (model->validate()) { model->saveData(); - notify(Wt::WString::tr("msg-settings-first-connection-done")); + LmsApp->notify(Wt::WString::tr("msg-settings-first-connection-done")); saveButton->setEnabled(false); } From 98f64e62f98633d77a3d9b76ad28ef7214762393 Mon Sep 17 00:00:00 2001 From: emeric Date: Tue, 13 Mar 2018 17:18:57 +0100 Subject: [PATCH 074/195] Removed overkill settings namespace --- Makefile.am | 2 +- ...nnection.xml => settings-admin-wizard.xml} | 2 +- src/Makefile.am | 4 +-- src/ui/LmsApplication.cpp | 10 +++---- ...ConnectionView.cpp => AdminWizardView.cpp} | 26 +++++++++---------- .../{DatabaseView.hpp => AdminWizardView.hpp} | 9 +++---- ...abaseView.cpp => DatabaseSettingsView.cpp} | 24 ++++++++--------- ...ctionView.hpp => DatabaseSettingsView.hpp} | 8 ++---- 8 files changed, 38 insertions(+), 47 deletions(-) rename approot/{settings-first-connection.xml => settings-admin-wizard.xml} (96%) rename src/ui/settings/{FirstConnectionView.cpp => AdminWizardView.cpp} (82%) rename src/ui/settings/{DatabaseView.hpp => AdminWizardView.hpp} (85%) rename src/ui/settings/{DatabaseView.cpp => DatabaseSettingsView.cpp} (91%) rename src/ui/settings/{FirstConnectionView.hpp => DatabaseSettingsView.hpp} (84%) diff --git a/Makefile.am b/Makefile.am index 3c24abc5..deedb672 100644 --- a/Makefile.am +++ b/Makefile.am @@ -26,7 +26,7 @@ lms_approot_DATA = \ approot/releases.xml \ approot/release.xml \ approot/settings-database.xml \ - approot/settings-first-connection.xml \ + approot/settings-admin-wizard.xml \ approot/templates.xml \ approot/tracks.xml diff --git a/approot/settings-first-connection.xml b/approot/settings-admin-wizard.xml similarity index 96% rename from approot/settings-first-connection.xml rename to approot/settings-admin-wizard.xml index c4fda6db..4618b6c8 100644 --- a/approot/settings-first-connection.xml +++ b/approot/settings-admin-wizard.xml @@ -3,7 +3,7 @@ - +