def installCoreDependencies(): requirements = getCoreDependencies() missing = checkRequirements(requirements, u"Lunchinator", u"Lunchinator") result = handleMissingDependencies(missing, optionalCallback=lambda req: not "yapsy" in req.lower()) if result == INSTALL_CANCEL: return False try: import yapsy if lunchinator_has_gui(): from PyQt4.QtGui import QMessageBox if result == INSTALL_SUCCESS: QMessageBox.information( None, "Success", "Dependencies were installed successfully.", buttons=QMessageBox.Ok, defaultButton=QMessageBox.Ok, ) elif result == INSTALL_FAIL: QMessageBox.warning( None, "Errors during installation", "There were errors during installation, but Lunchinator might work anyways. If you experience problems with some plugins, try to install the required libraries manually using pip.", ) return True getCoreLogger().info("yapsy is working after dependency installation") # without gui there are enough messages on the screen already except: if lunchinator_has_gui(): try: from PyQt4.QtGui import QMessageBox QMessageBox.critical( None, "Error installing dependencies", "There was an error, the dependencies could not be installed. Continuing without plugins.", ) except: getCoreLogger().error( "There was an error, the dependencies could not be installed. Continuing without plugins." ) getCoreLogger().error( "Lunchinator is running without plugins because of missing dependencies. \ Try executing 'lunchinator --install-dependencies' to install them automatically." ) return False
def activate(self): iface_gui_plugin.activate(self) if lunchinator_has_gui(): self._sendMessageAction = _SendMessageAction() self._openChatAction = _OpenChatAction(self._sendMessageAction) self._peerActions = [self._openChatAction, _BlockAction(self._sendMessageAction), self._sendMessageAction] else: self._peerActions = None
def handleMissingDependencies(missing, optionalCallback=lambda _req : True): """If there are missing dependencies, asks and installs them. Returns a list of components whose requirements were not fully installed. missing -- dictionary returned by checkRequirements(...) optionalCallbacl -- function that takes a requirement string and returns True if the requirement is optional and False otherwise. """ if missing: if isPyinstallerBuild(): canInstall = False text = u"There are missing dependencies in your PyInstaller build. " +\ u"Unfortunately, you cannot install additional packages for a PyInstaller build." getCoreLogger().warning(text + u"\n The missing dependencies are: \n" + unicode(str(missing))) else: canInstall = True text = None if lunchinator_has_gui(): from lunchinator.req_error_dialog import RequirementsErrorDialog from PyQt4.QtGui import QMessageBox requirements = [] for _component, missingList in missing.iteritems(): for dispName, req, reason, info in missingList: if reason == REASON_PACKAGE_MISSING: reasonStr = u"Not installed" elif reason == REASON_VERSION_CONFLICT: reasonStr = u"Wrong version (installed: %s)" % info.version else: reasonStr = u"Unknown" requirements.append((req, dispName, reasonStr, optionalCallback(req))) f = RequirementsErrorDialog(requirements, None, canInstall, text) res = f.exec_() if res == RequirementsErrorDialog.Accepted: if not canInstall: return INSTALL_NOT_POSSIBLE installRes = installDependencies(f.getSelectedRequirements()) if installRes == INSTALL_FAIL: QMessageBox.critical(None, "Install Failed", "Some dependencies could not be installed.") elif installRes == INSTALL_SUCCESS: QMessageBox.information(None, "Install Succeeded", "Dependencies were successfully installed.") elif installRes == INSTALL_RESTART: QMessageBox.information(None, "Install Finished", "Lunchinator needs to be restarted to complete the installation.") return installRes elif res == RequirementsErrorDialog.IGNORED: return INSTALL_IGNORE else: return INSTALL_CANCEL return INSTALL_FAIL return INSTALL_NOT_POSSIBLE
def displayNotification(name, msg, logger, icon=None): if msg == None: msg = u"" myPlatform = getPlatform() try: from lunchinator import get_server if not lunchinator_has_gui(): print time.strftime("%Y-%m-%d %H:%M"),name, msg except: print time.strftime("%Y-%m-%d %H:%M"),name, msg try: if myPlatform == PLATFORM_LINUX: fileToClose = None if icon is None or not os.path.exists(icon): icon = "" elif _mustScaleNotificationIcon(): import Image im = Image.open(icon) im.thumbnail((64,64), Image.ANTIALIAS) fileToClose = NamedTemporaryFile(suffix='.png', delete=True) im.save(fileToClose, "PNG") fileToClose.flush() icon = fileToClose.name subprocess.call(["notify-send","--icon="+icon, name, msg]) if fileToClose is not None: fileToClose.close() elif myPlatform == PLATFORM_MAC: fh = open(os.path.devnull,"w") exe = getBinary("terminal-notifier", os.path.join("bin", "terminal-notifier.app", "Contents", "MacOS")) if not exe: logger.warning("terminal-notifier not found.") return call = [exe, "-title", "Lunchinator: %s" % name, "-message", msg] if False and checkBundleIdentifier(_LUNCHINATOR_BUNDLE_IDENTIFIER): # no sender until code signing is fixed (probably never) call.extend(["-sender", _LUNCHINATOR_BUNDLE_IDENTIFIER]) logger.debug(call) try: subprocess.call(call, stdout=fh, stderr=fh) except OSError as e: if e.errno == errno.EINVAL: logger.warning("Ignoring invalid value on Mac") else: raise except: logger.exception("Error calling %s", call) elif myPlatform == PLATFORM_WINDOWS: from lunchinator import get_server if hasattr(get_server().controller, "statusicon"): get_server().controller.statusicon.showMessage(name,msg) except: logger.exception("error displaying notification")
def activate(self): iface_general_plugin.activate(self) try: from PyQt4.QtCore import QTimer from online_update.appupdate.git_update import GitUpdateHandler from online_update.appupdate.mac_update import MacUpdateHandler from online_update.appupdate.external_update import ExternalUpdateHandler from online_update.appupdate.win_update import WinUpdateHandler from online_update.appupdate.app_update_handler import AppUpdateHandler from online_update.repoupdate.repo_update_handler import RepoUpdateHandler self._activated = True except ImportError: self._activated = False self.logger.warning("ImportError, cannot activate Auto Update") return if GitUpdateHandler.appliesToConfiguration(self.logger): self._appUpdateHandler = GitUpdateHandler(self.logger) elif MacUpdateHandler.appliesToConfiguration(self.logger): self._appUpdateHandler = MacUpdateHandler(self.logger, self.hidden_options["check_url"]) elif ExternalUpdateHandler.appliesToConfiguration(self.logger): self._appUpdateHandler = ExternalUpdateHandler(self.logger) elif WinUpdateHandler.appliesToConfiguration(self.logger): self._appUpdateHandler = WinUpdateHandler(self.logger, self.hidden_options["check_url"]) else: self._appUpdateHandler = AppUpdateHandler(self.logger) self._repoUpdateHandler = RepoUpdateHandler(self.logger) self._appUpdateHandler.activate() self._repoUpdateHandler.activate() get_notification_center().connectInstallUpdates(self.installUpdates) get_notification_center().connectRepositoriesChanged(self._repoUpdateHandler.checkForUpdates) if lunchinator_has_gui(): self._scheduleTimer = QTimer(getValidQtParent()) self._scheduleTimer.timeout.connect(self.checkForUpdate) self._scheduleTimer.start(online_update.CHECK_INTERVAL)
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 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
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 appliesToConfiguration(cls, _logger): return lunchinator_has_gui() and getPlatform() == PLATFORM_MAC and getApplicationBundle() != None
def get_peer_actions(self): if lunchinator_has_gui(): self._rpAction = _RemotePictureAction() return [self._rpAction] else: return None
def extendsInfoDict(self): return lunchinator_has_gui()
def process_command(self, xmsg, ip, peer_info, preprocessedData=None): if xmsg.getCommand()=="PIPE": data = xmsg.getCommandPayload() if lunchinator_has_gui(): self._outputField.setText(data) self._outputField.setStatusTip("sent by "+peer_info[u"name"])
def extendsInfoDict(self): # do not except file transfers without GUI return lunchinator_has_gui()
def appliesToConfiguration(cls, _logger): return lunchinator_has_gui() and getPlatform() == PLATFORM_WINDOWS
def canCheckForUpdate(self): return lunchinator_has_gui()
def has_gui(self): """ returns if a GUI and qt is present @deprecated: use lunchinator.lunchinator_has_gui() instead """ return lunchinator_has_gui()
def _alertIfIPnotMyself(self, newPID, peerInfo): """ alert if ID is mine but ip is not from my machine this function has to be called from the main thread @return: True if that's my ID from another machine @type newPID: unicode @type peerInfo: dict @rtype: bool """ if not peerInfo.has_key("triggerIP") or newPID != get_settings().get_ID(): # that's not me! return False ip = peerInfo["triggerIP"] myname = socket.gethostname() # socket.getfqdn(socket.gethostname()) othername = "" try: othername = socket.gethostbyaddr(ip)[0] except: self.logger.warning( "Another IP (%s) contacted me with my ID, I can't find it's hostname..., won't do anything now." % ip ) return False # make sure, we only check the hostname, not the fqdn i = myname.find(".") if i != -1: myname = myname[:i] i = othername.find(".") if i != -1: othername = othername[:i] if myname == othername: # that seems to be me from another, maybe on a second # network interface return False if othername in get_settings().get_multiple_machines_allowed(): # he is allowed to do that return False # that seems to be coming from an unknown machine and has to be reported from lunchinator import lunchinator_has_gui msg = ( "Another lunchinator on the network (%s: %s)" % (ip, othername) + "is identifying itself with your (%s) ID. " % myname + "It will get all messages you get, also private ones!\n" ) if lunchinator_has_gui(): msg += "If this is not what you want, you should create a new ID immediately." from PyQt4.QtGui import QMessageBox, QPushButton msgBox = QMessageBox(None) # msgBox.setIcon(QMessageBox.Warning) # msgBox.setWindowTitle("Another Lunchinator with your ID detected") msgBox.setText(msg) msgBox.addButton(QPushButton("Create New ID"), QMessageBox.AcceptRole) msgBox.addButton(QPushButton("Ignore"), QMessageBox.NoRole) msgBox.addButton(QPushButton("Allow host to get my messages"), QMessageBox.RejectRole) ret = msgBox.exec_() if ret == QMessageBox.AcceptRole: get_settings().generate_ID() elif ret != QMessageBox.NoRole: get_settings().add_multiple_machines_allowed(othername) else: msg += ( "If you are sure that this is right you can set " + "multiple_machines_allowed = %s in your settings.cfg \n" % ip + "Otherwise you should create a new ID immediately.\n" ) self.logger.critical(msg) return True
def activate(self): get_notification_center().connectOutdatedRepositoriesChanged(self._processOutdated) if lunchinator_has_gui(): self.checkForUpdates()