Fcitx
addonmanager.cpp
1 /*
2  * SPDX-FileCopyrightText: 2016-2016 CSSlayer <wengxt@gmail.com>
3  *
4  * SPDX-License-Identifier: LGPL-2.1-or-later
5  *
6  */
7 
8 #include "addonmanager.h"
9 #include <fcntl.h>
10 #include <cstdint>
11 #include <memory>
12 #include <stdexcept>
13 #include <string>
14 #include <unordered_map>
15 #include <unordered_set>
16 #include <utility>
17 #include <vector>
18 #include "fcitx-config/iniparser.h"
19 #include "fcitx-config/rawconfig.h"
20 #include "fcitx-utils/log.h"
21 #include "fcitx-utils/macros.h"
22 #include "fcitx-utils/misc_p.h"
23 #include "fcitx-utils/semver.h"
25 #include "addoninfo.h"
26 #include "addoninstance.h"
27 #include "addoninstance_p.h"
28 #include "addonloader.h"
29 #include "addonloader_p.h"
30 #include "config.h"
31 #include "instance.h"
32 
33 #ifndef FCITX_NO_DL
34 #include "sharedlibraryloader_p.h"
35 #endif
36 
37 namespace fcitx {
38 
39 class Addon {
40  friend class AddonManagerPrivate;
41 
42 public:
43  Addon(const std::string &name, RawConfig &config)
44  : info_(name), failed_(false) {
45  info_.load(config);
46  }
47 
48  const AddonInfo &info() const { return info_; }
49 
50  bool isLoadable() const {
51  return info_.isValid() && info_.isEnabled() && !failed_;
52  }
53  bool isValid() const { return info_.isValid() && !failed_; }
54 
55  bool loaded() const { return !!instance_; }
56 
57  AddonInstance *instance() { return instance_.get(); }
58 
59  void setFailed(bool failed = true) { failed_ = failed; }
60  void setOverrideEnabled(OverrideEnabled overrideEnabled) {
61  info_.setOverrideEnabled(overrideEnabled);
62  }
63 
64 private:
65  AddonInfo info_;
66  bool failed_;
67  std::unique_ptr<AddonInstance> instance_;
68 };
69 
70 enum class DependencyCheckStatus {
71  Satisfied,
72  Pending,
73  PendingUpdateRequest,
74  Failed
75 };
76 
78 public:
80 
81  Addon *addon(const std::string &name) const {
82  auto iter = addons_.find(name);
83  if (iter != addons_.end()) {
84  return iter->second.get();
85  }
86  return nullptr;
87  }
88 
89  DependencyCheckStatus checkDependencies(const Addon &a) {
90  const auto &dependencies = a.info().dependenciesWithVersion();
91  for (const auto &[dependency, depVersion] : dependencies) {
92  if (dependency == "core") {
93  if (depVersion <= version_) {
94  continue;
95  }
96  return DependencyCheckStatus::Failed;
97  }
98  Addon *dep = addon(dependency);
99  if (!dep || !dep->isLoadable()) {
100  return DependencyCheckStatus::Failed;
101  }
102 
103  if (depVersion > dep->info().version()) {
104  return DependencyCheckStatus::Failed;
105  }
106 
107  if (!dep->loaded()) {
108  if (dep->info().onDemand() &&
109  requested_.insert(dep->info().uniqueName()).second) {
110  return DependencyCheckStatus::PendingUpdateRequest;
111  }
112  return DependencyCheckStatus::Pending;
113  }
114  }
115  const auto &optionalDependencies = a.info().optionalDependencies();
116  for (const auto &dependency : optionalDependencies) {
117  Addon *dep = addon(dependency);
118  // if not available, don't bother load it
119  if (!dep || !dep->isLoadable()) {
120  continue;
121  }
122 
123  // otherwise wait for it
124  if (!dep->loaded() && !dep->info().onDemand()) {
125  return DependencyCheckStatus::Pending;
126  }
127  }
128 
129  return DependencyCheckStatus::Satisfied;
130  }
131 
132  void loadAddons(AddonManager *q_ptr) {
133  if (instance_ && instance_->exiting()) {
134  return;
135  }
136  if (inLoadAddons_) {
137  throw std::runtime_error("loadAddons is not reentrant, do not call "
138  "addon(.., true) in constructor of addon");
139  }
140  inLoadAddons_ = true;
141  bool changed = false;
142  do {
143  changed = false;
144 
145  for (auto &item : addons_) {
146  changed |= loadAddon(q_ptr, *item.second);
147  // Exit if addon request it.
148  if (instance_ && instance_->exiting()) {
149  changed = false;
150  break;
151  }
152  }
153  } while (changed);
154  inLoadAddons_ = false;
155  }
156 
157  bool loadAddon(AddonManager *q_ptr, Addon &addon) {
158  if (unloading_) {
159  return false;
160  }
161 
162  if (addon.loaded() || !addon.isLoadable()) {
163  return false;
164  }
165  if (addon.info().onDemand() &&
166  !requested_.contains(addon.info().uniqueName())) {
167  return false;
168  }
169  auto result = checkDependencies(addon);
170  FCITX_DEBUG() << "Call loadAddon() with " << addon.info().uniqueName()
171  << " checkDependencies() returns "
172  << static_cast<int>(result)
173  << " Dep: " << addon.info().dependenciesWithVersion()
174  << " OptDep: "
175  << addon.info().optionalDependenciesWithVersion();
176  if (result == DependencyCheckStatus::Failed) {
177  addon.setFailed();
178  } else if (result == DependencyCheckStatus::Satisfied) {
179  realLoad(q_ptr, addon);
180  if (addon.loaded()) {
181  loadOrder_.push_back(addon.info().uniqueName());
182  return true;
183  }
184  } else if (result == DependencyCheckStatus::PendingUpdateRequest) {
185  return true;
186  }
187  // here we are "pending" on others.
188  return false;
189  }
190 
191  void realLoad(AddonManager *q_ptr, Addon &addon) {
192  if (!addon.isLoadable()) {
193  return;
194  }
195 
196  if (auto *loader = findValue(loaders_, addon.info().type())) {
197  addon.instance_.reset((*loader)->load(addon.info(), q_ptr));
198  } else {
199  FCITX_ERROR() << "Failed to find addon loader for: "
200  << addon.info().type();
201  }
202  if (!addon.instance_) {
203  addon.setFailed(true);
204  FCITX_INFO() << "Could not load addon "
205  << addon.info().uniqueName();
206  } else {
207  addon.instance_->d_func()->addonInfo_ = &(addon.info());
208  FCITX_INFO() << "Loaded addon " << addon.info().uniqueName();
209  }
210  }
211 
212  std::string addonConfigDir_ = "addon";
213 
214  bool unloading_ = false;
215  bool inLoadAddons_ = false;
216 
217  std::unordered_map<std::string, std::unique_ptr<Addon>> addons_;
218  std::unordered_map<std::string, std::unique_ptr<AddonLoader>> loaders_;
219  std::unordered_set<std::string> requested_;
220 
221  std::vector<std::string> loadOrder_;
222 
223  Instance *instance_ = nullptr;
224  EventLoop *eventLoop_ = nullptr;
225  int64_t timestamp_ = 0;
226  const SemanticVersion version_ =
227  SemanticVersion::parse(FCITX_VERSION_STRING).value();
228 
229  std::unordered_map<std::string, std::vector<std::string>> options_;
230 };
231 
233 
234 AddonManager::AddonManager(const std::string &addonConfigDir) : AddonManager() {
235  FCITX_D();
236  d->addonConfigDir_ = addonConfigDir;
237 }
238 
240 
241 void AddonManager::registerLoader(std::unique_ptr<AddonLoader> loader) {
242  FCITX_D();
243  // same loader shouldn't register twice
244  if (d->loaders_.contains(loader->type())) {
245  return;
246  }
247  d->loaders_.emplace(loader->type(), std::move(loader));
248 }
249 
250 void AddonManager::unregisterLoader(const std::string &name) {
251  FCITX_D();
252  d->loaders_.erase(name);
253 }
254 
255 void AddonManager::registerDefaultLoader(StaticAddonRegistry *registry) {
256 #ifndef FCITX_NO_DL
257  registerLoader(std::make_unique<SharedLibraryLoader>());
258 #endif
259  if (registry) {
260  registerLoader(std::make_unique<StaticLibraryLoader>(registry));
261  }
262 }
263 
264 void AddonManager::load(const std::unordered_set<std::string> &enabled,
265  const std::unordered_set<std::string> &disabled) {
266  FCITX_D();
267  const auto &path = StandardPaths::global();
268  d->timestamp_ =
269  path.timestamp(StandardPathsType::PkgData, d->addonConfigDir_);
270  auto fileNames = path.locate(StandardPathsType::PkgData, d->addonConfigDir_,
271  pathfilter::extension(".conf"));
272  bool enableAll = enabled.contains("all");
273  bool disableAll = disabled.contains("all");
274  for (const auto &[fileName, fullName] : fileNames) {
275  // remove .conf
276  std::string name = fileName.stem().string();
277  if (name == "core") {
278  FCITX_ERROR() << "\"core\" is not a valid addon name.";
279  continue;
280  }
281  if (d->addons_.contains(name)) {
282  continue;
283  }
284 
285  RawConfig config;
286  readAsIni(config, StandardPathsType::PkgData, fullName);
287 
288  // override configuration
289  auto addon = std::make_unique<Addon>(name, config);
290  if (addon->isValid()) {
291  if (enableAll || enabled.contains(name)) {
292  addon->setOverrideEnabled(OverrideEnabled::Enabled);
293  } else if (disableAll || disabled.contains(name)) {
294  addon->setOverrideEnabled(OverrideEnabled::Disabled);
295  }
296  d->addons_[addon->info().uniqueName()] = std::move(addon);
297  }
298  }
299 
300  d->loadAddons(this);
301 }
302 
304  FCITX_D();
305  if (d->unloading_) {
306  return;
307  }
308  d->unloading_ = true;
309  // reverse the unload order
310  for (auto iter = d->loadOrder_.rbegin(), end = d->loadOrder_.rend();
311  iter != end; iter++) {
312  FCITX_INFO() << "Unloading addon " << *iter;
313  d->addons_.erase(*iter);
314  }
315  d->loadOrder_.clear();
316  d->requested_.clear();
317  d->unloading_ = false;
318 }
319 
321  FCITX_D();
322  if (d->unloading_) {
323  return;
324  }
325  // reverse the unload order
326  for (auto iter = d->loadOrder_.rbegin(), end = d->loadOrder_.rend();
327  iter != end; iter++) {
328  if (auto *addonInst = addon(*iter)) {
329  addonInst->save();
330  }
331  }
332 }
333 
334 AddonInstance *AddonManager::addon(const std::string &name, bool load) {
335  FCITX_D();
336  auto *addon = d->addon(name);
337  if (!addon) {
338  return nullptr;
339  }
340  if (addon->isLoadable() && !addon->loaded() && addon->info().onDemand() &&
341  load) {
342  d->requested_.insert(name);
343  d->loadAddons(this);
344  }
345  return addon->instance();
346 }
347 
348 const AddonInfo *AddonManager::addonInfo(const std::string &name) const {
349  FCITX_D();
350  auto *addon = d->addon(name);
351  if (addon && addon->isValid()) {
352  return &addon->info();
353  }
354  return nullptr;
355 }
356 
357 AddonInstance *AddonManager::lookupAddon(const std::string &name) const {
358  FCITX_D();
359  auto *addon = d->addon(name);
360  return addon ? addon->instance() : nullptr;
361 }
362 
363 const std::vector<std::string> &AddonManager::loadedAddonNames() const {
364  FCITX_D();
365  return d->loadOrder_;
366 }
367 
368 std::unordered_set<std::string>
369 AddonManager::addonNames(AddonCategory category) {
370  FCITX_D();
371  std::unordered_set<std::string> result;
372  for (auto &item : d->addons_) {
373  if (item.second->isValid() &&
374  item.second->info().category() == category) {
375  result.insert(item.first);
376  }
377  }
378  return result;
379 }
380 
382  FCITX_D();
383  return d->instance_;
384 }
385 
386 void AddonManager::setInstance(Instance *instance) {
387  FCITX_D();
388  d->instance_ = instance;
389  d->eventLoop_ = &instance->eventLoop();
390 }
391 
393  FCITX_D();
394  d->eventLoop_ = eventLoop;
395 }
396 
398  FCITX_D();
399  return d->eventLoop_;
400 }
401 
403  FCITX_D();
404  return d->version_;
405 }
406 
408  FCITX_D();
409  auto timestamp = StandardPaths::global().timestamp(
410  StandardPathsType::PkgData, d->addonConfigDir_);
411  return timestamp > d->timestamp_;
412 }
413 
415  std::unordered_map<std::string, std::vector<std::string>> options) {
416  FCITX_D();
417  d->options_ = std::move(options);
418 }
419 
420 std::vector<std::string> AddonManager::addonOptions(const std::string &name) {
421  FCITX_D();
422  if (auto *options = findValue(d->options_, name)) {
423  return *options;
424  }
425  return {};
426 }
427 
428 } // namespace fcitx
static const StandardPaths & global()
Return the global instance of StandardPath.
void unload()
Destruct all addon, all information is cleared to the initial state.
const std::vector< std::string > & loadedAddonNames() const
Return the loaded addon name in the order of they were loaded.
AddonInstance * addon(const std::string &name, bool load=false)
Get the loaded addon instance.
An instance represents a standalone Fcitx instance.
Definition: instance.h:95
A raw configuration tree that stores key-value pairs in a hierarchical structure. ...
Definition: rawconfig.h:37
void registerLoader(std::unique_ptr< AddonLoader > loader)
Register new addon loader.
Definition: action.cpp:17
Definition: matchrule.h:78
Provide a Semantic version 2.0 implementation.
Definition: semver.h:46
EventLoop & eventLoop()
Get the fcitx event loop.
Definition: instance.cpp:1660
AddonInstance * lookupAddon(const std::string &name) const
Get the currently loaded addon instance.
bool checkUpdate() const
Check directory for quick hint for whether update is required.
void unregisterLoader(const std::string &name)
Unregister addon loader.
AddonManager()
Construct an addon manager.
New Utility classes to handle application specific path.
void load(const std::unordered_set< std::string > &enabled={}, const std::unordered_set< std::string > &disabled={})
Load addon based on given parameter.
Base class for any addon in fcitx.
Definition: addoninstance.h:74
EventLoop * eventLoop()
Return the associated event loop.
std::vector< std::string > addonOptions(const std::string &name)
Query addon options that set with setAddonOptions for given addon.
void setEventLoop(EventLoop *eventLoop)
Set event loop.
const SemanticVersion & version() const
Return the version number of Fcitx5Core library.
Addon For fcitx.
void registerDefaultLoader(StaticAddonRegistry *registry)
Register addon loader, including static and shared library loader.
const AddonInfo * addonInfo(const std::string &name) const
Get addon information for given addon.
Instance * instance()
Return the fcitx instance when it is created by Fcitx.
Log utilities.
virtual ~AddonManager()
Destruct and unload all addons.
Addon Manager class.
void saveAll()
Save all addon configuration.
void setAddonOptions(std::unordered_map< std::string, std::vector< std::string >> options)
Set addon parameters that may be used during addon construction.