diff --git a/babe.cpp b/babe.cpp index 5e1a830..1473ba1 100644 --- a/babe.cpp +++ b/babe.cpp @@ -1,612 +1,555 @@ #include "babe.h" #include "db/collectionDB.h" #include "db/conthread.h" #include "settings/BabeSettings.h" #include "pulpo/pulpo.h" #include #include #include #include #include #include #include #include #include "services/local/taginfo.h" //#include "Python.h" #include #if (defined (Q_OS_LINUX) && !defined (Q_OS_ANDROID)) #include #include "kde/notify.h" #endif using namespace BAE; Babe::Babe(QObject *parent) : QObject(parent) { this->settings = new BabeSettings(this); /*use another thread for the db to perfom heavy dutty actions*/ this->thread = new ConThread; this->pulpo = new Pulpo; this->db = CollectionDB::getInstance(); connect(pulpo, &Pulpo::infoReady, [&](const FMH::MODEL &track, const PULPO::RESPONSE &res) { qDebug()<<"GOT THE LYRICS"; if(!res[PULPO::ONTOLOGY::TRACK][PULPO::INFO::LYRICS].isEmpty()) { auto lyrics = res[PULPO::ONTOLOGY::TRACK][PULPO::INFO::LYRICS][PULPO::CONTEXT::LYRIC].toString(); this->db->lyricsTrack(track, lyrics); emit this->trackLyricsReady(lyrics, track[FMH::MODEL_KEY::URL]); } }); connect(settings, &BabeSettings::refreshTables, [this](int size) { emit this->refreshTables(size); }); connect(settings, &BabeSettings::refreshATable, [this](BAE::TABLE table) { switch(table) { case BAE::TABLE::TRACKS: emit this->refreshTracks(); break; case BAE::TABLE::ALBUMS: emit this->refreshAlbums(); break; case BAE::TABLE::ARTISTS: emit this->refreshArtists(); break; default: break; } }); /*The local streaming connection still unfinished*/ connect(&link, &Linking::parseAsk, this, &Babe::linkDecoder); // connect(&link, &Linking::bytesFrame, [this](QByteArray array) // { // this->player.appendBuffe(array); // }); // connect(&link, &Linking::arrayReady, [this](QByteArray array) // { // qDebug()<<"trying to play the array"; // Q_UNUSED(array); // this->player.playBuffer(); // }); #if (defined (Q_OS_LINUX) && !defined (Q_OS_ANDROID)) this->nof = new Notify(this); connect(this->nof,&Notify::babeSong,[this]() { emit this->babeIt(); }); connect(this->nof,&Notify::skipSong,[this]() { emit this->skipTrack(); }); #elif defined (Q_OS_ANDROID) #endif } Babe::~Babe() { delete this->thread; } //void Babe::runPy() //{ // QFile cat (BAE::CollectionDBPath+"cat"); // qDebug()<db->getDBDataQML(queryTxt); } QVariantList Babe::getList(const QStringList &urls) { return Babe::transformData(this->db->getDBData(urls)); } void Babe::set(const QString &table, const QVariantList &wheres) { this->thread->start(table, wheres); } void Babe::trackPlaylist(const QStringList &urls, const QString &playlist) { QVariantList data; for(auto url : urls) { QVariantMap map {{FMH::MODEL_NAME[FMH::MODEL_KEY::PLAYLIST],playlist}, {FMH::MODEL_NAME[FMH::MODEL_KEY::URL],url}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ADDDATE],QDateTime::currentDateTime()}}; data << map; } this->thread->start(BAE::TABLEMAP[TABLE::TRACKS_PLAYLISTS], data); } void Babe::trackLyrics(const QString &url) { auto track = this->db->getDBData(QString("SELECT * FROM %1 WHERE %2 = \"%3\"").arg(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::URL], url)); if(track.isEmpty()) return; qDebug()<< "Getting lyrics for track"<< track.first()[FMH::MODEL_KEY::TITLE]; if(!track.first()[FMH::MODEL_KEY::LYRICS].isEmpty() && track.first()[FMH::MODEL_KEY::LYRICS] != SLANG[W::NONE]) emit this->trackLyricsReady(track.first()[FMH::MODEL_KEY::LYRICS], url); else this->fetchTrackLyrics(track.first()); } bool Babe::trackBabe(const QString &path) { auto babe = this->db->getDBData(QString("SELECT %1 FROM %2 WHERE %3 = \"%4\"").arg(FMH::MODEL_NAME[FMH::MODEL_KEY::FAV], TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::URL],path)); if(!babe.isEmpty()) return babe.first()[FMH::MODEL_KEY::FAV].toInt(); return false; } QString Babe::artistArt(const QString &artist) { auto artwork = this->db->getDBData(QString("SELECT %1 FROM %2 WHERE %3 = \"%4\"").arg(FMH::MODEL_NAME[FMH::MODEL_KEY::ARTWORK], TABLEMAP[TABLE::ARTISTS], FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST],artist)); if(!artwork.isEmpty()) if(!artwork.first()[FMH::MODEL_KEY::ARTWORK].isEmpty() && artwork.first()[FMH::MODEL_KEY::ARTWORK] != SLANG[W::NONE]) return artwork.first()[FMH::MODEL_KEY::ARTWORK]; return ""; } QString Babe::artistWiki(const QString &artist) { auto wiki = this->db->getDBData(QString("SELECT %1 FROM %2 WHERE %3 = \"%4\"").arg(FMH::MODEL_NAME[FMH::MODEL_KEY::WIKI], TABLEMAP[TABLE::ARTISTS], FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST],artist)); if(!wiki.isEmpty()) return wiki.first()[FMH::MODEL_KEY::WIKI]; return ""; } QString Babe::albumArt(const QString &album, const QString &artist) { auto queryStr = QString("SELECT %1 FROM %2 WHERE %3 = \"%4\" AND %5 = \"%6\"").arg(FMH::MODEL_NAME[FMH::MODEL_KEY::ARTWORK], TABLEMAP[TABLE::ALBUMS], FMH::MODEL_NAME[FMH::MODEL_KEY::ALBUM], album, FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], artist); auto albumCover = this->db->getDBData(queryStr); if(!albumCover.isEmpty()) if(!albumCover.first()[FMH::MODEL_KEY::ARTWORK].isEmpty() && albumCover.first()[FMH::MODEL_KEY::ARTWORK] != SLANG[W::NONE]) return albumCover.first()[FMH::MODEL_KEY::ARTWORK]; return ""; } void Babe::fetchTrackLyrics(FMH::MODEL &song) { pulpo->registerServices({SERVICES::LyricWikia, SERVICES::Genius}); pulpo->setOntology(PULPO::ONTOLOGY::TRACK); pulpo->setInfo(PULPO::INFO::LYRICS); qDebug()<<"STARTED FETCHING LYRICS"; pulpo->feed(song, PULPO::RECURSIVE::OFF); qDebug()<<"DONE FETCHING LYRICS"; } void Babe::linkDecoder(QString json) { qDebug()<<"DECODING LINKER MSG"<(code)) { case LINK::CODE::CONNECTED : { this->link.deviceName = msg; emit this->link.serverConReady(msg); break; } case LINK::CODE::QUERY : case LINK::CODE::FILTER : case LINK::CODE::PLAYLISTS : { auto res = this->db->getDBDataQML(msg); link.sendToClient(link.packResponse(static_cast(code), res)); break; } case LINK::CODE::SEARCHFOR : { - auto res = this->searchFor(msg.split(",")); - link.sendToClient(link.packResponse(static_cast(code), res)); +// auto res = this->searchFor(msg.split(",")); +// link.sendToClient(link.packResponse(static_cast(code), res)); break; } case LINK::CODE::PLAY : { QFile file(msg); // sound dir file.open(QIODevice::ReadOnly); QByteArray arr = file.readAll(); qDebug()<<"Preparing track array"<db->getDBData(queryStr); if(!wiki.isEmpty()) return wiki.first()[FMH::MODEL_KEY::WIKI]; return ""; } QVariantList Babe::getFolders() { auto sources = this->db->getDBData("select * from sources"); QVariantList res; for(auto item : sources) res << FMH::getDirInfo(item[FMH::MODEL_KEY::URL]); qDebug()<<"FOLDERS:"<< res; return res; } +QStringList Babe::getSourceFolders() +{ + return this->db->getSourcesFolders(); +} + void Babe::notify(const QString &title, const QString &body) { #if (defined (Q_OS_LINUX) && !defined (Q_OS_ANDROID)) this->nof->notify(title, body); #elif defined (Q_OS_ANDROID) Q_UNUSED(title); Q_UNUSED(body); #endif } void Babe::notifySong(const QString &url) { #if (defined (Q_OS_LINUX) && !defined (Q_OS_ANDROID)) if(!this->db->check_existance(BAE::TABLEMAP[BAE::TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::URL], url)) return; auto query = QString("select t.*, al.artwork from tracks t inner join albums al on al.album = t.album and al.artist = t.artist where url = \"%1\"").arg(url); auto track = this->db->getDBData(query); this->nof->notifySong(track.first()); #else Q_UNUSED(url); #endif } void Babe::scanDir(const QString &url) { emit this->settings->collectionPathChanged({url}); } void Babe::brainz(const bool &on) { qDebug()<< "Changed vvae brainz state"<< on; this->settings->startBrainz(on); } bool Babe::brainzState() { return loadSetting("AUTO", "BRAINZ", false).toBool(); } void Babe::refreshCollection() { this->settings->refreshCollection(); } void Babe::getYoutubeTrack(const QString &message) { this->settings->fetchYoutubeTrack(message); } QVariant Babe::loadSetting(const QString &key, const QString &group, const QVariant &defaultValue) { return BAE::loadSettings(key, group, defaultValue); } void Babe::saveSetting(const QString &key, const QVariant &value, const QString &group) { BAE::saveSettings(key, value, group); } void Babe::savePlaylist(const QStringList &list) { BAE::saveSettings("PLAYLIST", list, "MAINWINDOW"); } QStringList Babe::lastPlaylist() { return BAE::loadSettings("PLAYLIST","MAINWINDOW",{}).toStringList(); } void Babe::savePlaylistPos(const int &pos) { BAE::saveSettings("PLAYLIST_POS", pos, "MAINWINDOW"); } int Babe::lastPlaylistPos() { return BAE::loadSettings("PLAYLIST_POS","MAINWINDOW",QVariant(0)).toInt(); } bool Babe::fileExists(const QString &url) { return BAE::fileExists(url); } void Babe::showFolder(const QStringList &urls) { for(auto url : urls) QDesktopServices::openUrl(QUrl::fromLocalFile(QFileInfo(url).dir().absolutePath())); } QString Babe::babeColor() { return "#f84172"; } void Babe::openUrls(const QStringList &urls) { if(urls.isEmpty()) return; QVariantList data; TagInfo info; for(auto url : urls) if(this->db->check_existance(BAE::TABLEMAP[BAE::TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::URL], url)) data << this->getList({url}).first().toMap(); else { if(info.feed(url)) { auto album = BAE::fixString(info.getAlbum()); auto track= info.getTrack(); auto title = BAE::fixString(info.getTitle()); /* to fix*/ auto artist = BAE::fixString(info.getArtist()); auto genre = info.getGenre(); auto sourceUrl = QFileInfo(url).dir().path(); auto duration = info.getDuration(); auto year = info.getYear(); data << QVariantMap({ {FMH::MODEL_NAME[FMH::MODEL_KEY::URL], url}, {FMH::MODEL_NAME[FMH::MODEL_KEY::TRACK], QString::number(track)}, {FMH::MODEL_NAME[FMH::MODEL_KEY::TITLE], title}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], artist}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ALBUM], album}, {FMH::MODEL_NAME[FMH::MODEL_KEY::DURATION],QString::number(duration)}, {FMH::MODEL_NAME[FMH::MODEL_KEY::GENRE], genre}, {FMH::MODEL_NAME[FMH::MODEL_KEY::SOURCE], sourceUrl}, {FMH::MODEL_NAME[FMH::MODEL_KEY::FAV],"0"}, {FMH::MODEL_NAME[FMH::MODEL_KEY::RELEASEDATE], QString::number(year)} }); } } qDebug()<< data; emit this->openFiles(data); } QString Babe::moodColor(const int &pos) { if(pos < BAE::MoodColors.size()) return BAE::MoodColors.at(pos); else return ""; } QString Babe::homeDir() { return BAE::HomePath; } QString Babe::musicDir() { return BAE::MusicPath; } QStringList Babe::defaultSources() { return BAE::defaultSources; } QString Babe::loadCover(const QString &url) { auto map = this->db->getDBData(QStringList() << url); if(map.isEmpty()) return ""; auto track = map.first(); auto artist = track[FMH::MODEL_KEY::ARTIST]; auto album = track[FMH::MODEL_KEY::ALBUM]; auto title = track[FMH::MODEL_KEY::TITLE]; auto artistImg = this->artistArt(artist); auto albumImg = this->albumArt(album, artist); if(!albumImg.isEmpty() && albumImg != BAE::SLANG[W::NONE]) return albumImg; else if (!artistImg.isEmpty() && artistImg != BAE::SLANG[W::NONE]) return artistImg; else return this->fetchCoverArt(track); } -QVariantList Babe::searchFor(const QStringList &queries) -{ - QVariantList mapList; - bool hasKey = false; - for(auto searchQuery : queries) - { - if(searchQuery.contains(BAE::SearchTMap[BAE::SearchT::LIKE]+":") || searchQuery.startsWith("#")) - { - if(searchQuery.startsWith("#")) - searchQuery=searchQuery.replace("#","").trimmed(); - else - searchQuery=searchQuery.replace(BAE::SearchTMap[BAE::SearchT::LIKE]+":","").trimmed(); - - - searchQuery = searchQuery.trimmed(); - if(!searchQuery.isEmpty()) - { - mapList += this->db->getSearchedTracks(FMH::MODEL_KEY::WIKI, searchQuery); - mapList += this->db->getSearchedTracks(FMH::MODEL_KEY::TAG, searchQuery); - mapList += this->db->getSearchedTracks(FMH::MODEL_KEY::LYRICS, searchQuery); - } - - }else if(searchQuery.contains((BAE::SearchTMap[BAE::SearchT::SIMILAR]+":"))) - { - searchQuery=searchQuery.replace(BAE::SearchTMap[BAE::SearchT::SIMILAR]+":","").trimmed(); - searchQuery=searchQuery.trimmed(); - if(!searchQuery.isEmpty()) - mapList += this->db->getSearchedTracks(FMH::MODEL_KEY::TAG, searchQuery); - - }else - { - FMH::MODEL_KEY key; - - QHashIterator k(FMH::MODEL_NAME); - while (k.hasNext()) - { - k.next(); - if(searchQuery.contains(QString(k.value()+":"))) - { - hasKey=true; - key=k.key(); - searchQuery = searchQuery.replace(k.value()+":","").trimmed(); - } - } - - searchQuery = searchQuery.trimmed(); - - if(!searchQuery.isEmpty()) - { - if(hasKey) - mapList += this->db->getSearchedTracks(key, searchQuery); - else - { - auto queryTxt = QString("SELECT t.*, al.artwork FROM tracks t INNER JOIN albums al ON t.album = al.album AND t.artist = al.artist WHERE t.title LIKE \"%"+searchQuery+"%\" OR t.artist LIKE \"%"+searchQuery+"%\" OR t.album LIKE \"%"+searchQuery+"%\"OR t.genre LIKE \"%"+searchQuery+"%\"OR t.url LIKE \"%"+searchQuery+"%\" ORDER BY strftime(\"%s\", t.addDate) desc LIMIT 1000"); - mapList += this->db->getDBDataQML(queryTxt); - } - } - } - } - - return mapList; -} QString Babe::fetchCoverArt(FMH::MODEL &song) { Pulpo pulpo; if(BAE::artworkCache(song, FMH::MODEL_KEY::ALBUM)) return song[FMH::MODEL_KEY::ARTWORK]; if(BAE::artworkCache(song, FMH::MODEL_KEY::ARTIST)) return song[FMH::MODEL_KEY::ARTWORK]; pulpo.registerServices({SERVICES::LastFm, SERVICES::Spotify}); pulpo.setOntology(PULPO::ONTOLOGY::ALBUM); pulpo.setInfo(PULPO::INFO::ARTWORK); QEventLoop loop; QTimer timer; timer.setSingleShot(true); timer.setInterval(1000); connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); connect(&pulpo, &Pulpo::infoReady, [&](const FMH::MODEL &track,const PULPO::RESPONSE &res) { Q_UNUSED(track); if(!res[PULPO::ONTOLOGY::ALBUM][PULPO::INFO::ARTWORK].isEmpty()) { auto artwork = res[PULPO::ONTOLOGY::ALBUM][PULPO::INFO::ARTWORK][PULPO::CONTEXT::IMAGE].toByteArray(); BAE::saveArt(song, artwork, BAE::CachePath); } loop.quit(); }); pulpo.feed(song, PULPO::RECURSIVE::OFF); timer.start(); loop.exec(); timer.stop(); return song[FMH::MODEL_KEY::ARTWORK]; } QVariantList Babe::transformData(const FMH::MODEL_LIST &dbList) { QVariantList res; // for(FMH::MODEL data : dbList) // { // FMH::MODEL copy = data; // res << FM::toMap(copy); // } return res; } diff --git a/babe.h b/babe.h index 6f51e39..3d76162 100644 --- a/babe.h +++ b/babe.h @@ -1,125 +1,125 @@ #ifndef BABE_H #define BABE_H #include #include #include "utils/bae.h" #include "db/collectionDB.h" #include "services/local/linking.h" #if (defined (Q_OS_LINUX) && !defined (Q_OS_ANDROID)) class Notify; #elif defined (Q_OS_ANDROID) //class NotificationClient; #endif class CollectionDB; class Pulpo; class BabeSettings; class ConThread; using namespace BAE; class Babe : public QObject { Q_OBJECT public: explicit Babe(QObject *parent = nullptr); ~Babe(); BabeSettings *settings; Linking link; // Q_INVOKABLE void runPy(); /* DATABASE INTERFACES */ Q_INVOKABLE QVariantList get(const QString &queryTxt); Q_INVOKABLE QVariantList getList(const QStringList &urls); Q_INVOKABLE void set(const QString &table, const QVariantList &wheres); Q_INVOKABLE void trackPlaylist(const QStringList &urls, const QString &playlist); Q_INVOKABLE void trackLyrics(const QString &url); Q_INVOKABLE bool trackBabe(const QString &path); Q_INVOKABLE QString artistArt(const QString &artist); Q_INVOKABLE QString albumArt(const QString &album, const QString &artist); Q_INVOKABLE QString artistWiki(const QString &artist); Q_INVOKABLE QString albumWiki(const QString &album, const QString &artist); Q_INVOKABLE QVariantList getFolders(); + Q_INVOKABLE QStringList getSourceFolders(); /* SETTINGS */ Q_INVOKABLE void scanDir(const QString &url); Q_INVOKABLE void brainz(const bool &on); Q_INVOKABLE bool brainzState(); Q_INVOKABLE void refreshCollection(); Q_INVOKABLE void getYoutubeTrack(const QString &message); /* STATIC METHODS */ Q_INVOKABLE static void saveSetting(const QString &key, const QVariant &value, const QString &group); Q_INVOKABLE static QVariant loadSetting(const QString &key, const QString &group, const QVariant &defaultValue); Q_INVOKABLE static void savePlaylist(const QStringList &list); Q_INVOKABLE static QStringList lastPlaylist(); Q_INVOKABLE static void savePlaylistPos(const int &pos); Q_INVOKABLE static int lastPlaylistPos(); Q_INVOKABLE static bool fileExists(const QString &url); Q_INVOKABLE static void showFolder(const QStringList &urls); /*COLORS*/ Q_INVOKABLE static QString babeColor(); /*UTILS*/ Q_INVOKABLE void openUrls(const QStringList &urls); Q_INVOKABLE static QString moodColor(const int &pos); Q_INVOKABLE static QString homeDir(); Q_INVOKABLE static QString musicDir(); Q_INVOKABLE static QStringList defaultSources(); /*USEFUL*/ Q_INVOKABLE QString loadCover(const QString &url); - Q_INVOKABLE QVariantList searchFor(const QStringList &queries); /*KDE*/ Q_INVOKABLE void notify(const QString &title, const QString &body); Q_INVOKABLE void notifySong(const QString &url); public slots: private: Pulpo *pulpo; ConThread *thread; CollectionDB *db; #if (defined (Q_OS_LINUX) && !defined (Q_OS_ANDROID)) Notify *nof; #elif defined (Q_OS_ANDROID) // NotificationClient *nof; #endif QString fetchCoverArt(FMH::MODEL &song); static QVariantList transformData(const FMH::MODEL_LIST &dbList); void fetchTrackLyrics(FMH::MODEL &song); void linkDecoder(QString json); signals: void refreshTables(int size); void refreshTracks(); void refreshAlbums(); void refreshArtists(); void trackLyricsReady(QString lyrics, QString url); void skipTrack(); void babeIt(); void message(QString msg); void openFiles(QVariantList tracks); }; #endif // BABE_H diff --git a/db/collectionDB.cpp b/db/collectionDB.cpp index 0dcb4b2..7b9c274 100644 --- a/db/collectionDB.cpp +++ b/db/collectionDB.cpp @@ -1,1094 +1,1092 @@ /* Babe - tiny music player Copyright (C) 2017 Camilo Higuita This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "collectionDB.h" #include #include #include #include "../utils/babeconsole.h" using namespace BAE; CollectionDB::CollectionDB(QObject *parent) : QObject(parent) {} CollectionDB::~CollectionDB() { this->m_db.close(); } CollectionDB *CollectionDB::instance = nullptr; CollectionDB *CollectionDB::getInstance() { if(!instance) { instance = new CollectionDB(); qDebug() << "getInstance(): First DBActions instance\n"; instance->init(); return instance; } else { qDebug()<< "getInstance(): previous DBActions instance\n"; return instance; } } void CollectionDB::init() { this->name = QUuid::createUuid().toString(); if(!BAE::fileExists(BAE::CollectionDBPath + BAE::DBName)) { QDir collectionDBPath_dir(BAE::CollectionDBPath); if (!collectionDBPath_dir.exists()) collectionDBPath_dir.mkpath("."); this->openDB(this->name); qDebug()<<"Collection doesn't exists, trying to create it" << BAE::CollectionDBPath + BAE::DBName; this->prepareCollectionDB(); }else this->openDB(this->name); } void CollectionDB::prepareCollectionDB() const { QSqlQuery query(this->m_db); QFile file(":/db/script.sql"); if (!file.exists()) { QString log = QStringLiteral("Fatal error on build database. The file '"); log.append(file.fileName() + QStringLiteral("' for database and tables creation query cannot be not found!")); qDebug()<getQuery(queryStr); if (!query.exec()) { qDebug()<m_db); query.prepare(sqlQueryString); int k = 0; foreach (const QVariant &value, values) query.bindValue(k++, value); return query.exec(); } bool CollectionDB::update(const QString &tableName, const FMH::MODEL &updateData, const QVariantMap &where) { if (tableName.isEmpty()) { qDebug()<getQuery(sqlQueryString); qDebug()<getQuery(queryStr); return query.exec(); } bool CollectionDB::execQuery(QSqlQuery &query) const { if(query.exec()) return true; qDebug()<<"ERROR ON EXEC QUERY"; qDebug()<getQuery(queryTxt); return query.exec(); } void CollectionDB::openDB(const QString &name) { if(!QSqlDatabase::contains(name)) { this->m_db = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), name); this->m_db.setDatabaseName(BAE::CollectionDBPath + BAE::DBName); } if (!this->m_db.isOpen()) { if(!this->m_db.open()) qDebug()<<"ERROR OPENING DB"<m_db.lastError().text()<execQuery("PRAGMA journal_mode=WAL"); } } } bool CollectionDB::addTrack(const FMH::MODEL &track) { auto url = track[FMH::MODEL_KEY::URL]; auto title = track[FMH::MODEL_KEY::TITLE]; auto artist = track[FMH::MODEL_KEY::ARTIST]; auto album = track[FMH::MODEL_KEY::ALBUM]; auto genre = track[FMH::MODEL_KEY::GENRE]; auto year = track[FMH::MODEL_KEY::RELEASEDATE]; auto sourceUrl = track[FMH::MODEL_KEY::SOURCE]; auto duration = track[FMH::MODEL_KEY::DURATION]; auto fav = track[FMH::MODEL_KEY::FAV]; auto trackNumber = track[FMH::MODEL_KEY::TRACK]; auto artwork = track[FMH::MODEL_KEY::ARTWORK].isEmpty() ? "" : track[FMH::MODEL_KEY::ARTWORK]; auto artistTrack = track; BAE::artworkCache(artistTrack, FMH::MODEL_KEY::ARTIST); auto artistArtwork = artistTrack[FMH::MODEL_KEY::ARTWORK]; /* first needs to insert the source, album and artist*/ QVariantMap sourceMap {{FMH::MODEL_NAME[FMH::MODEL_KEY::URL], sourceUrl}, {FMH::MODEL_NAME[FMH::MODEL_KEY::SOURCETYPE], sourceType(url)}}; insert(TABLEMAP[BAE::TABLE::SOURCES], sourceMap); QVariantMap artistMap {{FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], artist}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ARTWORK], artistArtwork}, {FMH::MODEL_NAME[FMH::MODEL_KEY::WIKI], ""}}; insert(TABLEMAP[TABLE::ARTISTS],artistMap); QVariantMap albumMap {{FMH::MODEL_NAME[FMH::MODEL_KEY::ALBUM], album}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], artist}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ARTWORK], artwork}, {FMH::MODEL_NAME[FMH::MODEL_KEY::WIKI],""}}; insert(TABLEMAP[TABLE::ALBUMS],albumMap); QVariantMap trackMap {{FMH::MODEL_NAME[FMH::MODEL_KEY::URL], url}, {FMH::MODEL_NAME[FMH::MODEL_KEY::SOURCE], sourceUrl}, {FMH::MODEL_NAME[FMH::MODEL_KEY::TRACK], trackNumber}, {FMH::MODEL_NAME[FMH::MODEL_KEY::TITLE], title}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], artist}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ALBUM], album}, {FMH::MODEL_NAME[FMH::MODEL_KEY::DURATION], duration}, {FMH::MODEL_NAME[FMH::MODEL_KEY::COUNT], 0}, {FMH::MODEL_NAME[FMH::MODEL_KEY::FAV], fav}, {FMH::MODEL_NAME[FMH::MODEL_KEY::RATE], 0}, {FMH::MODEL_NAME[FMH::MODEL_KEY::RELEASEDATE], year}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ADDDATE], QDateTime::currentDateTime()}, {FMH::MODEL_NAME[FMH::MODEL_KEY::LYRICS],""}, {FMH::MODEL_NAME[FMH::MODEL_KEY::GENRE], genre}, {FMH::MODEL_NAME[FMH::MODEL_KEY::COLOR], ""}, {FMH::MODEL_NAME[FMH::MODEL_KEY::WIKI], ""}, {FMH::MODEL_NAME[FMH::MODEL_KEY::COMMENT], ""}}; return this->insert(BAE::TABLEMAP[BAE::TABLE::TRACKS], trackMap); } bool CollectionDB::updateTrack(const FMH::MODEL &track) { if(this->check_existance(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::URL], track[FMH::MODEL_KEY::URL])) { QVariantMap artistMap {{FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], track[FMH::MODEL_KEY::ARTIST]}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ARTWORK], ""}, {FMH::MODEL_NAME[FMH::MODEL_KEY::WIKI],""}}; insert(TABLEMAP[TABLE::ARTISTS],artistMap); QVariantMap albumMap {{FMH::MODEL_NAME[FMH::MODEL_KEY::ALBUM], track[FMH::MODEL_KEY::ALBUM]}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], track[FMH::MODEL_KEY::ARTIST]}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ARTWORK], ""}, {FMH::MODEL_NAME[FMH::MODEL_KEY::WIKI],""}}; insert(TABLEMAP[TABLE::ALBUMS],albumMap); QVariantMap condition {{FMH::MODEL_NAME[FMH::MODEL_KEY::URL], track[FMH::MODEL_KEY::URL]}}; if(this->update(TABLEMAP[TABLE::TRACKS], track, condition)) if(cleanAlbums()) cleanArtists(); return true; } return false; } bool CollectionDB::rateTrack(const QString &path, const int &value) { if(update(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::RATE], value, FMH::MODEL_NAME[FMH::MODEL_KEY::URL], path)) return true; return false; } bool CollectionDB::colorTagTrack(const QString &path, const QString &value) { if(update(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::COLOR], value, FMH::MODEL_NAME[FMH::MODEL_KEY::URL], path)) return true; return false; } bool CollectionDB::lyricsTrack(const FMH::MODEL &track, const QString &value) { if(update(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::LYRICS], value, FMH::MODEL_NAME[FMH::MODEL_KEY::URL], track[FMH::MODEL_KEY::URL])) return true; return false; } bool CollectionDB::tagsTrack(const FMH::MODEL &track, const QString &value, const QString &context) { auto url = track[FMH::MODEL_KEY::URL]; qDebug()<<"INSERTIN TRACK TAG"<getDBData(queryTxt); if(result.isEmpty()) return false; auto oldAlbum = result.first(); QVariantMap albumMap {{FMH::MODEL_NAME[FMH::MODEL_KEY::ALBUM],value}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], oldAlbum[FMH::MODEL_KEY::ARTIST]}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ARTWORK], oldAlbum[FMH::MODEL_KEY::ARTWORK]}, {FMH::MODEL_NAME[FMH::MODEL_KEY::WIKI], oldAlbum[FMH::MODEL_KEY::WIKI]}}; if (!insert(TABLEMAP[TABLE::ALBUMS],albumMap)) return false; // update albums SET album = "newalbumname" WHERE album = "albumname" NAD artist = "aretist name"; queryTxt = QString("UPDATE %1 SET %2 = %3 AND %4 = %5 WHERE %2 = %6 AND %4 = %5").arg(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::ALBUM],value, FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], oldAlbum[FMH::MODEL_KEY::ARTIST], oldAlbum[FMH::MODEL_KEY::ALBUM]); auto query = this->getQuery(queryTxt); if(!execQuery(query)) return false; queryTxt = QString("DELETE FROM %1 WHERE %2 = %3 AND %4 = %5").arg(TABLEMAP[TABLE::ALBUMS], FMH::MODEL_NAME[FMH::MODEL_KEY::ALBUM], oldAlbum[FMH::MODEL_KEY::ALBUM], FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], artist); query.prepare(queryTxt); if(!execQuery(query)) return false; return true; } bool CollectionDB::playedTrack(const QString &url, const int &increment) { auto queryTxt = QString("UPDATE %1 SET %2 = %2 + %3 WHERE %4 = \"%5\"").arg(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::COUNT], QString::number(increment), FMH::MODEL_NAME[FMH::MODEL_KEY::URL], url); auto query = this->getQuery(queryTxt); if(query.exec()) return true; return false; } bool CollectionDB::wikiTrack(const FMH::MODEL &track, const QString &value) { auto url = track[FMH::MODEL_KEY::URL]; if(update(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::WIKI], value, FMH::MODEL_NAME[FMH::MODEL_KEY::URL], url)) return true; return false; } bool CollectionDB::wikiArtist(const FMH::MODEL &track, const QString &value) { auto artist = track[FMH::MODEL_KEY::ARTIST]; if(update(TABLEMAP[TABLE::ARTISTS], FMH::MODEL_NAME[FMH::MODEL_KEY::WIKI], value, FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], artist)) return true; return false; } bool CollectionDB::tagsArtist(const FMH::MODEL &track, const QString &value, const QString &context) { auto artist = track[FMH::MODEL_KEY::ARTIST]; QVariantMap tagMap {{FMH::MODEL_NAME[FMH::MODEL_KEY::TAG], value}, {FMH::MODEL_NAME[FMH::MODEL_KEY::CONTEXT], context}}; QVariantMap artistTagMap {{FMH::MODEL_NAME[FMH::MODEL_KEY::TAG], value}, {FMH::MODEL_NAME[FMH::MODEL_KEY::CONTEXT], context}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], artist}}; insert(TABLEMAP[TABLE::TAGS],tagMap); insert(TABLEMAP[TABLE::ARTISTS_TAGS],artistTagMap); return true; } bool CollectionDB::wikiAlbum(const FMH::MODEL &track, QString value) { auto artist = track[FMH::MODEL_KEY::ARTIST]; auto album = track[FMH::MODEL_KEY::ALBUM]; auto queryStr = QString("UPDATE %1 SET %2 = \"%3\" WHERE %4 = \"%5\" AND %6 = \"%7\"").arg( TABLEMAP[TABLE::ALBUMS], FMH::MODEL_NAME[FMH::MODEL_KEY::WIKI], value.replace("\"","\"\""), FMH::MODEL_NAME[FMH::MODEL_KEY::ALBUM], album,FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], artist); qDebug()<getQuery(queryStr); return query.exec(); } bool CollectionDB::tagsAlbum(const FMH::MODEL &track, const QString &value, const QString &context) { auto artist = track[FMH::MODEL_KEY::ARTIST]; auto album = track[FMH::MODEL_KEY::ALBUM]; QVariantMap tagMap {{FMH::MODEL_NAME[FMH::MODEL_KEY::TAG],value},{FMH::MODEL_NAME[FMH::MODEL_KEY::CONTEXT],context}}; QVariantMap albumsTagMap {{FMH::MODEL_NAME[FMH::MODEL_KEY::TAG],value}, {FMH::MODEL_NAME[FMH::MODEL_KEY::CONTEXT],context}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST],artist}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ALBUM],album}}; insert(TABLEMAP[TABLE::TAGS],tagMap); insert(TABLEMAP[TABLE::ALBUMS_TAGS],albumsTagMap); return true; } bool CollectionDB::addPlaylist(const QString &title) { if(!title.isEmpty()) { QVariantMap playlist {{FMH::MODEL_NAME[FMH::MODEL_KEY::PLAYLIST], title}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ADDDATE], QDateTime::currentDateTime()}}; if(insert(TABLEMAP[TABLE::PLAYLISTS],playlist)) return true; } return false; } bool CollectionDB::trackPlaylist(const QString &url, const QString &playlist) { QVariantMap map {{FMH::MODEL_NAME[FMH::MODEL_KEY::PLAYLIST],playlist}, {FMH::MODEL_NAME[FMH::MODEL_KEY::URL],url}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ADDDATE],QDate::currentDate()}}; if(insert(TABLEMAP[TABLE::TRACKS_PLAYLISTS],map)) return true; return false; } bool CollectionDB::addFolder(const QString &url) { QVariantMap map {{FMH::MODEL_NAME[FMH::MODEL_KEY::URL],url}, {FMH::MODEL_NAME[FMH::MODEL_KEY::ADDDATE],QDateTime::currentDateTime()}}; if(insert(TABLEMAP[TABLE::FOLDERS],map)) return true; return false; } FMH::MODEL_LIST CollectionDB::getDBData(const QStringList &urls) { FMH::MODEL_LIST mapList; for(auto url : urls) { auto queryTxt = QString("SELECT * FROM %1 t INNER JOIN albums a on a.album = t.album and a.artist = t.artist WHERE t.%2 = \"%3\"").arg(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::URL], url); mapList << this->getDBData(queryTxt); } return mapList; } FMH::MODEL_LIST CollectionDB::getDBData(const QString &queryTxt) { FMH::MODEL_LIST mapList; auto query = this->getQuery(queryTxt); if(query.exec()) { while(query.next()) { FMH::MODEL data; for(auto key : FMH::MODEL_NAME.keys()) if(query.record().indexOf(FMH::MODEL_NAME[key]) > -1) data.insert(key, query.value(FMH::MODEL_NAME[key]).toString()); mapList << data; } }else qDebug()<< query.lastError()<< query.lastQuery(); return mapList; } QVariantList CollectionDB::getDBDataQML(const QString &queryTxt) { QVariantList mapList; auto query = this->getQuery(queryTxt); if(query.exec()) { while(query.next()) { QVariantMap data; for(auto key : FMH::MODEL_NAME.keys()) if(query.record().indexOf(FMH::MODEL_NAME[key])>-1) data[FMH::MODEL_NAME[key]] = query.value(FMH::MODEL_NAME[key]).toString(); mapList<< data; } }else qDebug()<< (query.lastError().text()+" "+query.lastQuery()); return mapList; } QStringList CollectionDB::dataToList(const FMH::MODEL_LIST &list, const FMH::MODEL_KEY &key) { if(list.isEmpty()) return QStringList(); QStringList response; for(auto track : list) response << track[key]; return response; } FMH::MODEL_LIST CollectionDB::getAlbumTracks(const QString &album, const QString &artist, const FMH::MODEL_KEY &orderBy, const BAE::W &order) { auto queryTxt = QString("SELECT * FROM %1 WHERE %2 = \"%3\" AND %4 = \"%5\" ORDER by %6 %7").arg(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], artist, FMH::MODEL_NAME[FMH::MODEL_KEY::ALBUM], album, FMH::MODEL_NAME[orderBy], SLANG[order]); return this->getDBData(queryTxt); } FMH::MODEL_LIST CollectionDB::getArtistTracks(const QString &artist, const FMH::MODEL_KEY &orderBy, const BAE::W &order) { auto queryTxt = QString("SELECT * FROM %1 WHERE %2 = \"%3\" ORDER by %4 %5, %6 %5").arg(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], artist, FMH::MODEL_NAME[orderBy], SLANG[order], FMH::MODEL_NAME[FMH::MODEL_KEY::TRACK]); return this->getDBData(queryTxt); } QStringList CollectionDB::getArtistAlbums(const QString &artist) { QStringList albums; auto queryTxt = QString("SELECT %4 FROM %1 WHERE %2 = \"%3\" ORDER BY %4 ASC").arg(TABLEMAP[TABLE::ALBUMS], FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], artist, FMH::MODEL_NAME[FMH::MODEL_KEY::ALBUM]); auto query = this->getDBData(queryTxt); for(auto track : query) albums << track[FMH::MODEL_KEY::ALBUM]; return albums; } FMH::MODEL_LIST CollectionDB::getBabedTracks(const FMH::MODEL_KEY &orderBy, const BAE::W &order) { auto queryTxt = QString("SELECT * FROM %1 WHERE %2 = 1 ORDER by %3 %4").arg(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::FAV], FMH::MODEL_NAME[orderBy], SLANG[order]); return this->getDBData(queryTxt); } -QVariantList CollectionDB::getSearchedTracks(const FMH::MODEL_KEY &where, const QString &search) +FMH::MODEL_LIST CollectionDB::getSearchedTracks(const FMH::MODEL_KEY &where, const QString &search) { QString queryTxt; if(where == FMH::MODEL_KEY::COUNT || where == FMH::MODEL_KEY::RATE || where == FMH::MODEL_KEY::FAV) queryTxt = QString("SELECT t.*, al.artwork FROM %1 t inner join albums al on al.album = t.album and t.artist = al.artist WHERE %2 = \"%3\"").arg(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[where], search); else if(where == FMH::MODEL_KEY::WIKI) queryTxt = QString("SELECT DISTINCT t.*, al.artwork FROM %1 t INNER JOIN %2 al ON t.%3 = al.%3 INNER JOIN %4 ar ON t.%5 = ar.%5 WHERE al.%6 LIKE \"%%7%\" OR ar.%6 LIKE \"%%7%\" COLLATE NOCASE").arg(TABLEMAP[TABLE::TRACKS], TABLEMAP[TABLE::ALBUMS], FMH::MODEL_NAME[FMH::MODEL_KEY::ALBUM], TABLEMAP[TABLE::ARTISTS], FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], FMH::MODEL_NAME[where], search); else if(where == FMH::MODEL_KEY::PLAYLIST) queryTxt = QString("SELECT t.* FROM %1 t INNER JOIN %2 tp ON t.%3 = tp.%3 WHERE tp.%4 = \"%5\"").arg(TABLEMAP[TABLE::TRACKS], TABLEMAP[TABLE::TRACKS_PLAYLISTS], FMH::MODEL_NAME[FMH::MODEL_KEY::URL], FMH::MODEL_NAME[where], search); else if(where == FMH::MODEL_KEY::TAG) queryTxt = QString("SELECT t.* FROM %1 t INNER JOIN %2 tt ON t.%3 = tt.%3 WHERE tt.%4 = \"%5\" COLLATE NOCASE " "UNION " "SELECT t.* FROM %1 t INNER JOIN %6 at ON t.%7 = at.%7 AND t.%8 = at.%8 WHERE at.%4 = \"%5\" COLLATE NOCASE " "UNION " "SELECT t.* FROM %1 t INNER JOIN %9 art ON t.%8 = art.%8 WHERE art.%4 LIKE \"%%5%\" COLLATE NOCASE " "UNION " "SELECT DISTINCT t.* FROM %1 t INNER JOIN %9 at ON t.%8 = at.%4 WHERE at.%8 LIKE \"%%5%\" COLLATE NOCASE").arg( TABLEMAP[TABLE::TRACKS], TABLEMAP[TABLE::TRACKS_TAGS], FMH::MODEL_NAME[FMH::MODEL_KEY::URL], FMH::MODEL_NAME[where], search, TABLEMAP[TABLE::ALBUMS_TAGS], FMH::MODEL_NAME[FMH::MODEL_KEY::ALBUM], FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], TABLEMAP[TABLE::ARTISTS_TAGS]); // else if(where == FMH::MODEL_KEY::SQL) // queryTxt = search; else queryTxt = QString("SELECT t.*, al.artwork FROM %1 t inner join albums al on al.album = t.album and t.artist = al.artist WHERE t.%2 LIKE \"%%3%\" ORDER BY strftime(\"%s\", t.addDate) desc LIMIT 1000").arg(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[where], search); - bDebug::Instance()->msg("SEARCH QUERY: " + queryTxt); - - return this->getDBDataQML(queryTxt); + return this->getDBData(queryTxt); } FMH::MODEL_LIST CollectionDB::getPlaylistTracks(const QString &playlist, const FMH::MODEL_KEY &orderBy, const BAE::W &order) { auto queryTxt= QString("SELECT t.* FROM %1 t INNER JOIN %2 tp ON t.%3 = tp.%3 WHERE tp.%4 = '%5' ORDER BY tp.%6 %7").arg(TABLEMAP[TABLE::TRACKS], TABLEMAP[TABLE::TRACKS_PLAYLISTS], FMH::MODEL_NAME[FMH::MODEL_KEY::URL], FMH::MODEL_NAME[FMH::MODEL_KEY::PLAYLIST], playlist, FMH::MODEL_NAME[orderBy], SLANG[order]); return this->getDBData(queryTxt); } FMH::MODEL_LIST CollectionDB::getFavTracks(const int &stars, const int &limit, const FMH::MODEL_KEY &orderBy, const BAE::W &order) { auto queryTxt= QString("SELECT * FROM %1 WHERE %2 >= %3 ORDER BY %4 %5 LIMIT %6" ).arg(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::RATE], QString::number(stars), FMH::MODEL_NAME[orderBy],SLANG[order],QString::number(limit)); return this->getDBData(queryTxt); } FMH::MODEL_LIST CollectionDB::getRecentTracks(const int &limit, const FMH::MODEL_KEY &orderBy, const BAE::W &order) { auto queryTxt= QString("SELECT * FROM %1 ORDER BY strftime(\"%s\",%2) %3 LIMIT %4" ).arg(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[orderBy],SLANG[order],QString::number(limit)); return this->getDBData(queryTxt); } FMH::MODEL_LIST CollectionDB::getOnlineTracks(const FMH::MODEL_KEY &orderBy, const BAE::W &order) { auto queryTxt= QString("SELECT * FROM %1 WHERE %2 LIKE \"%3%\" ORDER BY %4 %5" ).arg(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::URL], YoutubeCachePath, FMH::MODEL_NAME[orderBy], SLANG[order]); return this->getDBData(queryTxt); } QStringList CollectionDB::getSourcesFolders() { auto data = this->getDBData("select * from folders order by strftime(\"%s\", addDate) desc"); if(data.isEmpty()) return QStringList(); return this->dataToList(data, FMH::MODEL_KEY::URL); } FMH::MODEL_LIST CollectionDB::getMostPlayedTracks(const int &greaterThan, const int &limit, const FMH::MODEL_KEY &orderBy, const BAE::W &order) { auto queryTxt = QString("SELECT * FROM %1 WHERE %2 > %3 ORDER BY %4 %5 LIMIT %6" ).arg(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::COUNT], QString::number(greaterThan), FMH::MODEL_NAME[orderBy], SLANG[order], QString::number(limit)); return this->getDBData(queryTxt); } QString CollectionDB::trackColorTag(const QString &path) { QString color; auto query = this->getDBData(QString("SELECT %1 FROM %2 WHERE %3 = \"%4\"").arg(FMH::MODEL_NAME[FMH::MODEL_KEY::COLOR], TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::URL],path)); for(auto track : query) color = track[FMH::MODEL_KEY::COLOR]; return color; } QStringList CollectionDB::getTrackTags(const QString &path) { Q_UNUSED(path); return {}; } int CollectionDB::getTrackStars(const QString &path) { int stars = 0; auto query = this->getDBData(QString("SELECT %1 FROM %2 WHERE %3 = \"%4\"").arg(FMH::MODEL_NAME[FMH::MODEL_KEY::RATE], TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::URL],path)); for(auto track : query) stars = track[FMH::MODEL_KEY::RATE].toInt(); return stars; } //QStringList CollectionDB::getArtistTags(const QString &artist) //{ // QStringList tags; // auto queryStr = QString("SELECT at.%1 FROM %2 at " // "INNER JOIN %3 ta ON ta.%1 = at.%1 " // "WHERE ta.%4 = '%5' " // "AND at.%6 = \"%7\"").arg(FMH::MODEL_NAME[KEY::TAG], // TABLEMAP[TABLE::ARTISTS_TAGS], // TABLEMAP[TABLE::TAGS], // FMH::MODEL_NAME[KEY::CONTEXT], // PULPO::CONTEXT_MAP[PULPO::CONTEXT::ARTIST_SIMILAR], // FMH::MODEL_NAME[KEY::ARTIST],artist); // auto query = this->getDBData(queryStr); // for(auto track : query) // tags << track[KEY::TAG]; // return tags; //} //QStringList CollectionDB::getAlbumTags(const QString &album, const QString &artist) //{ // QStringList tags; // auto queryStr = QString("SELECT at.%1 FROM %2 at " // "INNER JOIN %3 ta ON ta.%1 = at.%1 " // "WHERE ta.%4 = '%5' AND at.%6 = \"%7\" AND at.%8 = \"%9\"").arg(FMH::MODEL_NAME[KEY::TAG], // TABLEMAP[TABLE::ALBUMS_TAGS], // TABLEMAP[TABLE::TAGS], // FMH::MODEL_NAME[KEY::CONTEXT], // PULPO::CONTEXT_MAP[PULPO::CONTEXT::TAG], // FMH::MODEL_NAME[KEY::ALBUM],album, // FMH::MODEL_NAME[KEY::ARTIST],artist); // auto query = this->getDBData(queryStr); // for(auto track : query) // tags << track[KEY::TAG]; // return tags; //} QStringList CollectionDB::getPlaylistsList() { QStringList playlists; auto queryTxt = QString("SELECT %1, %2 FROM %3 ORDER BY %2 DESC").arg(FMH::MODEL_NAME[FMH::MODEL_KEY::PLAYLIST], FMH::MODEL_NAME[FMH::MODEL_KEY::ADDDATE], TABLEMAP[TABLE::PLAYLISTS]); for(auto data : this->getDBData(queryTxt)) playlists << data[FMH::MODEL_KEY::PLAYLIST]; return playlists; } FMH::MODEL_LIST CollectionDB::getPlaylists() { auto queryTxt = QString("SELECT %1, %2 FROM %3 ORDER BY %2 DESC").arg(FMH::MODEL_NAME[FMH::MODEL_KEY::PLAYLIST], FMH::MODEL_NAME[FMH::MODEL_KEY::ADDDATE], TABLEMAP[TABLE::PLAYLISTS]); return this->getDBData(queryTxt); } bool CollectionDB::removeTrack(const QString &path) { auto queryTxt = QString("DELETE FROM %1 WHERE %2 = \"%3\"").arg(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::URL],path); auto query = this->getQuery(queryTxt); if(query.exec()) { if(cleanAlbums()) cleanArtists(); return true; } return false; } QSqlQuery CollectionDB::getQuery(const QString &queryTxt) { return QSqlQuery(queryTxt, this->m_db); } bool CollectionDB::removeSource(const QString &url) { auto path = url.endsWith("/") ? url.chopped(1) : url; auto queryTxt = QString("DELETE FROM %1 WHERE %2 LIKE \"%3%\"").arg(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::SOURCE], path); qDebug() << queryTxt; auto query = this->getQuery(queryTxt); if(query.exec()) { queryTxt = QString("DELETE FROM %1 WHERE %2 LIKE \"%3%\"").arg(TABLEMAP[TABLE::SOURCES], FMH::MODEL_NAME[FMH::MODEL_KEY::URL],path); query.prepare(queryTxt); if(query.exec()) { this->removeFolder(path); if(cleanAlbums()) cleanArtists(); return true; } } return false; } sourceTypes CollectionDB::sourceType(const QString &url) { /*for now*/ Q_UNUSED(url); return sourceTypes::LOCAL; } /*******************OLD STUFF********************/ void CollectionDB::insertArtwork(const FMH::MODEL &track) { auto artist = track[FMH::MODEL_KEY::ARTIST]; auto album =track[FMH::MODEL_KEY::ALBUM]; auto path = track[FMH::MODEL_KEY::ARTWORK]; switch(BAE::albumType(track)) { case TABLE::ALBUMS : { auto queryStr = QString("UPDATE %1 SET %2 = \"%3\" WHERE %4 = \"%5\" AND %6 = \"%7\"").arg(TABLEMAP[TABLE::ALBUMS], FMH::MODEL_NAME[FMH::MODEL_KEY::ARTWORK], path.isEmpty() ? SLANG[W::NONE] : path, FMH::MODEL_NAME[FMH::MODEL_KEY::ALBUM], album, FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], artist); auto query = this->getQuery(queryStr); if(!query.exec())qDebug()<<"COULDNT Artwork[cover] inerted into DB"<getQuery(queryStr); if(!query.exec())qDebug()<<"COULDNT Artwork[head] inerted into DB"<getQuery(queryTxt); return query.exec(); } bool CollectionDB::removePlaylist(const QString &playlist) { QString queryTxt; queryTxt = QString("DELETE FROM %1 WHERE %2 = \"%3\"").arg(TABLEMAP[TABLE::TRACKS_PLAYLISTS], FMH::MODEL_NAME[FMH::MODEL_KEY::PLAYLIST],playlist); auto query = this->getQuery(queryTxt); if(!query.exec()) return false; queryTxt = QString("DELETE FROM %1 WHERE %2 = \"%3\"").arg(TABLEMAP[TABLE::PLAYLISTS], FMH::MODEL_NAME[FMH::MODEL_KEY::PLAYLIST],playlist); query.prepare(queryTxt); return query.exec(); } void CollectionDB::removeMissingTracks() { auto tracks = this->getDBData("select url from tracks"); for(auto track : tracks) if(!BAE::fileExists(track[FMH::MODEL_KEY::URL])) this->removeTrack(track[FMH::MODEL_KEY::URL]); } bool CollectionDB::removeArtist(const QString &artist) { auto queryTxt = QString("DELETE FROM %1 WHERE %2 = \"%3\" ").arg(TABLEMAP[TABLE::ARTISTS], FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST],artist); auto query = this->getQuery(queryTxt); return query.exec(); } bool CollectionDB::cleanArtists() { // delete from artists where artist in (select artist from artists except select distinct artist from tracks); auto queryTxt=QString("DELETE FROM %1 WHERE %2 IN (SELECT %2 FROM %1 EXCEPT SELECT DISTINCT %2 FROM %3)").arg( TABLEMAP[TABLE::ARTISTS], FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], TABLEMAP[TABLE::TRACKS] ); qDebug()<getQuery(queryTxt); emit this->artistsCleaned(query.numRowsAffected()); return query.exec(); } bool CollectionDB::removeFolder(const QString &url) { auto queryTxt=QString("DELETE FROM %1 WHERE %2 LIKE \"%3%\"").arg( TABLEMAP[TABLE::FOLDERS], FMH::MODEL_NAME[FMH::MODEL_KEY::URL], url); qDebug()<getQuery(queryTxt); return query.exec(); } bool CollectionDB::favTrack(const QString &path, const bool &value) { if(this->update(TABLEMAP[TABLE::TRACKS], FMH::MODEL_NAME[FMH::MODEL_KEY::FAV], value ? 1 : 0, FMH::MODEL_NAME[FMH::MODEL_KEY::URL], path)) return true; return false; } bool CollectionDB::removeAlbum(const QString &album, const QString &artist) { auto queryTxt = QString("DELETE FROM %1 WHERE %2 = \"%3\" AND %4 = \"%5\"").arg(TABLEMAP[TABLE::ALBUMS], FMH::MODEL_NAME[FMH::MODEL_KEY::ALBUM], album, FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], artist); auto query = this->getQuery(queryTxt); return query.exec(); } bool CollectionDB::cleanAlbums() { // delete from albums where (album, artist) in (select a.album, a.artist from albums a except select distinct album, artist from tracks); auto queryTxt=QString("DELETE FROM %1 WHERE (%2, %3) IN (SELECT %2, %3 FROM %1 EXCEPT SELECT DISTINCT %2, %3 FROM %4)").arg( TABLEMAP[TABLE::ALBUMS], FMH::MODEL_NAME[FMH::MODEL_KEY::ALBUM], FMH::MODEL_NAME[FMH::MODEL_KEY::ARTIST], TABLEMAP[TABLE::TRACKS] ); qDebug()<getQuery(queryTxt); emit albumsCleaned(query.numRowsAffected()); return query.exec(); } diff --git a/db/collectionDB.h b/db/collectionDB.h index 581784a..a20bfe7 100644 --- a/db/collectionDB.h +++ b/db/collectionDB.h @@ -1,133 +1,133 @@ #ifndef COLLECTIONDB_H #define COLLECTIONDB_H #include #include #include #include #include #include #include #include #include #include #include #include #include "../utils/bae.h" enum sourceTypes { LOCAL, ONLINE, DEVICE }; class CollectionDB : public QObject { Q_OBJECT public: static CollectionDB *getInstance(); bool insert(const QString &tableName, const QVariantMap &insertData); bool update(const QString &tableName, const FMH::MODEL &updateData, const QVariantMap &where); bool update(const QString &table, const QString &column, const QVariant &newValue, const QVariant &op, const QString &id); bool remove(); bool execQuery(QSqlQuery &query) const; bool execQuery(const QString &queryTxt); /*basic public actions*/ void prepareCollectionDB() const; bool check_existance(const QString &tableName, const QString &searchId, const QString &search); /* usefull actions */ void insertArtwork(const FMH::MODEL &track); bool addTrack(const FMH::MODEL &track); bool updateTrack(const FMH::MODEL &track); Q_INVOKABLE bool rateTrack(const QString &path, const int &value); Q_INVOKABLE bool colorTagTrack(const QString &path, const QString &value); Q_INVOKABLE QString trackColorTag(const QString &path); bool lyricsTrack(const FMH::MODEL &track, const QString &value); Q_INVOKABLE bool playedTrack(const QString &url, const int &increment = 1); bool wikiTrack(const FMH::MODEL &track, const QString &value); bool tagsTrack(const FMH::MODEL &track, const QString &value, const QString &context); bool albumTrack(const FMH::MODEL &track, const QString &value); bool trackTrack(const FMH::MODEL &track, const QString &value); bool wikiArtist(const FMH::MODEL &track, const QString &value); bool tagsArtist(const FMH::MODEL &track, const QString &value, const QString &context = ""); bool wikiAlbum(const FMH::MODEL &track, QString value); bool tagsAlbum(const FMH::MODEL &track, const QString &value, const QString &context = ""); Q_INVOKABLE bool addPlaylist(const QString &title); bool trackPlaylist(const QString &url, const QString &playlist); bool addFolder(const QString &url); bool removeFolder(const QString &url); bool favTrack(const QString &path, const bool &value); FMH::MODEL_LIST getDBData(const QStringList &urls); FMH::MODEL_LIST getDBData(const QString &queryTxt); QVariantList getDBDataQML(const QString &queryTxt); QStringList dataToList(const FMH::MODEL_LIST &list, const FMH::MODEL_KEY &key); FMH::MODEL_LIST getAlbumTracks(const QString &album, const QString &artist, const FMH::MODEL_KEY &orderBy = FMH::MODEL_KEY::TRACK, const BAE::W &order = BAE::W::ASC); FMH::MODEL_LIST getArtistTracks(const QString &artist, const FMH::MODEL_KEY &orderBy = FMH::MODEL_KEY::ALBUM, const BAE::W &order = BAE::W::ASC); FMH::MODEL_LIST getBabedTracks(const FMH::MODEL_KEY &orderBy = FMH::MODEL_KEY::COUNT, const BAE::W &order = BAE::W::DESC); - QVariantList getSearchedTracks(const FMH::MODEL_KEY &where, const QString &search); + FMH::MODEL_LIST getSearchedTracks(const FMH::MODEL_KEY &where, const QString &search); FMH::MODEL_LIST getPlaylistTracks(const QString &playlist, const FMH::MODEL_KEY &orderBy = FMH::MODEL_KEY::ADDDATE, const BAE::W &order = BAE::W::DESC); FMH::MODEL_LIST getMostPlayedTracks(const int &greaterThan = 1, const int &limit = 50, const FMH::MODEL_KEY &orderBy = FMH::MODEL_KEY::COUNT, const BAE::W &order = BAE::W::DESC); FMH::MODEL_LIST getFavTracks(const int &stars = 1, const int &limit = 50, const FMH::MODEL_KEY &orderBy = FMH::MODEL_KEY::RATE, const BAE::W &order = BAE::W::DESC); FMH::MODEL_LIST getRecentTracks(const int &limit = 50, const FMH::MODEL_KEY &orderBy = FMH::MODEL_KEY::ADDDATE, const BAE::W &order = BAE::W::DESC); FMH::MODEL_LIST getOnlineTracks(const FMH::MODEL_KEY &orderBy = FMH::MODEL_KEY::ADDDATE, const BAE::W &order = BAE::W::DESC); Q_INVOKABLE QStringList getSourcesFolders(); QStringList getTrackTags(const QString &path); Q_INVOKABLE int getTrackStars(const QString &path); // QStringList getArtistTags(const QString &artist); // QStringList getAlbumTags(const QString &album, const QString &artist); QStringList getArtistAlbums(const QString &artist); FMH::MODEL_LIST getPlaylists(); QStringList getPlaylistsList(); Q_INVOKABLE bool removePlaylistTrack(const QString &url, const QString &playlist); Q_INVOKABLE bool removePlaylist(const QString &playlist); Q_INVOKABLE void removeMissingTracks(); bool removeArtist(const QString &artist); bool cleanArtists(); bool removeAlbum(const QString &album, const QString &artist); bool cleanAlbums(); Q_INVOKABLE bool removeSource(const QString &url); Q_INVOKABLE bool removeTrack(const QString &path); QSqlQuery getQuery(const QString &queryTxt); /*useful tools*/ sourceTypes sourceType(const QString &url); void openDB(const QString &name); private: static CollectionDB* instance; QString name; QSqlDatabase m_db; explicit CollectionDB( QObject *parent = nullptr); ~CollectionDB() override; void init(); public slots: signals: void trackInserted(); void artworkInserted(const FMH::MODEL &albumMap); void DBactionFinished(); void albumsCleaned(const int &amount); void artistsCleaned(const int &amount); }; #endif // COLLECTION_H diff --git a/main.qml b/main.qml index 1a2e243..16d7b5d 100644 --- a/main.qml +++ b/main.qml @@ -1,911 +1,916 @@ import QtQuick 2.9 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.3 import QtGraphicalEffects 1.0 import QtQuick.Controls.Material 2.1 import "utils" import "widgets" import "widgets/MyBeatView" import "widgets/PlaylistsView" import "widgets/MainPlaylist" import "widgets/SettingsView" import "widgets/SearchView" import "view_models" import "view_models/BabeTable" import "services/local" import "services/web" import "services/web/Spotify" import "view_models/BabeGrid" import "db/Queries.js" as Q import "utils/Help.js" as H import "utils/Player.js" as Player import org.kde.kirigami 2.2 as Kirigami import org.kde.mauikit 1.0 as Maui import FMList 1.0 import Player 1.0 import AlbumsList 1.0 import TracksList 1.0 Maui.ApplicationWindow { id: root // minimumWidth: !isMobile ? columnWidth : 0 // minimumHeight: !isMobile ? columnWidth + 64 : 0 // flags: Qt.FramelessWindowHint title: qsTr("vvave") /***************************************************/ /******************** ALIASES ********************/ /*************************************************/ // property alias playIcon: playIcon // property alias babeBtnIcon: babeBtnIcon property alias progressBar: mainPlaylist.progressBar property alias animFooter: mainPlaylist.animFooter property alias mainPlaylist: mainPlaylist property alias selectionBar: selectionBar about.appIcon: "qrc:/assets/vvave.svg" about.appDescription: qsTr("VVAVE will handle your whole music collection by retreaving semantic information from the web. Just relax, enjoy and discover your new music ") /***************************************************/ /******************** PLAYBACK ********************/ /*************************************************/ property bool isShuffle: bae.loadSetting("SHUFFLE","PLAYBACK", false) == "true" ? true : false property var currentTrack: ({ babe: "0", stars: "0" }) property int currentTrackIndex: 0 property int prevTrackIndex: 0 property string currentArtwork: !mainlistEmpty ? mainPlaylist.list.get(0).artwork : "" property bool currentBabe: currentTrack.babe == "0" ? false : true property string durationTimeLabel: player.duration property string progressTimeLabel: player.transformTime(player.position/1000) property bool isPlaying: player.playing property bool autoplay: bae.loadSetting("AUTOPLAY", "BABE", false) === "true" ? true : false property int onQueue: 0 property bool mainlistEmpty: !mainPlaylist.table.count > 0 /***************************************************/ /******************** UI PROPS ********************/ /*************************************************/ readonly property real opacityLevel: 0.8 property int miniArtSize: iconSizes.large property int columnWidth: Kirigami.Units.gridUnit * 17 property int coverSize: focusMode ? columnWidth : (isAndroid ? Math.sqrt(root.width * root.height) * 0.4 : columnWidth * (isMobile ? 0.7 : 0.6)) /***************************************************/ /******************** HANDLERS ********************/ /*************************************************/ property int currentView: viewsIndex.tracks readonly property var viewsIndex: ({ tracks: 0, albums: 1, artists: 2, playlists: 3, search: 4, folders: 5, vvave: 6, linking: 7, youtube: 8, spotify: 9 }) property string syncPlaylist: "" property bool sync: false property string infoMsg: "" property bool infoLabels: bae.loadSetting("LABELS", "PLAYBACK", false) == "true" ? true : false property bool isLinked: false property bool isServing: false property bool focusMode : false property bool selectionMode : false /***************************************************/ /******************** UI COLORS *******************/ /*************************************************/ property string babeColor: bae.babeColor() //"#140032" /*SIGNALS*/ signal missingAlert(var track) /*CONF*/ pageStack.defaultColumnWidth: columnWidth pageStack.initialPage: [mainPlaylist, views] pageStack.interactive: isMobile pageStack.separatorVisible: pageStack.wideMode /*HANDLE EVENTS*/ onWidthChanged: if (isMobile) { if (width > height) mainPlaylist.cover.visible = false else mainPlaylist.cover.visible = true } onClosing: Player.savePlaylist() // pageStack.onCurrentIndexChanged: // { // if(pageStack.currentIndex === 0 && isMobile && !pageStack.wideMode) // { // bae.androidStatusBarColor(babeColor) // Material.background = babeColor // }else // { // bae.androidStatusBarColor(babeAltColor) // Material.background = babeAltColor // } // } onMissingAlert: { missingDialog.message = track.title + " by " + track.artist + " is missing" missingDialog.messageBody = "Do you want to remove it from your collection?" missingDialog.open() } /*COMPONENTS*/ Player { id: player volume: 100 // onFinishedChanged: if (!mainlistEmpty) // { // if (currentTrack.url) // bae.playedTrack(currentTrack.url) // Player.nextTrack() // } } BabeNotify { id: babeNotify } Maui.Dialog { id: missingDialog title: "Missing file" onAccepted: { bae.removeTrack(currentTrack.url) mainPlaylist.table.model.remove(mainPlaylist.table.currentIndex) } } /* UI */ property bool accent : pageStack.wideMode || (!pageStack.wideMode && pageStack.currentIndex === 1) // altToolBars: false accentColor: bae.babeColor() headBarFGColor: altColorText headBarBGColor: currentView === viewsIndex.vvave ? "#7e57c2" : "#212121" colorSchemeName: "vvave" altColorText: darkTextColor headBar.middleContent : [ // Row // { // Image // { // height: iconSizes.medium // width: height // source: "file://" + encodeURIComponent( // currentArtwork) // } // Label // { // text: "Now" // } // }, Maui.ToolButton { iconName: "headphones" iconColor: !accent ? babeColor : altColorText onClicked: pageStack.currentIndex = 0 text: qsTr("Now") }, Maui.ToolButton { iconName: "view-media-track" iconColor: accent && currentView === viewsIndex.tracks ? babeColor : altColorText onClicked: { pageStack.currentIndex = 1 currentView = viewsIndex.tracks } text: qsTr("Tracks") tooltipText: pageStack.wideMode ? "" : text }, Maui.ToolButton { text: qsTr("Albums") iconName: /*"album"*/ "view-media-album-cover" iconColor: accent && currentView === viewsIndex.albums ? babeColor : altColorText onClicked: { pageStack.currentIndex = 1 currentView = viewsIndex.albums } tooltipText: pageStack.wideMode ? "" : text }, Maui.ToolButton { text: qsTr("Artists") iconName: "view-media-artist" iconColor: accent && currentView === viewsIndex.artists ? babeColor : altColorText onClicked: { pageStack.currentIndex = 1 currentView = viewsIndex.artists } tooltipText: pageStack.wideMode ? "" : text }, Maui.ToolButton { text: qsTr("Playlists") iconName: "view-media-playlist" iconColor: accent && currentView === viewsIndex.playlists ? babeColor : altColorText onClicked: { pageStack.currentIndex = 1 currentView = viewsIndex.playlists } tooltipText: pageStack.wideMode ? "" : text } ] onSearchButtonClicked: { pageStack.currentIndex = 1 currentView = viewsIndex.search searchView.searchInput.forceActiveFocus() riseContent() } FloatingDisk { id: floatingDisk x: space.big y: pageStack.height - height z: 999 } Maui.ShareDialog { id: shareDialog } Maui.FileDialog { id: fmDialog onlyDirs: false filterType: FMList.AUDIO sortBy: FMList.MODIFIED mode: modes.OPEN } SourcesDialog { id: sourcesDialog } BabeConsole { id: babeConsole } // menuDrawer.bannerImageSource: "qrc:/assets/banner.svg" mainMenu: [ Maui.MenuItem { text: "Vvave Stream" + icon.name: "headphones" onTriggered: { pageStack.currentIndex = 1 currentView = viewsIndex.vvave } }, Maui.MenuItem { text: qsTr("Folders") + icon.name: "folder-open" onTriggered: { pageStack.currentIndex = 1 currentView = viewsIndex.folders } }, Maui.MenuItem { text: qsTr("Linking") + icon.name: "view-links" onTriggered: { pageStack.currentIndex = 1 currentView = viewsIndex.linking if(!isLinked) linkingView.linkingConf.open() } }, Maui.MenuItem { text: qsTr("YouTube") + icon.name: "internet-services" onTriggered: { pageStack.currentIndex = 1 currentView = viewsIndex.youtube } }, Maui.MenuItem { text: qsTr("Spotify") + icon.name: "internet-services" onTriggered: { pageStack.currentIndex = 1 currentView = viewsIndex.spotify } }, MenuSeparator{}, Maui.MenuItem { text: qsTr("Sources...") + icon.name: "folder-add" onTriggered: sourcesDialog.open() }, Maui.Menu { title: qsTr("Collection") - - +// icon.name: "settings-configure" Maui.MenuItem { text: qsTr("Re-Scan") onTriggered: bae.refreshCollection(); } Maui.MenuItem { text: qsTr("Refresh...") onTriggered: H.refreshCollection(); } Maui.MenuItem { text: qsTr("Clean") onTriggered: bae.removeMissingTracks(); } }, Maui.Menu { title: qsTr("Settings...") // Kirigami.Action // { // text: "Brainz" // Kirigami.Action // { // id: brainzToggle // text: checked ? "Turn OFF" : "Turn ON" // checked: bae.brainzState() // checkable: true // onToggled: // { // checked = !checked // bae.saveSetting("AUTO", checked, "BRAINZ") //// bae.brainz(checked) // } // } // } Maui.MenuItem { text: "Info label" + checked ? "ON" : "OFF" checked: infoLabels checkable: true onToggled: { infoLabels = checked bae.saveSetting("LABELS", infoLabels ? true : false, "PLAYBACK") } } Maui.MenuItem { text: "Autoplay" checked: autoplay checkable: true onToggled: { autoplay = checked bae.saveSetting("AUTOPLAY", autoplay ? true : false, "BABE") } } } ] Item { id: message visible: infoMsg.length > 0 && sync anchors.bottom: parent.bottom width: pageStack.wideMode ? columnWidth : parent.width height: iconSize z: 999 Rectangle { id: infoBg anchors.fill: parent z: -999 color: altColor opacity: opacityLevel SequentialAnimation { id: animBg PropertyAnimation { target: infoBg property: "color" easing.type: Easing.InOutQuad to: babeColor duration: 250 } PropertyAnimation { target: infoBg property: "color" easing.type: Easing.InOutQuad to: altColor duration: 500 } } } Label { id: infoTxt anchors.centerIn: parent anchors.fill: parent height: parent.height width: parent.width font.pointSize: fontSizes.medium text: infoMsg horizontalAlignment: Qt.AlignHCenter verticalAlignment: Qt.AlignVCenter color: textColor SequentialAnimation { id: animTxt PropertyAnimation { target: infoTxt property: "color" easing.type: Easing.InOutQuad to: "white" duration: 250 } PropertyAnimation { target: infoTxt property: "color" easing.type: Easing.InOutQuad to: textColor duration: 500 } } } } PlaylistDialog { id: playlistDialog } MainPlaylist { id: mainPlaylist Connections { target: mainPlaylist onCoverPressed: Player.appendAll(tracks) onCoverDoubleClicked: Player.playAll(tracks) } } Maui.Page { id: views headBar.visible: false margins: 0 // focusPolicy: Qt.WheelFocus // visualFocus: true ColumnLayout { anchors.fill: parent SwipeView { id: swipeView Layout.fillHeight: true Layout.fillWidth: true interactive: isMobile // contentItem: ListView // { // model: swipeView.contentModel // interactive: swipeView.interactive // currentIndex: swipeView.currentIndex // spacing: swipeView.spacing // orientation: swipeView.orientation // snapMode: ListView.SnapOneItem // boundsBehavior: Flickable.StopAtBounds // highlightRangeMode: ListView.StrictlyEnforceRange // preferredHighlightBegin: 0 // preferredHighlightEnd: 0 // highlightMoveDuration: 250 // // min:10 // maximumFlickVelocity: 10 * (swipeView.orientation === // Qt.Horizontal ? width : height) // } currentIndex: currentView onCurrentItemChanged: currentItem.forceActiveFocus() onCurrentIndexChanged: { currentView = currentIndex if (!babeitView.isConnected && currentIndex === viewsIndex.vvave) babeitView.logginDialog.open() } TracksView { id: tracksView Connections { target: tracksView onRowClicked: Player.addTrack(tracksView.list.get(index)) onQuickPlayTrack: Player.quickPlay(tracksView.list.get(index)) onPlayAll: Player.playAll(bae.get(Q.GET.allTracks)) onAppendAll: { mainPlaylist.list.append(Q.GET.allTracks) mainPlaylist.listView.positionViewAtEnd() } onQueueTrack: Player.queueTracks([tracksView.list.get(index)], index) } } AlbumsView { id: albumsView holder.emoji: "qrc:/assets/MusicBox.png" holder.isMask: false holder.title : "No Albums!" holder.body: "Add new music sources" holder.emojiSize: iconSizes.huge headBarTitle: count + qsTr(" albums") list.query: Q.GET.allAlbumsAsc list.sortBy: Albums.ALBUM Connections { target: albumsView onRowClicked: Player.addTrack(track) onPlayTrack: Player.quickPlay(track) onAlbumCoverClicked: albumsView.populateTable(album, artist) onAlbumCoverPressedAndHold: { var query = Q.GET.albumTracks_.arg(album) query = query.arg(artist) mainPlaylist.list.clear() mainPlaylist.list.query = query Player.playAll() } onPlayAll: { var query = Q.GET.albumTracks_.arg(album) query = query.arg(artist) - query = query.arg(data.artist) - var tracks = bae.get(query) mainPlaylist.list.clear() mainPlaylist.list.query = query Player.playAll() } onAppendAll: { var query = Q.GET.albumTracks_.arg(album) query = query.arg(artist) mainPlaylist.list.appendQuery(query) mainPlaylist.listView.positionViewAtEnd() } } } AlbumsView { id: artistsView holder.emoji: "qrc:/assets/MusicBox.png" holder.isMask: false holder.title : qsTr("No Artists!") holder.body: qsTr("Add new music sources") holder.emojiSize: iconSizes.huge headBarTitle: count + qsTr(" artists") list.query: Q.GET.allArtistsAsc list.sortBy: Albums.ARTIST table.list.sortBy: Tracks.NONE Connections { target: artistsView onRowClicked: Player.addTrack(track) onPlayTrack: Player.quickPlay(track) onAlbumCoverClicked: artistsView.populateTable(undefined, artist) onAlbumCoverPressedAndHold: { var query = Q.GET.artistTracks_.arg(artist) var map = bae.get(query) Player.playAll(map) } onPlayAll: { - var query = Q.GET.artistTracks_.arg(artist) query = query.arg(data.artist) - var tracks = bae.get(query) - Player.playAll(tracks) + + mainPlaylist.list.clear() + mainPlaylist.list.sortBy = Tracks.NONE + mainPlaylist.list.query = query + Player.playAll() } onAppendAll: { var query = Q.GET.artistTracks_.arg(artist) mainPlaylist.list.appendQuery(query) mainPlaylist.listView.positionViewAtEnd() } } } PlaylistsView { id: playlistsView Connections { target: playlistsView onRowClicked: Player.addTrack(track) onQuickPlayTrack: Player.quickPlay(track) onPlayAll: Player.playAll(tracks) onAppendAll: Player.appendAll(tracks) onPlaySync: { var tracks = bae.get(Q.GET.playlistTracks_.arg(playlist)) Player.playAll(tracks) root.sync = true root.syncPlaylist = playlist root.infoMsg = qsTr("Syncing to ") + playlist } } } SearchTable { id: searchView Connections { target: searchView.searchTable onRowClicked: Player.addTrack(searchView.searchTable.model.get(index)) onQuickPlayTrack: Player.quickPlay(searchView.searchTable.model.get(index)) onPlayAll: Player.playAll(searchView.searchRes) onAppendAll: Player.appendAll(searchView.searchRes) onArtworkDoubleClicked: { var query = Q.GET.albumTracks_.arg( searchView.searchTable.model.get( index).album) query = query.arg(searchView.searchTable.model.get( index).artist) Player.playAll(bae.get(query)) } } } FoldersView { id: foldersView Connections { target: foldersView.list onRowClicked: Player.addTrack(foldersView.list.model.get(index)) onQuickPlayTrack: Player.quickPlay(foldersView.list.model.get(index)) onPlayAll: Player.playAll(foldersView.getTracks()) onAppendAll: Player.appendAll(foldersView.getTracks()) onQueueTrack: Player.queueTracks([foldersView.list.model.get(index)], index) } } BabeitView { id: babeitView } LinkingView { id: linkingView } YouTube { id: youtubeView } Spotify { id: spotifyView } } Maui.SelectionBar { id: selectionBar Layout.fillWidth: true Layout.margins: space.huge Layout.topMargin: space.small Layout.bottomMargin: space.big onIconClicked: contextMenu.show(selectedPaths) onExitClicked: clear() TableMenu { id: contextMenu menuItem: Maui.MenuItem { text: qsTr("Play all") onTriggered: { var data = bae.getList(selectionBar.selectedPaths) contextMenu.close() selectionMode = false selectionBar.clear() Player.playAll(data) } } onFavClicked: H.faveIt(paths) onQueueClicked: H.queueIt(paths) onSaveToClicked: { playlistDialog.tracks = paths playlistDialog.open() } onOpenWithClicked: bae.showFolder(paths) onRemoveClicked: { } onRateClicked: H.rateIt(paths, rate) onColorClicked: H.moodIt(paths, color) } } } } /*animations*/ /*FUNCTIONS*/ function infoMsgAnim() { animBg.running = true animTxt.running = true } function toggleMaximized() { if (root.visibility === Window.Maximized) { root.showNormal(); } else { root.showMaximized(); } } /*CONNECTIONS*/ Connections { target: bae onRefreshTables: H.refreshCollection(size) // onRefreshTracks: H.refreshTracks() // onRefreshAlbums: H.refreshAlbums() // onRefreshArtists: H.refreshArtists() onTrackLyricsReady: { if (url === currentTrack.url) Player.setLyrics(lyrics) } onSkipTrack: Player.nextTrack() onBabeIt: Player.babeTrack() onOpenFiles: Player.playAll(tracks) } } diff --git a/models/tracks/tracksmodel.cpp b/models/tracks/tracksmodel.cpp index e969e62..625abef 100644 --- a/models/tracks/tracksmodel.cpp +++ b/models/tracks/tracksmodel.cpp @@ -1,248 +1,313 @@ #include "tracksmodel.h" #include "db/collectionDB.h" TracksModel::TracksModel(QObject *parent) : BaseList(parent) { this->db = CollectionDB::getInstance(); connect(this, &TracksModel::queryChanged, this, &TracksModel::setList); } FMH::MODEL_LIST TracksModel::items() const { return this->list; } void TracksModel::setQuery(const QString &query) { if(this->query == query) return; this->query = query; qDebug()<< "setting query"<< this->query; emit this->queryChanged(); } QString TracksModel::getQuery() const { return this->query; } void TracksModel::setSortBy(const SORTBY &sort) { if(this->sort == sort) return; this->sort = sort; this->preListChanged(); this->sortList(); this->postListChanged(); emit this->sortByChanged(); } TracksModel::SORTBY TracksModel::getSortBy() const { return this->sort; } void TracksModel::sortList() { if(this->sort == TracksModel::SORTBY::NONE) return; const auto key = static_cast(this->sort); qDebug()<< "SORTING LIST BY"<< this->sort; qSort(this->list.begin(), this->list.end(), [key](const FMH::MODEL &e1, const FMH::MODEL &e2) -> bool { auto role = key; switch(role) { case FMH::MODEL_KEY::RELEASEDATE: case FMH::MODEL_KEY::RATE: case FMH::MODEL_KEY::FAV: { if(e1[role].toInt() > e2[role].toInt()) return true; break; } case FMH::MODEL_KEY::TRACK: { if(e1[role].toInt() < e2[role].toInt()) return true; break; } case FMH::MODEL_KEY::ADDDATE: { auto currentTime = QDateTime::currentDateTime(); auto date1 = QDateTime::fromString(e1[role], Qt::TextDate); auto date2 = QDateTime::fromString(e2[role], Qt::TextDate); if(date1.secsTo(currentTime) < date2.secsTo(currentTime)) return true; break; } case FMH::MODEL_KEY::TITLE: case FMH::MODEL_KEY::ARTIST: case FMH::MODEL_KEY::ALBUM: case FMH::MODEL_KEY::FORMAT: { const auto str1 = QString(e1[role]).toLower(); const auto str2 = QString(e2[role]).toLower(); if(str1 < str2) return true; break; } default: if(e1[role] < e2[role]) return true; } return false; }); } void TracksModel::setList() { emit this->preListChanged(); this->list = this->db->getDBData(this->query); qDebug()<< "my LIST" ; this->sortList(); emit this->postListChanged(); } QVariantMap TracksModel::get(const int &index) const { if(index >= this->list.size() || index < 0) return QVariantMap(); QVariantMap res; const auto item = this->list.at(index); for(auto key : item.keys()) res.insert(FMH::MODEL_NAME[key], item[key]); return res; } void TracksModel::append(const QVariantMap &item) { if(item.isEmpty()) return; emit this->preItemAppended(); FMH::MODEL model; for(auto key : item.keys()) model.insert(FMH::MODEL_NAME_KEY[key], item[key].toString()); qDebug() << "Appending item to list" << item; this->list << model; qDebug()<< this->list; emit this->postItemAppended(); } void TracksModel::append(const QVariantMap &item, const int &at) { if(item.isEmpty()) return; if(at > this->list.size() || at < 0) return; qDebug()<< "trying to append at" << at << item["title"]; emit this->preItemAppendedAt(at); FMH::MODEL model; for(auto key : item.keys()) model.insert(FMH::MODEL_NAME_KEY[key], item[key].toString()); this->list.insert(at, model); emit this->postItemAppended(); } void TracksModel::appendQuery(const QString &query) { if(this->query.isEmpty()) return; emit this->preListChanged(); this->list << this->db->getDBData(query); emit this->postListChanged(); } +void TracksModel::searchQueries(const QStringList &queries) +{ + emit this->preListChanged(); + this->list.clear(); + + bool hasKey = false; + for(auto searchQuery : queries) + { + if(searchQuery.contains(BAE::SearchTMap[BAE::SearchT::LIKE]+":") || searchQuery.startsWith("#")) + { + if(searchQuery.startsWith("#")) + searchQuery = searchQuery.replace("#","").trimmed(); + else + searchQuery = searchQuery.replace(BAE::SearchTMap[BAE::SearchT::LIKE]+":","").trimmed(); + + + searchQuery = searchQuery.trimmed(); + if(!searchQuery.isEmpty()) + { + this->list << this->db->getSearchedTracks(FMH::MODEL_KEY::WIKI, searchQuery); + this->list << this->db->getSearchedTracks(FMH::MODEL_KEY::TAG, searchQuery); + this->list << this->db->getSearchedTracks(FMH::MODEL_KEY::LYRICS, searchQuery); + } + + }else if(searchQuery.contains((BAE::SearchTMap[BAE::SearchT::SIMILAR]+":"))) + { + searchQuery=searchQuery.replace(BAE::SearchTMap[BAE::SearchT::SIMILAR]+":","").trimmed(); + searchQuery=searchQuery.trimmed(); + if(!searchQuery.isEmpty()) + this->list << this->db->getSearchedTracks(FMH::MODEL_KEY::TAG, searchQuery); + + }else + { + FMH::MODEL_KEY key; + + QHashIterator k(FMH::MODEL_NAME); + while (k.hasNext()) + { + k.next(); + if(searchQuery.contains(QString(k.value()+":"))) + { + hasKey=true; + key=k.key(); + searchQuery = searchQuery.replace(k.value()+":","").trimmed(); + } + } + + searchQuery = searchQuery.trimmed(); + + if(!searchQuery.isEmpty()) + { + if(hasKey) + this->list << this->db->getSearchedTracks(key, searchQuery); + else + { + auto queryTxt = QString("SELECT t.*, al.artwork FROM tracks t INNER JOIN albums al ON t.album = al.album AND t.artist = al.artist WHERE t.title LIKE \"%"+searchQuery+"%\" OR t.artist LIKE \"%"+searchQuery+"%\" OR t.album LIKE \"%"+searchQuery+"%\"OR t.genre LIKE \"%"+searchQuery+"%\"OR t.url LIKE \"%"+searchQuery+"%\" ORDER BY strftime(\"%s\", t.addDate) desc LIMIT 1000"); + this->list << this->db->getDBData(queryTxt); + } + } + } + } + + emit this->postListChanged(); +} + void TracksModel::clear() { emit this->preListChanged(); this->list.clear(); emit this->postListChanged(); } bool TracksModel::color(const int &index, const QString &color) { if(index >= this->list.size() || index < 0) return false; auto item = this->list[index]; if(this->db->colorTagTrack(item[FMH::MODEL_KEY::URL], color)) { this->list[index][FMH::MODEL_KEY::COLOR] = color; emit this->updateModel(index, {FMH::MODEL_KEY::COLOR}); return true; } return false; } bool TracksModel::fav(const int &index, const bool &value) { if(index >= this->list.size() || index < 0) return false; auto item = this->list[index]; if(this->db->favTrack(item[FMH::MODEL_KEY::URL], value)) { this->list[index][FMH::MODEL_KEY::FAV] = value ? "1" : "0"; emit this->updateModel(index, {FMH::MODEL_KEY::FAV}); return true; } return false; } bool TracksModel::rate(const int &index, const int &value) { if(index >= this->list.size() || index < 0) return false; auto item = this->list[index]; if(this->db->rateTrack(item[FMH::MODEL_KEY::URL], value)) { this->list[index][FMH::MODEL_KEY::RATE] = QString::number(value); emit this->updateModel(index, {FMH::MODEL_KEY::RATE}); return true; } return false; } diff --git a/models/tracks/tracksmodel.h b/models/tracks/tracksmodel.h index 868db7f..d4dd42c 100644 --- a/models/tracks/tracksmodel.h +++ b/models/tracks/tracksmodel.h @@ -1,65 +1,67 @@ #ifndef TRACKSMODEL_H #define TRACKSMODEL_H #include #include "models/baselist.h" class CollectionDB; class TracksModel : public BaseList { Q_OBJECT Q_PROPERTY(QString query READ getQuery WRITE setQuery NOTIFY queryChanged()) Q_PROPERTY(TracksModel::SORTBY sortBy READ getSortBy WRITE setSortBy NOTIFY sortByChanged) public: enum SORTBY : uint_fast8_t { ADDDATE = FMH::MODEL_KEY::ADDDATE, RELEASEDATE = FMH::MODEL_KEY::RELEASEDATE, FORMAT = FMH::MODEL_KEY::FORMAT, ARTIST = FMH::MODEL_KEY::ARTIST, TITLE = FMH::MODEL_KEY::TITLE, ALBUM = FMH::MODEL_KEY::ALBUM, RATE = FMH::MODEL_KEY::RATE, FAV = FMH::MODEL_KEY::FAV, TRACK = FMH::MODEL_KEY::TRACK, + COUNT = FMH::MODEL_KEY::COUNT, NONE }; Q_ENUM(SORTBY) explicit TracksModel(QObject *parent = nullptr); FMH::MODEL_LIST items() const override; void setQuery(const QString &query); QString getQuery() const; void setSortBy(const TracksModel::SORTBY &sort); TracksModel::SORTBY getSortBy() const; private: CollectionDB *db; FMH::MODEL_LIST list; void sortList(); void setList(); QString query; TracksModel::SORTBY sort = TracksModel::SORTBY::ADDDATE; signals: void queryChanged(); void sortByChanged(); public slots: QVariantMap get(const int &index) const override; void append(const QVariantMap &item); void append(const QVariantMap &item, const int &at); void appendQuery(const QString &query); + void searchQueries(const QStringList &queries); void clear(); bool color(const int &index, const QString &color); bool fav(const int &index, const bool &value); bool rate(const int &index, const int &value); }; #endif // TRACKSMODEL_H diff --git a/view_models/BabeTable/BabeTable.qml b/view_models/BabeTable/BabeTable.qml index 6e52a25..22cd30e 100644 --- a/view_models/BabeTable/BabeTable.qml +++ b/view_models/BabeTable/BabeTable.qml @@ -1,418 +1,420 @@ import QtQuick 2.9 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 import org.kde.kirigami 2.2 as Kirigami import org.kde.mauikit 1.0 as Maui import BaseModel 1.0 import TracksList 1.0 import "../../utils/Player.js" as Player import "../../utils/Help.js" as H import "../../db/Queries.js" as Q import ".." BabeList { id: babeTableRoot // cacheBuffer : 300 property alias list : _tracksList property alias listModel : _tracksModel property alias listView : babeTableRoot.listView + + property bool trackNumberVisible property bool quickPlayVisible : true property bool coverArtVisible : false property bool menuItemVisible : isMobile property bool trackDuration property bool trackRating property bool allowMenu: true property bool isArtworkRemote : false property bool showIndicator : false property bool group : false property alias headerMenu: headerMenu property alias contextMenu : contextMenu property alias playAllBtn : playAllBtn property alias appendBtn : appendBtn property alias menuBtn : menuBtn signal rowClicked(int index) signal rowPressed(int index) signal quickPlayTrack(int index) signal queueTrack(int index) signal artworkDoubleClicked(int index) signal playAll() signal appendAll() // altToolBars: true onGroupChanged: groupBy() focus: true headBar.leftContent: [ Maui.ToolButton { id : playAllBtn visible : headBar.visible && count > 0 anim : true iconName : "media-playlist-play" onClicked : playAll() }, Maui.ToolButton { id: sortBtn anim: true iconName: "view-sort" onClicked: sortMenu.popup() Maui.Menu { id: sortMenu Maui.MenuItem { text: qsTr("Title") checkable: true checked: list.sortBy === Tracks.TITLE onTriggered: list.sortBy = Tracks.TITLE } Maui.MenuItem { text: qsTr("Track") checkable: true checked: list.sortBy === Tracks.TRACK onTriggered: list.sortBy = Tracks.TRACK } Maui.MenuItem { text: qsTr("Artist") checkable: true checked: list.sortBy === Tracks.ARTIST onTriggered: list.sortBy = Tracks.ARTIST } Maui.MenuItem { text: qsTr("Album") checkable: true checked: list.sortBy === Tracks.ALBUM onTriggered: list.sortBy = Tracks.ALBUM } Maui.MenuItem { text: qsTr("Rate") checkable: true checked: list.sortBy === Tracks.RATE onTriggered: list.sortBy = Tracks.RATE } Maui.MenuItem { text: qsTr("Fav") checkable: true checked: list.sortBy === Tracks.FAV onTriggered: list.sortBy = Tracks.FAV } Maui.MenuItem { text: qsTr("Release date") checkable: true checked: list.sortBy === Tracks.RELEASEDATE onTriggered: list.sortBy = Tracks.RELEASEDATE } Maui.MenuItem { text: qsTr("Add date") checkable: true checked: list.sortBy === Tracks.ADDDATE onTriggered: list.sortBy = Tracks.ADDDATE } MenuSeparator{} Maui.MenuItem { text: qsTr("Group") checkable: true checked: group onTriggered: group = !group } } } ] headBar.rightContent: [ Maui.ToolButton { id: appendBtn visible: headBar.visible && count > 0 anim : true iconName : "media-playlist-append"//"media-repeat-track-amarok" onClicked: appendAll() }, Maui.ToolButton { id: menuBtn iconName: /*"application-menu"*/ "overflow-menu" onClicked: headerMenu.popup() } ] HeaderMenu { id: headerMenu onSaveListClicked: saveList() onQueueListClicked: queueList() } TableMenu { id: contextMenu menuItem: [ Maui.MenuItem { text: qsTr("Select...") onTriggered: { H.addToSelection(listView.model.get(listView.currentIndex)) contextMenu.close() } }, MenuSeparator {}, Maui.MenuItem { text: qsTr("Go to Artist") onTriggered: goToArtist() }, Maui.MenuItem { text: qsTr("Go to Album") onTriggered: goToAlbum() } ] onFavClicked: { list.fav(listView.currentIndex, !(list.get(listView.currentIndex).fav == "1")) } onQueueClicked: Player.queueTracks([list.get(listView.currentIndex)]) onSaveToClicked: { playlistDialog.tracks = paths playlistDialog.open() } onOpenWithClicked: bae.showFolder(paths) onRemoveClicked: { listModel.remove(listView.currentIndex) } onRateClicked: { list.rate(listView.currentIndex, rate); } onColorClicked: { list.color(listView.currentIndex, color); } } listView.highlightFollowsCurrentItem: false listView.highlightMoveDuration: 0 listView.highlight: Rectangle { } section.criteria: ViewSection.FullString section.delegate: Maui.LabelDelegate { + id: _sectionDelegate label: section isSection: true boldLabel: true colorScheme.backgroundColor: "#333" colorScheme.textColor: "#fafafa" background: Rectangle { color: colorScheme.backgroundColor } - } BaseModel { id: _tracksModel list: _tracksList } Tracks { id: _tracksList onSortByChanged: if(babeTableRoot.group) babeTableRoot.groupBy() } model: _tracksModel // property alias animBabe: delegate.animBabe delegate: TableDelegate { id: delegate width: listView.width number : trackNumberVisible ? true : false quickPlay: quickPlayVisible coverArt : coverArtVisible trackDurationVisible : trackDuration trackRatingVisible : trackRating menuItem: menuItemVisible remoteArtwork: isArtworkRemote playingIndicator: showIndicator onPressAndHold: if(isMobile && allowMenu) openItemMenu(index) onRightClicked: if(allowMenu) openItemMenu(index) onClicked: { currentIndex = index if(selectionMode) { H.addToSelection(listView.model.get(listView.currentIndex)) return } if(isMobile) rowClicked(index) } onDoubleClicked: { currentIndex = index if(!isMobile) rowClicked(index) } onPlay: { currentIndex = index quickPlayTrack(index) } onArtworkCoverClicked: { currentIndex = index goToAlbum() } } function openItemMenu(index) { currentIndex = index contextMenu.rate = list.get(currentIndex).rate contextMenu.fav = list.get(currentIndex).fav == "1" contextMenu.show([list.get(currentIndex).url]) rowPressed(index) console.log(list.get(currentIndex).fav) } function saveList() { var trackList = [] if(model.count > 0) { for(var i = 0; i < model.count; ++i) trackList.push(model.get(i).url) playlistDialog.tracks = trackList playlistDialog.open() } } function queueList() { var trackList = [] if(model.count > 0) { for(var i = 0; i < model.count; ++i) trackList.push(model.get(i)) Player.queueTracks(trackList) } } function goToAlbum() { root.pageStack.currentIndex = 1 root.currentView = viewsIndex.albums var item = listView.model.get(listView.currentIndex) albumsView.populateTable(item.album, item.artist) contextMenu.close() } function goToArtist() { root.pageStack.currentIndex = 1 root.currentView = viewsIndex.artists var item = listView.model.get(listView.currentIndex) artistsView.populateTable(undefined, item.artist) contextMenu.close() } function groupBy() { var prop = "undefined" if(group) switch(list.sortBy) { case Tracks.TITLE: prop = "title" break case Tracks.ARTIST: prop = "artist" break case Tracks.ALBUM: prop = "album" break case Tracks.RATE: prop = "rate" break case Tracks.FAV: prop = "fav" break case Tracks.ADDDATE: prop = "adddate" break case Tracks.RELEASEDATE: prop = "releasedate" break } section.property = prop } } diff --git a/view_models/BabeTable/TableMenu.qml b/view_models/BabeTable/TableMenu.qml index 69f5cdf..a518621 100644 --- a/view_models/BabeTable/TableMenu.qml +++ b/view_models/BabeTable/TableMenu.qml @@ -1,220 +1,214 @@ import QtQuick 2.0 import QtQuick 2.9 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 import "../../utils" import ".." import org.kde.mauikit 1.0 as Maui Maui.Menu { id: control property var paths : [] property int rate : 0 property bool fav : false property string starColor : "#FFC107" property string starReg : textColor property string starIcon: "draw-star" signal removeClicked(var paths) signal favClicked(var paths) signal queueClicked(var paths) signal saveToClicked(var paths) signal openWithClicked(var paths) signal editClicked(var paths) signal shareClicked(var paths) signal selectClicked(var paths) signal rateClicked(var paths, int rate) signal colorClicked(var paths, string color) - property alias menuItem : customItems.children + property alias menuItem : control.content Maui.MenuItem { text: !fav ? qsTr("Fav it"): qsTr("UnFav it") onTriggered: { favClicked(paths) close() } } Maui.MenuItem { text: qsTr("Queue") onTriggered: { queueClicked(paths) close() } } Maui.MenuItem { text: qsTr("Save to...") onTriggered: { saveToClicked(paths) close() } } Maui.MenuItem { text: isAndroid ? qsTr("Open with...") : qsTr("Show in folder...") onTriggered: { openWithClicked(paths) close() } } Maui.MenuItem { text: qsTr("Edit...") onTriggered: { editClicked(paths) close() } } Maui.MenuItem { text: qsTr("Share...") onTriggered: { shareClicked(paths) isAndroid ? Maui.Android.shareDialog(paths) : shareDialog.show(paths) close() } } Maui.MenuItem { text: qsTr("Remove") onTriggered: { removeClicked(paths) // listModel.remove(list.currentIndex) close() } } - Column - { - id: customItems - width: parent.implicitWidth - } - Maui.MenuItem { id: starsRow width: parent.width height: iconSizes.medium + space.small RowLayout { anchors.fill: parent Maui.ToolButton { Layout.fillWidth: true Layout.fillHeight: true iconName: starIcon size: iconSizes.medium iconColor: rate >= 1 ? starColor :starReg onClicked: { rate = 1 rateClicked(paths, rate) close() } } Maui.ToolButton { Layout.fillWidth: true Layout.fillHeight: true size: iconSizes.medium iconName: starIcon iconColor: rate >= 2 ? starColor :starReg onClicked: { rate = 2 rateClicked(paths, rate) close() } } Maui.ToolButton { Layout.fillWidth: true Layout.fillHeight: true size: iconSizes.medium iconName: starIcon iconColor: rate >= 3 ? starColor :starReg onClicked: { rate = 3 rateClicked(paths, rate) close() } } Maui.ToolButton { Layout.fillWidth: true Layout.fillHeight: true size: iconSizes.medium iconName: starIcon iconColor: rate >= 4 ? starColor :starReg onClicked: { rate = 4 rateClicked(paths, rate) close() } } Maui.ToolButton { Layout.fillWidth: true Layout.fillHeight: true size: iconSizes.medium iconName: starIcon iconColor: rate >= 5 ? starColor :starReg onClicked: { rate = 5 rateClicked(paths, rate) close() } } } } Maui.MenuItem { id: colorsRow width: parent.width height: iconSizes.medium + space.small ColorTagsBar { anchors.fill: parent onColorClicked: control.colorClicked(paths, color) } } function show(urls) { paths = urls contextMenu.popup() } } diff --git a/widgets/FoldersView.qml b/widgets/FoldersView.qml index 5d8f6b0..8de911a 100644 --- a/widgets/FoldersView.qml +++ b/widgets/FoldersView.qml @@ -1,76 +1,70 @@ import QtQuick 2.0 import QtQuick.Controls 2.2 import org.kde.mauikit 1.0 as Maui import "../view_models/BabeTable" import "../db/Queries.js" as Q StackView { id: stack property alias list : filterList property var tracks : [] property string currentFolder : "" initialItem: Maui.Page { headBarTitle: qsTr("Source folders") headBarExit: false margins: space.large Maui.GridBrowser { anchors.fill: parent id: browser onItemClicked: { stack.push(filterList) var item = browser.model.get(index) filterList.headBarTitle= item.label currentFolder = item.path filter() } } } BabeTable { id: filterList coverArtVisible: true headBarExitIcon: "go-previous" holder.emoji: "qrc:/assets/MusicCloud.png" holder.isMask: false holder.title : "No Tracks!" holder.body: "This source folder seems to be empty!" holder.emojiSize: iconSizes.huge onExit: { stack.pop() } } + Component.onCompleted: populate() + function populate() { browser.model.clear() var folders = bae.getFolders(); if(folders.length > 0) for(var i in folders) browser.model.append(folders[i]) } function filter() { - filterList.model.clear() - tracks = getTracks() - - for(var i in tracks) - filterList.model.append(tracks[i]) - } + var where = "source = \""+currentFolder+"\"" + filterList.list.query = (Q.GET.tracksWhere_.arg(where)) - function getTracks() - { - var where = "sources_url = \""+currentFolder+"\"" - return bae.get(Q.GET.tracksWhere_.arg(where)) } } diff --git a/widgets/PlaylistsView/PlaylistsView.qml b/widgets/PlaylistsView/PlaylistsView.qml index adfe27c..fc3e5db 100644 --- a/widgets/PlaylistsView/PlaylistsView.qml +++ b/widgets/PlaylistsView/PlaylistsView.qml @@ -1,266 +1,258 @@ import QtQuick 2.9 import QtQuick.Layouts 1.3 import QtQuick.Controls 2.2 import org.kde.kirigami 2.2 as Kirigami import org.kde.mauikit 1.0 as Maui import "../../view_models/BabeTable" import "../../view_models" import "../../db/Queries.js" as Q import "../../utils/Help.js" as H Kirigami.PageRow { id: playlistViewRoot property string playlistQuery property alias playlistViewModel : playlistViewModel -// property alias list : _playlistsList -// property alias listModel: _playlistsModel -// property alias listView : playlistViewModel.listView + // property alias list : _playlistsList + // property alias listModel: _playlistsModel + // property alias listView : playlistViewModel.listView signal rowClicked(var track) signal quickPlayTrack(var track) signal playAll(var tracks) signal playSync(var playlist) signal appendAll(var tracks) clip: true separatorVisible: wideMode initialPage: [playlistList, filterList] defaultColumnWidth: Kirigami.Units.gridUnit * 15 interactive: currentIndex === 1 && !wideMode ColumnLayout { id: playlistList clip: true anchors.fill: parent spacing: 0 Layout.margins: 0 SwipeView { id: playlistSwipe Layout.fillHeight: true Layout.fillWidth: true interactive: false clip: true PlaylistsViewModel { id: playlistViewModel onPlaySync: syncAndPlay(index) } BabeList { id: playlistViewModelFilter headBarExitIcon: "go-previous" model : ListModel {} delegate: Maui.LabelDelegate { id: delegate label : tag Connections { target: delegate onClicked: {} } } onExit: playlistSwipe.currentIndex = 0 } } ColorTagsBar { Layout.fillWidth: true height: rowHeightAlt recSize: isMobile ? iconSize : 16 onColorClicked: { populate(Q.GET.colorTracks_.arg(color)) if(!playlistViewRoot.wideMode) playlistViewRoot.currentIndex = 1 } } } BabeTable { id: filterList clip: true anchors.fill: parent quickPlayVisible: true coverArtVisible: true trackRating: true trackDuration: false headBar.visible: !holder.visible headBarExitIcon: "go-previous" headBarExit: !playlistViewRoot.wideMode headBarTitle: playlistViewModel.model.get(playlistViewModel.currentIndex).playlist onExit: if(!playlistViewRoot.wideMode) playlistViewRoot.currentIndex = 0 holder.emoji: "qrc:/assets/Electricity.png" holder.isMask: false holder.title : playlistViewModel.model.get(playlistViewModel.currentIndex).playlist holder.body: "Your playlist is empty,
start adding new music to it" holder.emojiSize: iconSizes.huge headerMenu.menuItem: [ - MenuItem + Maui.MenuItem { enabled: !playlistViewModel.model.get(playlistViewModel.currentIndex).playlistIcon text: "Sync tags" onTriggered: {} }, - MenuItem + Maui.MenuItem { enabled: !playlistViewModel.model.get(playlistViewModel.currentIndex).playlistIcon text: "Play-n-Sync" onTriggered: { filterList.headerMenu.close() syncAndPlay(playlistViewModel.currentIndex) } }, - MenuItem + Maui.MenuItem { enabled: !playlistViewModel.model.get(playlistViewModel.currentIndex).playlistIcon text: "Remove playlist" onTriggered: removePlaylist() } ] // contextMenu.menuItem: [ // MenuItem // { // text: qsTr("Remove from playlist") // onTriggered: // { // bae.removePlaylistTrack(filterList.model.get(filterList.currentIndex).url, playlistViewModel.model.get(playlistViewModel.currentIndex).playlist) // populate(playlistQuery) // } // } // ] section.criteria: ViewSection.FullString section.delegate: Maui.LabelDelegate { label: filterList.section.property === qsTr("stars") ? H.setStars(section) : section isSection: true boldLabel: true labelTxt.font.family: "Material Design Icons" } Connections { target: filterList onRowClicked: playlistViewRoot.rowClicked(filterList.model.get(index)) onQuickPlayTrack: { playlistViewRoot.quickPlayTrack(filterList.model.get(index)) } onPlayAll: playAll(bae.get(playlistQuery)) onAppendAll: appendAll(bae.get(playlistQuery)) onPulled: populate(playlistQuery) } Connections { target: filterList.contextMenu onRemoveClicked: { bae.removePlaylistTrack(url, playlistViewModel.model.get(playlistViewModel.currentIndex).playlist) populate(playlistQuery) } } } function populateExtra(query, title) { playlistSwipe.currentIndex = 1 var res = bae.get(query) playlistViewModelFilter.clearTable() playlistViewModelFilter.headBarTitle = title appendToExtraList(res) } function appendToExtraList(res) { if(res.length>0) for(var i in res) playlistViewModelFilter.model.append(res[i]) } function populate(query) { - if(!playlistViewRoot.wideMode) playlistViewRoot.currentIndex = 1 - playlistQuery = query - filterList.clearTable() - - var tracks = bae.get(query) - - if(tracks.length>0) - for(var i in tracks) - filterList.model.append(tracks[i]) - + playlistViewRoot.playlistQuery = query + filterList.list.query = playlistViewRoot.playlistQuery } function refresh() { for(var i=9; i < playlistViewModel.count; i++) playlistViewModel.model.remove(i) setPlaylists() } function setPlaylists() { var playlists = bae.get(Q.GET.playlists) if(playlists.length > 0) for(var i in playlists) playlistViewModel.model.append(playlists[i]) } function syncAndPlay(index) { if(!playlistViewModel.model.get(index).playlistIcon) playlistViewRoot.playSync(playlistViewModel.model.get(index).playlist) } function removePlaylist() { bae.removePlaylist(playlistViewModel.model.get(playlistViewModel.currentIndex).playlist) filterList.clearTable() refresh() } Component.onCompleted: setPlaylists() } diff --git a/widgets/PlaylistsView/PlaylistsViewModel.qml b/widgets/PlaylistsView/PlaylistsViewModel.qml index 876f888..30b8e93 100644 --- a/widgets/PlaylistsView/PlaylistsViewModel.qml +++ b/widgets/PlaylistsView/PlaylistsViewModel.qml @@ -1,133 +1,140 @@ import QtQuick 2.9 import QtQuick.Layouts 1.3 import QtQuick.Controls 2.2 import org.kde.kirigami 2.2 as Kirigami import org.kde.mauikit 1.0 as Maui import PlaylistsList 1.0 import BaseModel 1.0 +import TracksList 1.0 + import "../../utils" import "../../view_models" import "../../db/Queries.js" as Q import "../../utils/Help.js" as H BabeList { id: playlistListRoot headBarExit: false headBarTitle: "Playlists" Maui.NewDialog { id: newPlaylistDialog title: qsTr("New Playlist...") onFinished: addPlaylist(text) acceptText: qsTr("Create") rejectButton.visible: false } signal playSync(int index) headBar.leftContent: Maui.ToolButton { id : createPlaylistBtn anim : true iconName : "list-add" onClicked : newPlaylistDialog.open() } headBar.rightContent: Maui.ToolButton { iconName: "list-remove" onClicked: removePlaylist() } BaseModel { id: _playlistsModel list: _playlistsList } Playlists { id: _playlistsList } model: _playlistsModel delegate : Maui.ListDelegate { id: delegate width: playlistListRoot.width label: model.playlist Connections { target : delegate onClicked : { currentIndex = index - var playlist = list.get(index).playlist - filterList.section.property = "" + var playlist = _playlistsList.get(index).playlist + filterList.group = false switch(playlist) { case "Most Played": playlistViewRoot.populate(Q.GET.mostPlayedTracks); + filterList.list.sortBy = Tracks.COUNT break; case "Favorites": - filterList.section.property = "stars" - playlistViewRoot.populate(Q.GET.favoriteTracks); + filterList.list.sortBy = Tracks.RATE + filterList.group = true + + playlistViewRoot.populate(Q.GET.favoriteTracks); break; case "Recent": playlistViewRoot.populate(Q.GET.recentTracks); + filterList.list.sortBy = Tracks.ADDDATE + filterList.group = true break; - case "Babes": + case "Favs": playlistViewRoot.populate(Q.GET.babedTracks); break; case "Online": playlistViewRoot.populate(Q.GET.favoriteTracks); break; case "Tags": populateExtra(Q.GET.tags, "Tags") break; case "Relationships": playlistViewRoot.populate(Q.GET.favoriteTracks); break; case "Popular": playlistViewRoot.populate(Q.GET.favoriteTracks); break; case "Genres": populateExtra(Q.GET.genres, "Genres") break; default: playlistViewRoot.populate(Q.GET.playlistTracks_.arg(playlist)); break; } } } } function addPlaylist(text) { var title = text.trim() if(bae.addPlaylist(title)) model.insert(9, {playlist: title}) list.positionViewAtEnd() } } diff --git a/widgets/SearchView/SearchTable.qml b/widgets/SearchView/SearchTable.qml index 82f20b1..1b0d1ca 100644 --- a/widgets/SearchView/SearchTable.qml +++ b/widgets/SearchView/SearchTable.qml @@ -1,127 +1,126 @@ import QtQuick 2.9 import QtQuick.Controls 2.2 import org.kde.kirigami 2.2 as Kirigami import org.kde.mauikit 1.0 as Maui import QtQuick.Layouts 1.3 import "../../view_models" import QtGraphicalEffects 1.0 import "../../view_models/BabeTable" import "../../db/Queries.js" as Q BabeTable { id: searchTable property alias searchInput : searchInput property alias searchTable : searchTable property var searchRes : [] property var savedQueries : [] property bool autoSuggestions : bae.loadSetting("AUTOSUGGESTIONS", "BABE", false) === "true" ? true : false Layout.fillHeight: true Layout.fillWidth: true trackNumberVisible: false headBar.visible: true headBarExit: true headBarExitIcon: "edit-clear" holder.emoji: "qrc:/assets/BugSearch.png" holder.isMask: false holder.title : "No search results!" holder.body: "Try with another query" holder.emojiSize: iconSizes.huge coverArtVisible: true trackDuration: true trackRating: true onExit: clearSearch() footBar.middleContent: Maui.TextField { id: searchInput placeholderText: qsTr("Search...") width: footBar.middleLayout.width * 0.9 onAccepted: runSearch(searchInput.text) // onActiveFocusChanged: if(activeFocus && autoSuggestions) suggestionsPopup.open() onTextEdited: if(autoSuggestions) suggestionsPopup.updateSuggestions() } footBar.leftContent: Maui.ToolButton { visible: true iconName: "view-filter" iconColor: autoSuggestions ? babeColor : textColor onClicked: { autoSuggestions = !autoSuggestions bae.saveSetting("AUTOSUGGESTIONS", autoSuggestions, "BABE") if(!autoSuggestions) suggestionsPopup.close() } } SearchSuggestions { id: suggestionsPopup // focus: false parent: parent // modal: false // closePolicy: Popup.CloseOnPressOutsideParent } Rectangle { visible: suggestionsPopup.visible width: parent.width height: parent.height - searchInput.height color: darkDarkColor z: 999 opacity: 0.5 } function runSearch(searchTxt) { if(searchTxt) if(searchTxt !== searchTable.headBarTitle) { if(savedQueries.indexOf(searchTxt) < 0) { savedQueries.unshift(searchTxt) // suggestionsPopup.model.insert(0, {suggestion: searchInput.text}) bae.saveSetting("QUERIES", savedQueries.join(","), "BABE") } searchTable.headBarTitle = searchTxt var queries = searchTxt.split(",") - searchRes = bae.searchFor(queries) - populate(searchView.searchRes) + searchTable.list.searchQueries(queries) searchTable.forceActiveFocus() suggestionsPopup.close() } } function clearSearch() { searchInput.clear() searchTable.clearTable() searchTable.headBarTitle = "" searchRes = [] suggestionsPopup.close() } function populate(tracks) { searchTable.clearTable() for(var i in tracks) searchTable.model.append(tracks[i]) } } diff --git a/widgets/SettingsView/SourcesDialog.qml b/widgets/SettingsView/SourcesDialog.qml index ba3f74e..c82476b 100644 --- a/widgets/SettingsView/SourcesDialog.qml +++ b/widgets/SettingsView/SourcesDialog.qml @@ -1,119 +1,120 @@ import QtQuick 2.9 import QtQuick.Layouts 1.3 import QtQuick.Controls 2.2 import org.kde.mauikit 1.0 as Maui import "../../view_models" -Maui.Popup +Maui.Dialog { property string pathToRemove : "" maxWidth: unit * 600 maxHeight: unit * 500 - +page.margins: 0 +defaultButtons: false function scanDir(folderUrl) { bae.scanDir(folderUrl) } Maui.Dialog { id: confirmationDialog onAccepted: { if(pathToRemove.length>0) if(bae.removeSource(pathToRemove)) bae.refreshCollection() } } BabeList { id: sources anchors.fill: parent headBar.visible: true - headBarExit: true + headBarExit: false headBarTitle: qsTr("Sources") Layout.fillWidth: true Layout.fillHeight: true width: parent.width onExit: close() ListModel { id: listModel } model: listModel delegate: Maui.LabelDelegate { id: delegate label: url Connections { target: delegate onClicked: sources.currentIndex = index } } headBar.rightContent: [ Maui.ToolButton { iconName: "list-remove" onClicked: { close() var index = sources.currentIndex var url = sources.list.model.get(index).url confirmationDialog.title = "Remove source" if(bae.defaultSources().indexOf(url)<0) { pathToRemove = url confirmationDialog.message = "Are you sure you want to remove the source: \n "+url } else { pathToRemove = "" confirmationDialog.message = url+"\nis a default source and cannot be removed" } confirmationDialog.open() } }, Maui.ToolButton { iconName: "list-add" onClicked: { close() fmDialog.onlyDirs = true fmDialog.show(function(paths) { for(var i in paths) { listModel.append({url: paths[i]}) scanDir(paths[i]) } close() }) } } ] } onOpened: getSources() function getSources() { - sources.clearTable() - var folders = bae.getSourcesFolders() + sources.model.clear() + var folders = bae.getSourceFolders() for(var i in folders) sources.model.append({url : folders[i]}) } }