diff options
author | Kevron Rees <tripzero.kev@gmail.com> | 2015-01-14 19:47:45 -0800 |
---|---|---|
committer | Kevron Rees <tripzero.kev@gmail.com> | 2015-01-14 19:47:45 -0800 |
commit | ac46d03e0a837a4411e7cdb7b61b21171d9bcbdc (patch) | |
tree | f678768bff4c839b5d4b536a3715f6b5d93d80dd | |
parent | fc8af9131b505fa0b4908c8b8a2be1dcd5f83c47 (diff) | |
parent | bb2360338fbf4afdf327c6e04a37b0541c767327 (diff) | |
download | automotive-message-broker-ac46d03e0a837a4411e7cdb7b61b21171d9bcbdc.tar.gz |
[AMB] - manual merge
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | docs/amb.in.fidl | 23 | ||||
-rw-r--r-- | lib/asyncqueue.hpp | 2 | ||||
-rw-r--r-- | lib/timestamp.cpp | 40 | ||||
-rw-r--r-- | lib/timestamp.h | 20 | ||||
-rw-r--r-- | plugins/bluemonkey/CMakeLists.txt | 5 | ||||
-rw-r--r-- | plugins/bluemonkey/bluemonkey.in.json | 6 | ||||
-rw-r--r-- | plugins/database/CMakeLists.txt | 4 | ||||
-rw-r--r-- | plugins/database/database.in.json | 0 | ||||
-rw-r--r-- | plugins/database/databasesink.cpp | 22 | ||||
-rw-r--r-- | plugins/database/databasesink.h | 4 | ||||
-rw-r--r-- | plugins/examplesink.cpp | 28 | ||||
-rw-r--r-- | plugins/websocket/protocol.idl | 5 | ||||
-rw-r--r-- | plugins/websocket/websocketsinkmanager.cpp | 1 | ||||
-rw-r--r-- | plugins/websocket/websocketsource.cpp | 80 | ||||
-rw-r--r-- | plugins/websocket/websocketsource.h | 6 | ||||
-rw-r--r-- | tools/ambctl.py | 56 |
17 files changed, 229 insertions, 75 deletions
@@ -60,7 +60,7 @@ You will also need to edit your config to enable the Qt-based mainloop: ~~~~~~~~~~~~~{.json} { - "mainloop" : "/usr/lib/x86_64-linux-gnu/automotive-message-broker/qtmainloopplugin.so", + "mainloop" : "/usr/lib/i386-linux-gnu/automotive-message-broker/qtmainloopplugin.so", "plugins" : "/etc/ambd/plugins.d" } ~~~~~~~~~~~~~ diff --git a/docs/amb.in.fidl b/docs/amb.in.fidl index 0a63fe20..2fd78306 100644 --- a/docs/amb.in.fidl +++ b/docs/amb.in.fidl @@ -116,6 +116,27 @@ map Dictionary { } /*! + * \brief HistoryObject is returned with GetHistory call + */ +interface HistoryObject { + + /*! + * \brief Name of property + */ + attribute String Name readonly + + /*! + * \brief Value of property + */ + attribute Variant Value readonly + + /*! + * \brief Time in seconds since unix epoch. + */ + attribute Double Time readonly +} + +/*! * VehiclePropertyType * \brief VehiclePropertyType is the base class for all Data types. */ @@ -138,7 +159,7 @@ interface VehiclePropertyType { * \arg endTime time stamp in Seconds since Unix Epoc */ method GetHistory(Double beginTime, Double endTime) { - out{ Dictionary result} + out{ array of HistoryObject result } } } diff --git a/lib/asyncqueue.hpp b/lib/asyncqueue.hpp index ada42ac7..61ff412e 100644 --- a/lib/asyncqueue.hpp +++ b/lib/asyncqueue.hpp @@ -64,7 +64,9 @@ public: } if(!mQueue.size()) + { throw std::runtime_error("nothing in queue"); + } auto itr = mQueue.begin(); diff --git a/lib/timestamp.cpp b/lib/timestamp.cpp index 112a7d1d..33e01823 100644 --- a/lib/timestamp.cpp +++ b/lib/timestamp.cpp @@ -6,9 +6,49 @@ double amb::currentTime() { + return Timestamp::instance()->currentTime(); +} + +amb::Timestamp* amb::Timestamp::mInstance = nullptr; + +amb::Timestamp::Timestamp() +{ + auto tm = std::chrono::system_clock::now(); + auto tm2 = std::chrono::steady_clock::now(); + + double eTime = (std::chrono::duration_cast<std::chrono::milliseconds>(tm.time_since_epoch()).count() / 1000.00); + double sTime = (std::chrono::duration_cast<std::chrono::milliseconds>(tm2.time_since_epoch()).count() / 1000.00); + + startTimeEpoch = eTime - sTime; +} + +double amb::Timestamp::currentTime() +{ auto tm = std::chrono::steady_clock::now(); double time = std::chrono::duration_cast<std::chrono::milliseconds>(tm.time_since_epoch()).count() / 1000.00; return time; } + +double amb::Timestamp::epochTime(double time) +{ + return startTimeEpoch + time; +} + +double amb::Timestamp::epochTime() +{ + auto tm = std::chrono::system_clock::now(); + + double time = std::chrono::duration_cast<std::chrono::milliseconds>(tm.time_since_epoch()).count() / 1000.00; + + return time; +} + +amb::Timestamp* amb::Timestamp::instance() +{ + if(!mInstance) + mInstance = new Timestamp(); + + return mInstance; +} diff --git a/lib/timestamp.h b/lib/timestamp.h index 745bc13b..5300488b 100644 --- a/lib/timestamp.h +++ b/lib/timestamp.h @@ -6,6 +6,26 @@ namespace amb { double currentTime(); +class Timestamp { +protected: + Timestamp(); + +public: + + double currentTime(); + + double epochTime(double time); + + double epochTime(); + +public: + static Timestamp *instance(); + +private: + double startTimeEpoch; + static Timestamp* mInstance; +}; + } #endif diff --git a/plugins/bluemonkey/CMakeLists.txt b/plugins/bluemonkey/CMakeLists.txt index bde4e189..5c97b54a 100644 --- a/plugins/bluemonkey/CMakeLists.txt +++ b/plugins/bluemonkey/CMakeLists.txt @@ -57,8 +57,11 @@ configure_file (${CMAKE_CURRENT_SOURCE_DIR}/config.js ${CMAKE_CURRENT_BINARY_DIR install(TARGETS bluemonkeyplugin LIBRARY DESTINATION ${PLUGIN_INSTALL_PATH}) install (FILES ${config_files} DESTINATION /etc/ambd/bluemonkey) -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/README ${CMAKE_CURRENT_BINARY_DIR}/bluemonkey.README @ONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/README ${CMAKE_CURRENT_BINARY_DIR}/bluemonkey.README.md @ONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/bluemonkey.in.idl ${CMAKE_CURRENT_BINARY_DIR}/docs/bluemonkey.idl @ONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/bluemonkey.in.json ${CMAKE_CURRENT_BINARY_DIR}/bluemonkey @ONLY) + +install (FILES ${CMAKE_CURRENT_BINARY_DIR}/bluemonkey DESTINATION ${PLUGIN_SEGMENT_INSTALL_PATH}) set(bluemonkey_doc_files ${CMAKE_CURRENT_BINARY_DIR}/bluemonkey.README ${CMAKE_CURRENT_BINARY_DIR}/docs/bluemonkey.idl) diff --git a/plugins/bluemonkey/bluemonkey.in.json b/plugins/bluemonkey/bluemonkey.in.json new file mode 100644 index 00000000..6d6609a2 --- /dev/null +++ b/plugins/bluemonkey/bluemonkey.in.json @@ -0,0 +1,6 @@ +{ + "name" : "bluemonkey", + "path" : "@PLUGIN_INSTALL_PATH@/bluemonkeyplugin.so", + "enabled" : false, + "config" : "/etc/ambd/bluemonkey/config.js" +} diff --git a/plugins/database/CMakeLists.txt b/plugins/database/CMakeLists.txt index 041fb01f..7f0dd495 100644 --- a/plugins/database/CMakeLists.txt +++ b/plugins/database/CMakeLists.txt @@ -19,6 +19,10 @@ install(TARGETS databasesinkplugin LIBRARY DESTINATION ${PLUGIN_INSTALL_PATH}) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/README ${CMAKE_CURRENT_BINARY_DIR}/database.README @ONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/database.in.json ${CMAKE_CURRENT_BINARY_DIR}/database @ONLY) + +install (FILES ${CMAKE_CURRENT_BINARY_DIR}/database DESTINATION ${PLUGIN_SEGMENT_INSTALL_PATH}) + install (FILES ${CMAKE_CURRENT_BINARY_DIR}/database.README DESTINATION ${DOC_INSTALL_DIR}/plugins) endif(database_plugin) diff --git a/plugins/database/database.in.json b/plugins/database/database.in.json new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/plugins/database/database.in.json diff --git a/plugins/database/databasesink.cpp b/plugins/database/databasesink.cpp index 8881ae53..5a933ef0 100644 --- a/plugins/database/databasesink.cpp +++ b/plugins/database/databasesink.cpp @@ -28,10 +28,10 @@ static void * cbFunc(Shared* shared) vector<DictionaryList<string> > insertList; + double startTime = amb::currentTime(); + while(1) { - usleep(timeout*1000); - DBObject obj = shared->queue.pop(); if( obj.quit ) @@ -45,7 +45,7 @@ static void * cbFunc(Shared* shared) NameValuePair<string> two("value", obj.value); NameValuePair<string> three("source", obj.source); NameValuePair<string> zone("zone", boost::lexical_cast<string>(obj.zone)); - NameValuePair<string> four("time", boost::lexical_cast<string>(obj.time)); + NameValuePair<string> four("time", boost::lexical_cast<string>(amb::Timestamp::instance()->epochTime(obj.time))); NameValuePair<string> five("sequence", boost::lexical_cast<string>(obj.sequence)); NameValuePair<string> six("tripId", shared->tripId); @@ -59,8 +59,10 @@ static void * cbFunc(Shared* shared) insertList.push_back(dict); - if(insertList.size() >= bufferLength) + if(insertList.size() >= bufferLength && amb::currentTime() - startTime >= timeout / 1000) { + startTime = amb::currentTime(); + shared->db->exec("BEGIN IMMEDIATE TRANSACTION"); for(int i=0; i< insertList.size(); i++) { @@ -340,9 +342,9 @@ void DatabaseSink::initDb() shared->db->init(databaseName->value<std::string>(), tablename, tablecreate); } -void DatabaseSink::setDatabaseFileName(string filename) +void DatabaseSink::updateForNewDbFilename() { - bool isLogging = databaseLogging->value<bool>(); + bool wasLogging = databaseLogging->value<bool>(); stopDb(); initDb(); @@ -366,7 +368,7 @@ void DatabaseSink::setDatabaseFileName(string filename) } } - if(isLogging) + if(wasLogging) { stopDb(); startDb(); @@ -411,8 +413,8 @@ void DatabaseSink::init() { if(configuration.find("databaseFile") != configuration.end()) { - databaseName->setValue(configuration["databaseFile"]); - setDatabaseFileName(configuration["databaseFile"]); + setValue(databaseName, configuration["databaseFile"]); + updateForNewDbFilename(); } DebugOut() << "databaseLogging: " << databaseLogging->value<bool>() << endl; @@ -517,7 +519,7 @@ AsyncPropertyReply *DatabaseSink::setProperty(const AsyncSetPropertyRequest &req } else if(request.property == DatabaseFile) { - setDatabaseFileName(databaseName->value<std::string>()); + updateForNewDbFilename(); } else if( request.property == DatabasePlayback) { diff --git a/plugins/database/databasesink.h b/plugins/database/databasesink.h index 459ec928..3b7efdbf 100644 --- a/plugins/database/databasesink.h +++ b/plugins/database/databasesink.h @@ -97,7 +97,7 @@ class Shared { public: Shared() - :queue(true) + :queue(true, true) { db = new BaseDB; } @@ -163,7 +163,7 @@ private: //methods: void startDb(); void startPlayback(); void initDb(); - void setDatabaseFileName(std::string filename); + void updateForNewDbFilename(); private: PropertyList mSubscriptions; diff --git a/plugins/examplesink.cpp b/plugins/examplesink.cpp index bd0afef4..f696479d 100644 --- a/plugins/examplesink.cpp +++ b/plugins/examplesink.cpp @@ -1,19 +1,19 @@ /* - Copyright (C) 2012 Intel Corporation + Copyright (C) 2012 Intel Corporation - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -169,8 +169,8 @@ void ExampleSink::supportedChanged(const PropertyList & supportedProperties) AsyncRangePropertyRequest vehicleSpeedFromLastWeek; - vehicleSpeedFromLastWeek.timeBegin = amb::currentTime() - 10; - vehicleSpeedFromLastWeek.timeEnd = amb::currentTime(); + vehicleSpeedFromLastWeek.timeBegin = amb::Timestamp::instance()->epochTime() - 10; + vehicleSpeedFromLastWeek.timeEnd = amb::Timestamp::instance()->epochTime(); PropertyList requestList; requestList.push_back(VehicleProperty::VehicleSpeed); diff --git a/plugins/websocket/protocol.idl b/plugins/websocket/protocol.idl index cb24cf1e..18b46a3e 100644 --- a/plugins/websocket/protocol.idl +++ b/plugins/websocket/protocol.idl @@ -216,6 +216,11 @@ interface GetSupportedReply : BaseMessage { const DOMString name = "getSupported"; /*! + * \brief systemTime of the other system. Used to synchronize time + */ + attribute double systemTime; + + /*! * \brief data - array of properties supported by the system */ attribute Property[] data; diff --git a/plugins/websocket/websocketsinkmanager.cpp b/plugins/websocket/websocketsinkmanager.cpp index 5975c65f..6003af9b 100644 --- a/plugins/websocket/websocketsinkmanager.cpp +++ b/plugins/websocket/websocketsinkmanager.cpp @@ -558,6 +558,7 @@ static int websocket_callback(struct libwebsocket_context *context,struct libweb reply["name"] = "getSupported"; reply["transactionid"] = id.c_str(); reply["data"] = list; + reply["systemTime"] = amb::Timestamp::instance()->epochTime(); lwsWriteVariant(wsi, reply); } diff --git a/plugins/websocket/websocketsource.cpp b/plugins/websocket/websocketsource.cpp index f4b2ff9e..a52cdb18 100644 --- a/plugins/websocket/websocketsource.cpp +++ b/plugins/websocket/websocketsource.cpp @@ -150,18 +150,18 @@ UniquePropertyCache properties; static int callback_http_only(libwebsocket_context *context,struct libwebsocket *wsi,enum libwebsocket_callback_reasons reason,void *user, void *in, size_t len); static struct libwebsocket_protocols protocols[] = { - { - "http-only", - callback_http_only, - 0, - 128, - }, - { /* end of list */ - NULL, - NULL, - 0, - 0 - } +{ + "http-only", + callback_http_only, + 0, + 128, +}, +{ /* end of list */ + NULL, + NULL, + 0, + 0 +} }; //Called when a client connects, subscribes, or unsubscribes. @@ -333,7 +333,7 @@ static int callback_http_only(libwebsocket_context *context, struct libwebsocket //printf("Connection closed!\n"); break; - //case LWS_CALLBACK_PROTOCOL_INIT: + //case LWS_CALLBACK_PROTOCOL_INIT: case LWS_CALLBACK_CLIENT_ESTABLISHED: { //This happens when a client initally connects. We need to request the support event types. @@ -369,22 +369,45 @@ static int callback_http_only(libwebsocket_context *context, struct libwebsocket manager->expectedMessageFrames = 0; } - QJsonDocument doc; + DebugOut(7) << "data received: " << d.data() << endl; - if(doBinary) - doc = QJsonDocument::fromBinaryData(d); - else + int start = d.indexOf("{"); + + if(manager->incompleteMessage.isEmpty() && start > 0) { - doc = QJsonDocument::fromJson(d); - DebugOut(7)<<d.data()<<endl; + DebugOut(7)<< "We have an incomplete message at the beginning. Toss it away." << endl; + d = d.right(start-1); + } + + + int end = d.lastIndexOf("}"); + + if(end == -1) + { + manager->incompleteMessage += d; + break; } + QByteArray tryMessage = manager->incompleteMessage + d.left(end+1); + + DebugOut(6) << "Trying to parse message: " << tryMessage.data() << endl; + + QJsonDocument doc; + + QJsonParseError parseError; + + doc = QJsonDocument::fromJson(tryMessage, &parseError); + if(doc.isNull()) { - DebugOut(DebugOut::Warning)<<"Invalid message"<<endl; + DebugOut(7) << "Invalid or incomplete message" << endl; + DebugOut(7) << parseError.errorString().toStdString() << ": " << parseError.offset << endl; + manager->incompleteMessage += d; break; } + manager->incompleteMessage = end == d.length()-1 ? "" : d.right(end); + QVariantMap call = doc.toVariant().toMap(); string type = call["type"].toString().toStdString(); @@ -457,6 +480,12 @@ static int callback_http_only(libwebsocket_context *context, struct libwebsocket DebugOut() << __SMALLFILE__ <<":"<< __LINE__ << "Got getSupported request"<<endl; + double serverTime = call["systemTime"].toDouble(); + + DebugOut() << "Server time is: " << serverTime << endl; + + if(serverTime) + source->serverTimeOffset = amb::Timestamp::instance()->epochTime() - serverTime; Q_FOREACH(QVariant p, supported) { @@ -484,7 +513,7 @@ static int callback_http_only(libwebsocket_context *context, struct libwebsocket std::string name = obj["property"].toString().toStdString(); std::string value = obj["value"].toString().toStdString(); - double timestamp = obj["timestamp"].toDouble(); + double timestamp = obj["timestamp"].toDouble() + source->serverTimeOffset; int sequence = obj["sequence"].toInt(); AbstractPropertyType* type = VehicleProperty::getPropertyTypeForPropertyNameValue(name, value); @@ -620,7 +649,7 @@ static int callback_http_only(libwebsocket_context *context, struct libwebsocket break; } - return 0; + return 0; } } void WebSocketSource::updateSupported() @@ -635,7 +664,8 @@ void WebSocketSource::updateSupported() m_re->updateSupported(list, PropertyList(), this); } -WebSocketSource::WebSocketSource(AbstractRoutingEngine *re, map<string, string> config) : AbstractSource(re, config), partialMessageIndex(0),expectedMessageFrames(0) +WebSocketSource::WebSocketSource(AbstractRoutingEngine *re, map<string, string> config) : AbstractSource(re, config), partialMessageIndex(0),expectedMessageFrames(0), + serverTimeOffset(0) { m_sslEnabled = false; clientConnected = false; @@ -738,8 +768,8 @@ void WebSocketSource::getRangePropertyAsync(AsyncRangePropertyReply *reply) replyvar["type"] = "method"; replyvar["name"] = "getRanged"; replyvar["transactionid"] = uuid.c_str(); - replyvar["timeBegin"] = reply->timeBegin; - replyvar["timeEnd"] = reply->timeEnd; + replyvar["timeBegin"] = reply->timeBegin - serverTimeOffset; + replyvar["timeEnd"] = reply->timeEnd - serverTimeOffset; replyvar["sequenceBegin"] = reply->sequenceBegin; replyvar["sequenceEnd"] = reply->sequenceEnd; diff --git a/plugins/websocket/websocketsource.h b/plugins/websocket/websocketsource.h index 4af23ba2..966fee3d 100644 --- a/plugins/websocket/websocketsource.h +++ b/plugins/websocket/websocketsource.h @@ -62,6 +62,12 @@ public: int expectedMessageFrames; PropertyInfo getPropertyInfo(const VehicleProperty::Property & property); + + /*! + * \brief serverTimeOffset offset between server time and local time + */ + double serverTimeOffset; +private: }; #endif // WEBSOCKETSOURCE_H diff --git a/tools/ambctl.py b/tools/ambctl.py index 0a84d26c..c064a506 100644 --- a/tools/ambctl.py +++ b/tools/ambctl.py @@ -52,7 +52,7 @@ class Autocomplete: except dbus.exceptions.DBusException, error: print error - def complete(self, partialString): + def complete(self, partialString, commandsOnly = False): results = [] sameString = "" @@ -61,9 +61,10 @@ class Autocomplete: if not (len(partialString)) or cmd.name.startswith(partialString): results.append(cmd.name) - for property in self.properties: - if not(len(partialString)) or property.startswith(partialString): - results.append(str(property)) + if not commandsOnly: + for property in self.properties: + if not(len(partialString)) or property.startswith(partialString): + results.append(str(property)) if len(results) > 1 and len(results[0]) > 0: for i in range(len(results[0])): @@ -131,23 +132,29 @@ def processCommand(command, commandArgs, noMain=True): dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) bus = dbus.SystemBus() - try: - managerObject = bus.get_object("org.automotive.message.broker", "/"); - managerInterface = dbus.Interface(managerObject, "org.automotive.Manager") - except: - print "Error connecting to AMB. is AMB running?" - return 1 + + def getManager(bus): + try: + managerObject = bus.get_object("org.automotive.message.broker", "/"); + managerInterface = dbus.Interface(managerObject, "org.automotive.Manager") + return managerInterface + except: + print "Error connecting to AMB. is AMB running?" + return None if command == "list" : + managerInterface = getManager(bus) supportedList = managerInterface.List() for objectName in supportedList: print objectName return 1 elif command == "get": + if len(commandArgs) == 0: + commandArgs = ['help'] if commandArgs[0] == "help": print "ObjectName [ObjectName...]" return 1 - + managerInterface = getManager(bus) for objectName in commandArgs: objects = managerInterface.FindObject(objectName) print objectName @@ -161,6 +168,7 @@ def processCommand(command, commandArgs, noMain=True): if commandArgs[0] == "help": print "ObjectName [ObjectName...]" return 1 + managerInterface = getManager(bus) for objectName in commandArgs: objects = managerInterface.FindObject(objectName) for o in objects: @@ -188,6 +196,7 @@ def processCommand(command, commandArgs, noMain=True): zone = 0 if len(commandArgs) == 4: zone = int(commandArgs[3]) + managerInterface = getManager(bus) object = managerInterface.FindObjectForZone(objectName, zone) propertiesInterface = dbus.Interface(bus.get_object("org.automotive.message.broker", object),"org.freedesktop.DBus.Properties") property = propertiesInterface.Get("org.automotive."+objectName, propertyName) @@ -220,6 +229,7 @@ def processCommand(command, commandArgs, noMain=True): zone = 0 if len(commandArgs) >= 2: zone = int(commandArgs[1]) + managerInterface = getManager(bus) object = managerInterface.FindObjectForZone(objectName, zone); propertiesInterface = dbus.Interface(bus.get_object("org.automotive.message.broker", object),"org.automotive."+objectName) print json.dumps(propertiesInterface.GetHistory(start, end), indent=2) @@ -465,27 +475,31 @@ if args.command == "stdin": cursor_right() elif len(results) and not results[0] == toComplete: - print "" - if len(results) < 3: + print '' + print len(results), "results:" + if len(results) <= 3: print ' '.join(results) else: longestLen = 0 for r in results: if len(r) > longestLen: longestLen = len(r) - for i in range(0, len(results) / 3): + i=0 + while i < len(results) / 3: row = "" - endRow = -1 - if len(results) >= i+3: - endRow = 2 - for col in results[i : endRow]: - row += col - for i in range((longestLen + 5) - len(col)): + numCols = 3 + if len(results) < i+3: + numCols = len(results) - i + for n in xrange(numCols): + row += results[i] + for n in xrange((longestLen + 5) - len(results[i])): row += ' ' + i += 1 + print row redraw(data) - elif curses.ascii.isalnum(ord(str)) or ord(str) == curses.ascii.SP: #regular text + elif curses.ascii.isprint(ord(str)) or ord(str) == curses.ascii.SP: #regular text data.insert(str) redraw(data) |