D-Bus server: utility class for localed D-Bus interface

This is the client side for the org.freedesktop.locale1 "Locale"
property. It ensures that new values of that property are made
available locally.

Optionally it can also update the current process' environment and
send a simpler "locale environment changed" if it made some changes.
This needs to be enabled by connecting the setLocale() method to the
m_localeValues signal.
This commit is contained in:
Patrick Ohly 2013-07-17 15:19:05 +02:00
parent 44b31eac29
commit 76d7af91ce
3 changed files with 298 additions and 0 deletions

View File

@ -0,0 +1,192 @@
/*
* Copyright (C) 2013 Intel Corporation
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) version 3.
*
* 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include "localed-listener.h"
#include <syncevo/BoostHelper.h>
#include <syncevo/Logging.h>
#include <syncevo/util.h>
#include <boost/foreach.hpp>
#include <boost/algorithm/string/join.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <algorithm>
SE_BEGIN_CXX
static const char LOCALED_PATH[] = "/org/freedesktop/locale1";
static const char LOCALED_INTERFACE[] = "org.freedesktop.locale1";
static const char LOCALED_DESTINATION[] = "org.freedesktop.locale1";
static const char LOCALED_LOCALE_PROPERTY[] = "Locale";
/**
* Must be a complete list, because we need to know which variables
* we have to unset if not set remotely.
*
* Localed intentionally does not support LC_ALL. As localed.c says:
* "We don't list LC_ALL here on purpose. People should be using LANG instead."
*/
static const char * const LOCALED_ENV_VARS[] = {
"LANG",
"LC_CTYPE",
"LC_NUMERIC",
"LC_TIME",
"LC_COLLATE",
"LC_MONETARY",
"LC_MESSAGES",
"LC_PAPER",
"LC_NAME",
"LC_ADDRESS",
"LC_TELEPHONE",
"LC_MEASUREMENT",
"LC_IDENTIFICATION"
};
static const char PROPERTIES_INTERFACE[] = "org.freedesktop.DBus.Properties";
static const char PROPERTIES_CHANGED_SIGNAL[] = "PropertiesChanged";
static const char PROPERTIES_GET[] = "Get";
LocaledListener::LocaledListener():
GDBusCXX::DBusRemoteObject(!strcmp(getEnv("SYNCEVOLUTION_LOCALED", ""), "none") ?
NULL : /* simulate missing localed */
GDBusCXX::dbus_get_bus_connection(!strcmp(getEnv("SYNCEVOLUTION_LOCALED", ""), "session") ?
"SESSION" : /* use our own localed stub */
"SYSTEM" /* use real localed */,
NULL, false, NULL),
LOCALED_PATH,
PROPERTIES_INTERFACE,
LOCALED_DESTINATION),
m_propertiesChanged(*this, PROPERTIES_CHANGED_SIGNAL),
m_propertiesGet(*this, PROPERTIES_GET)
{
m_propertiesChanged.activate(boost::bind(&LocaledListener::onPropertiesChange, this, _1, _2, _3));
};
boost::shared_ptr<LocaledListener> LocaledListener::create()
{
static boost::weak_ptr<LocaledListener> singleton;
boost::shared_ptr<LocaledListener> self = singleton.lock();
if (!self) {
self.reset(new LocaledListener());
self->m_self = self;
singleton = self;
}
return self;
};
void LocaledListener::onPropertiesChange(const std::string &interface,
const Properties &properties,
const Invalidated &invalidated)
{
if (interface == LOCALED_INTERFACE) {
boost::function<void (const LocaleEnv &env)> result(boost::bind(&LocaledListener::emitLocaleEnv, m_self, _1));
BOOST_FOREACH (const Properties::value_type &entry, properties) {
if (entry.first == LOCALED_LOCALE_PROPERTY) {
const LocaleEnv *locale = boost::get<LocaleEnv>(&entry.second);
if (locale) {
SE_LOG_DEBUG(NULL, "localed: got new Locale");
processLocaleProperty(*locale, "", false, result);
} else {
SE_LOG_DEBUG(NULL, "localed: got new Locale of invalid type?! Ignore.");
}
return;
}
}
if (std::find(invalidated.begin(),
invalidated.end(),
LOCALED_LOCALE_PROPERTY) != invalidated.end()) {
SE_LOG_DEBUG(NULL, "localed: Locale changed, need to get new value");
m_propertiesGet.start(std::string(LOCALED_INTERFACE),
std::string(LOCALED_LOCALE_PROPERTY),
boost::bind(&LocaledListener::processLocaleProperty, m_self,
_1, _2, false,
result));
}
SE_LOG_DEBUG(NULL, "localed: ignoring irrelevant property change");
}
}
void LocaledListener::processLocaleProperty(const LocaleVariant &variant,
const std::string &error,
bool mustCall,
const ProcessLocalePropCB_t &result)
{
SE_LOG_DEBUG(NULL, "localed: got Locale property: %s", error.empty() ? "<<successfully>>" : error.c_str());
const LocaleEnv *locale =
error.empty() ?
boost::get<LocaleEnv>(&variant) :
NULL;
LocaleEnv current;
if (!locale && mustCall) {
SE_LOG_DEBUG(NULL, "localed: using current environment as fallback");
BOOST_FOREACH (const char *name, LOCALED_ENV_VARS) {
const char *value = getenv(name);
if (value) {
current.push_back(StringPrintf("%s=%s", name, value));
}
}
locale = &current;
}
if (locale) {
result(*locale);
}
}
void LocaledListener::emitLocaleEnv(const LocaleEnv &env)
{
SE_LOG_DEBUG(NULL, "localed: got environment: %s",
boost::join(env, " ").c_str());
m_localeValues(env);
}
void LocaledListener::check(const boost::function<void (const LocaleEnv &env)> &result)
{
SE_LOG_DEBUG(NULL, "localed: get current Locale property");
m_propertiesGet.start(std::string(LOCALED_INTERFACE),
std::string(LOCALED_LOCALE_PROPERTY),
boost::bind(&LocaledListener::processLocaleProperty, m_self, _1, _2, true, result));
}
void LocaledListener::setLocale(const LocaleEnv &locale)
{
bool modified = false;
BOOST_FOREACH (const char *name, LOCALED_ENV_VARS) {
const char *value = getenv(name);
std::string assignment = StringPrintf("%s=", name);
LocaleEnv::const_iterator instance = std::find_if(locale.begin(), locale.end(),
boost::bind(boost::starts_with<std::string, std::string>, _1, name));
const char *newvalue = instance != locale.end() ? instance->c_str() + assignment.size() : NULL;
if ((value && newvalue && strcmp(value, newvalue)) ||
(!value && newvalue)) {
modified = true;
setenv(name, newvalue, true);
SE_LOG_DEBUG(NULL, "localed: %s = %s -> %s", name, value ? value : "<none>", newvalue);
} else if (value && !newvalue) {
modified = true;
unsetenv(name);
SE_LOG_DEBUG(NULL, "localed: %s = %s -> <none>", name, value);
}
}
SE_LOG_DEBUG(NULL, "localed: environment %s", modified ? "changed" : "unchanged");
if (modified) {
m_localeChanged();
}
}
SE_END_CXX

View File

@ -0,0 +1,105 @@
/*
* Copyright (C) 2013 Intel Corporation
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) version 3.
*
* 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#ifndef INCL_LOCALED_LISTENER
#define INCL_LOCALED_LISTENER
#include <gdbus-cxx-bridge.h>
#include <boost/signals2.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <syncevo/declarations.h>
SE_BEGIN_CXX
/**
* The D-Bus binding for http://www.freedesktop.org/wiki/Software/systemd/localed/
*/
class LocaledListener : public GDBusCXX::DBusRemoteObject
{
public:
/**
* Singleton - at most one instance of LocaledListener will exist.
* It lives as long as one of the create() callers keeps the reference.
*/
static boost::shared_ptr<LocaledListener> create();
/**
* array of var=value, for example LANG, LC_NUMERIC, etc.
*/
typedef std::vector<std::string> LocaleEnv;
/**
* Emitted for each new set of env variables from localed.
* May or may not be different from what we have already.
*/
boost::signals2::signal<void (const LocaleEnv &env)> m_localeValues;
/**
* The result callback is guaranteed to be invoked once,
* either with the current settings from localed or, if
* retrieving those fails, with the current environment.
*/
void check(const boost::function<void (const LocaleEnv &env)> &result);
/**
* Updates current environment to match the one in the parameter.
* Emits m_localeChanged if and only if something really changed.
*
* Not called by default. To ensure that the current environment
* matches localed, do:
* - use current settings
* - m_localeValues -> setLocale
* - check -> setLocale
*
* Alternatively, one could wait until check() completes and only
* then use the current settings.
*/
void setLocale(const LocaleEnv &locale);
typedef boost::signals2::signal<void ()> LocaleChangedSignal;
/**
* Emitted by setLocale() only if something really changed in the
* local environment.
*/
LocaleChangedSignal m_localeChanged;
private:
boost::weak_ptr<LocaledListener> m_self;
typedef boost::variant<LocaleEnv> LocaleVariant;
typedef std::map<std::string, LocaleVariant> Properties;
typedef std::vector<std::string> Invalidated;
GDBusCXX::SignalWatch3<std::string, Properties, Invalidated> m_propertiesChanged;
GDBusCXX::DBusClientCall1<LocaleVariant> m_propertiesGet;
LocaledListener();
void onPropertiesChange(const std::string &interface,
const Properties &properties,
const Invalidated &invalidated);
typedef boost::function<void (const LocaleEnv &env)> ProcessLocalePropCB_t;
void processLocaleProperty(const LocaleVariant &locale,
const std::string &error,
bool mustCall,
const ProcessLocalePropCB_t &result);
void emitLocaleEnv(const LocaleEnv &env);
};
SE_END_CXX
#endif // INCL_LOCALED_LISTENER

View File

@ -25,6 +25,7 @@ src_dbus_server_server_cpp_files = \
src/dbus/server/dbus-callbacks.cpp \
src/dbus/server/dbus-user-interface.cpp \
src/dbus/server/exceptions.cpp \
src/dbus/server/localed-listener.cpp \
src/dbus/server/info-req.cpp \
src/dbus/server/network-manager-client.cpp \
src/dbus/server/presence-status.cpp \