diff --git a/babe.cpp b/babe.cpp index ae52a56..5e1a830 100644 --- a/babe.cpp +++ b/babe.cpp @@ -1,613 +1,612 @@ #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)); 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; } 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/db/collectionDB.cpp b/db/collectionDB.cpp index 61b919b..0dcb4b2 100644 --- a/db/collectionDB.cpp +++ b/db/collectionDB.cpp @@ -1,1084 +1,1094 @@ /* 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) { 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); } 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::getPlaylists() +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 abaa3c0..581784a 100644 --- a/db/collectionDB.h +++ b/db/collectionDB.h @@ -1,132 +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 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); - Q_INVOKABLE QStringList getPlaylists(); + 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: +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.cpp b/main.cpp index 180f614..4f9e4f2 100644 --- a/main.cpp +++ b/main.cpp @@ -1,142 +1,144 @@ #include #include #include #include #include #include #include #include #include "babe.h" #include "services/local/player.h" #ifdef STATIC_KIRIGAMI #include "./3rdparty/kirigami/src/kirigamiplugin.h" #endif #ifdef STATIC_MAUIKIT #include "./mauikit/src/mauikit.h" #endif #ifdef Q_OS_ANDROID #include #include #include #else #include #include #endif #include "utils/bae.h" #include "services/web/youtube.h" #include "services/web/Spotify/spotify.h" #include "services/local/linking.h" #include "services/local/player.h" #include "models/tracks/tracksmodel.h" +#include "models/playlists/playlistsmodel.h" #include "models/baselist.h" #include "models/basemodel.h" #ifdef Q_OS_ANDROID Q_DECL_EXPORT #endif int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); #ifdef Q_OS_ANDROID QGuiApplication app(argc, argv); QGuiApplication::styleHints()->setMousePressAndHoldInterval(1000); // in [ms] #else QApplication app(argc, argv); #endif app.setApplicationName(BAE::App); app.setApplicationVersion(BAE::Version); app.setWindowIcon(QIcon("qrc:/assets/vvave.png")); app.setDesktopFileName(BAE::App); QCommandLineParser parser; parser.setApplicationDescription(BAE::Description); const QCommandLineOption versionOption = parser.addVersionOption(); parser.process(app); // bool version = parser.isSet(versionOption); // if(version) // { // printf("%s %s\n", qPrintable(QCoreApplication::applicationName()), // qPrintable(QCoreApplication::applicationVersion())); // return 0; // } const QStringList args = parser.positionalArguments(); QStringList urls; if(!args.isEmpty()) urls = args; Babe bae; /* Services */ YouTube youtube; Spotify spotify; QFontDatabase::addApplicationFont(":/assets/materialdesignicons-webfont.ttf"); QQmlApplicationEngine engine; QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, [&]() { qDebug()<<"FINISHED LOADING QML APP"; bae.refreshCollection(); if(!urls.isEmpty()) bae.openUrls(urls); }); auto context = engine.rootContext(); context->setContextProperty("bae", &bae); context->setContextProperty("youtube", &youtube); context->setContextProperty("spotify", &spotify); context->setContextProperty("link", &bae.link); qmlRegisterUncreatableType("BaseList", 1, 0, "BaseList", QStringLiteral("BaseList should not be created in QML")); qmlRegisterType("BaseModel", 1, 0, "BaseModel"); qmlRegisterType("TracksList", 1, 0, "Tracks"); + qmlRegisterType("PlaylistsList", 1, 0, "Playlists"); qmlRegisterType("Player", 1, 0, "Player"); qmlRegisterUncreatableMetaObject( LINK::staticMetaObject, // static meta object "Link.Codes", // import statement (can be any string) 1, 0, // major and minor version of the import "LINK", // name in QML (does not have to match C++ name) "Error: only enums" // error in case someone tries to create a MyNamespace object ); #ifdef STATIC_KIRIGAMI KirigamiPlugin::getInstance().registerTypes(); #endif #ifdef STATIC_MAUIKIT MauiKit::getInstance().registerTypes(); #endif #ifdef Q_OS_ANDROID QtWebView::initialize(); #else // if(QQuickStyle::availableStyles().contains("nomad")) // QQuickStyle::setStyle("nomad"); if(!BAE::isMobile()) QtWebEngine::initialize(); #endif engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); if (engine.rootObjects().isEmpty()) return -1; return app.exec(); } diff --git a/models/baselist.h b/models/baselist.h index d082446..6c59cf9 100644 --- a/models/baselist.h +++ b/models/baselist.h @@ -1,74 +1,77 @@ #ifndef BASELIST_H #define BASELIST_H #ifdef STATIC_MAUIKIT #include "fm.h" #include "fmh.h" #else #include #include #endif class BaseList : public QObject { Q_OBJECT public: explicit BaseList(QObject *parent = nullptr); //* To be overrided *// virtual FMH::MODEL_LIST items() const {return FMH::MODEL_LIST({{}});} protected: signals: void preItemAppended(); void postItemAppended(); + + void preItemAppendedAt(int index); + void preItemRemoved(int index); void postItemRemoved(); void updateModel(int index, QVector roles); void preListChanged(); void postListChanged(); public slots: virtual QVariantMap get(const int &index) const { Q_UNUSED(index); return QVariantMap(); } virtual bool update(const int &index, const QVariant &value, const int &role) { Q_UNUSED(index); Q_UNUSED(value); Q_UNUSED(role); return false; } virtual bool update(const QVariantMap &data, const int &index) { Q_UNUSED(index); Q_UNUSED(data); return false; } virtual bool update(const FMH::MODEL &data) { Q_UNUSED(data); return false; } virtual bool insert(const QVariantMap &map) { Q_UNUSED(map); return false; } virtual bool remove(const int &index) { Q_UNUSED(index); return false; } }; #endif // BASELIST_H diff --git a/models/basemodel.cpp b/models/basemodel.cpp index 3cfab03..2476441 100644 --- a/models/basemodel.cpp +++ b/models/basemodel.cpp @@ -1,113 +1,118 @@ #include "basemodel.h" #include "baselist.h" BaseModel::BaseModel(QObject *parent) : QAbstractListModel(parent), mList(nullptr) {} int BaseModel::rowCount(const QModelIndex &parent) const { if (parent.isValid() || !mList) return 0; return mList->items().size(); } QVariant BaseModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || !mList) return QVariant(); return mList->items().at(index.row())[static_cast(role)]; } bool BaseModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!mList) return false; if (mList->update(index.row(), value, role)) { emit dataChanged(index, index, QVector() << role); return true; } return false; } Qt::ItemFlags BaseModel::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::NoItemFlags; return Qt::ItemIsEditable; // FIXME: Implement me! } QHash BaseModel::roleNames() const { QHash names; for(auto key : FMH::MODEL_NAME.keys()) names[key] = QString(FMH::MODEL_NAME[key]).toUtf8(); return names; } BaseList *BaseModel::getList() const { return this->mList; } void BaseModel::setList(BaseList *value) { beginResetModel(); if(mList) mList->disconnect(this); mList = value; if(mList) { connect(this->mList, &BaseList::preItemAppended, this, [=]() { const int index = mList->items().size(); beginInsertRows(QModelIndex(), index, index); }); connect(this->mList, &BaseList::postItemAppended, this, [=]() { endInsertRows(); }); + connect(this->mList, &BaseList::preItemAppendedAt, this, [=](int index) + { + beginInsertRows(QModelIndex(), index, index); + }); + connect(this->mList, &BaseList::preItemRemoved, this, [=](int index) { beginRemoveRows(QModelIndex(), index, index); }); connect(this->mList, &BaseList::postItemRemoved, this, [=]() { endRemoveRows(); }); connect(this->mList, &BaseList::updateModel, this, [=](int index, QVector roles) { emit this->dataChanged(this->index(index), this->index(index), roles); }); connect(this->mList, &BaseList::preListChanged, this, [=]() { beginResetModel(); }); connect(this->mList, &BaseList::postListChanged, this, [=]() { endResetModel(); }); } endResetModel(); } QVariantMap BaseModel::get(const int &index) const { return this->mList->get(index); } diff --git a/models/playlists/playlistsmodel.cpp b/models/playlists/playlistsmodel.cpp new file mode 100644 index 0000000..f7f969d --- /dev/null +++ b/models/playlists/playlistsmodel.cpp @@ -0,0 +1,201 @@ +#include "playlistsmodel.h" +#include "db/collectionDB.h" + +PlaylistsModel::PlaylistsModel(QObject *parent) : BaseList(parent) +{ + this->db = CollectionDB::getInstance(); + this->setList(); +} + +FMH::MODEL_LIST PlaylistsModel::items() const +{ + return this->list; +} + +void PlaylistsModel::setSortBy(const SORTBY &sort) +{ + if(this->sort == sort) + return; + + this->sort = sort; + + this->preListChanged(); + this->sortList(); + this->postListChanged(); + emit this->sortByChanged(); +} + +PlaylistsModel::SORTBY PlaylistsModel::getSortBy() const +{ + return this->sort; +} + +void PlaylistsModel::sortList() +{ + const auto key = static_cast(this->sort); + qDebug()<< "SORTING LIST BY"<< this->sort; + qSort(this->list.begin() + this->defaultPlaylists().size(), this->list.end(), [key](const FMH::MODEL &e1, const FMH::MODEL &e2) -> bool + { + auto role = key; + + switch(role) + { + 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: + { + 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 PlaylistsModel::setList() +{ + qDebug()<< "trying to set playlists list"; + emit this->preListChanged(); + + this->list = this->defaultPlaylists(); +// this->list << this->db->getPlaylists(); + +// this->sortList(); + emit this->postListChanged(); +} + +FMH::MODEL_LIST PlaylistsModel::defaultPlaylists() +{ + return FMH::MODEL_LIST { + { + {FMH::MODEL_KEY::PLAYLIST, "Most Played"}, + {FMH::MODEL_KEY::ICON, "view-media-playcount"}, + {FMH::MODEL_KEY::ADDDATE,QDateTime::currentDateTime().toString(Qt::DateFormat::TextDate)} + }, + + { + {FMH::MODEL_KEY::PLAYLIST, "Favorites"}, + {FMH::MODEL_KEY::ICON, "view-media-favorite"}, + {FMH::MODEL_KEY::ADDDATE,QDateTime::currentDateTime().toString(Qt::DateFormat::TextDate)} + }, + + { + {FMH::MODEL_KEY::PLAYLIST, "Recent"}, + {FMH::MODEL_KEY::ICON, "view-media-recent"}, + {FMH::MODEL_KEY::ADDDATE,QDateTime::currentDateTime().toString(Qt::DateFormat::TextDate)} + }, + + { + {FMH::MODEL_KEY::PLAYLIST, "Favs"}, + {FMH::MODEL_KEY::ICON, "love"}, + {FMH::MODEL_KEY::ADDDATE,QDateTime::currentDateTime().toString(Qt::DateFormat::TextDate)} + }, + + { + {FMH::MODEL_KEY::PLAYLIST, "Online"}, + {FMH::MODEL_KEY::ICON, "internet-services"}, + {FMH::MODEL_KEY::ADDDATE,QDateTime::currentDateTime().toString(Qt::DateFormat::TextDate)} + }, + + { + {FMH::MODEL_KEY::PLAYLIST, "Online"}, + {FMH::MODEL_KEY::ICON, "internet-services"}, + {FMH::MODEL_KEY::ADDDATE,QDateTime::currentDateTime().toString(Qt::DateFormat::TextDate)} + }, + + { + {FMH::MODEL_KEY::PLAYLIST, "Tags"}, + {FMH::MODEL_KEY::ICON, "tag"}, + {FMH::MODEL_KEY::ADDDATE,QDateTime::currentDateTime().toString(Qt::DateFormat::TextDate)} + }, + + { + {FMH::MODEL_KEY::PLAYLIST, "Relationships"}, + {FMH::MODEL_KEY::ICON, "view-media-similarartists"}, + {FMH::MODEL_KEY::ADDDATE,QDateTime::currentDateTime().toString(Qt::DateFormat::TextDate)} + }, + + { + {FMH::MODEL_KEY::PLAYLIST, "Popular"}, + {FMH::MODEL_KEY::ICON, "view-media-chart"}, + {FMH::MODEL_KEY::ADDDATE,QDateTime::currentDateTime().toString(Qt::DateFormat::TextDate)} + }, + + { + {FMH::MODEL_KEY::PLAYLIST, "Genres"}, + {FMH::MODEL_KEY::ICON, "view-media-genre"}, + {FMH::MODEL_KEY::ADDDATE,QDateTime::currentDateTime().toString(Qt::DateFormat::TextDate)} + } + }; +} + +QVariantMap PlaylistsModel::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 PlaylistsModel::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()); + + this->list << model; + + emit this->postItemAppended(); +} + +void PlaylistsModel::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(); +} diff --git a/models/playlists/playlistsmodel.h b/models/playlists/playlistsmodel.h new file mode 100644 index 0000000..727bb16 --- /dev/null +++ b/models/playlists/playlistsmodel.h @@ -0,0 +1,46 @@ +#ifndef PLAYLISTSMODEL_H +#define PLAYLISTSMODEL_H + +#include "models/baselist.h" + +class CollectionDB; +class PlaylistsModel : public BaseList +{ + Q_OBJECT + Q_PROPERTY(PlaylistsModel::SORTBY sortBy READ getSortBy WRITE setSortBy NOTIFY sortByChanged) + +public: + + enum SORTBY : uint_fast8_t + { + ADDDATE = FMH::MODEL_KEY::ADDDATE, + TITLE = FMH::MODEL_KEY::TITLE + }; Q_ENUM(SORTBY) + + explicit PlaylistsModel(QObject *parent = nullptr); + + FMH::MODEL_LIST items() const override; + + void setSortBy(const PlaylistsModel::SORTBY &sort); + PlaylistsModel::SORTBY getSortBy() const; + +private: + CollectionDB *db; + FMH::MODEL_LIST list; + void sortList(); + void setList(); + + FMH::MODEL_LIST defaultPlaylists(); + + PlaylistsModel::SORTBY sort = PlaylistsModel::SORTBY::ADDDATE; + +signals: + void sortByChanged(); + +public slots: + QVariantMap get(const int &index) const override; + void append(const QVariantMap &item); + void append(const QVariantMap &item, const int &at); +}; + +#endif // PLAYLISTSMODEL_H diff --git a/models/tracks/tracksmodel.cpp b/models/tracks/tracksmodel.cpp index 8defb36..8d67d20 100644 --- a/models/tracks/tracksmodel.cpp +++ b/models/tracks/tracksmodel.cpp @@ -1,174 +1,197 @@ #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() { 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].toDouble() > e2[role].toDouble()) 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()); this->list << model; 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(); +} + 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)) { - list[index][FMH::MODEL_KEY::COLOR] = 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)) { - list[index].insert(FMH::MODEL_KEY::FAV, value ? "1" : "0"); + this->list[index][FMH::MODEL_KEY::FAV] = value ? "1" : "0"; emit this->updateModel(index, {FMH::MODEL_KEY::FAV}); return true; } return false; } diff --git a/models/tracks/tracksmodel.h b/models/tracks/tracksmodel.h index 08f16b0..227c868 100644 --- a/models/tracks/tracksmodel.h +++ b/models/tracks/tracksmodel.h @@ -1,62 +1,60 @@ -#ifndef TRACKSMODEL_H +#ifndef TRACKSMODEL_H #define TRACKSMODEL_H #include #include "models/baselist.h" -#include "db/collectionDB.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 }; 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; - bool addDoc(const FMH::MODEL &doc); - void refreshCollection(); - 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); bool color(const int &index, const QString &color); bool fav(const int &index, const bool &value); }; #endif // TRACKSMODEL_H diff --git a/settings/BabeSettings.cpp b/settings/BabeSettings.cpp index db20e2a..a614091 100644 --- a/settings/BabeSettings.cpp +++ b/settings/BabeSettings.cpp @@ -1,129 +1,129 @@ /* 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 "BabeSettings.h" #include "../db/collectionDB.h" #include "../utils/brain.h" #include "../services/local/socket.h" #include "../services/local/youtubedl.h" #include "../utils/babeconsole.h" BabeSettings::BabeSettings(QObject *parent) : QObject(parent) { this->connection = CollectionDB::getInstance(); this->brainDeamon = new Brain; this->ytFetch = new youtubedl(this); this->babeSocket = new Socket(static_cast(BAE::BabePort.toInt()), this); #if (defined (Q_OS_LINUX) && !defined (Q_OS_ANDROID)) if(!BAE::fileExists(BAE::NotifyDir+"/vvave.notifyrc")) { QFile knotify(":/assets/vvave.notifyrc"); if(knotify.copy(BAE::NotifyDir+"/vvave.notifyrc")) bDebug::Instance()->msg("the knotify file got copied"); } #endif QDir cachePath_dir(BAE::CachePath); QDir youtubeCache_dir(BAE::YoutubeCachePath); if (!cachePath_dir.exists()) cachePath_dir.mkpath("."); if (!youtubeCache_dir.exists()) youtubeCache_dir.mkpath("."); connect(this->ytFetch, &youtubedl::done, [this]() { this->startBrainz(true, BAE::SEG::THREE); }); connect(this->babeSocket, &Socket::message, this, &BabeSettings::fetchYoutubeTrack); connect(this->babeSocket, &Socket::connected, [this](const int &index) { - auto playlists = this->connection->getPlaylists(); + auto playlists = this->connection->getPlaylistsList(); bDebug::Instance()->msg("Sending playlists to socket: "+playlists.join(", ")); this->babeSocket->sendMessageTo(index, playlists.join(",")); }); connect(this->brainDeamon, &Brain::finished, [this]() { emit this->brainFinished(); }); connect(this->brainDeamon, &Brain::done, [this](const TABLE type) { emit this->refreshATable(type); }); connect(&this->fileLoader, &FileLoader::finished, [this](int size) { bDebug::Instance()->msg("Finished inserting into DB "+QString::number(size)+" tracks"); if(size > 0) this->startBrainz(true, BAE::SEG::HALF); // else // this->startBrainz(BAE::loadSettings("AUTO", "BRAINZ", true).toBool(), BAE::SEG::TWO); emit refreshTables(size); }); connect(this, &BabeSettings::collectionPathChanged, this, &BabeSettings::populateDB); } BabeSettings::~BabeSettings() { qDebug()<<"DELETING SETTINGS"; delete brainDeamon; } void BabeSettings::refreshCollection() { if(this->connection->getSourcesFolders().isEmpty()) this->populateDB(BAE::defaultSources); else this->populateDB(this->connection->getSourcesFolders()); } void BabeSettings::fetchYoutubeTrack(const QString &message) { this->ytFetch->fetch(message); } void BabeSettings::startBrainz(const bool &on, const uint &speed) { qDebug()<< "Starting Brainz with interval: "<< speed << on; this->brainDeamon->setInterval(speed); if(on) this->brainDeamon->start(); else this->brainDeamon->pause(); } void BabeSettings::populateDB(const QStringList &paths) { auto newPaths = paths; for(auto path : newPaths) if(path.startsWith("file://")) path.replace("file://", ""); fileLoader.requestPaths(newPaths); } diff --git a/utils/Player.js b/utils/Player.js index 31c2ba9..4b7d676 100644 --- a/utils/Player.js +++ b/utils/Player.js @@ -1,263 +1,263 @@ Qt.include("Icons.js") function playTrack(index) { if((index < mainPlaylist.listView.count) && (mainPlaylist.listView.count > 0) && (index > -1)) { currentTrack = mainPlaylist.list.get(index) if(typeof(currentTrack) === "undefined") return if(bae.fileExists(currentTrack.url)) { player.url = currentTrack.url; player.playing = true var artwork = currentTrack.artwork currentArtwork = artwork && artwork.length > 0 && artwork !== "NONE"? artwork : bae.loadCover(currentTrack.url) currentTrack.artwork = currentArtwork currentBabe = bae.trackBabe(currentTrack.url) progressBar.enabled = true if(!isMobile) { root.title = currentTrack.title + " - " +currentTrack.artist if(!root.active) bae.notifySong(currentTrack.url) } // if(currentTrack.lyrics.length < 1) // bae.trackLyrics(currentTrack.url) // root.mainPlaylist.infoView.wikiAlbum = bae.albumWiki(root.mainPlaylist.currentTrack.album,root.mainPlaylist.currentTrack.artist) // root.mainPlaylist.infoView.wikiArtist = bae.artistWiki(root.mainPlaylist.currentTrack.artist) // // root.mainPlaylist.infoView.artistHead = bae.artistArt(root.mainPlaylist.currentTrack.artist) }else missingAlert(currentTrack) } } function queueTracks(tracks) { if(tracks) { if(tracks.length > 0) { onQueue++ console.log(onQueue) appendTracksAt(tracks, currentTrackIndex+1) bae.notify("Queue", tracks.length + " tracks added put on queue") } } } function setLyrics(lyrics) { currentTrack.lyrics = lyrics mainPlaylist.infoView.lyricsText.text = lyrics } function stop() { player.stop() progressBar.value = 0 progressBar.enabled = false root.title = "Babe..." } function pauseTrack() { player.playing = false } function resumeTrack() { if(!player.play() && !mainlistEmpty) playAt(0) } function nextTrack() { if(!mainlistEmpty) { var next = 0 if(isShuffle && onQueue === 0) next = shuffle() else next = currentTrackIndex+1 >= mainPlaylist.listView.count? 0 : currentTrackIndex+1 prevTrackIndex = mainPlaylist.listView.currentIndex playAt(next) if(onQueue > 0) { onQueue-- console.log(onQueue) } } } function previousTrack() { if(!mainlistEmpty>0) { var previous = previous = currentTrackIndex-1 >= 0 ? mainPlaylist.listView.currentIndex-1 : currentTrackIndex-1 prevTrackIndex = mainPlaylist.listView.currentIndex playAt(previous) } } function shuffle() { var pos = Math.floor(Math.random() * mainPlaylist.list.count) return pos } function playAt(index) { if((index < mainPlaylist.listView.count) && (mainPlaylist.listView.count > 0) && (index > -1)) { currentTrackIndex = index mainPlaylist.listView.currentIndex = currentTrackIndex mainPlaylist.albumsRoll.positionAlbum(currentTrackIndex) playTrack(currentTrackIndex) } } function quickPlay(track) { // root.pageStack.currentIndex = 0 appendTrack(track) playAt(mainPlaylist.listView.count-1) mainPlaylist.listView.positionViewAtEnd() mainPlaylist.albumsRoll.positionViewAtEnd() } function appendTracksAt(tracks, at) { if(tracks) for(var i in tracks) - mainPlaylist.list.model.insert(parseInt(at)+parseInt(i), tracks[i]) + mainPlaylist.list.append(tracks[i], parseInt(at)+parseInt(i)) } function appendTrack(track) { if(track) { mainPlaylist.list.append(track) animFooter.running = true if(sync === true) { infoMsgAnim() addToPlaylist([track.url], syncPlaylist) } } // if(track) // { // var empty = root.mainPlaylist.list.count // if((empty > 0 && track.url !== root.mainPlaylist.list.model.get(root.mainPlaylist.list.count-1).url) || empty === 0) // { // root.mainPlaylist.list.model.append(track) // if(empty === 0 && root.mainPlaylist.list.count>0) // playAt(0) // } // } } function addTrack(track) { if(track) { appendTrack(track) mainPlaylist.listView.positionViewAtEnd() } } function appendAll(tracks) { if(tracks) { for(var i in tracks) appendTrack(tracks[i]) mainPlaylist.listView.positionViewAtEnd() } } function savePlaylist() { var list = [] var n = mainPlaylist.list.count n = n > 15 ? 15 : n for(var i=0 ; i < n; i++) { var url = mainPlaylist.list.get(i).url list.push(url) } bae.savePlaylist(list) bae.savePlaylistPos(mainPlaylist.listView.currentIndex) } function clearOutPlaylist() { mainPlaylist.table.clearTable() stop() } function cleanPlaylist() { var urls = [] for(var i = 0; i < mainPlaylist.listView.count; i++) { var url = mainPlaylist.list.get(i).url if(urls.indexOf(url)<0) urls.push(url) else mainPlaylist.list.remove(i) } } function playAll(tracks) { if(tracks.length > 0) { sync = false syncPlaylist = "" infoMsg = "" mainPlaylist.table.clearTable() pageStack.currentIndex = 0 for(var i in tracks) appendTrack(tracks[i]) // root.mainPlaylist.list.currentIndex = 0 // playTrack(root.mainPlaylist.list.model.get(0)) mainPlaylist.listView.positionViewAtBeginning() playAt(0) } } function babeTrack(url, value) { bae.babeTrack(url, value) } function addToPlaylist(urls, playlist) { if(urls.length > 0) { bae.trackPlaylist(urls, playlist) bae.notify(playlist, urls.length + " tracks added to the playlist:\n"+urls.join("\n")) } } diff --git a/view_models/BabeList.qml b/view_models/BabeList.qml index adb722a..dd08efc 100644 --- a/view_models/BabeList.qml +++ b/view_models/BabeList.qml @@ -1,101 +1,101 @@ import QtQuick 2.9 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 import org.kde.kirigami 2.2 as Kirigami import QtQuick.Controls.Material 2.1 import org.kde.mauikit 1.0 as Maui Maui.Page { id: control property alias listView : babeList property alias model : babeList.model property alias delegate : babeList.delegate property alias count : babeList.count property alias currentIndex : babeList.currentIndex property alias currentItem : babeList.currentItem property alias holder : holder property alias section : babeList.section property bool wasPulled : false signal pulled() focus: true margins: 0 function clearTable() { - list.model.clear() + model.clear() } Maui.Holder { id: holder visible: babeList.count === 0 focus: true } ListView { id: babeList anchors.fill: parent clip: true highlight: Rectangle { width: babeList.width height: babeList.currentItem.height color: highlightColor } focus: true interactive: true highlightFollowsCurrentItem: true highlightMoveDuration: 0 keyNavigationWraps: true keyNavigationEnabled : true Keys.onUpPressed: decrementCurrentIndex() Keys.onDownPressed: incrementCurrentIndex() Keys.onReturnPressed: rowClicked(currentIndex) Keys.onEnterPressed: quickPlayTrack(currentIndex) boundsBehavior: !isMobile? Flickable.StopAtBounds : Flickable.OvershootBounds flickableDirection: Flickable.AutoFlickDirection // snapMode: isMobile? ListView.SnapToItem : ListView.NoSnap addDisplaced: Transition { NumberAnimation { properties: "x,y"; duration: 100 } } ScrollBar.vertical:BabeScrollBar { visible: !isMobile} onContentYChanged: { if(contentY < -120) wasPulled = true if(contentY == 0 && wasPulled) { pulled(); wasPulled = false} } // Scroll is too fast on desktop, see QTBUG-56075 // https://bugreports.qt.io/browse/QTBUG-56075 ScrollHelper { enabled: !isMobile id: scrollHelper flickable: babeList anchors.fill: babeList } } } diff --git a/view_models/BabeTable/BabeTable.qml b/view_models/BabeTable/BabeTable.qml index c4d4c26..7ebf8a0 100644 --- a/view_models/BabeTable/BabeTable.qml +++ b/view_models/BabeTable/BabeTable.qml @@ -1,413 +1,414 @@ 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("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: H.queueIt(paths) + onQueueClicked: Player.queueTracks([list.get(listView.currentIndex)]) + onSaveToClicked: { playlistDialog.tracks = paths playlistDialog.open() } onOpenWithClicked: bae.showFolder(paths) onRemoveClicked: { listModel.remove(listView.currentIndex) } onRateClicked: { var value = H.rateIt(paths, rate) listView.currentItem.rate(H.setStars(value)) listView.model.get(listView.currentIndex).rate = value } onColorClicked: { list.color(listView.currentIndex, color); } } listView.highlightFollowsCurrentItem: false listView.highlightMoveDuration: 0 listView.highlight: Rectangle { } section.criteria: ViewSection.FullString section.delegate: Maui.LabelDelegate { 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/vvave.pro b/vvave.pro index 73c9951..239db00 100644 --- a/vvave.pro +++ b/vvave.pro @@ -1,138 +1,140 @@ QT += quick QT += multimedia QT += sql QT += websockets QT += network QT += xml QT += qml QT += widgets QT += quickcontrols2 TARGET = vvave TEMPLATE = app CONFIG += ordered CONFIG += c++11 linux:unix:!android { message(Building for Linux KDE) include($$PWD/kde/kde.pri) LIBS += -lMauiKit } else:android { message(Building helpers for Android) QT += androidextras webview include($$PWD/3rdparty/taglib.pri) include($$PWD/mauikit/mauikit.pri) include($$PWD/3rdparty/kirigami/kirigami.pri) include($$PWD/android-openssl.pri) DEFINES += STATIC_KIRIGAMI } else { message("Unknown configuration") } include(pulpo/pulpo.pri) # The following define makes your compiler emit warnings if you use # any feature of Qt which as been marked deprecated (the exact warnings # depend on your compiler). Please consult the documentation of the # deprecated API in order to know how to port your code away from it. DEFINES += QT_DEPRECATED_WARNINGS # You can also make your code fail to compile if you use deprecated APIs. # In order to do so, uncomment the following line. # You can also select to disable deprecated APIs only up to a certain version of Qt. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 SOURCES += main.cpp \ db/collectionDB.cpp \ services/local/taginfo.cpp \ services/local/player.cpp \ utils/brain.cpp \ services/local/socket.cpp \ services/web/youtube.cpp \ babe.cpp \ settings/BabeSettings.cpp \ db/conthread.cpp \ services/web/babeit.cpp \ utils/babeconsole.cpp \ services/local/youtubedl.cpp \ services/local/linking.cpp \ settings/fileloader.cpp \ services/web/Spotify/spotify.cpp \ models/tracks/tracksmodel.cpp \ models/basemodel.cpp \ - models/baselist.cpp + models/baselist.cpp \ + models/playlists/playlistsmodel.cpp RESOURCES += qml.qrc \ # Additional import path used to resolve QML modules in Qt Creator's code model QML_IMPORT_PATH = # Additional import path used to resolve QML modules just for Qt Quick Designer QML_DESIGNER_IMPORT_PATH = DISTFILES += \ db/script.sql \ CMakeLists.txt HEADERS += \ db/collectionDB.h \ utils/bae.h \ settings/fileloader.h \ services/local/taginfo.h \ services/local/player.h \ utils/brain.h \ services/local/socket.h \ services/web/youtube.h \ babe.h \ settings/BabeSettings.h \ db/conthread.h \ services/web/babeit.h \ utils/babeconsole.h \ utils/singleton.h \ services/local/youtubedl.h \ services/local/linking.h \ services/web/Spotify/spotify.h \ models/tracks/tracksmodel.h \ models/basemodel.h \ - models/baselist.h + models/baselist.h \ + models/playlists/playlistsmodel.h include(install.pri) #TAGLIB #INCLUDEPATH += /usr/include/python3.6m #LIBS += -lpython3.6m #defineReplace(copyToDir) { # files = $$1 # DIR = $$2 # LINK = # for(FILE, files) { # LINK += $$QMAKE_COPY $$shell_path($$FILE) $$shell_path($$DIR) $$escape_expand(\\n\\t) # } # return($$LINK) #} #defineReplace(copyToBuilddir) { # return($$copyToDir($$1, $$OUT_PWD)) #} ## Copy the binary files dependent on the system architecture #unix:!macx { # message("Linux") # QMAKE_POST_LINK += $$copyToBuilddir($$PWD/library/cat) #} diff --git a/widgets/PlaylistsView/PlaylistsView.qml b/widgets/PlaylistsView/PlaylistsView.qml index d12fae5..adfe27c 100644 --- a/widgets/PlaylistsView/PlaylistsView.qml +++ b/widgets/PlaylistsView/PlaylistsView.qml @@ -1,261 +1,266 @@ 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 + 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 { enabled: !playlistViewModel.model.get(playlistViewModel.currentIndex).playlistIcon text: "Sync tags" onTriggered: {} }, MenuItem { enabled: !playlistViewModel.model.get(playlistViewModel.currentIndex).playlistIcon text: "Play-n-Sync" onTriggered: { filterList.headerMenu.close() syncAndPlay(playlistViewModel.currentIndex) } }, 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]) } 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 d47fd3a..876f888 100644 --- a/widgets/PlaylistsView/PlaylistsViewModel.qml +++ b/widgets/PlaylistsView/PlaylistsViewModel.qml @@ -1,134 +1,133 @@ 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 "../../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() } - ListModel + + BaseModel + { + id: _playlistsModel + list: _playlistsList + } + + Playlists { - id: playlistListModel - - ListElement { playlist: qsTr("Most Played"); icon: "view-media-playcount"; /*query: Q.Query.mostPlayedTracks*/ } - ListElement { playlist: qsTr("Favorites"); icon: "view-media-favorite"} - ListElement { playlist: qsTr("Recent"); icon: "view-media-recent"} - ListElement { playlist: qsTr("Babes"); icon: "love"} - ListElement { playlist: qsTr("Online"); icon: "internet-services"} - ListElement { playlist: qsTr("Tags"); icon: "tag"} - ListElement { playlist: qsTr("Relationships"); icon: "view-media-similarartists"} - ListElement { playlist: qsTr("Popular"); icon: "view-media-chart"} - ListElement { playlist: qsTr("Genres"); icon: "view-media-genre"} + id: _playlistsList } - model: playlistListModel + model: _playlistsModel delegate : Maui.ListDelegate { id: delegate width: playlistListRoot.width label: model.playlist Connections { target : delegate onClicked : { currentIndex = index - var playlist = playlistListModel.get(index).playlist + var playlist = list.get(index).playlist filterList.section.property = "" switch(playlist) { case "Most Played": playlistViewRoot.populate(Q.GET.mostPlayedTracks); break; case "Favorites": filterList.section.property = "stars" playlistViewRoot.populate(Q.GET.favoriteTracks); break; case "Recent": playlistViewRoot.populate(Q.GET.recentTracks); break; case "Babes": 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() } }