def __init__(self): cmd.Cmd.__init__(self) LunchServerController.__init__(self) self.logger = newLogger("cli") self.prompt = "> " self.commands = set(["exit"]) self.addModule(CLIMessageHandling()) self.addModule(CLIOptionHandling()) self.addModule(CLIPluginHandling(self)) self.addModule(CLIPrivacyHandling()) self.addModule(CLILoggingHandling()) get_server().initialize(self) if get_settings().get_plugins_enabled(): for pluginInfo in get_plugin_manager().getAllPlugins(): if pluginInfo.plugin_object.is_activated: self.addModule(pluginInfo.plugin_object) get_notification_center().connectApplicationUpdate(self.notifyUpdates) self.exitCode = 0 # if serverStopped is called, we can determine if it was a regular exit. self.cleanExit = False self.initialized = False
def __init__(self): QObject.__init__(self) LunchServerController.__init__(self) getCoreLogger().info("Your PyQt version is %s, based on Qt %s", QtCore.PYQT_VERSION_STR, QtCore.QT_VERSION_STR) self._shuttingDown = False self.resetNextLunchTimeTimer = None self._updateAvailable = False self._repoUpdates = 0 self._installUpdatesAction = None self._appUpdateStatusAction = None self._repoUpdateStatusAction = None self._restartAction = None self._restartStatusAction = None self._restartReason = u"" self._highlightNewMessage = False self._highlightPeersReady = False self.exitCode = 0 self.serverThread = None self.running = True get_server().initialize(self) self.pluginNameToMenuAction = {} # initialize main window self.mainWindow = LunchinatorWindow(self) self.settingsWindow = None self.errorDialog = ErrorLogDialog(self.mainWindow) self.setParent(self.mainWindow) if not self.createTrayIcon(): return self.mainWindow.createMenuBar(self.pluginActions) # connect private signals self._initDone.connect(self.initDoneSlot) self._performCall.connect(self.performCallSlot) self._receiveFile.connect(self.receiveFileSlot) self._sendFile.connect(self.sendFileSlot) self._processEvent.connect(self.processEventSlot) self._processMessage.connect(self.processMessageSlot) self._updateRequested.connect(self.updateRequested) self._openWindow.connect(self.openWindowClicked) get_notification_center().connectApplicationUpdate(self._appUpdateAvailable) get_notification_center().connectOutdatedRepositoriesChanged(self._outdatedReposChanged) get_notification_center().connectUpdatesDisabled(self._updatesDisabled) get_notification_center().connectMessagePrepended(self._newMessage) get_notification_center().connectRestartRequired(self._restartRequired) get_notification_center().connectPluginActivated(self._pluginActivated) get_notification_center().connectPluginDeactivated(self._pluginDeactivated) self.serverThread = LunchServerThread(self) self.serverThread.finished.connect(self.serverFinishedUnexpectedly) self.serverThread.finished.connect(self.serverThread.deleteLater) self.serverThread.start()
def initialize(self, controller=None): """Initialize Lunch Server with a specific controller""" if self.initialized: return self.initialized = True if controller != None: self.controller = controller else: from lunchinator.lunch_server_controller import LunchServerController self.controller = LunchServerController() #TODO: Plugin init cannot be done in controller constructor because the GUI has to be ready #separation of gui Plugins necessary - but how *sigh*? if get_settings().get_plugins_enabled(): self.controller.initPlugins() from lunchinator.messages import Messages self._messages = Messages(logging=get_settings().get_verbose()) else: self._messages = None from lunchinator.lunch_peers import LunchPeers self._peers = LunchPeers() get_notification_center().connectPeerUpdated(self._peers._alertIfIPnotMyself)
class lunch_server(object): _instance = None @classmethod def get_singleton_server(cls): if cls._instance == None: cls._instance = cls() return cls._instance def __init__(self): super(lunch_server, self).__init__() self.controller = None self.initialized = False self._disable_broadcast = False self.running = False self._peer_nr = 0 self._peers = None self.plugin_manager = None self._message_queues = {} # queues for messages from new peers self._last_messages = {} # last messages by peer ID, to avoid duplicates self._send_logger = newLogger("Core Send") self._recv_logger = newLogger("Core Recv") self.message_queues_lock = loggingMutex("message_queues", logging=get_settings().get_verbose()) self.cached_messages_lock = loggingMutex("cached_messages", logging=get_settings().get_verbose()) self.exitCode = 0 """ -------------------------- CALLED FROM MAIN THREAD -------------------------------- """ def initialize(self, controller=None): """Initialize Lunch Server with a specific controller""" if self.initialized: return self.initialized = True if controller != None: self.controller = controller else: from lunchinator.lunch_server_controller import LunchServerController self.controller = LunchServerController() #TODO: Plugin init cannot be done in controller constructor because the GUI has to be ready #separation of gui Plugins necessary - but how *sigh*? if get_settings().get_plugins_enabled(): self.controller.initPlugins() from lunchinator.messages import Messages self._messages = Messages(logging=get_settings().get_verbose()) else: self._messages = None from lunchinator.lunch_peers import LunchPeers self._peers = LunchPeers() get_notification_center().connectPeerUpdated(self._peers._alertIfIPnotMyself) """ -------------------------- CALLED FROM ARBITRARY THREAD -------------------------- """ def call(self, msg, peerIDs=[], peerIPs=[]): '''Sends a call to the given peers, specified by either there IDs or there IPs''' assert(type(peerIPs) in [list,set]) self.controller.call(msg, set(peerIDs), set(peerIPs)) def call_all_members(self, msg): '''Sends a call to all members''' self.call(msg, self._peers.getMembers(), set()) def call_info(self, peerIPs=[]): '''An info call informs a peer about my name etc... by default to every peer''' if 0 == len(peerIPs): peerIPs = self._peers.getFirstPeerIP() return self.call("HELO_INFO " + self._build_info_string(), peerIPs=peerIPs) def call_request_info(self, peerIPs=[]): '''Similar to a info call but also request information from the peer by default to every/from every peer''' if 0 == len(peerIPs): peerIPs = self._peers.getFirstPeerIP() return self.call("HELO_REQUEST_INFO " + self._build_info_string(), peerIPs=peerIPs) def call_dict(self, ip): '''Sends the information about my peers to one peer identified by its IP at a time''' peers_dict = {} for pIP in self._peers.getFirstPeerIP(): peers_dict[pIP] = self._peers.getRealPeerName(pIP=pIP) self.call("HELO_DICT " + json.dumps(peers_dict), peerIPs=[ip]) def call_request_dict(self): '''round robin I ask every peer for his peers, but one by one. (Sometimes the member asked is referred to as master)''' peers = self._peers.getFirstPeerIP() try: peers.remove(self._peers.getFirstPeerIP(get_settings().get_ID())) except ValueError: pass if len(peers) > self._peer_nr: self.call("HELO_REQUEST_DICT " + self._build_info_string(), peerIPs=[peers[self._peer_nr]]) if len(peers): self._peer_nr = (self._peer_nr + 1) % len(peers) def changeGroup(self, _newgroup): """Call get_setting().set_group(...) to change the group programatically.""" peerIPs = self._peers.getFirstPeerIP() self.call("HELO_LEAVE Changing Group") self._peers.removeMembersByIP() self.call_request_info(peerIPs) #call stored peerIPs, otherwise I forget myself after the Leave Call def get_messages(self): return self._messages def is_running(self): return self.running def set_disable_broadcast(self, disable): self._disable_broadcast = disable def get_disable_broadcast(self): return self._disable_broadcast def getLunchPeers(self): return self._peers def getController(self): return self.controller def start_server(self): '''listening method - should be started in its own thread''' getCoreLogger().info("%s - Starting the lunch notifier service", strftime("%a, %d %b %Y %H:%M:%S", localtime()).decode("utf-8")) self.my_master = -1 # the peer i use as master is_in_broadcast_mode = False self._recv_socket = lunchSocket(self._peers) try: self._recv_socket.bind() self.running = True self._cleanupLock = loggingMutex("cleanup", logging=get_settings().get_verbose()) self._startCleanupTimer() self.controller.initDone() #first thing to do: ask stored peers for their info: if len(self._peers) == 0: requests = self._peers.initPeersFromFile() self.call_request_info(requests) while self.running: try: xmsg, ip = self._recv_socket.recv() try: plainMsg = xmsg.getPlainMessage() self._recv_logger.info("From %s: %s",ip,plainMsg) except: getCoreLogger().exception("There was an error when trying to parse a message from %s", ip) continue # check for local address: only stop command allowed, else ignore if ip.startswith("127."): if xmsg.getCommand() == "STOP": getCoreLogger().info("Got Stop Command from localhost: %s", plainMsg) self.running = False self.exitCode = EXIT_CODE_STOP elif xmsg.getCommand() == "OPEN_WINDOW" and lunchinator_has_gui(): self.controller._openWindow.emit() elif xmsg.getCommand() == "LOCAL_PIPE": getCoreLogger().debug("Relaying LOCAL_PIPE call") self.call_all_members("HELO_PIPE "+xmsg.getCommandPayload()) continue # first we save the timestamp of this contact, no matter what self._peers.seenIP(ip) # check if we know this peer isNewPeer = self._peers.getPeerInfo(pIP=ip) == None if isNewPeer and self._should_call_info_on_event(plainMsg): #this is a new member - we ask for info right away self.call_request_info([ip]) self._handle_event(xmsg, ip, time(), isNewPeer, False) except splitCall as e: getCoreLogger().debug(e.value) except socket.timeout: if len(self._peers) > 1: if is_in_broadcast_mode: is_in_broadcast_mode = False getCoreLogger().info("ending broadcast") else: if not self._disable_broadcast: if not is_in_broadcast_mode: is_in_broadcast_mode = True getCoreLogger().info("seems like you are alone - broadcasting for others") s_broad = lunchSocket(self._peers) msg = 'HELO_REQUEST_INFO ' + self._build_info_string() self._send_logger.info(msg) s_broad.broadcast(msg) s_broad.close() #forgotten peers may be on file requests = self._peers.initPeersFromFile() self.call_request_info(requests) except socket.error as e: # socket error messages may contain special characters, which leads to crashes on old python versions getCoreLogger().error(u"stopping lunchinator because of socket error: %s", convert_string(str(e))) except KeyboardInterrupt: getCoreLogger().info("Received keyboard interrupt, stopping.") except: getCoreLogger().exception("stopping - Critical error: %s", str(sys.exc_info())) finally: self.running = False try: #make sure to close the cleanup thread first with self._cleanupLock: self._cleanupTimer.cancel() self.call("HELO_LEAVE bye") self._recv_socket.close() self._recv_socket = None except: getCoreLogger().warning("Wasn't able to send the leave call and close the socket...") self._finish() def stop_server(self, stop_any=False): '''this stops a running server thread Usually this will not do anything if there is no running thread within the process @param: stop_any if true it will send a stop call in case another instance has to be stopped ''' if stop_any or self.running: self.perform_call("HELO_STOP shutdown", peerIPs=set([u"127.0.0.1"]), peerIDs=set()) # Just in case the call does not reach the socket: self.running = False else: getCoreLogger().warning("There is no running server to stop") def perform_call(self, msg, peerIDs, peerIPs): """Only the controller should invoke this method -> Called from main thread both peerIDs and peerIPs should be sets Used also by start_lunchinator to send messages without initializing the whole lunch server.""" msg = convert_string(msg) # make sure, msg is unicode target = [] if len(peerIDs) == 0 and len(peerIPs) == 0: target = self._peers.getFirstPeerIP() else: target = peerIPs for pID in peerIDs: pIPs = self._peers.getFirstPeerIP(pID=pID) if len(pIPs): target = target.union(pIPs) else: getCoreLogger().warning("While calling: I do not know a peer with ID %s, ignoring ", pID) if 0 == len(target): getCoreLogger().warning("Cannot send message (%s), there is no peer given or none found", msg) if lunchinator_has_gui() and \ get_settings().get_warn_if_members_not_ready() and \ not msg.startswith(u"HELO") and \ get_settings().get_lunch_trigger().upper() in msg.upper(): # check if everyone is ready notReadyMembers = [self._peers.getDisplayedPeerName(pID=peerID) for peerID in peerIDs if not self._peers.isPeerReady(pID=peerID)] if notReadyMembers: if len(notReadyMembers) == 1: warn = "%s is not ready for lunch." % iter(notReadyMembers).next() elif len(notReadyMembers) == 2: it = iter(notReadyMembers) warn = "%s and %s are not ready for lunch." % (it.next(), it.next()) else: warn = "%s and %d others are not ready for lunch." % (random.sample(notReadyMembers, 1)[0], len(notReadyMembers) - 1) try: from PyQt4.QtGui import QMessageBox warn = "WARNING: %s Send lunch call anyways?" % warn result = QMessageBox.warning(None, "Members not ready", warn, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if result == QMessageBox.No: return except ImportError: print warn i = 0 s = lunchSocket(self._peers) try: self._send_logger.info(msg) for ip in target: try: short = msg if len(msg)<15 else msg[:14]+"..." self._send_logger.debug("To %s: %s", ip.strip(), short) s.sendto(msg, ip.strip()) i += 1 except socket.error as e: if e.errno in [64,65]: getCoreLogger().debug("lunch_socket: Removing IP because host is down or there is no route") self._peers.removePeerIPs([ip]) else: getCoreLogger().warning("The following message could not be delivered to %s: %s", ip, msg, exc_info=1) except Exception as e: getCoreLogger().warning("The following message could not be delivered to %s: %s", ip, msg, exc_info=1) finally: s.close() return i """ ---------------------- PRIVATE -------------------------------- """ def _startCleanupTimer(self): interval = get_settings().get_peer_timeout() / 2 interval = 30 if interval>30 else interval self._cleanupTimer = Timer(interval, self._cleanup) self._cleanupTimer.start() def _cleanup(self): with self._cleanupLock: if not self.running: return getCoreLogger().debug("clean up thread runs") try: # it's time to announce my name again and switch the master self.call("HELO " + get_settings().get_user_name(), peerIPs=self._peers.getFirstPeerIP()) self.call_request_dict() # clean up peers self._peers.removeInactive() self._remove_timed_out_queues() self._cleanup_cached_messages() self._recv_socket.drop_incomplete_messages() except: getCoreLogger().exception("Something went wrong in the lunch interval thread") self._startCleanupTimer() def _build_info_string(self): from lunchinator.utilities import getPlatform, PLATFORM_LINUX, PLATFORM_MAC, PLATFORM_WINDOWS info_d = {u"avatar": get_settings().get_avatar_file(), u"name": get_settings().get_user_name(), u"group": get_settings().get_group(), u"ID": get_settings().get_ID(), u"next_lunch_begin":get_settings().get_default_lunch_begin(), u"next_lunch_end":get_settings().get_default_lunch_end(), u"version":get_settings().get_version(), u"version_commit_count":get_settings().get_commit_count(), u"platform": sys.platform} try: if getPlatform() == PLATFORM_LINUX: info_d[u"os"] = u" ".join(aString if type(aString) in (str, unicode) else "[%s]" % " ".join(aString) for aString in platform.linux_distribution()) elif getPlatform() == PLATFORM_WINDOWS: info_d[u"os"] = u" ".join(aString if type(aString) in (str, unicode) else "[%s]" % " ".join(aString) for aString in platform.win32_ver()) elif getPlatform() == PLATFORM_MAC: info_d[u"os"] = u" ".join(aString if type(aString) in (str, unicode) else "[%s]" % " ".join(aString) for aString in platform.mac_ver()) except: getCoreLogger().exception("Error generating OS version string") if get_settings().get_next_lunch_begin(): info_d[u"next_lunch_begin"] = get_settings().get_next_lunch_begin() if get_settings().get_next_lunch_end(): info_d[u"next_lunch_end"] = get_settings().get_next_lunch_end() self.controller.extendMemberInfo(info_d) return json.dumps(info_d) def _enqueue_event(self, xmsg, ip, eventTime): getCoreLogger().debug("Peer of IP %s is unknown, enqueuing message", ip) with self.message_queues_lock: if ip in self._message_queues: queue = self._message_queues[ip] else: queue = (time(), []) queue[1].append((eventTime, xmsg)) self._message_queues[ip] = queue def _process_queued_messages(self, ip): with self.message_queues_lock: if ip in self._message_queues: if len(self._message_queues[ip][1]) > 0: getCoreLogger().debug("Processing enqueued messages of IP %s", ip) for eventTime, xmsg in self._message_queues[ip][1]: self._handle_event(xmsg, ip, eventTime, newPeer=False, fromQueue=True) del self._message_queues[ip] def _remove_timed_out_queues(self): with self.message_queues_lock: for ip in set(self._message_queues.keys()): if time() - self._message_queues[ip][0] > get_settings().get_peer_timeout(): getCoreLogger().debug("Removing queued messages from IP %s", ip) del self._message_queues[ip] def _should_call_info_on_event(self, data): return not data.startswith("HELO_REQUEST_INFO") and \ not data.startswith("HELO_INFO") and \ not data.startswith("HELO_REQUEST_DICT") def _cache_message(self, peerID, ip, data, eventTime): with self.cached_messages_lock: if peerID in self._last_messages: lastMessages = self._last_messages[peerID] else: lastMessages = deque() lastMessages.append((eventTime, ip, data)) self._last_messages[peerID] = lastMessages def _is_message_duplicate(self, peerID, ip, data): # with a high probability, the message is a duplicate if it comes # from the same ID but from a different IP. with self.cached_messages_lock: if peerID in self._last_messages: for _eventTime, old_ip, old_data in self._last_messages[peerID]: if ip != old_ip and data == old_data: return True return False def _cleanup_cached_messages(self): with self.cached_messages_lock: curTime = time() for queue in self._last_messages.itervalues(): while len(queue) > 0 and curTime - queue[0][0] > get_settings().get_message_cache_timeout(): queue.popleft() def _handle_event(self, xmsg, ip, eventTime, newPeer, fromQueue): """ processes a call received by the lunch_socket @type xmsg: extMessageIncoming @type ip: unicode @type eventTime: float @type newPeer: boolean @type fromQueue: boolean """ data = xmsg.getPlainMessage() # if there is no HELO in the beginning, it's just a message and # we handle it, if the peer is in our group if not xmsg.isCommand(): # only process message if we know the peer if newPeer: self._enqueue_event(xmsg, ip, eventTime) else: peerID = self._peers.getPeerID(pIP=ip) if self._peers.isMember(pID=peerID): if self._is_message_duplicate(peerID, ip, data): getCoreLogger().debug("Dropping duplicate message from peer %s", peerID) return try: self.getController().processMessage(xmsg, ip, eventTime, newPeer, fromQueue) self._cache_message(peerID, ip, data, eventTime) except: getCoreLogger().exception("Error while handling incoming message from %s: %s", ip, data) else: getCoreLogger().debug("Dropped a message from %s: %s", ip, data) return # if this packet has info about the peer, we record it and # are done. These events are always processed immediately and # not enqueued. if self._handle_structure_event(ip, xmsg, newPeer): # now it's the plugins' turn: self.controller.processEvent(xmsg, ip, eventTime, False, False) return try: if newPeer: self._enqueue_event(xmsg, ip, eventTime) self._handle_core_event(ip, xmsg, newPeer, fromQueue) # now it's the plugins' turn: self.controller.processEvent(xmsg, ip, eventTime, newPeer, fromQueue) except: getCoreLogger().exception("Unexpected error while handling event from group member %s call: %s", ip, str(sys.exc_info())) getCoreLogger().critical("The data received was: %s", data) def _updateInfoDict(self, ip, value): self._peers.updatePeerInfoByIP(ip, json.loads(value)) if self._peers.getAvatarOutdated(pIP=ip): self.request_avatar(ip) def _handle_structure_event(self, ip, xmsg, newPeer): """ handle events that influence peer list and info @type ip: unicode @type xmsg: extMessageIncoming @type newPeer: bool """ r_value = True cmd = xmsg.getCommand() value = xmsg.getCommandPayload() if cmd == "INFO": self._updateInfoDict(ip, value) if newPeer: self._process_queued_messages(ip) elif cmd == "REQUEST_INFO": self._updateInfoDict(ip, value) self.call_info() if newPeer: self._process_queued_messages(ip) elif cmd == "REQUEST_DICT": self._updateInfoDict(ip, value) self.call_dict(ip) if newPeer: self._process_queued_messages(ip) elif cmd == "DICT": # the master send me the list of _members - yeah ext_members = json.loads(value) # add every entry and assume, the member is in my group for m_ip, _m_name in ext_members.iteritems(): if not self._peers.getPeerInfo(pIP=m_ip): #this is a new peer - ask for info right away self.call_request_info([m_ip]) elif cmd == "LEAVE": #the peer tells me that he leaves, I'll remove all of his IPs pID = self._peers.getPeerID(pIP=ip) self._peers.removePeer(pID) elif cmd == "HELO": # this is just a ping with the members name if not self._peers.getPeerInfo(pIP=ip): #this is a new peer - ask for info right away self.call_request_info([ip]) else: r_value = False return r_value def request_avatar(self, ip): info = self._peers.getPeerInfo(pIP=ip) if info and u"avatar" in info and not os.path.exists(os.path.join(get_settings().get_avatar_dir(), info[u"avatar"])): openPort = self.controller.getOpenPort(ip) self.call("HELO_REQUEST_AVATAR " + str(openPort), peerIPs=[ip]) return True return False def _handle_core_event(self, ip, xmsg, newPeer, _fromQueue): ''' handles cmds that are not necessary for peer discovery but should work without plugins ''' # I don't see any reason to process these events for unknown peers. if newPeer: return cmd = xmsg.getCommand() value = xmsg.getCommandPayload() if cmd == "AVATAR": # someone wants to send me his pic via TCP values = value.split() file_size = int(values[0].strip()) tcp_port = 0 # 0 means we must guess the port if len(values) > 1: tcp_port = int(values[1].strip()) file_name = "" info = self._peers.getPeerInfo(pIP=ip) if u"avatar" in info: file_name = os.path.join(get_settings().get_avatar_dir(), info[u"avatar"]) else: getCoreLogger().error("%s tried to send his avatar, but I don't know where to save it", ip) if len(file_name): pID = self._peers.getPeerID(pIP=ip) getCoreLogger().info("Receiving avatar from peer with ID %s, IP %s", pID, ip) self.controller.receiveFile(ip, file_size, file_name, tcp_port, successFunc=partial(get_notification_center().emitAvatarChanged, pID, info[u"avatar"])) elif cmd == "REQUEST_AVATAR": # someone wants my pic other_tcp_port = get_settings().get_tcp_port() try: other_tcp_port = int(value.strip()) except: getCoreLogger().exception("%s requested avatar, I could not parse the port from value %s, using standard %d", str(ip), str(value), other_tcp_port) fileToSend = os.path.join(get_settings().get_avatar_dir(), get_settings().get_avatar_file()) if os.path.exists(fileToSend): fileSize = os.path.getsize(fileToSend) getCoreLogger().info("Sending file of size %d to %s : %d", fileSize, str(ip), other_tcp_port) self.call("HELO_AVATAR %s %s" % (fileSize, other_tcp_port), peerIPs = [ip]) self.controller.sendFile(ip, fileToSend, other_tcp_port) else: # TODO should this be an error? If somebody deletes the avatar file, it should be reset silently -> warning getCoreLogger().error("Want to send file %s, but cannot find it", fileToSend) elif cmd == "REQUEST_LOGFILE": # someone wants my logfile other_tcp_port = get_settings().get_tcp_port() try: (oport, _) = value.split(" ", 1) other_tcp_port = int(oport.strip()) except: getCoreLogger().warning("%s requested the logfile, I could not parse the port and number from value %s, using standard %d and logfile 0", str(ip), str(value), other_tcp_port) fileToSend = StringIO() with contextlib.closing(tarfile.open(mode='w:gz', fileobj=fileToSend)) as tarWriter: if os.path.exists(get_settings().log_file()): tarWriter.add(get_settings().log_file(), arcname="0.log") logIndex = 1 while os.path.exists("%s.%d" % (get_settings().log_file(), logIndex)): tarWriter.add("%s.%d" % (get_settings().log_file(), logIndex), arcname="%d.log" % logIndex) logIndex = logIndex + 1 fileSize = fileToSend.tell() getCoreLogger().info("Sending file of size %d to %s : %d", fileSize, str(ip), other_tcp_port) self.call("HELO_LOGFILE_TGZ %d %d" % (fileSize, other_tcp_port), peerIPs=[ip]) self.controller.sendFile(ip, fileToSend.getvalue(), other_tcp_port, True) elif cmd == "PIPE": #to hell, I am going to print this in the hope that this is ASCII art if not lunchinator_has_gui(): print value def _finish(self): getCoreLogger().info("%s - Stopping the lunch notifier service", strftime("%a, %d %b %Y %H:%M:%S", localtime()).decode("utf-8")) self._peers.finish() if self._messages: self._messages.finish() self.controller.serverStopped(self.exitCode) def has_gui(self): """ returns if a GUI and qt is present @deprecated: use lunchinator.lunchinator_has_gui() instead """ return lunchinator_has_gui()
def _insertMessage(self, mtime, addr, msg): QCoreApplication.processEvents() LunchServerController._insertMessage(self, mtime, addr, msg)