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 namespace fcitx {
34 
35 class Addon {
36  friend class AddonManagerPrivate;
37 
38 public:
39  Addon(const std::string &name, RawConfig &config)
40  : info_(name), failed_(false) {
41  info_.load(config);
42  }
43 
44  const AddonInfo &info() const { return info_; }
45 
46  bool isLoadable() const {
47  return info_.isValid() && info_.isEnabled() && !failed_;
48  }
49  bool isValid() const { return info_.isValid() && !failed_; }
50 
51  bool loaded() const { return !!instance_; }
52 
53  AddonInstance *instance() { return instance_.get(); }
54 
55  void setFailed(bool failed = true) { failed_ = failed; }
56  void setOverrideEnabled(OverrideEnabled overrideEnabled) {
57  info_.setOverrideEnabled(overrideEnabled);
58  }
59 
60 private:
61  AddonInfo info_;
62  bool failed_;
63  std::unique_ptr<AddonInstance> instance_;
64 };
65 
66 enum class DependencyCheckStatus {
67  Satisfied,
68  Pending,
69  PendingUpdateRequest,
70  Failed
71 };
72 
74 public:
76 
77  Addon *addon(const std::string &name) const {
78  auto iter = addons_.find(name);
79  if (iter != addons_.end()) {
80  return iter->second.get();
81  }
82  return nullptr;
83  }
84 
85  DependencyCheckStatus checkDependencies(const Addon &a) {
86  const auto &dependencies = a.info().dependenciesWithVersion();
87  for (const auto &[dependency, depVersion] : dependencies) {
88  if (dependency == "core") {
89  if (depVersion <= version_) {
90  continue;
91  }
92  return DependencyCheckStatus::Failed;
93  }
94  Addon *dep = addon(dependency);
95  if (!dep || !dep->isLoadable()) {
96  return DependencyCheckStatus::Failed;
97  }
98 
99  if (depVersion > dep->info().version()) {
100  return DependencyCheckStatus::Failed;
101  }
102 
103  if (!dep->loaded()) {
104  if (dep->info().onDemand() &&
105  requested_.insert(dep->info().uniqueName()).second) {
106  return DependencyCheckStatus::PendingUpdateRequest;
107  }
108  return DependencyCheckStatus::Pending;
109  }
110  }
111  const auto &optionalDependencies = a.info().optionalDependencies();
112  for (const auto &dependency : optionalDependencies) {
113  Addon *dep = addon(dependency);
114  // if not available, don't bother load it
115  if (!dep || !dep->isLoadable()) {
116  continue;
117  }
118 
119  // otherwise wait for it
120  if (!dep->loaded() && !dep->info().onDemand()) {
121  return DependencyCheckStatus::Pending;
122  }
123  }
124 
125  return DependencyCheckStatus::Satisfied;
126  }
127 
128  void loadAddons(AddonManager *q_ptr) {
129  if (instance_ && instance_->exiting()) {
130  return;
131  }
132  if (inLoadAddons_) {
133  throw std::runtime_error("loadAddons is not reentrant, do not call "
134  "addon(.., true) in constructor of addon");
135  }
136  inLoadAddons_ = true;
137  bool changed = false;
138  do {
139  changed = false;
140 
141  for (auto &item : addons_) {
142  changed |= loadAddon(q_ptr, *item.second);
143  // Exit if addon request it.
144  if (instance_ && instance_->exiting()) {
145  changed = false;
146  break;
147  }
148  }
149  } while (changed);
150  inLoadAddons_ = false;
151  }
152 
153  bool loadAddon(AddonManager *q_ptr, Addon &addon) {
154  if (unloading_) {
155  return false;
156  }
157 
158  if (addon.loaded() || !addon.isLoadable()) {
159  return false;
160  }
161  if (addon.info().onDemand() &&
162  !requested_.contains(addon.info().uniqueName())) {
163  return false;
164  }
165  auto result = checkDependencies(addon);
166  FCITX_DEBUG() << "Call loadAddon() with " << addon.info().uniqueName()
167  << " checkDependencies() returns "
168  << static_cast<int>(result)
169  << " Dep: " << addon.info().dependenciesWithVersion()
170  << " OptDep: "
171  << addon.info().optionalDependenciesWithVersion();
172  if (result == DependencyCheckStatus::Failed) {
173  addon.setFailed();
174  } else if (result == DependencyCheckStatus::Satisfied) {
175  realLoad(q_ptr, addon);
176  if (addon.loaded()) {
177  loadOrder_.push_back(addon.info().uniqueName());
178  return true;
179  }
180  } else if (result == DependencyCheckStatus::PendingUpdateRequest) {
181  return true;
182  }
183  // here we are "pending" on others.
184  return false;
185  }
186 
187  void realLoad(AddonManager *q_ptr, Addon &addon) {
188  if (!addon.isLoadable()) {
189  return;
190  }
191 
192  if (auto *loader = findValue(loaders_, addon.info().type())) {
193  addon.instance_.reset((*loader)->load(addon.info(), q_ptr));
194  } else {
195  FCITX_ERROR() << "Failed to find addon loader for: "
196  << addon.info().type();
197  }
198  if (!addon.instance_) {
199  addon.setFailed(true);
200  FCITX_INFO() << "Could not load addon "
201  << addon.info().uniqueName();
202  } else {
203  addon.instance_->d_func()->addonInfo_ = &(addon.info());
204  FCITX_INFO() << "Loaded addon " << addon.info().uniqueName();
205  }
206  }
207 
208  std::string addonConfigDir_ = "addon";
209 
210  bool unloading_ = false;
211  bool inLoadAddons_ = false;
212 
213  std::unordered_map<std::string, std::unique_ptr<Addon>> addons_;
214  std::unordered_map<std::string, std::unique_ptr<AddonLoader>> loaders_;
215  std::unordered_set<std::string> requested_;
216 
217  std::vector<std::string> loadOrder_;
218 
219  Instance *instance_ = nullptr;
220  EventLoop *eventLoop_ = nullptr;
221  int64_t timestamp_ = 0;
222  const SemanticVersion version_ =
223  SemanticVersion::parse(FCITX_VERSION_STRING).value();
224 
225  std::unordered_map<std::string, std::vector<std::string>> options_;
226 };
227 
229 
230 AddonManager::AddonManager(const std::string &addonConfigDir) : AddonManager() {
231  FCITX_D();
232  d->addonConfigDir_ = addonConfigDir;
233 }
234 
236 
237 void AddonManager::registerLoader(std::unique_ptr<AddonLoader> loader) {
238  FCITX_D();
239  // same loader shouldn't register twice
240  if (d->loaders_.contains(loader->type())) {
241  return;
242  }
243  d->loaders_.emplace(loader->type(), std::move(loader));
244 }
245 
246 void AddonManager::unregisterLoader(const std::string &name) {
247  FCITX_D();
248  d->loaders_.erase(name);
249 }
250 
251 void AddonManager::registerDefaultLoader(StaticAddonRegistry *registry) {
252  registerLoader(std::make_unique<SharedLibraryLoader>());
253  if (registry) {
254  registerLoader(std::make_unique<StaticLibraryLoader>(registry));
255  }
256 }
257 
258 void AddonManager::load(const std::unordered_set<std::string> &enabled,
259  const std::unordered_set<std::string> &disabled) {
260  FCITX_D();
261  const auto &path = StandardPaths::global();
262  d->timestamp_ =
263  path.timestamp(StandardPathsType::PkgData, d->addonConfigDir_);
264  auto fileNames = path.locate(StandardPathsType::PkgData, d->addonConfigDir_,
265  pathfilter::extension(".conf"));
266  bool enableAll = enabled.contains("all");
267  bool disableAll = disabled.contains("all");
268  for (const auto &[fileName, fullName] : fileNames) {
269  // remove .conf
270  std::string name = fileName.stem().string();
271  if (name == "core") {
272  FCITX_ERROR() << "\"core\" is not a valid addon name.";
273  continue;
274  }
275  if (d->addons_.contains(name)) {
276  continue;
277  }
278 
279  RawConfig config;
280  readAsIni(config, StandardPathsType::PkgData, fullName);
281 
282  // override configuration
283  auto addon = std::make_unique<Addon>(name, config);
284  if (addon->isValid()) {
285  if (enableAll || enabled.contains(name)) {
286  addon->setOverrideEnabled(OverrideEnabled::Enabled);
287  } else if (disableAll || disabled.contains(name)) {
288  addon->setOverrideEnabled(OverrideEnabled::Disabled);
289  }
290  d->addons_[addon->info().uniqueName()] = std::move(addon);
291  }
292  }
293 
294  d->loadAddons(this);
295 }
296 
298  FCITX_D();
299  if (d->unloading_) {
300  return;
301  }
302  d->unloading_ = true;
303  // reverse the unload order
304  for (auto iter = d->loadOrder_.rbegin(), end = d->loadOrder_.rend();
305  iter != end; iter++) {
306  FCITX_INFO() << "Unloading addon " << *iter;
307  d->addons_.erase(*iter);
308  }
309  d->loadOrder_.clear();
310  d->requested_.clear();
311  d->unloading_ = false;
312 }
313 
315  FCITX_D();
316  if (d->unloading_) {
317  return;
318  }
319  // reverse the unload order
320  for (auto iter = d->loadOrder_.rbegin(), end = d->loadOrder_.rend();
321  iter != end; iter++) {
322  if (auto *addonInst = addon(*iter)) {
323  addonInst->save();
324  }
325  }
326 }
327 
328 AddonInstance *AddonManager::addon(const std::string &name, bool load) {
329  FCITX_D();
330  auto *addon = d->addon(name);
331  if (!addon) {
332  return nullptr;
333  }
334  if (addon->isLoadable() && !addon->loaded() && addon->info().onDemand() &&
335  load) {
336  d->requested_.insert(name);
337  d->loadAddons(this);
338  }
339  return addon->instance();
340 }
341 
342 const AddonInfo *AddonManager::addonInfo(const std::string &name) const {
343  FCITX_D();
344  auto *addon = d->addon(name);
345  if (addon && addon->isValid()) {
346  return &addon->info();
347  }
348  return nullptr;
349 }
350 
351 AddonInstance *AddonManager::lookupAddon(const std::string &name) const {
352  FCITX_D();
353  auto *addon = d->addon(name);
354  return addon ? addon->instance() : nullptr;
355 }
356 
357 const std::vector<std::string> &AddonManager::loadedAddonNames() const {
358  FCITX_D();
359  return d->loadOrder_;
360 }
361 
362 std::unordered_set<std::string>
363 AddonManager::addonNames(AddonCategory category) {
364  FCITX_D();
365  std::unordered_set<std::string> result;
366  for (auto &item : d->addons_) {
367  if (item.second->isValid() &&
368  item.second->info().category() == category) {
369  result.insert(item.first);
370  }
371  }
372  return result;
373 }
374 
376  FCITX_D();
377  return d->instance_;
378 }
379 
380 void AddonManager::setInstance(Instance *instance) {
381  FCITX_D();
382  d->instance_ = instance;
383  d->eventLoop_ = &instance->eventLoop();
384 }
385 
387  FCITX_D();
388  d->eventLoop_ = eventLoop;
389 }
390 
392  FCITX_D();
393  return d->eventLoop_;
394 }
395 
397  FCITX_D();
398  return d->version_;
399 }
400 
402  FCITX_D();
403  auto timestamp = StandardPaths::global().timestamp(
404  StandardPathsType::PkgData, d->addonConfigDir_);
405  return timestamp > d->timestamp_;
406 }
407 
409  std::unordered_map<std::string, std::vector<std::string>> options) {
410  FCITX_D();
411  d->options_ = std::move(options);
412 }
413 
414 std::vector<std::string> AddonManager::addonOptions(const std::string &name) {
415  FCITX_D();
416  if (auto *options = findValue(d->options_, name)) {
417  return *options;
418  }
419  return {};
420 }
421 
422 } // 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:88
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:1583
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.