#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "taglib/tag.h" #include "taglib/audioproperties.h" #include "indexerwidget.h" #include "globals.h" IndexerWidget::IndexerWidget(QWidget *parent) : QWidget(parent), mMax(0) { //errors QGroupBox *gb1 = new QGroupBox(tr("Errors")); mError = new QTextEdit; mError->setFont(QFont("courier new")); mError->setTextColor(Qt::red); QVBoxLayout *gb1L = new QVBoxLayout; gb1L->addWidget(mError); gb1->setLayout(gb1L); //progress QLabel *l1 = new QLabel(tr("Indexing:")); l1->setFont(QFont("courier")); mProgress = new QProgressBar; QHBoxLayout *progressL = new QHBoxLayout; mProgressCount = new QLabel(tr("000000/00000")); mProgressCount->setFont(QFont("courier")); progressL->addWidget(l1); progressL->addWidget(mProgress); progressL->addWidget(mProgressCount); //reader mReader = new BeetReader; connect(mReader, &BeetReader::errorMsg, this, &IndexerWidget::addToError); connect(mReader, &BeetReader::totalCount, this, &IndexerWidget::setupProgress); connect(mReader, &BeetReader::progress, this, &IndexerWidget::progress); connect(mReader, &BeetReader::indexingDone, this, &IndexerWidget::indexingDone); //main layout QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(gb1); mainLayout->addLayout(progressL); setLayout(mainLayout); } void IndexerWidget::startIndexing(){ mError->clear(); mReader->start(); } void IndexerWidget::stopIndexing(){ mReader->cancel(); addToError(tr("Canceled!")); } void IndexerWidget::addToError(QString msg){ mError->append(msg); } void IndexerWidget::setupProgress(int max){ mProgress->reset(); mProgress->setMinimum(0); mProgress->setMaximum(max); mMax = max; } void IndexerWidget::progress(int cur){ mProgress->setValue(cur); QString p = QString("%1/%2").arg(cur, 6, 10, QChar('0')).arg(mMax, 6, 10, QChar('0')); mProgressCount->setText(p); } BeetReader::BeetReader() : mCanceled(false){ mDb = QSqlDatabase::database("beetplayerdb"); mInsertArtistsQ = new QSqlQuery(mDb); mInsertArtistsQ->prepare("INSERT INTO artists (tartists_name) VALUES(:name)"); mCurArtistQ = new QSqlQuery(mDb); mCurArtistQ->prepare("SELECT currval('artist_iartists_id__seq')"); mInsertGenresQ = new QSqlQuery(mDb); mInsertGenresQ->prepare("INSERT INTO genres (tgenres_name) VALUES(:name)"); mCurGenresQ = new QSqlQuery(mDb); mCurGenresQ->prepare("SELECT currval('genres_igenre_id__seq')"); mInsertAlbumQ = new QSqlQuery(mDb); mInsertAlbumQ->prepare("INSERT INTO albums(talbum_name, siyear, dadded) VALUES(:name, :year, :added)"); mCurAlbumQ = new QSqlQuery(mDb); mCurAlbumQ->prepare("SELECT currval('albums_ialbums_id__seq')"); mInsertSongQ = new QSqlQuery(mDb); mInsertSongQ->prepare("INSERT INTO songs (ialbums_id, sipos, ttitle, iartists_id, igenres_id, tfullpath, ilength) VALUES(:aid, :pos, :title, :iartistid, :igenid, :tfp, :len)"); } void BeetReader::run(){ clearAll(); QSettings s; bool useBeet = s.value("usebeet", true).toBool(); QStringList files; if(useBeet){ QProcess lister; lister.start("beet", QStringList() << "ls" << "-p"); qApp->setOverrideCursor(QCursor(Qt::WaitCursor)); emit errorMsg(tr("Waiting for beet ls -p")); lister.waitForStarted(); lister.waitForFinished(); qApp->restoreOverrideCursor(); emit errorMsg(tr("Done Waiting - Starting some serious work!")); QByteArray lOut = lister.readAllStandardOutput(); QList filesBA = lOut.split('\n'); for(const QByteArray &ba : filesBA){ files << QString(ba); } }else{ QString dir = s.value("scandir").toString(); QDirIterator dIt(dir, QDirIterator::Subdirectories); while(dIt.hasNext()){ QString f = dIt.next(); if(QFileInfo(f).isFile()){ files << f; } } emit errorMsg(tr("Gathered %1 files in %2").arg(QString::number(files.count())).arg(dir)); } int total = files.size(); emit totalCount(total); int ctr = 1; emit errorMsg(tr("Indexing...")); for(const QString &s : files){ //fetch data from file TagLib::FileRef file(QString(s).toUtf8()); if(file.isNull()){ QString fn(s); if(!fn.isEmpty()){ QString error = QString(tr("IsNull: %1")).arg(fn); emit errorMsg(error); } continue; } QString artist = toQString(file.tag()->artist()); int featIdx = artist.indexOf(" feat."); if(featIdx > -1){ artist = artist.left(featIdx); } QString album = toQString(file.tag()->album()); QString title = toQString(file.tag()->title()); QString genre = toQString(file.tag()->genre()); int length = file.audioProperties()->lengthInSeconds(); uint track = file.tag()->track(); uint year = file.tag()->year(); QString parentDir = QFileInfo(QString(s)).absolutePath(); QFileInfo albumDir(parentDir); QDateTime dateAdded = albumDir.lastModified(); //write to database int artistId = doArtist(artist); int genreId = doGenre(genre); int albumId = doAlbum(album, year, dateAdded); doSong(title, track, albumId, genreId, artistId, s, length); if(ctr % 100 == 0){ emit progress(ctr); } mCancelMx.lock(); if(mCanceled == true){ mCanceled = false; mCancelMx.unlock(); return; } mCancelMx.unlock(); ++ctr; } emit progress(total); emit errorMsg(tr("Indexing done!")); emit indexingDone(); } void BeetReader::cancel(){ mCancelMx.lock(); mCanceled = true; mCancelMx.unlock(); } QString BeetReader::toQString(TagLib::String string){ QString retval = QString::fromStdWString(string.toWString()); retval = retval.simplified().toLower(); return retval; } void BeetReader::clearAll(){ QSqlQuery q1("DELETE FROM songs", mDb); QSqlQuery q2("DELETE FROM albums", mDb); QSqlQuery q3("DELETE FROM genres", mDb); QSqlQuery q4("DELETE FROM artists", mDb); mArtistH.clear(); mAlbumH.clear(); mGenreH.clear(); QSqlQuery q5("INSERT INTO genres VALUES(-1, 'none')", mDb); q5.exec(); emit cleared(); } int BeetReader::doArtist(QString name){ if(mArtistH.contains(name)){ return mArtistH.value(name); } mDb.transaction(); mInsertArtistsQ->bindValue(":name", name); mInsertArtistsQ->exec(); mCurArtistQ->exec(); int retval = -1; while(mCurArtistQ->next()){ retval = mCurArtistQ->value(0).toInt(); } mArtistH.insert(name, retval); mDb.commit(); return retval; } int BeetReader::doGenre(QString name){ if(mGenreH.contains(name)){ return mGenreH.value(name); } mDb.transaction(); mInsertGenresQ->bindValue(":name", name); mInsertGenresQ->exec(); mCurGenresQ->exec(); int retval = -1; mCurGenresQ->exec(); while(mCurGenresQ->next()){ retval = mCurGenresQ->value(0).toInt(); } mGenreH.insert(name, retval); mDb.commit(); return retval; } int BeetReader::doAlbum(QString name, uint year, QDateTime created){ QString key = name; if(mAlbumH.contains(key)){ return mAlbumH.value(key); } mDb.transaction(); mInsertAlbumQ->bindValue(":name", name); mInsertAlbumQ->bindValue(":year", year); mInsertAlbumQ->bindValue(":added", created.date()); mInsertAlbumQ->exec(); int retval = -1; mCurAlbumQ->exec(); while(mCurAlbumQ->next()){ retval = mCurAlbumQ->value(0).toInt(); } mAlbumH.insert(key, retval); mDb.commit(); return retval; } void BeetReader::doSong(QString title, uint pos, int album, int genre, int artist, QString fullpath, int length){ mDb.transaction(); mInsertSongQ->bindValue(":title", title); mInsertSongQ->bindValue(":pos", pos); mInsertSongQ->bindValue(":aid", album); mInsertSongQ->bindValue(":iartistid", artist); mInsertSongQ->bindValue(":igenid", genre); mInsertSongQ->bindValue(":tfp", fullpath); mInsertSongQ->bindValue(":len", length); mInsertSongQ->exec(); mDb.commit(); }