ArnLib  4.0.x
Active Registry Network
ArnScriptJob.cpp
Go to the documentation of this file.
1 // Copyright (C) 2010-2022 Michael Wiklund.
2 // All rights reserved.
3 // Contact: arnlib@wiklunden.se
4 //
5 // This file is part of the ArnLib - Active Registry Network.
6 // Parts of ArnLib depend on Qt and/or other libraries that have their own
7 // licenses. Usage of these other libraries is subject to their respective
8 // license agreements.
9 //
10 // GNU Lesser General Public License Usage
11 // This file may be used under the terms of the GNU Lesser General Public
12 // License version 2.1 as published by the Free Software Foundation and
13 // appearing in the file LICENSE_LGPL.txt included in the packaging of this
14 // file. In addition, as a special exception, you may use the rights described
15 // in the Nokia Qt LGPL Exception version 1.1, included in the file
16 // LGPL_EXCEPTION.txt in this package.
17 //
18 // GNU General Public License Usage
19 // Alternatively, this file may be used under the terms of the GNU General Public
20 // License version 3.0 as published by the Free Software Foundation and appearing
21 // in the file LICENSE_GPL.txt included in the packaging of this file.
22 //
23 // Other Usage
24 // Alternatively, this file may be used in accordance with the terms and conditions
25 // contained in a signed written agreement between you and Michael Wiklund.
26 //
27 // This program is distributed in the hope that it will be useful, but WITHOUT ANY
28 // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
29 // PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
30 //
31 
32 #include "ArnInc/ArnScriptJob.hpp"
33 #include <QFileInfo>
34 #include <QTimer>
35 #include <QEvent>
36 #include <QCoreApplication>
37 #include <QDebug>
38 
39 #ifdef ARNUSE_SCRIPTJS
40  #include <QJSEngine>
41  #include <QMetaObject>
42 #else
43  #include <QScriptable>
44  #include <QtScript>
45  #include <QScriptEngine>
46 #endif
47 
48 const QEvent::Type EventQuit = QEvent::Type( QEvent::User + 0);
49 
50 
51 ArnScriptJobB::ArnScriptJobB( int id, QObject* parent) :
52  QObject( parent)
53 {
54  _id = id;
55  _isSleepState = false;
56  _isStopped = false;
57  _isRunning = false;
58  _quitInProgress = false;
59  _watchDogTime = 2000; // ms
60  _pollTime = 2000; // ms
61 
62  _jobFactory = arnNullptr;
63  _watchdog = arnNullptr;
64  _configObj = new QObject( this);
65  _arnScr = new ArnScript( this);
66  setPollTime( _pollTime);
67 
68  _watchdog = new ArnScriptWatchdog( &_arnScr->engine(), this);
69 
70  connect( _watchdog, SIGNAL(timeout()), this, SLOT(doTimeoutAbort()));
71  connect( _arnScr, SIGNAL(errorText(QString)), this, SIGNAL(errorText(QString)));
72 
73 #ifdef ARNUSE_SCRIPTJS
74  _arnScr->setInterruptedText( "Error: Job run timeout");
75 #endif
76 }
77 
78 
79 void ArnScriptJobB::setWatchDogTime( int time)
80 {
81  _watchDogTime = time; // ms
82 }
83 
84 
85 int ArnScriptJobB::watchDogTime()
86 {
87  return _watchDogTime;
88 }
89 
90 
91 void ArnScriptJobB::setWatchDog( int time, bool persist)
92 {
93  int wt = time;
94  if (wt < 0)
95  wt = _watchDogTime;
96 
97  if (persist)
98  _watchDogTime = wt;
99 
100 #ifdef ARNUSE_SCRIPTJS
101  _watchdog->setTime( wt);
102 #else
103  _watchdog->setTime( wt);
104  if (wt > 0) {
105  _arnScr->engine().setProcessEventsInterval( wt);
106  }
107  else {
108  _arnScr->engine().setProcessEventsInterval( _watchDogTime > 0 ? _watchDogTime : _pollTime);
109  }
110 #endif
111 }
112 
113 
114 void ArnScriptJobB::setPollTime( int time)
115 {
116  _pollTime = time;
117 
118 #ifdef ARNUSE_SCRIPTJS
119  // No support for this in QJSEngine
120 #else
121  _arnScr->engine().setProcessEventsInterval( _pollTime);
122 #endif
123 }
124 
125 
126 int ArnScriptJobB::pollTime()
127 {
128  return _pollTime;
129 }
130 
131 
132 void ArnScriptJobB::installInterface( const QString& id, QObject* obj)
133 {
134  if ((id.isEmpty()) || (obj == arnNullptr)) return;
135 
136  if (obj != this) obj->setParent( this); // Reparent interface to this Job
137  //MW: fix parrenting and delete failed install object
138 
139  _arnScr->addObject( id, obj);
140 }
141 
142 
143 bool ArnScriptJobB::installExtension( const QString& id, ArnScriptJobControl *jobControl)
144 {
145  if (!_jobFactory || !_arnScr) return false;
146 
147  return _jobFactory->installExtension( id, _arnScr->engine(), jobControl);
148 }
149 
150 
151 bool ArnScriptJobB::evaluateScript( const QByteArray& script, const QString& idName)
152 {
153  setWatchDog();
154  bool stat = true;
155 #ifdef ARN_JSENGINE
156  QByteArray conf = "config = {";
158  QByteArray sep;
159  QList<QByteArray> nameList = _configObj->dynamicPropertyNames();
160  foreach (QByteArray name, nameList) {
161  conf += sep;
162  name.replace(".", "_");
163  conf += name + ": \"" + _configObj->property( name.constData()).toString().toUtf8() + "\"" ;
164  sep = ", ";
165  }
166  conf += "};";
167  stat = _arnScr->evaluate( conf, idName, "Config");
168  if (!stat)
169  errorLog( "Config-script: " + conf );
170 #endif
171  if (stat) {
172  stat = _arnScr->evaluate( script, idName);
173  }
174  setWatchDog( 0, false);
175  return stat;
176 }
177 
178 
179 bool ArnScriptJobB::evaluateScriptFile( const QString& fileName)
180 {
181  setWatchDog();
182  bool stat = _arnScr->evaluateFile( fileName);
183  setWatchDog( 0, false);
184  return stat;
185 }
186 
187 
188 int ArnScriptJobB::id() const
189 {
190  return _id;
191 }
192 
193 
194 QString ArnScriptJobB::name() const
195 {
196  return _arnScr->idName();
197 }
198 
199 
200 bool ArnScriptJobB::setConfig( const char* name, const QVariant& value)
201 {
202  if (!name) return false;
203 
204  return _configObj->setProperty( name, value);
205 }
206 
207 
208 void ArnScriptJobB::addConfig( QObject* obj)
209 {
210  if (!obj) return;
211 
212  QList<QByteArray> nameList = obj->dynamicPropertyNames();
213  foreach (QByteArray name, nameList) {
214  setConfig( name.constData(), obj->property( name.constData()));
215  }
216 }
217 
218 
219 void ArnScriptJobB::setJobFactory( ArnScriptJobFactory* jobFactory)
220 {
221  _jobFactory = jobFactory;
222 }
223 
224 
225 ArnScriptJobFactory* ArnScriptJobB::jobFactory() const
226 {
227  return _jobFactory;
228 }
229 
230 
231 ArnScriptWatchdog* ArnScriptJobB::watchdog() const
232 {
233  return _watchdog;
234 }
235 
236 
237 bool ArnScriptJobB::setupScript()
238 {
239  _jobInit = _arnScr->globalProperty("jobInit");
240  _jobEnter = _arnScr->globalProperty("jobEnter");
241  _jobLeave = _arnScr->globalProperty("jobLeave");
242 
243  setWatchDog();
244  ARN_JSVALUE result = _arnScr->callFunc( _jobInit, ARN_JSVALUE(), ARN_JSVALUE_LIST());
245  if (result.isError()) {
246  _isStopped = true;
247  _isRunning = false;
248  }
249  setWatchDog( 0, false);
250 
251  return true;
252 }
253 
254 
255 void ArnScriptJobB::enterScript()
256 {
257  if (isStopped()) return; // Don't execute Enter if Job is stopped (error ...)
258 
259  _isRunning = true;
260  setWatchDog();
261  ARN_JSVALUE result = _arnScr->callFunc( _jobEnter, ARN_JSVALUE(), ARN_JSVALUE_LIST());
262  if (result.isError()) {
263  setWatchDog( 0, false);
264  _isStopped = true;
265  _isRunning = false;
266  //qDebug() << "Enter Script timeout: isEval=" << _arnScr->engine().isEvaluating();
267  emit scheduleRequest( _id);
268  }
269 }
270 
271 
272 void ArnScriptJobB::leaveScript()
273 {
274  if (isStopped()) return; // Don't execute Leave if Job is stopped (error ...)
275 
276  ARN_JSVALUE result = _arnScr->callFunc( _jobLeave, ARN_JSVALUE(), ARN_JSVALUE_LIST());
277  if (result.isError()) {
278  _isStopped = true;
279  //qDebug() << "Leave Script timeout: isEval=" << _arnScr->engine().isEvaluating();
280  }
281  _isRunning = false;
282  setWatchDog( 0, false);
283 }
284 
285 
286 void ArnScriptJobB::yield()
287 {
288  emit scheduleRequest( _id);
289 }
290 
291 
292 void ArnScriptJobB::quit()
293 {
294  if (_quitInProgress) return; // Quit is already in progress
295  _quitInProgress = true;
296 
297  ARN_JSENGINE& engine = _arnScr->engine();
298 #ifdef ARNUSE_SCRIPTJS
299  Q_UNUSED(engine)
300  engine.setInterrupted( true);
301 #else
302  engine.abortEvaluation( engine.currentContext()->throwError("Job quit"));
303 #endif
304 
305  QEvent* ev = new QEvent( EventQuit);
306  QCoreApplication::postEvent( this, ev, Qt::HighEventPriority);
307 }
308 
309 
310 void ArnScriptJobB::customEvent(QEvent *ev)
311 {
312  switch (ev->type()) {
313  case EventQuit:
314  _quitInProgress = false;
315  emit quitRequest( _id);
316  break;
317  default:;
318  }
319 }
320 
321 
322 void ArnScriptJobB::doTimeoutAbort()
323 {
324 #ifdef ARNUSE_SCRIPTJS
325  // qDebug() << "Script timeout:";
326  if (!_isStopped) {
327  ARN_JSENGINE& engine = _arnScr->engine();
328  engine.setInterrupted( false);
329  _isStopped = true;
330  _isRunning = false;
331  errorLog( "Error: Job run timeout sig");
332 
333  emit scheduleRequest( _id);
334  }
335 #else
336  // qDebug() << "Script timeout: isEval=" << _arnScr->engine().isEvaluating();
337  _isStopped = true;
338  _isRunning = false;
339  errorLog( "Error: Watchdog timeout"); // Extra error as throwError() not allways works ...
340  ARN_JSENGINE& engine = _arnScr->engine();
341  engine.abortEvaluation( engine.currentContext()->throwError("Job run timeout"));
342 
343  emit scheduleRequest( _id);
344 #endif
345 
346  emit timeoutAbort( _id);
347 }
348 
349 
350 void ArnScriptJobB::setSleepState( bool isSleepState)
351 {
352  _isSleepState = isSleepState;
353  //qDebug() << "ArnScriptJob: sleepState=" << isSleepState << " fileName=" << name();
354  emit scheduleRequest( _id);
355 }
356 
357 
358 bool ArnScriptJobB::isSleepState() const
359 {
360  return _isSleepState;
361 }
362 
363 
364 bool ArnScriptJobB::isRunable() const
365 {
366  return !_isSleepState && !_isStopped;
367 }
368 
369 
370 bool ArnScriptJobB::isStopped() const
371 {
372  return _isStopped;
373 }
374 
375 
376 bool ArnScriptJobB::isRunning() const
377 {
378  return _isRunning;
379 }
380 
381 
382 
383 void ArnScriptJobB::errorLog( const QString& txt)
384 {
385  ArnM::errorLog( txt + " name=" + name(), ArnError::ScriptError);
386  emit errorText( txt);
387 }
388 
389 
391 {
392 }
393 
394 
396 {
397 }
398 
399 
400 void ArnScriptJobFactory::setupJsObj( const QString& id, const ARN_JSVALUE& jsObj, ARN_JSENGINE& engine)
401 {
402  engine.globalObject().setProperty( id, jsObj);
403 }
404 
405 
406 bool ArnScriptJobFactory::setupInterface( const QString& id, QObject* interface, ARN_JSENGINE& engine)
407 {
408  if (id.isEmpty() || (interface == arnNullptr)) return false;
409 
410 #ifdef ARNUSE_SCRIPTJS
411  ARN_JSVALUE objScr = engine.newQObject( interface);
412 #else
413  QScriptValue objScr = engine.newQObject( interface, QScriptEngine::QtOwnership,
414  QScriptEngine::ExcludeSuperClassContents);
415 #endif
416  if (objScr.isNull()) return false;
417 
418  setupJsObj( id, objScr, engine);
419 
420  if (!interface->parent())
421  interface->setParent( engine.parent()); // Reparent interface to same parent as engine
422 
423  return true;
424  //MW: fix parrenting and delete failed install object
425 }
426 
427 
429 
430 ArnScriptWatchdog::ArnScriptWatchdog( ARN_JSENGINE* jsEngine, QObject* parent)
431  : QObject( parent)
432 {
433  _jsEngine = jsEngine;
434  _lastTimeMs = 0;
435  _isSetup = false;
436  _timer = new QTimer( this);
437  _timer->setSingleShot( true);
438 }
439 
440 
441 ArnScriptWatchdog::~ArnScriptWatchdog()
442 {
443 }
444 
445 
446 void ArnScriptWatchdog::setup()
447 {
448 #ifdef ARNUSE_SCRIPTJS
449  connect( _timer, &QTimer::timeout,
450  [this]() {
451  _mutex.lock();
452  if (_jsEngine)
453  _jsEngine->setInterrupted( true);
454  _mutex.unlock();
455  emit timeout();
456  });
457 #else
458  connect( _timer, SIGNAL(timeout()), this, SIGNAL(timeout()));
459 #endif
460  setTimeNow( _lastTimeMs );
461  _isSetup = true;
462 }
463 
464 
465 // Threadsafe
466 void ArnScriptWatchdog::setJsEngine( ARN_JSENGINE* jsEngine)
467 {
468  _mutex.lock();
469  _jsEngine = jsEngine;
470  _mutex.unlock();
471 }
472 
473 
474 // Threadsafe when using ARNUSE_SCRIPTJS
475 void ArnScriptWatchdog::setTime( int timeMs)
476 {
477  _lastTimeMs = timeMs;
478  if (_isSetup) {
479 #ifdef ARNUSE_SCRIPTJS
480  QMetaObject::invokeMethod( this, [timeMs, this]() { this->setTimeNow( timeMs); }, Qt::QueuedConnection);
481 #else
482  setTimeNow( timeMs);
483 #endif
484  }
485 }
486 
487 
488 void ArnScriptWatchdog::setTimeNow( int timeMs)
489 {
490  if (timeMs > 0)
491  _timer->start( timeMs);
492  else
493  _timer->stop();
494 }
495 
496 
498 
499 ArnScriptJob::ArnScriptJob( int id, QObject* parent) :
500  ArnScriptJobB( id, parent)
501 {
502  installInterface( "job", this);
503 #ifndef ARN_JSENGINE
504  installInterface( "config", _configObj);
505 #endif
506 }
507 
508 
509 QAtomicInt ArnScriptJobControl::_idCount(1);
510 
511 
513  QObject( parent)
514 {
515  _id = _idCount.fetchAndAddRelaxed(1);
516  _configObj = new QObject( this);
517  _isThreaded = true; // Default safe
518 }
519 
520 
521 void ArnScriptJobControl::setName( const QString& name)
522 {
523  if (_isThreaded) _mutex.lock();
524  _name = name;
525  if (_isThreaded) _mutex.unlock();
526 }
527 
528 
530 {
531  if (_isThreaded) _mutex.lock();
532  int retVal = _id;
533  if (_isThreaded) _mutex.unlock();
534 
535  return retVal;
536 }
537 
538 
540 {
541  if (_isThreaded) _mutex.lock();
542  QString retVal = _name;
543  if (_isThreaded) _mutex.unlock();
544 
545  return retVal;
546 }
547 
548 
549 void ArnScriptJobControl::addInterface( const QString& id)
550 {
551  if (_isThreaded) _mutex.lock();
552  if (!id.isEmpty()) _interfaceList += id;
553  _interfaceList.removeDuplicates();
554  if (_isThreaded) _mutex.unlock();
555 }
556 
557 
558 void ArnScriptJobControl::addInterfaceList( const QStringList& interfaceList)
559 {
560  if (_isThreaded) _mutex.lock();
561  _interfaceList += interfaceList;
562  _interfaceList.removeDuplicates();
563  if (_isThreaded) _mutex.unlock();
564 }
565 
566 
567 void ArnScriptJobControl::setScript( const QByteArray& script)
568 {
569  if (_isThreaded) _mutex.lock();
570  _script = script;
571  if (_isThreaded) _mutex.unlock();
572 
573  emit scriptChanged( _id);
574 }
575 
576 
577 QByteArray ArnScriptJobControl::script() const
578 {
579  if (_isThreaded) _mutex.lock();
580  QByteArray retVal = _script;
581  if (_isThreaded) _mutex.unlock();
582 
583  return retVal;
584 }
585 
586 
587 void ArnScriptJobControl::loadScriptFile( const QString& fileName)
588 {
589  if (fileName.isEmpty()) return;
590 
591  QFile file( fileName);
592  file.open( QIODevice::ReadOnly);
593 
594  setName( QFileInfo( file).baseName());
595  setScript( file.readAll());
596 }
597 
598 
599 bool ArnScriptJobControl::setConfig( const char* name, const QVariant& value)
600 {
601  if (!name) return false;
602 
603  if (_isThreaded) _mutex.lock();
604  bool retVal = _configObj->setProperty( name, value);
605  if (_isThreaded) _mutex.unlock();
606 
607  return retVal;
608 }
609 
610 
612 {
613  if (!obj) return;
614 
615  QList<QByteArray> nameList = obj->dynamicPropertyNames();
616  foreach (QByteArray name, nameList) {
617  setConfig( name.constData(), obj->property( name.constData()));
618  }
619 }
620 
621 
622 void ArnScriptJobControl::setThreaded( bool isThreaded)
623 {
624  _isThreaded = isThreaded;
625 }
626 
627 
630 {
631  if (!job) return;
632  if (!jobFactory) return;
633 
634  // qDebug() << "ScrJobConfig: setup job=" << _name;
635  job->setJobFactory( jobFactory);
636  foreach (QString id, _interfaceList) {
637  job->installExtension( id, this); // Depends on jobFactory
638  }
639  job->addConfig( _configObj);
640  job->evaluateScript( _script, _name);
641  job->setupScript();
642 }
643 
644 
645 QVariant ArnScriptJobControl::config( const char* name) const
646 {
647  if (!name) return QVariant();
648 
649  if (_isThreaded) _mutex.lock();
650  QVariant retVal = _configObj->property( name);
651  if (_isThreaded) _mutex.unlock();
652 
653  return retVal;
654 }
void setThreaded(bool isThreaded)
ArnScriptJobControl(QObject *parent=arnNullptr)
void addInterfaceList(const QStringList &interfaceList)
static bool setupInterface(const QString &id, QObject *interface, ARN_JSENGINE &engine)
QByteArray script() const
void setName(const QString &name)
bool setConfig(const char *name, const QVariant &value)
static void setupJsObj(const QString &id, const ARN_JSVALUE &jsObj, ARN_JSENGINE &engine)
void scriptChanged(int id)
QVariant config(const char *name) const
#define ARN_JSVALUE
Definition: ArnScript.hpp:55
#define ARN_JSENGINE
Definition: ArnScript.hpp:54
Must be thread-safe as subclassed.
Is thread-safe (except doSetupJob)
Interface class to be normally used, is also Script Job interface.
ArnScriptJob(int id, QObject *parent=arnNullptr)
const QEvent::Type EventQuit
QString name() const
void addConfig(QObject *obj)
#define ARN_JSVALUE_LIST
Definition: ArnScript.hpp:56
static void errorLog(QString errText, ArnError err=ArnError::Undef, void *reference=arnNullptr)
Definition: ArnM.cpp:1025
void setScript(const QByteArray &script)
void doSetupJob(ArnScriptJob *job, ArnScriptJobFactory *jobFactory)
Not threadsafe, only run in same thread as script.
void loadScriptFile(const QString &fileName)
virtual ~ArnScriptJobFactory()
void addInterface(const QString &id)