Make QCalendarBackend's ID an opaque type
As Giuseppe pointed out in API change review, an opaque type should be used. Task-number: QTBUG-94407 Pick-to: 6.2 Change-Id: I862a6f52d284317e1243fd91f45bb0af130d154a Reviewed-by: Giuseppe D'Angelo <giuseppe.dangelo@kdab.com>bb10
parent
d115f17cb8
commit
1c547698ac
|
|
@ -1,6 +1,6 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2020 The Qt Company Ltd.
|
||||
** Copyright (C) 2021 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the QtCore module of the Qt Toolkit.
|
||||
|
|
@ -81,7 +81,6 @@ inline size_t qHash(const CalendarName &key, size_t seed = 0) noexcept
|
|||
return qHash(key.toLower(), seed);
|
||||
}
|
||||
|
||||
constexpr size_t invalidBackendId = ~size_t(0);
|
||||
static QBasicMutex registryMutex; // Protects registry from concurrent access
|
||||
|
||||
struct Registry
|
||||
|
|
@ -106,7 +105,7 @@ struct Registry
|
|||
bool registerName(QCalendarBackend *calendar, const QString &name)
|
||||
{
|
||||
Q_ASSERT(!name.isEmpty());
|
||||
if (status.loadRelaxed() > 1 || name.isEmpty() || calendar->calendarId() == invalidBackendId)
|
||||
if (status.loadRelaxed() > 1 || name.isEmpty() || !calendar->calendarId().isValid())
|
||||
return false;
|
||||
const auto lock = qt_scoped_lock(registryMutex);
|
||||
|
||||
|
|
@ -127,18 +126,18 @@ struct Registry
|
|||
If \a id is QCalendar::System::User, a new unique ID is allocated;
|
||||
otherwise, \a id must be a member of the QCalendar::System enumeration
|
||||
and the \a calendar is registered with that as its ID unless this ID is
|
||||
already in use, in which case registration fails and \c{~size_t(0)} is
|
||||
returned. In (only) this last case, the caller is responsible for
|
||||
destruction. Otherwise, registry destruction will take care of it on
|
||||
program exit.
|
||||
already in use, in which case registration fails and
|
||||
\c{~QCalendar::SystemId(0)} is returned. In (only) this last case, the
|
||||
caller is responsible for destruction. Otherwise, registry destruction
|
||||
will take care of it on program exit.
|
||||
*/
|
||||
size_t registerId(QCalendarBackend *calendar, QCalendar::System system)
|
||||
{
|
||||
if (status.loadRelaxed() > 1)
|
||||
return invalidBackendId;
|
||||
return QCalendar::SystemId().index();
|
||||
const auto lock = qt_scoped_lock(registryMutex);
|
||||
|
||||
size_t index = size_t(system);
|
||||
auto index = size_t(system);
|
||||
if (system == QCalendar::System::User) {
|
||||
index = byId.size();
|
||||
byId.push_back(calendar);
|
||||
|
|
@ -148,7 +147,7 @@ struct Registry
|
|||
Q_ASSERT(byId[index] == nullptr);
|
||||
byId[index] = calendar;
|
||||
} else {
|
||||
return invalidBackendId;
|
||||
return QCalendar::SystemId().index();
|
||||
}
|
||||
if (system == QCalendar::System::Gregorian) {
|
||||
// Only the first QGregorianCalendar can get here, so this should succeed:
|
||||
|
|
@ -173,7 +172,7 @@ struct Registry
|
|||
return;
|
||||
const auto lock = qt_scoped_lock(registryMutex);
|
||||
|
||||
const size_t id = calendar->calendarId();
|
||||
const size_t id = calendar->calendarId().index();
|
||||
if (~id && byId[id] == calendar)
|
||||
byId[id] = nullptr; // potentially purge nullptr entries from tail of byId[Last+1:] ?
|
||||
for (auto i = byName.begin(); i != byName.end(); ) {
|
||||
|
|
@ -250,7 +249,7 @@ static const QCalendarBackend *backendFromEnum(QCalendar::System system)
|
|||
if (!backend)
|
||||
return backend;
|
||||
// Check for successful registration:
|
||||
if (~backend->calendarId()) {
|
||||
if (backend->calendarId().isValid()) {
|
||||
#if defined(QT_FORCE_ASSERTS) || !defined(QT_NO_DEBUG)
|
||||
const auto lock = qt_scoped_lock(registryMutex);
|
||||
Q_ASSERT(backend == calendarRegistry->byId[size_t(system)]);
|
||||
|
|
@ -326,8 +325,8 @@ static const QCalendarBackend *backendFromEnum(QCalendar::System system)
|
|||
the registry in which QCalendar records backends.
|
||||
|
||||
The single exception to this is that each backend's instantiator should
|
||||
verify that it was registered successfully. If its calendarId() is
|
||||
\c{~size_t(0)}, it has not been registered and attempts to register aliases
|
||||
verify that it was registered successfully. If its calendarId()'s isValid()
|
||||
is \c{false}, it has not been registered and attempts to register aliases
|
||||
for it shall fail. In such a case, the instantiator retains ownership of the
|
||||
backend instance; it will not be accessible to QCalendar. Note that (since
|
||||
Qt 6.1) a backend may be successfully registered by ID without being
|
||||
|
|
@ -339,8 +338,8 @@ static const QCalendarBackend *backendFromEnum(QCalendar::System system)
|
|||
other code should ever instantiate one. As noted above, such a backend must
|
||||
be a singleton. Its constructor passes down the \c enum member that
|
||||
identifies it as \c system to the base-class constructor; this is also its
|
||||
calendarSystem(). Its unique ID (see \l calendarId()) shall be the \c size_t
|
||||
equivalent to this \c system value.
|
||||
calendarSystem(). Its unique ID (see \l calendarId()) shall be the \c
|
||||
QCalendar::SystemId equivalent to this \c system value.
|
||||
|
||||
The shareable base-classes for backends, QRomanCalendar and QHijriCalendar,
|
||||
are not themselves identified by QCalendar::System and may be used as
|
||||
|
|
@ -357,16 +356,16 @@ static const QCalendarBackend *backendFromEnum(QCalendar::System system)
|
|||
On successful registration, the calendar backend registry takes over
|
||||
ownership of the instance and shall delete it on program exit in the course
|
||||
of the registry's own destruction. The instance can determine whether it was
|
||||
successfully registered by testing \c{~calendarId()}: this shall be zero
|
||||
precisely if registration failed. QCalendar cannot use an unregistered
|
||||
successfully registered by testing \c{calendarId().isValid()}: this shall be
|
||||
zero precisely if registration failed. QCalendar cannot use an unregistered
|
||||
backend and the code that instantiated it is responsible for deleting it.
|
||||
|
||||
The \a system is optional and should only be passed by built-in
|
||||
implementations of the standard calendars documented in \l
|
||||
QCalendar::System. When \a system is passed, unless a backend is already
|
||||
registered for this \c enum value, the backend is registered with
|
||||
\c{size_t(system)} as its ID. Custom backends should not pass \a
|
||||
system. When it is not passed, the backend is allocated a unique calendar
|
||||
\c{QCalendar::SystemId(system)} as its ID. Custom backends should not pass
|
||||
\a system. When it is not passed, the backend is allocated a unique calendar
|
||||
ID, which shall be accessible as \l calendarId(). This ID can (since Qt 6.1)
|
||||
be used to instantiate a QCalendar using the given backend, avoiding
|
||||
problems with potential clashes of name or alias.
|
||||
|
|
@ -419,7 +418,7 @@ QCalendarBackend::QCalendarBackend(const QString &name, QCalendar::System system
|
|||
if (!name.isEmpty())
|
||||
calendarRegistry->registerName(this, name);
|
||||
} else {
|
||||
Q_ASSERT(size_t(system) <= size_t(QCalendar::System::Last));
|
||||
Q_ASSERT(QCalendar::SystemId(system).isInEnum());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -432,8 +431,8 @@ QCalendarBackend::QCalendarBackend(const QString &name, QCalendar::System system
|
|||
successfully-registered backend otherwise may leave existing QCalendar
|
||||
instances referencing the destroyed calendar, with undefined results.
|
||||
|
||||
However, if a backend, when instantiated, has calendarId() equal to
|
||||
\c{~size_t(0)}, it has not been registered and cannot be accessed by
|
||||
However, if a backend, when instantiated, has a calendarId() whose \c
|
||||
isValid() is \c{false}, it has not been registered and cannot be accessed by
|
||||
QCalendar. Code that instantiates backends must check for this and delete
|
||||
the unusable backend object, as it shall be leaked otherwise. This is the
|
||||
only case in which client code needs to delete a backend.
|
||||
|
|
@ -447,16 +446,16 @@ QCalendarBackend::QCalendarBackend(const QString &name, QCalendar::System system
|
|||
QCalendarBackend::~QCalendarBackend()
|
||||
{
|
||||
// No registration to undo unless registry exists and gave this a valid ID:
|
||||
if (calendarRegistry.exists() && ~calendarId())
|
||||
if (calendarRegistry.exists() && calendarId().isValid())
|
||||
calendarRegistry->unRegister(this);
|
||||
}
|
||||
|
||||
/*!
|
||||
\since 6.2
|
||||
\fn size_t QCalendarBackend::calendarId() const
|
||||
\fn QCalendar::SystemId QCalendarBackend::calendarId() const
|
||||
|
||||
Each backend is allocated an ID when successfully registered as part of
|
||||
instantiation. A backend for which calendarId() is \c{~size_t(0)} has not
|
||||
instantiation. A backend whose calendarId() has isValid() \c{false} has not
|
||||
been successfully registered and the code that created it must take care to
|
||||
ensure it is deleted; it also cannot be used, as it is not known to any of
|
||||
the available ways to create a QCalendar.
|
||||
|
|
@ -478,9 +477,7 @@ QCalendarBackend::~QCalendarBackend()
|
|||
*/
|
||||
QCalendar::System QCalendarBackend::calendarSystem() const
|
||||
{
|
||||
if (m_id > size_t(QCalendar::System::Last))
|
||||
return QCalendar::System::User;
|
||||
return QCalendar::System(m_id);
|
||||
return m_id.isInEnum() ? QCalendar::System(m_id.index()) : QCalendar::System::User;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
|
@ -929,14 +926,15 @@ const QCalendarBackend *QCalendarBackend::fromName(QLatin1String name)
|
|||
|
||||
\sa fromEnum(), calendarId()
|
||||
*/
|
||||
const QCalendarBackend *QCalendarBackend::fromId(size_t id)
|
||||
const QCalendarBackend *QCalendarBackend::fromId(QCalendar::SystemId id)
|
||||
{
|
||||
if (calendarRegistry.isDestroyed() || !~id)
|
||||
if (calendarRegistry.isDestroyed() || !id.isValid())
|
||||
return nullptr;
|
||||
if (auto *c = id < calendarRegistry->byId.size() ? calendarRegistry->byId[id] : nullptr)
|
||||
const size_t i = id.index();
|
||||
if (auto *c = i < calendarRegistry->byId.size() ? calendarRegistry->byId[i] : nullptr)
|
||||
return c;
|
||||
if (id <= size_t(QCalendar::System::Last))
|
||||
return fromEnum(QCalendar::System(id));
|
||||
if (id.isInEnum())
|
||||
return fromEnum(QCalendar::System(id.index()));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
|
@ -952,20 +950,20 @@ const QCalendarBackend *QCalendarBackend::fromId(size_t id)
|
|||
*/
|
||||
const QCalendarBackend *QCalendarBackend::fromEnum(QCalendar::System system)
|
||||
{
|
||||
Q_ASSERT(system == QCalendar::System::User
|
||||
|| size_t(system) <= size_t(QCalendar::System::Last));
|
||||
const auto systemId = QCalendar::SystemId(system);
|
||||
Q_ASSERT(system == QCalendar::System::User || systemId.isInEnum());
|
||||
if (calendarRegistry.isDestroyed() || system == QCalendar::System::User)
|
||||
return nullptr;
|
||||
{
|
||||
const auto registryLock = qt_scoped_lock(registryMutex);
|
||||
Q_ASSERT(calendarRegistry->byId.size() > size_t(system));
|
||||
if (auto *c = calendarRegistry->byId[size_t(system)])
|
||||
Q_ASSERT(calendarRegistry->byId.size() > systemId.index());
|
||||
if (auto *c = calendarRegistry->byId[systemId.index()])
|
||||
return c;
|
||||
}
|
||||
if (auto *result = backendFromEnum(system))
|
||||
return result;
|
||||
const auto registryLock = qt_scoped_lock(registryMutex);
|
||||
return calendarRegistry->byId[size_t(system)];
|
||||
return calendarRegistry->byId[systemId.index()];
|
||||
}
|
||||
|
||||
/*!
|
||||
|
|
@ -1008,9 +1006,46 @@ const QCalendarBackend *QCalendarBackend::fromEnum(QCalendar::System system)
|
|||
\omitvalue Last
|
||||
\omitvalue User
|
||||
|
||||
\sa QCalendar, QCalendar::SystemId
|
||||
*/
|
||||
|
||||
/*!
|
||||
\class QCalendar::SystemId
|
||||
\since 6.2
|
||||
|
||||
This is an opaque type used to identify custom calendar implementations. The
|
||||
only supported source for values of this type is the backend's \c
|
||||
calendarId() method. A value of this type whose isValid() is false does not
|
||||
identify a successfully-registered backend. The only valid consumer of
|
||||
values of this type is a QCalendar constructor, which will only produce a
|
||||
valid QCalendar instance if the ID passed to it is valid.
|
||||
|
||||
\sa QCalendar, QCalendar::System
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn QCalendar::SystemId::isValid()
|
||||
|
||||
Returns true if this is a valid calendar implementation identifier, else
|
||||
false.
|
||||
|
||||
\sa QCalendar
|
||||
*/
|
||||
|
||||
/*!
|
||||
\internal
|
||||
\fn QCalendar::SystemId::SystemId()
|
||||
|
||||
Constructs an invalid calendar system identifier.
|
||||
*/
|
||||
|
||||
/*!
|
||||
\internal
|
||||
\fn QCalendar::SystemId::index()
|
||||
|
||||
Returns the internal representation of the identifier.
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn QCalendar::QCalendar()
|
||||
\fn QCalendar::QCalendar(QCalendar::System system)
|
||||
|
|
@ -1040,7 +1075,7 @@ QCalendar::QCalendar()
|
|||
delete fresh;
|
||||
Q_ASSERT(d);
|
||||
}
|
||||
Q_ASSERT(~d->calendarId());
|
||||
Q_ASSERT(d->calendarId().isValid());
|
||||
}
|
||||
|
||||
QCalendar::QCalendar(QCalendar::System system)
|
||||
|
|
@ -1048,7 +1083,7 @@ QCalendar::QCalendar(QCalendar::System system)
|
|||
{
|
||||
// If system is valid, we should get a valid d for that system.
|
||||
Q_ASSERT(uint(system) > uint(QCalendar::System::Last)
|
||||
|| (d && d->calendarId() == size_t(system)));
|
||||
|| (d && d->calendarId().index() == size_t(system)));
|
||||
}
|
||||
|
||||
/*!
|
||||
|
|
@ -1062,22 +1097,22 @@ QCalendar::QCalendar(QCalendar::System system)
|
|||
QCalendar using that backend. This can be useful when the backend is not
|
||||
registered by name.
|
||||
*/
|
||||
QCalendar::QCalendar(size_t id)
|
||||
QCalendar::QCalendar(QCalendar::SystemId id)
|
||||
: d(QCalendarBackend::fromId(id))
|
||||
{
|
||||
Q_ASSERT(!d || d->calendarId() == id);
|
||||
Q_ASSERT(!d || d->calendarId().index() == id.index());
|
||||
}
|
||||
|
||||
QCalendar::QCalendar(QLatin1String name)
|
||||
: d(QCalendarBackend::fromName(name))
|
||||
{
|
||||
Q_ASSERT(!d || ~d->calendarId());
|
||||
Q_ASSERT(!d || d->calendarId().isValid());
|
||||
}
|
||||
|
||||
QCalendar::QCalendar(QStringView name)
|
||||
: d(QCalendarBackend::fromName(name))
|
||||
{
|
||||
Q_ASSERT(!d || ~d->calendarId());
|
||||
Q_ASSERT(!d || d->calendarId().isValid());
|
||||
}
|
||||
|
||||
/*!
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2020 The Qt Company Ltd.
|
||||
** Copyright (C) 2021 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the QtCore module of the Qt Toolkit.
|
||||
|
|
@ -130,12 +130,24 @@ public:
|
|||
// New entries must be added to the \enum doc in qcalendar.cpp and
|
||||
// handled in QCalendarBackend::fromEnum()
|
||||
Q_ENUM(System)
|
||||
class SystemId
|
||||
{
|
||||
const size_t id;
|
||||
friend class QCalendarBackend;
|
||||
constexpr bool isInEnum() const { return id <= size_t(QCalendar::System::Last); }
|
||||
constexpr SystemId(QCalendar::System e) : id(size_t(e)) {}
|
||||
constexpr SystemId(size_t i) : id(i) {}
|
||||
public:
|
||||
constexpr SystemId() : id(~size_t(0)) {}
|
||||
constexpr size_t index() const noexcept { return id; }
|
||||
constexpr bool isValid() const noexcept { return ~id; }
|
||||
};
|
||||
|
||||
explicit QCalendar(); // Gregorian, optimised
|
||||
explicit QCalendar(System system);
|
||||
explicit QCalendar(QLatin1String name);
|
||||
explicit QCalendar(QStringView name);
|
||||
explicit QCalendar(size_t id);
|
||||
explicit QCalendar(SystemId id);
|
||||
|
||||
// QCalendar is a trivially copyable value type.
|
||||
bool isValid() const { return d != nullptr; }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2020 The Qt Company Ltd.
|
||||
** Copyright (C) 2021 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the QtCore module of the Qt Toolkit.
|
||||
|
|
@ -93,7 +93,7 @@ public:
|
|||
virtual ~QCalendarBackend();
|
||||
virtual QString name() const = 0;
|
||||
QCalendar::System calendarSystem() const;
|
||||
size_t calendarId() const { return m_id; }
|
||||
QCalendar::SystemId calendarId() const { return m_id; }
|
||||
// Date queries:
|
||||
virtual int daysInMonth(int month, int year = QCalendar::Unspecified) const = 0;
|
||||
virtual int daysInYear(int year) const;
|
||||
|
|
@ -143,11 +143,11 @@ protected:
|
|||
bool registerAlias(const QString &name);
|
||||
|
||||
private:
|
||||
const size_t m_id = ~size_t(0);
|
||||
const QCalendar::SystemId m_id;
|
||||
// QCalendar's access to its registry:
|
||||
static const QCalendarBackend *fromName(QStringView name);
|
||||
static const QCalendarBackend *fromName(QLatin1String name);
|
||||
static const QCalendarBackend *fromId(size_t id);
|
||||
static const QCalendarBackend *fromId(QCalendar::SystemId id);
|
||||
// QCalendar's access to singletons:
|
||||
static const QCalendarBackend *fromEnum(QCalendar::System system);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ using namespace QRoundingDown;
|
|||
QGregorianCalendar::QGregorianCalendar()
|
||||
: QRomanCalendar(QStringLiteral("Gregorian"), QCalendar::System::Gregorian)
|
||||
{
|
||||
if (~calendarId()) {
|
||||
if (calendarId().isValid()) {
|
||||
Q_ASSERT(calendarSystem() == QCalendar::System::Gregorian);
|
||||
registerAlias(QStringLiteral("gregory"));
|
||||
} // else: being ignored in favor of a duplicate created at the same time
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2020 The Qt Company Ltd.
|
||||
** Copyright (C) 2021 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the QtCore module of the Qt Toolkit.
|
||||
|
|
@ -75,7 +75,7 @@ using namespace QRoundingDown;
|
|||
QIslamicCivilCalendar::QIslamicCivilCalendar()
|
||||
: QHijriCalendar(QStringLiteral("Islamic Civil"), QCalendar::System::IslamicCivil)
|
||||
{
|
||||
if (~calendarId()) {
|
||||
if (calendarId().isValid()) {
|
||||
Q_ASSERT(calendarSystem() == QCalendar::System::IslamicCivil);
|
||||
registerAlias(QStringLiteral("islamic-civil")); // CLDR name
|
||||
registerAlias(QStringLiteral("islamicc")); // old CLDR name, still (2018) used by Mozilla
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2020 The Qt Company Ltd.
|
||||
** Copyright (C) 2021 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the QtCore module of the Qt Toolkit.
|
||||
|
|
@ -116,7 +116,7 @@ qint64 firstDayOfYear(int year, int cycleNo)
|
|||
QJalaliCalendar::QJalaliCalendar()
|
||||
: QCalendarBackend(QStringLiteral("Jalali"), QCalendar::System::Jalali)
|
||||
{
|
||||
if (~calendarId()) {
|
||||
if (calendarId().isValid()) {
|
||||
Q_ASSERT(calendarSystem() == QCalendar::System::Jalali);
|
||||
registerAlias(QStringLiteral("Persian"));
|
||||
} // else: being ignored in favor of a duplicate created at the same time
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2020 The Qt Company Ltd.
|
||||
** Copyright (C) 2021 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the QtCore module of the Qt Toolkit.
|
||||
|
|
@ -78,7 +78,7 @@ using namespace QRoundingDown;
|
|||
QJulianCalendar::QJulianCalendar()
|
||||
: QRomanCalendar(QStringLiteral("Julian"), QCalendar::System::Julian)
|
||||
{
|
||||
Q_ASSERT(calendarSystem() == QCalendar::System::Julian || !~calendarId());
|
||||
Q_ASSERT(calendarSystem() == QCalendar::System::Julian || !calendarId().isValid());
|
||||
}
|
||||
|
||||
QString QJulianCalendar::name() const
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2020 The Qt Company Ltd.
|
||||
** Copyright (C) 2021 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the QtCore module of the Qt Toolkit.
|
||||
|
|
@ -76,7 +76,7 @@ using namespace QRoundingDown;
|
|||
QMilankovicCalendar::QMilankovicCalendar()
|
||||
: QRomanCalendar(QStringLiteral("Milankovic"), QCalendar::System::Milankovic)
|
||||
{
|
||||
Q_ASSERT(calendarSystem() == QCalendar::System::Milankovic || !~calendarId());
|
||||
Q_ASSERT(calendarSystem() == QCalendar::System::Milankovic || !calendarId().isValid());
|
||||
}
|
||||
|
||||
QString QMilankovicCalendar::name() const
|
||||
|
|
|
|||
Loading…
Reference in New Issue