Squashed commit of the following:

commit e946bf124602f224ce0e371ba1374f0355b537eb
Merge: d6a4e64 4505909
Author: Rosemary Witchaven <32779090+in1tiate@users.noreply.github.com>
Date:   Fri Jan 28 19:43:36 2022 -0600

    Merge pull request #225 from Salanto/Dynamic-Area-Musiclist-Take2

    Allow users to add custom songs to the music list on a per-area basis

commit 45059092d2888b60912f721e43a380910d10ccd8
Author: Salanto <62221668+Salanto@users.noreply.github.com>
Date:   Mon Jan 24 22:05:27 2022 +0100

    TIL what a typedef is

commit 02584db9640fff0a1969a7f516c4bccfae9b5388
Author: Salanto <62221668+Salanto@users.noreply.github.com>
Date:   Mon Jan 24 21:39:17 2022 +0100

    Not all OR are equal. Explain weird command splitting

    Remove hardcoded URLs

commit d00ebd5692296cd0c29dd377113b53fe0e7b99c0
Author: Salanto <62221668+Salanto@users.noreply.github.com>
Date:   Mon Jan 24 21:28:08 2022 +0100

    Salanto PR Language Update by in1tiate

    As usual, my English is absolutely unreadable.

    Co-authored-by: Rosemary Witchaven <32779090+in1tiate@users.noreply.github.com>

commit d3842106e06350dc02d8864bb28232fdc5643f00
Author: Salanto <62221668+Salanto@users.noreply.github.com>
Date:   Mon Jan 24 20:35:32 2022 +0100

    Add missing config file + document commands for help

commit ac64360e1c1741023b01052977de77a7d5ea4f8c
Author: Salanto <62221668+Salanto@users.noreply.github.com>
Date:   Mon Jan 24 19:52:13 2022 +0100

    Minor improvements to command usage and addition of clear command

commit c614578e78ce9afa0c8e22aa36bdf46a70a97169
Author: Salanto <62221668+Salanto@users.noreply.github.com>
Date:   Sun Jan 23 22:19:54 2022 +0100

    Purge last traces of old songInformation system

commit 07618761f044a13d75587b28a9c994342a5980e2
Author: Salanto <62221668+Salanto@users.noreply.github.com>
Date:   Sun Jan 23 22:10:54 2022 +0100

    Working version, needs some refinement and debugging in AOClient

commit 33c0358c98c0fd2de805356a9aa3ac7bbed204e1
Author: Salanto <62221668+Salanto@users.noreply.github.com>
Date:   Sun Jan 23 21:48:05 2022 +0100

    Almost functional implementation

    Now only need to determine why I can't play the customs yet

commit b0acbace78b3f16f2fe4f3c6f65a422e3343f992
Author: Salanto <62221668+Salanto@users.noreply.github.com>
Date:   Sun Jan 23 15:26:42 2022 +0100

    Fix build error, expand validation test slightly

commit a48c4f503998ce8e42f0bb409c5a3c7dc5e40329
Author: Salanto <62221668+Salanto@users.noreply.github.com>
Date:   Sun Jan 23 01:03:27 2022 +0100

    Add commands

commit 88ab0b473953873166e291e5009b97df31547b3f
Author: Salanto <62221668+Salanto@users.noreply.github.com>
Date:   Sat Jan 22 15:10:26 2022 +0100

    Float sucks, int has to be good enough

    + add retrival of song information

commit e924e1340be1a0909eba84072f1646fe9770bd02
Author: Salanto <62221668+Salanto@users.noreply.github.com>
Date:   Thu Jan 20 22:28:35 2022 +0100

    Fix removing moving

    Add necessary tests

commit 3df088f8d07ce7e0d8fe08b6a97608a623e6ef97
Author: Salanto <62221668+Salanto@users.noreply.github.com>
Date:   Wed Jan 19 19:34:16 2022 +0100

    Start work on adding this shit into commands

commit c293ecfa99d1b2bd1e0b34cb8752d69b2eca057c
Author: Salanto <62221668+Salanto@users.noreply.github.com>
Date:   Tue Jan 18 19:07:11 2022 +0100

    Fix typo and add singal for incremental upgrades

commit 10a42322e1e23af5795278a40b2ac59f3ab952ef
Author: Salanto <62221668+Salanto@users.noreply.github.com>
Date:   Tue Jan 18 06:17:51 2022 +0100

    Hookup packet sending to music manager

    This might sound like a bad idea on first glance, but otherwise it breaks the AreaData tests and I am NOT gonna try to fix those without even understanding why they break.

commit 319836296374162b0b847432e8a626778317b869
Author: Salanto <62221668+Salanto@users.noreply.github.com>
Date:   Tue Jan 18 06:17:51 2022 +0100

    Enraged comments, make area send FM packet

    Revert "Enraged comments, make area send FM packet"

    This reverts commit ec7a1a25646b2c2acc8a3a748b853851cc47d205.

commit 224a0d7efe989a5f336167c3f716061813f93ee3
Author: Salanto <62221668+Salanto@users.noreply.github.com>
Date:   Tue Jan 18 05:10:18 2022 +0100

    Change packet communication from area to client

    First steps to hookup the custom musiclist.

commit 65aa8f7855a36f2c668b1399a5ed22fefeaf186d
Author: Salanto <62221668+Salanto@users.noreply.github.com>
Date:   Mon Jan 17 00:48:38 2022 +0100

    Add test for custom list sanitisation.

    + Fix intentionally broken tests

commit 7c00ab437a6ff12033742d029ce49037f5bb1ebe
Author: Salanto <62221668+Salanto@users.noreply.github.com>
Date:   Mon Jan 17 00:29:51 2022 +0100

    Sanitise the custom list opposed to deleting it

    This will fail tests intentionally to test the CI.

commit 80ad401267068e75707b2517a0bf836763141f8b
Author: Salanto <62221668+Salanto@users.noreply.github.com>
Date:   Sun Jan 16 17:17:12 2022 +0100

    Add custom category capabilities

commit 08d8f5f8f683816ceba532f9c47cd0d5ab34389a
Author: Salanto <62221668+Salanto@users.noreply.github.com>
Date:   Sun Jan 16 03:58:18 2022 +0100

    Fix music addition and move relevant tests

commit 6ebf0d03b5da61a9c287115009d28038710ba7af
Author: Salanto <62221668+Salanto@users.noreply.github.com>
Date:   Sat Jan 15 08:08:20 2022 +0100

    Hookup music_manager into server, change default musiclist source for new clients

    + More tests 🆒

commit bd50c62376f131e2508ecdd3e272209894ecaec1
Author: Salanto <62221668+Salanto@users.noreply.github.com>
Date:   Sat Jan 15 03:13:42 2022 +0100

    Add central song validator for other classes

    Also added applicable test cases to ensure proper operation.
This commit is contained in:
in1tiate 2022-01-28 19:51:30 -06:00
parent d6a4e64070
commit 83c41c05f7
19 changed files with 1063 additions and 62 deletions

View File

@ -0,0 +1 @@
cdn.discord.com

View File

@ -646,7 +646,32 @@
},
{
"name":"areamessage",
"usage":"/areamessage",
"usage":"/areamessage 'Message'",
"text":"Returns the area message in OOC. If text is entered it updates the current area message."
},
{
"name":"addsong",
"usage":"/addsong <Songname>,'TrueName','Duration'",
"text":"Adds a song to the custom musiclist. Optionally can be an aliased name to hide a streamlink. Set the duration for the jukebox."
},
{
"name":"addcategory",
"usage":"/addsong <Category>",
"text":"Adds a category to the custom musiclist."
},
{
"name":"removeentry",
"usage":"/removeentry 'Name'",
"text":"Removes an entry from the custom musiclist."
},
{
"name":"toggleglobal",
"usage":"/toggleglobal",
"text":"Changes the behaviour of prepending the server root musiclist to the custom lists of the area."
},
{
"name":"clearcustom",
"usage":"/clearcustom",
"text":"Removes all custom songs from the area."
}
]

View File

@ -47,7 +47,8 @@ SOURCES += \
src/advertiser.cpp \
src/logger/u_logger.cpp \
src/logger/writer_modcall.cpp \
src/logger/writer_full.cpp
src/logger/writer_full.cpp \
src/music_manager.cpp
HEADERS += include/aoclient.h \
include/aopacket.h \
@ -62,4 +63,5 @@ HEADERS += include/aoclient.h \
include/advertiser.h \
include/logger/u_logger.h \
include/logger/writer_modcall.h \
include/logger/writer_full.h
include/logger/writer_full.h \
include/music_manager.h

View File

@ -22,6 +22,7 @@
#include "include/server.h"
#include "include/area_data.h"
#include "include/db_manager.h"
#include "include/music_manager.h"
#include <algorithm>
@ -50,14 +51,7 @@ class AOClient : public QObject {
* @param user_id The user ID of the client.
* @param parent Qt-based parent, passed along to inherited constructor from QObject.
*/
AOClient(Server* p_server, QTcpSocket* p_socket, QObject* parent = nullptr, int user_id = 0)
: QObject(parent), m_id(user_id), m_remote_ip(p_socket->peerAddress()), m_password(""),
m_joined(false), m_current_area(0), m_current_char(""), m_socket(p_socket), server(p_server),
is_partial(false), m_last_wtce_time(0) {
m_afk_timer = new QTimer;
m_afk_timer->setSingleShot(true);
connect(m_afk_timer, SIGNAL(timeout()), this, SLOT(onAfkTimeout()));
};
AOClient(Server* p_server, QTcpSocket* p_socket, QObject* parent = nullptr, int user_id = 0, MusicManager* p_manager = nullptr);;
/**
* @brief Destructor for the AOClient instance.
@ -1868,6 +1862,31 @@ class AOClient : public QObject {
*/
void cmdToggleJukebox(int argc, QStringList argv);
/**
* @brief Adds a song to the custom list.
*/
void cmdAddSong(int argc, QStringList argv);
/**
* @brief Adds a category to the areas custom music list.
*/
void cmdAddCategory(int argc, QStringList argv);
/**
* @brief Removes any matching song or category from the custom area.
*/
void cmdRemoveCategorySong(int argc, QStringList argv);
/**
* @brief Toggles the prepending behaviour of the servers root musiclist.
*/
void cmdToggleRootlist(int argc, QStringList argv);
/**
* @brief Clears the entire custom list of this area.
*/
void cmdClearCustom(int argc, QStringList argv);
///@}
/**
@ -2185,7 +2204,12 @@ class AOClient : public QObject {
{"clearcm", {ACLFlags.value("KICK"), 0, &AOClient::cmdClearCM}},
{"togglemessage", {ACLFlags.value("CM"), 0, &AOClient::cmdToggleAreaMessageOnJoin}},
{"clearmessage", {ACLFlags.value("CM"), 0, &AOClient::cmdClearAreaMessage}},
{"areamessage", {ACLFlags.value("CM"), 0, &AOClient::cmdAreaMessage}}
{"areamessage", {ACLFlags.value("CM"), 0, &AOClient::cmdAreaMessage}},
{"addsong", {ACLFlags.value("CM"), 1, &AOClient::cmdAddSong}},
{"addcategory", {ACLFlags.value("CM"), 1, &AOClient::cmdAddCategory}},
{"removeentry", {ACLFlags.value("CM"), 1, &AOClient::cmdRemoveCategorySong}},
{"toggleroot", {ACLFlags.value("CM"), 0, &AOClient::cmdToggleRootlist}},
{"clearcustom", {ACLFlags.value("CM"), 0, &AOClient::cmdClearCustom}}
};
/**
@ -2234,6 +2258,11 @@ class AOClient : public QObject {
*/
QString m_last_message;
/**
* @brief Pointer to the servers music manager instance.
*/
MusicManager* m_music_manager;
/**
* @brief A helper function to add recorded packets to an area's judgelog.
*

View File

@ -20,6 +20,7 @@
#include "aopacket.h"
#include "config_manager.h"
#include "music_manager.h"
#include <QMap>
#include <QString>
@ -44,7 +45,7 @@ class AreaData : public QObject {
* and `YYYYYY` is the actual name of the area.
* @param p_index The index of the area in the area list.
*/
AreaData(QString p_name, int p_index);
AreaData(QString p_name, int p_index, MusicManager* p_music_manager);
/**
* @brief The data for evidence in the area.
@ -865,9 +866,11 @@ public slots:
signals:
/**
* @brief Changes the song in the current area when the jukebox timer expires.
* @brief Sends a packet to every client inside the area.
*/
void playJukeboxSong(AOPacket f_packet, int f_area_index);
void sendAreaPacket(AOPacket f_packet, int f_area_index);
void userJoinedArea(int f_area_index, int f_user_id);
private:
/**
@ -885,6 +888,11 @@ private:
*/
int m_index;
/**
* @brief Pointer to the global music manager.
*/
MusicManager* m_music_manager;
/**
* @brief A list of the character IDs of all characters taken.
*/

View File

@ -37,6 +37,8 @@
#include <QJsonObject>
#include <QJsonArray>
typedef QMap<QString,QPair<QString,int>> MusicList;
/**
* @brief The config file handler class.
*/
@ -76,7 +78,14 @@ class ConfigManager {
*
* @return See short description.
*/
static QStringList musiclist();
static MusicList musiclist();
/**
* @brief Returns an ordered QList of all basesongs of this server.
*
* @return See short description.
*/
static QStringList ordered_songs();
/**
* @brief Loads help information into m_help_information.
@ -85,13 +94,6 @@ class ConfigManager {
*/
static void loadCommandHelp();
/**
* @brief Returns the duration of a song in the songlist.
* @param The name of the song where duration is requested
* @return The duration of the song
*/
static QPair<QString, float> songInformation(const QString& f_songName);
/**
* @brief Returns the content of
*
@ -412,6 +414,13 @@ class ConfigManager {
*/
static QStringList gimpList();
/**
* @brief Returns the server approved domain list.
*
* @return See short description.
*/
static QStringList cdnList();
/**
* @brief Returns if the advertiser is enabled to advertise on ms3.
*/
@ -473,6 +482,7 @@ class ConfigManager {
*/
static void reloadSettings();
private:
/**
* @brief Checks if a file exists and is valid.
@ -500,6 +510,7 @@ private:
QStringList praises; //!< Contains command praises, found in config/text/praises.txt
QStringList reprimands; //!< Contains command reprimands, found in config/text/reprimands.txt
QStringList gimps; //!< Contains phrases for /gimp, found in config/text/gimp.txt
QStringList cdns; // !< Contains domains for custom song validation, found in config/text/cdns.txt
};
/**
@ -535,7 +546,12 @@ private:
/**
* @brief Contains the musiclist with time durations.
*/
static QHash<QString,QPair<QString,float>>* m_musicList;
static MusicList* m_musicList;
/**
* @brief Contains an ordered list for the musiclist.
*/
static QStringList* m_ordered_list;
/**
* @brief QHash containing the help information for all commands registered to the server.

View File

@ -0,0 +1,214 @@
//////////////////////////////////////////////////////////////////////////////////////
// akashi - a server for Attorney Online 2 //
// Copyright (C) 2020 scatterflower //
// //
// This program is free software: you can redistribute it and/or modify //
// it under the terms of the GNU Affero 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 Affero General Public License for more details. //
// //
// You should have received a copy of the GNU Affero General Public License //
// along with this program. If not, see <https://www.gnu.org/licenses/>. //
//////////////////////////////////////////////////////////////////////////////////////
#ifndef MUSIC_MANAGER_H
#define MUSIC_MANAGER_H
#include <QObject>
#include <QMap>
#include <QHash>
#include <QPair>
#include <include/config_manager.h>
#include <include/aopacket.h>
class MusicManager : public QObject
{
Q_OBJECT
public:
/**
* @brief Constructor for the server-wide musiclist manager.
*
* @param Copy of the server musiclist generated by ConfigManager::musiclist();
*/
MusicManager(QObject* parent = nullptr, QStringList f_root_ordered = {}, QStringList f_cdns = {"cdn.discord.com"},
QMap<QString, QPair<QString, int> > f_root_list = ConfigManager::musiclist());
/**
* @brief Destructor for the server-wide musiclist manager.
*/
virtual ~MusicManager();
/**
* @brief Returns a musiclist with aliased names.
*
* @return See short description.
*/
QStringList musiclist(int f_area_id);
/**
* @brief Returns only the root musiclist with aliased names.
*
*/
QStringList rootMusiclist();
/**
* @brief Adds a new area to the music_manager.
*
* @param f_area_id ID of the new area being added.
*
* @return Returns false if registering the area fails.
*/
bool registerArea(int f_area_id);
/**
* @brief Validates the song candidate to be played. If validation fails, false is returned.
*
* @param f_song_name The song to be played. Can be an http/https stream or a local file.
*
* @param f_approved_cdns A list of approved remote content sources.
*
* @return Wether or not the song can be played or added.
*/
bool validateSong(QString f_song_name, QStringList f_approved_cdns);
/**
* @brief Attempts to add the new song to the custom musiclist.
*
* @param f_song_name Friendly name shown in the clients musiclist.
*
* @param f_real_name Real name/url of the file.
*
* @param f_duration Playtime of the musicfile in seconds.
*
* @param f_area_id Area id of the clients current area.
*
* @return Returns true on success, false on fail.
*/
bool addCustomSong(QString f_song_name, QString f_real_name, int f_duration, int f_area_id);
/**
* @brief Attempts to add the new category to the custom musiclist.
*
* @param f_category_name Category name candidate.
*
* @return Returns true on saccess, false on fail.
*/
bool addCustomCategory(QString f_category_name, int f_area_id);
/**
* @brief Removes either a song or a category from the custom list.
*
* @param Name of the category or song to remove
*
* @return True on success, false on failure.
*/
bool removeCategorySong(QString f_songcategory_name, int f_area_id);
/**
* @brief Toggles wether the root list is included for this area.
* This also delets the custom llist if it was enabled prior.
*
* @return Current state of the music list.
*/
bool toggleRootEnabled(int f_area_id);
/**
* @brief Removes conflicting songnames from the custom list.
*
* @param f_area_id Id of the area this is invoked in.
*/
void sanitiseCustomList(int f_area_id);
/**
* @brief Removes all entries from the custom list.
* @param f_area_id Id of the area custom list.
*/
void clearCustomList(int f_area_id);
/**
* @brief Returns song information necessary for the operation of the jukebox.
* @param Alias name of the song.
* @param Area of the jukebox checking for information.
* @return Returns a QPair with the true name and duration of the invoked song.
*/
QPair<QString, int> songInformation(QString f_song_name, int f_area_id);
/**
* @brief Checks if a song is part of the clients current area custom list.
*
* @return Returns true if the song exists as a custom song.
*/
bool isCustom(int f_area_id, QString f_song_name);
public slots:
/**
* @brief Updates the root musiclist and CDN list.
*/
void reloadRequest();
/**
* @brief Triggers sending of FM packet to client joining a new area.
*/
void userJoinedArea(int f_area_index, int f_user_id);
signals:
/**
* @brief Sends the FM packet with the musiclist of the area when a client enters.
*
* @param f_packet FM packet with the full musiclist, when enabled, and custom list.
*
* @param f_user_id temporary userid of the incoming client.
*/
void sendFMPacket(AOPacket f_packet, int f_user_id);
/**
* @brief Sends the FM packet with the musiclist of the area when changes are made.
*
* @param f_packet FM packet with the full musiclist, when enabled, and eventual custom list.
*
* @param f_area_index Index of the current area the edit is made in.
*/
void sendAreaFMPacket(AOPacket f_packet, int f_area_index);
private:
/**
* @brief Contains all custom lists of all areas in the server.
*/
QHash<int,MusicList>* m_custom_lists;
/**
* @brief Server musiclist shared among all areas.
*/
MusicList m_root_list;
/**
* @brief QList with the ordered musiclist.
*/
QStringList m_root_ordered;
/**
* @brief Contains all custom songs ordered in a per-area buffer.
*/
QMap<int,QStringList> m_customs_ordered;
/**
* @brief Wether the global musiclist is prepend and validation when adding custom music.
*/
QHash<int,bool> m_global_enabled;
/**
* @brief Contains all server approved content sources.
*/
QStringList m_cdns;
};
#endif // MUSIC_MANAGER_H

View File

@ -27,6 +27,7 @@
#include "include/config_manager.h"
#include "include/advertiser.h"
#include "include/logger/u_logger.h"
#include "include/music_manager.h"
#include <QCoreApplication>
#include <QDebug>
@ -126,6 +127,7 @@ class Server : public QObject {
* @brief Sends a packet to all clients in a given area.
*
* @param packet The packet to send to the clients.
*
* @param area_index The index of the area to look for clients in.
*
* @note Does nothing if an area by the given index does not exist.
@ -140,7 +142,7 @@ class Server : public QObject {
void broadcast(AOPacket packet);
/**
* @brief Sends a packet to clients, sends an altered packet to a specific usergroup.
* @brief Sends a packet to a specific usergroup..
*
* @param The packet to send to the clients.
*
@ -159,6 +161,15 @@ class Server : public QObject {
*/
void broadcast(AOPacket packet, AOPacket other_packet, enum TARGET_TYPE target);
/**
* @brief Sends a packet to a single client.
*
* @param The packet send to the client.
*
* @param The temporary userID of the client.
*/
void unicast(AOPacket f_packet, int f_client_id);
/**
* @brief Returns the character's character ID (= their index in the character list).
*
@ -385,6 +396,11 @@ class Server : public QObject {
*/
ULogger* logger;
/**
* @brief Handles all musiclists.
*/
MusicManager* music_manager;
/**
* @brief The port through which the server will accept TCP connections.
*/

View File

@ -347,9 +347,15 @@ bool AOClient::checkAuth(unsigned long long acl_mask)
}
QString AOClient::getIpid() const { return m_ipid; }
QString AOClient::getIpid() const
{
return m_ipid;
}
QString AOClient::getHwid() const { return m_hwid; }
QString AOClient::getHwid() const
{
return m_hwid;
}
Server* AOClient::getServer() { return server; }
@ -360,6 +366,26 @@ void AOClient::onAfkTimeout()
m_is_afk = true;
}
AOClient::AOClient(Server *p_server, QTcpSocket *p_socket, QObject *parent, int user_id, MusicManager *p_manager)
: QObject(parent),
m_id(user_id),
m_remote_ip(p_socket->peerAddress()),
m_password(""),
m_joined(false),
m_current_area(0),
m_current_char(""),
m_socket(p_socket),
server(p_server),
is_partial(false),
m_last_wtce_time(0),
m_music_manager(p_manager)
{
m_afk_timer = new QTimer;
m_afk_timer->setSingleShot(true);
connect(m_afk_timer, &QTimer::timeout,
this, &AOClient::onAfkTimeout);
}
AOClient::~AOClient() {
m_socket->deleteLater();
}

View File

@ -20,8 +20,9 @@
#include "include/area_data.h"
AreaData::AreaData(QString p_name, int p_index) :
AreaData::AreaData(QString p_name, int p_index, MusicManager* p_music_manager = nullptr) :
m_index(p_index),
m_music_manager(p_music_manager),
m_playerCount(0),
m_status(IDLE),
m_locked(FREE),
@ -93,6 +94,7 @@ void AreaData::clientJoinedArea(int f_charId, int f_userId)
m_charactersTaken.append(f_charId);
}
m_joined_ids.append(f_userId);
emit userJoinedArea(m_index, f_userId);
}
QList<int> AreaData::owners() const
@ -564,12 +566,12 @@ QString AreaData::addJukeboxSong(QString f_song)
{
if(!m_jukebox_queue.contains(f_song)) {
//Retrieve song information.
QPair<QString,float> l_song = ConfigManager::songInformation(f_song);
QPair<QString,float> l_song = m_music_manager->songInformation(f_song, index());
if (l_song.second > 0) {
if (m_jukebox_queue.size() == 0) {
emit playJukeboxSong(AOPacket("MC",{l_song.first,QString::number(-1)}), index());
emit sendAreaPacket(AOPacket("MC",{l_song.first,QString::number(-1)}), index());
m_jukebox_timer->start(l_song.second * 1000);
setCurrentMusic(f_song);
setMusicPlayedBy("Jukebox");
@ -594,16 +596,16 @@ void AreaData::switchJukeboxSong()
QString l_song_name;
if(m_jukebox_queue.size() == 1) {
l_song_name = m_jukebox_queue[0];
QPair<QString,float> l_song = ConfigManager::songInformation(l_song_name);
emit playJukeboxSong(AOPacket("MC",{l_song.first,"-1"}), m_index);
QPair<QString,float> l_song = m_music_manager->songInformation(l_song_name, index());
emit sendAreaPacket(AOPacket("MC",{l_song.first,"-1"}), m_index);
m_jukebox_timer->start(l_song.second * 1000);
}
else {
int l_random_index = QRandomGenerator::system()->bounded(m_jukebox_queue.size() -1);
l_song_name = m_jukebox_queue[l_random_index];
QPair<QString,float> l_song = ConfigManager::songInformation(l_song_name);
emit playJukeboxSong(AOPacket("MC",{l_song.first,"-1"}), m_index);
QPair<QString,float> l_song = m_music_manager->songInformation(l_song_name, index());
emit sendAreaPacket(AOPacket("MC",{l_song.first,"-1"}), m_index);
m_jukebox_timer->start(l_song.second * 1000);
m_jukebox_queue.remove(l_random_index);

View File

@ -127,3 +127,79 @@ void AOClient::cmdToggleJukebox(int argc, QStringList argv)
sendServerMessage("You do not have permission to change the jukebox status.");
}
}
void AOClient::cmdAddSong(int argc, QStringList argv)
{
Q_UNUSED(argc);
//This needs some explanation.
//Akashi has no concept of argument count,so any space is interpreted as a new element
//in the QStringList. This works fine until someone enters something with a space.
//Since we can't preencode those elements, we join all as a string and use a delimiter
//that does not exist in file and URL paths. I decided on the ol' reliable ','.
QString l_argv_string = argv.join(" ");
QStringList l_argv = l_argv_string.split(",");
bool l_success = false;
if (l_argv.size() == 1) {
QString l_song_name = l_argv.value(0);
l_success = m_music_manager->addCustomSong(l_song_name, l_song_name, 0, m_current_area);
}
if (l_argv.size() == 2) {
QString l_song_name = l_argv.value(0);
QString l_true_name = l_argv.value(1);
l_success = m_music_manager->addCustomSong(l_song_name, l_true_name, 0, m_current_area);
}
if (l_argv.size() == 3) {
QString l_song_name = l_argv.value(0);
QString l_true_name = l_argv.value(1);
bool ok;
int l_song_duration = l_argv.value(2).toInt(&ok);
if (!ok)
l_song_duration = 0;
l_success = m_music_manager->addCustomSong(l_song_name, l_true_name, l_song_duration, m_current_area);
}
if (l_argv.size() >= 4) {
sendServerMessage("Too many arguments. Addition of song has failed.");
return;
}
QString l_message = l_success ? "succeeded." : "failed.";
sendServerMessage("The addition of the song has " + l_message);
}
void AOClient::cmdAddCategory(int argc, QStringList argv)
{
Q_UNUSED(argc);
bool l_success = m_music_manager->addCustomCategory(argv.join(" "), m_current_area);
QString l_message = l_success ? "succeeded." : "failed.";
sendServerMessage("The addition of the category has " + l_message);
}
void AOClient::cmdRemoveCategorySong(int argc, QStringList argv)
{
Q_UNUSED(argc);
bool l_success = m_music_manager->removeCategorySong(argv.join(" "), m_current_area);
QString l_message = l_success ? "succeeded." : "failed.";
sendServerMessage("The removal of the entry has " + l_message);
}
void AOClient::cmdToggleRootlist(int argc, QStringList argv)
{
Q_UNUSED(argc);
Q_UNUSED(argv);
bool l_status = m_music_manager->toggleRootEnabled(m_current_area);
QString l_message = (l_status) ? "enabled." : "disabled.";
sendServerMessage("Global musiclist has been " + l_message);
}
void AOClient::cmdClearCustom(int argc, QStringList argv)
{
Q_UNUSED(argc);
Q_UNUSED(argv);
m_music_manager->clearCustomList(m_current_area);
sendServerMessage("Custom songs have been cleared.");
}

View File

@ -25,8 +25,9 @@ QSettings* ConfigManager::m_areas = new QSettings("config/areas.ini", QSettings:
QSettings* ConfigManager::m_logtext = new QSettings("config/text/logtext.ini", QSettings::IniFormat);
ConfigManager::CommandSettings* ConfigManager::m_commands = new CommandSettings();
QElapsedTimer* ConfigManager::m_uptimeTimer = new QElapsedTimer;
QHash<QString,QPair<QString,float>>* ConfigManager::m_musicList = new QHash<QString,QPair<QString,float>>;
MusicList* ConfigManager::m_musicList = new MusicList;
QHash<QString,ConfigManager::help>* ConfigManager::m_commands_help = new QHash<QString,ConfigManager::help>;
QStringList* ConfigManager::m_ordered_list = new QStringList;
bool ConfigManager::verifyServerConfig()
{
@ -42,7 +43,7 @@ bool ConfigManager::verifyServerConfig()
// Verify config files
QStringList l_config_files{"config/config.ini", "config/areas.ini", "config/backgrounds.txt", "config/characters.txt", "config/music.json",
"config/discord.ini", "config/text/8ball.txt", "config/text/gimp.txt", "config/text/praise.txt",
"config/text/reprimands.txt","config/text/commandhelp.json"};
"config/text/reprimands.txt","config/text/commandhelp.json","config/text/cdns.txt"};
for (const QString &l_file : l_config_files) {
if (!fileExists(QFileInfo(l_file))) {
qCritical() << l_file + " does not exist!";
@ -92,6 +93,7 @@ bool ConfigManager::verifyServerConfig()
m_commands->praises = (loadConfigFile("praise"));
m_commands->reprimands = (loadConfigFile("reprimands"));
m_commands->gimps = (loadConfigFile("gimp"));
m_commands->cdns = (loadConfigFile("cdns"));
m_uptimeTimer->start();
@ -129,7 +131,7 @@ QStringList ConfigManager::backgrounds()
return l_backgrounds;
}
QStringList ConfigManager::musiclist()
MusicList ConfigManager::musiclist()
{
QFile l_music_json("config/music.json");
l_music_json.open(QIODevice::ReadOnly | QIODevice::Text);
@ -138,14 +140,13 @@ QStringList ConfigManager::musiclist()
QJsonDocument l_music_list_json = QJsonDocument::fromJson(l_music_json.readAll(), &l_error);
if (!(l_error.error == QJsonParseError::NoError)) { //Non-Terminating error.
qWarning() << "Unable to load musiclist. The following error was encounted : " + l_error.errorString();
return QStringList {}; //Server can still run without music.
return QMap<QString,QPair<QString,int>>{}; //Server can still run without music.
}
// Akashi expects the musiclist to be contained in a JSON array, even if its only a single category.
QJsonArray l_Json_root_array = l_music_list_json.array();
QJsonObject l_child_obj;
QJsonArray l_child_array;
QStringList l_musiclist;
for (int i = 0; i <= l_Json_root_array.size() -1; i++){ //Iterate trough entire JSON file to assemble musiclist
l_child_obj = l_Json_root_array.at(i).toObject();
@ -153,7 +154,7 @@ QStringList ConfigManager::musiclist()
QString l_category_name = l_child_obj["category"].toString();
if (!l_category_name.isEmpty()) {
m_musicList->insert(l_category_name,{l_category_name,0});
l_musiclist.append(l_category_name);
m_ordered_list->append(l_category_name);
}
else {
qWarning() << "Category name not set. This may cause the musiclist to be displayed incorrectly.";
@ -167,17 +168,19 @@ QStringList ConfigManager::musiclist()
if (l_real_name.isEmpty()) {
l_real_name = l_song_name;
}
float l_song_duration = l_song_obj["length"].toVariant().toFloat();
int l_song_duration = l_song_obj["length"].toVariant().toInt();
m_musicList->insert(l_song_name,{l_real_name,l_song_duration});
l_musiclist.append(l_song_name);
m_ordered_list->append(l_song_name);
}
}
l_music_json.close();
if(!l_musiclist[0].contains("==")) // Add a default category if none exists
l_musiclist.insert(0,"==Music==");
return *m_musicList;
}
return l_musiclist;
QStringList ConfigManager::ordered_songs()
{
return *m_ordered_list;
}
void ConfigManager::loadCommandHelp()
@ -211,11 +214,6 @@ void ConfigManager::loadCommandHelp()
}
}
QPair<QString,float> ConfigManager::songInformation(const QString &f_songName)
{
return m_musicList->value(f_songName);
}
QSettings* ConfigManager::areaData()
{
return m_areas;
@ -566,6 +564,11 @@ QStringList ConfigManager::gimpList()
return m_commands->gimps;
}
QStringList ConfigManager::cdnList()
{
return m_commands->cdns;
}
bool ConfigManager::advertiseServer()
{
return m_settings->value("Advertiser/advertise","true").toBool();

237
core/src/music_manager.cpp Normal file
View File

@ -0,0 +1,237 @@
#include "include/music_manager.h"
MusicManager::MusicManager(QObject *parent, QStringList f_root_ordered, QStringList f_cdns, QMap<QString, QPair<QString, int>> f_root_list) :
QObject(parent),
m_root_list(f_root_list),
m_root_ordered(f_root_ordered)
{
m_custom_lists = new QHash<int,QMap<QString,QPair<QString,int>>>;
if (!f_cdns.isEmpty()) {
m_cdns = f_cdns;
}
}
MusicManager::~MusicManager()
{
}
QStringList MusicManager::musiclist(int f_area_id)
{
if (m_global_enabled.value(f_area_id)) {
QStringList l_combined_list = m_root_ordered;
l_combined_list.append(m_customs_ordered.value(f_area_id));
return l_combined_list;
}
return m_custom_lists->value(f_area_id).keys();
}
QStringList MusicManager::rootMusiclist()
{
return m_root_ordered;
}
bool MusicManager::registerArea(int f_area_id)
{
if(m_custom_lists->contains(f_area_id)) {
//This area is already registered. We can't add it.
return false;
}
m_custom_lists->insert(f_area_id,{});
m_global_enabled.insert(f_area_id,true);
return true;
}
bool MusicManager::validateSong(QString f_song_name, QStringList f_approved_cdns)
{
QStringList l_extensions = {".opus", ".ogg", ".mp3", ".wav" };
bool l_cdn_approved = false;
//Check if URL formatted.
if (f_song_name.contains("/")) {
//Only allow HTTPS/HTTP sources.
if (f_song_name.startsWith("https://") || f_song_name.startsWith("http://")) {
for (const QString &l_cdn : qAsConst(f_approved_cdns)) {
//Iterate trough all available CDNs to find an approved match
if (f_song_name.startsWith("https://" + l_cdn + "/", Qt::CaseInsensitive)
|| f_song_name.startsWith("http://" + l_cdn + "/", Qt::CaseInsensitive)) {
l_cdn_approved = true;
break;
}
}
if (!l_cdn_approved) {
return false;
}
}
else {
return false;
}
}
bool l_suffix_found = false;;
for (const QString &suffix : qAsConst(l_extensions)) {
if (f_song_name.endsWith(suffix)){
l_suffix_found = true;
break;
}
}
if (!l_suffix_found) {
return false;
}
return true;
}
bool MusicManager::addCustomSong(QString f_song_name, QString f_real_name, int f_duration, int f_area_id)
{
//Validate if simple name.
QString l_song_name = f_song_name;
if (f_song_name.split(".").size() == 1) {
l_song_name = l_song_name + ".opus";
}
QString l_real_name = f_real_name;
if (f_real_name.split(".").size() == 1) {
l_real_name = l_real_name + ".opus";
}
if (!(validateSong(l_song_name, m_cdns) && validateSong(l_real_name, m_cdns))) {
return false;
}
// Avoid conflicts by checking if it exists.
if (m_root_list.contains(l_song_name) && m_global_enabled[f_area_id]) {
return false;
}
if (m_custom_lists->value(f_area_id).contains(f_song_name)) {
return false;
}
if (m_customs_ordered.value(f_area_id).contains(l_song_name)){
return false;
}
// There should be a way to directly insert into the QMap. Too bad!
MusicList l_custom_list = m_custom_lists->value(f_area_id);
l_custom_list.insert(l_song_name,{l_real_name,f_duration});
m_custom_lists->insert(f_area_id,l_custom_list);
m_customs_ordered.insert(f_area_id,(QStringList {m_customs_ordered.value(f_area_id)} << l_song_name));
emit sendAreaFMPacket(AOPacket("FM",musiclist(f_area_id)), f_area_id);
return true;
}
bool MusicManager::addCustomCategory(QString f_category_name, int f_area_id)
{
if (f_category_name.split(".").size() > 1) {
return false;
}
QString l_category_name = f_category_name;
if (!f_category_name.startsWith("==")) {
l_category_name = "==" + l_category_name + "==";
}
// Avoid conflicts by checking if it exists.
if (m_root_list.contains(l_category_name) && m_global_enabled.value(f_area_id)) {
return false;
}
if (m_custom_lists->value(f_area_id).contains(l_category_name)) {
return false;
}
QMap<QString,QPair<QString,int>> l_custom_list = m_custom_lists->value(f_area_id);
l_custom_list.insert(l_category_name,{l_category_name,0});
m_custom_lists->insert(f_area_id,l_custom_list);
m_customs_ordered.insert(f_area_id,(QStringList {m_customs_ordered.value(f_area_id)} << l_category_name));
emit sendAreaFMPacket(AOPacket("FM",musiclist(f_area_id)), f_area_id);
return true;
}
bool MusicManager::removeCategorySong(QString f_songcategory_name, int f_area_id)
{
if (!m_root_list.contains(f_songcategory_name)){
MusicList l_custom_list = m_custom_lists->value(f_area_id);
if (l_custom_list.contains(f_songcategory_name)) {
l_custom_list.remove(f_songcategory_name);
m_custom_lists->insert(f_area_id,l_custom_list);
//Updating the list alias too.
QStringList l_customs_ordered = m_customs_ordered.value(f_area_id);
l_customs_ordered.removeAll(f_songcategory_name);
m_customs_ordered.insert(f_area_id, l_customs_ordered);
emit sendAreaFMPacket(AOPacket("FM",musiclist(f_area_id)), f_area_id);
return true;
} // Fallthrough
}
return false;
}
bool MusicManager::toggleRootEnabled(int f_area_id)
{
m_global_enabled.insert(f_area_id, !m_global_enabled.value(f_area_id));
if (m_global_enabled.value(f_area_id)) {
sanitiseCustomList(f_area_id);
}
emit sendAreaFMPacket(AOPacket("FM",musiclist(f_area_id)), f_area_id);
return m_global_enabled.value(f_area_id);
}
void MusicManager::sanitiseCustomList(int f_area_id)
{
MusicList l_sanitised_list;
QStringList l_sanitised_ordered = m_customs_ordered.value(f_area_id);
for (auto iterator = m_custom_lists->value(f_area_id).keyBegin(),
end = m_custom_lists->value(f_area_id).keyEnd(); iterator != end; ++iterator)
{
QString l_key = iterator.operator*();
if (!m_root_list.contains(l_key)) {
l_sanitised_list.insert(l_key, m_custom_lists->value(f_area_id).value(l_key));
}
else {
l_sanitised_ordered.removeAll(l_key);
}
}
m_custom_lists->insert(f_area_id, l_sanitised_list);
m_customs_ordered.insert(f_area_id, l_sanitised_ordered);
}
void MusicManager::clearCustomList(int f_area_id)
{
m_custom_lists->remove(f_area_id);
m_custom_lists->insert(f_area_id,{});
m_customs_ordered.remove(f_area_id);
m_customs_ordered.insert(f_area_id, {});
}
QPair<QString, int> MusicManager::songInformation(QString f_song_name, int f_area_id)
{
if (m_root_list.contains(f_song_name)) {
return m_root_list.value(f_song_name);
}
return m_custom_lists->value(f_area_id).value(f_song_name);
}
bool MusicManager::isCustom(int f_area_id, QString f_song_name)
{
if (m_customs_ordered.value(f_area_id).contains(f_song_name, Qt::CaseInsensitive)) {
return true;
}
return false;
}
void MusicManager::reloadRequest()
{
m_root_list = ConfigManager::musiclist();
m_root_ordered = ConfigManager::ordered_songs();
m_cdns = ConfigManager::cdnList();
}
void MusicManager::userJoinedArea(int f_area_index, int f_user_id)
{
emit sendFMPacket(AOPacket("FM", musiclist(f_area_index)), f_user_id);
}

View File

@ -299,7 +299,7 @@ void AOClient::pktChangeMusic(AreaData* area, int argc, QStringList argv, AOPack
// argument is a valid song
QString l_argument = argv[0];
if (server->m_music_list.contains(l_argument) || l_argument == "~stop.mp3") { // ~stop.mp3 is a dummy track used by 2.9+
if (server->m_music_list.contains(l_argument) || m_music_manager->isCustom(m_current_area, l_argument) || l_argument == "~stop.mp3") { // ~stop.mp3 is a dummy track used by 2.9+
// We have a song here
if (m_is_dj_blocked) {
sendServerMessage("You are blocked from changing the music.");
@ -327,9 +327,8 @@ void AOClient::pktChangeMusic(AreaData* area, int argc, QStringList argv, AOPack
return;
}
QPair<QString,float> l_song = ConfigManager::songInformation(l_final_song);
QPair<QString,float> l_song = m_music_manager->songInformation(l_final_song, m_current_area);
QString l_real_name = l_song.first;
qDebug() << l_real_name;
AOPacket l_music_change("MC", {l_real_name, argv[1], m_showname, "1", "0", l_effects});
area->setCurrentMusic(l_final_song);
area->setMusicPlayedBy(m_showname);

View File

@ -81,21 +81,32 @@ void Server::start()
//Get characters from config file
m_characters = ConfigManager::charlist();
//Get musiclist from config file
m_music_list = ConfigManager::musiclist();
//Get backgrounds from config file
m_backgrounds = ConfigManager::backgrounds();
//Build our music manager.
ConfigManager::musiclist();
music_manager = new MusicManager(this, ConfigManager::ordered_songs(), ConfigManager::cdnList());
connect(music_manager, &MusicManager::sendFMPacket,
this, &Server::unicast);
connect(music_manager, &MusicManager::sendAreaFMPacket,
this, QOverload<AOPacket,int>::of(&Server::broadcast));
//Get musiclist from config file
m_music_list = music_manager->rootMusiclist();
//Assembles the area list
m_area_names = ConfigManager::sanitizedAreaNames();
QStringList raw_area_names = ConfigManager::rawAreaNames();
for (int i = 0; i < raw_area_names.length(); i++) {
QString area_name = raw_area_names[i];
AreaData* l_area = new AreaData(area_name, i);
AreaData* l_area = new AreaData(area_name, i, music_manager);
m_areas.insert(i, l_area);
connect(l_area, &AreaData::playJukeboxSong,
connect(l_area, &AreaData::sendAreaPacket,
this, QOverload<AOPacket,int>::of(&Server::broadcast));
connect(l_area, &AreaData::userJoinedArea,
music_manager, &MusicManager::userJoinedArea);
music_manager->registerArea(i);
}
//Loads the command help information. This is not stored inside the server.
@ -130,7 +141,7 @@ void Server::clientConnected()
}
int user_id = m_available_ids.dequeue();
AOClient* client = new AOClient(this, socket, this, user_id);
AOClient* client = new AOClient(this, socket, this, user_id, music_manager);
m_clients_ids.insert(user_id, client);
int multiclient_count = 1;
@ -247,7 +258,6 @@ void Server::reloadSettings()
emit updateHTTPConfiguration();
handleDiscordIntegration();
logger->loadLogtext();
m_music_list = ConfigManager::musiclist();
m_ipban_list = ConfigManager::iprangeBans();
}
@ -304,6 +314,15 @@ void Server::broadcast(AOPacket packet, AOPacket other_packet, TARGET_TYPE targe
}
}
void Server::unicast(AOPacket f_packet, int f_client_id)
{
AOClient* l_client = getClientByID(f_client_id);
if (l_client != nullptr) { // This should never happen, but safety first.
l_client->sendPacket(f_packet);
return;
}
}
QList<AOClient*> Server::getClientsByIpid(QString ipid)
{
QList<AOClient*> return_clients;

View File

@ -1,4 +1,5 @@
TEMPLATE = subdirs
SUBDIRS += \
unittest_area
unittest_area \
unittest_music_manager

View File

@ -66,7 +66,7 @@ private slots:
void Area::init()
{
m_area = new AreaData("Test Area", 0);
m_area = new AreaData("Test Area", 0, nullptr);
}
void Area::cleanup()

View File

@ -0,0 +1,322 @@
#include <QTest>
#include <include/music_manager.h>
namespace tests {
namespace unittests {
/**
* @brief Unit Tester class for the musiclist-related functions.
*/
class MusicListManager : public QObject
{
Q_OBJECT
public :
MusicManager* m_music_manager;
private slots:
/**
* @brief Initialises every tests with creating a new MusicManager with a small sample root list.
*/
void init();
/**
* @brief Tests the registration of areas in the music manager.
*/
void registerArea();
/**
* @brief Tests toggling the enabling/disabling of the prepend behaviour of our root list.
*/
void toggleRootEnabled();
/**
* @brief The data function for validateSong()
*/
void validateSong_data();
/**
* @brief Tests validation of song candidates.
*/
void validateSong();
/**
* @brief Tests the addition of custom music.
*/
void addCustomSong();
/**
* @brief Tests the addition of a custom category.
*/
void addCustomCategory();
/**
* @brief Test the sanitisation of the custom list when root prepend is reenabled.
*/
void sanitiseCustomList();
/**
* @brief Tests the removing of custom songs and categories.
*/
void removeCategorySong();
/**
* @brief Tests the retrieval of song information.
*/
void songInformation();
/**
* @brief Tests the retrieval of the full musiclist for an area.
*/
void musiclist();
};
void MusicListManager::init()
{
QMap<QString,QPair<QString,int>> l_test_list;
l_test_list.insert("==Music==",{"==Music==",0});
l_test_list.insert("Announce The Truth (AJ).opus",{"Announce The Truth (AJ).opus",59});
l_test_list.insert("Announce The Truth (JFA).opus",{"Announce The Truth (JFA).opus",98});
QStringList l_list = {};
l_list << "==Music==" << "Announce The Truth (AJ).opus" << "Announce The Truth (JFA).opus";
m_music_manager = new MusicManager(nullptr, l_list ,{"my.cdn.com","your.cdn.com"}, l_test_list);
}
void MusicListManager::registerArea()
{
{
//We register a single area with the music manager of ID 0.
//Creation should work as there are no other areas yet.
bool l_creation_success = m_music_manager->registerArea(0);
QCOMPARE(l_creation_success,true);
}
{
//Someone tries to register the same area again!
//This should fail as this area already exists.
bool l_creation_success = m_music_manager->registerArea(0);
QCOMPARE(l_creation_success,false);
}
}
void MusicListManager::toggleRootEnabled()
{
{
//We register an area of ID0 and toggle the inclusion of global list.
m_music_manager->registerArea(0);
QCOMPARE(m_music_manager->toggleRootEnabled(0), false);
}
{
//We toggle it again. It should return true now.
//Since this is now true, we should have the root list with customs cleared.
QCOMPARE(m_music_manager->toggleRootEnabled(0), true);
}
}
void MusicListManager::validateSong_data()
{
//Songname can also be the realname.
QTest::addColumn<QString>("songname");
QTest::addColumn<bool>("expectedResult");
QTest::addRow("Songname - No extension") << "Announce The Truth (AA)" << false;
QTest::addRow("Songname - Valid Extension") << "Announce The Truth (AA).opus" << true;
QTest::addRow("Songname - Invalid Extension") << "Announce The Truth (AA).aac" << false;
QTest::addRow("URL - Valid primary") << "https://my.cdn.com/mysong.opus" << true;
QTest::addRow("URL - Valid secondary") << "https://your.cdn.com/mysong.opus" << true;
QTest::addRow("URL - Invalid extension") << "https://my.cdn.com/mysong.aac." << false;
QTest::addRow("URL - Invalid prefix") << "ftp://my.cdn.com/mysong.opus" << false;
QTest::addRow("URL - Invalid missing prefix") << "my.cdn.com/mysong.opus" << false;
QTest::addRow("URL - Invalid CDN") << "https://myipgrabber.com/mysong.opus" << false;
QTest::addRow("URL - Subdomain Attack") << "https://my.cdn.com.fakedomain.com/mysong.opus" << false;
}
void MusicListManager::validateSong()
{
QFETCH(QString,songname);
QFETCH(bool,expectedResult);
bool l_result = m_music_manager->validateSong(songname, {"my.cdn.com","your.cdn.com"});
QCOMPARE(expectedResult,l_result);
}
void MusicListManager::addCustomSong()
{
{
//Dummy register.
m_music_manager->registerArea(0);
//No custom songs, so musiclist = root_list.size()
QCOMPARE(m_music_manager->musiclist(0).size(), 3);
}
{
//Add a song that's valid. The musiclist is now root_list.size() + custom_list.size()
m_music_manager->addCustomSong("mysong","mysong.opus",0,0);
QCOMPARE(m_music_manager->musiclist(0).size(), 4);
}
{
//Add a song that's part of the root list. This should fail and not increase the size.
bool l_result = m_music_manager->addCustomSong("Announce The Truth (AJ)","Announce The Truth (AJ).opus",0,0);
QCOMPARE(l_result,false);
QCOMPARE(m_music_manager->musiclist(0).size(), 4);
}
{
//Disable the root list. Musiclist is now custom_list.size()
m_music_manager->toggleRootEnabled(0);
QCOMPARE(m_music_manager->musiclist(0).size(), 1);
}
{
//Add an item that is in the root list into the custom list. Size is still custom_list.size()
bool l_result = m_music_manager->addCustomSong("Announce The Truth (AJ)","Announce The Truth (AJ).opus",0,0);
QCOMPARE(l_result,true);
QCOMPARE(m_music_manager->musiclist(0).size(), 2);
}
{
bool l_result = m_music_manager->addCustomSong("Announce The Truth (AJ)2","https://my.cdn.com/mysong.opus",0,0);
QCOMPARE(l_result,true);
QCOMPARE(m_music_manager->musiclist(0).size(), 3);
}
}
void MusicListManager::addCustomCategory()
{
{
//Dummy register.
m_music_manager->registerArea(0);
//Add category to the custom list. Category marker are added manually.
bool l_result = m_music_manager->addCustomCategory("Music2",0);
QCOMPARE(l_result,true);
QCOMPARE(m_music_manager->musiclist(0).size(), 4);
QCOMPARE(m_music_manager->musiclist(0).at(3), "==Music2==");
}
{
//Add a category that already exists on root. This should fail and not increase the size of our list.
bool l_result = m_music_manager->addCustomCategory("Music",0);
QCOMPARE(l_result, false);
QCOMPARE(m_music_manager->musiclist(0).size(), 4);
}
{
//We disable the root list. We now insert the category again.
m_music_manager->toggleRootEnabled(0);
bool l_result = m_music_manager->addCustomCategory("Music",0);
QCOMPARE(l_result, true);
QCOMPARE(m_music_manager->musiclist(0).size(), 2);
QCOMPARE(m_music_manager->musiclist(0).at(1), "==Music==");
}
{
//Global now enabled. We add a song with three ===.
m_music_manager->toggleRootEnabled(0);
bool l_result = m_music_manager->addCustomCategory("===Music===",0);
QCOMPARE(l_result, true);
QCOMPARE(m_music_manager->musiclist(0).size(), 5);
QCOMPARE(m_music_manager->musiclist(0).at(4), "===Music===");
}
}
void MusicListManager::sanitiseCustomList()
{
//Prepare a dummy area with root list disabled.Insert both non-root and root elements.
m_music_manager->registerArea(0);
m_music_manager->toggleRootEnabled(0);
m_music_manager->addCustomCategory("Music",0);
m_music_manager->addCustomCategory("Music2",0);
m_music_manager->addCustomSong("Announce The Truth (AJ)","Announce The Truth (AJ).opus",0,0);
m_music_manager->addCustomSong("mysong","mysong.opus",0,0);
//We now only have custom elements.
QCOMPARE(m_music_manager->musiclist(0).size(), 4);
//We reenable the root list. Sanisation should only leave the non-root elements in the custom list.
m_music_manager->toggleRootEnabled(0);
QCOMPARE(m_music_manager->musiclist(0).size(), 5);
QCOMPARE(m_music_manager->musiclist(0).at(3), "==Music2==");
QCOMPARE(m_music_manager->musiclist(0).at(4), "mysong.opus");
}
void MusicListManager::removeCategorySong()
{
{
//Prepare dummy area. Add both custom songs and categories.
m_music_manager->registerArea(0);
m_music_manager->addCustomCategory("Music2",0);
m_music_manager->addCustomSong("mysong","mysong.opus",0,0);
m_music_manager->addCustomCategory("Music3",0);
m_music_manager->addCustomSong("mysong2","mysong.opus",0,0);
QCOMPARE(m_music_manager->musiclist(0).size(), 7);
}
{
//Delete a category that is not custom. This should fail.
bool l_success = m_music_manager->removeCategorySong("==Music==",0);
QCOMPARE(l_success, false);
QCOMPARE(m_music_manager->musiclist(0).size(), 7);
}
{
//Correct category name, wrong format.
bool l_success = m_music_manager->removeCategorySong("Music2",0);
QCOMPARE(l_success, false);
QCOMPARE(m_music_manager->musiclist(0).size(), 7);
}
{
//Correct category name. This should be removed.
bool l_success = m_music_manager->removeCategorySong("==Music2==",0);
QCOMPARE(l_success, true);
QCOMPARE(m_music_manager->musiclist(0).size(), 6);
}
{
//Correct song name. This should be removed. This needs to be with the extension.
bool l_success = m_music_manager->removeCategorySong("mysong2.opus",0);
QCOMPARE(l_success, true);
QCOMPARE(m_music_manager->musiclist(0).size(), 5);
}
}
void MusicListManager::songInformation()
{
{
//Prepare dummy area. Add both custom songs and categories.
m_music_manager->registerArea(0);
m_music_manager->addCustomCategory("Music2",0);
m_music_manager->addCustomSong("mysong","realmysong.opus",47,0);
m_music_manager->addCustomCategory("Music3",0);
m_music_manager->addCustomSong("mysong2","mysong.opus",42,0);
}
{
QPair<QString,int> l_song_information = m_music_manager->songInformation("mysong.opus",0);
QCOMPARE(l_song_information.first, "realmysong.opus");
QCOMPARE(l_song_information.second, 47);
}
{
QPair<QString,int> l_song_information = m_music_manager->songInformation("Announce The Truth (AJ).opus",0);
QCOMPARE(l_song_information.first, "Announce The Truth (AJ).opus");
QCOMPARE(l_song_information.second, 59);
}
}
void MusicListManager::musiclist()
{
{
//Prepare dummy area. Add both custom songs and categories.
m_music_manager->registerArea(0);
m_music_manager->addCustomCategory("Music2",0);
m_music_manager->addCustomSong("mysong","realmysong.opus",47,0);
m_music_manager->addCustomCategory("Music3",0);
m_music_manager->addCustomSong("mysong2","mysong.opus",42,0);
}
{
QCOMPARE(m_music_manager->musiclist(0).size(), 7);
}
}
}
}
QTEST_APPLESS_MAIN(tests::unittests::MusicListManager)
#include "tst_unittest_music_manager.moc"

View File

@ -0,0 +1,5 @@
QT -= gui
include(../tests_common.pri)
SOURCES += tst_unittest_music_manager.cpp