Skip to content

Commit

Permalink
implement pack user override system
Browse files Browse the repository at this point in the history
  • Loading branch information
black-sliver committed Jan 3, 2024
1 parent 7b1ef31 commit 63a6a00
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 18 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,15 @@ Currently there is no plug-in interface.
If you want to work towards implementing such a system, please check
[PLUGIN LICENSE ADDENDUM.md](PLUGIN%20LICENSE%20ADDENDUM.md)
for licensing considerations.

## User Overrides
Users can override files from packs by creating a folder with the same file
structure as in the pack, named `.../user-override/<pack_uid>` where `...` is
any one of `Documents/PopTracker`, `%home%/PopTracker` or `AppPath`.

## Portable Mode
When creating a file called `portable.txt` next to the program (not macos) or
next to poptracker **inside** the AppBundle (macos-only), the app runs in
portable mode, which changes the default pack folder to be next to the program
(not in home folder) and disables asset and pack overrides from home folder
(only allows overrides from program folder).
2 changes: 1 addition & 1 deletion doc/PACKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ Configures behavior of the pack.
"smooth_scaling": true|false|null, // configure the image scaling method. null = default = currently crisp
}

Currently **not** user overridable.
NOTE: User overrides for settings are merged with the pack, i.e. do not simply replace it.


## Lua Interface
Expand Down
121 changes: 109 additions & 12 deletions src/core/pack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <dirent.h>
#include <stdlib.h>
#include "sha256.h"
#include "util.h"


using nlohmann::json;
Expand Down Expand Up @@ -103,9 +104,10 @@ static bool dirNewerThan(const char* path, const std::chrono::system_clock::time


std::vector<std::string> Pack::_searchPaths;
std::vector<std::string> Pack::_overrideSearchPaths;


Pack::Pack(const std::string& path) : _zip(nullptr), _path(path)
Pack::Pack(const std::string& path) : _zip(nullptr), _path(path), _override(nullptr)
{
_loaded = std::chrono::system_clock::now();
std::string s;
Expand Down Expand Up @@ -134,10 +136,23 @@ Pack::Pack(const std::string& path) : _zip(nullptr), _path(path)
_minPopTrackerVersion = to_string(_manifest, "min_poptracker_version", "");
_targetPopTrackerVersion = to_string(_manifest, "target_poptracker_version", "");
}

if (!_uid.empty()) {
for (const auto& path: _overrideSearchPaths) {
std::string overridePath = os_pathcat(path, _uid);
if (dirExists(overridePath)) {
_override = new Override(overridePath);
break;
}
}
}
}

Pack::~Pack()
{
if (_override)
delete _override;
_override = nullptr;
if (_zip)
delete _zip;
_zip = nullptr;
Expand Down Expand Up @@ -201,8 +216,23 @@ bool Pack::hasFile(const std::string& userfile) const
bool Pack::ReadFile(const std::string& userfile, std::string& out) const
{
std::string file;
if (!sanitizePath(userfile, file)) return false;

if (!sanitizePath(userfile, file))
return false;

if (_override && userfile != "settings.json") {
bool packHasVariantFile = false;
if (!_variant.empty()) {
if (_zip)
packHasVariantFile = _zip->hasFile(_variant+"/"+file);
else
packHasVariantFile = fileExists(os_pathcat(_path,_variant,file));
}
if (packHasVariantFile && _override->ReadFile(_variant+"/"+file, out))
return true;
else if ((!packHasVariantFile || _variant.empty()) && _override->ReadFile(file, out))
return true;
}

if (_zip) {
if (!_variant.empty() && _zip->readFile(_variant+"/"+file, out))
return true;
Expand Down Expand Up @@ -252,14 +282,26 @@ void Pack::setVariant(const std::string& variant)

std::string s;
if (ReadFile("settings.json", s)) {
// TODO: allow extending from pack overrides
_settings = parse_jsonc(s);
if (_settings.type() != json::value_t::object)
if (!_settings.is_object())
fprintf(stderr, "WARNING: invalid settings.json\n");
}

if (_settings.type() != json::value_t::object)
_settings = json::object();

if (_override) {
printf("overriding from %s\n", sanitize_print(_override->getPath()).c_str());
if (_override->ReadFile("settings.json", s)) {
json overrides = parse_jsonc(s);
if (!overrides.is_object()) {
fprintf(stderr, "WARNING: invalid settings.json override\n");
} else {
printf("settings.json overridden\n");
_settings.update(overrides); // extend/override
}
}
}
}

std::string Pack::getPlatform() const
Expand Down Expand Up @@ -322,6 +364,8 @@ std::set<std::string> Pack::getVariantFlags() const

bool Pack::hasFilesChanged() const
{
if (_override && _override->hasFilesChanged(_loaded))
return true;
if (_zip)
return fileNewerThan(_path, _loaded);
else
Expand Down Expand Up @@ -409,34 +453,39 @@ Pack::Info Pack::Find(const std::string& uid, const std::string& version, const
return {};
}

void Pack::addSearchPath(const std::string& path)
static void addPath(const std::string& path, std::vector<std::string>& paths)
{
#if !defined WIN32 && !defined _WIN32
char* tmp = realpath(path.c_str(), NULL);
if (tmp) {
std::string real = tmp;
free(tmp);
if (std::find(_searchPaths.begin(), _searchPaths.end(), real) != _searchPaths.end())
if (std::find(paths.begin(), paths.end(), real) != paths.end())
return;
_searchPaths.push_back(real);
paths.push_back(real);
} else
#else
char* tmp = _fullpath(NULL, path.c_str(), 1024);
if (tmp) {
auto cmp = [tmp](const std::string& s) { return strcasecmp(tmp, s.c_str()) == 0; };
if (std::find_if(_searchPaths.begin(), _searchPaths.end(), cmp) != _searchPaths.end())
if (std::find_if(paths.begin(), paths.end(), cmp) != paths.end())
return;
_searchPaths.push_back(tmp);
paths.push_back(tmp);
free(tmp);
} else
#endif
{
if (std::find(_searchPaths.begin(), _searchPaths.end(), path) != _searchPaths.end())
if (std::find(paths.begin(), paths.end(), path) != paths.end())
return;
_searchPaths.push_back(path);
paths.push_back(path);
}
}

void Pack::addSearchPath(const std::string& path)
{
addPath(path, _searchPaths);
}

bool Pack::isInSearchPath(const std::string& uncleanPath)
{
std::string path = cleanUpPath(uncleanPath);
Expand All @@ -452,3 +501,51 @@ const std::vector<std::string>& Pack::getSearchPaths()
{
return _searchPaths;
}

void Pack::addOverrideSearchPath(const std::string& path)
{
addPath(path, _overrideSearchPaths);
}

Pack::Override::Override(const std::string& path)
: _path(path)
{
}

Pack::Override::~Override()
{
}

bool Pack::Override::ReadFile(const std::string& userfile, std::string& out) const
{
std::string file;
if (!sanitizePath(userfile, file))
return false;

out.clear();
FILE* f = nullptr;
if (!f) {
f = fopen(os_pathcat(_path,file).c_str(), "rb");
}
if (!f) {
return false;
}
while (!feof(f)) {
char buf[4096];
size_t sz = fread(buf, 1, sizeof(buf), f);
if (ferror(f)) goto read_err;
out += std::string(buf, sz);
}
fclose(f);
return true;

read_err:
fclose(f);
out.clear();
return false;
}

bool Pack::Override::hasFilesChanged(std::chrono::system_clock::time_point since) const
{
return dirNewerThan(_path.c_str(), since);
}
17 changes: 17 additions & 0 deletions src/core/pack.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,22 @@ class Pack final {
static bool isInSearchPath(const std::string& path);
static const std::vector<std::string>& getSearchPaths();

static void addOverrideSearchPath(const std::string& path);

private:
class Override {
public:
Override(const std::string& path);
virtual ~Override();

bool ReadFile(const std::string& file, std::string& out) const;
bool hasFilesChanged(std::chrono::system_clock::time_point since) const;
const std::string& getPath() const { return _path; }

private:
std::string _path;
};

Zip* _zip;
std::string _path;
std::string _variant;
Expand All @@ -80,10 +95,12 @@ class Pack final {
Version _targetPopTrackerVersion;
nlohmann::json _manifest;
nlohmann::json _settings;
Override* _override;

std::chrono::system_clock::time_point _loaded;

static std::vector<std::string> _searchPaths;
static std::vector<std::string> _overrideSearchPaths;
};

#endif // _CORE_PACK_H
27 changes: 22 additions & 5 deletions src/poptracker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -170,28 +170,45 @@ PopTracker::PopTracker(int argc, char** argv, bool cli, const json& args)
_ui = nullptr; // UI init moved to start()

Pack::addSearchPath("packs"); // current directory
Pack::addOverrideSearchPath("user-override");

std::string cwdPath = getCwd();
std::string documentsPath = getDocumentsPath();
std::string homePath = getHomePath();

_homePackDir = os_pathcat(homePath, "PopTracker", "packs");
std::string homePopTrackerPath = os_pathcat(homePath, "PopTracker");
_homePackDir = os_pathcat(homePopTrackerPath, "packs");
_appPackDir = os_pathcat(appPath, "packs");

if (!homePath.empty()) {
Pack::addSearchPath(_homePackDir); // default user packs
if (!_isPortable)
Assets::addSearchPath(os_pathcat(homePath, "PopTracker", "assets")); // default user overrides
if (!_isPortable) {
// default user overrides
std::string homeUserOverrides = os_pathcat(homePopTrackerPath, "user-override");
if (dirExists(homePopTrackerPath))
mkdir_recursive(homeUserOverrides);
Pack::addOverrideSearchPath(homeUserOverrides);
Assets::addSearchPath(os_pathcat(homePopTrackerPath, "assets"));
}
}
if (!documentsPath.empty() && documentsPath != ".") {
Pack::addSearchPath(os_pathcat(documentsPath, "PopTracker", "packs")); // alternative user packs
std::string documentsPopTrackerPath = os_pathcat(documentsPath, "PopTracker");
Pack::addSearchPath(os_pathcat(documentsPopTrackerPath, "packs")); // alternative user packs
if (!_isPortable) {
std::string documentsUserOverrides = os_pathcat(documentsPopTrackerPath, "user-override");
Pack::addOverrideSearchPath(documentsUserOverrides);
if (dirExists(documentsPopTrackerPath))
mkdir_recursive(documentsUserOverrides);
}
if (_config.value<bool>("add_emo_packs", false)) {
Pack::addSearchPath(os_pathcat(documentsPath, "EmoTracker", "packs")); // "old" packs
}
}

if (!appPath.empty() && appPath != "." && appPath != cwdPath) {
Pack::addSearchPath(_appPackDir); // system packs
Assets::addSearchPath(os_pathcat(appPath,"assets")); // system assets
Pack::addOverrideSearchPath(os_pathcat(appPath, "user-override")); // portable/system overrides
Assets::addSearchPath(os_pathcat(appPath, "assets")); // system assets
}

_asio = new asio::io_service();
Expand Down

0 comments on commit 63a6a00

Please sign in to comment.