Merge branch 'shuffle-geoip'

This commit is contained in:
Adriaan de Groot 2019-05-10 15:07:20 -04:00
commit c3754126d0
31 changed files with 877 additions and 383 deletions

View File

@ -35,6 +35,11 @@ This release contains contributions from (alphabetically by first name):
- *finished* has a new mechanism for configuring the behavior of the
*restart now* button. The old-style boolean configuration is still
supported but generates a warning. #1138
- *locale* module GeoIP configuration has a new preferred format.
See `locale.conf` for details. The old configuration is still
supported but will be phased out before 3.3.0 -- in particular,
support for "legacy" format will be removed, since that was a
crutch for the disappearance of one GeoIP provider in 2018.
- *oemid* is a new module for configuring OEM phase-0 (image pre-mastering,
or pre-deployment) things. It has limited functionality at the moment,
writing only a single batch-identifier file. #943

View File

@ -13,6 +13,9 @@ configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/CalamaresConfig.h.in
configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/../calamares/CalamaresVersion.h.in
${CMAKE_CURRENT_BINARY_DIR}/CalamaresVersion.h )
set( OPTIONAL_PRIVATE_LIBRARIES "" )
set( OPTIONAL_PUBLIC_LIBRARIES "" )
set( libSources
CppJob.cpp
GlobalStorage.cpp
@ -21,10 +24,17 @@ set( libSources
JobQueue.cpp
ProcessJob.cpp
Settings.cpp
# GeoIP services
geoip/Interface.cpp
geoip/GeoIPJSON.cpp
geoip/Handler.cpp
# Locale-data service
locale/Label.cpp
locale/LabelModel.cpp
locale/Lookup.cpp
# Partition service
partition/PartitionSize.cpp
@ -32,7 +42,6 @@ set( libSources
utils/CalamaresUtilsSystem.cpp
utils/CommandList.cpp
utils/Dirs.cpp
utils/LocaleLabel.cpp
utils/Logger.cpp
utils/PluginFactory.cpp
utils/Retranslator.cpp
@ -54,9 +63,11 @@ include_directories(
${YAMLCPP_INCLUDE_DIR}
)
### OPTIONAL Python support
#
#
if( WITH_PYTHON )
set( libSources
${libSources}
list( APPEND libSources
PythonHelper.cpp
PythonJob.cpp
PythonJobApi.cpp
@ -71,13 +82,24 @@ if( WITH_PYTHON )
include_directories(${Boost_INCLUDE_DIRS})
link_directories(${Boost_LIBRARY_DIRS})
set( OPTIONAL_PRIVATE_LIBRARIES
${OPTIONAL_PRIVATE_LIBRARIES}
list( APPEND OPTIONAL_PRIVATE_LIBRARIES
${PYTHON_LIBRARIES}
${Boost_LIBRARIES}
)
endif()
### OPTIONAL GeoIP XML support
#
#
find_package(Qt5 COMPONENTS Xml)
if( Qt5Xml_FOUND )
list( APPEND libSources geoip/GeoIPXML.cpp )
list( APPEND OPTIONAL_PUBLIC_LIBRARIES Qt5::Network Qt5::Xml )
endif()
### LIBRARY
#
#
add_library( calamares SHARED ${libSources} ${kdsagSources} )
set_target_properties( calamares
PROPERTIES
@ -92,6 +114,7 @@ target_link_libraries( calamares
LINK_PUBLIC
${YAMLCPP_LIBRARY}
Qt5::Core
${OPTIONAL_PUBLIC_LIBRARIES}
)
install( TARGETS calamares
@ -101,19 +124,6 @@ install( TARGETS calamares
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
if ( ECM_FOUND AND BUILD_TESTING )
ecm_add_test(
Tests.cpp
TEST_NAME
libcalamarestest
LINK_LIBRARIES
calamares
Qt5::Core
Qt5::Test
)
calamares_automoc( libcalamarestest )
endif()
# Make symlink lib/calamares/libcalamares.so to lib/libcalamares.so.VERSION so
# lib/calamares can be used as module path for the Python interpreter.
install( CODE "
@ -130,3 +140,37 @@ install( FILES ${CMAKE_CURRENT_BINARY_DIR}/CalamaresConfig.h DESTINATION include
install( FILES ${rootHeaders} DESTINATION include/libcalamares )
install( FILES ${kdsingleapplicationguardHeaders} DESTINATION include/libcalamares/kdsingleapplicationguard )
install( FILES ${utilsHeaders} DESTINATION include/libcalamares/utils )
### TESTING
#
#
if ( ECM_FOUND AND BUILD_TESTING )
ecm_add_test(
Tests.cpp
TEST_NAME
libcalamarestest
LINK_LIBRARIES
calamares
Qt5::Core
Qt5::Test
)
calamares_automoc( libcalamarestest )
ecm_add_test(
geoip/GeoIPTests.cpp
${geoip_src}
TEST_NAME
geoiptest
LINK_LIBRARIES
calamares
Qt5::Test
${YAMLCPP_LIBRARY}
)
calamares_automoc( geoiptest )
endif()
if( BUILD_TESTING )
add_executable( test_geoip geoip/test_geoip.cpp ${geoip_src} )
target_link_libraries( test_geoip calamares Qt5::Network ${YAMLCPP_LIBRARY} )
calamares_automoc( test_geoip )
endif()

View File

@ -25,11 +25,20 @@
#include <QByteArray>
namespace CalamaresUtils::GeoIP
{
GeoIPJSON::GeoIPJSON(const QString& attribute)
: GeoIP( attribute.isEmpty() ? QStringLiteral( "time_zone" ) : attribute )
: Interface( attribute.isEmpty() ? QStringLiteral( "time_zone" ) : attribute )
{
}
/** @brief Indexes into a map @m by selectors @p l
*
* Each element of @p l is an index into map @m or a sub-map thereof,
* so that "foo.bar.baz" looks up "baz" in the sub-map "bar" of sub-map
* "foo" of @p m, like a regular JSON lookup would.
*/
static QString
selectMap( const QVariantMap& m, const QStringList& l, int index)
{
@ -48,8 +57,8 @@ selectMap( const QVariantMap& m, const QStringList& l, int index)
}
}
GeoIP::RegionZonePair
GeoIPJSON::processReply( const QByteArray& data )
QString
GeoIPJSON::rawReply( const QByteArray& data )
{
try
{
@ -60,7 +69,7 @@ GeoIPJSON::processReply( const QByteArray& data )
var.isValid() &&
var.type() == QVariant::Map )
{
return splitTZString( selectMap( var.toMap(), m_element.split('.'), 0 ) );
return selectMap( var.toMap(), m_element.split('.'), 0 );
}
else
cWarning() << "Invalid YAML data for GeoIPJSON";
@ -70,5 +79,15 @@ GeoIPJSON::processReply( const QByteArray& data )
CalamaresUtils::explainYamlException( e, data, "GeoIP data");
}
return qMakePair( QString(), QString() );
return QString();
}
GeoIP::RegionZonePair
GeoIPJSON::processReply( const QByteArray& data )
{
return splitTZString( rawReply( data ) );
}
} // namespace

View File

@ -1,6 +1,6 @@
/* === This file is part of Calamares - <http://github.com/calamares> ===
*
* Copyright 2018, Adriaan de Groot <groot@kde.org>
* Copyright 2018-2019, Adriaan de Groot <groot@kde.org>
*
* Calamares is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -16,19 +16,23 @@
* along with Calamares. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef GEOIPJSON_H
#define GEOIPJSON_H
#ifndef GEOIP_GEOIPJSON_H
#define GEOIP_GEOIPJSON_H
#include "GeoIP.h"
#include "Interface.h"
namespace CalamaresUtils::GeoIP
{
/** @brief GeoIP lookup for services that return JSON.
*
* This is the original implementation of GeoIP lookup,
* (e.g. using the FreeGeoIP.net service), or similar.
*
* The data is assumed to be in JSON format with a time_zone attribute.
*
* @note This class is an implementation detail.
*/
class GeoIPJSON : public GeoIP
class GeoIPJSON : public Interface
{
public:
/** @brief Configure the attribute name which is selected.
@ -38,7 +42,9 @@ public:
*/
explicit GeoIPJSON( const QString& attribute = QString() );
virtual RegionZonePair processReply( const QByteArray& );
virtual RegionZonePair processReply( const QByteArray& ) override;
virtual QString rawReply(const QByteArray & ) override;
} ;
} // namespace
#endif

View File

@ -19,9 +19,10 @@
#include "GeoIPTests.h"
#include "GeoIPJSON.h"
#ifdef HAVE_XML
#ifdef QT_XML_LIB
#include "GeoIPXML.h"
#endif
#include "Handler.h"
#include <QNetworkAccessManager>
#include <QNetworkReply>
@ -31,6 +32,8 @@
QTEST_GUILESS_MAIN( GeoIPTests )
using namespace CalamaresUtils::GeoIP;
GeoIPTests::GeoIPTests()
{
}
@ -118,7 +121,7 @@ static const char xml_data_ubiquity[] =
void
GeoIPTests::testXML()
{
#ifdef HAVE_XML
#ifdef QT_XML_LIB
GeoIPXML handler;
auto tz = handler.processReply( xml_data_ubiquity );
@ -133,7 +136,7 @@ GeoIPTests::testXML2()
static const char data[] =
"<Response><TimeZone>America/North Dakota/Beulah</TimeZone></Response>"; // With a space!
#ifdef HAVE_XML
#ifdef QT_XML_LIB
GeoIPXML handler;
auto tz = handler.processReply( data );
@ -145,7 +148,7 @@ GeoIPTests::testXML2()
void GeoIPTests::testXMLalt()
{
#ifdef HAVE_XML
#ifdef QT_XML_LIB
GeoIPXML handler( "ZT" );
auto tz = handler.processReply( "<A><B/><C><ZT>Moon/Dark_side</ZT></C></A>" );
@ -157,7 +160,7 @@ void GeoIPTests::testXMLalt()
void
GeoIPTests::testXMLbad()
{
#ifdef HAVE_XML
#ifdef QT_XML_LIB
GeoIPXML handler;
auto tz = handler.processReply( "{time_zone: \"Europe/Paris\"}" );
QCOMPARE( tz.first, QString() );
@ -172,24 +175,25 @@ GeoIPTests::testXMLbad()
void GeoIPTests::testSplitTZ()
{
auto tz = GeoIP::splitTZString( QStringLiteral("Moon/Dark_side") );
using namespace CalamaresUtils::GeoIP;
auto tz = splitTZString( QStringLiteral("Moon/Dark_side") );
QCOMPARE( tz.first, QStringLiteral("Moon") );
QCOMPARE( tz.second, QStringLiteral("Dark_side") );
// Some providers return weirdly escaped data
tz = GeoIP::splitTZString( QStringLiteral("America\\/NewYork") );
tz = splitTZString( QStringLiteral("America\\/NewYork") );
QCOMPARE( tz.first, QStringLiteral("America") );
QCOMPARE( tz.second, QStringLiteral("NewYork") ); // That's not actually the zone name
// Check that bogus data fails
tz = GeoIP::splitTZString( QString() );
tz = splitTZString( QString() );
QCOMPARE( tz.first, QString() );
tz = GeoIP::splitTZString( QStringLiteral("America.NewYork") );
tz = splitTZString( QStringLiteral("America.NewYork") );
QCOMPARE( tz.first, QString() );
// Check that three-level is split properly and space is replaced
tz = GeoIP::splitTZString( QStringLiteral("America/North Dakota/Beulah") );
tz = splitTZString( QStringLiteral("America/North Dakota/Beulah") );
QCOMPARE( tz.first, QStringLiteral("America") );
QCOMPARE( tz.second, QStringLiteral("North_Dakota/Beulah") );
}
@ -216,14 +220,18 @@ synchronous_get( const char* urlstring )
#define CHECK_GET(t, selector, url) \
{ \
auto tz = GeoIP##t( selector ).processReply( synchronous_get( url ) ); \
qDebug() << tz; \
QCOMPARE( default_tz, tz ); \
auto tz2 = CalamaresUtils::GeoIP::Handler( ""#t, url, selector ).get(); \
qDebug() << tz2; \
QCOMPARE( default_tz, tz2 ); \
}
void GeoIPTests::testGet()
{
if ( !QProcessEnvironment::systemEnvironment().contains( QStringLiteral("TEST_HTTP_GET") ) )
{
qDebug() << "Skipping HTTP GET tests";
qDebug() << "Skipping HTTP GET tests, set TEST_HTTP_GET environment variable to enable";
return;
}
@ -241,15 +249,12 @@ void GeoIPTests::testGet()
// the TZ data is the same as the default_tz; this is fragile if the
// services don't agree on the location of where the test is run.
CHECK_GET( JSON, QString(), "https://geoip.kde.org/v1/calamares" ) // Check it's consistent
CHECK_GET( JSON, QString(), "http://freegeoip.net/json/" ) // Original FreeGeoIP service
CHECK_GET( JSON, QStringLiteral("timezone"), "https://ipapi.co/json" ) // Different JSON
CHECK_GET( JSON, QStringLiteral("timezone"), "http://ip-api.com/json" )
CHECK_GET( JSON, QStringLiteral("location.time_zone"), "http://geoip.nekudo.com/api/" ) // 2-level JSON
CHECK_GET( JSON, QStringLiteral("Location.TimeZone"), "https://geoip.kde.org/debug" ) // 2-level JSON
#ifdef HAVE_XML
#ifdef QT_XML_LIB
CHECK_GET( XML, QString(), "http://geoip.ubuntu.com/lookup" ) // Ubiquity's XML format
CHECK_GET( XML, QString(), "https://geoip.kde.org/v1/ubiquity" ) // Temporary KDE service
#endif

View File

@ -23,38 +23,68 @@
#include <QNetworkReply>
#include <QtXml/QDomDocument>
namespace CalamaresUtils::GeoIP
{
GeoIPXML::GeoIPXML( const QString& element )
: GeoIP( element.isEmpty() ? QStringLiteral( "TimeZone" ) : element )
: Interface( element.isEmpty() ? QStringLiteral( "TimeZone" ) : element )
{
}
GeoIP::RegionZonePair
GeoIPXML::processReply( const QByteArray& data )
static QStringList
getElementTexts( const QByteArray& data, const QString& tag )
{
QStringList elements;
QString domError;
int errorLine, errorColumn;
QDomDocument doc;
if ( doc.setContent( data, false, &domError, &errorLine, &errorColumn ) )
{
const auto tzElements = doc.elementsByTagName( m_element );
const auto tzElements = doc.elementsByTagName( tag );
cDebug() << "GeoIP found" << tzElements.length() << "elements";
for ( int it = 0; it < tzElements.length(); ++it )
{
auto e = tzElements.at(it).toElement();
auto tz = splitTZString( e.text() );
if ( !tz.first.isEmpty() )
return tz;
auto e_text = e.text();
if ( !e_text.isEmpty() )
elements.append( e_text );
}
// None of them valid
cWarning() << "GeopIP XML had no recognizable timezone";
return qMakePair( QString(), QString() );
}
else
{
cWarning() << "GeoIP XML data error:" << domError << "(line" << errorLine << errorColumn << ')';
}
return qMakePair( QString(), QString() );
if ( elements.count() < 1 )
cWarning() << "GeopIP XML had no non-empty elements" << tag;
return elements;
}
QString
GeoIPXML::rawReply( const QByteArray& data )
{
for ( const auto& e : getElementTexts( data, m_element ) )
if ( !e.isEmpty() )
return e;
return QString();
}
GeoIP::RegionZonePair
GeoIPXML::processReply( const QByteArray& data )
{
for ( const auto& e : getElementTexts( data, m_element ) )
{
auto tz = splitTZString( e );
if ( !tz.first.isEmpty() )
return tz;
}
return RegionZonePair();
}
} // namespace

View File

@ -1,6 +1,6 @@
/* === This file is part of Calamares - <http://github.com/calamares> ===
*
* Copyright 2018, Adriaan de Groot <groot@kde.org>
* Copyright 2018-2019, Adriaan de Groot <groot@kde.org>
*
* Calamares is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -16,19 +16,23 @@
* along with Calamares. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef GEOIPXML_H
#define GEOIPXML_H
#ifndef GEOIP_GEOIPXML_H
#define GEOIP_GEOIPXML_H
#include "GeoIP.h"
#include "Interface.h"
namespace CalamaresUtils::GeoIP
{
/** @brief GeoIP lookup with XML data
*
* The data is assumed to be in XML format with a
* <Response><TimeZone></TimeZone></Response>
* element, which contains the text (string) for the region/zone. This
* format is expected by, e.g. the Ubiquity installer.
*
* @note This class is an implementation detail.
*/
class GeoIPXML : public GeoIP
class GeoIPXML : public Interface
{
public:
/** @brief Configure the element tag which is selected.
@ -38,7 +42,9 @@ public:
*/
explicit GeoIPXML( const QString& element = QString() );
virtual RegionZonePair processReply( const QByteArray& );
virtual RegionZonePair processReply( const QByteArray& ) override;
virtual QString rawReply(const QByteArray & ) override;
} ;
} // namespace
#endif

View File

@ -0,0 +1,183 @@
/* === This file is part of Calamares - <http://github.com/calamares> ===
*
* Copyright 2019, Adriaan de Groot <groot@kde.org>
*
* Calamares is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Calamares 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Calamares. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Handler.h"
#include "GeoIPJSON.h"
#if defined(QT_XML_LIB)
#include "GeoIPXML.h"
#endif
#include "utils/Logger.h"
#include "utils/NamedEnum.h"
#include "utils/Variant.h"
#include <QEventLoop>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <memory>
static const NamedEnumTable< CalamaresUtils::GeoIP::Handler::Type >&
handlerTypes()
{
using Type = CalamaresUtils::GeoIP::Handler::Type;
static const NamedEnumTable<Type> names{
{ QStringLiteral( "none" ), Type::None},
{ QStringLiteral( "json" ), Type::JSON},
{ QStringLiteral( "xml" ), Type::XML}
};
return names;
}
namespace CalamaresUtils::GeoIP
{
Handler::Handler()
: m_type( Type::None )
{
}
Handler::Handler( const QString& implementation, const QString& url, const QString& selector )
: m_type( Type::None )
, m_url( url )
, m_selector( selector )
{
bool ok = false;
m_type = handlerTypes().find( implementation, ok );
#if !defined(QT_XML_LIB)
if ( m_type == Type::XML )
{
m_type = Type::None;
cWarning() << "GeoIP style XML is not supported in this version of Calamares.";
}
#endif
if ( !ok )
{
cWarning() << "GeoIP Style" << implementation << "is not recognized.";
}
}
Handler::~Handler()
{
}
static QByteArray
synchronous_get( const QString& urlstring )
{
QUrl url( urlstring );
QNetworkAccessManager manager;
QEventLoop loop;
QObject::connect( &manager, &QNetworkAccessManager::finished, &loop, &QEventLoop::quit );
QNetworkRequest request( url );
QNetworkReply* reply = manager.get( request );
loop.exec();
reply->deleteLater();
return reply->readAll();
}
static std::unique_ptr< Interface >
create_interface( Handler::Type t, const QString& selector )
{
switch( t )
{
case Handler::Type::None:
return nullptr;
case Handler::Type::JSON:
return std::make_unique< GeoIPJSON >( selector );
case Handler::Type::XML:
#if defined(QT_XML_LIB)
return std::make_unique< GeoIPXML >( selector );
#else
return nullptr;
#endif
default: // there are no others
return nullptr;
}
}
static RegionZonePair
do_query( Handler::Type type, const QString& url, const QString& selector )
{
const auto interface = create_interface( type, selector );
if ( !interface )
return RegionZonePair();
return interface->processReply( synchronous_get( url ) );
}
static QString
do_raw_query( Handler::Type type, const QString& url, const QString& selector )
{
const auto interface = create_interface( type, selector );
if ( !interface )
return QString();
return interface->rawReply( synchronous_get( url ) );
}
RegionZonePair
Handler::get() const
{
if ( !isValid() )
return RegionZonePair();
return do_query( m_type, m_url, m_selector );
}
QFuture< RegionZonePair >
Handler::query() const
{
Handler::Type type = m_type;
QString url = m_url;
QString selector = m_selector;
return QtConcurrent::run( [=]
{
return do_query( type, url, selector );
} );
}
QString
Handler::getRaw() const
{
if ( !isValid() )
return QString();
return do_raw_query( m_type, m_url, m_selector );
}
QFuture< QString >
Handler::queryRaw() const
{
Handler::Type type = m_type;
QString url = m_url;
QString selector = m_selector;
return QtConcurrent::run( [=]
{
return do_raw_query( type, url, selector );
} );
}
} // namespace

View File

@ -0,0 +1,91 @@
/* === This file is part of Calamares - <http://github.com/calamares> ===
*
* Copyright 2019, Adriaan de Groot <groot@kde.org>
*
* Calamares is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Calamares 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Calamares. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef GEOIP_HANDLER_H
#define GEOIP_HANDLER_H
#include "Interface.h"
#include <QtConcurrent/QtConcurrentRun>
#include <QString>
#include <QVariantMap>
namespace CalamaresUtils {}
namespace CalamaresUtils::GeoIP
{
/** @brief Handle one complete GeoIP lookup.
*
* This class handles one complete GeoIP lookup. Create it with
* suitable configuration values, then call get(). This is a
* synchronous API and will return an invalid zone pair on
* error or if the configuration is not understood. For an
* async API, use query().
*/
class DLLEXPORT Handler
{
public:
enum class Type
{
None,
JSON,
XML
} ;
/** @brief An unconfigured handler; this always returns errors. */
Handler();
/** @brief A handler for a specific GeoIP source.
*
* The @p implementation name selects an implementation; currently JSON and XML
* are supported. The @p url is retrieved by query() and then the @p selector
* is used to select something from the data returned by the @url.
*/
Handler( const QString& implementation, const QString& url, const QString& selector );
~Handler();
/** @brief Synchronously get the GeoIP result.
*
* If the Handler is valid, then do the actual fetching and interpretation
* of data and return the result. An invalid Handler will return an
* invalid (empty) result.
*/
RegionZonePair get() const;
/// @brief Like get, but don't interpret the contents
QString getRaw() const;
/** @brief Asynchronously get the GeoIP result.
*
* See get() for the return value.
*/
QFuture< RegionZonePair > query() const;
/// @brief Like query, but don't interpret the contents
QFuture< QString > queryRaw() const;
bool isValid() const { return m_type != Type::None; }
Type type() const { return m_type; }
private:
Type m_type;
const QString m_url;
const QString m_selector;
};
} // namespace
#endif

View File

@ -16,21 +16,24 @@
* along with Calamares. If not, see <http://www.gnu.org/licenses/>.
*/
#include "GeoIP.h"
#include "Interface.h"
#include "utils/Logger.h"
GeoIP::GeoIP(const QString& e)
namespace CalamaresUtils::GeoIP
{
Interface::Interface(const QString& e)
: m_element( e )
{
}
GeoIP::~GeoIP()
Interface::~Interface()
{
}
GeoIP::RegionZonePair
GeoIP::splitTZString( const QString& tz )
RegionZonePair
splitTZString( const QString& tz )
{
QString timezoneString( tz );
timezoneString.remove( '\\' );
@ -42,8 +45,10 @@ GeoIP::splitTZString( const QString& tz )
cDebug() << "GeoIP reporting" << timezoneString;
QString region = tzParts.takeFirst();
QString zone = tzParts.join( '/' );
return qMakePair( region, zone );
return RegionZonePair( region, zone );
}
return qMakePair( QString(), QString() );
return RegionZonePair( QString(), QString() );
}
} // namespace

View File

@ -0,0 +1,98 @@
/* === This file is part of Calamares - <http://github.com/calamares> ===
*
* Copyright 2018-2019, Adriaan de Groot <groot@kde.org>
*
* Calamares is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Calamares 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Calamares. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef GEOIP_INTERFACE_H
#define GEOIP_INTERFACE_H
#include "DllMacro.h"
#include <QPair>
#include <QString>
#include <QUrl>
class QByteArray;
namespace CalamaresUtils {}
namespace CalamaresUtils::GeoIP
{
/** @brief A Region, Zone pair of strings
*
* A GeoIP lookup returns a timezone, which is represented as a Region,
* Zone pair of strings (e.g. "Europe" and "Amsterdam"). Generally,
* pasting the strings back together with a "/" is the right thing to
* do. The Zone **may** contain a "/" (e.g. "Kentucky/Monticello").
*/
class DLLEXPORT RegionZonePair : public QPair<QString, QString>
{
public:
/** @brief Construct from an existing pair. */
explicit RegionZonePair( const QPair& p ) : QPair(p) { }
/** @brief Construct from two strings, like qMakePair(). */
RegionZonePair( const QString& region, const QString& zone ) : QPair( region, zone ) { }
/** @brief An invalid zone pair (empty strings). */
RegionZonePair() : QPair( QString(), QString() ) { }
bool isValid() const { return !first.isEmpty(); }
} ;
/** @brief Splits a region/zone string into a pair.
*
* Cleans up the string by removing backslashes (\\)
* since some providers return silly-escaped names. Replaces
* spaces with _ since some providers return human-readable names.
* Splits on the first / in the resulting string, or returns a
* pair of empty QStrings if it can't. (e.g. America/North Dakota/Beulah
* will return "America", "North_Dakota/Beulah").
*/
DLLEXPORT RegionZonePair
splitTZString( const QString& s );
/**
* @brief Interface for GeoIP retrievers.
*
* A GeoIP retriever takes a configured URL (from the config file)
* and can handle the data returned from its interpretation of that
* configured URL, returning a region and zone.
*/
class DLLEXPORT Interface
{
public:
virtual ~Interface();
/** @brief Handle a (successful) request by interpreting the data.
*
* Should return a ( <zone>, <region> ) pair, e.g.
* ( "Europe", "Amsterdam" ). This is called **only** if the
* request to the fullUrl was successful; the handler
* is free to read as much, or as little, data as it
* likes. On error, returns a RegionZonePair with empty
* strings (e.g. ( "", "" ) ).
*/
virtual RegionZonePair processReply( const QByteArray& ) = 0;
/** @brief Get the raw reply data. */
virtual QString rawReply( const QByteArray& ) = 0;
protected:
Interface( const QString& e = QString() );
QString m_element; // string for selecting from data
} ;
} // namespace
#endif

View File

@ -23,11 +23,12 @@
#include <iostream>
#include "GeoIPJSON.h"
#ifdef HAVE_XML
#ifdef QT_XML_LIB
#include "GeoIPXML.h"
#endif
using std::cerr;
using namespace CalamaresUtils::GeoIP;
int main(int argc, char** argv)
{
@ -37,10 +38,10 @@ int main(int argc, char** argv)
return 1;
}
GeoIP* handler = nullptr;
Interface* handler = nullptr;
if ( QStringLiteral( "json" ) == argv[1] )
handler = new GeoIPJSON;
#ifdef HAVE_XML
#ifdef QT_XML_LIB
else if ( QStringLiteral( "xml" ) == argv[1] )
handler = new GeoIPXML;
#endif

View File

@ -1,7 +1,7 @@
/* === This file is part of Calamares - <https://github.com/calamares> ===
*
* Copyright 2014-2015, Teo Mrnjavac <teo@kde.org>
* Copyright 2017-2018, Adriaan de Groot <groot@kde.org>
* Copyright 2017-2019, Adriaan de Groot <groot@kde.org>
*
* Calamares is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -17,12 +17,12 @@
* along with Calamares. If not, see <http://www.gnu.org/licenses/>.
*/
#include "LocaleLabel.h"
#include "Label.h"
namespace CalamaresUtils
namespace CalamaresUtils::Locale
{
LocaleLabel::LocaleLabel()
Label::Label()
: m_locale( QLocale() )
{
m_localeId = m_locale.name();
@ -30,15 +30,15 @@ LocaleLabel::LocaleLabel()
setLabels( QString(), LabelFormat::IfNeededWithCountry );
}
LocaleLabel::LocaleLabel( const QString& locale, LabelFormat format )
: m_locale( LocaleLabel::getLocale( locale ) )
Label::Label( const QString& locale, LabelFormat format )
: m_locale( Label::getLocale( locale ) )
, m_localeId( locale )
{
setLabels( locale, format );
}
void
LocaleLabel::setLabels( const QString& locale, LabelFormat format )
Label::setLabels( const QString& locale, LabelFormat format )
{
//: language[name] (country[name])
QString longFormat = QObject::tr( "%1 (%2)" );
@ -59,7 +59,7 @@ LocaleLabel::setLabels( const QString& locale, LabelFormat format )
m_englishLabel = needsCountryName ? longFormat.arg( englishName, QLocale::countryToString( m_locale.country() ) ) : englishName;
}
QLocale LocaleLabel::getLocale( const QString& localeName )
QLocale Label::getLocale( const QString& localeName )
{
if ( localeName.contains( "@latin" ) )
{

View File

@ -1,7 +1,7 @@
/* === This file is part of Calamares - <https://github.com/calamares> ===
*
* Copyright 2014-2015, Teo Mrnjavac <teo@kde.org>
* Copyright 2017-2018, Adriaan de Groot <groot@kde.org>
* Copyright 2017-2019, Adriaan de Groot <groot@kde.org>
*
* Calamares is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -17,13 +17,14 @@
* along with Calamares. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef UTILS_LOCALELABEL_H
#define UTILS_LOCALELABEL_H
#ifndef LOCALE_LABEL_H
#define LOCALE_LABEL_H
#include <QLocale>
#include <QString>
namespace CalamaresUtils
namespace CalamaresUtils {}
namespace CalamaresUtils::Locale
{
/**
@ -33,14 +34,14 @@ namespace CalamaresUtils
* translation system) into QLocales, and also into consistent
* human-readable text labels.
*/
class LocaleLabel
class Label
{
public:
/** @brief Formatting option for label -- add (country) to label. */
enum class LabelFormat { AlwaysWithCountry, IfNeededWithCountry } ;
/** @brief Empty locale. This uses the system-default locale. */
LocaleLabel();
Label();
/** @brief Construct from a locale name.
*
@ -48,13 +49,13 @@ public:
* The @p format determines whether the country name is always present
* in the label (human-readable form) or only if needed for disambiguation.
*/
LocaleLabel( const QString& localeName, LabelFormat format = LabelFormat::IfNeededWithCountry );
Label( const QString& localeName, LabelFormat format = LabelFormat::IfNeededWithCountry );
/** @brief Define a sorting order.
*
* English (@see isEnglish() -- it means en_US) is sorted at the top.
*/
bool operator <( const LocaleLabel& other ) const
bool operator <( const Label& other ) const
{
return m_localeId < other.m_localeId;
}
@ -91,6 +92,18 @@ public:
return m_locale.name();
}
/// @brief Convenience accessor to the language part of the locale
QLocale::Language language() const
{
return m_locale.language();
}
/// @brief Convenience accessor to the country part (if any) of the locale
QLocale::Country country() const
{
return m_locale.country();
}
/** @brief Get a Qt locale for the given @p localeName
*
* This special-cases `sr@latin`, which is used as a translation

View File

@ -16,30 +16,37 @@
* along with Calamares. If not, see <http://www.gnu.org/licenses/>.
*/
#include "LocaleModel.h"
#include "LabelModel.h"
LocaleModel::LocaleModel( const QStringList& locales, QObject* parent )
#include "Lookup.h"
#include "CalamaresVersion.h" // For the list of translations
namespace CalamaresUtils::Locale
{
LabelModel::LabelModel( const QStringList& locales, QObject* parent )
: QAbstractListModel( parent )
{
Q_ASSERT( locales.count() > 0 );
m_locales.reserve( locales.count() );
for ( const auto& l : locales )
m_locales.push_back( CalamaresUtils::LocaleLabel( l ) );
m_locales.push_back( Label( l ) );
}
LocaleModel::~LocaleModel()
LabelModel::~LabelModel()
{
}
int
LocaleModel::rowCount( const QModelIndex& ) const
LabelModel::rowCount( const QModelIndex& ) const
{
return m_locales.count();
}
QVariant
LocaleModel::data( const QModelIndex& index, int role ) const
LabelModel::data( const QModelIndex& index, int role ) const
{
if ( ( role != LabelRole ) && ( role != EnglishLabelRole ) )
return QVariant();
@ -59,8 +66,8 @@ LocaleModel::data( const QModelIndex& index, int role ) const
}
}
const CalamaresUtils::LocaleLabel&
LocaleModel::locale( int row )
const Label&
LabelModel::locale( int row ) const
{
if ( ( row < 0 ) || ( row >= m_locales.count() ) )
{
@ -73,7 +80,7 @@ LocaleModel::locale( int row )
}
int
LocaleModel::find( std::function<bool ( const LocaleLabel& )> predicate ) const
LabelModel::find( std::function<bool ( const Label& )> predicate ) const
{
for ( int row = 0; row < m_locales.count() ; ++row )
{
@ -84,26 +91,40 @@ LocaleModel::find( std::function<bool ( const LocaleLabel& )> predicate ) const
}
int
LocaleModel::find( std::function<bool ( const QLocale& )> predicate ) const
LabelModel::find( std::function<bool ( const QLocale& )> predicate ) const
{
return find( [&]( const LocaleLabel& l )
return find( [&]( const Label& l )
{
return predicate( l.locale() );
} );
}
int
LocaleModel::find( const QLocale& locale ) const
LabelModel::find( const QLocale& locale ) const
{
return find( [&]( const LocaleLabel& l )
return find( [&]( const Label& l )
{
return locale == l.locale();
} );
}
void
LocaleTwoColumnDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
int
LabelModel::find( const QString& countryCode ) const
{
QStyledItemDelegate::paint( painter, option, index );
option.widget->style()->drawItemText( painter, option.rect, Qt::AlignRight | Qt::AlignVCenter, option.palette, false, index.data( LocaleModel::EnglishLabelRole ).toString() );
if ( countryCode.length() != 2 )
return -1;
auto c_l = countryData( countryCode );
int r = find( [&]( const Label& l ){ return ( l.language() == c_l.second ) && ( l.country() == c_l.first ); } );
if ( r >= 0 )
return r;
return find( [&]( const Label& l ){ return l.language() == c_l.second; } );
}
LabelModel* const availableTranslations()
{
static LabelModel model( QString( CALAMARES_TRANSLATION_LANGUAGES ).split( ';') );
return &model;
}
} // namespace

View File

@ -16,28 +16,31 @@
* along with Calamares. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef WELCOME_LOCALEMODEL_H
#define WELCOME_LOCALEMODEL_H
#ifndef LOCALE_LABELMODEL_H
#define LOCALE_LABELMODEL_H
#include "DllMacro.h"
#include "Label.h"
#include <QAbstractListModel>
#include <QStyledItemDelegate>
#include <QVector>
#include "utils/LocaleLabel.h"
class LocaleModel : public QAbstractListModel
namespace CalamaresUtils {}
namespace CalamaresUtils::Locale
{
class DLLEXPORT LabelModel : public QAbstractListModel
{
public:
using LocaleLabel = CalamaresUtils::LocaleLabel;
enum
{
LabelRole = Qt::DisplayRole,
EnglishLabelRole = Qt::UserRole + 1
};
LocaleModel( const QStringList& locales, QObject* parent = nullptr );
virtual ~LocaleModel() override;
LabelModel( const QStringList& locales, QObject* parent = nullptr );
virtual ~LabelModel() override;
int rowCount( const QModelIndex& parent ) const override;
@ -48,26 +51,34 @@ public:
* This is the backing data for the model; if @p row is out-of-range,
* returns a reference to en_US.
*/
const LocaleLabel& locale( int row );
const Label& locale( int row ) const;
/** @brief Searches for an item that matches @p predicate
*
* Returns the row number of the first match, or -1 if there isn't one.
*/
int find( std::function<bool( const QLocale& )> predicate ) const;
int find( std::function<bool( const LocaleLabel& )> predicate ) const;
int find( std::function<bool( const Label& )> predicate ) const;
/// @brief Looks for an item using the same locale, -1 if there isn't one
int find( const QLocale& ) const;
/// @brief Looks for an item that best matches the 2-letter country code
int find( const QString& countryCode ) const;
private:
QVector< LocaleLabel > m_locales;
QVector< Label > m_locales;
} ;
class LocaleTwoColumnDelegate : public QStyledItemDelegate
{
public:
using QStyledItemDelegate::QStyledItemDelegate;
void paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const override;
} ;
/** @brief Returns a model with all available translations.
*
* The translations are set when Calamares is compiled; the list
* is provided by CMake via the CALAMARES_TRANSLATION_LANGUAGES
* #define.
*
* This model is a singleton and can be shared.
*
* NOTE: While the model is not typed const, it should be. Do not modify.
*/
DLLEXPORT LabelModel* const availableTranslations();
} // namespace
#endif

View File

@ -20,7 +20,7 @@
#include "CountryData_p.cpp"
namespace Calamares
namespace CalamaresUtils::Locale
{
struct TwoChar
@ -35,7 +35,7 @@ struct TwoChar
cc2 = code[1].toLatin1();
}
}
char cc1;
char cc2;
};
@ -44,7 +44,7 @@ static const CountryData* lookup( TwoChar c )
{
if ( !c.cc1 )
return nullptr;
const CountryData* p = std::find_if(country_data_table, country_data_table + country_data_size,
[c=c]( const CountryData& d ){ return (d.cc1 == c.cc1) && (d.cc2 == c.cc2); }
);
@ -52,7 +52,7 @@ static const CountryData* lookup( TwoChar c )
return nullptr;
return p;
}
QLocale::Country countryForCode(const QString& code)
{
const CountryData* p = lookup( TwoChar( code ) );

View File

@ -24,25 +24,26 @@
#include <QLocale>
#include <QPair>
namespace Calamares
namespace CalamaresUtils {}
namespace CalamaresUtils::Locale
{
/* All the functions in this file do lookups of locale data
* based on CLDR tables; these are lookups that you can't (easily)
* do with just QLocale (e.g. from 2-letter country code to a likely
* locale).
*/
/// @brief Map a 2-letter code to a Country, or AnyCountry if not found
DLLEXPORT QLocale::Country countryForCode( const QString& code );
/** @brief Map a Country to a Language, or AnyLanguage if not found
*
*
* This is a *likely* language for the given country, based on the
* CLDR tables. For instance, this maps Belgium to Dutch.
*/
DLLEXPORT QLocale::Language languageForCountry( QLocale::Country country );
/// @brief Map a 2-letter code to a Language, or AnyLanguage if not found
DLLEXPORT QLocale::Language languageForCountry( const QString& code );
/// @brief Get both Country and Language for a 2-letter code
DLLEXPORT QPair< QLocale::Country, QLocale::Language > countryData( const QString& code );
/// @brief Get a likely locale for a 2-letter country code

View File

@ -8,16 +8,6 @@ endif()
include_directories( ${PROJECT_BINARY_DIR}/src/libcalamaresui )
set( geoip_src GeoIP.cpp GeoIPJSON.cpp )
set( geoip_libs )
find_package(Qt5 COMPONENTS Xml)
if( Qt5Xml_FOUND )
list( APPEND geoip_src GeoIPXML.cpp )
list( APPEND geoip_libs Qt5::Xml )
add_definitions( -DHAVE_XML )
endif()
calamares_add_plugin( locale
TYPE viewmodule
EXPORT_MACRO PLUGINDLLEXPORT_PRO
@ -42,20 +32,6 @@ calamares_add_plugin( locale
)
if( ECM_FOUND AND BUILD_TESTING )
ecm_add_test(
GeoIPTests.cpp
${geoip_src}
TEST_NAME
geoiptest
LINK_LIBRARIES
calamaresui
Qt5::Network
Qt5::Test
${geoip_libs}
${YAMLCPP_LIBRARY}
)
calamares_automoc( geoiptest )
ecm_add_test(
Tests.cpp
LocaleConfiguration.cpp
@ -67,9 +43,3 @@ if( ECM_FOUND AND BUILD_TESTING )
)
calamares_automoc( localetest )
endif()
if( BUILD_TESTING )
add_executable( test_geoip test_geoip.cpp ${geoip_src} )
target_link_libraries( test_geoip calamaresui Qt5::Network ${geoip_libs} ${YAMLCPP_LIBRARY} )
calamares_automoc( test_geoip )
endif()

View File

@ -1,70 +0,0 @@
/* === This file is part of Calamares - <http://github.com/calamares> ===
*
* Copyright 2018, Adriaan de Groot <groot@kde.org>
*
* Calamares is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Calamares 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Calamares. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef GEOIP_H
#define GEOIP_H
#include <QPair>
#include <QString>
#include <QUrl>
class QByteArray;
/**
* @brief Interface for GeoIP retrievers.
*
* A GeoIP retriever takes a configured URL (from the config file)
* and can handle the data returned from its interpretation of that
* configured URL, returning a region and zone.
*/
class GeoIP
{
public:
using RegionZonePair = QPair<QString, QString>;
virtual ~GeoIP();
/** @brief Handle a (successful) request by interpreting the data.
*
* Should return a ( <zone>, <region> ) pair, e.g.
* ( "Europe", "Amsterdam" ). This is called **only** if the
* request to the fullUrl was successful; the handler
* is free to read as much, or as little, data as it
* likes. On error, returns a RegionZonePair with empty
* strings (e.g. ( "", "" ) ).
*/
virtual RegionZonePair processReply( const QByteArray& ) = 0;
/** @brief Splits a region/zone string into a pair.
*
* Cleans up the string by removing backslashes (\\)
* since some providers return silly-escaped names. Replaces
* spaces with _ since some providers return human-readable names.
* Splits on the first / in the resulting string, or returns a
* pair of empty QStrings if it can't. (e.g. America/North Dakota/Beulah
* will return "America", "North_Dakota/Beulah").
*/
static RegionZonePair splitTZString( const QString& s );
protected:
GeoIP( const QString& e = QString() );
QString m_element; // string for selecting from data
} ;
#endif

View File

@ -27,8 +27,8 @@
#include "LCLocaleDialog.h"
#include "Settings.h"
#include "locale/Label.h"
#include "utils/CalamaresUtilsGui.h"
#include "utils/LocaleLabel.h"
#include "utils/Logger.h"
#include "utils/Retranslator.h"
@ -387,10 +387,10 @@ LocalePage::init( const QString& initialRegion,
std::pair< QString, QString > LocalePage::prettyLocaleStatus( const LocaleConfiguration& lc ) const
{
using CalamaresUtils::LocaleLabel;
using CalamaresUtils::Locale::Label;
LocaleLabel lang( lc.language(), LocaleLabel::LabelFormat::AlwaysWithCountry );
LocaleLabel num( lc.lc_numeric, LocaleLabel::LabelFormat::AlwaysWithCountry );
Label lang( lc.language(), Label::LabelFormat::AlwaysWithCountry );
Label num( lc.lc_numeric, Label::LabelFormat::AlwaysWithCountry );
return std::make_pair< QString, QString >(
tr( "The system language will be set to %1." ).arg( lang.label() ),

View File

@ -19,18 +19,15 @@
#include "LocaleViewStep.h"
#include "GeoIP.h"
#include "GeoIPJSON.h"
#ifdef HAVE_XML
#include "GeoIPXML.h"
#endif
#include "GlobalStorage.h"
#include "JobQueue.h"
#include "LocalePage.h"
#include "timezonewidget/localeglobal.h"
#include "widgets/WaitingWidget.h"
#include "GlobalStorage.h"
#include "JobQueue.h"
#include "geoip/Handler.h"
#include "utils/CalamaresUtilsGui.h"
#include "utils/Logger.h"
#include "utils/Variant.h"
@ -116,54 +113,16 @@ LocaleViewStep::setUpPage()
void
LocaleViewStep::fetchGeoIpTimezone()
{
QString actualUrl( m_geoipUrl );
GeoIP *handler = nullptr;
if ( m_geoipStyle.isEmpty() || m_geoipStyle == "legacy" )
CalamaresUtils::GeoIP::Handler h( m_geoipStyle, m_geoipUrl, m_geoipSelector );
if ( h.isValid() )
{
actualUrl.append( "/json/" );
handler = new GeoIPJSON( m_geoipSelector );
m_startingTimezone = h.get();
if ( !m_startingTimezone.isValid() )
cWarning() << "GeoIP lookup at" << m_geoipUrl << "failed.";
}
else if ( m_geoipStyle == "json" )
{
handler = new GeoIPJSON( m_geoipSelector );
}
#if defined(HAVE_XML)
else if ( m_geoipStyle == "xml" )
{
handler = new GeoIPXML( m_geoipSelector );
}
#endif
else
{
cWarning() << "GeoIP Style" << m_geoipStyle << "is not recognized.";
setUpPage();
return;
}
cDebug() << "Fetching GeoIP data from" << actualUrl;
QNetworkAccessManager *manager = new QNetworkAccessManager( this );
connect( manager, &QNetworkAccessManager::finished,
[=]( QNetworkReply* reply )
{
if ( reply->error() == QNetworkReply::NoError )
{
auto tz = handler->processReply( reply->readAll() );
if ( !tz.first.isEmpty() )
m_startingTimezone = tz;
else
cWarning() << "GeoIP lookup at" << reply->url() << "failed.";
}
delete handler;
reply->deleteLater();
manager->deleteLater();
setUpPage();
} );
QNetworkRequest request;
request.setUrl( QUrl::fromUserInput( actualUrl ) );
request.setAttribute( QNetworkRequest::FollowRedirectsAttribute, true );
manager->get( request );
setUpPage();
}
@ -251,35 +210,43 @@ LocaleViewStep::onLeave()
void
LocaleViewStep::setConfigurationMap( const QVariantMap& configurationMap )
{
if ( configurationMap.contains( "region" ) &&
configurationMap.value( "region" ).type() == QVariant::String &&
!configurationMap.value( "region" ).toString().isEmpty() &&
configurationMap.contains( "zone" ) &&
configurationMap.value( "zone" ).type() == QVariant::String &&
!configurationMap.value( "zone" ).toString().isEmpty() )
QString region = CalamaresUtils::getString( configurationMap, "region" );
QString zone = CalamaresUtils::getString( configurationMap, "zone" );
if ( !region.isEmpty() && !zone.isEmpty() )
{
m_startingTimezone = qMakePair( configurationMap.value( "region" ).toString(),
configurationMap.value( "zone" ).toString() );
m_startingTimezone = CalamaresUtils::GeoIP::RegionZonePair( region, zone );
}
else
{
m_startingTimezone = qMakePair( QStringLiteral( "America" ),
QStringLiteral( "New_York" ) );
m_startingTimezone = CalamaresUtils::GeoIP::RegionZonePair( QStringLiteral( "America" ), QStringLiteral( "New_York" ) );
}
if ( configurationMap.contains( "localeGenPath" ) &&
configurationMap.value( "localeGenPath" ).type() == QVariant::String &&
!configurationMap.value( "localeGenPath" ).toString().isEmpty() )
{
m_localeGenPath = configurationMap.value( "localeGenPath" ).toString();
}
else
{
m_localeGenPath = CalamaresUtils::getString( configurationMap, "localeGenPath" );
if ( m_localeGenPath.isEmpty() )
m_localeGenPath = QStringLiteral( "/etc/locale.gen" );
}
// Optional
m_geoipUrl = CalamaresUtils::getString( configurationMap, "geoipUrl" );
m_geoipStyle = CalamaresUtils::getString( configurationMap, "geoipStyle" );
m_geoipSelector = CalamaresUtils::getString( configurationMap, "geoipSelector" );
bool ok = false;
QVariantMap geoip = CalamaresUtils::getSubMap( configurationMap, "geoip", ok );
if ( ok )
{
m_geoipUrl = CalamaresUtils::getString( geoip, "url" );
m_geoipStyle = CalamaresUtils::getString( geoip, "style" );
m_geoipSelector = CalamaresUtils::getString( geoip, "selector" );
}
else
{
// Optional
m_geoipUrl = CalamaresUtils::getString( configurationMap, "geoipUrl" );
m_geoipStyle = CalamaresUtils::getString( configurationMap, "geoipStyle" );
m_geoipSelector = CalamaresUtils::getString( configurationMap, "geoipSelector" );
if ( !m_geoipUrl.isEmpty() && ( m_geoipStyle.isEmpty() || m_geoipStyle == "legacy" ) )
{
m_geoipStyle = "json";
m_geoipUrl.append( "/json/" );
}
if ( !m_geoipUrl.isEmpty() )
cWarning() << "Legacy-style GeoIP configuration is deprecated. Use geoip: map.";
}
}

View File

@ -20,14 +20,14 @@
#ifndef LOCALEVIEWSTEP_H
#define LOCALEVIEWSTEP_H
#include <QObject>
#include "geoip/Interface.h"
#include "utils/PluginFactory.h"
#include "viewpages/ViewStep.h"
#include <utils/PluginFactory.h>
#include <viewpages/ViewStep.h>
#include <PluginDllMacro.h>
#include "PluginDllMacro.h"
#include <QFutureWatcher>
#include <QObject>
class LocalePage;
class WaitingWidget;
@ -71,7 +71,7 @@ private:
bool m_nextEnabled;
QString m_prettyStatus;
QPair< QString, QString > m_startingTimezone;
CalamaresUtils::GeoIP::RegionZonePair m_startingTimezone;
QString m_localeGenPath;
QString m_geoipUrl; // The URL, depening on style might be modified on lookup

View File

@ -25,21 +25,46 @@ zone: "New_York"
# custom path for locale.gen
#localeGenPath: "PATH_TO/locale.gen"
# GeoIP based Language settings:
# GeoIP based Language settings: Leave commented out to disable GeoIP.
#
# GeoIP need an working Internet connection.
# GeoIP needs a working Internet connection.
# This can be managed from `welcome.conf` by adding
# internet to the list of required conditions.
#
# Leave commented out to disable GeoIP.
# The configuration
# is in three parts: a *style*, which can be "json" or "xml"
# depending on the kind of data returned by the service, and
# a *url* where the data is retrieved, and an optional *selector*
# to pick the right field out of the returned data (e.g. field
# name in JSON or element name in XML).
#
# An HTTP request is made to *geoipUrl* -- depending on the geoipStyle,
# the URL may be modified before use. The request should return
# valid data in a suitable format, depending on geoipStyle;
# The default selector (when the setting is blank) is picked to
# work with existing JSON providers (which use "time_zone") and
# Ubiquity's XML providers (which use "TimeZone").
#
# If the service configured via *url* uses
# a different attribute name (e.g. "timezone") in JSON or a
# different element tag (e.g. "<Time_Zone>") in XML, set this
# string to the name or tag to be used.
#
# In JSON:
# - if the string contains "." characters, this is used as a
# multi-level selector, e.g. "a.b" will select the timezone
# from data "{a: {b: "Europe/Amsterdam" } }".
# - each part of the string split by "." characters is used as
# a key into the JSON data.
# In XML:
# - all elements with the named tag (e.g. all TimeZone) elements
# from the document are checked; the first one with non-empty
# text value is used.
#
#
# An HTTP(S) request is made to *url*. The request should return
# valid data in a suitable format, depending on *style*;
# generally this includes a string value with the timezone
# in <region>/<zone> format. For services that return data which
# does not follow the conventions of "suitable data" described
# below, *geoIPSelector* may be used to pick different data.
# below, *selector* may be used to pick different data.
#
# Note that this example URL works, but the service is shutting
# down in June 2018.
@ -58,40 +83,9 @@ zone: "New_York"
# - backslashes are removed
# - spaces are replaced with _
#
#geoipUrl: "freegeoip.net"
# GeoIP style. Leave commented out for the "legacy" interpretation.
# This setting only makes sense if geoipUrl is set, enabliing geoIP.
#
# Possible values are:
# unset same as "legacy"
# blank same as "legacy"
# "legacy" appends "/json" to geoipUrl, above, and uses JSON format
# (which is what freegeoip.net provides there).
# "json" URL is not modified, uses JSON format.
# "xml" URL is not modified, uses XML format.
#
# The JSON format is provided by freegeoip.net, but that service is
# shutting down in June 2018. There are other providers with the same
# format. XML format is provided for Ubiquity.
#geoipStyle: "legacy"
# GeoIP selector. Leave commented out for the default selector
# (which depends on the style: JSON uses "time_zone" and XML
# uses TimeZone, for the FreeGeoIP-alike and the Ubiquity-alike
# respectively). If the service configured via *geoipUrl* uses
# a different attribute name (e.g. "timezone") in JSON or a
# different element tag (e.g. "<Time_Zone>") in XML, set this
# string to the name or tag to be used.
#
# In JSON:
# - if the string contains "." characters, this is used as a
# multi-level selector, e.g. "a.b" will select the timezone
# from data "{a: {b: "Europe/Amsterdam" } }".
# - each part of the string split by "." characters is used as
# a key into the JSON data.
# In XML:
# - all elements with the named tag (e.g. all TimeZone) elements
# from the document are checked; the first one with non-empty
# text value is used.
#geoipSelector: ""
# Legacy settings "geoipStyle", "geoipUrl" and "geoipSelector"
# in the top-level are still supported, but I'd advise against.
geoip:
style: "json"
url: "https://geoip.kde.org/v1/calamares"
selector: "" # leave blank for the default

View File

@ -27,7 +27,6 @@ calamares_add_plugin( welcome
EXPORT_MACRO PLUGINDLLEXPORT_PRO
SOURCES
${CHECKER_SOURCES}
LocaleModel.cpp
WelcomeViewStep.cpp
WelcomePage.cpp
UI

View File

@ -21,16 +21,16 @@
#include "WelcomePage.h"
#include "ui_WelcomePage.h"
#include "LocaleModel.h"
#include "checker/CheckerContainer.h"
#include "Branding.h"
#include "CalamaresVersion.h"
#include "Settings.h"
#include "ViewManager.h"
#include "locale/LabelModel.h"
#include "modulesystem/ModuleManager.h"
#include "utils/CalamaresUtilsGui.h"
#include "utils/LocaleLabel.h"
#include "utils/Logger.h"
#include "utils/Retranslator.h"
@ -131,7 +131,7 @@ WelcomePage::initLanguages()
ui->languageWidget->clear();
ui->languageWidget->setInsertPolicy( QComboBox::InsertAtBottom );
m_languages = new LocaleModel( QString( CALAMARES_TRANSLATION_LANGUAGES ).split( ';') );
m_languages = CalamaresUtils::Locale::availableTranslations();
ui->languageWidget->setModel( m_languages );
ui->languageWidget->setItemDelegate( new LocaleTwoColumnDelegate( ui->languageWidget ) );
@ -257,7 +257,23 @@ WelcomePage::focusInEvent( QFocusEvent* e )
e->accept();
}
bool WelcomePage::verdict() const
bool
WelcomePage::verdict() const
{
return m_checkingWidget->verdict();
}
void
WelcomePage::externallySelectedLanguage( int row )
{
if ( ( row >= 0 ) && ( row < ui->languageWidget->count() ) )
ui->languageWidget->setCurrentIndex( row );
}
void
LocaleTwoColumnDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
QStyledItemDelegate::paint( painter, option, index );
option.widget->style()->drawItemText( painter, option.rect, Qt::AlignRight | Qt::AlignVCenter, option.palette, false, index.data( CalamaresUtils::Locale::LabelModel::EnglishLabelRole ).toString() );
}

View File

@ -20,6 +20,9 @@
#ifndef WELCOMEPAGE_H
#define WELCOMEPAGE_H
#include "locale/LabelModel.h"
#include <QStyledItemDelegate>
#include <QWidget>
namespace Ui
@ -28,7 +31,6 @@ class WelcomePage;
}
class CheckerContainer;
class LocaleModel;
class WelcomePage : public QWidget
{
@ -44,6 +46,8 @@ public:
/// @brief Results of requirements checking
bool verdict() const;
/// @brief Change the language from an external source.
void externallySelectedLanguage( int row );
protected:
void focusInEvent( QFocusEvent* e ) override; //choose the child widget to focus
@ -53,7 +57,19 @@ private:
Ui::WelcomePage* ui;
CheckerContainer* m_checkingWidget;
LocaleModel *m_languages;
CalamaresUtils::Locale::LabelModel *m_languages;
};
/** @brief Delegate to display language information in two columns.
*
* Displays the native language name and the English language name.
*/
class LocaleTwoColumnDelegate : public QStyledItemDelegate
{
public:
using QStyledItemDelegate::QStyledItemDelegate;
void paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const override;
} ;
#endif // WELCOMEPAGE_H

View File

@ -22,9 +22,13 @@
#include "WelcomePage.h"
#include "checker/GeneralRequirements.h"
#include "geoip/Handler.h"
#include "locale/Lookup.h"
#include "modulesystem/ModuleManager.h"
#include "utils/Logger.h"
#include "utils/Variant.h"
#include <QFutureWatcher>
#include <QVariant>
CALAMARES_PLUGIN_FACTORY_DEFINITION( WelcomeViewStepFactory, registerPlugin<WelcomeViewStep>(); )
@ -97,18 +101,9 @@ WelcomeViewStep::jobs() const
void
WelcomeViewStep::setConfigurationMap( const QVariantMap& configurationMap )
{
bool showSupportUrl =
configurationMap.contains( "showSupportUrl" ) &&
configurationMap.value( "showSupportUrl" ).type() == QVariant::Bool &&
configurationMap.value( "showSupportUrl" ).toBool();
bool showKnownIssuesUrl =
configurationMap.contains( "showKnownIssuesUrl" ) &&
configurationMap.value( "showKnownIssuesUrl" ).type() == QVariant::Bool &&
configurationMap.value( "showKnownIssuesUrl" ).toBool();
bool showReleaseNotesUrl =
configurationMap.contains( "showReleaseNotesUrl" ) &&
configurationMap.value( "showReleaseNotesUrl" ).type() == QVariant::Bool &&
configurationMap.value( "showReleaseNotesUrl" ).toBool();
bool showSupportUrl = CalamaresUtils::getBool( configurationMap, "showSupportUrl", false );
bool showKnownIssuesUrl = CalamaresUtils::getBool( configurationMap, "showKnownIssuesUrl", false );
bool showReleaseNotesUrl = CalamaresUtils::getBool( configurationMap, "showReleaseNotesUrl", false );
m_widget->setUpLinks( showSupportUrl,
showKnownIssuesUrl,
@ -120,9 +115,57 @@ WelcomeViewStep::setConfigurationMap( const QVariantMap& configurationMap )
else
cWarning() << "no valid requirements map found in welcome "
"module configuration.";
bool ok = false;
QVariantMap geoip = CalamaresUtils::getSubMap( configurationMap, "geoip", ok );
if ( ok )
{
using FWString = QFutureWatcher< QString >;
auto* handler = new CalamaresUtils::GeoIP::Handler(
CalamaresUtils::getString( geoip, "style" ),
CalamaresUtils::getString( geoip, "url" ),
CalamaresUtils::getString( geoip, "selector" ) );
auto* future = new FWString();
connect( future, &FWString::finished, [view=this, f=future, h=handler]()
{
QString countryResult = f->future().result();
cDebug() << "GeoIP result for welcome=" << countryResult;
view->setCountry( countryResult );
f->deleteLater();
delete h;
} );
future->setFuture( handler->queryRaw() );
}
}
Calamares::RequirementsList WelcomeViewStep::checkRequirements()
Calamares::RequirementsList
WelcomeViewStep::checkRequirements()
{
return m_requirementsChecker->checkRequirements();
}
void
WelcomeViewStep::setCountry( const QString& countryCode )
{
if ( countryCode.length() != 2 )
{
cDebug() << "Unusable country code" << countryCode;
return;
}
auto c_l = CalamaresUtils::Locale::countryData( countryCode );
if ( c_l.first == QLocale::Country::AnyCountry )
{
cDebug() << "Unusable country code" << countryCode;
return;
}
else
{
int r = CalamaresUtils::Locale::availableTranslations()->find( countryCode );
if ( r < 0 )
cDebug() << "Unusable country code" << countryCode << "(no suitable translation)";
if ( ( r >= 0 ) && m_widget )
m_widget->externallySelectedLanguage( r );
}
}

View File

@ -54,6 +54,13 @@ public:
void setConfigurationMap( const QVariantMap& configurationMap ) override;
/** @brief Sets the country that Calamares is running in.
*
* This (ideally) sets up language and locale settings that are right for
* the given 2-letter country code.
*/
void setCountry( const QString& );
Calamares::RequirementsList checkRequirements() override;
private:

View File

@ -47,3 +47,16 @@ requirements:
# - storage
- ram
# - root
# GeoIP checking
#
# This can be used to pre-select a language based on the country
# the user is currently in. It *assumes* that there's internet
# connectivity, though. Configuration is like in the locale module,
# but remember to use a URL that returns full data **and** to
# use a selector that will pick the country, not the timezone.
geoip:
style: "xml"
url: "https://geoip.kde.org/v1/ubiquity" # extended XML format
selector: "CountryCode" # blank uses default, which is wrong