Remoto - VFS: VFS_session.cpp Source File
Remoto - VFS
VFS_session.cpp
Go to the documentation of this file.
1 
2 #include <QFileInfo>
3 #include <QRegularExpression>
4 
5 #include "VFS_session.h"
6 
7 #include "VFS.h"
8 #include "VFS_base/VFS_icons.h"
9 #include "utilities/rutils.h"
10 
11 QJsonObject VFS_session::_sessionTypes = QJsonObject();
12 QJsonObject VFS_session::_sessionDefaults = QJsonObject();
13 QMutex VFS_session::_sessionTypesLock(QMutex::Recursive);
15 
51 VFS_session::VFS_session(VFS_node *client, VFS_sessionManager *manager, QString user, QString type, QString home, QString authpath, QDateTime expires, QString address, QJsonObject parameters, QString authtoken)
52 : VFS_node()
53 , _initialized(false)
54 , _client(client)
55 , _manager(manager)
56 , _created(QDateTime::currentDateTimeUtc())
57 , _expires(expires)
58 , _address(address)
59 , _user(user)
60 , _type(type)
61 , _home(home)
62 , _authpath(authpath)
63 , _authtoken(authtoken)
64 , _tokens(parameters)
65 {
66  _tokens["username"] = _user;
67  _tokens["authpath"] = _authpath;
68  _tokens["type"] = _type;
69  _tokens["home"] = _home;
70  _tokens["address"] = _address;
71 
72  if (_expires.isValid())
73  VFS::LOG( QString("Created '%1' session for user '%2' which will expire on %3").arg(_type).arg(_user).arg(_expires.toLocalTime().toString(Qt::SystemLocaleLongDate)), 6 );
74  else
75  VFS::LOG( QString("Created non-expiring '%1' session for user '%2'").arg(_type).arg(_user), 6 );
76 
77  //start the session timer at one minute
78  if (_expires.isValid())
79  timerID = startTimer( 60000, Qt::VeryCoarseTimer );
80 
81  if (__isNode(_client))
82  {
83  //ensure that the session and the client die at the same time
84  //the connection must be a Qt::QueuedConnection for cases of single-threaded configuration
85  connect( this, SIGNAL(finished(bool)), _client, SIGNAL(finished(bool)), Qt::QueuedConnection );
86  connect( _client, SIGNAL(finished(bool)), this, SIGNAL(finished(bool)), Qt::QueuedConnection );
87  }
88  else
89  VFS::WARN("Null or non-node client provided to session.");
90 }
91 
121 VFS_session::VFS_session(VFS_node *client, VFS_sessionManager *manager, QJsonObject data)
122 : VFS_node()
123 , _initialized(false)
124 , _client(client)
125 , _manager(manager)
126 {
127  _created = QDateTime::fromSecsSinceEpoch( (qint64) data["created"].toDouble() );
128  //_expires = QDateTime::fromSecsSinceEpoch( (qint64) data["expires"].toDouble() );
129  _expires = QDateTime::fromSecsSinceEpoch( (qint64) QDateTime::currentSecsSinceEpoch() + 590 ); //expire 10 minutes from now
130  _address = data["address"].toString();
131  _user = data["user"].toString();
132  _type = data["type"].toString();
133  _groups = data["groups"].toArray();
134  _home = data["home"].toString();
135  _authpath = "";
136 
137  _tokens = data["tokens"].toObject();
138  _sessionData = data["sessionData"].toObject();
139 
140  if (_expires.isValid())
141  VFS::LOG( QString("Created '%1' session for user '%2' which will expire on %3").arg(_type).arg(_user).arg(_expires.toLocalTime().toString(Qt::SystemLocaleLongDate)), 8 );
142  else
143  VFS::LOG( QString("Created non-expiring '%1' session for user '%2'").arg(_type).arg(_user), 8 );
144 
145  //start the session timer at one minute
146  if (_expires.isValid())
147  timerID = startTimer( 60000, Qt::VeryCoarseTimer );
148 
149  if (__isNode(_client))
150  {
151  //ensure that the session and the client die at the same time
152  //the connection must be a Qt::QueuedConnection for cases of single-threaded configuration
153  //connect( this, SIGNAL(finished(bool)), _client, SIGNAL(finished(bool)), Qt::QueuedConnection );
154  connect( _client, SIGNAL(finished(bool)), this, SIGNAL(finished(bool)), Qt::QueuedConnection );
155  }
156  else
157  VFS::WARN("Null or non-node client provided to session.");
158 }
159 
161 {
162  VFS::LOG( QString("Deleting %1 session for user '%2'").arg(_type).arg(_user), 7 );
163 }
164 
165 
175 {
176  QMutexLocker l(&_sessionTypesLock);
177 
178  if (_initializedTypes) return;
179 
180  QJsonObject t;
181  t["preferencesPath"] = "";
182  t["interfacePath"] = "";
183 
184  _sessionTypes["default"] = t;
185 
186  QJsonObject d;
187  d["preferencesPath"] = rutils::jsonResource( ":/skeleton/preferencesSkeleton.rfm" );
188  d["interfacePath"] = rutils::jsonResource( ":/skeleton/layout.rui" );
189 
190  _sessionDefaults["default"] = d;
191 
192  _initializedTypes = true;
193 }
194 
195 
241 bool VFS_session::registerSessionType(QString type, QJsonObject definition, QJsonObject defaults)
242 {
243  QMutexLocker ll(&_sessionTypesLock);
244 
245  initializeTypes();
246 
247  if (!_sessionTypes.contains(type))
248  { _sessionTypes[type] = definition;
249  _sessionDefaults[type] = defaults;
250  VFS::LOG( QString("Registered VFS_session type '%1'.").arg(type), 8 );
251  return true;
252  }
253  else
254  VFS::WARN( QString("Can't register VFS_session type '%1'. It is already registered.").arg(type) );
255 
256  return false;
257 }
258 
259 
268 QJsonValue VFS_session::fetchSessionValue(QString key)
269 {
270  QMutexLocker l(&_lock);
271 
272  if (_sessionData.contains(key))
273  return _sessionData.value(key);
274 
275  if (_tokens.contains(key))
276  return _tokens.value(key);
277 
278  if (key == "user")
279  return _user;
280 
281  if (key == "uidnumber")
282  return _uidnumber;
283 
284  if (key == "type")
285  return _type;
286 
287  if (key == "realname")
288  return _realname;
289 
290  if (key == "groups")
291  return _groups;
292 
293  return QJsonValue::Null;
294 }
295 
296 
304 QJsonValue VFS_session::fetchDefaultSessionValue(QString type, QString key)
305 {
306  QMutexLocker ll(&_sessionTypesLock);
307 
308  if (_sessionDefaults.contains(type))
309  if (_sessionDefaults[type].toObject().contains(key))
310  {
311  return _sessionDefaults[type].toObject()[key];
312  }
313 
314  return QJsonValue::Null;
315 }
316 
317 
325 {
326  //for instance 0x00007fbfea385f90
327 
328  bool ok;
329  VFS_node *n = (VFS_node *) address.toULongLong(&ok,0);
330 
331  if (ok)
332  {
333  if (__isNode(n))
334  {
335  VFS_session *s = dynamic_cast<VFS_session *>(n);
336 
337  if (s) return s;
338  }
339  }
340 
341  return nullptr;
342 }
343 
344 
354 QJsonObject VFS_session::toJson()
355 {
356  QMutexLocker l(&_lock);
357 
358  QJsonObject o;
359 
360  o["created"] = _created.toSecsSinceEpoch();
361  o["expires"] = _expires.toSecsSinceEpoch();
362  o["address"] = _address;
363 
364  o["user"] = _user;
365  o["uidnumber"] = _uidnumber;
366  o["realname"] = _realname;
367  o["groups"] = _groups;
368  o["type"] = _type;
369  o["home"] = _home;
370  //o["authpath"] = _authpath; //don't want this, it's meaningless to another VFS
371 
372  o["tokens"] = _tokens;
373  o["sessionData"] = _sessionData;
374 
375  return o;
376 }
377 
378 
387 {
388  QDateTime n = QDateTime::currentDateTimeUtc();
389  qint64 s = n.secsTo(_expires);
390 
391  return s;
392 }
393 
394 
401 {
402  if (_authtoken.isEmpty())
403  return "";
404 
405  return _authtoken.split("/",Qt::SkipEmptyParts).last();
406 }
407 
408 
415 bool VFS_session::isMemberOf(QString group)
416 {
417  QMutexLocker l(&_lock);
418 
419  if (group == "")
420  return false;
421 
422  if (_groups.contains(group))
423  return true;
424 
425  return false;
426 }
427 
428 
435 bool VFS_session::isMemberOf(QStringList groups)
436 {
437  QMutexLocker l(&_lock);
438 
439  foreach(QString g, groups)
440  { if (isMemberOf(g))
441  return true;
442  }
443 
444  return false;
445 }
446 
447 
454 {
455  return false;
456 }
457 
458 
465 {
466  QMutexLocker l(&_lock);
467 
468  QString e;
469  if (_expires.isValid())
470  e = _expires.toString(Qt::SystemLocaleLongDate);
471  else
472  e = "never";
473 
474  QString s = QString("user: %1\ntype: %2\naddress: %6\nexpires: %5\nhome: %3\nauth: %4\ngroups: %7").arg(_user).arg(_type).arg(_home).arg(_authpath).arg(e).arg(_address).arg(QString(QJsonDocument(_groups).toJson()).replace("\n",""));
475 
476  return s;
477 }
478 
479 
488 void VFS_session::timerEvent(QTimerEvent *event)
489 {
490  if (event->timerId() == timerID)
491  {
492  if ( QDateTime::currentDateTimeUtc() >= _expires)
493  { VFS::WARN( QString("%1 session for user '%2' has expired.").arg(_type).arg(_user), 7 );
494  emit finished(true);
495  }
496  }
497 }
498 
499 
510 {
511  if (_initialized)
512  {
513  VFS::ERROR(r->_reason = "Session is already initialized.",0,r->_user);
514  r->_success = false;
515  return;
516  }
517 
518  //printf("INITIALIZE USER!\n");
519 
520  QMutexLocker ll(&_sessionTypesLock);
521  QMutexLocker l(&_lock);
522 
523  QJsonObject o,t,d;
524  QJsonObject::iterator i;
525 
526  //QString rel = "data/"+_user;
527 
528  //get the type-dependent data for this session
529  if (_sessionTypes.contains(_type))
530  t = _sessionTypes[_type].toObject();
531  else
532  t = _sessionTypes["default"].toObject();
533 
534  if (_sessionDefaults.contains(_type))
535  d = _sessionDefaults[_type].toObject();
536  else
537  d = _sessionDefaults["default"].toObject();
538 
539  //resolve and store the resolved type-dependent data for this session
541 
542  //if a token path is present we need to create or update the session token entry
543  if (!_authtoken.isEmpty())
544  {
545  QJsonObject tm;
546  tm["createIfMissing"] = true;
547 
548  QJsonObject td;
549  td["username"] = _user;
550  td["authpath"] = _authpath;
551  td["expire"] = _expires.toSecsSinceEpoch()/60;
552 
553  VFS_request *ta = createRequest(VFS_request::submit,_authtoken,"server",QJsonDocument(td),tm);
554  ta->_metadata["suppressError"] = true;
555  _initializers.enqueue(ta);
556 
558  _initializers.enqueue(tb);
559  }
560 
561  //check if the home directory and sub-paths and preferences exist, or create them
562  //set up metadata
563  QJsonObject m;
564  m["createIfMissing"] = true;
565  m["container"] = true;
566 
567  //check paths
568  //check directories
569  m["container"] = true; //just as a reminder
570  i = _sessionData.begin();
571  while (i != _sessionData.end())
572  {
573  QString s = i.value().toString();
574 
575  //QFileInfo f(s);
576  //if (f.completeSuffix().isEmpty()) //if it's a directory
577  if (s.endsWith("/")) //if it's a directory
578  {
579  //printf("dir: %s\n",qUtf8Printable(s));
580 
581  VFS_request *q = createRequest(VFS_request::read,s,_user,QJsonDocument(),m);
582  _initializers.enqueue(q);
583  }
584 
585  ++i;
586  }
587 
588  //check files
589  m["container"] = false; //these are files
590  i = _sessionData.begin();
591  while (i != _sessionData.end())
592  {
593  QString s = i.value().toString();
594 
595  //QFileInfo f(s);
596  //if (!f.completeSuffix().isEmpty()) //if it's a file
597  if (!s.endsWith("/"))
598  {
599  //printf("file: %s\n",qUtf8Printable(s));
600 
601  VFS_request *q = createRequest(VFS_request::subscribe,s,_user,QJsonDocument(),m);
602 
603  if (d.contains(i.key()))
604  { q->_data.setObject( d[i.key()].toObject() );
605  //printf("VAL: %s\n",qUtf8Printable( QJsonDocument( d[i.key()].toObject() ).toJson() ) );
606  }
607  else
608  { VFS::WARN( QString("No key '%2' in sessionDefaults for '%1' when creating '%3'").arg(_type).arg(i.key()).arg(s) );
609  }
610 
611  _initializers.enqueue(q);
612  }
613 
614  ++i;
615  }
616 
617  //kick off the first domino!
618  r->_isCallback = true;
619  _initializers.enqueue(r);
621 }
622 
623 
655 {
656  if (_initializers.length())
657  {
658  VFS_request *r = _initializers.dequeue();
659 
660  //the last item is the sessionManager's initial request
661  if (!_initializers.length())
662  {
663  QJsonObject d = r->_data.object();
664  _uidnumber = (quint16) d["uidnumber"].toInt( 0 );
665  _realname = d["realname"].toString( _user );
666  _groups = d["groups"].toArray();
667 
668  _tokens["realname"] = _realname;
669  _tokens["uidnumber"] = _uidnumber;
670 
671  QJsonObject o = _sessionData;
672 
673  //populate the additional data
674  o["accepted"] = true;
675  o["username"] = _user;
676  o["uidnumber"] = _uidnumber;
677  o["realname"] = _realname;
678  o["preferencesBase"] = _manager->getCWD(); //the base class for the preferences skeleton
679 
680  r->_data.setObject(o);
681 
682  r->_session = this;
683  r->_success = true;
684 
685  _initialized = true;
686 
687  issueResponse(r);
688  }
689  else
690  issueRequest(r);
691  }
692 }
693 
694 
701 QJsonObject VFS_session::resolveTokens(QJsonObject data)
702 {
703  QMutexLocker l(&_lock);
704 
705  QRegularExpression tokenRX("@([^@]+)@");
706  QRegularExpressionMatch match;
707 
708  QJsonObject::iterator i;
709  i = data.begin();
710  while (i != data.end())
711  {
712  QString key = i.key();
713  QString value = i.value().toString();
714 
715  match = tokenRX.match(value);
716  while (match.hasMatch())
717  {
718  QString matched = match.captured(1);
719  //printf("found: %s on %s\n",qUtf8Printable(matched),qUtf8Printable(key));
720  QString t = _tokens.contains(matched) ? _tokens[matched].toString() : "[unresolved: "+matched+"]";
721  value = value.replace("@"+matched+"@", t );
722  match = tokenRX.match(value);
723  }
724 
725  //printf("value: %s\n",qUtf8Printable(value));
726  data[key] = value;
727 
728  ++i;
729  }
730 
731  //printf("%s\n", qUtf8Printable(QJsonDocument(data).toJson()));
732 
733  return data;
734 }
735 
736 
742 QByteArray VFS_session::icon()
743 {
744  return VFS_icons::get("user");
745 }
746 
747 
770 {
771  QJsonObject o;
772  o["username"] = _user;
773  o["uidnumber"] = _uidnumber;
774  o["realname"] = _realname;
775  o["groups"] = _groups;
776  o["created"] = _created.toMSecsSinceEpoch();
777  o["expires"] = _expires.toMSecsSinceEpoch();
778  o["authpath"] = _authpath;
779  o["type"] = _type;
780  o["home"] = _home;
781  o["address"] = _address;
782 
783  if (r->_session == this)
784  o["self"] = true;
785 
786  r->_data.setObject(o);
787  r->_success = true;
788 }
789 
796 {
797  //FIXME: detect if termination was caused by admin request
798  if (_client)
799  {
800  QJsonObject metadata;
801  metadata["type"] = "reload";
802 
803  VFS_request *s = createRequest(VFS_request::write, "", r->_user, QJsonDocument(), metadata );
805  }
806 
807  //simplifies the teardown process and prevents spurious requests from not being satisfied
808  //it should be here, and not in the destructor, to catch the effect as early as possible
810 
811  VFS_node::rm(r);
812 }
813 
825 {
826  //printf("Apply diff!\n%s\n",qUtf8Printable(r->toJson(0,true,true)));
827 
828  if (r->_data.isEmpty())
829  emit finished(true);
830 
831  r->_success = true;
832 }
833 
840 {
841  //printf("VFS_session receiveResponse: %d %d %s %s\n",_initializers.length(),r->_requestType,qUtf8Printable(r->_initialPath),qUtf8Printable(r->_path));
842  //printf("VFS_session receiveResponse: %s\n",qUtf8Printable(r->toJson(0,true,true)));
843 
844  if (r->_success)
845  {
846  //if it was a read from the auth node
847  if (r->_initialPath == _authpath+"/"+_user)
848  {
849  //printf("VFS_session user reload receiveResponse: %s\n",qUtf8Printable(r->toJson(0,true,true)));
850 
851  if (_initializers.length())
852  {
853  QJsonObject l = _initializers.last()->_data.object();
854  QJsonObject d = r->_data.object();
855  l["username"] = d["username"];
856  l["uidnumber"] = d["uidnumber"];
857  l["realname"] = d["realname"];
858  l["groups"] = d["groups"];
859 
860  _initializers.last()->_data.setObject(l);
861  }
862  else
863  VFS::WARN("Could not populate user information.");
864  }
865 
867  }
868  else
869  {
870  //delete the initializers and die on failure to initialize
871 
872  while (_initializers.length() > 1)
873  delete _initializers.dequeue();
874 
875  if (_initializers.length())
876  {
877  VFS_request *rr = _initializers.dequeue();
878  rr->_success = false;
879  rr->_reason = "Failed to initialize session. (failed at "+r->_initialPath+")";
880  issueResponse(rr);
881  }
882 
883  emit finished();
884 
885  return;
886  }
887 
889 }
static char * get(QString which="")
Fetch an icon from the library.
Definition: VFS_icons.cpp:34
VFS_node is the base class from which all other VFS_node classes derive.
Definition: VFS_node.h:143
virtual VFS_request * createRequest(VFS_request::requestType type, QString path, QString user="unknown", QJsonDocument data=QJsonDocument(), QJsonObject metadata=QJsonObject(), bool dontDelete=false)
Create a new VFS_request with this object as _origin.
Definition: VFS_node.cpp:1913
virtual void issueResponse(VFS_request *t)
Once a request has been completed, issue a response.
Definition: VFS_node.cpp:1981
virtual void metadata(VFS_request *r)
Fetch the metadata of this node.
Definition: VFS_node.cpp:797
static bool __removeNode(VFS_node *)
Remove a node from the global registry.
Definition: VFS_node.cpp:617
virtual void issueRequest(VFS_request *t)
A convenience function.
Definition: VFS_node.cpp:1933
static bool __isNode(VFS_node *)
Check to see if a node is in the global registry.
Definition: VFS_node.cpp:588
QMutex _lock
A recursive mutex that is local to this node.
Definition: VFS_node.h:178
friend class VFS_session
Definition: VFS_node.h:146
virtual void rm(VFS_request *r)
Remove a child entry from a node, or the node itself.
Definition: VFS_node.cpp:1005
void finished(bool andDelete=false)
Emitted if a thread fails to create its node, or a node is rm()'d, or any other reason a node has com...
virtual void receiveResponse(VFS_request *t)
Once a VFS_request has been completed, a response will be issued back to its _origin.
Definition: VFS_node.cpp:1870
The base class for all requests between nodes.
Definition: VFS_node.h:54
@ read
read full contents (4)
Definition: VFS_node.h:68
@ write
write full contents (5)
Definition: VFS_node.h:69
@ subscribe
subscribe to a path (9)
Definition: VFS_node.h:73
@ submit
apply a diff (8)
Definition: VFS_node.h:72
QString _initialPath
the target path when the request was made (relative to the responder)
Definition: VFS_node.h:93
QString _user
who initiated this request, mostly for logging
Definition: VFS_node.h:106
VFS_session * _session
The session associated with this request. This is an optional value, and care should be taken to chec...
Definition: VFS_node.h:105
QString _reason
if something (probably bad) happened, this is the reason
Definition: VFS_node.h:108
bool _isCallback
whether or not to issue a response (IE, another request is chained to this request,...
Definition: VFS_node.h:98
bool _success
if the request was successfully completed
Definition: VFS_node.h:107
QJsonDocument _data
the request payload
Definition: VFS_node.h:102
QJsonObject _metadata
the request payload
Definition: VFS_node.h:101
The VFS_session object represents a single session.
Definition: VFS_session.h:14
QString _realname
The real name of the user associated with this session.
Definition: VFS_session.h:77
virtual void timerEvent(QTimerEvent *event)
The timer event used to check if the session is still valid.
bool _initialized
false until initialization is complete
Definition: VFS_session.h:65
static QMutex _sessionTypesLock
A mutex to protect thread safety during initialization.
Definition: VFS_session.h:89
QDateTime _expires
The Expiration date/time of this session.
Definition: VFS_session.h:72
virtual void rm(VFS_request *r)
Remove (delete) this node, and also the _client associated with it.
QJsonObject _tokens
The tokens available to resolve session values.
Definition: VFS_session.h:84
virtual ~VFS_session()
static bool _initializedTypes
Whether or not VFS_session static variables have been initialized.
Definition: VFS_session.h:88
virtual QByteArray icon()
Return the user icon from VFS_icons.
static QJsonObject _sessionTypes
The registered session types. The entries here will be de-tokenized on read()
Definition: VFS_session.h:90
QQueue< VFS_request * > _initializers
The list of initialization requests which must be empty for _initialized to become true.
Definition: VFS_session.h:66
virtual void applyDiff(VFS_request *r)
Receive diffs from our subscriptions.
virtual void receiveResponse(VFS_request *t)
VFS_sessionManager::receiveResponse.
VFS_sessionManager * _manager
The sessionManager tracking this session.
Definition: VFS_session.h:70
QJsonObject resolveTokens(QJsonObject _data)
Resolve the tokens for a session type based on variables gathered in the _tokens object.
virtual void initializeUser(VFS_request *r)
Check and create session files for a user.
static QJsonValue fetchDefaultSessionValue(QString type, QString key)
Fetch a session type's default value by key, as registered by registerSessionType()
qint64 secondsToExpire()
Given the current time, calculate the number of seconds until this session will expire.
QString _type
The session or client type.
Definition: VFS_session.h:79
static QJsonObject _sessionDefaults
The default values, per type, per entry.
Definition: VFS_session.h:91
bool isMemberOf(QString group)
Check if this session's group list includes the specified group.
QJsonObject toJson()
Return a json object with the salient data from this session.
quint16 _uidnumber
The uidnumber of the user associated with this session.
Definition: VFS_session.h:76
QString _user
The username associated with this session.
Definition: VFS_session.h:75
QJsonArray _groups
The list of groups this user is a member of.
Definition: VFS_session.h:78
QString _authpath
The VFS_auth path that authenticated this session.
Definition: VFS_session.h:81
QString _address
The remote address of the client.
Definition: VFS_session.h:73
VFS_node * _client
The client this session is for, which will usually be a subclass of VFS_websocket_client.
Definition: VFS_session.h:69
static VFS_session * fetchSession(QString address)
Try to resolve and cast a string address to a VFS_session.
virtual QString reportDetails()
Report data about a connected client.
QString _home
The VFS path to this user's home directory.
Definition: VFS_session.h:80
void dequeueInitializer()
Remove the first initializer item from _initializers and call issueRequest().
QString _authtoken
The authtoken VFS path associated with this session, which corresponds to an entry at VFS_sessionMana...
Definition: VFS_session.h:82
QJsonObject _sessionData
The resolved session data for this user type.
Definition: VFS_session.h:85
QDateTime _created
The creation date/time of this session.
Definition: VFS_session.h:71
QString authtoken()
Return the file component of the _authtoken path.
static bool registerSessionType(QString type, QJsonObject definition, QJsonObject defaults=QJsonObject())
Register a new session type by name.
static void initializeTypes()
Initialize the VFS_session::_sessionTypes member.
int timerID
The ID of the session timer.
Definition: VFS_session.h:44
virtual bool isContainer()
A VFS_session cannot have children.
virtual void read(VFS_request *r)
Read the session data.
QJsonValue fetchSessionValue(QString key)
Fetch a value from the _sessionData, by key.
VFS_sessionManager manages connected clients and their session data.
virtual QString getCWD()
Return the manager's _cwd value.
static void LOG(QString message, int level=0, QString user="server")
Send a message to the VFS::_messages VFS_stream.
Definition: VFS.cpp:209
static void ERROR(QString message, int level=0, QString user="server")
Send a message to the VFS::_errors VFS_stream.
Definition: VFS.cpp:307
static void WARN(QString message, int level=0, QString user="server")
Send a message to the VFS::_warnings VFS_stream.
Definition: VFS.cpp:258
setter user
a setter DOCME
setter type
a setter DOCME
QJsonObject jsonResource(QString resource, bool *ok=nullptr)
Fetch the contents of a Qt resource as a QJsonObject.
Definition: rutils.cpp:90