Includes, comments and bugfixes

- Fix includes
- Use common regex (Song::kCoverRemoveDisc) for removing Disc/CD from album
- Remove Disc/CD from album when creating hash
- Make imobiledevice support compile
- Fix setting device on windows
This commit is contained in:
Jonas Kvinge
2018-05-01 00:41:33 +02:00
parent fccbd6790c
commit e337b7933b
518 changed files with 7003 additions and 4693 deletions

View File

@@ -20,19 +20,23 @@
#include "config.h"
#include <stdbool.h>
#include <QObject>
#include <QThread>
#include <QList>
#include "collection.h"
#include "collectionmodel.h"
#include "collectionbackend.h"
#include "core/application.h"
#include "core/database.h"
#include "core/player.h"
#include "core/tagreaderclient.h"
#include "core/taskmanager.h"
#include "core/thread.h"
#include "core/logging.h"
#include "core/utilities.h"
#include "collection.h"
#include "collectionwatcher.h"
#include "collectionbackend.h"
#include "collectionmodel.h"
#include "playlist/playlistmanager.h"
const char *Collection::kSongsTable = "songs";
const char *Collection::kDirsTable = "directories";
@@ -47,8 +51,6 @@ Collection::Collection(Application *app, QObject *parent)
watcher_(nullptr),
watcher_thread_(nullptr) {
//qLog(Debug) << __PRETTY_FUNCTION__;
backend_ = new CollectionBackend;
backend()->moveToThread(app->database()->thread());
@@ -62,8 +64,6 @@ Collection::Collection(Application *app, QObject *parent)
}
Collection::~Collection() {
//qLog(Debug) << __PRETTY_FUNCTION__;
watcher_->deleteLater();
watcher_thread_->exit();
@@ -71,8 +71,6 @@ Collection::~Collection() {
}
void Collection::Init() {
//qLog(Debug) << __PRETTY_FUNCTION__;
watcher_ = new CollectionWatcher;
watcher_thread_ = new Thread(this);
@@ -109,8 +107,6 @@ void Collection::PauseWatcher() { watcher_->SetRescanPausedAsync(true); }
void Collection::ResumeWatcher() { watcher_->SetRescanPausedAsync(false); }
void Collection::ReloadSettings() {
//qLog(Debug) << __PRETTY_FUNCTION__;
watcher_->ReloadSettingsAsync();
@@ -118,14 +114,10 @@ void Collection::ReloadSettings() {
void Collection::Stopped() {
//qLog(Debug) << __PRETTY_FUNCTION__;
CurrentSongChanged(Song());
}
void Collection::CurrentSongChanged(const Song &song) {
//qLog(Debug) << __PRETTY_FUNCTION__;
TagReaderReply *reply = nullptr;
@@ -140,8 +132,6 @@ void Collection::CurrentSongChanged(const Song &song) {
SongList Collection::FilterCurrentWMASong(SongList songs, Song* queued) {
//qLog(Debug) << __PRETTY_FUNCTION__;
for (SongList::iterator it = songs.begin(); it != songs.end(); ) {
if (it->url() == current_wma_song_url_) {
*queued = *it;

View File

@@ -23,19 +23,18 @@
#include "config.h"
#include <QHash>
#include <QObject>
#include <QHash>
#include <QString>
#include <QUrl>
#include "core/song.h"
class Application;
class Database;
class Thread;
class CollectionBackend;
class CollectionModel;
class CollectionWatcher;
class TaskManager;
class Thread;
class Collection : public QObject {
Q_OBJECT
@@ -85,13 +84,11 @@ class Collection : public QObject {
CollectionWatcher *watcher_;
Thread *watcher_thread_;
// Hack: Gstreamer doesn't cope well with WMA files being rewritten while
// being played, so we delay statistics and rating changes until the current
// song has finished playing.
// Hack: Gstreamer doesn't cope well with WMA files being rewritten while being played,
// so we delay statistics and rating changes until the current song has finished playing.
QUrl current_wma_song_url_;
// DB schema versions which should trigger a full collection rescan (each of
// those with a short reason why).
// DB schema versions which should trigger a full collection rescan (each of those with a short reason why).
QHash<int, QString> full_rescan_revisions_;
};

View File

@@ -20,22 +20,32 @@
#include "config.h"
#include <QCoreApplication>
#include <QDateTime>
#include <QDir>
#include <QtGlobal>
#include <QObject>
#include <QMutex>
#include <QSet>
#include <QMap>
#include <QByteArray>
#include <QFileInfo>
#include <QSettings>
#include <QDateTime>
#include <QVariant>
#include <QString>
#include <QStringList>
#include <QUrl>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QtDebug>
#include "core/application.h"
#include "core/database.h"
#include "core/logging.h"
#include "core/scopedtransaction.h"
#include "core/utilities.h"
#include "directory.h"
#include "collectionbackend.h"
#include "collectionquery.h"
#include "sqlrow.h"
#include "core/application.h"
#include "core/database.h"
#include "core/scopedtransaction.h"
#include "core/tagreaderclient.h"
#include "core/utilities.h"
const char *CollectionBackend::kSettingsGroup = "Collection";
@@ -258,6 +268,7 @@ void CollectionBackend::AddDirectory(const QString &path) {
dir.id = q.lastInsertId().toInt();
emit DirectoryDiscovered(dir, SubdirectoryList());
}
void CollectionBackend::RemoveDirectory(const Directory &dir) {
@@ -287,6 +298,7 @@ void CollectionBackend::RemoveDirectory(const Directory &dir) {
emit DirectoryDeleted(dir);
transaction.Commit();
}
SongList CollectionBackend::FindSongsInDirectory(int id) {
@@ -307,6 +319,7 @@ SongList CollectionBackend::FindSongsInDirectory(int id) {
ret << song;
}
return ret;
}
void CollectionBackend::AddOrUpdateSubdirs(const SubdirectoryList &subdirs) {
@@ -381,8 +394,7 @@ void CollectionBackend::AddOrUpdateSongs(const SongList &songs) {
for (const Song &song : songs) {
// Do a sanity check first - make sure the song's directory still exists
// This is to fix a possible race condition when a directory is removed
// while CollectionWatcher is scanning it.
// This is to fix a possible race condition when a directory is removed while CollectionWatcher is scanning it.
if (!dirs_table_.isEmpty()) {
check_dir.bindValue(":id", song.directory_id());
check_dir.exec();
@@ -752,8 +764,7 @@ void CollectionBackend::UpdateCompilations() {
QMutexLocker l(db_->Mutex());
QSqlDatabase db(db_->Connect());
// Look for albums that have songs by more than one 'effective album artist' in the same
// directory
// Look for albums that have songs by more than one 'effective album artist' in the same directory
QSqlQuery q(db);
q.prepare(QString("SELECT effective_albumartist, album, filename, compilation_detected FROM %1 WHERE unavailable = 0 ORDER BY album").arg(songs_table_));
@@ -819,8 +830,7 @@ void CollectionBackend::UpdateCompilations() {
void CollectionBackend::UpdateCompilations(QSqlQuery &find_songs, QSqlQuery &update, SongList &deleted_songs, SongList &added_songs, const QString &album, int compilation_detected) {
// Get songs that were already in that album, so we can tell the model
// they've been updated
// Get songs that were already in that album, so we can tell the model they've been updated
find_songs.bindValue(":album", album);
find_songs.bindValue(":compilation_detected", int(!compilation_detected));
find_songs.exec();
@@ -1104,6 +1114,7 @@ void CollectionBackend::ResetStatistics(int id) {
}
void CollectionBackend::DeleteAll() {
{
QMutexLocker l(db_->Mutex());
QSqlDatabase db(db_->Connect());

View File

@@ -23,13 +23,22 @@
#include "config.h"
#include <QObject>
#include <QSet>
#include <QUrl>
#include <stdbool.h>
#include <QtGlobal>
#include <QObject>
#include <QList>
#include <QVector>
#include <QSet>
#include <QString>
#include <QStringList>
#include <QUrl>
#include <QSqlDatabase>
#include <QSqlQuery>
#include "directory.h"
#include "collectionquery.h"
#include "core/song.h"
#include "collectionquery.h"
#include "directory.h"
class Database;
@@ -94,11 +103,9 @@ class CollectionBackendInterface : public QObject {
virtual Song GetSongById(int id) = 0;
// Returns all sections of a song with the given filename. If there's just one section
// the resulting list will have it's size equal to 1.
// Returns all sections of a song with the given filename. If there's just one section the resulting list will have it's size equal to 1.
virtual SongList GetSongsByUrl(const QUrl &url) = 0;
// Returns a section of a song with the given filename and beginning. If the section
// is not present in collection, returns invalid song.
// Returns a section of a song with the given filename and beginning. If the section is not present in collection, returns invalid song.
// Using default beginning value is suitable when searching for single-section songs.
virtual Song GetSongByUrl(const QUrl &url, qint64 beginning = 0) = 0;

View File

@@ -20,15 +20,23 @@
#include "config.h"
#include "collectiondirectorymodel.h"
#include "collectionbackend.h"
#include <QObject>
#include <QStandardItemModel>
#include <QAbstractItemModel>
#include <QVariant>
#include <QString>
#include <QUrl>
#include "core/application.h"
#include "core/filesystemmusicstorage.h"
#include "core/iconloader.h"
#include "core/musicstorage.h"
#include "core/utilities.h"
#include "core/iconloader.h"
#include "directory.h"
#include "collectionbackend.h"
#include "collectiondirectorymodel.h"
CollectionDirectoryModel::CollectionDirectoryModel(CollectionBackend* backend, QObject* parent)
CollectionDirectoryModel::CollectionDirectoryModel(CollectionBackend *backend, QObject *parent)
: QStandardItemModel(parent),
dir_icon_(IconLoader::Load("document-open-folder")),
backend_(backend)
@@ -43,7 +51,7 @@ CollectionDirectoryModel::~CollectionDirectoryModel() {}
void CollectionDirectoryModel::DirectoryDiscovered(const Directory &dir) {
QStandardItem* item;
QStandardItem *item;
if (Application::kIsPortable && Utilities::UrlOnSameDriveAsStrawberry(QUrl::fromLocalFile(dir.path))) {
item = new QStandardItem(Utilities::GetRelativePathToStrawberryBin(QUrl::fromLocalFile(dir.path)).toLocalFile());
}

View File

@@ -25,11 +25,16 @@
#include <memory>
#include <QIcon>
#include <QObject>
#include <QStandardItemModel>
#include <QList>
#include <QVariant>
#include <QString>
#include <QIcon>
#include "directory.h"
class QModelIndex;
struct Directory;
class CollectionBackend;
class MusicStorage;

View File

@@ -20,23 +20,35 @@
#include "config.h"
#include <QApplication>
#include <QWidget>
#include <QObject>
#include <QDataStream>
#include <QIODevice>
#include <QAction>
#include <QActionGroup>
#include <QInputDialog>
#include <QKeyEvent>
#include <QMenu>
#include <QByteArray>
#include <QVariant>
#include <QString>
#include <QStringList>
#include <QRegExp>
#include <QInputDialog>
#include <QList>
#include <QTimer>
#include <QMenu>
#include <QSettings>
#include <QSignalMapper>
#include <QTimer>
#include <QToolButton>
#include <QtEvents>
#include "collectionfilterwidget.h"
#include "core/iconloader.h"
#include "core/song.h"
#include "collectionmodel.h"
#include "collectionquery.h"
#include "savedgroupingmanager.h"
#include "collectionfilterwidget.h"
#include "groupbydialog.h"
#include "ui_collectionfilterwidget.h"
#include "core/song.h"
#include "core/iconloader.h"
#include "settings/settingsdialog.h"
CollectionFilterWidget::CollectionFilterWidget(QWidget *parent)
: QWidget(parent),
@@ -48,8 +60,7 @@ CollectionFilterWidget::CollectionFilterWidget(QWidget *parent)
delay_behaviour_(DelayedOnLargeLibraries) {
ui_->setupUi(this);
// Add the available fields to the tooltip here instead of the ui
// file to prevent that they get translated by mistake.
// Add the available fields to the tooltip here instead of the ui file to prevent that they get translated by mistake.
QString available_fields = Song::kFtsColumns.join(", ").replace(QRegExp("\\bfts"), "");
ui_->filter->setToolTip(ui_->filter->toolTip().arg(available_fields));
@@ -125,8 +136,7 @@ void CollectionFilterWidget::UpdateGroupByActions() {
group_by_group_ = CreateGroupByActions(this);
group_by_menu_->clear();
group_by_menu_->addActions(group_by_group_->actions());
connect(group_by_group_, SIGNAL(triggered(QAction*)),
SLOT(GroupByClicked(QAction*)));
connect(group_by_group_, SIGNAL(triggered(QAction*)), SLOT(GroupByClicked(QAction*)));
if (model_) {
CheckCurrentGrouping(model_->GetGroupBy());
}
@@ -338,10 +348,9 @@ void CollectionFilterWidget::keyReleaseEvent(QKeyEvent *e) {
void CollectionFilterWidget::FilterTextChanged(const QString &text) {
// Searching with one or two characters can be very expensive on the database
// even with FTS, so if there are a large number of songs in the database
// introduce a small delay before actually filtering the model, so if the
// user is typing the first few characters of something it will be quicker.
// Searching with one or two characters can be very expensive on the database even with FTS,
// so if there are a large number of songs in the database introduce a small delay before actually filtering the model,
// so if the user is typing the first few characters of something it will be quicker.
const bool delay = (delay_behaviour_ == AlwaysDelayed) || (delay_behaviour_ == DelayedOnLargeLibraries && !text.isEmpty() && text.length() < 3 && model_->total_song_count() >= 100000);
if (delay) {

View File

@@ -24,22 +24,29 @@
#include "config.h"
#include <memory>
#include <stdbool.h>
#include <QWidget>
#include <QObject>
#include <QString>
#include <QMenu>
#include <QSignalMapper>
#include <QTimer>
#include <QAction>
#include <QActionGroup>
#include <QtEvents>
#include "collectionquery.h"
#include "collectionmodel.h"
#include "savedgroupingmanager.h"
class QKeyEvent;
class GroupByDialog;
class SavedGroupingManager;
class SettingsDialog;
class Ui_CollectionFilterWidget;
struct QueryOptions;
class QMenu;
class QActionGroup;
class QSignalMapper;
class CollectionFilterWidget : public QWidget {
Q_OBJECT

View File

@@ -23,9 +23,6 @@
#include "config.h"
#include <QString>
#include <QList>
#include "core/simpletreeitem.h"
#include "core/song.h"

View File

@@ -22,32 +22,44 @@
#include <functional>
#include <QObject>
#include <QtGlobal>
#include <QtConcurrentRun>
#include <QtAlgorithms>
#include <QMutex>
#include <QFuture>
#include <QDataStream>
#include <QMimeData>
#include <QIODevice>
#include <QMetaEnum>
#include <QNetworkCacheMetaData>
#include <QNetworkDiskCache>
#include <QPixmapCache>
#include <QSettings>
#include <QByteArray>
#include <QVariant>
#include <QList>
#include <QSet>
#include <QChar>
#include <QRegExp>
#include <QString>
#include <QStringList>
#include <QUrl>
#include <QtConcurrentRun>
#include <QImage>
#include <QPixmapCache>
#include <QSettings>
#include <QtDebug>
#include "collectionmodel.h"
#include "collectionbackend.h"
#include "collectionitem.h"
#include "collectiondirectorymodel.h"
#include "collectionview.h"
#include "sqlrow.h"
#include "core/application.h"
#include "core/closure.h"
#include "core/database.h"
#include "core/iconloader.h"
#include "core/logging.h"
#include "core/taskmanager.h"
#include "core/utilities.h"
#include "core/iconloader.h"
#include "covermanager/albumcoverloader.h"
#include "collectionquery.h"
#include "collectionbackend.h"
#include "collectiondirectorymodel.h"
#include "collectionitem.h"
#include "collectionmodel.h"
#include "sqlrow.h"
#include "playlist/playlistmanager.h"
#include "playlist/songmimedata.h"
#include "covermanager/albumcoverloader.h"
using std::placeholders::_1;
using std::placeholders::_2;
@@ -178,11 +190,9 @@ void CollectionModel::SongsDiscovered(const SongList &songs) {
// Hey, we've already got that one!
if (song_nodes_.contains(song.id())) continue;
// Before we can add each song we need to make sure the required container
// items already exist in the tree. These depend on which "group by"
// settings the user has on the collection. Eg. if the user grouped by
// artist and album, we would need to make sure nodes for the song's artist
// and album were already in the tree.
// Before we can add each song we need to make sure the required container items already exist in the tree.
// These depend on which "group by" settings the user has on the collection.
// Eg. if the user grouped by artist and album, we would need to make sure nodes for the song's artist and album were already in the tree.
// Find parent containers in the tree
CollectionItem *container = root_;
@@ -190,8 +200,7 @@ void CollectionModel::SongsDiscovered(const SongList &songs) {
GroupBy type = group_by_[i];
if (type == GroupBy_None) break;
// Special case: if the song is a compilation and the current GroupBy
// level is Artists, then we want the Various Artists node :(
// Special case: if the song is a compilation and the current GroupBy level is Artists, then we want the Various Artists node :(
if (IsArtistGroupBy(type) && song.is_compilation()) {
if (container->compilation_artist_node_ == nullptr)
CreateCompilationArtistNode(true, container);
@@ -240,15 +249,13 @@ void CollectionModel::SongsDiscovered(const SongList &songs) {
container = container_nodes_[i][key];
}
// If we just created the damn thing then we don't need to continue into
// it any further because it'll get lazy-loaded properly later.
// If we just created the damn thing then we don't need to continue into it any further because it'll get lazy-loaded properly later.
if (!container->lazy_loaded) break;
}
if (!container->lazy_loaded) continue;
// We've gone all the way down to the deepest level and everything was
// already lazy loaded, so now we have to create the song in the container.
// We've gone all the way down to the deepest level and everything was already lazy loaded, so now we have to create the song in the container.
song_nodes_[song.id()] = ItemFromSong(GroupBy_None, true, false, container, song, -1);
}
@@ -256,9 +263,8 @@ void CollectionModel::SongsDiscovered(const SongList &songs) {
void CollectionModel::SongsSlightlyChanged(const SongList &songs) {
// This is called if there was a minor change to the songs that will not
// normally require the collection to be restructured. We can just update our
// internal cache of Song objects without worrying about resetting the model.
// This is called if there was a minor change to the songs that will not normally require the collection to be restructured.
// We can just update our internal cache of Song objects without worrying about resetting the model.
for (const Song &song : songs) {
if (song_nodes_.contains(song.id())) {
song_nodes_[song.id()]->metadata = song;
@@ -285,8 +291,7 @@ CollectionItem *CollectionModel::CreateCompilationArtistNode(bool signal, Collec
QString CollectionModel::DividerKey(GroupBy type, CollectionItem *item) const {
// Items which are to be grouped under the same divider must produce the
// same divider key. This will only get called for top-level items.
// Items which are to be grouped under the same divider must produce the same divider key. This will only get called for top-level items.
if (item->sort_text.isEmpty()) return QString();
@@ -371,8 +376,7 @@ QString CollectionModel::DividerDisplayText(GroupBy type, const QString &key) co
void CollectionModel::SongsDeleted(const SongList &songs) {
// Delete the actual song nodes first, keeping track of each parent so we
// might check to see if they're empty later.
// Delete the actual song nodes first, keeping track of each parent so we might check to see if they're empty later.
QSet<CollectionItem*> parents;
for (const Song &song : songs) {
if (song_nodes_.contains(song.id())) {
@@ -386,11 +390,9 @@ void CollectionModel::SongsDeleted(const SongList &songs) {
endRemoveRows();
}
else {
// If we get here it means some of the songs we want to delete haven't
// been lazy-loaded yet. This is bad, because it would mean that to
// clean up empty parents we would need to lazy-load them all
// individually to see if they're empty. This can take a very long time,
// so better to just reset the model and be done with it.
// If we get here it means some of the songs we want to delete haven't been lazy-loaded yet.
// This is bad, because it would mean that to clean up empty parents we would need to lazy-load them all individually to see if they're empty.
// This can take a very long time, so better to just reset the model and be done with it.
Reset();
return;
}
@@ -399,9 +401,8 @@ void CollectionModel::SongsDeleted(const SongList &songs) {
// Now delete empty parents
QSet<QString> divider_keys;
while (!parents.isEmpty()) {
// Since we are going to remove elements from the container, we
// need a copy to iterate over. If we iterate over the original,
// the behavior will be undefined.
// Since we are going to remove elements from the container, we need a copy to iterate over.
// If we iterate over the original, the behavior will be undefined.
QSet<CollectionItem*> parents_copy = parents;
for (CollectionItem *node : parents_copy) {
parents.remove(node);

View File

@@ -23,26 +23,40 @@
#include "config.h"
#include <QAbstractItemModel>
#include <QIcon>
#include <QNetworkDiskCache>
#include <stdbool.h>
#include <QtGlobal>
#include <QObject>
#include <QAbstractItemModel>
#include <QFuture>
#include <QDataStream>
#include <QList>
#include <QMap>
#include <QMetaType>
#include <QMimeData>
#include <QPair>
#include <QSet>
#include <QVariant>
#include <QString>
#include <QStringList>
#include <QUrl>
#include <QImage>
#include <QIcon>
#include <QPixmap>
#include <QNetworkDiskCache>
#include <QSettings>
#include "collectionitem.h"
#include "collectionquery.h"
#include "collectionwatcher.h"
#include "sqlrow.h"
#include "core/simpletreemodel.h"
#include "core/song.h"
#include "collectionquery.h"
#include "collectionitem.h"
#include "sqlrow.h"
#include "covermanager/albumcoverloaderoptions.h"
#include "engine/engine_fwd.h"
#include "playlist/playlistmanager.h"
class Application;
class AlbumCoverLoader;
class CollectionDirectoryModel;
class CollectionBackend;
class QSettings;
class CollectionDirectoryModel;
class CollectionItem;
class CollectionModel : public SimpleTreeModel<CollectionItem> {
Q_OBJECT
@@ -190,8 +204,7 @@ signals:
private:
// Provides some optimisations for loading the list of items in the root.
// This gets called a lot when filtering the playlist, so it's nice to be
// able to do it in a background thread.
// This gets called a lot when filtering the playlist, so it's nice to be able to do it in a background thread.
QueryResult RunQuery(CollectionItem *parent);
void PostQuery(CollectionItem *parent, const QueryResult &result, bool signal);
@@ -200,25 +213,18 @@ signals:
void BeginReset();
// Functions for working with queries and creating items.
// When the model is reset or when a node is lazy-loaded the Collection
// constructs a database query to populate the items. Filters are added
// for each parent item, restricting the songs returned to a particular
// album or artist for example.
// When the model is reset or when a node is lazy-loaded the Collection constructs a database query to populate the items.
// Filters are added for each parent item, restricting the songs returned to a particular album or artist for example.
static void InitQuery(GroupBy type, CollectionQuery *q);
void FilterQuery(GroupBy type, CollectionItem *item, CollectionQuery *q);
// Items can be created either from a query that's been run to populate a
// node, or by a spontaneous SongsDiscovered emission from the backend.
// Items can be created either from a query that's been run to populate a node, or by a spontaneous SongsDiscovered emission from the backend.
CollectionItem *ItemFromQuery(GroupBy type, bool signal, bool create_divider, CollectionItem *parent, const SqlRow &row, int container_level);
CollectionItem *ItemFromSong(GroupBy type, bool signal, bool create_divider, CollectionItem *parent, const Song &s, int container_level);
// The "Various Artists" node is an annoying special case.
CollectionItem *CreateCompilationArtistNode(bool signal, CollectionItem *parent);
// Smart playlists are shown in another top-level node
void ItemFromSmartPlaylist(const QSettings &s, bool notify) const;
// Helpers for ItemFromQuery and ItemFromSong
CollectionItem *InitItem(GroupBy type, bool signal, CollectionItem *parent, int container_level);
void FinishItem(GroupBy type, bool signal, bool create_divider, CollectionItem *parent, CollectionItem *item);
@@ -256,8 +262,7 @@ signals:
QIcon artist_icon_;
QIcon album_icon_;
// used as a generic icon to show when no cover art is found,
// fixed to the same size as the artwork (32x32)
// Used as a generic icon to show when no cover art is found, fixed to the same size as the artwork (32x32)
QPixmap no_cover_icon_;
QIcon playlists_dir_icon_;
QIcon playlist_icon_;

View File

@@ -20,10 +20,14 @@
#include "config.h"
#include <QVariant>
#include <QString>
#include <QUrl>
#include "collectionplaylistitem.h"
#include "core/tagreaderclient.h"
#include <QSettings>
class SqlRow;
CollectionPlaylistItem::CollectionPlaylistItem(const QString &type)
: PlaylistItem(type) {}

View File

@@ -23,9 +23,17 @@
#include "config.h"
#include <stdbool.h>
#include <QVariant>
#include <QString>
#include <QUrl>
#include "core/song.h"
#include "playlist/playlistitem.h"
class SqlRow;
class CollectionPlaylistItem : public PlaylistItem {
public:
CollectionPlaylistItem(const QString &type);

View File

@@ -20,21 +20,26 @@
#include "config.h"
#include <QtGlobal>
#include <QDateTime>
#include <QVariant>
#include <QString>
#include <QStringList>
#include <QStringBuilder>
#include <QRegExp>
#include <QSqlDatabase>
#include <QSqlQuery>
#include "collectionquery.h"
#include "core/song.h"
#include <QtDebug>
#include <QDateTime>
#include <QSqlError>
QueryOptions::QueryOptions() : max_age_(-1), query_mode_(QueryMode_All) {}
CollectionQuery::CollectionQuery(const QueryOptions& options)
: include_unavailable_(false), join_with_fts_(false), limit_(-1) {
if (!options.filter().isEmpty()) {
// We need to munge the filter text a little bit to get it to work as
// expected with sqlite's FTS3:
// We need to munge the filter text a little bit to get it to work as expected with sqlite's FTS3:
// 1) Append * to all tokens.
// 2) Prefix "fts" to column names.
// 3) Remove colons which don't correspond to column names.
@@ -50,8 +55,7 @@ CollectionQuery::CollectionQuery(const QueryOptions& options)
if (token.contains(':')) {
// Only prefix fts if the token is a valid column name.
if (Song::kFtsColumns.contains("fts" + token.section(':', 0, 0),
Qt::CaseInsensitive)) {
if (Song::kFtsColumns.contains("fts" + token.section(':', 0, 0), Qt::CaseInsensitive)) {
// Account for multiple colons.
QString columntoken = token.section(':', 0, 0, QString::SectionIncludeTrailingSep);
QString subtoken = token.section(':', 1, -1);
@@ -82,16 +86,12 @@ CollectionQuery::CollectionQuery(const QueryOptions& options)
bound_values_ << cutoff;
}
// TODO: currently you cannot use any QueryMode other than All and fts at the
// same time.
// joining songs, duplicated_songs and songs_fts all together takes a huge
// amount of
// time. the query takes about 20 seconds on my machine then. why?
// untagged mode could work with additional filtering but I'm disabling it
// just to be
// consistent - this way filtering is available only in the All mode.
// remember though that when you fix the Duplicates + FTS cooperation, enable
// the filtering in both Duplicates and Untagged modes.
// TODO: Currently you cannot use any QueryMode other than All and fts at the same time.
// Joining songs, duplicated_songs and songs_fts all together takes a huge amount of time.
// The query takes about 20 seconds on my machine then. Why?
// Untagged mode could work with additional filtering but I'm disabling it just to be consistent
// this way filtering is available only in the All mode.
// Remember though that when you fix the Duplicates + FTS cooperation, enable the filtering in both Duplicates and Untagged modes.
duplicates_only_ = options.query_mode() == QueryOptions::QueryMode_Duplicates;
if (options.query_mode() == QueryOptions::QueryMode_Untagged) {
@@ -114,7 +114,7 @@ void CollectionQuery::AddWhere(const QString &column, const QVariant &value, con
// ignore 'literal' for IN
if (!op.compare("IN", Qt::CaseInsensitive)) {
QStringList final;
for (const QString& single_value : value.toStringList()) {
for (const QString &single_value : value.toStringList()) {
final.append("?");
bound_values_ << single_value;
}
@@ -122,8 +122,7 @@ void CollectionQuery::AddWhere(const QString &column, const QVariant &value, con
where_clauses_ << QString("%1 IN (" + final.join(",") + ")").arg(column);
}
else {
// Do integers inline - sqlite seems to get confused when you pass integers
// to bound parameters
// Do integers inline - sqlite seems to get confused when you pass integers to bound parameters
if (value.type() == QVariant::Int) {
where_clauses_ << QString("%1 %2 %3").arg(column, op, value.toString());
}
@@ -136,10 +135,8 @@ void CollectionQuery::AddWhere(const QString &column, const QVariant &value, con
}
void CollectionQuery::AddCompilationRequirement(bool compilation) {
// The unary + is added to prevent sqlite from using the index
// idx_comp_artist. When joining with fts, sqlite 3.8 has a tendency
// to use this index and thereby nesting the tables in an order
// which gives very poor performance
// The unary + is added to prevent sqlite from using the index idx_comp_artist.
// When joining with fts, sqlite 3.8 has a tendency to use this index and thereby nesting the tables in an order which gives very poor performance
where_clauses_ << QString("+compilation_effective = %1").arg(compilation ? 1 : 0);
@@ -175,7 +172,7 @@ QSqlQuery CollectionQuery::Exec(QSqlDatabase db, const QString &songs_table, con
query_.prepare(sql);
// Bind values
for (const QVariant& value : bound_values_) {
for (const QVariant &value : bound_values_) {
query_.addBindValue(value);
}

View File

@@ -23,11 +23,14 @@
#include "config.h"
#include <QString>
#include <stdbool.h>
#include <QMetaType>
#include <QVariant>
#include <QSqlQuery>
#include <QString>
#include <QStringList>
#include <QVariantList>
#include <QSqlDatabase>
#include <QSqlQuery>
class Song;
class CollectionBackend;
@@ -36,13 +39,9 @@ class CollectionBackend;
struct QueryOptions {
// Modes of CollectionQuery:
// - use the all songs table
// - use the duplicated songs view; by duplicated we mean those songs
// for which the (artist, album, title) tuple is found more than once
// in the songs table
// - use the untagged songs view; by untagged we mean those for which
// at least one of the (artist, album, title) tags is empty
// Please note that additional filtering based on fts table (the filter
// attribute) won't work in Duplicates and Untagged modes.
// - use the duplicated songs view; by duplicated we mean those songs for which the (artist, album, title) tuple is found more than once in the songs table
// - use the untagged songs view; by untagged we mean those for which at least one of the (artist, album, title) tags is empty
// Please note that additional filtering based on fts table (the filter attribute) won't work in Duplicates and Untagged modes.
enum QueryMode {
QueryMode_All,
QueryMode_Duplicates,
@@ -83,8 +82,7 @@ class CollectionQuery {
// Sets an ORDER BY clause on the query.
void SetOrderBy(const QString &order_by) { order_by_ = order_by; }
// Adds a fragment of WHERE clause. When executed, this Query will connect all
// the fragments with AND operator.
// Adds a fragment of WHERE clause. When executed, this Query will connect all the fragments with AND operator.
// Please note that IN operator expects a QStringList as value.
void AddWhere(const QString &column, const QVariant &value, const QString &op = "=");

View File

@@ -20,35 +20,59 @@
#include "config.h"
#include "collectionview.h"
#include <qcoreevent.h>
#include <QPainter>
#include <QContextMenuEvent>
#include <QHelpEvent>
#include <QMenu>
#include <QMessageBox>
#include <QSet>
#include <QSettings>
#include <QtGlobal>
#include <QWidget>
#include <QItemSelectionModel>
#include <QSortFilterProxyModel>
#include <QAbstractItemView>
#include <QStyleOptionViewItem>
#include <QAction>
#include <QVariant>
#include <QString>
#include <QUrl>
#include <QList>
#include <QLocale>
#include <QMap>
#include <QMessageBox>
#include <QMenu>
#include <QMimeData>
#include <QPainter>
#include <QPalette>
#include <QPen>
#include <QPoint>
#include <QRect>
#include <QSet>
#include <QSize>
#include <QToolTip>
#include <QTreeView>
#include <QWhatsThis>
#include <QBrush>
#include <QColor>
#include <QFont>
#include <QFontMetrics>
#include <QPixmap>
#include <QIcon>
#include <QLinearGradient>
#include <QSettings>
#include <QtEvents>
#include "core/application.h"
#include "core/iconloader.h"
#include "core/mimedata.h"
#include "core/utilities.h"
#include "collectionbackend.h"
#include "collectiondirectorymodel.h"
#include "collectionfilterwidget.h"
#include "collectionmodel.h"
#include "collectionitem.h"
#include "collectionbackend.h"
#include "core/application.h"
#include "core/logging.h"
#include "core/mimedata.h"
#include "core/musicstorage.h"
#include "core/utilities.h"
#include "core/iconloader.h"
#include "collectionmodel.h"
#include "collectionview.h"
#include "device/devicemanager.h"
#include "device/devicestatefiltermodel.h"
#include "dialogs/edittagdialog.h"
#ifdef HAVE_GSTREAMER
#include "dialogs/organisedialog.h"
#include "dialogs/organiseerrordialog.h"
#endif
#include "settings/collectionsettingspage.h"
@@ -293,8 +317,7 @@ bool CollectionView::RestoreLevelFocus(const QModelIndex &parent) {
}
else if (last_selected_path_.contains(text)) {
emit expand(current);
// If a selected container or song were not found, we've got into a wrong subtree
// (happens with "unknown" all the time)
// If a selected container or song were not found, we've got into a wrong subtree (happens with "unknown" all the time)
if (!RestoreLevelFocus(current)) {
emit collapse(current);
}
@@ -312,8 +335,6 @@ bool CollectionView::RestoreLevelFocus(const QModelIndex &parent) {
void CollectionView::ReloadSettings() {
//qLog(Debug) << __PRETTY_FUNCTION__;
QSettings settings;
settings.beginGroup(CollectionSettingsPage::kSettingsGroup);
@@ -330,8 +351,6 @@ void CollectionView::ReloadSettings() {
void CollectionView::SetApplication(Application *app) {
//qLog(Debug) << __PRETTY_FUNCTION__;
app_ = app;
ReloadSettings();
@@ -342,8 +361,6 @@ void CollectionView::SetFilter(CollectionFilterWidget *filter) { filter_ = filte
void CollectionView::TotalSongCountUpdated(int count) {
//qLog(Debug) << __FUNCTION__ << count;
bool old = total_song_count_;
total_song_count_ = count;
if (old != total_song_count_) update();
@@ -359,8 +376,6 @@ void CollectionView::TotalSongCountUpdated(int count) {
void CollectionView::TotalArtistCountUpdated(int count) {
//qLog(Debug) << __FUNCTION__ << count;
bool old = total_artist_count_;
total_artist_count_ = count;
if (old != total_artist_count_) update();
@@ -376,8 +391,6 @@ void CollectionView::TotalArtistCountUpdated(int count) {
void CollectionView::TotalAlbumCountUpdated(int count) {
//qLog(Debug) << __FUNCTION__ << count;
bool old = total_album_count_;
total_album_count_ = count;
if (old != total_album_count_) update();
@@ -393,8 +406,6 @@ void CollectionView::TotalAlbumCountUpdated(int count) {
void CollectionView::paintEvent(QPaintEvent *event) {
//qLog(Debug) << __FUNCTION__;
if (total_song_count_ == 0) {
QPainter p(viewport());
QRect rect(viewport()->rect());
@@ -491,7 +502,6 @@ void CollectionView::contextMenuEvent(QContextMenuEvent *e) {
}
// TODO: check if custom plugin actions should be enabled / visible
//const int songs_selected = smart_playlists + smart_playlists_header + regular_elements;
const int songs_selected = regular_elements;
const bool regular_elements_only = songs_selected == regular_elements && regular_elements > 0;
@@ -502,7 +512,6 @@ void CollectionView::contextMenuEvent(QContextMenuEvent *e) {
add_to_playlist_enqueue_->setEnabled(songs_selected);
// if neither edit_track not edit_tracks are available, we show disabled edit_track element
//edit_track_->setVisible(!smart_playlists_only && (regular_editable <= 1));
edit_track_->setVisible(regular_editable <= 1);
edit_track_->setEnabled(regular_editable == 1);
@@ -534,9 +543,9 @@ void CollectionView::ShowInVarious(bool on) {
if (!context_menu_index_.isValid()) return;
// Map is from album name -> all artists sharing that album name, built from each selected
// song. We put through "Various Artists" changes one album at a time, to make sure the old album
// node gets removed (due to all children removed), before the new one gets added
// Map is from album name -> all artists sharing that album name, built from each selected song.
// We put through "Various Artists" changes one album at a time,
// to make sure the old album node gets removed (due to all children removed), before the new one gets added
QMultiMap<QString, QString> albums;
for (const Song& song : GetSelectedSongs()) {
if (albums.find(song.album(), song.artist()) == albums.end())
@@ -586,16 +595,12 @@ void CollectionView::Load() {
void CollectionView::AddToPlaylist() {
//qLog(Debug) << __PRETTY_FUNCTION__;
emit AddToPlaylistSignal(model()->mimeData(selectedIndexes()));
}
void CollectionView::AddToPlaylistEnqueue() {
//qLog(Debug) << __PRETTY_FUNCTION__;
QMimeData *data = model()->mimeData(selectedIndexes());
if (MimeData* mime_data = qobject_cast<MimeData*>(data)) {
mime_data->enqueue_now_ = true;

View File

@@ -24,21 +24,38 @@
#include "config.h"
#include <memory>
#include <stdbool.h>
#include <QObject>
#include <QWidget>
#include <QAbstractItemModel>
#include <QAbstractItemView>
#include <QString>
#include <QPixmap>
#include <QPainter>
#include <QSet>
#include <QStyleOption>
#include <QStyledItemDelegate>
#include <QStyleOptionViewItem>
#include <QAction>
#include <QMenu>
#include <QtEvents>
#include "core/song.h"
#include "dialogs/edittagdialog.h"
#include "widgets/autoexpandingtreeview.h"
class QContextMenuEvent;
class QHelpEvent;
class QMouseEvent;
class QPaintEvent;
class Application;
class CollectionFilterWidget;
class EditTagDialog;
#ifdef HAVE_GSTREAMER
class OrganiseDialog;
#endif
class QMimeData;
class CollectionItemDelegate : public QStyledItemDelegate {
Q_OBJECT
@@ -57,11 +74,8 @@ class CollectionView : public AutoExpandingTreeView {
CollectionView(QWidget *parent = nullptr);
~CollectionView();
//static const char *kSettingsGroup;
// Returns Songs currently selected in the collection view. Please note that the
// selection is recursive meaning that if for example an album is selected
// this will return all of it's songs.
// Returns Songs currently selected in the collection view.
// Please note that the selection is recursive meaning that if for example an album is selected this will return all of it's songs.
SongList GetSelectedSongs() const;
void SetApplication(Application *app);

View File

@@ -20,6 +20,11 @@
#include "config.h"
#include <QWidget>
#include <QKeyEvent>
#include "collectionfilterwidget.h"
#include "collectionview.h"
#include "collectionviewcontainer.h"
#include "ui_collectionviewcontainer.h"

View File

@@ -23,6 +23,7 @@
#include "config.h"
#include <QObject>
#include <QWidget>
class CollectionFilterWidget;

View File

@@ -20,34 +20,42 @@
#include "config.h"
#include <fileref.h>
#include <tag.h>
#include <QObject>
#include <QIODevice>
#include <QDir>
#include <QDirIterator>
#include <QFile>
#include <QFileInfo>
#include <QMetaObject>
#include <QDateTime>
#include <QHash>
#include <QMap>
#include <QList>
#include <QSet>
#include <QTimer>
#include <QVariant>
#include <QString>
#include <QStringList>
#include <QUrl>
#include <QImage>
#include <QSettings>
#include <QtDebug>
// This is defined by one of the windows headers that is included by taglib.
#ifdef RemoveDirectory
#undef RemoveDirectory
#endif
#include "collectionwatcher.h"
#include "collectionbackend.h"
#include "core/filesystemwatcherinterface.h"
#include "core/logging.h"
#include "core/tagreaderclient.h"
#include "core/taskmanager.h"
#include "core/utilities.h"
#include "directory.h"
#include "collectionbackend.h"
#include "collectionwatcher.h"
#include "playlistparsers/cueparser.h"
#include "settings/collectionsettingspage.h"
#include <QDateTime>
#include <QDirIterator>
#include <QtDebug>
#include <QThread>
#include <QDateTime>
#include <QHash>
#include <QSet>
#include <QSettings>
#include <QTimer>
// This is defined by one of the windows headers that is included by taglib.
#ifdef RemoveDirectory
#undef RemoveDirectory
#endif
namespace {
static const char *kNoMediaFile = ".nomedia";
@@ -218,8 +226,7 @@ void CollectionWatcher::AddDirectory(const Directory &dir, const SubdirectoryLis
ScanSubdirectory(dir.path, Subdirectory(), &transaction);
}
else {
// We can do an incremental scan - looking at the mtimes of each
// subdirectory and only rescan if the directory has changed.
// We can do an incremental scan - looking at the mtimes of each subdirectory and only rescan if the directory has changed.
ScanTransaction transaction(this, dir.id, true);
transaction.SetKnownSubdirs(subdirs);
transaction.AddToProgressMax(subdirs.count());
@@ -268,8 +275,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
QStringList files_on_disk;
SubdirectoryList my_new_subdirs;
// If a directory is moved then only its parent gets a changed notification,
// so we need to look and see if any of our children don't exist any more.
// If a directory is moved then only its parent gets a changed notification, so we need to look and see if any of our children don't exist any more.
// If one has been removed, "rescan" it to get the deleted songs
SubdirectoryList previous_subdirs = t->GetImmediateSubdirs(path);
for (const Subdirectory& subdir : previous_subdirs) {
@@ -289,8 +295,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
if (child_info.isDir()) {
if (!child_info.isHidden() && !t->HasSeenSubdir(child)) {
// We haven't seen this subdirectory before - add it to a list and
// later we'll tell the backend about it and scan it.
// We haven't seen this subdirectory before - add it to a list and later we'll tell the backend about it and scan it.
Subdirectory new_subdir;
new_subdir.directory_id = -1;
new_subdir.path = child;
@@ -332,8 +337,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
QFileInfo file_info(file);
if (!file_info.exists()) {
// Partially fixes race condition - if file was removed between being
// added to the list and now.
// Partially fixes race condition - if file was removed between being added to the list and now.
files_on_disk.removeAll(file);
continue;
}
@@ -345,8 +349,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
bool cue_deleted = song_cue_mtime == 0 && matching_song.has_cue();
bool cue_added = matching_cue_mtime != 0 && !matching_song.has_cue();
// watch out for cue songs which have their mtime equal to
// qMax(media_file_mtime, cue_sheet_mtime)
// watch out for cue songs which have their mtime equal to qMax(media_file_mtime, cue_sheet_mtime)
bool changed = (matching_song.mtime() != qMax(file_info.lastModified().toTime_t(), song_cue_mtime)) || cue_deleted || cue_added;
// Also want to look to see whether the album art has changed
@@ -372,7 +375,8 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
// nothing has changed - mark the song available without re-scanning
if (matching_song.is_unavailable()) t->readded_songs << matching_song;
} else {
}
else {
// The song is on disk but not in the DB
SongList song_list = ScanNewFile(file, path, matching_cue, &cues_processed);
@@ -437,7 +441,7 @@ void CollectionWatcher::UpdateCueAssociatedSongs(const QString &file, const QStr
QSet<int> used_ids;
// update every song that's in the cue and collection
// Update every song that's in the cue and collection
for (Song cue_song : cue_parser_->Load(&cue, matching_cue, path)) {
cue_song.set_directory_id(t->dir());
@@ -463,9 +467,7 @@ void CollectionWatcher::UpdateCueAssociatedSongs(const QString &file, const QStr
void CollectionWatcher::UpdateNonCueAssociatedSong(const QString &file, const Song &matching_song, const QString &image, bool cue_deleted, ScanTransaction *t) {
// if a cue got deleted, we turn it's first section into the new
// 'raw' (cueless) song and we just remove the rest of the sections
// from the collection
// If a cue got deleted, we turn it's first section into the new 'raw' (cueless) song and we just remove the rest of the sections from the collection
if (cue_deleted) {
for (const Song &song :
backend_->GetSongsByUrl(QUrl::fromLocalFile(file))) {
@@ -490,7 +492,7 @@ SongList CollectionWatcher::ScanNewFile(const QString &file, const QString &path
SongList song_list;
uint matching_cue_mtime = GetMtimeForCue(matching_cue);
// if it's a cue - create virtual tracks
// If it's a cue - create virtual tracks
if (matching_cue_mtime) {
// don't process the same cue many times
if (cues_processed->contains(matching_cue)) return song_list;
@@ -498,9 +500,9 @@ SongList CollectionWatcher::ScanNewFile(const QString &file, const QString &path
QFile cue(matching_cue);
cue.open(QIODevice::ReadOnly);
// Ignore FILEs pointing to other media files. Also, watch out for incorrect
// media files. Playlist parser for CUEs considers every entry in sheet
// valid and we don't want invalid media getting into collection!
// Ignore FILEs pointing to other media files.
// Also, watch out for incorrect media files.
// Playlist parser for CUEs considers every entry in sheet valid and we don't want invalid media getting into collection!
QString file_nfd = file.normalized(QString::NormalizationForm_D);
for (const Song& cue_song : cue_parser_->Load(&cue, matching_cue, path)) {
if (cue_song.url().toLocalFile().normalized(QString::NormalizationForm_D) == file_nfd) {
@@ -533,15 +535,13 @@ void CollectionWatcher::PreserveUserSetData(const QString &file, const QString &
out->set_id(matching_song.id());
// Previous versions of Clementine incorrectly overwrote this and
// stored it in the DB, so we can't rely on matching_song to
// know if it has embedded artwork or not, but we can check here.
// Previous versions of Clementine incorrectly overwrote this and stored it in the DB,
// so we can't rely on matching_song to know if it has embedded artwork or not, but we can check here.
if (!out->has_embedded_cover()) out->set_art_automatic(image);
out->MergeUserSetData(matching_song);
// The song was deleted from the database (e.g. due to an unmounted
// filesystem), but has been restored.
// The song was deleted from the database (e.g. due to an unmounted filesystem), but has been restored.
if (matching_song.is_unavailable()) {
qLog(Debug) << file << " unavailable song restored";
@@ -562,7 +562,7 @@ void CollectionWatcher::PreserveUserSetData(const QString &file, const QString &
uint CollectionWatcher::GetMtimeForCue(const QString &cue_path) {
// slight optimisation
// Slight optimisation
if (cue_path.isEmpty()) {
return 0;
}
@@ -593,7 +593,7 @@ void CollectionWatcher::RemoveDirectory(const Directory &dir) {
watched_dirs_.remove(dir.id);
// Stop watching the directory's subdirectories
for (const QString& subdir_path : subdir_mapping_.keys(dir)) {
for (const QString &subdir_path : subdir_mapping_.keys(dir)) {
fs_watcher_->RemovePath(subdir_path);
subdir_mapping_.remove(subdir_path);
}
@@ -662,8 +662,7 @@ QString CollectionWatcher::PickBestImage(const QStringList &images) {
QStringList filtered;
for (const QString &filter_text : best_image_filters_) {
// the images in the images list are represented by a full path,
// so we need to isolate just the filename
// The images in the images list are represented by a full path, so we need to isolate just the filename
for (const QString& image : images) {
QFileInfo file_info(image);
QString filename(file_info.fileName());
@@ -671,14 +670,13 @@ QString CollectionWatcher::PickBestImage(const QStringList &images) {
filtered << image;
}
/* We assume the filters are give in the order best to worst, so
if we've got a result, we go with it. Otherwise we might
start capturing more generic rules */
// We assume the filters are give in the order best to worst, so if we've got a result, we go with it.
// Otherwise we might start capturing more generic rules
if (!filtered.isEmpty()) break;
}
if (filtered.isEmpty()) {
// the filter was too restrictive, just use the original list
// The filter was too restrictive, just use the original list
filtered = images;
}
@@ -734,7 +732,7 @@ void CollectionWatcher::ReloadSettings() {
best_image_filters_.clear();
QStringList filters = s.value("cover_art_patterns", QStringList() << "front" << "cover").toStringList();
for (const QString& filter : filters) {
for (const QString &filter : filters) {
QString s = filter.trimmed();
if (!s.isEmpty()) best_image_filters_ << s;
}

View File

@@ -23,22 +23,24 @@
#include "config.h"
#include "directory.h"
#include <stdbool.h>
#include <QHash>
#include <QtGlobal>
#include <QObject>
#include <QStringList>
#include <QHash>
#include <QMap>
#include <QSet>
#include <QString>
#include <QStringList>
#include <QTimer>
#include "directory.h"
#include "core/song.h"
class QFileSystemWatcher;
class QTimer;
class CueParser;
class FileSystemWatcherInterface;
class CollectionBackend;
class FileSystemWatcherInterface;
class TaskManager;
class CueParser;
class CollectionWatcher : public QObject {
Q_OBJECT
@@ -76,14 +78,11 @@ signals:
private:
// This class encapsulates a full or partial scan of a directory.
// Each directory has one or more subdirectories, and any number of
// subdirectories can be scanned during one transaction. ScanSubdirectory()
// adds its results to the members of this transaction class, and they are
// "committed" through calls to the CollectionBackend in the transaction's dtor.
// The transaction also caches the list of songs in this directory according
// to the collection. Multiple calls to FindSongsInSubdirectory during one
// transaction will only result in one call to
// CollectionBackend::FindSongsInDirectory.
// Each directory has one or more subdirectories, and any number of subdirectories can be scanned during one transaction.
// ScanSubdirectory() adds its results to the members of this transaction class,
// and they are "committed" through calls to the CollectionBackend in the transaction's dtor.
// The transaction also caches the list of songs in this directory according to the collection.
// Multiple calls to FindSongsInSubdirectory during one transaction will only result in one call to CollectionBackend::FindSongsInDirectory.
class ScanTransaction {
public:
ScanTransaction(CollectionWatcher *watcher, int dir, bool incremental, bool ignores_mtime = false);
@@ -120,10 +119,9 @@ signals:
int dir_;
// Incremental scan enters a directory only if it has changed since the last scan.
bool incremental_;
// This type of scan updates every file in a folder that's
// being scanned. Even if it detects the file hasn't changed since
// the last scan. Also, since it's ignoring mtimes on folders too,
// it will go as deep in the folder hierarchy as it's possible.
// This type of scan updates every file in a folder that's being scanned.
// Even if it detects the file hasn't changed since the last scan.
// Also, since it's ignoring mtimes on folders too, it will go as deep in the folder hierarchy as it's possible.
bool ignores_mtime_;
CollectionWatcher *watcher_;
@@ -153,18 +151,14 @@ signals:
uint GetMtimeForCue(const QString &cue_path);
void PerformScan(bool incremental, bool ignore_mtimes);
// Updates the sections of a cue associated and altered (according to mtime)
// media file during a scan.
// Updates the sections of a cue associated and altered (according to mtime) media file during a scan.
void UpdateCueAssociatedSongs(const QString &file, const QString &path, const QString &matching_cue, const QString &image, ScanTransaction *t);
// Updates a single non-cue associated and altered (according to mtime) song
// during a scan.
// Updates a single non-cue associated and altered (according to mtime) song during a scan.
void UpdateNonCueAssociatedSong(const QString &file, const Song &matching_song, const QString &image, bool cue_deleted, ScanTransaction *t);
// Updates a new song with some metadata taken from it's equivalent old
// song (for example rating and score).
// Updates a new song with some metadata taken from it's equivalent old song (for example rating and score).
void PreserveUserSetData(const QString &file, const QString &image, const Song &matching_song, Song *out, ScanTransaction *t);
// Scans a single media file that's present on the disk but not yet in the collection.
// It may result in a multiple files added to the collection when the media file
// has many sections (like a CUE related media file).
// It may result in a multiple files added to the collection when the media file has many sections (like a CUE related media file).
SongList ScanNewFile(const QString &file, const QString &path, const QString &matching_cue, QSet<QString> *cues_processed);
private:
@@ -175,11 +169,8 @@ signals:
FileSystemWatcherInterface *fs_watcher_;
QHash<QString, Directory> subdir_mapping_;
/* A list of words use to try to identify the (likely) best image
* found in an directory to use as cover artwork.
* e.g. using ["front", "cover"] would identify front.jpg and
* exclude back.jpg.
*/
// A list of words use to try to identify the (likely) best image found in an directory to use as cover artwork.
// e.g. using ["front", "cover"] would identify front.jpg and exclude back.jpg.
QStringList best_image_filters_;
bool stop_requested_;

View File

@@ -23,11 +23,10 @@
#include "config.h"
#include <QMetaType>
#include <QList>
#include <QString>
#include <QMetaType>
class QSqlQuery;
#include <QSqlQuery>
struct Directory {
Directory() : id(-1) {}

View File

@@ -22,8 +22,13 @@
#include <functional>
#include <QDialog>
#include <QComboBox>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QWidget>
#include "collectionmodel.h"
#include "groupbydialog.h"
#include "ui_groupbydialog.h"
@@ -31,9 +36,13 @@
using std::placeholders::_1;
using std::placeholders::_2;
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/indexed_by.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/tag.hpp>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index_container_fwd.hpp>
#include <boost/operators.hpp>
using boost::multi_index_container;
using boost::multi_index::indexed_by;
@@ -70,7 +79,7 @@ class GroupByDialogPrivate {
MappingContainer mapping_;
};
GroupByDialog::GroupByDialog(QWidget* parent) : QDialog(parent), ui_(new Ui_GroupByDialog), p_(new GroupByDialogPrivate) {
GroupByDialog::GroupByDialog(QWidget *parent) : QDialog(parent), ui_(new Ui_GroupByDialog), p_(new GroupByDialogPrivate) {
ui_->setupUi(this);
Reset();

View File

@@ -26,6 +26,9 @@
#include <memory>
#include <QDialog>
#include <QObject>
#include <QWidget>
#include <QString>
#include "collectionmodel.h"

View File

@@ -18,19 +18,33 @@
*
*/
#include "config.h"
#include <stdbool.h>
#include <QDialog>
#include <QWidget>
#include <QStandardItemModel>
#include <QItemSelectionModel>
#include <QAbstractItemModel>
#include <QIODevice>
#include <QDataStream>
#include <QByteArray>
#include <QList>
#include <QVariant>
#include <QString>
#include <QStringList>
#include <QKeySequence>
#include <QPushButton>
#include <QTreeView>
#include <QSettings>
#include <QtDebug>
#include "core/logging.h"
#include "core/iconloader.h"
#include "collectionfilterwidget.h"
#include "collectionmodel.h"
#include "savedgroupingmanager.h"
#include "ui_savedgroupingmanager.h"
#include <QKeySequence>
#include <QList>
#include <QSettings>
#include <QStandardItem>
SavedGroupingManager::SavedGroupingManager(QWidget *parent)
: QDialog(parent),
ui_(new Ui_SavedGroupingManager),
@@ -58,6 +72,7 @@ SavedGroupingManager::~SavedGroupingManager() {
}
QString SavedGroupingManager::GroupByToString(const CollectionModel::GroupBy &g) {
switch (g) {
case CollectionModel::GroupBy_None: {
return tr("None");
@@ -137,8 +152,7 @@ void SavedGroupingManager::Remove() {
if (ui_->list->selectionModel()->hasSelection()) {
QSettings s;
s.beginGroup(CollectionModel::kSavedGroupingsSettingsGroup);
for (const QModelIndex &index :
ui_->list->selectionModel()->selectedRows()) {
for (const QModelIndex &index : ui_->list->selectionModel()->selectedRows()) {
if (index.isValid()) {
qLog(Debug) << "Remove saved grouping: " << model_->item(index.row(), 0)->text();
s.remove(model_->item(index.row(), 0)->text());

View File

@@ -24,12 +24,15 @@
#include "config.h"
#include <QDialog>
#include <QObject>
#include <QWidget>
#include <QStandardItemModel>
#include <QString>
#include "collectionmodel.h"
class Ui_SavedGroupingManager;
class CollectionFilterWidget;
class Ui_SavedGroupingManager;
class SavedGroupingManager : public QDialog {
Q_OBJECT

View File

@@ -20,12 +20,14 @@
#include "config.h"
#include "collectionquery.h"
#include "sqlrow.h"
#include <QVariant>
#include <QSqlQuery>
#include <QSqlRecord>
#include "sqlrow.h"
#include "collectionquery.h"
SqlRow::SqlRow(const QSqlQuery &query) { Init(query); }
SqlRow::SqlRow(const CollectionQuery &query) { Init(query); }

View File

@@ -25,8 +25,7 @@
#include <QList>
#include <QVariant>
class QSqlQuery;
#include <QSqlQuery>
class CollectionQuery;
@@ -37,7 +36,7 @@ class SqlRow {
SqlRow(const QSqlQuery &query);
SqlRow(const CollectionQuery &query);
const QVariant& value(int i) const { return columns_[i]; }
const QVariant &value(int i) const { return columns_[i]; }
QList<QVariant> columns_;