From b9ba710848284a7dad7bbcd43a7a8b45ab8677a9 Mon Sep 17 00:00:00 2001 From: Jan Klopper Date: Thu, 28 Apr 2016 15:34:57 +0200 Subject: [PATCH] Add pylintrc file Move client functions to vloed module, simplify client file for newbee's, Make sure the server module only starts operating when actually initialised --- client.py | 109 +++++-------------- pylintrc | 311 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ vloed.py | 197 +++++++++++++++++++++++++++++----- 3 files changed, 503 insertions(+), 114 deletions(-) create mode 100644 pylintrc diff --git a/client.py b/client.py index 917f351..6aaa5c2 100755 --- a/client.py +++ b/client.py @@ -5,48 +5,15 @@ Inspired by the PixelFlut beamer on eth0:winter 2016 and code from https://github.com/defnull/pixelflut/ """ -__version__ = 0.2 +__version__ = 0.3 __author__ = "Jan Klopper " -MAX_PROTOCOL_VERSION = 1; -PROTOCOL_PREAMBLE = 'pixelvloed' - -import socket -import struct import random -import time +from vloed import PixelVloedClient, NewMessage, RGBPixel -class MaxSizeList(list): - - def __init__(self, maxcount=100): - self.maxsize = maxcount - super( MaxSizeList, self ).__init__() - - def append(self, item): - super( MaxSizeList, self ).append(item) - if self.__len__() == self.maxsize: - raise IndexError('max size reached') - -def RGBPixel(x, y, r, g, b, a=None): # pylint: disable=C0103 - """Generates the packed data for a pixel""" - if a: - return struct.pack("<2H4B", x, y, r, g, b, a) - return struct.pack("<2H3B", x, y, r, g, b) - -def SetRGBAMode(mode): - """Generate the rgb/rgba bit""" - return struct.pack(" to the black list. It should be a base name, not a +# path. You may set this option multiple times. +ignore=CVS +ignore=.hg + +# Pickle collected data for later comparisons. +persistent=yes + +# Set the cache size for astng objects. +cache-size=500 + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + + +[MESSAGES CONTROL] + +# Enable only checker(s) with the given id(s). This option conflicts with the +# disable-checker option +#enable-checker= + +# Enable all checker(s) except those with the given id(s). This option +# conflicts with the enable-checker option +#disable-checker= + +# Enable all messages in the listed categories. +#enable-msg-cat= + +# Disable all messages in the listed categories. +#disable-msg-cat= + +# Enable the message(s) with the given id(s). +#enable-msg= + +# Disable the message(s) with the given id(s). +# W0142: Used * or ** magic -- this is in fact a very Pythonic approach. +# E1103: %s %r has no %r member (but some types could not be inferred) +# This is generally not an actual problem, and causes false positives +disable-msg=W0403, W0142, E1103 +disable=W0403, W0142, E1103 + + +[REPORTS] + +# set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html +output-format=colorized + +# Include message's id in output +include-ids=yes + +# Put messages in a separate file for each module / package specified on the +# command line instead of printing them on stdout. Reports (if any) will be +# written in a file name "pylint_global.[txt|html]". +files-output=no + +# Tells wether to display a full report or only the messages +reports=yes + +# Python expression which should return a note less than 10 (10 is the highest +# note).You have access to the variables errors warning, statement which +# respectivly contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (R0004). +evaluation=10.0 - (5.0 * error + warning + refactor + convention) / statement * 10 + +# Add a comment according to your evaluation note. This is used by the global +# evaluation report (R0004). +comment=yes + +# Enable the report(s) with the given id(s). +#enable-report= + +# Disable the report(s) with the given id(s). +# R0801 is the "similar lines" report, which is not used. +disable-report=R0801 + + +# checks for +# * unused variables / imports +# * undefined variables +# * redefinition of variable from builtins or from an outer scope +# * use of variable before assigment +# +[VARIABLES] + +# Tells wether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching names used for dummy variables (i.e. not used). +dummy-variables-rgx=_.*$ + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + + +# try to find bugs in the code using type inference +# +[TYPECHECK] + +# Tells wether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# When zope mode is activated, consider the acquired-members option to ignore +# access to some undefined attributes. +zope=no + +# List of members which are usually get through zope's acquisition mecanism and +# so shouldn't trigger E0201 when accessed (need zope=yes to be considered). +acquired-members=REQUEST,acl_users,aq_parent + + +# checks for : +# * doc strings +# * modules / classes / functions / methods / arguments / variables name +# * number of arguments, local variables, branchs, returns and statements in +# functions, methods +# * required module attributes +# * dangerous default values as arguments +# * redefinition of function / method / class +# * uses of the global statement +# +[BASIC] + +# Required attributes for module, separated by a comma +required-attributes=__author__, __version__ + +# Regular expression which should only match functions or classes name which do +# not require a docstring +no-docstring-rgx=_.*$ + +# Regular expression which should only match correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression which should only match correct module level names +const-rgx=(([A-Z_][A-Z1-9_]*)|(__.*__))$ + +# Regular expression which should only match correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression which should only match correct function names +function-rgx=([A-Z_][a-zA-Z0-9_]{2,30})|(main)$ + +# Regular expression which should only match correct method names +method-rgx=([a-zA-Z_][a-zA-Z0-9_]{2,30})|(test.*)$ + +# Regular expression which should only match correct instance attribute names +attr-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct argument names +argument-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct variable names +variable-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct list comprehension / +# generator expression variable names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Good variable names which should always be accepted, separated by a comma +good-names=i,n + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar + +# List of builtins function names that should not be used, separated by a comma +bad-functions=filter,apply,input + + +# checks for +# * external modules dependencies +# * relative / wildcard imports +# * cyclic imports +# * uses of deprecated modules +# +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub,string,TERMIOS,Bastion,rexec + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report R0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report R0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report R0402 must +# not be disabled) +int-import-graph= + + +# checks for : +# * methods without self as first argument +# * overridden methods signature +# * access only to existant members via self +# * attributes not defined in the __init__ method +# * supported interfaces implementation +# * unreachable code +# +[CLASSES] + +# List of interface methods to ignore, separated by a comma. This is used for +# instance to not check methods defines in Zope's Interface base class. +ignore-iface-methods= + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp,run + + +# checks for sign of poor/misdesign: +# * number of methods, attributes, local variables... +# * size, complexity of functions, methods +# +[DESIGN] + +# Maximum number of arguments for function / method +max-args=8 + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branchs=20 + +# Maximum number of statements in function / method body +max-statements=40 + +# Maximum number of parents for a class (see R0901). +max-parents=12 + +# Maximum number of attributes for a class (see R0902). +max-attributes=10 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=0 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + + +# checks for: +# * warning notes in the code like FIXME, XXX +# * PEP 263: source code with non ascii character but no encoding declaration +# +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +# checks for : +# * unauthorized constructions +# * strict indentation +# * line length +# * use of <> instead of != +# +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=80 + +# Maximum number of lines in a module +max-module-lines=1000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + + +# checks for similarities and duplicated code. This computation may be +# memory / CPU intensive, so you should disable it if you experiments some +# problems. +# +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=4 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes diff --git a/vloed.py b/vloed.py index f587e56..0e11919 100755 --- a/vloed.py +++ b/vloed.py @@ -5,31 +5,32 @@ Inspired by the PixelFlut beamer on eth0:winter 2016 and code from https://github.com/defnull/pixelflut/ """ -__version__ = 0.2 +__version__ = 0.3 __author__ = "Jan Klopper " import pygame -from pygame.locals import * +from pygame import locals as pygamelocals import struct import time -from gevent import spawn, socket, monkey +import socket + +from gevent import spawn, monkey from gevent.server import DatagramServer from gevent.queue import Queue monkey.patch_all() UDP_IP = "127.0.0.1" -UDP_PORT= 5005 +UDP_PORT = 5005 +DISCOVER_PORT = 5006 PROTOCOL_VERSION = 1 +MAX_PROTOCOL_VERSION = 1 PROTOCOL_PREAMBLE = "pixelvloed" - -def main(): - """Runs a pixelvloed server""" - PixelVloed(':%d' %(UDP_PORT)).serve_forever() +MAX_PIXELS = 140 class Canvas(object): - """PixelVloed server class""" + """PixelVloed display class""" - def __init__(self, queue, width=1366, height=768, debug=True): + def __init__(self, queue, width=1366, height=768, debug=False): """Init the pixelVloed server""" self.debug = debug self.pixeloffset = 2 @@ -42,10 +43,11 @@ class Canvas(object): self.canvas() self.set_title() self.queue = queue - self.limit = 140 - self.broadcastSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - self.broadcastSocket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) - self.broadcastSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.limit = MAX_PIXELS + self.pixels = None + self.broadcastsocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.broadcastsocket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) + self.broadcastsocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) @staticmethod def set_title(text=None): @@ -59,8 +61,8 @@ class Canvas(object): """Init the pygame canvas""" pygame.init() pygame.mixer.quit() - flags = DOUBLEBUF - self.screen = pygame.display.set_mode((self.width, self.height), flags) + self.screen = pygame.display.set_mode((self.width, self.height), + pygamelocals.DOUBLEBUF) def clear(self, r=0, g=0, b=0): # pylint: disable=C0103 """ Fill the entire screen with a solid colour (default: black)""" @@ -115,7 +117,7 @@ class Canvas(object): pixelcount = min(((len(data)-1) / pixellength), self.limit) if self.debug: - print '%d pixels received, protocol V %d ' % (pixelcount, protocol) + print '%d pixels received, protocol V %d' % (pixelcount, protocol) for i in xrange(0, pixelcount): pixel = struct.unpack_from( packetformat, @@ -124,36 +126,173 @@ class Canvas(object): if self.debug: print pixel self.Pixel(*pixel) - except Exception as e: + except Exception as error: if self.debug: # All exceptions will be printed, but won't result in a crash. - print e + print error # indicate that we have been drawing stuff return True def SendDiscoveryPacket(self): """Lets send out our ip/port/resolution to any listening clients""" - self.broadcastSocket.sendto( + self.broadcastsocket.sendto( '%s:%f %s:%d %d*%d' % (PROTOCOL_PREAMBLE, PROTOCOL_VERSION, UDP_IP, UDP_PORT, self.width, self.height), - ('', UDP_PORT+1)) + ('', DISCOVER_PORT)) if self.debug: print 'sending discovery packet' def __del__(self): """Clean up any sockets we created""" - self.broadcastSocket.close() + self.broadcastsocket.close() -class PixelVloed(DatagramServer): +class PixelVloedServer(DatagramServer): """PixelVloed server class""" - queue = Queue() - pixelcanvas = Canvas(queue) - __request_processing_greenlet = spawn(pixelcanvas.CanvasUpdate) - def handle(self, data, address): - """Is called by the DataGramServer whenever a package is received""" + def __init__(self, *args, **kwargs): + """Set up some vars for this instance""" + self.queue = Queue() + pixelcanvas = Canvas(self.queue) + __request_processing_greenlet = spawn(pixelcanvas.CanvasUpdate) + DatagramServer.__init__(self, *args, **kwargs) + + def handle(self, data, _address): + """Is called by the DataGramServer whenever an udp package is received""" self.queue.put(data) +class PixelVloedClient(object): + """Sets up a client + + Arguments: + firstserver: (bool) False, select the first server immediately + debug: (bool) False + ip: (str) None + port: (int) None + width: (int) 640 + height: (int) 480 + + Listens for servers if no ip is given + """ + + def __init__(self, firstserver=False, debug=False, + ip=None, port=None, + width=640, height=480): + self.sleep = 0.01 + self.debug = debug + if not ip: + servers = False + while servers == False: + servers = self.DiscoverServers(firstserver) + self.ipaddress = servers[0]['ip'] + self.port = servers[0]['port'] + self.width = servers[0]['width'] + self.height = servers[0]['height'] + + if self.debug: + print ('displaying on %(ip)s:%(port)d, %(width)d*%(height)dpx' % + servers[0]) + else: + self.ipaddress = ip + self.port = port + self.width = width + self.height = height + if self.debug: + print ('displaying on %(ip)s:%(port)d, %(width)d*%(height)dpx' % + self) + self.sock = socket.socket(socket.AF_INET, # Internet + socket.SOCK_DGRAM) # UDP + + def Sleep(self, duration=None): + """Sleeps the designated amount of time""" + time.sleep(duration if duration else self.sleep) + + def SendPacket(self, message, sleep=True): + """Sends the message to the udp server + + Arguments: + message: (str, 140) + sleep: (bool) True, should the client sleep for a while? + """ + self.sock.sendto(message, (self.ipaddress, self.port)) + if sleep: + self.Sleep() + + @staticmethod + def DiscoverServers(returnfirst=False, timeout=5): + """Discover servers that send out the pixelfvloed preample""" + discoverysock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + discoverysock.bind(('', DISCOVER_PORT)) + starttime = time.time() + servers = [] + foundhash = {} + while (time.time() - timeout) < starttime: + data, _addr = discoverysock.recvfrom(1024) + try: + if data.startswith(PROTOCOL_PREAMBLE): + dataset = data.split(' ') + if float(dataset[0].split(':')[1]) <= MAX_PROTOCOL_VERSION: + ipaddress = dataset[1].split(':')[0] + port = int(dataset[1].split(':')[1]) + width = int(dataset[2].split('*')[0]) + height = int(dataset[2].split('*')[1]) + if data not in foundhash: + newserver = {'ip': ipaddress, + 'port': port, + 'width': width, + 'height': height} + foundhash[data] = True + print 'New pixelvloed screen found: %r' % newserver + servers.append(newserver) + if returnfirst: + return servers + except: + pass + if servers: + return servers + return False + +def NewMessage(): + """Creates a new message with the correct max size, rgb mode and version""" + message = MaxSizeList(MAX_PIXELS+2) + message.append(SetRGBAMode(False)) + message.append(SetVersionBit()) + return message + +def RGBPixel(x, y, r, g, b, a=None): # pylint: disable=C0103 + """Generates the packed data for a pixel""" + if a: + return struct.pack("<2H4B", x, y, r, g, b, a) + return struct.pack("<2H3B", x, y, r, g, b) + +def SetRGBAMode(mode): + """Generate the rgb/rgba bit""" + return struct.pack("