VoltMod
C++23 framework for CS2 server plugins
Loading...
Searching...
No Matches
InstalledPlugins.cpp
Go to the documentation of this file.
2
6#include <format>
7#include <optional>
8#include <string_view>
9#include <system_error>
10#include <utility>
11
12namespace VoltMod
13{
14
15/** The shape of plugin.json: the member names are its keys, and an unknown key is an error. */
17{
18 std::string name;
19 std::string version;
20 std::string logTag;
21 std::string description;
22 std::string author;
23 std::string website;
24 std::string license;
25 std::string logLevel = "info";
26 std::vector<std::string> dependencies;
27 std::vector<std::string> optionalDependencies;
28 // Read by `voltmod database header`; the host only accepts it.
29 std::optional<glz::raw_json> database;
30 // Points editors at the plugin schema; the host only accepts it.
31 std::optional<std::string> schema;
32};
33
34} // namespace VoltMod
35
36template <>
37struct glz::meta<VoltMod::PluginDocument>
38{
39 static constexpr std::string_view rename_key(std::string_view key) { return key == "schema" ? "$schema" : key; }
40};
41
42namespace VoltMod
43{
44
45static constexpr std::string_view ManifestName = "plugin.json";
46
48{
50
51 std::error_code failed;
52 if (!std::filesystem::exists(plugins, failed))
53 {
54 if (failed)
55 {
56 Log::Error("Cannot inspect {}: {}", plugins.string(), failed.message());
57 }
58 return installed;
59 }
60
61 for (std::filesystem::directory_iterator entry(plugins, failed), end; !failed && entry != end;
62 entry.increment(failed))
63 {
64 if (!entry->is_directory(failed) || failed)
65 {
66 continue;
67 }
68
69 const std::string directory = entry->path().filename().string();
70 const std::filesystem::path manifest = entry->path() / ManifestName;
71 if (!std::filesystem::exists(manifest, failed) || failed)
72 {
73 continue;
74 }
75
78 if (!document)
79 {
80 installed.Refused.push_back({.Name = directory, .Reason = document.error()});
81 continue;
82 }
83
84 if (document->name != directory)
85 {
86 installed.Refused.push_back({.Name = directory,
87 .Reason = Error::Invalid(std::format(
88 "{} names it '{}', and a plugin lives in the directory it is named after.",
89 ManifestName, document->name))});
90 continue;
91 }
92
93 const std::optional<LogLevel> level = Parse<LogLevel>(document->logLevel);
94 if (!level)
95 {
96 installed.Refused.push_back(
97 {.Name = directory,
98 .Reason = Error::Invalid(
99 std::format("{}: logLevel '{}' is not info, warn or error", ManifestName, document->logLevel))});
100 continue;
101 }
102
103 installed.Plugins.push_back({.Name = document->name,
104 .Version = document->version,
105 .LogTag = document->logTag.empty() ? document->name : document->logTag,
106 .Description = document->description,
107 .Author = document->author,
108 .Website = document->website,
109 .License = document->license,
110 .LogLevel = *level,
111 .Dependencies = document->dependencies,
112 .OptionalDependencies = document->optionalDependencies});
113 }
114
115 if (failed)
116 {
117 Log::Error("Cannot read {}: {}", plugins.string(), failed.message());
118 }
119
120 return installed;
121}
122
124{
125 if (descriptor == nullptr)
126 {
127 return std::unexpected(Error::Invalid(std::format("{} returned nothing", PluginEntryName)));
128 }
129
130 const std::string_view pluginVersion = descriptor->VoltModVersion != nullptr ? descriptor->VoltModVersion : "";
132 {
133 return std::unexpected(Error::Invalid(
134 std::format("it was built for VoltMod {} and the host is {}; rebuild it", pluginVersion, hostVersion)));
135 }
136
137 const bool whole = descriptor->BuildStamp != nullptr && descriptor->Load != nullptr &&
138 descriptor->Unload != nullptr && descriptor->Status != nullptr;
139 if (!whole)
140 {
141 return std::unexpected(Error::Invalid("its descriptor leaves out one of BuildStamp, Load, Unload and Status"));
142 }
143
144 return {};
145}
146
147} // namespace VoltMod
Enumerator names without a hand-written switch.
static Result< T > ReadFile(std::string_view path)
Parse path (resolved via ResolvePath) into T.
Definition Json.hpp:46
InstalledScan Discover(const std::filesystem::path &plugins)
void Error(std::format_string< Args... > fmt, Args &&... args)
Definition Log.hpp:97
static std::string ReadFile(const std::filesystem::path &path)
Definition Loader.cpp:56
Status ValidateDescriptor(const PluginDescriptor *descriptor, std::string_view hostVersion)
std::expected< void, Error > Status
Definition Result.hpp:67
constexpr const char * PluginEntryName
std::expected< T, Error > Result
Definition Result.hpp:64
static constexpr std::string_view ManifestName
static Error Invalid(std::string detail)
Definition Result.hpp:51
std::optional< std::string > schema
std::optional< glz::raw_json > database
std::vector< std::string > dependencies
std::vector< std::string > optionalDependencies
static constexpr std::string_view rename_key(std::string_view key)