core: Add a periodic donation request (#8981)
Will fire once in december and july. Disableable with `ecosystem:no_donation:nag`
This commit is contained in:
parent
da9252a23e
commit
b5fb6110ab
10 changed files with 278 additions and 115 deletions
114
src/managers/DonationNagManager.cpp
Normal file
114
src/managers/DonationNagManager.cpp
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
#include "DonationNagManager.hpp"
|
||||
#include "../debug/Log.hpp"
|
||||
#include "VersionKeeperManager.hpp"
|
||||
#include "eventLoop/EventLoopManager.hpp"
|
||||
#include "../config/ConfigValue.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <format>
|
||||
|
||||
#include "../helpers/fs/FsUtils.hpp"
|
||||
|
||||
#include <hyprutils/os/Process.hpp>
|
||||
using namespace Hyprutils::OS;
|
||||
|
||||
constexpr const char* LAST_NAG_FILE_NAME = "lastNag";
|
||||
constexpr uint64_t DAY_IN_SECONDS = 3600ULL * 24;
|
||||
constexpr uint64_t MONTH_IN_SECONDS = DAY_IN_SECONDS * 30;
|
||||
|
||||
struct SNagDatePoint {
|
||||
// Counted from 1, as in Jan 1st is 1, 1
|
||||
// No month-boundaries because I am lazy
|
||||
uint8_t month = 0, dayStart = 0, dayEnd = 0;
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
const std::vector<SNagDatePoint> NAG_DATE_POINTS = {
|
||||
SNagDatePoint {
|
||||
7, 20, 31,
|
||||
},
|
||||
SNagDatePoint {
|
||||
12, 1, 28
|
||||
},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
CDonationNagManager::CDonationNagManager() {
|
||||
static auto PNONAG = CConfigValue<Hyprlang::INT>("ecosystem:no_donation_nag");
|
||||
|
||||
if (g_pVersionKeeperMgr->fired() || *PNONAG)
|
||||
return;
|
||||
|
||||
const auto DATAROOT = NFsUtils::getDataHome();
|
||||
|
||||
if (!DATAROOT)
|
||||
return;
|
||||
|
||||
const auto EPOCH = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
|
||||
const auto LASTNAGSTR = NFsUtils::readFileAsString(*DATAROOT + "/" + LAST_NAG_FILE_NAME);
|
||||
|
||||
if (!LASTNAGSTR) {
|
||||
const auto EPOCHSTR = std::format("{}", EPOCH);
|
||||
NFsUtils::writeToFile(*DATAROOT + "/" + LAST_NAG_FILE_NAME, EPOCHSTR);
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t LAST_EPOCH = 0;
|
||||
|
||||
try {
|
||||
LAST_EPOCH = std::stoull(*LASTNAGSTR);
|
||||
} catch (std::exception& e) {
|
||||
Debug::log(ERR, "DonationNag: Last epoch invalid? Failed to parse \"{}\". Setting to today.", *LASTNAGSTR);
|
||||
const auto EPOCHSTR = std::format("{}", EPOCH);
|
||||
NFsUtils::writeToFile(*DATAROOT + "/" + LAST_NAG_FILE_NAME, EPOCHSTR);
|
||||
return;
|
||||
}
|
||||
|
||||
// don't nag if the last nag was less than a month ago. This is
|
||||
// mostly for first-time nags, as other nags happen in specific time frames shorter than a month
|
||||
if (EPOCH - LAST_EPOCH < MONTH_IN_SECONDS) {
|
||||
Debug::log(LOG, "DonationNag: last nag was {} days ago, too early for a nag.", (int)std::round((EPOCH - LAST_EPOCH) / (double)MONTH_IN_SECONDS));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NFsUtils::executableExistsInPath("hyprland-donate-screen")) {
|
||||
Debug::log(ERR, "DonationNag: executable doesn't exist, skipping.");
|
||||
return;
|
||||
}
|
||||
|
||||
auto tt = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
||||
auto local = *localtime(&tt);
|
||||
|
||||
const auto MONTH = local.tm_mon + 1;
|
||||
const auto DAY = local.tm_mday;
|
||||
|
||||
for (const auto& nagPoint : NAG_DATE_POINTS) {
|
||||
if (MONTH != nagPoint.month)
|
||||
continue;
|
||||
|
||||
if (DAY < nagPoint.dayStart || DAY > nagPoint.dayEnd)
|
||||
continue;
|
||||
|
||||
Debug::log(LOG, "DonationNag: hit nag month {} days {}-{}, it's {} today, nagging", MONTH, nagPoint.dayStart, nagPoint.dayEnd, DAY);
|
||||
|
||||
m_bFired = true;
|
||||
|
||||
const auto EPOCHSTR = std::format("{}", EPOCH);
|
||||
NFsUtils::writeToFile(*DATAROOT + "/" + LAST_NAG_FILE_NAME, EPOCHSTR);
|
||||
|
||||
g_pEventLoopManager->doLater([] {
|
||||
CProcess proc("hyprland-donate-screen", {});
|
||||
proc.runAsync();
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (!m_bFired)
|
||||
Debug::log(LOG, "DonationNag: didn't hit any nagging periods");
|
||||
}
|
||||
|
||||
bool CDonationNagManager::fired() {
|
||||
return m_bFired;
|
||||
}
|
||||
16
src/managers/DonationNagManager.hpp
Normal file
16
src/managers/DonationNagManager.hpp
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
class CDonationNagManager {
|
||||
public:
|
||||
CDonationNagManager();
|
||||
|
||||
// whether the donation nag was shown this boot.
|
||||
bool fired();
|
||||
|
||||
private:
|
||||
bool m_bFired = false;
|
||||
};
|
||||
|
||||
inline std::unique_ptr<CDonationNagManager> g_pDonationNagManager;
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
#include "../helpers/varlist/VarList.hpp"
|
||||
#include "eventLoop/EventLoopManager.hpp"
|
||||
#include "../config/ConfigValue.hpp"
|
||||
#include "../helpers/fs/FsUtils.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
|
@ -20,12 +21,12 @@ constexpr const char* VERSION_FILE_NAME = "lastVersion";
|
|||
CVersionKeeperManager::CVersionKeeperManager() {
|
||||
static auto PNONOTIFY = CConfigValue<Hyprlang::INT>("ecosystem:no_update_news");
|
||||
|
||||
const auto DATAROOT = getDataHome();
|
||||
const auto DATAROOT = NFsUtils::getDataHome();
|
||||
|
||||
if (!DATAROOT)
|
||||
return;
|
||||
|
||||
const auto LASTVER = getDataLastVersion(*DATAROOT);
|
||||
const auto LASTVER = NFsUtils::readFileAsString(*DATAROOT + "/" + VERSION_FILE_NAME);
|
||||
|
||||
if (!LASTVER)
|
||||
return;
|
||||
|
|
@ -35,101 +36,26 @@ CVersionKeeperManager::CVersionKeeperManager() {
|
|||
return;
|
||||
}
|
||||
|
||||
writeVersionToVersionFile(*DATAROOT);
|
||||
NFsUtils::writeToFile(*DATAROOT + "/" + VERSION_FILE_NAME, HYPRLAND_VERSION);
|
||||
|
||||
if (*PNONOTIFY) {
|
||||
Debug::log(LOG, "CVersionKeeperManager: updated, but update news is disabled in the config :(");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!executableExistsInPath("hyprland-update-screen")) {
|
||||
if (!NFsUtils::executableExistsInPath("hyprland-update-screen")) {
|
||||
Debug::log(ERR, "CVersionKeeperManager: hyprland-update-screen doesn't seem to exist, skipping notif about update...");
|
||||
return;
|
||||
}
|
||||
|
||||
m_bFired = true;
|
||||
|
||||
g_pEventLoopManager->doLater([]() {
|
||||
CProcess proc("hyprland-update-screen", {"--new-version", HYPRLAND_VERSION});
|
||||
proc.runAsync();
|
||||
});
|
||||
}
|
||||
|
||||
std::optional<std::string> CVersionKeeperManager::getDataHome() {
|
||||
const auto DATA_HOME = getenv("XDG_DATA_HOME");
|
||||
|
||||
std::string dataRoot;
|
||||
|
||||
if (!DATA_HOME) {
|
||||
const auto HOME = getenv("HOME");
|
||||
|
||||
if (!HOME) {
|
||||
Debug::log(ERR, "CVersionKeeperManager: can't get data home: no $HOME or $XDG_DATA_HOME");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
dataRoot = HOME + std::string{"/.local/share/"};
|
||||
} else
|
||||
dataRoot = DATA_HOME + std::string{"/"};
|
||||
|
||||
std::error_code ec;
|
||||
if (!std::filesystem::exists(dataRoot, ec) || ec) {
|
||||
Debug::log(ERR, "CVersionKeeperManager: can't get data home: inaccessible / missing");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
dataRoot += "hyprland/";
|
||||
|
||||
if (!std::filesystem::exists(dataRoot, ec) || ec) {
|
||||
Debug::log(LOG, "CVersionKeeperManager: no hyprland data home, creating.");
|
||||
std::filesystem::create_directory(dataRoot, ec);
|
||||
if (ec) {
|
||||
Debug::log(ERR, "CVersionKeeperManager: can't create new data home for hyprland");
|
||||
return std::nullopt;
|
||||
}
|
||||
std::filesystem::permissions(dataRoot, std::filesystem::perms::owner_read | std::filesystem::perms::owner_write | std::filesystem::perms::owner_exec, ec);
|
||||
if (ec)
|
||||
Debug::log(WARN, "CVersionKeeperManager: couldn't set perms on hyprland data store. Proceeding anyways.");
|
||||
}
|
||||
|
||||
if (!std::filesystem::exists(dataRoot, ec) || ec) {
|
||||
Debug::log(ERR, "CVersionKeeperManager: no hyprland data home, failed to create.");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return dataRoot;
|
||||
}
|
||||
|
||||
std::optional<std::string> CVersionKeeperManager::getDataLastVersion(const std::string& dataRoot) {
|
||||
std::error_code ec;
|
||||
std::string lastVerFile = dataRoot + "/" + VERSION_FILE_NAME;
|
||||
|
||||
if (!std::filesystem::exists(lastVerFile, ec) || ec) {
|
||||
Debug::log(LOG, "CVersionKeeperManager: no hyprland last version file, creating.");
|
||||
writeVersionToVersionFile(dataRoot);
|
||||
|
||||
return "0.0.0";
|
||||
}
|
||||
|
||||
std::ifstream file(lastVerFile);
|
||||
if (!file.good()) {
|
||||
Debug::log(ERR, "CVersionKeeperManager: couldn't open an ifstream for reading the version file.");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return trim(std::string((std::istreambuf_iterator<char>(file)), (std::istreambuf_iterator<char>())));
|
||||
}
|
||||
|
||||
void CVersionKeeperManager::writeVersionToVersionFile(const std::string& dataRoot) {
|
||||
std::string lastVerFile = dataRoot + "/" + VERSION_FILE_NAME;
|
||||
std::ofstream of(lastVerFile, std::ios::trunc);
|
||||
if (!of.good()) {
|
||||
Debug::log(ERR, "CVersionKeeperManager: couldn't open an ofstream for writing the version file.");
|
||||
return;
|
||||
}
|
||||
|
||||
of << HYPRLAND_VERSION;
|
||||
of.close();
|
||||
}
|
||||
|
||||
bool CVersionKeeperManager::isVersionOlderThanRunning(const std::string& ver) {
|
||||
const CVarList verStrings(ver, 0, '.', true);
|
||||
|
||||
|
|
@ -151,3 +77,7 @@ bool CVersionKeeperManager::isVersionOlderThanRunning(const std::string& ver) {
|
|||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CVersionKeeperManager::fired() {
|
||||
return m_bFired;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
class CVersionKeeperManager {
|
||||
public:
|
||||
CVersionKeeperManager();
|
||||
|
||||
// whether the update screen was shown this boot.
|
||||
bool fired();
|
||||
|
||||
private:
|
||||
std::optional<std::string> getDataHome();
|
||||
std::optional<std::string> getDataLastVersion(const std::string& dataRoot);
|
||||
void writeVersionToVersionFile(const std::string& dataRoot);
|
||||
bool isVersionOlderThanRunning(const std::string& ver);
|
||||
bool isVersionOlderThanRunning(const std::string& ver);
|
||||
|
||||
bool m_bFired = false;
|
||||
};
|
||||
|
||||
inline std::unique_ptr<CVersionKeeperManager> g_pVersionKeeperMgr;
|
||||
Loading…
Add table
Add a link
Reference in a new issue