Files
create/src/Gui/PreferencePackManager.cpp
Kacper Donat a32594faea Gui: Add ThemeTokenManager class to contain theme parameters
This class aims to implement Design Token idea into FreeCAD themes. It
allows themes to use generic variables with generic values so we could
use one qss theme and change the style based on values from preference
packs.
2025-07-07 00:07:31 +02:00

669 lines
27 KiB
C++

/***************************************************************************
* Copyright (c) 2021 Chris Hennes <chennes@pioneerlibrarysystem.org> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library 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 Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
#include "PreCompiled.h"
#ifndef _PreComp_
# include <memory>
# include <string_view>
# include <mutex>
#endif
#include <filesystem>
#include <boost/algorithm/string.hpp>
#include <QDir>
#include "PreferencePackManager.h"
#include "App/Metadata.h"
#include "Base/Parameter.h"
#include "Base/Interpreter.h"
#include "Base/Console.h"
#include "DockWindowManager.h"
#include "ToolBarManager.h"
#include <Application.h>
#include <App/Application.h>
#include <ctime> // For generating a timestamped filename
#include <Base/ServiceProvider.h>
#include <Dialogs/DlgThemeEditor.h>
using namespace Gui;
using namespace xercesc;
namespace fs = std::filesystem;
static std::filesystem::path getSavedPrefPacksPath()
{
return fs::path(Base::FileInfo::stringToPath(App::Application::getUserAppDataDir()))
/ "SavedPreferencePacks";
}
static std::filesystem::path getResourcePrefPacksPath()
{
return fs::path(Base::FileInfo::stringToPath(App::Application::getResourceDir())) / "Gui"
/ "PreferencePacks";
}
PreferencePack::PreferencePack(const fs::path& path, const App::Metadata& metadata) :
_path(path), _metadata(metadata)
{
if (!fs::exists(_path)) {
throw std::runtime_error{ "Cannot access " + path.string() };
}
auto qssPaths = QDir::searchPaths(QStringLiteral("qss"));
auto cssPaths = QDir::searchPaths(QStringLiteral("css"));
auto overlayPaths = QDir::searchPaths(QStringLiteral("overlay"));
qssPaths.append(QString::fromStdString(Base::FileInfo::pathToString(_path)));
cssPaths.append(QString::fromStdString(Base::FileInfo::pathToString(_path)));
overlayPaths.append(QString::fromStdString(Base::FileInfo::pathToString(_path) + "/overlay"));
QDir::setSearchPaths(QStringLiteral("qss"), qssPaths);
QDir::setSearchPaths(QStringLiteral("css"), cssPaths);
QDir::setSearchPaths(QStringLiteral("overlay"), overlayPaths);
}
std::string PreferencePack::name() const
{
return _metadata.name();
}
bool PreferencePack::apply() const
{
// Run the pre.FCMacro, if it exists: if it raises an exception, abort the process
auto preMacroPath = _path / "pre.FCMacro";
if (fs::exists(preMacroPath)) {
try {
Base::Interpreter().runFile(Base::FileInfo::pathToString(preMacroPath).c_str(), false);
}
catch (...) {
Base::Console().message("PreferencePack application aborted by the preferencePack's pre.FCMacro");
return false;
}
}
// Back up the old config file
auto savedPreferencePacksDirectory = getSavedPrefPacksPath();
auto backupFile = savedPreferencePacksDirectory / "user.cfg.backup";
try {
fs::remove(backupFile);
}
catch (...) {}
App::GetApplication().GetUserParameter().SaveDocument(Base::FileInfo::pathToString(backupFile).c_str());
// Apply the config settings
applyConfigChanges();
// Run the Post.FCMacro, if it exists
auto postMacroPath = _path / "post.FCMacro";
if (fs::exists(postMacroPath)) {
try {
Base::Interpreter().runFile(Base::FileInfo::pathToString(postMacroPath).c_str(), false);
}
catch (...) {
Base::Console().message("PreferencePack application reverted by the preferencePack's post.FCMacro");
App::GetApplication().GetUserParameter().LoadDocument(Base::FileInfo::pathToString(backupFile).c_str());
return false;
}
}
return true;
}
App::Metadata Gui::PreferencePack::metadata() const
{
return _metadata;
}
void PreferencePack::applyConfigChanges() const
{
auto configFile = _path / (_metadata.name() + ".cfg");
if (fs::exists(configFile)) {
auto newParameters = ParameterManager::Create();
newParameters->LoadDocument(Base::FileInfo::pathToString(configFile).c_str());
auto baseAppGroup = App::GetApplication().GetUserParameter().GetGroup("BaseApp");
newParameters->GetGroup("BaseApp")->insertTo(baseAppGroup);
}
}
PreferencePackManager::PreferencePackManager()
: _preferencePackPaths(modPaths())
{
auto savedPath = getSavedPreferencePacksPath();
auto resourcePath = getResourcePreferencePacksPath();
_preferencePackPaths.insert(_preferencePackPaths.begin(), resourcePath);
_preferencePackPaths.push_back(savedPath);
rescan();
// Housekeeping:
DeleteOldBackups();
}
void PreferencePackManager::rescan()
{
std::lock_guard<std::mutex> lock(_mutex);
_preferencePacks.clear();
for (const auto& path : _preferencePackPaths) {
if (fs::exists(path) && fs::is_directory(path)) {
FindPreferencePacksInPackage(path);
for (const auto& mod : fs::directory_iterator(path)) {
if (fs::is_directory(mod)) {
FindPreferencePacksInPackage(mod);
}
}
}
}
}
void Gui::PreferencePackManager::AddPackToMetadata(const std::string &packName) const
{
std::lock_guard<std::mutex> lock(_mutex);
auto savedPreferencePacksDirectory = getSavedPreferencePacksPath();
fs::path preferencePackDirectory(savedPreferencePacksDirectory / packName);
if (fs::exists(preferencePackDirectory) && !fs::is_directory(preferencePackDirectory))
throw std::runtime_error("Cannot create " + savedPreferencePacksDirectory.string()
+ ": file with that name exists already");
if (!fs::exists(preferencePackDirectory)) fs::create_directories(preferencePackDirectory);
// Create or update the saved user preferencePacks package.xml metadata file
std::unique_ptr<App::Metadata> metadata;
if (fs::exists(savedPreferencePacksDirectory / "package.xml")) {
metadata = std::make_unique<App::Metadata>(savedPreferencePacksDirectory / "package.xml");
}
else {
metadata = std::make_unique<App::Metadata>();
metadata->setName("User-Saved Preference Packs");
std::stringstream str;
str << "Generated automatically -- edits may be lost when saving new preference packs. To "
<< "distribute one or more of these packs:\n"
<< " 1) copy the entire SavedPreferencePacks directory to a convenient location,\n"
<< " 2) rename the directory (usually to the name of the preference pack you are "
<< "distributing),\n"
<< " 3) delete any subfolders containing packs you don't want to distribute,\n"
<< " 4) use git to initialize the directory as a git repository,\n"
<< " 5) push it to a remote git host,\n"
<< " 6) activate Developer Mode in the Addon Manager,\n"
<< " 7) use Developer Tools in the Addon Manager to update the metadata file,\n"
<< " 8) add, commit, and push the updated package.xml file,\n"
<< " 9) add your remote host to the custom repositories list in the Addon Manager"
<< " preferences,\n"
<< " 10) use the Addon Manager to install your preference pack locally for testing.";
metadata->setDescription(str.str());
metadata->addLicense(App::Meta::License("All Rights Reserved", fs::path()));
}
for (const auto &item : metadata->content()) {
if (item.first == "preferencepack") {
if (item.second.name() == packName) {
// A pack with this name exists already, bail out
return;
}
}
}
App::Metadata newPreferencePackMetadata;
newPreferencePackMetadata.setName(packName);
metadata->addContentItem("preferencepack", newPreferencePackMetadata);
metadata->write(savedPreferencePacksDirectory / "package.xml");
}
void Gui::PreferencePackManager::importConfig(const std::string& packName,
const std::filesystem::path& path)
{
AddPackToMetadata(packName);
auto savedPreferencePacksDirectory = getSavedPreferencePacksPath();
auto cfgFilename = savedPreferencePacksDirectory / packName / (packName + ".cfg");
fs::copy_file(path, cfgFilename, fs::copy_options::overwrite_existing);
rescan();
}
// TODO(Shvedov): Is this suitable place for this method? It is more generic,
// and maybe more suitable place at Application?
std::vector<std::filesystem::path> Gui::PreferencePackManager::modPaths() const
{
auto userModPath = fs::path(Base::FileInfo::stringToPath(App::Application::getUserAppDataDir())) / "Mod";
auto& config = App::Application::Config();
auto additionalModules = config.find("AdditionalModulePaths");
std::vector<std::filesystem::path> result;
if (additionalModules != config.end()) {
boost::split(result,
additionalModules->second,
boost::is_any_of(";"),
boost::token_compress_on);
}
result.emplace_back(userModPath);
return result;
}
std::filesystem::path Gui::PreferencePackManager::getSavedPreferencePacksPath() const
{
return getSavedPrefPacksPath();
}
std::filesystem::path Gui::PreferencePackManager::getResourcePreferencePacksPath() const
{
return getResourcePrefPacksPath();
}
std::vector<std::string> Gui::PreferencePackManager::getPacksFromDirectory(const fs::path& path) const
{
std::vector<std::string> results;
auto packageMetadataFile = path / "package.xml";
if (fs::exists(packageMetadataFile) && fs::is_regular_file(packageMetadataFile)) {
try {
App::Metadata metadata(packageMetadataFile);
auto content = metadata.content();
for (const auto& item : content) {
if (item.first == "preferencepack") {
results.push_back(item.second.name());
}
}
}
catch (...) {
// Failed to read the metadata, or to create the preferencePack based on it...
Base::Console().error(("Failed to read " + packageMetadataFile.string()).c_str());
}
}
return results;
}
void Gui::PreferencePackManager::FindPreferencePacksInPackage(const fs::path &mod)
{
try {
TryFindPreferencePacksInPackage(mod);
}
catch (const std::exception& e) {
Base::Console().error("%s\n", e.what());
}
catch (...) {
// Failed to read the metadata, or to create the preferencePack based on it...
auto packageMetadataFile = mod / "package.xml";
Base::Console().error("Failed to read %s\n", Base::FileInfo::pathToString(packageMetadataFile).c_str());
}
}
void PreferencePackManager::TryFindPreferencePacksInPackage(const std::filesystem::path& mod)
{
auto packageMetadataFile = mod / "package.xml";
static const auto modDirectory = fs::path(Base::FileInfo::stringToPath(App::Application::getUserAppDataDir())) / "Mod" / "SavedPreferencePacks";
static const auto resourcePath = getResourcePreferencePacksPath();
if (fs::exists(packageMetadataFile) && fs::is_regular_file(packageMetadataFile)) {
App::Metadata metadata(packageMetadataFile);
auto content = metadata.content();
auto basename = Base::FileInfo::pathToString(mod.filename());
if (mod == modDirectory)
basename = "##USER_SAVED##";
else if (mod == resourcePath)
basename = "##BUILT_IN##";
for (const auto& item : content) {
if (item.first == "preferencepack") {
if (isVisible(basename, item.second.name())) {
PreferencePack newPreferencePack(mod / item.second.name(), item.second);
_preferencePacks.insert(std::make_pair(newPreferencePack.name(), newPreferencePack));
}
}
}
}
}
std::vector<std::string> PreferencePackManager::preferencePackNames() const
{
std::lock_guard<std::mutex> lock(_mutex);
std::vector<std::string> names;
for (const auto& preferencePack : _preferencePacks)
names.push_back(preferencePack.first);
return names;
}
std::map<std::string, PreferencePack> Gui::PreferencePackManager::preferencePacks() const
{
return _preferencePacks;
}
bool PreferencePackManager::apply(const std::string& preferencePackName) const
{
std::lock_guard<std::mutex> lock(_mutex);
if (auto preferencePack = _preferencePacks.find(preferencePackName); preferencePack != _preferencePacks.end()) {
BackupCurrentConfig();
bool wasApplied = preferencePack->second.apply();
if (wasApplied) {
// If the visibility state of the dock windows was changed we have to manually reload their state
Gui::DockWindowManager* pDockMgr = Gui::DockWindowManager::instance();
pDockMgr->loadState();
// Same goes for toolbars:
Gui::ToolBarManager* pToolbarMgr = Gui::ToolBarManager::getInstance();
pToolbarMgr->restoreState();
// We need to reload stylesheet to apply any changed style parameters
Gui::Application::Instance->reloadStyleSheet();
// TODO: Are there other things that have to be manually triggered?
}
return wasApplied;
}
else {
throw std::runtime_error("No such Preference Pack: " + preferencePackName);
}
}
static std::string findUnusedName(const std::string &basename, ParameterGrp::handle parent)
{
int i = 1;
while (true) {
std::ostringstream nameToTest;
nameToTest << basename << "_" << i;
if (!parent->HasGroup(nameToTest.str().c_str()))
return nameToTest.str();
++i;
}
}
bool PreferencePackManager::isVisible(const std::string& addonName, const std::string& preferencePackName) const
{
if (addonName.empty() || preferencePackName.empty())
return true;
auto pref = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/General/HiddenPreferencePacks");
auto hiddenPacks = pref->GetGroups();
auto hiddenPack = std::find_if(hiddenPacks.begin(), hiddenPacks.end(), [addonName, preferencePackName](ParameterGrp::handle handle) {
return (handle->GetASCII("addonName", "") == addonName) && (handle->GetASCII("preferencePackName", "") == preferencePackName);
});
if (hiddenPack == hiddenPacks.end())
return true;
else
return false;
}
void PreferencePackManager::toggleVisibility(const std::string& addonName, const std::string& preferencePackName)
{
if (preferencePackName.empty())
return;
auto pref = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/General/HiddenPreferencePacks");
auto hiddenPacks = pref->GetGroups();
auto hiddenPack = std::find_if(hiddenPacks.begin(), hiddenPacks.end(), [addonName,preferencePackName](ParameterGrp::handle handle) {
return (handle->GetASCII("addonName", "") == addonName) && (handle->GetASCII("preferencePackName", "") == preferencePackName);
});
if (hiddenPack == hiddenPacks.end()) {
auto name = findUnusedName("PreferencePack", pref);
auto group = pref->GetGroup(name.c_str());
group->SetASCII("addonName", addonName.c_str());
group->SetASCII("preferencePackName", preferencePackName.c_str());
}
else {
auto groupName = (*hiddenPack)->GetGroupName();
hiddenPacks.clear(); // To decrement the reference count of the group we are about the remove...
pref->RemoveGrp(groupName);
}
rescan();
}
void Gui::PreferencePackManager::deleteUserPack(const std::string& name)
{
if (name.empty())
return;
auto savedPreferencePacksDirectory = getSavedPreferencePacksPath();
auto savedPath = savedPreferencePacksDirectory / name;
std::unique_ptr<App::Metadata> metadata;
if (fs::exists(savedPreferencePacksDirectory / "package.xml")) {
metadata = std::make_unique<App::Metadata>(savedPreferencePacksDirectory / "package.xml");
}
else {
throw std::runtime_error("Lost the user-saved preference packs metadata file!");
}
metadata->removeContentItem("preferencepack", name);
metadata->write(savedPreferencePacksDirectory / "package.xml");
if (fs::exists(savedPath))
fs::remove_all(savedPath);
rescan();
}
static void copyTemplateParameters(Base::Reference<ParameterGrp> templateGroup, const std::string& path, Base::Reference<ParameterGrp> outputGroup)
{
auto userParameterHandle = App::GetApplication().GetParameterGroupByPath(path.c_str());
// Ensure that the DockWindowManager has saved its current state:
Gui::DockWindowManager* pDockMgr = Gui::DockWindowManager::instance();
pDockMgr->saveState();
// Do the same for ToolBars
Gui::ToolBarManager* pToolbarMgr = Gui::ToolBarManager::getInstance();
pToolbarMgr->saveState();
auto boolMap = templateGroup->GetBoolMap();
for (const auto& kv : boolMap) {
auto currentValue = userParameterHandle->GetBool(kv.first.c_str(), kv.second);
outputGroup->SetBool(kv.first.c_str(), currentValue);
}
auto intMap = templateGroup->GetIntMap();
for (const auto& kv : intMap) {
auto currentValue = userParameterHandle->GetInt(kv.first.c_str(), kv.second);
outputGroup->SetInt(kv.first.c_str(), currentValue);
}
auto uintMap = templateGroup->GetUnsignedMap();
for (const auto& kv : uintMap) {
auto currentValue = userParameterHandle->GetUnsigned(kv.first.c_str(), kv.second);
outputGroup->SetUnsigned(kv.first.c_str(), currentValue);
}
auto floatMap = templateGroup->GetFloatMap();
for (const auto& kv : floatMap) {
auto currentValue = userParameterHandle->GetFloat(kv.first.c_str(), kv.second);
outputGroup->SetFloat(kv.first.c_str(), currentValue);
}
auto asciiMap = templateGroup->GetASCIIMap();
for (const auto& kv : asciiMap) {
auto currentValue = userParameterHandle->GetASCII(kv.first.c_str(), kv.second.c_str());
outputGroup->SetASCII(kv.first.c_str(), currentValue.c_str());
}
// Recurse...
auto templateSubgroups = templateGroup->GetGroups();
for (auto& templateSubgroup : templateSubgroups) {
std::string sgName = templateSubgroup->GetGroupName();
auto outputSubgroupHandle = outputGroup->GetGroup(sgName.c_str());
copyTemplateParameters(templateSubgroup, path + "/" + sgName, outputSubgroupHandle);
}
}
static void copyTemplateParameters(/*const*/ ParameterManager& templateParameterManager, ParameterManager& outputParameterManager)
{
auto groups = templateParameterManager.GetGroups();
for (auto& group : groups) {
std::string name = group->GetGroupName();
auto groupHandle = outputParameterManager.GetGroup(name.c_str());
copyTemplateParameters(group, "User parameter:" + name, groupHandle);
}
}
void PreferencePackManager::save(const std::string& name, const std::string& directory, const std::vector<TemplateFile>& templates)
{
if (templates.empty())
return;
// Create the config file
auto outputParameterManager = ParameterManager::Create();
outputParameterManager->CreateDocument();
for (const auto& t : templates) {
auto templateParameterManager = ParameterManager::Create();
templateParameterManager->LoadDocument(Base::FileInfo::pathToString(t.path).c_str());
copyTemplateParameters(*templateParameterManager, *outputParameterManager);
}
std::string cfgFilename;
if (directory.empty()) {
AddPackToMetadata(name);
auto savedPreferencePacksDirectory = getSavedPreferencePacksPath();
cfgFilename = Base::FileInfo::pathToString(savedPreferencePacksDirectory / name / (name + ".cfg"));
}
else {
cfgFilename = Base::FileInfo::pathToString(fs::path(directory) / (name + ".cfg"));
}
outputParameterManager->SaveDocument(cfgFilename.c_str());
}
static std::vector<fs::path> scanForTemplateFolders(const std::string& groupName, const fs::path& entry)
{
// From this location, find the folder(s) called "PreferencePackTemplates"
std::vector<fs::path> templateFolders;
if (fs::exists(entry)) {
if (fs::is_directory(entry)) {
if (entry.filename() == "PreferencePackTemplates" ||
entry.filename() == "preference_pack_templates") {
templateFolders.push_back(entry);
}
else {
std::string subgroupName = groupName + "/" + Base::FileInfo::pathToString(entry.filename());
for (const auto& subentry : fs::directory_iterator(entry)) {
auto contents = scanForTemplateFolders(subgroupName, subentry);
std::copy(contents.begin(), contents.end(), std::back_inserter(templateFolders));
}
}
}
}
return templateFolders;
}
static std::vector<PreferencePackManager::TemplateFile> scanForTemplateFiles(const std::string& groupName, const fs::path& entry)
{
auto templateFolders = scanForTemplateFolders(groupName, entry);
std::vector<PreferencePackManager::TemplateFile> templateFiles;
for (const auto& templateDir : templateFolders) {
if (!fs::exists(templateDir) || !fs::is_directory(templateDir))
continue;
for (const auto& entry : fs::directory_iterator(templateDir)) {
if (entry.path().extension() == ".cfg") {
auto name = Base::FileInfo::pathToString(entry.path().filename().stem());
std::replace(name.begin(), name.end(), '_', ' ');
// Make sure we don't insert the same thing twice...
if (std::find_if(templateFiles.begin(), templateFiles.end(), [groupName, name](const auto &rhs)->bool {
return groupName == rhs.group && name == rhs.name;
} ) != templateFiles.end())
continue;
templateFiles.push_back({ groupName, name, entry });
}
}
}
return templateFiles;
}
std::vector<PreferencePackManager::TemplateFile> PreferencePackManager::templateFiles(bool rescan)
{
std::lock_guard<std::mutex> lock(_mutex);
if (!_templateFiles.empty() && !rescan)
return _templateFiles;
// Locate all of the template files available on this system
// Template files end in ".cfg" -- They are located in:
// * $INSTALL_DIR/data/Gui/PreferencePackTemplates/(Appearance|Behavior)/*
// * $DATA_DIR/Mod/**/PreferencePackTemplates/(Appearance|Behavior)/*
// (alternate spellings are provided for packages using CamelCase and snake_case, and both major English dialects)
auto resourcePath = fs::path(Base::FileInfo::stringToPath(App::Application::getResourceDir())) / "Gui";
std::string group = "Built-In";
if (fs::exists(resourcePath) && fs::is_directory(resourcePath)) {
const auto localFiles = scanForTemplateFiles(group, resourcePath);
std::copy(localFiles.begin(), localFiles.end(), std::back_inserter(_templateFiles));
}
for (const auto& modPath : modPaths()) {
if (fs::exists(modPath) && fs::is_directory(modPath)) {
for (const auto& mod : fs::directory_iterator(modPath)) {
group = Base::FileInfo::pathToString(mod.path().filename());
const auto localFiles = scanForTemplateFiles(group, mod);
std::copy(localFiles.begin(), localFiles.end(), std::back_inserter(_templateFiles));
}
}
}
return _templateFiles;
}
void Gui::PreferencePackManager::BackupCurrentConfig() const
{
auto backupDirectory = getSavedPreferencePacksPath() / "Backups";
fs::create_directories(backupDirectory);
// Create a timestamped filename:
auto time = std::time(nullptr);
std::ostringstream timestampStream;
timestampStream << "user." << time << ".cfg";
auto filename = backupDirectory / timestampStream.str();
// Save the current config:
App::GetApplication().GetUserParameter().SaveDocument(Base::FileInfo::pathToString(filename).c_str());
}
// FIXME: Replace with more accurate C++20 solution once its usable: https://stackoverflow.com/a/68593141
template <typename TP>
static std::time_t to_time_t(TP tp)
{
using namespace std::chrono;
auto sctp = time_point_cast<system_clock::duration>(tp - TP::clock::now()
+ system_clock::now());
return system_clock::to_time_t(sctp);
}
void Gui::PreferencePackManager::DeleteOldBackups() const
{
constexpr auto oneWeek = 60.0 * 60.0 * 24.0 * 7.0;
const auto now = std::time(nullptr);
auto backupDirectory = getSavedPreferencePacksPath() / "Backups";
if (fs::exists(backupDirectory) && fs::is_directory(backupDirectory)) {
for (const auto& backup : fs::directory_iterator(backupDirectory)) {
if (std::difftime(now, to_time_t(fs::last_write_time(backup))) > oneWeek) {
try {
fs::remove(backup);
}
catch (...) {}
}
}
}
}
// FIXME: Replace with more accurate C++20 solution once its usable: https://stackoverflow.com/a/68593141
std::vector<std::filesystem::path> Gui::PreferencePackManager::configBackups() const
{
std::vector<std::filesystem::path> results;
auto backupDirectory = getSavedPreferencePacksPath() / "Backups";
if (fs::exists(backupDirectory) && fs::is_directory(backupDirectory)) {
for (const auto& backup : fs::directory_iterator(backupDirectory)) {
results.push_back(backup);
}
}
return results;
}