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:
Arnaud Bienner 2011-04-16 14:04:15 +00:00
parent 23c744f41a
commit 65ef047c38
11 changed files with 144 additions and 7 deletions

View File

@ -560,6 +560,24 @@ void Song::InitFromQuery(const SqlRow& q, int col) {
#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
void Song::InitFromLastFM(const lastfm::Track& track) {
d->valid_ = true;

View File

@ -128,6 +128,7 @@ class Song {
void Init(const QString& title, const QString& artist, const QString& album, qint64 beginning, qint64 end);
void InitFromFile(const QString& filename, int directory_id);
void InitFromQuery(const SqlRow& query, int col = 0);
void InitFromFilePartial(const QString& filename); // Just store the filename: incomplete but fast
#ifdef HAVE_LIBLASTFM
void InitFromLastFM(const lastfm::Track& track);
#endif

View File

@ -82,6 +82,21 @@ SongLoader::Result SongLoader::Load(const QUrl& url) {
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,
bool ignore_playlists) {
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())
songs_ << song;
}
@ -175,6 +189,28 @@ SongLoader::Result SongLoader::LoadLocal(const QString& filename, bool block,
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) {
LoadPlaylist(parser, filename);
emit LoadFinished(true);
@ -209,8 +245,7 @@ void SongLoader::LoadLocalDirectory(const QString& filename) {
QDirIterator::Subdirectories);
while (it.hasNext()) {
// This is in another thread so we can do blocking calls.
LoadLocal(it.next(), true, true);
LoadLocalPartial(it.next());
}
qStableSort(songs_.begin(), songs_.end(), CompareSongs);

View File

@ -54,6 +54,9 @@ public:
Result Load(const QUrl& url);
// For async load, to effectively load songs
void EffectiveSongsLoad();
signals:
void LoadFinished(bool success);
@ -70,6 +73,7 @@ private:
};
Result LoadLocal(const QString& filename, bool block = false, bool ignore_playlists = false);
Result LoadLocalPartial(const QString& filename);
void LoadLocalDirectory(const QString& filename);
void LoadPlaylist(ParserBase* parser, const QString& filename);
void LoadLocalDirectoryAndEmit(const QString& filename);

View File

@ -952,6 +952,38 @@ void Playlist::InsertRadioStations(const RadioModel* model,
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* data = new QMimeData;

View File

@ -211,6 +211,7 @@ class Playlist : public QAbstractListModel {
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.
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 the "valid" flag is false, the song will be greyed out. Otherwise the grey color will

View File

@ -47,6 +47,17 @@ void InsertItems::undo() {
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)
: Base(playlist)

View File

@ -47,6 +47,11 @@ namespace PlaylistUndoCommands {
void undo();
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:
PlaylistItemList items_;

View File

@ -15,6 +15,8 @@
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QtConcurrentRun>
#include "playlist.h"
#include "songloaderinserter.h"
#include "core/songloader.h"
@ -34,6 +36,7 @@ SongLoaderInserter::SongLoaderInserter(
SongLoaderInserter::~SongLoaderInserter() {
qDeleteAll(pending_);
qDeleteAll(pending_async_);
}
void SongLoaderInserter::Load(Playlist *destination,
@ -84,21 +87,44 @@ void SongLoaderInserter::PendingLoadFinished(bool success) {
if (!loader || !pending_.contains(loader))
return;
pending_.remove(loader);
pending_async_.insert(loader);
if (success)
songs_ << loader->songs();
else
emit Error(tr("Error loading %1").arg(loader->url().toString()));
loader->deleteLater();
task_manager_->SetTaskProgress(async_load_id_, ++async_progress_);
if (pending_.isEmpty()) {
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() {
if (destination_) {
destination_->InsertSongsOrLibraryItems(songs_, row_, play_now_, enqueue_);

View File

@ -48,6 +48,8 @@ private slots:
void DestinationDestroyed();
private:
void PartiallyFinished();
void EffectiveLoad();
void Finished();
private:
@ -61,6 +63,7 @@ private:
SongList songs_;
QSet<SongLoader*> pending_;
QSet<SongLoader*> pending_async_;
int async_load_id_;
int async_progress_;
LibraryBackendInterface* library_;

View File

@ -325,6 +325,7 @@ void SongLoaderTest::LoadLocalDirectory(const QString &filename) {
QObject::connect(loader_.get(), SIGNAL(LoadFinished(bool)),
&loop, SLOT(quit()));
loop.exec(QEventLoop::ExcludeUserInputEvents);
loader_.get()->EffectiveSongsLoad();
// Check the signal was emitted with Success
ASSERT_EQ(1, spy.count());