Centralized scan settings into a dedicated ScanSettings table
This commit is contained in:
+1
-2
@@ -8,16 +8,15 @@ lms_SOURCES = \
|
|||||||
$(srcdir)/database/Artist.cpp \
|
$(srcdir)/database/Artist.cpp \
|
||||||
$(srcdir)/database/Cluster.cpp \
|
$(srcdir)/database/Cluster.cpp \
|
||||||
$(srcdir)/database/DatabaseHandler.cpp \
|
$(srcdir)/database/DatabaseHandler.cpp \
|
||||||
$(srcdir)/database/MediaDirectory.cpp \
|
|
||||||
$(srcdir)/database/Playlist.cpp \
|
$(srcdir)/database/Playlist.cpp \
|
||||||
$(srcdir)/database/Release.cpp \
|
$(srcdir)/database/Release.cpp \
|
||||||
|
$(srcdir)/database/ScanSettings.cpp \
|
||||||
$(srcdir)/database/Setting.cpp \
|
$(srcdir)/database/Setting.cpp \
|
||||||
$(srcdir)/database/SqlQuery.cpp \
|
$(srcdir)/database/SqlQuery.cpp \
|
||||||
$(srcdir)/database/Track.cpp \
|
$(srcdir)/database/Track.cpp \
|
||||||
$(srcdir)/database/User.cpp \
|
$(srcdir)/database/User.cpp \
|
||||||
$(srcdir)/image/Image.cpp \
|
$(srcdir)/image/Image.cpp \
|
||||||
$(srcdir)/metadata/AvFormat.cpp \
|
$(srcdir)/metadata/AvFormat.cpp \
|
||||||
$(srcdir)/metadata/MetaData.cpp \
|
|
||||||
$(srcdir)/metadata/TagLibParser.cpp \
|
$(srcdir)/metadata/TagLibParser.cpp \
|
||||||
$(srcdir)/scanner/MediaScanner.cpp \
|
$(srcdir)/scanner/MediaScanner.cpp \
|
||||||
$(srcdir)/ui/Auth.cpp \
|
$(srcdir)/ui/Auth.cpp \
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
#include "Artist.hpp"
|
#include "Artist.hpp"
|
||||||
#include "Release.hpp"
|
#include "Release.hpp"
|
||||||
|
#include "ScanSettings.hpp"
|
||||||
#include "SqlQuery.hpp"
|
#include "SqlQuery.hpp"
|
||||||
#include "Track.hpp"
|
#include "Track.hpp"
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ namespace Database {
|
|||||||
|
|
||||||
class Track;
|
class Track;
|
||||||
class ClusterType;
|
class ClusterType;
|
||||||
|
class ScanSettings;
|
||||||
|
|
||||||
class Cluster : public Wt::Dbo::Dbo<Cluster>
|
class Cluster : public Wt::Dbo::Dbo<Cluster>
|
||||||
{
|
{
|
||||||
@@ -101,6 +102,7 @@ class ClusterType : public Wt::Dbo::Dbo<ClusterType>
|
|||||||
{
|
{
|
||||||
Wt::Dbo::field(a, _name, "name");
|
Wt::Dbo::field(a, _name, "name");
|
||||||
Wt::Dbo::hasMany(a, _clusters, Wt::Dbo::ManyToOne, "cluster_type");
|
Wt::Dbo::hasMany(a, _clusters, Wt::Dbo::ManyToOne, "cluster_type");
|
||||||
|
Wt::Dbo::belongsTo(a, _scanSettings, "scan_settings", Wt::Dbo::OnDeleteCascade);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -109,6 +111,7 @@ class ClusterType : public Wt::Dbo::Dbo<ClusterType>
|
|||||||
|
|
||||||
std::string _name;
|
std::string _name;
|
||||||
Wt::Dbo::collection< Wt::Dbo::ptr<Cluster> > _clusters;
|
Wt::Dbo::collection< Wt::Dbo::ptr<Cluster> > _clusters;
|
||||||
|
Wt::Dbo::ptr<ScanSettings> _scanSettings;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Database
|
} // namespace Database
|
||||||
|
|||||||
@@ -37,9 +37,9 @@
|
|||||||
|
|
||||||
#include "Artist.hpp"
|
#include "Artist.hpp"
|
||||||
#include "Cluster.hpp"
|
#include "Cluster.hpp"
|
||||||
#include "MediaDirectory.hpp"
|
|
||||||
#include "Playlist.hpp"
|
#include "Playlist.hpp"
|
||||||
#include "Release.hpp"
|
#include "Release.hpp"
|
||||||
|
#include "ScanSettings.hpp"
|
||||||
#include "Track.hpp"
|
#include "Track.hpp"
|
||||||
|
|
||||||
namespace Database {
|
namespace Database {
|
||||||
@@ -98,20 +98,21 @@ Handler::Handler(Wt::Dbo::SqlConnectionPool& connectionPool)
|
|||||||
{
|
{
|
||||||
_session.setConnectionPool(connectionPool);
|
_session.setConnectionPool(connectionPool);
|
||||||
|
|
||||||
_session.mapClass<Database::Artist>("artist");
|
_session.mapClass<Artist>("artist");
|
||||||
_session.mapClass<Database::Cluster>("cluster");
|
_session.mapClass<Cluster>("cluster");
|
||||||
_session.mapClass<Database::ClusterType>("cluster_type");
|
_session.mapClass<ClusterType>("cluster_type");
|
||||||
_session.mapClass<Database::MediaDirectory>("media_directory");
|
_session.mapClass<Playlist>("playlist");
|
||||||
_session.mapClass<Database::Playlist>("playlist");
|
_session.mapClass<PlaylistEntry>("playlist_entry");
|
||||||
_session.mapClass<Database::PlaylistEntry>("playlist_entry");
|
_session.mapClass<Release>("release");
|
||||||
_session.mapClass<Database::Release>("release");
|
_session.mapClass<Setting>("setting");
|
||||||
_session.mapClass<Database::Setting>("setting");
|
_session.mapClass<Track>("track");
|
||||||
_session.mapClass<Database::Track>("track");
|
|
||||||
|
|
||||||
_session.mapClass<Database::AuthInfo>("auth_info");
|
_session.mapClass<ScanSettings>("scan_settings");
|
||||||
_session.mapClass<Database::AuthInfo::AuthIdentityType>("auth_identity");
|
|
||||||
_session.mapClass<Database::AuthInfo::AuthTokenType>("auth_token");
|
_session.mapClass<AuthInfo>("auth_info");
|
||||||
_session.mapClass<Database::User>("user");
|
_session.mapClass<AuthInfo::AuthIdentityType>("auth_identity");
|
||||||
|
_session.mapClass<AuthInfo::AuthTokenType>("auth_token");
|
||||||
|
_session.mapClass<User>("user");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Wt::Dbo::Transaction transaction(_session);
|
Wt::Dbo::Transaction transaction(_session);
|
||||||
|
|||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "MediaDirectory.hpp"
|
|
||||||
|
|
||||||
#include "utils/Utils.hpp"
|
|
||||||
|
|
||||||
namespace Database {
|
|
||||||
|
|
||||||
MediaDirectory::MediaDirectory(boost::filesystem::path p)
|
|
||||||
: _path(stringTrimEnd(p.string(), "/\\"))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
MediaDirectory::pointer
|
|
||||||
MediaDirectory::create(Wt::Dbo::Session& session, boost::filesystem::path p)
|
|
||||||
{
|
|
||||||
return session.add( std::make_unique<MediaDirectory>(p) );
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
MediaDirectory::eraseAll(Wt::Dbo::Session& session)
|
|
||||||
{
|
|
||||||
for (auto dir : getAll(session))
|
|
||||||
dir.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<MediaDirectory::pointer>
|
|
||||||
MediaDirectory::getAll(Wt::Dbo::Session& session)
|
|
||||||
{
|
|
||||||
Wt::Dbo::collection< MediaDirectory::pointer > res = session.find<MediaDirectory>();
|
|
||||||
|
|
||||||
return std::vector<MediaDirectory::pointer>(res.begin(), res.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
boost::filesystem::path
|
|
||||||
MediaDirectory::getPath(void) const
|
|
||||||
{
|
|
||||||
return boost::filesystem::path(stringTrimEnd(_path, "/\\"));
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Database
|
|
||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <boost/filesystem/path.hpp>
|
|
||||||
|
|
||||||
#include <Wt/Dbo/Dbo.h>
|
|
||||||
|
|
||||||
namespace Database {
|
|
||||||
|
|
||||||
class MediaDirectory
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
typedef Wt::Dbo::ptr<MediaDirectory> pointer;
|
|
||||||
|
|
||||||
MediaDirectory() {}
|
|
||||||
MediaDirectory(boost::filesystem::path p);
|
|
||||||
|
|
||||||
// Accessors
|
|
||||||
static pointer create(Wt::Dbo::Session& session, boost::filesystem::path p);
|
|
||||||
static std::vector<MediaDirectory::pointer> getAll(Wt::Dbo::Session& session);
|
|
||||||
|
|
||||||
static void eraseAll(Wt::Dbo::Session& session);
|
|
||||||
static void eraseByPath(Wt::Dbo::Session& session, boost::filesystem::path p);
|
|
||||||
|
|
||||||
boost::filesystem::path getPath(void) const;
|
|
||||||
|
|
||||||
template<class Action>
|
|
||||||
void persist(Action& a)
|
|
||||||
{
|
|
||||||
Wt::Dbo::field(a, _path, "path");
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
std::string _path;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Database
|
|
||||||
|
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ScanSettings.hpp"
|
||||||
|
|
||||||
|
#include <Wt/Dbo/WtSqlTraits.h>
|
||||||
|
|
||||||
|
#include "utils/Logger.hpp"
|
||||||
|
#include "utils/Utils.hpp"
|
||||||
|
|
||||||
|
#include "Cluster.hpp"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::set<std::string> defaultClusterTypeNames =
|
||||||
|
{
|
||||||
|
"GENRE",
|
||||||
|
"ALBUMGROUPING",
|
||||||
|
"MOOD",
|
||||||
|
"ALBUMMOOD",
|
||||||
|
"COMMENT:SONGS-DB_OCCASION",
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Database {
|
||||||
|
|
||||||
|
|
||||||
|
ScanSettings::pointer
|
||||||
|
ScanSettings::get(Wt::Dbo::Session& session)
|
||||||
|
{
|
||||||
|
ScanSettings::pointer settings = session.find<ScanSettings>();
|
||||||
|
if (!settings)
|
||||||
|
{
|
||||||
|
settings = session.add(std::make_unique<ScanSettings>());
|
||||||
|
settings.modify()->setClusterTypes(defaultClusterTypeNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::set<boost::filesystem::path>
|
||||||
|
ScanSettings::getAudioFileExtensions() const
|
||||||
|
{
|
||||||
|
auto extensions = splitString(_audioFileExtensions, " ");
|
||||||
|
return std::set<boost::filesystem::path>(extensions.begin(), extensions.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ClusterType::pointer>
|
||||||
|
ScanSettings::getClusterTypes() const
|
||||||
|
{
|
||||||
|
return std::vector<ClusterType::pointer>(_clusterTypes.begin(), _clusterTypes.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ScanSettings::setMediaDirectory(boost::filesystem::path p)
|
||||||
|
{
|
||||||
|
_mediaDirectory = stringTrimEnd(p.string(), "/\\");
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ScanSettings::setClusterTypes(const std::set<std::string>& clusterTypeNames)
|
||||||
|
{
|
||||||
|
assert(session());
|
||||||
|
|
||||||
|
// Backup the old list
|
||||||
|
std::vector<ClusterType::pointer> oldClusterTypes(_clusterTypes.begin(), _clusterTypes.end());
|
||||||
|
|
||||||
|
_clusterTypes.clear();
|
||||||
|
// Create any missing cluster type
|
||||||
|
for (auto clusterTypeName : clusterTypeNames)
|
||||||
|
{
|
||||||
|
auto clusterType = ClusterType::getByName(*session(), clusterTypeName);
|
||||||
|
if (!clusterType)
|
||||||
|
{
|
||||||
|
LMS_LOG(DB, INFO) << "Creating cluster type " << clusterTypeName;
|
||||||
|
clusterType = ClusterType::create(*session(), clusterTypeName);
|
||||||
|
}
|
||||||
|
_clusterTypes.insert(clusterType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete no longer existing cluster types
|
||||||
|
for (auto oldClusterType : oldClusterTypes)
|
||||||
|
{
|
||||||
|
if (std::none_of(clusterTypeNames.begin(), clusterTypeNames.end(),
|
||||||
|
[oldClusterType](const std::string& name) { return name == oldClusterType->getName(); }))
|
||||||
|
{
|
||||||
|
LMS_LOG(DB, INFO) << "Deleting cluster type " << oldClusterType->getName();
|
||||||
|
oldClusterType.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Database
|
||||||
|
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <boost/filesystem.hpp>
|
||||||
|
|
||||||
|
#include <Wt/Dbo/Dbo.h>
|
||||||
|
#include <Wt/WTime.h>
|
||||||
|
|
||||||
|
namespace Database {
|
||||||
|
|
||||||
|
class ClusterType;
|
||||||
|
// class meant to store general settings
|
||||||
|
class ScanSettings : public Wt::Dbo::Dbo<ScanSettings>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using pointer = Wt::Dbo::ptr<ScanSettings>;
|
||||||
|
|
||||||
|
enum class UpdatePeriod {
|
||||||
|
Never = 0,
|
||||||
|
Daily,
|
||||||
|
Weekly,
|
||||||
|
Monthly
|
||||||
|
};
|
||||||
|
|
||||||
|
ScanSettings() {}
|
||||||
|
|
||||||
|
static pointer get(Wt::Dbo::Session& session);
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
boost::filesystem::path getMediaDirectory() const { return _mediaDirectory; }
|
||||||
|
Wt::WTime getUpdateStartTime() const { return _startTime; }
|
||||||
|
UpdatePeriod getUpdatePeriod() const { return _updatePeriod; }
|
||||||
|
std::vector<Wt::Dbo::ptr<ClusterType>> getClusterTypes() const;
|
||||||
|
std::set<boost::filesystem::path> getAudioFileExtensions() const;
|
||||||
|
|
||||||
|
// Setters
|
||||||
|
void setMediaDirectory(boost::filesystem::path p);
|
||||||
|
void setUpdateStartTime(Wt::WTime t) { _startTime = t; }
|
||||||
|
void setUpdatePeriod(UpdatePeriod p) { _updatePeriod = p; }
|
||||||
|
void setClusterTypes(const std::set<std::string>& clusterTypeNames);
|
||||||
|
void setAudioFileExtensions(std::set<boost::filesystem::path> fileExtensions);
|
||||||
|
|
||||||
|
template<class Action>
|
||||||
|
void persist(Action& a)
|
||||||
|
{
|
||||||
|
Wt::Dbo::field(a, _mediaDirectory, "media_directory");
|
||||||
|
Wt::Dbo::field(a, _startTime, "start_time");
|
||||||
|
Wt::Dbo::field(a, _updatePeriod, "update_period");
|
||||||
|
Wt::Dbo::field(a, _audioFileExtensions, "audio_file_extensions");
|
||||||
|
Wt::Dbo::hasMany(a, _clusterTypes, Wt::Dbo::ManyToOne, "scan_settings");
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::string _mediaDirectory = "";
|
||||||
|
Wt::WTime _startTime = Wt::WTime(0,0,0);
|
||||||
|
UpdatePeriod _updatePeriod = UpdatePeriod::Never;
|
||||||
|
std::string _audioFileExtensions = ".mp3 .ogg .oga .aac .m4a .flac .wav .wma .aif .aiff .ape .mpc .shn";
|
||||||
|
Wt::Dbo::collection<Wt::Dbo::ptr<ClusterType>> _clusterTypes;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace Database
|
||||||
|
|
||||||
@@ -29,11 +29,6 @@
|
|||||||
namespace MetaData
|
namespace MetaData
|
||||||
{
|
{
|
||||||
|
|
||||||
AvFormat::AvFormat(const ClusterTypes& clusterTypes)
|
|
||||||
: Parser(clusterTypes)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
boost::optional<Items>
|
boost::optional<Items>
|
||||||
AvFormat::parse(const boost::filesystem::path& p, bool debug)
|
AvFormat::parse(const boost::filesystem::path& p, bool debug)
|
||||||
{
|
{
|
||||||
@@ -155,7 +150,7 @@ AvFormat::parse(const boost::filesystem::path& p, bool debug)
|
|||||||
{
|
{
|
||||||
items.insert( std::make_pair(MetaData::Type::AcoustID, stringTrim(value)) );
|
items.insert( std::make_pair(MetaData::Type::AcoustID, stringTrim(value)) );
|
||||||
}
|
}
|
||||||
else if (_clusterTypes.find(tag) != _clusterTypes.end())
|
else if (_clusterTypeNames.find(tag) != _clusterTypeNames.end())
|
||||||
{
|
{
|
||||||
std::vector<std::string> clusterNames = splitString(value, ";,\\");
|
std::vector<std::string> clusterNames = splitString(value, ";,\\");
|
||||||
|
|
||||||
|
|||||||
@@ -31,10 +31,7 @@ namespace MetaData
|
|||||||
class AvFormat : public Parser
|
class AvFormat : public Parser
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
boost::optional<Items> parse(const boost::filesystem::path& p, bool debug = false) override;
|
||||||
AvFormat(const ClusterTypes& clusterTypes = defaultClusterTypes);
|
|
||||||
|
|
||||||
boost::optional<Items> parse(const boost::filesystem::path& p, bool debug = false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace MetaData
|
} // namespace MetaData
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "MetaData.hpp"
|
|
||||||
|
|
||||||
namespace MetaData {
|
|
||||||
|
|
||||||
const ClusterTypes Parser::defaultClusterTypes =
|
|
||||||
{
|
|
||||||
"GENRE",
|
|
||||||
"ALBUMGROUPING",
|
|
||||||
"MOOD",
|
|
||||||
"ALBUMMOOD",
|
|
||||||
"COMMENT:SONGS-DB_OCCASION",
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace MetaData
|
|
||||||
@@ -63,24 +63,16 @@ namespace MetaData
|
|||||||
// See enum Type's comments
|
// See enum Type's comments
|
||||||
using Items = std::map<Type, boost::any>;
|
using Items = std::map<Type, boost::any>;
|
||||||
using Clusters = std::map<std::string /* type */, std::set<std::string> /* names */>;
|
using Clusters = std::map<std::string /* type */, std::set<std::string> /* names */>;
|
||||||
using ClusterTypes = std::set<std::string>;
|
|
||||||
|
|
||||||
class Parser
|
class Parser
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static const ClusterTypes defaultClusterTypes;
|
|
||||||
|
|
||||||
// Provide a map for tag name -> Cluster name
|
|
||||||
Parser(const ClusterTypes& clusterTypes) : _clusterTypes(clusterTypes) {}
|
|
||||||
|
|
||||||
virtual boost::optional<Items> parse(const boost::filesystem::path& p, bool debug = false) = 0;
|
virtual boost::optional<Items> parse(const boost::filesystem::path& p, bool debug = false) = 0;
|
||||||
|
|
||||||
void updateClusterTypes(const ClusterTypes& clusterTypes) { _clusterTypes = clusterTypes; }
|
void setClusterTypeNames(const std::set<std::string>& clusterTypeNames) { _clusterTypeNames = clusterTypeNames; }
|
||||||
const ClusterTypes& getClusterTypes() const { return _clusterTypes; }
|
|
||||||
bool isClusterTypeSupported(const std::string& clusterType) const { return _clusterTypes.find(clusterType) != _clusterTypes.end(); }
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
ClusterTypes _clusterTypes;
|
std::set<std::string> _clusterTypeNames;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace MetaData
|
} // namespace MetaData
|
||||||
|
|||||||
@@ -31,11 +31,6 @@
|
|||||||
namespace MetaData
|
namespace MetaData
|
||||||
{
|
{
|
||||||
|
|
||||||
TagLibParser::TagLibParser(const ClusterTypes& clusterTypes)
|
|
||||||
: Parser(clusterTypes)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
boost::optional<Items>
|
boost::optional<Items>
|
||||||
TagLibParser::parse(const boost::filesystem::path& p, bool debug)
|
TagLibParser::parse(const boost::filesystem::path& p, bool debug)
|
||||||
{
|
{
|
||||||
@@ -199,7 +194,7 @@ TagLibParser::parse(const boost::filesystem::path& p, bool debug)
|
|||||||
if (items.find(MetaData::Type::HasCover) == items.end())
|
if (items.find(MetaData::Type::HasCover) == items.end())
|
||||||
items.insert( std::make_pair(MetaData::Type::HasCover, true));
|
items.insert( std::make_pair(MetaData::Type::HasCover, true));
|
||||||
}
|
}
|
||||||
else if (_clusterTypes.find(tag) != _clusterTypes.end())
|
else if (_clusterTypeNames.find(tag) != _clusterTypeNames.end())
|
||||||
{
|
{
|
||||||
std::set<std::string> clusterNames;
|
std::set<std::string> clusterNames;
|
||||||
for (const auto& value : values)
|
for (const auto& value : values)
|
||||||
|
|||||||
@@ -31,10 +31,7 @@ namespace MetaData
|
|||||||
class TagLibParser : public Parser
|
class TagLibParser : public Parser
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
boost::optional<Items> parse(const boost::filesystem::path& p, bool debug = false) override;
|
||||||
TagLibParser(const ClusterTypes& clusterTypes = defaultClusterTypes);
|
|
||||||
|
|
||||||
boost::optional<Items> parse(const boost::filesystem::path& p, bool debug = false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace MetaData
|
} // namespace MetaData
|
||||||
|
|||||||
+38
-169
@@ -31,9 +31,8 @@
|
|||||||
#include "database/Artist.hpp"
|
#include "database/Artist.hpp"
|
||||||
#include "database/Cluster.hpp"
|
#include "database/Cluster.hpp"
|
||||||
#include "database/Release.hpp"
|
#include "database/Release.hpp"
|
||||||
#include "database/MediaDirectory.hpp"
|
#include "database/ScanSettings.hpp"
|
||||||
#include "database/Track.hpp"
|
#include "database/Track.hpp"
|
||||||
#include "database/Setting.hpp"
|
|
||||||
|
|
||||||
#include "utils/Logger.hpp"
|
#include "utils/Logger.hpp"
|
||||||
#include "utils/Path.hpp"
|
#include "utils/Path.hpp"
|
||||||
@@ -43,28 +42,6 @@ using namespace Database;
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
const std::string updatePeriodSetting = "update_period";
|
|
||||||
const std::string updateStartTimeSetting = "update_start_time";
|
|
||||||
const std::string clusterTypesSetting = "cluster_types";
|
|
||||||
const std::string fileExtensionsSetting = "file_extensions";
|
|
||||||
|
|
||||||
const std::vector<std::string> defaultFileExtensions =
|
|
||||||
{
|
|
||||||
".mp3",
|
|
||||||
".ogg",
|
|
||||||
".oga",
|
|
||||||
".aac",
|
|
||||||
".m4a",
|
|
||||||
".flac",
|
|
||||||
".wav",
|
|
||||||
".wma",
|
|
||||||
".aif",
|
|
||||||
".aiff",
|
|
||||||
".ape",
|
|
||||||
".mpc",
|
|
||||||
".shn",
|
|
||||||
};
|
|
||||||
|
|
||||||
Wt::WDate
|
Wt::WDate
|
||||||
getNextMonday(Wt::WDate current)
|
getNextMonday(Wt::WDate current)
|
||||||
{
|
{
|
||||||
@@ -88,17 +65,9 @@ getNextFirstOfMonth(Wt::WDate current)
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
isFileSupported(const boost::filesystem::path& file, const std::vector<boost::filesystem::path> extensions)
|
isFileSupported(const boost::filesystem::path& file, const std::set<boost::filesystem::path>& extensions)
|
||||||
{
|
{
|
||||||
boost::filesystem::path fileExtension = file.extension();
|
return (extensions.find(file.extension()) != extensions.end());
|
||||||
|
|
||||||
for (auto& extension : extensions)
|
|
||||||
{
|
|
||||||
if (extension == fileExtension)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
@@ -219,47 +188,6 @@ getClusters(Wt::Dbo::Session& session, const MetaData::Clusters& clustersNames)
|
|||||||
|
|
||||||
namespace Scanner {
|
namespace Scanner {
|
||||||
|
|
||||||
UpdatePeriod
|
|
||||||
getUpdatePeriod(Wt::Dbo::Session& session)
|
|
||||||
{
|
|
||||||
return static_cast<UpdatePeriod>(Setting::getInt(session, updatePeriodSetting, static_cast<int>(UpdatePeriod::Never)));
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
setUpdatePeriod(Wt::Dbo::Session& session, UpdatePeriod updatePeriod)
|
|
||||||
{
|
|
||||||
Setting::setInt(session, updatePeriodSetting, static_cast<int>(updatePeriod));
|
|
||||||
}
|
|
||||||
|
|
||||||
Wt::WTime
|
|
||||||
getUpdateStartTime(Wt::Dbo::Session& session)
|
|
||||||
{
|
|
||||||
return Setting::getTime(session, updateStartTimeSetting);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
setUpdateStartTime(Wt::Dbo::Session& session, Wt::WTime startTime)
|
|
||||||
{
|
|
||||||
Setting::setTime(session, updateStartTimeSetting, startTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::set<std::string>
|
|
||||||
getClusterTypes(Wt::Dbo::Session& session)
|
|
||||||
{
|
|
||||||
MetaData::ClusterTypes clusterTypes;
|
|
||||||
|
|
||||||
for (auto cluster : splitString(Setting::getString(session, clusterTypesSetting), " "))
|
|
||||||
clusterTypes.insert(cluster);
|
|
||||||
|
|
||||||
return clusterTypes;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setClusterTypes(Wt::Dbo::Session& session, const std::set<std::string> clusterTypes)
|
|
||||||
{
|
|
||||||
std::vector<std::string> vecClusterTypes(clusterTypes.begin(), clusterTypes.end());
|
|
||||||
Setting::setString(session, clusterTypesSetting, joinStrings(vecClusterTypes, " "));
|
|
||||||
}
|
|
||||||
|
|
||||||
MediaScanner::MediaScanner(Wt::Dbo::SqlConnectionPool& connectionPool)
|
MediaScanner::MediaScanner(Wt::Dbo::SqlConnectionPool& connectionPool)
|
||||||
: _running(false),
|
: _running(false),
|
||||||
_scheduleTimer(_ioService),
|
_scheduleTimer(_ioService),
|
||||||
@@ -267,14 +195,6 @@ _db(connectionPool)
|
|||||||
{
|
{
|
||||||
_ioService.setThreadCount(1);
|
_ioService.setThreadCount(1);
|
||||||
|
|
||||||
Wt::Dbo::Transaction transaction(_db.getSession());
|
|
||||||
|
|
||||||
if (!Setting::exists(_db.getSession(), fileExtensionsSetting))
|
|
||||||
Setting::setString(_db.getSession(), fileExtensionsSetting, joinStrings(defaultFileExtensions, " "));
|
|
||||||
|
|
||||||
if (!Setting::exists(_db.getSession(), clusterTypesSetting))
|
|
||||||
setClusterTypes(_db.getSession(), MetaData::Parser::defaultClusterTypes);
|
|
||||||
|
|
||||||
refreshScanSettings();
|
refreshScanSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,43 +249,41 @@ MediaScanner::reschedule()
|
|||||||
void
|
void
|
||||||
MediaScanner::scheduleScan()
|
MediaScanner::scheduleScan()
|
||||||
{
|
{
|
||||||
using namespace std::chrono_literals;
|
refreshScanSettings();
|
||||||
|
|
||||||
Wt::WTime startTime = getUpdateStartTime(_db.getSession());
|
|
||||||
Wt::WDateTime now = Wt::WLocalDateTime::currentServerDateTime().toUTC();
|
Wt::WDateTime now = Wt::WLocalDateTime::currentServerDateTime().toUTC();
|
||||||
|
|
||||||
Wt::WDate nextScanDate;
|
Wt::WDate nextScanDate;
|
||||||
|
switch (_updatePeriod)
|
||||||
switch ( getUpdatePeriod(_db.getSession()) )
|
|
||||||
{
|
{
|
||||||
case UpdatePeriod::Daily:
|
case ScanSettings::UpdatePeriod::Daily:
|
||||||
if (now.time() < startTime)
|
if (now.time() < _startTime)
|
||||||
nextScanDate = now.date();
|
nextScanDate = now.date();
|
||||||
else
|
else
|
||||||
nextScanDate = now.date().addDays(1);
|
nextScanDate = now.date().addDays(1);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case UpdatePeriod::Weekly:
|
case ScanSettings::UpdatePeriod::Weekly:
|
||||||
if (now.time() < startTime && now.date().dayOfWeek() == 1)
|
if (now.time() < _startTime && now.date().dayOfWeek() == 1)
|
||||||
nextScanDate = now.date();
|
nextScanDate = now.date();
|
||||||
else
|
else
|
||||||
nextScanDate = getNextMonday(now.date());
|
nextScanDate = getNextMonday(now.date());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case UpdatePeriod::Monthly:
|
case ScanSettings::UpdatePeriod::Monthly:
|
||||||
if (now.time() < startTime && now.date().day() == 1)
|
if (now.time() < _startTime && now.date().day() == 1)
|
||||||
nextScanDate = now.date();
|
nextScanDate = now.date();
|
||||||
else
|
else
|
||||||
nextScanDate = getNextFirstOfMonth(now.date());
|
nextScanDate = getNextFirstOfMonth(now.date());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case UpdatePeriod::Never:
|
case ScanSettings::UpdatePeriod::Never:
|
||||||
LMS_LOG(DBUPDATER, INFO) << "Auto scan disabled!";
|
LMS_LOG(DBUPDATER, INFO) << "Auto scan disabled!";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextScanDate.isValid())
|
if (nextScanDate.isValid())
|
||||||
scheduleScan( Wt::WDateTime(nextScanDate, startTime).toTimePoint() );
|
scheduleScan( Wt::WDateTime(nextScanDate, _startTime).toTimePoint() );
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -399,19 +317,12 @@ MediaScanner::scan(boost::system::error_code err)
|
|||||||
Stats stats;
|
Stats stats;
|
||||||
|
|
||||||
checkAudioFiles(stats);
|
checkAudioFiles(stats);
|
||||||
forceScan = checkClusters();
|
|
||||||
|
|
||||||
LMS_LOG(UI, INFO) << "Checks complete, force scan = " << forceScan;
|
LMS_LOG(UI, INFO) << "Checks complete, force scan = " << forceScan;
|
||||||
|
|
||||||
for (auto rootDirectory : _rootDirectories)
|
LMS_LOG(DBUPDATER, INFO) << "scaning media directory '" << _mediaDirectory.string() << "'...";
|
||||||
{
|
scanMediaDirectory(_mediaDirectory, forceScan, stats);
|
||||||
if (!_running)
|
LMS_LOG(DBUPDATER, INFO) << "scaning media directory '" << _mediaDirectory.string() << "' DONE";
|
||||||
break;
|
|
||||||
|
|
||||||
LMS_LOG(DBUPDATER, INFO) << "scaning root directory '" << rootDirectory.string() << "'...";
|
|
||||||
scanRootDirectory(rootDirectory, forceScan, stats);
|
|
||||||
LMS_LOG(DBUPDATER, INFO) << "scaning root directory '" << rootDirectory.string() << "' DONE";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_running)
|
if (_running)
|
||||||
checkDuplicatedAudioFiles(stats);
|
checkDuplicatedAudioFiles(stats);
|
||||||
@@ -432,15 +343,22 @@ MediaScanner::refreshScanSettings()
|
|||||||
{
|
{
|
||||||
Wt::Dbo::Transaction transaction(_db.getSession());
|
Wt::Dbo::Transaction transaction(_db.getSession());
|
||||||
|
|
||||||
_fileExtensions.clear();
|
auto scanSettings = ScanSettings::get(_db.getSession());
|
||||||
for (auto extension : splitString(Setting::getString(_db.getSession(), fileExtensionsSetting), " "))
|
|
||||||
_fileExtensions.push_back( extension );
|
|
||||||
|
|
||||||
_rootDirectories.clear();
|
_startTime = scanSettings->getUpdateStartTime();
|
||||||
for (auto rootDir : Database::MediaDirectory::getAll(_db.getSession()))
|
_updatePeriod = scanSettings->getUpdatePeriod();
|
||||||
_rootDirectories.push_back(rootDir->getPath());
|
|
||||||
|
|
||||||
_metadataParser.updateClusterTypes(getClusterTypes(_db.getSession()));
|
_fileExtensions = scanSettings->getAudioFileExtensions();
|
||||||
|
_mediaDirectory = scanSettings->getMediaDirectory();
|
||||||
|
|
||||||
|
auto clusterTypes = scanSettings->getClusterTypes();
|
||||||
|
std::set<std::string> clusterTypeNames;
|
||||||
|
|
||||||
|
std::transform(clusterTypes.begin(), clusterTypes.end(),
|
||||||
|
std::inserter(clusterTypeNames, clusterTypeNames.begin()),
|
||||||
|
[](ClusterType::pointer clusterType) -> std::string { return clusterType->getName(); });
|
||||||
|
|
||||||
|
_metadataParser.setClusterTypeNames(clusterTypeNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -653,11 +571,11 @@ MediaScanner::scanAudioFile(const boost::filesystem::path& file, bool forceScan,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MediaScanner::scanRootDirectory(boost::filesystem::path rootDirectory, bool forceScan, Stats& stats)
|
MediaScanner::scanMediaDirectory(boost::filesystem::path mediaDirectory, bool forceScan, Stats& stats)
|
||||||
{
|
{
|
||||||
boost::system::error_code ec;
|
boost::system::error_code ec;
|
||||||
|
|
||||||
boost::filesystem::recursive_directory_iterator itPath(rootDirectory, ec);
|
boost::filesystem::recursive_directory_iterator itPath(mediaDirectory, ec);
|
||||||
|
|
||||||
boost::filesystem::recursive_directory_iterator itEnd;
|
boost::filesystem::recursive_directory_iterator itEnd;
|
||||||
while (!ec && itPath != itEnd)
|
while (!ec && itPath != itEnd)
|
||||||
@@ -676,16 +594,16 @@ MediaScanner::scanRootDirectory(boost::filesystem::path rootDirectory, bool forc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if a file exists and is still in a root directory
|
// Check if a file exists and is still in a media directory
|
||||||
static bool
|
static bool
|
||||||
checkFile(const boost::filesystem::path& p, const std::vector<boost::filesystem::path>& rootDirs, const std::vector<boost::filesystem::path>& extensions)
|
checkFile(const boost::filesystem::path& p, boost::filesystem::path mediaDirectory, const std::set<boost::filesystem::path>& extensions)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
bool status = true;
|
bool status = true;
|
||||||
|
|
||||||
// For each track, make sure the the file still exists
|
// For each track, make sure the the file still exists
|
||||||
// and still belongs to a root directory
|
// and still belongs to a media directory
|
||||||
if (!boost::filesystem::exists( p )
|
if (!boost::filesystem::exists( p )
|
||||||
|| !boost::filesystem::is_regular( p ) )
|
|| !boost::filesystem::is_regular( p ) )
|
||||||
{
|
{
|
||||||
@@ -694,20 +612,9 @@ checkFile(const boost::filesystem::path& p, const std::vector<boost::filesystem:
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
if (!isPathInParentPath(p, mediaDirectory))
|
||||||
bool foundRoot = false;
|
|
||||||
for (auto& rootDir : rootDirs)
|
|
||||||
{
|
{
|
||||||
if (isPathInParentPath(p, rootDir))
|
LMS_LOG(DBUPDATER, INFO) << "File '" << p.string() << "' is out of media directory '";
|
||||||
{
|
|
||||||
foundRoot = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!foundRoot)
|
|
||||||
{
|
|
||||||
LMS_LOG(DBUPDATER, INFO) << "Out of root file '" << p.string() << "'";
|
|
||||||
status = false;
|
status = false;
|
||||||
}
|
}
|
||||||
else if (!isFileSupported(p, extensions))
|
else if (!isFileSupported(p, extensions))
|
||||||
@@ -741,7 +648,7 @@ MediaScanner::checkAudioFiles( Stats& stats )
|
|||||||
if (!_running)
|
if (!_running)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!checkFile(trackPath, _rootDirectories, _fileExtensions))
|
if (!checkFile(trackPath, _mediaDirectory, _fileExtensions))
|
||||||
{
|
{
|
||||||
Wt::Dbo::Transaction transaction(_db.getSession());
|
Wt::Dbo::Transaction transaction(_db.getSession());
|
||||||
|
|
||||||
@@ -798,44 +705,6 @@ MediaScanner::checkAudioFiles( Stats& stats )
|
|||||||
LMS_LOG(DBUPDATER, INFO) << "Check audio files done!";
|
LMS_LOG(DBUPDATER, INFO) << "Check audio files done!";
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
|
||||||
MediaScanner::checkClusters()
|
|
||||||
{
|
|
||||||
bool hasChanges = false;
|
|
||||||
|
|
||||||
LMS_LOG(DBUPDATER, INFO) << "Checking clusters";
|
|
||||||
|
|
||||||
Wt::Dbo::Transaction transaction(_db.getSession());
|
|
||||||
auto clusterTypes = ClusterType::getAll(_db.getSession());
|
|
||||||
|
|
||||||
// Remove no longer desired clusters
|
|
||||||
for (auto clusterType : clusterTypes)
|
|
||||||
{
|
|
||||||
if (!_metadataParser.isClusterTypeSupported(clusterType->getName()))
|
|
||||||
{
|
|
||||||
LMS_LOG(DBUPDATER, INFO) << "Removing cluster type " << clusterType->getName();
|
|
||||||
clusterType.remove();
|
|
||||||
hasChanges = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add any missing clusters
|
|
||||||
for (auto clusterTypeName : _metadataParser.getClusterTypes())
|
|
||||||
{
|
|
||||||
if (std::none_of(clusterTypes.begin(), clusterTypes.end(),
|
|
||||||
[&clusterTypeName](ClusterType::pointer clusterType) { return (clusterType->getName() == clusterTypeName); }))
|
|
||||||
{
|
|
||||||
LMS_LOG(DBUPDATER, INFO) << "Creating cluster type " << clusterTypeName;
|
|
||||||
ClusterType::create(_db.getSession(), clusterTypeName);
|
|
||||||
hasChanges = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LMS_LOG(DBUPDATER, INFO) << "Checking clusters done!";
|
|
||||||
|
|
||||||
return hasChanges;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
MediaScanner::checkDuplicatedAudioFiles(Stats& stats)
|
MediaScanner::checkDuplicatedAudioFiles(Stats& stats)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -28,26 +28,11 @@
|
|||||||
|
|
||||||
#include "metadata/TagLibParser.hpp"
|
#include "metadata/TagLibParser.hpp"
|
||||||
|
|
||||||
|
#include "database/ScanSettings.hpp"
|
||||||
#include "database/DatabaseHandler.hpp"
|
#include "database/DatabaseHandler.hpp"
|
||||||
|
|
||||||
namespace Scanner {
|
namespace Scanner {
|
||||||
|
|
||||||
enum class UpdatePeriod {
|
|
||||||
Never = 0,
|
|
||||||
Daily,
|
|
||||||
Weekly,
|
|
||||||
Monthly
|
|
||||||
};
|
|
||||||
|
|
||||||
UpdatePeriod getUpdatePeriod(Wt::Dbo::Session& session);
|
|
||||||
void setUpdatePeriod(Wt::Dbo::Session& session, UpdatePeriod updatePeriod);
|
|
||||||
|
|
||||||
Wt::WTime getUpdateStartTime(Wt::Dbo::Session& session);
|
|
||||||
void setUpdateStartTime(Wt::Dbo::Session& session, Wt::WTime time);
|
|
||||||
|
|
||||||
std::set<std::string> getClusterTypes(Wt::Dbo::Session& session);
|
|
||||||
void setClusterTypes(Wt::Dbo::Session& session, const std::set<std::string> clusterTypes);
|
|
||||||
|
|
||||||
class MediaScanner
|
class MediaScanner
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -93,13 +78,12 @@ class MediaScanner
|
|||||||
// Update database (scheduled callback)
|
// Update database (scheduled callback)
|
||||||
void scan(boost::system::error_code ec);
|
void scan(boost::system::error_code ec);
|
||||||
|
|
||||||
void scanRootDirectory( boost::filesystem::path rootDirectory, bool forceScan, Stats& stats);
|
void scanMediaDirectory( boost::filesystem::path mediaDirectory, bool forceScan, Stats& stats);
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
void refreshScanSettings();
|
void refreshScanSettings();
|
||||||
|
|
||||||
void checkAudioFiles( Stats& stats );
|
void checkAudioFiles( Stats& stats );
|
||||||
bool checkClusters();
|
|
||||||
void checkDuplicatedAudioFiles( Stats& stats );
|
void checkDuplicatedAudioFiles( Stats& stats );
|
||||||
void scanAudioFile( const boost::filesystem::path& file, bool forceScan, Stats& stats);
|
void scanAudioFile( const boost::filesystem::path& file, bool forceScan, Stats& stats);
|
||||||
|
|
||||||
@@ -112,9 +96,11 @@ class MediaScanner
|
|||||||
|
|
||||||
Database::Handler _db;
|
Database::Handler _db;
|
||||||
|
|
||||||
// Scan settings
|
// Current scan settings
|
||||||
std::vector<boost::filesystem::path> _fileExtensions;
|
Wt::WTime _startTime;
|
||||||
std::vector<boost::filesystem::path> _rootDirectories;
|
Database::ScanSettings::UpdatePeriod _updatePeriod;
|
||||||
|
std::set<boost::filesystem::path> _fileExtensions;
|
||||||
|
boost::filesystem::path _mediaDirectory;
|
||||||
|
|
||||||
MetaData::TagLibParser _metadataParser;
|
MetaData::TagLibParser _metadataParser;
|
||||||
|
|
||||||
|
|||||||
@@ -29,8 +29,6 @@
|
|||||||
#include <Wt/WStringListModel.h>
|
#include <Wt/WStringListModel.h>
|
||||||
|
|
||||||
#include "common/Validators.hpp"
|
#include "common/Validators.hpp"
|
||||||
#include "database/MediaDirectory.hpp"
|
|
||||||
#include "scanner/MediaScanner.hpp"
|
|
||||||
#include "utils/Logger.hpp"
|
#include "utils/Logger.hpp"
|
||||||
#include "utils/Utils.hpp"
|
#include "utils/Utils.hpp"
|
||||||
|
|
||||||
@@ -80,40 +78,45 @@ class DatabaseSettingsModel : public Wt::WFormModel
|
|||||||
|
|
||||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||||
|
|
||||||
std::vector<MediaDirectory::pointer> mediaDirectories = MediaDirectory::getAll(LmsApp->getDboSession());
|
auto scanSettings = ScanSettings::get(LmsApp->getDboSession());
|
||||||
if (!mediaDirectories.empty())
|
|
||||||
setValue(MediaDirectoryField, mediaDirectories.front()->getPath().string());
|
|
||||||
|
|
||||||
auto periodRow = getUpdatePeriodModelRow( Scanner::getUpdatePeriod(LmsApp->getDboSession()) );
|
setValue(MediaDirectoryField, scanSettings->getMediaDirectory());
|
||||||
|
|
||||||
|
auto periodRow = getUpdatePeriodModelRow( scanSettings->getUpdatePeriod() );
|
||||||
if (periodRow)
|
if (periodRow)
|
||||||
setValue(UpdatePeriodField, updatePeriodString(*periodRow));
|
setValue(UpdatePeriodField, updatePeriodString(*periodRow));
|
||||||
|
|
||||||
auto startTimeRow = getUpdateStartTimeModelRow( Scanner::getUpdateStartTime(LmsApp->getDboSession()) );
|
auto startTimeRow = getUpdateStartTimeModelRow( scanSettings->getUpdateStartTime() );
|
||||||
if (startTimeRow)
|
if (startTimeRow)
|
||||||
setValue(UpdateStartTimeField, updateStartTimeString(*startTimeRow) );
|
setValue(UpdateStartTimeField, updateStartTimeString(*startTimeRow) );
|
||||||
|
|
||||||
auto clusterTypes = Scanner::getClusterTypes(LmsApp->getDboSession());
|
auto clusterTypes = scanSettings->getClusterTypes();
|
||||||
if (!clusterTypes.empty())
|
if (!clusterTypes.empty())
|
||||||
setValue(TagsField, joinStrings(std::vector<std::string>(clusterTypes.begin(), clusterTypes.end()), " "));
|
{
|
||||||
|
std::vector<std::string> names;
|
||||||
|
std::transform(clusterTypes.begin(), clusterTypes.end(),std::back_inserter(names), [](auto clusterType) { return clusterType->getName(); });
|
||||||
|
setValue(TagsField, joinStrings(names, " "));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void saveData()
|
void saveData()
|
||||||
{
|
{
|
||||||
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
|
||||||
|
|
||||||
MediaDirectory::eraseAll(LmsApp->getDboSession());
|
auto scanSettings = ScanSettings::get(LmsApp->getDboSession());
|
||||||
MediaDirectory::create(LmsApp->getDboSession(), valueText(MediaDirectoryField).toUTF8());
|
|
||||||
|
scanSettings.modify()->setMediaDirectory(valueText(MediaDirectoryField).toUTF8());
|
||||||
|
|
||||||
auto updatePeriodRow = getUpdatePeriodModelRow( valueText(UpdatePeriodField));
|
auto updatePeriodRow = getUpdatePeriodModelRow( valueText(UpdatePeriodField));
|
||||||
assert(updatePeriodRow);
|
assert(updatePeriodRow);
|
||||||
Scanner::setUpdatePeriod(LmsApp->getDboSession(), updatePeriod(*updatePeriodRow));
|
scanSettings.modify()->setUpdatePeriod(updatePeriod(*updatePeriodRow));
|
||||||
|
|
||||||
auto startTimeRow = getUpdateStartTimeModelRow( valueText(UpdateStartTimeField));
|
auto startTimeRow = getUpdateStartTimeModelRow( valueText(UpdateStartTimeField));
|
||||||
assert(startTimeRow);
|
assert(startTimeRow);
|
||||||
Scanner::setUpdateStartTime(LmsApp->getDboSession(), updateStartTime(*startTimeRow));
|
scanSettings.modify()->setUpdateStartTime(updateStartTime(*startTimeRow));
|
||||||
|
|
||||||
auto clusterTypes = splitString(valueText(TagsField).toUTF8(), " ");
|
auto clusterTypes = splitString(valueText(TagsField).toUTF8(), " ");
|
||||||
Scanner::setClusterTypes(LmsApp->getDboSession(), std::set<std::string>(clusterTypes.begin(), clusterTypes.end()));
|
scanSettings.modify()->setClusterTypes(std::set<std::string>(clusterTypes.begin(), clusterTypes.end()));
|
||||||
}
|
}
|
||||||
|
|
||||||
boost::optional<int> getUpdatePeriodModelRow(Wt::WString value)
|
boost::optional<int> getUpdatePeriodModelRow(Wt::WString value)
|
||||||
@@ -127,7 +130,7 @@ class DatabaseSettingsModel : public Wt::WFormModel
|
|||||||
return boost::none;
|
return boost::none;
|
||||||
}
|
}
|
||||||
|
|
||||||
boost::optional<int> getUpdatePeriodModelRow(Scanner::UpdatePeriod period)
|
boost::optional<int> getUpdatePeriodModelRow(ScanSettings::UpdatePeriod period)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < _updatePeriodModel->rowCount(); ++i)
|
for (int i = 0; i < _updatePeriodModel->rowCount(); ++i)
|
||||||
{
|
{
|
||||||
@@ -138,9 +141,9 @@ class DatabaseSettingsModel : public Wt::WFormModel
|
|||||||
return boost::none;
|
return boost::none;
|
||||||
}
|
}
|
||||||
|
|
||||||
Scanner::UpdatePeriod updatePeriod(int row)
|
ScanSettings::UpdatePeriod updatePeriod(int row)
|
||||||
{
|
{
|
||||||
return Wt::cpp17::any_cast<Scanner::UpdatePeriod>
|
return Wt::cpp17::any_cast<ScanSettings::UpdatePeriod>
|
||||||
(_updatePeriodModel->data(_updatePeriodModel->index(row, 0), Wt::ItemDataRole::User));
|
(_updatePeriodModel->data(_updatePeriodModel->index(row, 0), Wt::ItemDataRole::User));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,16 +203,16 @@ class DatabaseSettingsModel : public Wt::WFormModel
|
|||||||
_updatePeriodModel = std::make_shared<Wt::WStringListModel>();
|
_updatePeriodModel = std::make_shared<Wt::WStringListModel>();
|
||||||
|
|
||||||
_updatePeriodModel->addString(Wt::WString::tr("Lms.Admin.Database.never"));
|
_updatePeriodModel->addString(Wt::WString::tr("Lms.Admin.Database.never"));
|
||||||
_updatePeriodModel->setData(0, 0, Scanner::UpdatePeriod::Never, Wt::ItemDataRole::User);
|
_updatePeriodModel->setData(0, 0, ScanSettings::UpdatePeriod::Never, Wt::ItemDataRole::User);
|
||||||
|
|
||||||
_updatePeriodModel->addString(Wt::WString::tr("Lms.Admin.Database.daily"));
|
_updatePeriodModel->addString(Wt::WString::tr("Lms.Admin.Database.daily"));
|
||||||
_updatePeriodModel->setData(1, 0, Scanner::UpdatePeriod::Daily, Wt::ItemDataRole::User);
|
_updatePeriodModel->setData(1, 0, ScanSettings::UpdatePeriod::Daily, Wt::ItemDataRole::User);
|
||||||
|
|
||||||
_updatePeriodModel->addString(Wt::WString::tr("Lms.Admin.Database.weekly"));
|
_updatePeriodModel->addString(Wt::WString::tr("Lms.Admin.Database.weekly"));
|
||||||
_updatePeriodModel->setData(2, 0, Scanner::UpdatePeriod::Weekly, Wt::ItemDataRole::User);
|
_updatePeriodModel->setData(2, 0, ScanSettings::UpdatePeriod::Weekly, Wt::ItemDataRole::User);
|
||||||
|
|
||||||
_updatePeriodModel->addString(Wt::WString::tr("Lms.Admin.Database.monthly"));
|
_updatePeriodModel->addString(Wt::WString::tr("Lms.Admin.Database.monthly"));
|
||||||
_updatePeriodModel->setData(3, 0, Scanner::UpdatePeriod::Monthly, Wt::ItemDataRole::User);
|
_updatePeriodModel->setData(3, 0, ScanSettings::UpdatePeriod::Monthly, Wt::ItemDataRole::User);
|
||||||
|
|
||||||
_updateStartTimeModel = std::make_shared<Wt::WStringListModel>();
|
_updateStartTimeModel = std::make_shared<Wt::WStringListModel>();
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ lms_metadata_SOURCES = \
|
|||||||
$(srcdir)/LmsMetadata.cpp \
|
$(srcdir)/LmsMetadata.cpp \
|
||||||
$(top_srcdir)/src/utils/Logger.cpp \
|
$(top_srcdir)/src/utils/Logger.cpp \
|
||||||
$(top_srcdir)/src/utils/Utils.cpp \
|
$(top_srcdir)/src/utils/Utils.cpp \
|
||||||
$(top_srcdir)/src/metadata/TagLibParser.cpp \
|
$(top_srcdir)/src/metadata/TagLibParser.cpp
|
||||||
$(top_srcdir)/src/metadata/MetaData.cpp
|
|
||||||
|
|
||||||
lms_metadata_CXXFLAGS=-std=c++14 -Wall -I$(top_srcdir)/src -D_REENTRANT
|
lms_metadata_CXXFLAGS=-std=c++14 -Wall -I$(top_srcdir)/src -D_REENTRANT
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user