This commit is contained in:
2023-03-28 10:30:50 +02:00
parent fcf8ba46fa
commit f8323c3936
9 changed files with 781 additions and 41 deletions
+62 -41
View File
@@ -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*
+52
View File
@@ -0,0 +1,52 @@
#include "cmainwindow.h"
#include "ui_cmainwindow.h"
#include "csubsonic.h"
#include <QDateTime>
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<qint32, QString> musicFolders;
QMap<qint32, QString> shortcuts;
QStringList indexes;
QMap<qint32, INDEX> indexArtists;
QString artistName;
qint32 artistPlayCount;
QMap<qint32, ALBUM> albums;
qint32 albumParent;
QString albumName;
qint32 albumPlayCount;
QMap<qint32, TRACK> tracks;
QMap<QString, GENRE> genres;
QMap<qint32, ARTIST> 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;
}
+21
View File
@@ -0,0 +1,21 @@
#ifndef CMAINWINDOW_H
#define CMAINWINDOW_H
#include <QMainWindow>
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
+22
View File
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>cMainWindow</class>
<widget class="QMainWindow" name="cMainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>cMainWindow</string>
</property>
<widget class="QWidget" name="centralwidget"/>
<widget class="QMenuBar" name="menubar"/>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>
+464
View File
@@ -0,0 +1,464 @@
#include "csubsonic.h"
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QEventLoop>
#include <QUrl>
#include <QDomDocument>
#include <QCryptographicHash>
#include <QDateTime>
#include <QDebug>
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<qint32, QString>& 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<qint32, QString>& shortcuts, QStringList& indexes, QMap<qint32, INDEX> &artists, const qint32 id, const QDate& modifiedSince)
{
QMap<QString, QString> 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<qint32, ALBUM>& albums)
{
QMap<QString, QString> 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<qint32, TRACK>& tracks)
{
QMap<QString, QString> 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<QString, GENRE>& 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<qint32, ARTIST>& artists, const qint32 id)
{
QMap<QString, QString> 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<QString, QString>& 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<QString, QString> 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);
}
}
+102
View File
@@ -0,0 +1,102 @@
#ifndef CSUBSONIC_H
#define CSUBSONIC_H
#include <QObject>
#include <QDomElement>
#include <QMap>
#include <QDate>
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<qint32, QString>& folders);
bool getIndexes(QMap<qint32, QString>& shortcuts, QStringList& indexes, QMap<qint32, INDEX>& artists, const qint32 id = -1, const QDate& modifiedSince = QDate());
bool getMusicDirectory(const qint32 id, QString& name, qint32& playCount, QMap<qint32, ALBUM>& albums);
bool getMusicDirectory(const qint32 id, qint32& parent, QString& name, qint32& playCount, QMap<qint32, TRACK>& tracks);
bool getGenres(QMap<QString, GENRE>& genres);
bool getArtists(QStringList& indexes, QMap<qint32, ARTIST>& 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<QString, QString>& parameter = QMap<QString, QString>());
};
#endif // CSUBSONIC_H
+24
View File
@@ -0,0 +1,24 @@
#include "cmainwindow.h"
#include <QApplication>
#include <QLocale>
#include <QTranslator>
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();
}
+31
View File
@@ -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
+3
View File
@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="de_AT"></TS>