/******************************************************************************!
* \file List.cpp
* \author Sebastien Beaugrand
* \sa http://beaugrand.chez.com/
* \copyright CeCILL 2.1 Free Software license
******************************************************************************/
#include <filesystem>
#include <fstream>
#include <chrono>
#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <ctime>
#include "List.h"
#include "log.h"
/******************************************************************************!
* \fn List
******************************************************************************/
List::List(std::string_view path)
: mPath(path)
{
this->readList();
this->readWeights();
this->readAbrev();
this->readLog();
}
/******************************************************************************!
* \fn timediff
******************************************************************************/
int
List::timediff(std::string_view line) const
{
if (line.substr(0, 11) == " ") {
return -86400;
}
std::tm timeinfo{};
std::istringstream ss(line.data());
ss >> std::get_time(&timeinfo, "%Y-%m-%d %T ");
const std::time_t tt = std::mktime(&timeinfo) + timezone;
const auto tp = std::chrono::system_clock::from_time_t(tt);
const auto now = std::chrono::system_clock::now();
const std::chrono::duration<double> diff = now - tp;
DEBUG(diff.count() << " seconds");
return diff.count();
}
/******************************************************************************!
* \fn rand
******************************************************************************/
std::tuple<std::string, std::string, int>
List::rand() const
{
static int r = -1;
if (r < 0) {
std::srand(std::time(nullptr));
}
r = std::rand() / ((RAND_MAX / mWeightSum) + 1);
int max = 0;
for (auto it : mList) {
max += it.weight;
if (max > r) {
r = std::rand() / ((RAND_MAX / it.size) + 1);
for (auto al : it.list) {
if (r == 0) {
return {
it.name + '/' + al.substr(11),
it.abrev,
this->timediff(al) / 86400
};
}
--r;
}
}
}
return { "error", "XX", -1 };
}
/******************************************************************************!
* \fn artist
******************************************************************************/
Json::Value
List::artist(const std::string& artist,
const std::string& album) const
{
Json::Value r;
std::string search;
std::string current;
if (artist.empty()) {
if (! std::filesystem::exists(mPath + "/mps.last")) {
return r;
}
std::ifstream input(mPath + "/mps.last");
std::string line;
std::getline(input, line);
if (line.empty()) {
return r;
}
auto path = line.substr(20);
auto pos1 = path.rfind('/');
auto pos2 = path.rfind(" - ", pos1 - 1) + 3;
auto pos3 = path.rfind(" - ", pos2 - 4);
auto pos4 = path.rfind('/', pos3 - 1) + 1;
search = path.substr(pos4, pos3 - pos4);
current = path.substr(pos2, pos1 - pos2);
} else {
search = artist;
current = album;
}
int count = 0;
r["artist"] = search;
for (auto it : mList) {
for (auto al : it.list) {
auto path = al.substr(11);
if (path.starts_with(search + " - ")) {
auto pos1 = path.rfind('/');
auto pos2 = path.rfind(" - ", pos1 - 1) + 3;
auto pos3 = path.rfind(" - ", pos2 - 4) + 3;
auto albu = path.substr(pos2, pos1 - pos2);
auto date = path.substr(pos3, pos2 - pos3 - 3);
r["album"].append(date + " " + albu);
if (albu == current) {
r["pos"] = count;
DEBUG("pos " << count);
}
++count;
}
}
}
return r;
}
/******************************************************************************!
* \fn album
******************************************************************************/
std::string
List::album(const std::string& search, int pos) const
{
if (pos < 0) {
for (auto it : mList) {
for (auto al : it.list) {
auto path = al.substr(11);
auto pos1 = path.rfind('/');
auto pos3 = path.rfind('/', pos1 - 1) + 1;
auto pos2 = path.find(" - ", pos3);
auto arti = path.substr(pos3, pos2 - pos3);
auto count = search.size();
std::string str;
for (const auto c : arti) {
if (c != ' ') {
str.push_back(::toupper(c));
if (--count == 0) {
break;
}
}
}
if (str == search) {
return it.name + '/' + al.substr(11);
}
}
}
} else {
int count = 0;
for (auto it : mList) {
for (auto al : it.list) {
auto path = al.substr(11);
if (path.starts_with(search + " - ")) {
auto pos1 = path.rfind('/');
auto pos3 = path.rfind('/', pos1 - 1) + 1;
auto pos2 = path.find(" - ", pos3);
auto arti = path.substr(pos3, pos2 - pos3);
if (arti == search) {
if (count == pos) {
return it.name + '/' + al.substr(11);
}
++count;
}
}
}
}
}
ERROR(search);
return "";
}
/******************************************************************************!
* \fn readResumeTime
******************************************************************************/
int
List::readResumeTime() const
{
if (std::filesystem::exists(mPath + "/mps.resume")) {
std::ifstream file(mPath + "/mps.resume");
int ms;
file >> ms;
return ms;
}
return 0;
}
/******************************************************************************!
* \fn writeResumeTime
******************************************************************************/
void
List::writeResumeTime(int ms) const
{
std::ofstream file(mPath + "/mps.resume");
file << ms;
}
/******************************************************************************!
* \fn writeLog
******************************************************************************/
void
List::writeLog(std::string_view album) const
{
if (std::filesystem::exists(mPath + "/mps.last")) {
std::ifstream input(mPath + "/mps.last");
std::string line;
std::getline(input, line);
if (this->timediff(line) > 300) {
std::ofstream output{ mPath + "/mps.log", std::ios_base::app };
output << line << std::endl;
}
}
std::ofstream file(mPath + "/mps.last");
std::time_t time = std::time({});
char timeString[std::size("yyyy-mm-dd hh:mm:ss ")];
std::strftime(std::data(timeString), std::size(timeString),
"%F %T ", std::localtime(&time));
file << timeString << album << std::endl;
}
/******************************************************************************!
* \fn dir
******************************************************************************/
Json::Value
List::dir(const std::string& path) const
{
Json::Value result;
std::list<std::string> list;
try {
std::filesystem::directory_iterator d(mPath + '/' + path);
while (d != std::filesystem::end(d)) {
if (d->is_directory()) {
if (path.empty()) {
list.push_back(d->path().filename().string());
} else {
list.push_back(path + '/' +
d->path().filename().string());
}
}
++d;
}
} catch (const std::filesystem::filesystem_error& e) {
ERROR(e.what());
}
list.sort();
for (const auto& d : list) {
result["dir"].append(d);
}
return result;
}
/******************************************************************************!
* \fn push
******************************************************************************/
void
List::push(const std::string& path)
{
auto pos = path.find('/');
auto search = path.substr(0, pos);
auto al = std::string(" ") + path.substr(pos + 1);
if (auto it = std::find_if(std::begin(mList), std::end(mList),
[&search](const Part& part) {
return part.name == search;
});
it != std::end(mList)) {
it->list.push_back(al);
++it->size;
return;
}
mList.push_back(Part{ search, 1, 1, { al }, "XX" });
}
/******************************************************************************!
* \fn readList
******************************************************************************/
void
List::readList()
{
if (std::filesystem::exists(mPath + "/mps.list")) {
std::ifstream file(mPath + "/mps.list");
std::string line;
while (file) {
std::getline(file, line);
if (! line.empty()) {
this->push(line);
}
}
} else {
int pathSize = mPath.size() + 1;
std::filesystem::recursive_directory_iterator d(mPath);
while (d != std::filesystem::end(d)) {
if (d->path().extension() == ".m3u") {
this->push(d->path().string().substr(pathSize));
}
++d;
}
mList.sort([](const List::Part& a,
const List::Part& b) {
return a.name < b.name;
});
std::ofstream file(mPath + "/mps.list");
for (auto it : mList) {
it.list.sort();
for (auto al : it.list) {
file << it.name << '/' << al.substr(11) << std::endl;
}
}
}
}
/******************************************************************************!
* \fn readWeights
******************************************************************************/
void
List::readWeights()
{
if (std::filesystem::exists(mPath + "/mps.weights")) {
std::ifstream file(mPath + "/mps.weights");
while (file) {
std::string name;
std::string weight;
std::getline(file, name);
std::getline(file, weight);
for (auto& it : mList) {
if (it.name == name) {
it.weight = std::stoi(weight);
mWeightSum += it.weight;
}
}
}
} else {
std::ofstream file(mPath + "/mps.weights");
for (auto it : mList) {
file << it.name << std::endl;
file << 1 << std::endl;
mWeightSum += 1;
}
}
}
/******************************************************************************!
* \fn readAbrev
******************************************************************************/
void
List::readAbrev()
{
if (std::filesystem::exists(mPath + "/mps.abrev")) {
std::ifstream file(mPath + "/mps.abrev");
auto it = mList.begin();
while (file && it != mList.end()) {
file >> it->abrev;
++it;
}
} else {
std::ofstream file(mPath + "/mps.abrev");
for (auto it : mList) {
file << "XX" << std::endl;
}
}
}
/******************************************************************************!
* \fn readLog
******************************************************************************/
void
List::readLog()
{
if (! std::filesystem::exists(mPath + "/mps.log")) {
return;
}
std::ifstream file(mPath + "/mps.log");
std::string line;
while (file) {
std::getline(file, line);
if (line.empty()) {
continue;
}
auto date = line.substr(0, 11);
auto path = line.substr(20);
auto pos = path.find('/');
auto search = path.substr(0, pos);
auto albu = path.substr(pos + 1);
if (auto it = std::find_if(std::begin(mList), std::end(mList),
[&search](const Part& part) {
return part.name == search;
});
it != std::end(mList)) {
for (auto& al : it->list) {
if (al.substr(11) == albu) {
al = date + al.substr(11);
}
}
}
}
}