swift
user.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2013 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
4 #include "misc/network/user.h"
5 
6 #include <QChar>
7 #include <QRegularExpression>
8 #include <QRegularExpressionMatch>
9 #include <QStringBuilder>
10 #include <Qt>
11 #include <QtGlobal>
12 
14 #include "misc/comparefunctions.h"
15 #include "misc/obfuscation.h"
16 #include "misc/propertyindexref.h"
17 #include "misc/propertyindexvariantmap.h" // needed for mixin::Index::apply
18 #include "misc/statusmessage.h"
19 #include "misc/stringutils.h"
20 
21 using namespace swift::misc::aviation;
22 
23 SWIFT_DEFINE_VALUEOBJECT_MIXINS(swift::misc::network, CUser)
24 
25 namespace swift::misc::network
26 {
27  CUser::CUser(const CCallsign &callsign) : m_callsign(callsign) { this->deriveHomeBaseFromCallsign(); }
28 
29  CUser::CUser(const QString &id, const QString &realname, const CCallsign &callsign)
30  : m_id(CObfuscation::decode(id)), m_realname(CObfuscation::decode(realname)), m_callsign(callsign)
31  {
32  this->deriveHomeBaseFromCallsign();
33  this->setRealName(m_realname); // extracts homebase if this is included in real name
34  }
35 
36  CUser::CUser(const QString &id, const QString &realname, const QString &email, const QString &password,
37  const CCallsign &callsign)
38  : m_id(CObfuscation::decode(id)), m_realname(CObfuscation::decode(realname)),
39  m_email(CObfuscation::decode(email)), m_password(CObfuscation::decode(password)), m_callsign(callsign)
40  {
41  this->deriveHomeBaseFromCallsign();
42  this->setRealName(m_realname); // extracts homebase
43  }
44 
45  QString CUser::getRealNameAndHomeBase(const QString &separator) const
46  {
47  if (!this->hasHomeBase()) { return this->getRealName(); }
48  if (!this->hasRealName()) { return this->getHomeBase().asString(); }
49  return this->getRealName() % separator % this->getHomeBase().asString();
50  }
51 
52  QString CUser::getRealNameAndId() const
53  {
54  if (this->hasRealName())
55  {
56  if (this->hasId()) { return this->getRealName() % u" (" % this->getId() % QStringLiteral(")"); }
57  return this->getRealName();
58  }
59  return this->getId();
60  }
61 
62  bool CUser::setCallsign(const CCallsign &callsign)
63  {
64  if (m_callsign == callsign) { return false; }
65  m_callsign = callsign;
66  this->deriveHomeBaseFromCallsign();
67  return true;
68  }
69 
70  QString CUser::convertToQString(bool i18n) const
71  {
72  Q_UNUSED(i18n)
73  if (m_realname.isEmpty()) return QStringLiteral("<no realname>");
74  QString s = m_realname;
75  if (this->hasId()) { s += u" (" % m_id % u')'; }
76  if (this->hasCallsign()) { s += u' ' % this->getCallsign().getStringAsSet(); }
77  return s;
78  }
79 
80  void CUser::deriveHomeBaseFromCallsign()
81  {
82  if (m_callsign.isEmpty()) { return; }
83  if (m_homebase.isEmpty())
84  {
85  if (m_callsign.isAtcCallsign())
86  {
87  // option 1: real ATC station
88  // option 2: a copilot cmoning in Observer
89  if (m_callsign.isObserverCallsign())
90  {
91  // copilot
92  }
93  else { m_homebase = m_callsign.getIcaoCode(); }
94  }
95  }
96  }
97 
98  void CUser::setRealName(const QString &realname)
99  {
100  QString rn(simplifyAccents(CObfuscation::decode(realname).simplified()));
101  if (rn.isEmpty())
102  {
103  m_realname.clear();
104  return;
105  }
106 
107  if (!this->hasHomeBase())
108  {
109  // only apply stripping if home base is not explicitly given
110  // try to strip homebase: I understand the limitations, but we will have more correct hits as failures I
111  // assume
112  thread_local QRegularExpression tsRegex("(-\\s*|\\s)([A-Z]{4})$");
113  const QRegularExpressionMatch match = tsRegex.match(rn);
114  if (match.hasMatch())
115  {
116  const int pos = match.capturedStart(0);
117  const QString icao = match.captured(0).trimmed().right(4);
118  rn = QStringView { rn }.left(pos).trimmed().toString();
119  this->setHomeBase(CAirportIcaoCode(icao));
120  }
121  }
122 
123  // do not beautify before stripping home base
124  m_realname = beautifyRealName(rn);
125  }
126 
127  bool CUser::hasValidHomeBase() const { return m_homebase.hasValidIcaoCode(false); }
128 
129  bool CUser::hasValidOrEmptyHomeBase() const { return m_homebase.isEmpty() || this->hasValidHomeBase(); }
130 
132  {
133  CStatusMessageList msgs;
134  // callsign optional
135  if (!this->hasId()) { msgs.push_back(CStatusMessage(CStatusMessage::SeverityWarning, u"Invalid id")); }
136  if (!this->hasRealName())
137  {
138  msgs.push_back(CStatusMessage(CStatusMessage::SeverityWarning, u"Invalid real name"));
139  }
140  if (!this->hasCredentials())
141  {
142  msgs.push_back(CStatusMessage(CStatusMessage::SeverityWarning, u"Invalid credentials"));
143  }
144  return msgs;
145  }
146 
147  QString CUser::get7DigitId() const
148  {
149  if (!this->hasNumericId()) { return m_id; }
150  if (m_id.length() > 6) { return m_id; }
151 
152  static const QString zeros("0000000");
153  return zeros.left(7 - m_id.length()) % m_id;
154  }
155 
157  {
158  if (m_id.isEmpty()) { return -1; }
159  if (is09OnlyString(m_id)) { return m_id.toInt(); }
160  return -1;
161  }
162 
163  bool CUser::hasNumericId() const
164  {
165  if (m_id.isEmpty()) { return false; }
166  return is09OnlyString(m_id);
167  }
168 
169  void CUser::updateMissingParts(const CUser &otherUser)
170  {
171  if (this == &otherUser) { return; }
172  if (!this->hasRealName()) { this->setRealName(otherUser.getRealName()); }
173  if (!this->hasId()) { this->setId(otherUser.getId()); }
174  if (!this->hasValidEmail()) { this->setEmail(otherUser.getEmail()); }
175  if (!this->hasCallsign()) { this->setCallsign(otherUser.getCallsign()); }
176  }
177 
178  void CUser::synchronizeData(CUser &otherUser)
179  {
180  if (this == &otherUser) { return; }
181  this->updateMissingParts(otherUser);
182  otherUser.updateMissingParts(*this);
183  }
184 
185  bool CUser::isValidVatsimId(const QString &id)
186  {
187  if (id.isEmpty()) { return false; }
188  if (!is09OnlyString(id)) { return false; }
189  const int i = id.toInt();
190  return i >= 100000 && i <= 9999999;
191  }
192 
193  QString CUser::beautifyRealName(const QString &realName)
194  {
195  QString newRealName(realName.simplified().trimmed());
196  if (newRealName.isEmpty()) { return newRealName; }
197  if (is09OnlyString(newRealName))
198  {
199  return newRealName;
200  } // new VATSIM COD, allowing id as name, see
201  // https://discordapp.com/channels/539048679160676382/539846348275449887/599969308247851018
202 
203  int uc = 0;
204  int lc = 0;
205  for (const QChar &ch : realName)
206  {
207  // Joe Doe -> valid
208  // jOE dOE -> invalid
209  // Joe McArthur -> valid
210  if (uc > 1 && lc > 2 && lc > uc) { return newRealName; } // mixed case name, no need to beautify
211  if (ch.isLower())
212  {
213  lc++;
214  continue;
215  }
216  if (ch.isUpper())
217  {
218  uc++;
219  continue;
220  }
221  }
222 
223  // simple title case beautifying
224  newRealName = newRealName.toLower();
225  QString::Iterator i = newRealName.begin();
226  bool upperNextChar = true;
227  while (i != newRealName.end())
228  {
229  if (i->isSpace() || *i == '-') { upperNextChar = true; }
230  else if (upperNextChar)
231  {
232  const QChar u(i->toUpper());
233  *i = u;
234  upperNextChar = false;
235  }
236  ++i;
237  }
238  return newRealName;
239  }
240 
242  {
243  if (index.isMyself()) { return QVariant::fromValue(*this); }
244  const ColumnIndex i = index.frontCasted<ColumnIndex>();
245  switch (i)
246  {
247  case IndexEmail: return QVariant(m_email);
248  case IndexId: return QVariant(m_id);
249  case IndexId7Digit: return QVariant(this->get7DigitId());
250  case IndexIdInteger: return QVariant::fromValue(this->getIntegerId());
251  case IndexPassword: return QVariant(m_password);
252  case IndexRealName: return QVariant(m_realname);
253  case IndexHomebase: return m_homebase.propertyByIndex(index.copyFrontRemoved());
254  case IndexCallsign: return m_callsign.propertyByIndex(index.copyFrontRemoved());
255  default: return CValueObject::propertyByIndex(index);
256  }
257  }
258 
259  void CUser::setPropertyByIndex(CPropertyIndexRef index, const QVariant &variant)
260  {
261  if (index.isMyself())
262  {
263  (*this) = variant.value<CUser>();
264  return;
265  }
266  const ColumnIndex i = index.frontCasted<ColumnIndex>();
267  switch (i)
268  {
269  case IndexEmail: this->setEmail(variant.value<QString>()); break;
270  case IndexId: // fallthru
271  case IndexId7Digit: this->setId(variant.value<QString>()); break;
272  case IndexIdInteger: this->setId(QString::number(variant.toInt())); break;
273  case IndexPassword: this->setPassword(variant.value<QString>()); break;
274  case IndexRealName: this->setRealName(variant.value<QString>()); break;
275  case IndexHomebase: m_homebase.setPropertyByIndex(index.copyFrontRemoved(), variant); break;
276  case IndexCallsign: m_callsign.setPropertyByIndex(index.copyFrontRemoved(), variant); break;
277  default: CValueObject::setPropertyByIndex(index, variant); break;
278  }
279  }
280 
281  int CUser::comparePropertyByIndex(CPropertyIndexRef index, const CUser &compareValue) const
282  {
283  if (index.isMyself()) { return this->getRealName().compare(compareValue.getRealName(), Qt::CaseInsensitive); }
284  const ColumnIndex i = index.frontCasted<ColumnIndex>();
285  switch (i)
286  {
287  case IndexEmail: return m_email.compare(compareValue.getEmail(), Qt::CaseInsensitive);
288  case IndexId: return m_id.compare(compareValue.getId(), Qt::CaseInsensitive);
289  case IndexId7Digit: return this->get7DigitId().compare(compareValue.get7DigitId(), Qt::CaseInsensitive);
290  case IndexIdInteger: return swift::misc::Compare::compare(getIntegerId(), compareValue.getIntegerId());
291  case IndexRealName: return m_realname.compare(compareValue.getRealName(), Qt::CaseInsensitive);
292  case IndexHomebase:
293  return m_homebase.comparePropertyByIndex(index.copyFrontRemoved(), compareValue.getHomeBase());
294  case IndexCallsign:
295  return m_callsign.comparePropertyByIndex(index.copyFrontRemoved(), compareValue.getCallsign());
296  case IndexPassword: break;
297  default: return CValueObject::comparePropertyByIndex(index, compareValue);
298  }
299  Q_ASSERT_X(false, Q_FUNC_INFO, "compare failed");
300  return 0;
301  }
302 
303  void CUser::setPassword(const QString &pw) { m_password = CObfuscation::decode(pw); }
304 
305  void CUser::setEmail(const QString &email) { m_email = CObfuscation::decode(email); }
306 
307  void CUser::setId(const QString &id) { m_id = CObfuscation::decode(id); }
308 } // namespace swift::misc::network
Utility class to obfuscate strings in the source code to make them unreadable.
Definition: obfuscation.h:19
static QString decode(const QString &inString, bool trimmed=true)
Decode string if it has the prefix, otherwise do nothing with it.
Definition: obfuscation.cpp:12
Non-owning reference to a CPropertyIndex with a subset of its features.
Q_REQUIRED_RESULT CPropertyIndexRef copyFrontRemoved() const
Copy with first element removed.
CastType frontCasted() const
First element casted to given type, usually the PropertIndex enum.
bool isMyself() const
Myself index, used with nesting.
void push_back(const T &value)
Appends an element at the end of the sequence.
Definition: sequence.h:305
Streamable status message, e.g.
constexpr static auto SeverityWarning
Status severities.
Status messages, e.g. from Core -> GUI.
Value object encapsulating information of airport ICAO data.
int comparePropertyByIndex(CPropertyIndexRef index, const CAirportIcaoCode &compareValue) const
Compare for index.
void setPropertyByIndex(CPropertyIndexRef index, const QVariant &variant)
Set property by index.
QVariant propertyByIndex(CPropertyIndexRef index) const
Property by index.
bool hasValidIcaoCode(bool strict) const
Has valid code?
const QString & asString() const
Get code.
Value object encapsulating information of a callsign.
Definition: callsign.h:30
int comparePropertyByIndex(CPropertyIndexRef index, const CCallsign &compareValue) const
Compare for index.
Definition: callsign.cpp:341
QVariant propertyByIndex(CPropertyIndexRef index) const
Property by index.
Definition: callsign.cpp:310
void setPropertyByIndex(CPropertyIndexRef index, const QVariant &variant)
Set property by index.
Definition: callsign.cpp:324
bool isObserverCallsign() const
Observer callsign?
Definition: callsign.cpp:201
bool isEmpty() const
Is empty?
Definition: callsign.h:63
QString getIcaoCode() const
Get ICAO code, if this makes sense (EDDF_TWR -> EDDF)
Definition: callsign.cpp:185
bool isAtcCallsign() const
ATC callsign.
Definition: callsign.cpp:139
const QString & getStringAsSet() const
Get callsign.
Definition: callsign.h:99
ColumnIndex
Base class enums.
Definition: mixinindex.h:44
int comparePropertyByIndex(CPropertyIndexRef index, const Derived &compareValue) const
Compare for index.
Definition: mixinindex.h:187
void setPropertyByIndex(CPropertyIndexRef index, const QVariant &variant)
Set property by index.
Definition: mixinindex.h:160
QVariant propertyByIndex(CPropertyIndexRef index) const
Property by index.
Definition: mixinindex.h:167
Value object encapsulating information of a user.
Definition: user.h:28
const aviation::CAirportIcaoCode & getHomeBase() const
Homebase.
Definition: user.h:134
bool hasValidOrEmptyHomeBase() const
Has valid or empty home base?
Definition: user.cpp:129
bool hasRealName() const
Valid real name?
Definition: user.h:80
const QString & getRealName() const
Get full name.
Definition: user.h:59
bool hasNumericId() const
Has a numeric id?
Definition: user.cpp:163
bool hasValidHomeBase() const
Has valid home base?
Definition: user.cpp:127
int getIntegerId() const
Id as integer if possible, otherwise -1.
Definition: user.cpp:156
static QString beautifyRealName(const QString &realName)
Beautify real name, e.g. "JOE DoE" -> "Joe Doe";.
Definition: user.cpp:193
void setRealName(const QString &realname)
Set real name.
Definition: user.cpp:98
QString getRealNameAndId() const
Real name and id.
Definition: user.cpp:52
bool hasValidEmail() const
Valid email?
Definition: user.h:116
int comparePropertyByIndex(CPropertyIndexRef index, const CUser &compareValue) const
Compare for index.
Definition: user.cpp:281
QString get7DigitId() const
Numeric ids get a leading zeros if required.
Definition: user.cpp:147
void setId(const QString &id)
Set id.
Definition: user.cpp:307
const QString & getId() const
Get id.
Definition: user.h:119
QVariant propertyByIndex(CPropertyIndexRef index) const
Property by index.
Definition: user.cpp:241
const aviation::CCallsign & getCallsign() const
Get associated callsign.
Definition: user.h:140
void setHomeBase(const aviation::CAirportIcaoCode &homebase)
Set homebase.
Definition: user.h:137
CUser()=default
Default constructor.
void setEmail(const QString &email)
Set email.
Definition: user.cpp:305
bool hasId() const
Valid id?
Definition: user.h:83
bool hasHomeBase() const
Has home base?
Definition: user.h:92
bool setCallsign(const aviation::CCallsign &callsign)
Set associated callsign.
Definition: user.cpp:62
void setPassword(const QString &pw)
Set password.
Definition: user.cpp:303
CStatusMessageList validate() const
Validate, provide details about issues.
Definition: user.cpp:131
const QString & getEmail() const
Get email.
Definition: user.h:110
void setPropertyByIndex(CPropertyIndexRef index, const QVariant &variant)
Set property by index.
Definition: user.cpp:259
QString convertToQString(bool i18n=false) const
Cast as QString.
Definition: user.cpp:70
bool hasCredentials() const
Valid credentials?
Definition: user.h:77
static bool isValidVatsimId(const QString &id)
Valid VATSIM id.
Definition: user.cpp:185
QString getRealNameAndHomeBase(const QString &separator=QString(" ")) const
Real name + homebase.
Definition: user.cpp:45
void synchronizeData(CUser &otherUser)
This and another user exchange missing data, This user has priority and overrides first.
Definition: user.cpp:178
bool hasCallsign() const
Has associated callsign?
Definition: user.h:89
void updateMissingParts(const CUser &otherUser)
Update missing parts in this object.
Definition: user.cpp:169
bool is09OnlyString(const QString &testString)
String with 0-9 only.
Definition: stringutils.h:174
SWIFT_MISC_EXPORT QString simplifyAccents(const QString &candidate)
Remove accents / diacritic marks from a string.
#define SWIFT_DEFINE_VALUEOBJECT_MIXINS(Namespace, Class)
Explicit template definition of mixins for a CValueObject subclass.
Definition: valueobject.h:67