[bcat/news/web/am] Implement news applet, proper TLV return, external web browser URL and qlaunch app sorting (#3308)

This pulls eden releases changelog & text from our github releases.
We don't store the msgpack file but rather generate them in-memory for the News Applet.
Uses cache folder. Files generated are:
- cache/news/github_releases.json
- cache/news/eden_logo.jpg
- cache/news/news_read

Additional changes:
- Proper TLV returning for online web applet, to open external URL
- Add applet type `LHub` to properly close, as it also uses TLV return
- qlaunch app sorting, adds another cached .json to track last launched app timestamps and sort them accordingly

Co-authored-by: crueter <crueter@eden-emu.dev>
Co-authored-by: DraVee <dravee@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3308
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Co-authored-by: Maufeat <sahyno1996@gmail.com>
Co-committed-by: Maufeat <sahyno1996@gmail.com>
This commit is contained in:
Maufeat 2026-01-17 01:48:15 +01:00 committed by crueter
parent b7417f68ce
commit ae501e256e
No known key found for this signature in database
GPG key ID: 425ACD2D4830EBC6
35 changed files with 2978 additions and 315 deletions

View file

@ -0,0 +1,135 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "core/launch_timestamp_cache.h"
#include <filesystem>
#include <fstream>
#include <mutex>
#include <sstream>
#include <string>
#include <unordered_map>
#include <fmt/format.h>
#include <nlohmann/json.hpp>
#include "common/fs/fs.h"
#include "common/fs/file.h"
#include "common/fs/path_util.h"
#include "common/logging/log.h"
namespace Core::LaunchTimestampCache {
namespace {
using CacheMap = std::unordered_map<u64, s64>;
std::mutex mutex;
CacheMap cache;
bool loaded = false;
std::filesystem::path GetCachePath() {
return Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir) / "launched.json";
}
std::optional<std::string> ReadFileToString(const std::filesystem::path& path) {
const std::ifstream file{path, std::ios::in | std::ios::binary};
if (!file) {
return std::nullopt;
}
std::ostringstream ss;
ss << file.rdbuf();
return ss.str();
}
bool WriteStringToFile(const std::filesystem::path& path, const std::string& data) {
if (!Common::FS::CreateParentDirs(path)) {
return false;
}
std::ofstream file{path, std::ios::out | std::ios::binary | std::ios::trunc};
if (!file) {
return false;
}
file.write(data.data(), static_cast<std::streamsize>(data.size()));
return static_cast<bool>(file);
}
void Load() {
if (loaded) {
return;
}
loaded = true;
const auto path = GetCachePath();
if (!std::filesystem::exists(path)) {
return;
}
const auto data = ReadFileToString(path);
if (!data) {
LOG_WARNING(Core, "Failed to read launch timestamp cache: {}",
Common::FS::PathToUTF8String(path));
return;
}
try {
const auto json = nlohmann::json::parse(data->data(), data->data() + data->size());
if (!json.is_object()) {
return;
}
for (auto it = json.begin(); it != json.end(); ++it) {
const auto key_str = it.key();
const auto value = it.value();
u64 key{};
try {
key = std::stoull(key_str, nullptr, 16);
} catch (...) {
continue;
}
if (value.is_number_integer()) {
cache[key] = value.get<s64>();
}
}
} catch (const std::exception& e) {
LOG_WARNING(Core, "Failed to parse launch timestamp cache");
}
}
void Save() {
nlohmann::json json = nlohmann::json::object();
for (const auto& [key, value] : cache) {
json[fmt::format("{:016X}", key)] = value;
}
const auto path = GetCachePath();
if (!WriteStringToFile(path, json.dump(4))) {
LOG_WARNING(Core, "Failed to write launch timestamp cache: {}",
Common::FS::PathToUTF8String(path));
}
}
s64 NowSeconds() {
return std::time(nullptr);
}
} // namespace
void SaveLaunchTimestamp(u64 title_id) {
std::scoped_lock lk{mutex};
Load();
cache[title_id] = NowSeconds();
Save();
}
s64 GetLaunchTimestamp(u64 title_id) {
std::scoped_lock lk{mutex};
Load();
const auto it = cache.find(title_id);
if (it != cache.end()) {
return it->second;
}
// we need a timestamp, i decided on 01/01/2026 00:00
return 1767225600;
}
} // namespace Core::LaunchTimestampCache