From e96c2ee30342b5a198025c3dd0c51bf43688d9ee Mon Sep 17 00:00:00 2001 From: Javier Date: Fri, 5 Dec 2014 22:56:27 +0100 Subject: partial implementation of geolocation --- daemon/jskitmanager.cpp | 10 ++- daemon/jskitmanager.h | 2 + daemon/jskitobjects.cpp | 172 +++++++++++++++++++++++++++++++++++++++++------- daemon/jskitobjects.h | 20 ++++-- 4 files changed, 175 insertions(+), 29 deletions(-) diff --git a/daemon/jskitmanager.cpp b/daemon/jskitmanager.cpp index 70ea4bd..9c739fc 100644 --- a/daemon/jskitmanager.cpp +++ b/daemon/jskitmanager.cpp @@ -29,6 +29,14 @@ bool JSKitManager::isJSKitAppRunning() const return _engine != 0; } +QString JSKitManager::describeError(QJSValue error) +{ + return QString("%1:%2: %3") + .arg(error.property("fileName").toString()) + .arg(error.property("lineNumber").toInt()) + .arg(error.toString()); +} + void JSKitManager::showConfiguration() { if (_engine) { @@ -134,7 +142,7 @@ void JSKitManager::startJsApp() QJSValue result = _engine->evaluate(script, scriptFile.fileName()); if (result.isError()) { - logger()->warn() << "error while evaluating JSKit script:" << result.toString(); + logger()->warn() << "error while evaluating JSKit script:" << describeError(result); } logger()->debug() << "JS script evaluated"; diff --git a/daemon/jskitmanager.h b/daemon/jskitmanager.h index 1f842b7..873489b 100644 --- a/daemon/jskitmanager.h +++ b/daemon/jskitmanager.h @@ -22,6 +22,8 @@ public: QJSEngine * engine(); bool isJSKitAppRunning() const; + static QString describeError(QJSValue error); + signals: void appNotification(const QUuid &uuid, const QString &title, const QString &body); void appOpenUrl(const QUrl &url); diff --git a/daemon/jskitobjects.cpp b/daemon/jskitobjects.cpp index 3b4584b..b3820b5 100644 --- a/daemon/jskitobjects.cpp +++ b/daemon/jskitobjects.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "jskitobjects.h" JSKitPebble::JSKitPebble(const AppInfo &info, JSKitManager *mgr) @@ -45,7 +46,7 @@ void JSKitPebble::sendAppMessage(QJSValue message, QJSValue callbackForAck, QJSV QJSValue result = callbackForAck.call(); if (result.isError()) { logger()->warn() << "error while invoking ACK callback" << callbackForAck.toString() << ":" - << result.toString(); + << JSKitManager::describeError(result); } } else { logger()->debug() << "Ack callback not callable"; @@ -56,7 +57,7 @@ void JSKitPebble::sendAppMessage(QJSValue message, QJSValue callbackForAck, QJSV QJSValue result = callbackForNack.call(); if (result.isError()) { logger()->warn() << "error while invoking NACK callback" << callbackForNack.toString() << ":" - << result.toString(); + << JSKitManager::describeError(result); } } else { logger()->debug() << "Nack callback not callable"; @@ -93,7 +94,7 @@ void JSKitPebble::invokeCallbacks(const QString &type, const QJSValueList &args) QJSValue result = it->call(args); if (result.isError()) { logger()->warn() << "error while invoking callback" << type << it->toString() << ":" - << result.toString(); + << JSKitManager::describeError(result); } } } @@ -287,12 +288,11 @@ void JSKitXMLHttpRequest::handleReplyFinished() emit statusChanged(); emit responseTextChanged(); - if (_onload.isCallable()) { logger()->debug() << "going to call onload handler:" << _onload.toString(); QJSValue result = _onload.callWithInstance(_mgr->engine()->newQObject(this)); if (result.isError()) { - logger()->warn() << "JS error on onload handler:" << result.toString(); + logger()->warn() << "JS error on onload handler:" << JSKitManager::describeError(result); } } else { logger()->debug() << "No onload set"; @@ -312,7 +312,7 @@ void JSKitXMLHttpRequest::handleReplyError(QNetworkReply::NetworkError code) logger()->debug() << "going to call onerror handler:" << _onload.toString(); QJSValue result = _onerror.callWithInstance(_mgr->engine()->newQObject(this)); if (result.isError()) { - logger()->warn() << "JS error on onerror handler:" << result.toString(); + logger()->warn() << "JS error on onerror handler:" << JSKitManager::describeError(result); } } } @@ -320,7 +320,6 @@ void JSKitXMLHttpRequest::handleReplyError(QNetworkReply::NetworkError code) JSKitGeolocation::JSKitGeolocation(JSKitManager *mgr) : QObject(mgr), _mgr(mgr), _source(0), _lastWatchId(0) { - } void JSKitGeolocation::getCurrentPosition(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options) @@ -331,28 +330,59 @@ void JSKitGeolocation::getCurrentPosition(const QJSValue &successCallback, const int JSKitGeolocation::watchPosition(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options) { - logger()->debug() << Q_FUNC_INFO; return setupWatcher(successCallback, errorCallback, options, false); } void JSKitGeolocation::clearWatch(int watchId) { - logger()->debug() << Q_FUNC_INFO; + removeWatcher(watchId); } void JSKitGeolocation::handleError(QGeoPositionInfoSource::Error error) { - logger()->debug() << Q_FUNC_INFO; + logger()->warn() << "positioning error: " << error; + // TODO } void JSKitGeolocation::handlePosition(const QGeoPositionInfo &pos) { logger()->debug() << Q_FUNC_INFO; + if (_watches.empty()) { + logger()->warn() << "got position update but no one is watching"; + } + + QJSValue obj = buildPositionObject(pos); + + for (auto it = _watches.begin(); it != _watches.end(); /*no adv*/) { + invokeCallback(it->successCallback, obj); + + if (it->once) { + it = _watches.erase(it); + } else { + ++it; + } + } + + if (_watches.empty()) { + _source->stopUpdates(); + } } void JSKitGeolocation::handleTimeout() { logger()->debug() << Q_FUNC_INFO; + // TODO +} + +uint JSKitGeolocation::minimumTimeout() const +{ + uint minimum = std::numeric_limits::max(); + Q_FOREACH(const Watcher &watcher, _watches) { + if (!watcher.once) { + minimum = qMin(watcher.timeout, minimum); + } + } + return minimum; } int JSKitGeolocation::setupWatcher(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options, bool once) @@ -362,10 +392,13 @@ int JSKitGeolocation::setupWatcher(const QJSValue &successCallback, const QJSVal watcher.errorCallback = errorCallback; watcher.highAccuracy = options.value("enableHighAccuracy").toBool(); watcher.timeout = options.value("timeout", 0xFFFFFFFFU).toUInt(); - watcher.maximumAge = options.value("maximumAge", 0).toUInt(); watcher.once = once; watcher.watchId = ++_lastWatchId; + uint maximumAge = options.value("maximumAge", 0).toUInt(); + + logger()->debug() << "setting up watcher, gps=" << watcher.highAccuracy << "timeout=" << watcher.timeout << "maximumAge=" << maximumAge << "once=" << once; + if (!_source) { _source = QGeoPositionInfoSource::createDefaultSource(this); connect(_source, static_cast(&QGeoPositionInfoSource::error), @@ -376,14 +409,17 @@ int JSKitGeolocation::setupWatcher(const QJSValue &successCallback, const QJSVal this, &JSKitGeolocation::handleTimeout); } - if (once && watcher.maximumAge > 0) { - QDateTime threshold = QDateTime::currentDateTime().addMSecs(-watcher.maximumAge); + if (maximumAge > 0) { + QDateTime threshold = QDateTime::currentDateTime().addMSecs(-qint64(maximumAge)); QGeoPositionInfo pos = _source->lastKnownPosition(watcher.highAccuracy); + logger()->debug() << "got pos timestamp" << pos.timestamp() << " but we want" << threshold; if (pos.isValid() && pos.timestamp() >= threshold) { - invokeSuccessCallback(watcher, pos); - return -1; - } else if (watcher.timeout == 0) { - invokeErrorCallback(watcher); + invokeCallback(watcher.successCallback, buildPositionObject(pos)); + if (once) { + return -1; + } + } else if (watcher.timeout == 0 && once) { + invokeCallback(watcher.errorCallback, buildPositionErrorObject(TIMEOUT, "no cached position")); return -1; } } @@ -391,21 +427,111 @@ int JSKitGeolocation::setupWatcher(const QJSValue &successCallback, const QJSVal if (once) { _source->requestUpdate(watcher.timeout); } else { - // TODO _source->setInterval to the minimum of all watches + uint timeout = minimumTimeout(); + logger()->debug() << "setting location update interval to" << timeout; + _source->setUpdateInterval(timeout); + logger()->debug() << "starting location updates"; _source->startUpdates(); } + _watches.append(watcher); + + logger()->debug() << "added new watch" << watcher.watchId; + return watcher.watchId; } -void JSKitGeolocation::invokeSuccessCallback(Watcher &watcher, const QGeoPositionInfo &pos) +void JSKitGeolocation::removeWatcher(int watchId) { - // TODO + Watcher watcher; + + logger()->debug() << "removing watchId" << watcher.watchId; + + for (int i = 0; i < _watches.size(); i++) { + if (_watches[i].watchId == watchId) { + watcher = _watches.takeAt(i); + break; + } + } + + if (watcher.watchId != watchId) { + logger()->warn() << "watchId not found"; + return; + } + + if (_watches.empty()) { + logger()->debug() << "stopping updates"; + _source->stopUpdates(); + } else { + uint timeout = minimumTimeout(); + logger()->debug() << "setting location update interval to" << timeout; + _source->setUpdateInterval(timeout); + } } -void JSKitGeolocation::invokeErrorCallback(Watcher &watcher) +QJSValue JSKitGeolocation::buildPositionObject(const QGeoPositionInfo &pos) { - if (watcher.errorCallback.isCallable()) { - watcher.errorCallback.call(); // TODO this, eventArgs + QJSEngine *engine = _mgr->engine(); + QJSValue obj = engine->newObject(); + QJSValue coords = engine->newObject(); + QJSValue timestamp = engine->toScriptValue(pos.timestamp().toMSecsSinceEpoch()); + + coords.setProperty("latitude", engine->toScriptValue(pos.coordinate().latitude())); + coords.setProperty("longitude", engine->toScriptValue(pos.coordinate().longitude())); + if (pos.coordinate().type() == QGeoCoordinate::Coordinate3D) { + coords.setProperty("altitude", engine->toScriptValue(pos.coordinate().altitude())); + } else { + coords.setProperty("altitude", engine->toScriptValue(0)); + } + + coords.setProperty("accuracy", engine->toScriptValue(pos.attribute(QGeoPositionInfo::HorizontalAccuracy))); + + if (pos.hasAttribute(QGeoPositionInfo::VerticalAccuracy)) { + coords.setProperty("altitudeAccuracy", engine->toScriptValue(pos.attribute(QGeoPositionInfo::VerticalAccuracy))); + } else { + coords.setProperty("altitudeAccuracy", engine->toScriptValue(0)); + } + + if (pos.hasAttribute(QGeoPositionInfo::Direction)) { + coords.setProperty("heading", engine->toScriptValue(pos.attribute(QGeoPositionInfo::Direction))); + } else { + coords.setProperty("heading", engine->toScriptValue(0)); + } + + if (pos.hasAttribute(QGeoPositionInfo::GroundSpeed)) { + coords.setProperty("speed", engine->toScriptValue(pos.attribute(QGeoPositionInfo::GroundSpeed))); + } else { + coords.setProperty("speed", engine->toScriptValue(0)); + } + + obj.setProperty("coords", coords); + obj.setProperty("timestamp", timestamp); + + logger()->debug() << obj.toString(); + + return obj; +} + +QJSValue JSKitGeolocation::buildPositionErrorObject(PositionError error, const QString &message) +{ + QJSEngine *engine = _mgr->engine(); + QJSValue obj = engine->newObject(); + + obj.setProperty("code", engine->toScriptValue(error)); + obj.setProperty("message", engine->toScriptValue(message)); + + return obj; +} + +void JSKitGeolocation::invokeCallback(QJSValue callback, QJSValue event) +{ + if (callback.isCallable()) { + logger()->debug() << "invoking callback" << callback.toString(); + QJSValue result = callback.call(QJSValueList({event})); + if (result.isError()) { + logger()->warn() << "while invoking callback: " << JSKitManager::describeError(result); + } + } else { + logger()->warn() << "callback is not callable"; } } diff --git a/daemon/jskitobjects.h b/daemon/jskitobjects.h index 9c9b84e..0dccc05 100644 --- a/daemon/jskitobjects.h +++ b/daemon/jskitobjects.h @@ -98,9 +98,9 @@ public: DONE = 4 }; - Q_INVOKABLE void open(const QString &method, const QString &url, bool async); + Q_INVOKABLE void open(const QString &method, const QString &url, bool async = false); Q_INVOKABLE void setRequestHeader(const QString &header, const QString &value); - Q_INVOKABLE void send(const QString &body); + Q_INVOKABLE void send(const QString &body = QString()); Q_INVOKABLE void abort(); QJSValue onload() const; @@ -138,6 +138,7 @@ private: class JSKitGeolocation : public QObject { Q_OBJECT + Q_ENUMS(PositionError) LOG4QT_DECLARE_QCLASS_LOGGER struct Watcher; @@ -145,6 +146,12 @@ class JSKitGeolocation : public QObject public: explicit JSKitGeolocation(JSKitManager *mgr); + enum PositionError { + PERMISSION_DENIED = 1, + POSITION_UNAVAILABLE = 2, + TIMEOUT = 3 + }; + Q_INVOKABLE void getCurrentPosition(const QJSValue &successCallback, const QJSValue &errorCallback = QJSValue(), const QVariantMap &options = QVariantMap()); Q_INVOKABLE int watchPosition(const QJSValue &successCallback, const QJSValue &errorCallback = QJSValue(), const QVariantMap &options = QVariantMap()); Q_INVOKABLE void clearWatch(int watchId); @@ -155,9 +162,13 @@ private slots: void handleTimeout(); private: + uint minimumTimeout() const; int setupWatcher(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options, bool once); - void invokeSuccessCallback(Watcher &watcher, const QGeoPositionInfo &pos); - void invokeErrorCallback(Watcher &watcher); + void removeWatcher(int watchId); + QJSValue buildPositionObject(const QGeoPositionInfo &pos); + QJSValue buildPositionErrorObject(PositionError error, const QString &message = QString()); + QJSValue buildPositionErrorObject(const QGeoPositionInfoSource::Error error); + void invokeCallback(QJSValue callback, QJSValue event); private: JSKitManager *_mgr; @@ -170,7 +181,6 @@ private: bool once; bool highAccuracy; uint timeout; - uint maximumAge; }; QList _watches; -- cgit v1.2.3