From f8323c39367b619dfdfdf8f3b4ed4aaf648dab66 Mon Sep 17 00:00:00 2001 From: Herwig Birke Date: Tue, 28 Mar 2023 10:30:50 +0200 Subject: [PATCH] migrate --- .gitignore | 103 ++++++----- cmainwindow.cpp | 52 ++++++ cmainwindow.h | 21 +++ cmainwindow.ui | 22 +++ csubsonic.cpp | 464 +++++++++++++++++++++++++++++++++++++++++++++++ csubsonic.h | 102 +++++++++++ main.cpp | 24 +++ subJuke.pro | 31 ++++ subJuke_de_AT.ts | 3 + 9 files changed, 781 insertions(+), 41 deletions(-) create mode 100644 cmainwindow.cpp create mode 100644 cmainwindow.h create mode 100644 cmainwindow.ui create mode 100644 csubsonic.cpp create mode 100644 csubsonic.h create mode 100644 main.cpp create mode 100644 subJuke.pro create mode 100644 subJuke_de_AT.ts diff --git a/.gitignore b/.gitignore index f147edf..fab7372 100644 --- a/.gitignore +++ b/.gitignore @@ -1,52 +1,73 @@ -# C++ objects and libs -*.slo -*.lo -*.o +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave *.a -*.la -*.lai +*.core +*.moc +*.o +*.obj +*.orig +*.rej *.so *.so.* -*.dll -*.dylib - -# Qt-es -object_script.*.Release -object_script.*.Debug -*_plugin_import.cpp +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc /.qmake.cache /.qmake.stash -*.pro.user -*.pro.user.* -*.qbs.user -*.qbs.user.* -*.moc -moc_*.cpp -moc_*.h -qrc_*.cpp -ui_*.h -*.qmlc -*.jsc -Makefile* -*build-* -*.qm -*.prl -# Qt unit tests -target_wrapper.* +# qtcreator generated files +*.pro.user* -# QtCreator -*.autosave +# xemacs temporary files +*.flc -# QtCreator Qml -*.qmlproject.user -*.qmlproject.user.* +# Vim temporary files +.*.swp -# QtCreator CMake -CMakeLists.txt.user* +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* -# QtCreator 4.8< compilation database -compile_commands.json +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe -# QtCreator local machine specific files for imported projects -*creator.user* diff --git a/cmainwindow.cpp b/cmainwindow.cpp new file mode 100644 index 0000000..58149de --- /dev/null +++ b/cmainwindow.cpp @@ -0,0 +1,52 @@ +#include "cmainwindow.h" +#include "ui_cmainwindow.h" + +#include "csubsonic.h" + +#include + + +cMainWindow::cMainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::cMainWindow) +{ + ui->setupUi(this); + + cSubsonic* subsonic = new cSubsonic("https://music.windesign.at", "birkeh", "WeissIchNicht8"); + + bool licenseValid; + QString licenseEmail; + QDateTime licenseExpires; + QMap musicFolders; + QMap shortcuts; + QStringList indexes; + QMap indexArtists; + + QString artistName; + qint32 artistPlayCount; + QMap albums; + + qint32 albumParent; + QString albumName; + qint32 albumPlayCount; + QMap tracks; + + QMap genres; + + QMap artists; + + subsonic->getLicense(licenseValid, licenseEmail, licenseExpires); + subsonic->getMusicFolders(musicFolders); + subsonic->getIndexes(shortcuts, indexes, indexArtists); + subsonic->getMusicDirectory(230, artistName, artistPlayCount, albums); + subsonic->getMusicDirectory(885, albumParent, albumName, albumPlayCount, tracks); + subsonic->getGenres(genres); + subsonic->getArtists(indexes, artists); + + delete subsonic; +} + +cMainWindow::~cMainWindow() +{ + delete ui; +} diff --git a/cmainwindow.h b/cmainwindow.h new file mode 100644 index 0000000..65f0642 --- /dev/null +++ b/cmainwindow.h @@ -0,0 +1,21 @@ +#ifndef CMAINWINDOW_H +#define CMAINWINDOW_H + +#include + +QT_BEGIN_NAMESPACE +namespace Ui { class cMainWindow; } +QT_END_NAMESPACE + +class cMainWindow : public QMainWindow +{ + Q_OBJECT + +public: + cMainWindow(QWidget *parent = nullptr); + ~cMainWindow(); + +private: + Ui::cMainWindow *ui; +}; +#endif // CMAINWINDOW_H diff --git a/cmainwindow.ui b/cmainwindow.ui new file mode 100644 index 0000000..7dddbe4 --- /dev/null +++ b/cmainwindow.ui @@ -0,0 +1,22 @@ + + + cMainWindow + + + + 0 + 0 + 800 + 600 + + + + cMainWindow + + + + + + + + diff --git a/csubsonic.cpp b/csubsonic.cpp new file mode 100644 index 0000000..0ea94ad --- /dev/null +++ b/csubsonic.cpp @@ -0,0 +1,464 @@ +#include "csubsonic.h" + +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#include + + +cSubsonic::cSubsonic() : + m_server(""), + m_user(""), + m_password(""), + m_version("1.15.0") +{ + verify(); +} + +cSubsonic::cSubsonic(const QString& server, const QString& user, const QString& password) : + m_server(server), + m_user(user), + m_password(password), + m_version("1.15.0") +{ + verify(); +} + +void cSubsonic::setServer(const QString& server) +{ + m_server = server; +} + +QString cSubsonic::server() +{ + return(m_server); +} + +void cSubsonic::setUser(const QString& user) +{ + m_user = user; +} + +QString cSubsonic::user() +{ + return(m_user); +} + +void cSubsonic::setPassword(const QString& password) +{ + m_password = password; +} + +bool cSubsonic::verify() +{ + QDomElement element; + + if(!sendRequest("ping.view", element)) + { + m_version = element.attribute("version"); + + if(!sendRequest("ping.view", element)) + return(false); + } + + return(true); +} + +bool cSubsonic::getLicense(bool& valid, QString& email, QDateTime& licenseExpires) +{ + QDomElement element; + + if(!sendRequest("getLicense", element, "license")) + return(false); + + valid = element.attribute("valid") == "true"; + email = element.attribute("email"); + licenseExpires = QDateTime::fromString(element.attribute("licenseExpires"), "yyyy-MM-ddThh:mm:ss.zzzZ"); + + return(true); +} + +bool cSubsonic::getMusicFolders(QMap& folders) +{ + QDomElement element; + + if(!sendRequest("getMusicFolders", element, "musicFolders")) + return(false); + + QDomNode child = element.firstChild(); + QDomElement childElement; + qint32 id; + QString name; + + while(!child.isNull()) + { + childElement = child.toElement(); + + if(!childElement.tagName().compare("musicFolder")) + { + id = childElement.attribute("id").toInt(); + name = childElement.attribute("name"); + folders.insert(id, name); + } + child = child.nextSibling(); + } + + return(true); +} + +bool cSubsonic::getIndexes(QMap& shortcuts, QStringList& indexes, QMap &artists, const qint32 id, const QDate& modifiedSince) +{ + QMap parameter; + QDomElement element; + + if(id != -1) + parameter.insert("musicFolderId", QString::number(id)); + + if(modifiedSince.isValid()) + parameter.insert("ifModifiedSince", QString::number(QDateTime(modifiedSince, QTime(0, 0)).toMSecsSinceEpoch())); + + if(!sendRequest("getIndexes", element, "indexes", parameter)) + return(false); + + QDomNode child = element.firstChild(); + QDomElement childElement; + QDomElement artistElement; + + qint32 shortcutID; + QString shortcutName; + QString indexName; + qint32 artistID; + QString artistName; + + while(!child.isNull()) + { + childElement = child.toElement(); + + if(!childElement.tagName().compare("shortcut")) + { + shortcutID = childElement.attribute("id").toInt(); + shortcutName = childElement.attribute("name"); + shortcuts.insert(shortcutID, shortcutName); + } + else if(!childElement.tagName().compare("index")) + { + indexName = childElement.attribute("name"); + indexes.append(indexName); + + QDomNode artistChild = childElement.firstChild(); + + while(!artistChild.isNull()) + { + artistElement = artistChild.toElement(); + + if(!artistElement.tagName().compare("artist")) + { + artistID = artistElement.attribute("id").toInt(); + artistName = artistElement.attribute("name"); + + INDEX artist = { indexName, artistName, -1 }; + artists.insert(artistID, artist); + } + + artistChild = artistChild.nextSibling(); + } + } + + child = child.nextSibling(); + } + + return(true); +} + +bool cSubsonic::getMusicDirectory(const qint32 id, QString &name, qint32 &playCount, QMap& albums) +{ + QMap parameter; + QDomElement element; + + parameter.insert("id", QString::number(id)); + + if(!sendRequest("getMusicDirectory", element, "directory", parameter)) + return(false); + + name = element.attribute("name"); + playCount = element.attribute("playCount").toInt(); + + QDomNode child = element.firstChild(); + QDomElement childElement; + QDomElement artistElement; + + while(!child.isNull()) + { + childElement = child.toElement(); + + if(!childElement.tagName().compare("child")) + { + if(!childElement.hasAttribute("contentType")) + { + ALBUM album; + qint32 albumID; + + albumID = childElement.attribute("id").toInt(); + album.parent = childElement.attribute("parent").toInt(); + album.title = childElement.attribute("title"); + album.album = childElement.attribute("album"); + album.artist = childElement.attribute("artist"); + album.year = childElement.attribute("year").toInt(); + album.genre = childElement.attribute("genre"); + album.coverArt = childElement.attribute("coverArt").toInt(); + album.playCount = childElement.attribute("playCount").toInt(); + album.created = QDateTime::fromString(childElement.attribute("created"), "yyyy-MM-ddThh:mm:ss.zzzZ"); + + albums.insert(albumID, album); + } + } + + child = child.nextSibling(); + } + + return(true); +} + +bool cSubsonic::getMusicDirectory(const qint32 id, qint32& parent, QString& name, qint32& playCount, QMap& tracks) +{ + QMap parameter; + QDomElement element; + + parameter.insert("id", QString::number(id)); + + if(!sendRequest("getMusicDirectory", element, "directory", parameter)) + return(false); + + parent = element.attribute("parent").toInt(); + name = element.attribute("name"); + playCount = element.attribute("playCount").toInt(); + + QDomNode child = element.firstChild(); + QDomElement childElement; + QDomElement artistElement; + + while(!child.isNull()) + { + childElement = child.toElement(); + + if(!childElement.tagName().compare("child")) + { + if(childElement.hasAttribute("contentType")) + { + TRACK track; + qint32 trackID; + + trackID = childElement.attribute("id").toInt(); + track.parent = childElement.attribute("parent").toInt(); + track.title = childElement.attribute("title"); + track.album = childElement.attribute("album"); + track.artist = childElement.attribute("artist"); + track.track = childElement.attribute("track"); + track.year = childElement.attribute("year").toInt(); + track.genre = childElement.attribute("genre"); + track.coverArt = childElement.attribute("coverArt").toInt(); + track.size = childElement.attribute("size").toInt(); + track.contentType = childElement.attribute("contentType"); + track.suffix = childElement.attribute("suffix"); + track.duration = childElement.attribute("duration").toInt(); + track.bitRate = childElement.attribute("duration").toInt(); + track.path = childElement.attribute("path"); + track.isVideo = childElement.attribute("isVideo") == "true"; + track.playCount = childElement.attribute("playCount").toInt(); + track.discNumber = childElement.attribute("discNumber"); + track.created = QDateTime::fromString(childElement.attribute("created"), "yyyy-MM-ddThh:mm:ss.zzzZ"); + track.albumID = childElement.attribute("albumID").toInt(); + track.artistID = childElement.attribute("artistID").toInt(); + track.type = childElement.attribute("type"); + + tracks.insert(trackID, track); + } + } + + child = child.nextSibling(); + } + + return(true); +} + +bool cSubsonic::getGenres(QMap& genres) +{ + QDomElement element; + + if(!sendRequest("getGenres", element, "genres")) + return(false); + + QDomNode child = element.firstChild(); + QDomElement childElement; + + while(!child.isNull()) + { + childElement = child.toElement(); + + if(!childElement.tagName().compare("genre")) + { + GENRE genre; + QString name; + + genre.songCount = childElement.attribute("songCount").toInt(); + genre.albumCount = childElement.attribute("albumCount").toInt(); + name = childElement.text(); + + genres.insert(name, genre); + } + + child = child.nextSibling(); + } + + return(true); +} + +bool cSubsonic::getArtists(QStringList& indexes, QMap& artists, const qint32 id) +{ + QMap parameter; + QDomElement element; + + if(id != -1) + parameter.insert("musicFolderId", QString::number(id)); + + if(!sendRequest("getArtists", element, "artists", parameter)) + return(false); + + QDomNode child = element.firstChild(); + QDomElement childElement; + QDomElement artistElement; + + QString index; + + while(!child.isNull()) + { + childElement = child.toElement(); + + if(!childElement.tagName().compare("index")) + { + index = childElement.attribute("name"); + indexes.append(index); + + QDomNode artistChild = childElement.firstChild(); + + while(!artistChild.isNull()) + { + artistElement = artistChild.toElement(); + + if(!artistElement.tagName().compare("artist")) + { + ARTIST artist; + + artist.index = index; + artist.name = artistElement.attribute("name"); + artist.coverArt = artistElement.attribute("coverArt"); + artist.albumCount = artistElement.attribute("albumCount").toInt(); + + artists.insert(artistElement.attribute("id").toInt(), artist); + } + + artistChild = artistChild.nextSibling(); + } + } + + child = child.nextSibling(); + } + + return(true); +} + +QString cSubsonic::randomString() +{ + const QString possibleCharacters("abcdef0123456789"); + const int randomStringLength = 6; + + QString randomString; + + for(int i = 0; i < randomStringLength; ++i) + { + int index = rand() % possibleCharacters.length(); + QChar nextChar = possibleCharacters.at(index); + randomString.append(nextChar); + } + return(randomString); +} + +bool cSubsonic::sendRequest(const QString& request, QDomElement& element, const QString& verify, const QMap& parameter) +{ + QNetworkAccessManager networkManager; + QString salt = randomString(); + QString token = QCryptographicHash::hash(QString(m_password+salt).toUtf8(), QCryptographicHash::Md5).toHex(); + QString strRequest = QString("%1/rest/%2?u=%3&t=%4&s=%5&v=%6&c=subJuke").arg(m_server, request, m_user, token, salt, m_version); + + if(parameter.count()) + { + QString param; + + QMapIterator i(parameter); + while(i.hasNext()) + { + i.next(); + param.append(QString("&%1=%2").arg(i.key(), i.value())); + } + + strRequest.append(param); + } + +// qDebug() << strRequest; + + QUrl url(strRequest); + QNetworkRequest networkRequest(url); + QNetworkReply* reply = networkManager.get(networkRequest); + QEventLoop loop; + + QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit())); + loop.exec(); + + if(reply->error() == QNetworkReply::NoError) + { + QDomDocument doc; + QString errorStr; + int errorLine; + int errorColumn; + if(!doc.setContent(reply->readAll(), false, &errorStr, &errorLine, &errorColumn)) + return(false); + +// qDebug() << doc.toString(); + element = doc.documentElement(); + if(element.tagName().compare("subsonic-response")) + return(false); + + QString status = element.attribute("status"); + if(status == "failed") + return(false); + + if(verify.isEmpty()) + { + if(element.isNull()) + return(false); + return(true); + } + + element = element.firstChild().toElement(); + if(element.isNull()) + return(false); + + if(element.tagName().compare(verify)) + return(false); + return(true); + } + else + { +// qDebug() << reply->errorString(); + return(false); + } +} diff --git a/csubsonic.h b/csubsonic.h new file mode 100644 index 0000000..4d08877 --- /dev/null +++ b/csubsonic.h @@ -0,0 +1,102 @@ +#ifndef CSUBSONIC_H +#define CSUBSONIC_H + + +#include +#include +#include +#include + + +typedef struct tagINDEX +{ + QString index; + QString name; + qint32 playCount; +} INDEX; + +typedef struct tagALBUM +{ + qint32 parent; + QString title; + QString album; + QString artist; + qint16 year; + QString genre; + qint32 coverArt; + qint32 playCount; + QDateTime created; +} ALBUM; + +typedef struct tagTRACK +{ + qint32 parent; + QString title; + QString album; + QString artist; + QString track; + qint16 year; + QString genre; + qint32 coverArt; + qint32 size; + QString contentType; + QString suffix; + qint32 duration; + qint32 bitRate; + QString path; + bool isVideo; + qint32 playCount; + QString discNumber; + QDateTime created; + qint32 albumID; + qint32 artistID; + QString type; +} TRACK; + +typedef struct tagGENRE +{ + qint32 songCount; + qint32 albumCount; +} GENRE; + +typedef struct tagARTIST +{ + QString index; + QString name; + QString coverArt; + qint32 albumCount; +} ARTIST; + +class cSubsonic +{ +public: + cSubsonic(); + cSubsonic(const QString& server, const QString& user, const QString& password); + + void setServer(const QString& server); + QString server(); + + void setUser(const QString& user); + QString user(); + + void setPassword(const QString& password); + + bool verify(); + bool getLicense(bool &valid, QString &email, QDateTime &licenseExpires); + bool getMusicFolders(QMap& folders); + bool getIndexes(QMap& shortcuts, QStringList& indexes, QMap& artists, const qint32 id = -1, const QDate& modifiedSince = QDate()); + bool getMusicDirectory(const qint32 id, QString& name, qint32& playCount, QMap& albums); + bool getMusicDirectory(const qint32 id, qint32& parent, QString& name, qint32& playCount, QMap& tracks); + bool getGenres(QMap& genres); + bool getArtists(QStringList& indexes, QMap& artists, const qint32 id = -1); +private: + QString m_server; + QString m_user; + QString m_password; + QString m_version; + + QString randomString(); + bool sendRequest(const QString& request, QDomElement& element, const QString& verify = "", const QMap& parameter = QMap()); +}; + +#endif // CSUBSONIC_H diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..eaa4c0b --- /dev/null +++ b/main.cpp @@ -0,0 +1,24 @@ +#include "cmainwindow.h" + +#include +#include +#include + + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + + QTranslator translator; + const QStringList uiLanguages = QLocale::system().uiLanguages(); + for (const QString &locale : uiLanguages) { + const QString baseName = "subJuke_" + QLocale(locale).name(); + if (translator.load(":/i18n/" + baseName)) { + a.installTranslator(&translator); + break; + } + } + cMainWindow w; + w.show(); + return a.exec(); +} diff --git a/subJuke.pro b/subJuke.pro new file mode 100644 index 0000000..d9b6e1a --- /dev/null +++ b/subJuke.pro @@ -0,0 +1,31 @@ +QT += core gui network xml + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +CONFIG += c++11 + +# You can make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + csubsonic.cpp \ + main.cpp \ + cmainwindow.cpp + +HEADERS += \ + cmainwindow.h \ + csubsonic.h + +FORMS += \ + cmainwindow.ui + +TRANSLATIONS += \ + subJuke_de_AT.ts +CONFIG += lrelease +CONFIG += embed_translations + +# Default rules for deployment. +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target diff --git a/subJuke_de_AT.ts b/subJuke_de_AT.ts new file mode 100644 index 0000000..b01af89 --- /dev/null +++ b/subJuke_de_AT.ts @@ -0,0 +1,3 @@ + + +