Loading file in async way, in two times: fast loading files and insert them in playlist view, while loading info (tags, ...) in background thread
This commit is contained in:
parent
23c744f41a
commit
65ef047c38
@ -560,6 +560,24 @@ void Song::InitFromQuery(const SqlRow& q, int col) {
|
|||||||
#undef tofloat
|
#undef tofloat
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Song::InitFromFilePartial(const QString& filename) {
|
||||||
|
d->filename_ = filename;
|
||||||
|
// We currently rely on filename suffix to know if it's a music file or not.
|
||||||
|
// TODO: I know this is not satisfying, but currently, we rely on TagLib
|
||||||
|
// which seems to have the behavior (filename checks). Someday, it would be
|
||||||
|
// nice to perform some magic tests everywhere.
|
||||||
|
QFileInfo info(filename);
|
||||||
|
d->basefilename_ = info.fileName();
|
||||||
|
QString suffix = info.suffix().toLower();
|
||||||
|
if (suffix == "mp3" || suffix == "ogg" || suffix == "flac" || suffix == "mpc"
|
||||||
|
|| suffix == "m4a" || suffix == "aac" || suffix == "wma" || suffix == "mp4"
|
||||||
|
|| suffix == "spx" || suffix == "wav") {
|
||||||
|
d->valid_ = true;
|
||||||
|
} else {
|
||||||
|
d->valid_ = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef HAVE_LIBLASTFM
|
#ifdef HAVE_LIBLASTFM
|
||||||
void Song::InitFromLastFM(const lastfm::Track& track) {
|
void Song::InitFromLastFM(const lastfm::Track& track) {
|
||||||
d->valid_ = true;
|
d->valid_ = true;
|
||||||
|
@ -128,6 +128,7 @@ class Song {
|
|||||||
void Init(const QString& title, const QString& artist, const QString& album, qint64 beginning, qint64 end);
|
void Init(const QString& title, const QString& artist, const QString& album, qint64 beginning, qint64 end);
|
||||||
void InitFromFile(const QString& filename, int directory_id);
|
void InitFromFile(const QString& filename, int directory_id);
|
||||||
void InitFromQuery(const SqlRow& query, int col = 0);
|
void InitFromQuery(const SqlRow& query, int col = 0);
|
||||||
|
void InitFromFilePartial(const QString& filename); // Just store the filename: incomplete but fast
|
||||||
#ifdef HAVE_LIBLASTFM
|
#ifdef HAVE_LIBLASTFM
|
||||||
void InitFromLastFM(const lastfm::Track& track);
|
void InitFromLastFM(const lastfm::Track& track);
|
||||||
#endif
|
#endif
|
||||||
|
@ -82,6 +82,21 @@ SongLoader::Result SongLoader::Load(const QUrl& url) {
|
|||||||
return LoadRemote();
|
return LoadRemote();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SongLoader::Result SongLoader::LoadLocalPartial(const QString& filename) {
|
||||||
|
qDebug() << "Fast Loading local file" << filename;
|
||||||
|
// First check to see if it's a directory - if so we can load all the songs
|
||||||
|
// inside right away.
|
||||||
|
if (QFileInfo(filename).isDir()) {
|
||||||
|
LoadLocalDirectory(filename);
|
||||||
|
return Success;
|
||||||
|
}
|
||||||
|
Song song;
|
||||||
|
song.InitFromFilePartial(filename);
|
||||||
|
if (song.is_valid())
|
||||||
|
songs_ << song;
|
||||||
|
return Success;
|
||||||
|
}
|
||||||
|
|
||||||
SongLoader::Result SongLoader::LoadLocal(const QString& filename, bool block,
|
SongLoader::Result SongLoader::LoadLocal(const QString& filename, bool block,
|
||||||
bool ignore_playlists) {
|
bool ignore_playlists) {
|
||||||
qDebug() << "Loading local file" << filename;
|
qDebug() << "Loading local file" << filename;
|
||||||
@ -166,8 +181,7 @@ SongLoader::Result SongLoader::LoadLocal(const QString& filename, bool block,
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
foreach (const Song& song, song_list) {
|
||||||
foreach(const Song& song, song_list) {
|
|
||||||
if (song.is_valid())
|
if (song.is_valid())
|
||||||
songs_ << song;
|
songs_ << song;
|
||||||
}
|
}
|
||||||
@ -175,6 +189,28 @@ SongLoader::Result SongLoader::LoadLocal(const QString& filename, bool block,
|
|||||||
return Success;
|
return Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SongLoader::EffectiveSongsLoad() {
|
||||||
|
for (int i = 0; i < songs_.size(); i++) {
|
||||||
|
Song& song = songs_[i];
|
||||||
|
QString filename = song.filename();
|
||||||
|
QFileInfo info(filename);
|
||||||
|
|
||||||
|
LibraryQuery query;
|
||||||
|
query.SetColumnSpec("%songs_table.ROWID, " + Song::kColumnSpec);
|
||||||
|
query.AddWhere("filename", info.canonicalFilePath());
|
||||||
|
|
||||||
|
if (library_->ExecQuery(&query) && query.Next()) {
|
||||||
|
// we may have many results when the file has many sections
|
||||||
|
do {
|
||||||
|
song.InitFromQuery(query);
|
||||||
|
} while(query.Next());
|
||||||
|
} else {
|
||||||
|
// it's a normal media file
|
||||||
|
song.InitFromFile(filename, -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void SongLoader::LoadPlaylistAndEmit(ParserBase* parser, const QString& filename) {
|
void SongLoader::LoadPlaylistAndEmit(ParserBase* parser, const QString& filename) {
|
||||||
LoadPlaylist(parser, filename);
|
LoadPlaylist(parser, filename);
|
||||||
emit LoadFinished(true);
|
emit LoadFinished(true);
|
||||||
@ -209,8 +245,7 @@ void SongLoader::LoadLocalDirectory(const QString& filename) {
|
|||||||
QDirIterator::Subdirectories);
|
QDirIterator::Subdirectories);
|
||||||
|
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
// This is in another thread so we can do blocking calls.
|
LoadLocalPartial(it.next());
|
||||||
LoadLocal(it.next(), true, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
qStableSort(songs_.begin(), songs_.end(), CompareSongs);
|
qStableSort(songs_.begin(), songs_.end(), CompareSongs);
|
||||||
|
@ -54,6 +54,9 @@ public:
|
|||||||
|
|
||||||
Result Load(const QUrl& url);
|
Result Load(const QUrl& url);
|
||||||
|
|
||||||
|
// For async load, to effectively load songs
|
||||||
|
void EffectiveSongsLoad();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void LoadFinished(bool success);
|
void LoadFinished(bool success);
|
||||||
|
|
||||||
@ -70,6 +73,7 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
Result LoadLocal(const QString& filename, bool block = false, bool ignore_playlists = false);
|
Result LoadLocal(const QString& filename, bool block = false, bool ignore_playlists = false);
|
||||||
|
Result LoadLocalPartial(const QString& filename);
|
||||||
void LoadLocalDirectory(const QString& filename);
|
void LoadLocalDirectory(const QString& filename);
|
||||||
void LoadPlaylist(ParserBase* parser, const QString& filename);
|
void LoadPlaylist(ParserBase* parser, const QString& filename);
|
||||||
void LoadLocalDirectoryAndEmit(const QString& filename);
|
void LoadLocalDirectoryAndEmit(const QString& filename);
|
||||||
|
@ -952,6 +952,38 @@ void Playlist::InsertRadioStations(const RadioModel* model,
|
|||||||
InsertItems(playlist_items, pos, play_now, enqueue);
|
InsertItems(playlist_items, pos, play_now, enqueue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Playlist::UpdateItems(const SongList& songs) {
|
||||||
|
qDebug() << "Updating playlist with new tracks' info";
|
||||||
|
foreach (const Song& song, songs) {
|
||||||
|
// Update current items list
|
||||||
|
for (int i=0; i<items_.size(); i++) {
|
||||||
|
PlaylistItemPtr item = items_[i];
|
||||||
|
if (item->Metadata().filename() == song.filename()) {
|
||||||
|
PlaylistItemPtr new_item;
|
||||||
|
if (song.id() == -1) {
|
||||||
|
new_item = PlaylistItemPtr(new SongPlaylistItem(song));
|
||||||
|
} else {
|
||||||
|
new_item = PlaylistItemPtr(new LibraryPlaylistItem(song));
|
||||||
|
library_items_by_id_.insertMulti(song.id(), new_item);
|
||||||
|
}
|
||||||
|
items_[i] = new_item;
|
||||||
|
emit dataChanged(index(i, 0), index(i, ColumnCount-1));
|
||||||
|
// Also update undo actions
|
||||||
|
for (int i=0; i<undo_stack_->count(); i++) {
|
||||||
|
QUndoCommand *undo_action = const_cast<QUndoCommand*>(undo_stack_->command(i));
|
||||||
|
PlaylistUndoCommands::InsertItems *undo_action_insert =
|
||||||
|
dynamic_cast<PlaylistUndoCommands::InsertItems*>(undo_action);
|
||||||
|
if (undo_action_insert) {
|
||||||
|
bool found_and_updated = undo_action_insert->UpdateItem(new_item);
|
||||||
|
if (found_and_updated) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QMimeData* Playlist::mimeData(const QModelIndexList& indexes) const {
|
QMimeData* Playlist::mimeData(const QModelIndexList& indexes) const {
|
||||||
QMimeData* data = new QMimeData;
|
QMimeData* data = new QMimeData;
|
||||||
|
|
||||||
|
@ -211,6 +211,7 @@ class Playlist : public QAbstractListModel {
|
|||||||
void InsertUrls (const QList<QUrl>& urls, int pos = -1, bool play_now = false, bool enqueue = false);
|
void InsertUrls (const QList<QUrl>& urls, int pos = -1, bool play_now = false, bool enqueue = false);
|
||||||
// Removes items with given indices from the playlist. This operation is not undoable.
|
// Removes items with given indices from the playlist. This operation is not undoable.
|
||||||
void RemoveItemsWithoutUndo (const QList<int>& indices);
|
void RemoveItemsWithoutUndo (const QList<int>& indices);
|
||||||
|
void UpdateItems (const SongList& songs);
|
||||||
|
|
||||||
// If this playlist contains the current item, this method will apply the "valid" flag on it.
|
// If this playlist contains the current item, this method will apply the "valid" flag on it.
|
||||||
// If the "valid" flag is false, the song will be greyed out. Otherwise the grey color will
|
// If the "valid" flag is false, the song will be greyed out. Otherwise the grey color will
|
||||||
|
@ -47,6 +47,17 @@ void InsertItems::undo() {
|
|||||||
playlist_->RemoveItemsWithoutUndo(start, items_.count());
|
playlist_->RemoveItemsWithoutUndo(start, items_.count());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool InsertItems::UpdateItem(const PlaylistItemPtr& updated_item) {
|
||||||
|
for (int i=0; i<items_.size(); i++) {
|
||||||
|
PlaylistItemPtr item = items_[i];
|
||||||
|
if (item->Metadata().filename() == updated_item->Metadata().filename()) {
|
||||||
|
items_[i] = updated_item;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
RemoveItems::RemoveItems(Playlist *playlist, int pos, int count)
|
RemoveItems::RemoveItems(Playlist *playlist, int pos, int count)
|
||||||
: Base(playlist)
|
: Base(playlist)
|
||||||
|
@ -47,6 +47,11 @@ namespace PlaylistUndoCommands {
|
|||||||
|
|
||||||
void undo();
|
void undo();
|
||||||
void redo();
|
void redo();
|
||||||
|
// When load is async, items have already been pushed, so we need to update them.
|
||||||
|
// This function try to find the equivalent item, and replace it with the
|
||||||
|
// new (completely loaded) one.
|
||||||
|
// return true if the was found (and updated), false otherwise
|
||||||
|
bool UpdateItem(const PlaylistItemPtr& updated_item);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PlaylistItemList items_;
|
PlaylistItemList items_;
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <QtConcurrentRun>
|
||||||
|
|
||||||
#include "playlist.h"
|
#include "playlist.h"
|
||||||
#include "songloaderinserter.h"
|
#include "songloaderinserter.h"
|
||||||
#include "core/songloader.h"
|
#include "core/songloader.h"
|
||||||
@ -34,6 +36,7 @@ SongLoaderInserter::SongLoaderInserter(
|
|||||||
|
|
||||||
SongLoaderInserter::~SongLoaderInserter() {
|
SongLoaderInserter::~SongLoaderInserter() {
|
||||||
qDeleteAll(pending_);
|
qDeleteAll(pending_);
|
||||||
|
qDeleteAll(pending_async_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SongLoaderInserter::Load(Playlist *destination,
|
void SongLoaderInserter::Load(Playlist *destination,
|
||||||
@ -84,21 +87,44 @@ void SongLoaderInserter::PendingLoadFinished(bool success) {
|
|||||||
if (!loader || !pending_.contains(loader))
|
if (!loader || !pending_.contains(loader))
|
||||||
return;
|
return;
|
||||||
pending_.remove(loader);
|
pending_.remove(loader);
|
||||||
|
pending_async_.insert(loader);
|
||||||
|
|
||||||
if (success)
|
if (success)
|
||||||
songs_ << loader->songs();
|
songs_ << loader->songs();
|
||||||
else
|
else
|
||||||
emit Error(tr("Error loading %1").arg(loader->url().toString()));
|
emit Error(tr("Error loading %1").arg(loader->url().toString()));
|
||||||
|
|
||||||
loader->deleteLater();
|
|
||||||
|
|
||||||
task_manager_->SetTaskProgress(async_load_id_, ++async_progress_);
|
task_manager_->SetTaskProgress(async_load_id_, ++async_progress_);
|
||||||
if (pending_.isEmpty()) {
|
if (pending_.isEmpty()) {
|
||||||
task_manager_->SetTaskFinished(async_load_id_);
|
task_manager_->SetTaskFinished(async_load_id_);
|
||||||
Finished();
|
async_progress_ = 0;
|
||||||
|
async_load_id_ = task_manager_->StartTask(tr("Loading tracks info"));
|
||||||
|
task_manager_->SetTaskProgress(async_load_id_, async_progress_, pending_async_.count());
|
||||||
|
PartiallyFinished();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SongLoaderInserter::PartiallyFinished() {
|
||||||
|
// Insert songs (that haven't been completelly loaded) to allow user to see
|
||||||
|
// and playing them while not loaded completely
|
||||||
|
if (destination_) {
|
||||||
|
destination_->InsertSongsOrLibraryItems(songs_, row_, play_now_, enqueue_);
|
||||||
|
}
|
||||||
|
QtConcurrent::run(this, &SongLoaderInserter::EffectiveLoad);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SongLoaderInserter::EffectiveLoad() {
|
||||||
|
foreach (SongLoader* loader, pending_async_) {
|
||||||
|
loader->EffectiveSongsLoad();
|
||||||
|
task_manager_->SetTaskProgress(async_load_id_, ++async_progress_);
|
||||||
|
if(destination_) {
|
||||||
|
destination_->UpdateItems(loader->songs());
|
||||||
|
}
|
||||||
|
loader->deleteLater();
|
||||||
|
}
|
||||||
|
task_manager_->SetTaskFinished(async_load_id_);
|
||||||
|
}
|
||||||
|
|
||||||
void SongLoaderInserter::Finished() {
|
void SongLoaderInserter::Finished() {
|
||||||
if (destination_) {
|
if (destination_) {
|
||||||
destination_->InsertSongsOrLibraryItems(songs_, row_, play_now_, enqueue_);
|
destination_->InsertSongsOrLibraryItems(songs_, row_, play_now_, enqueue_);
|
||||||
|
@ -48,6 +48,8 @@ private slots:
|
|||||||
void DestinationDestroyed();
|
void DestinationDestroyed();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void PartiallyFinished();
|
||||||
|
void EffectiveLoad();
|
||||||
void Finished();
|
void Finished();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -61,6 +63,7 @@ private:
|
|||||||
SongList songs_;
|
SongList songs_;
|
||||||
|
|
||||||
QSet<SongLoader*> pending_;
|
QSet<SongLoader*> pending_;
|
||||||
|
QSet<SongLoader*> pending_async_;
|
||||||
int async_load_id_;
|
int async_load_id_;
|
||||||
int async_progress_;
|
int async_progress_;
|
||||||
LibraryBackendInterface* library_;
|
LibraryBackendInterface* library_;
|
||||||
|
@ -325,6 +325,7 @@ void SongLoaderTest::LoadLocalDirectory(const QString &filename) {
|
|||||||
QObject::connect(loader_.get(), SIGNAL(LoadFinished(bool)),
|
QObject::connect(loader_.get(), SIGNAL(LoadFinished(bool)),
|
||||||
&loop, SLOT(quit()));
|
&loop, SLOT(quit()));
|
||||||
loop.exec(QEventLoop::ExcludeUserInputEvents);
|
loop.exec(QEventLoop::ExcludeUserInputEvents);
|
||||||
|
loader_.get()->EffectiveSongsLoad();
|
||||||
|
|
||||||
// Check the signal was emitted with Success
|
// Check the signal was emitted with Success
|
||||||
ASSERT_EQ(1, spy.count());
|
ASSERT_EQ(1, spy.count());
|
||||||
|
Loading…
x
Reference in New Issue
Block a user