mirror of
https://github.com/clementine-player/Clementine
synced 2025-01-31 03:27:40 +01:00
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
|
||||
}
|
||||
|
||||
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;
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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_;
|
||||
|
@ -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_);
|
||||
|
@ -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_;
|
||||
|
@ -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());
|
||||
|
Loading…
x
Reference in New Issue
Block a user