--- a/Beremiz.py Wed May 09 00:00:50 2012 +0200
+++ b/Beremiz.py Wed May 09 00:12:40 2012 +0200
@@ -149,7 +149,7 @@
import types, time, re, platform, time, traceback, commands
from ProjectController import ProjectController, MATIEC_ERROR_MODEL
from util import MiniTextControler
-from ProcessLogger import ProcessLogger
+from util.ProcessLogger import ProcessLogger from PLCOpenEditor import IDEFrame, AppendMenu, TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU, TYPESTREE, INSTANCESTREE, LIBRARYTREE, SCALING, PAGETITLES, USE_AUI
--- a/ProcessLogger.py Wed May 09 00:00:50 2012 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,199 +0,0 @@
-#This file is part of Beremiz, a Integrated Development Environment for
-#programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
-#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
-#See COPYING file for copyrights details.
-#This library is free software; you can redistribute it and/or
-#modify it under the terms of the GNU 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
-#General Public License for more details.
-#You should have received a copy of the GNU General Public
-#License along with this library; if not, write to the Free Software
-#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-import subprocess, ctypes
-from threading import Timer, Lock, Thread, Semaphore
- from signal import SIGTERM, SIGKILL
-class outputThread(Thread):
- Thread is used to print the output of a command to the stdout
- def __init__(self, Proc, fd, callback=None, endcallback=None):
- self.callback = callback
- self.endcallback = endcallback
- while outchunk != '' and not self.killed :
- outchunk = self.fd.readline()
- if self.callback : self.callback(outchunk)
- while self.retval is None and not self.killed :
- self.retval = self.Proc.poll()
- outchunk = self.fd.readline()
- if self.callback : self.callback(outchunk)
- while outchunk != '' and not self.killed :
- outchunk = self.fd.readline()
- if self.callback : self.callback(outchunk)
- self.endcallback(self.Proc.pid, err)
- def __init__(self, logger, Command, finish_callback = None,
- no_stdout = False, no_stderr = False, no_gui = True,
- timeout = None, outlimit = None, errlimit = None,
- endlog = None, keyword = None, kill_it = False):
- if not isinstance(Command, list):
- self.Command_str = Command
- for i,word in enumerate(Command.replace("'",'"').split('"')):
- self.Command.extend(word.split())
- self.Command.append(word)
- self.Command_str = subprocess.list2cmdline(self.Command)
- self.finish_callback = finish_callback
- self.no_stdout = no_stdout
- self.no_stderr = no_stderr
- self.startupinfo = None
- self.errlimit = errlimit
- self.outlimit = outlimit
- self.finishsem = Semaphore(0)
- "stdin":subprocess.PIPE,
- "stdout":subprocess.PIPE,
- "stderr":subprocess.PIPE}
- if no_gui == True and wx.Platform == '__WXMSW__':
- self.startupinfo = subprocess.STARTUPINFO()
- self.startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
- popenargs["startupinfo"] = self.startupinfo
- elif wx.Platform == '__WXGTK__':
- popenargs["shell"] = False
- self.Proc = subprocess.Popen( self.Command, **popenargs )
- self.outt = outputThread(
- self.errt = outputThread(
- self.timeout = Timer(timeout,self.endlog)
- if (self.keyword and v.find(self.keyword)!=-1) or (self.outlimit and self.outlen > self.outlimit):
- self.logger.write_warning(v)
- if self.errlimit and self.errlen > self.errlimit:
- def log_the_end(self,ecode,pid):
- self.logger.write(self.Command_str + "\n")
- self.logger.write_warning(_("exited with status %s (pid %s)\n")%(str(ecode),str(pid)))
- def finish(self, pid,ecode):
- if self.timeout: self.timeout.cancel()
- self.log_the_end(ecode,pid)
- if self.finish_callback is not None:
- self.finish_callback(self,ecode,pid)
- self.finishsem.release()
- def kill(self,gently=True):
- self.outt.killed = True
- self.errt.killed = True
- if wx.Platform == '__WXMSW__':
- handle = ctypes.windll.kernel32.OpenProcess(PROCESS_TERMINATE, False, self.Proc.pid)
- ctypes.windll.kernel32.TerminateProcess(handle, -1)
- ctypes.windll.kernel32.CloseHandle(handle)
- os.kill(self.Proc.pid, sig)
- if self.endlock.acquire(False):
- self.finishsem.release()
- if not self.outt.finished and self.kill_it:
- self.finishsem.acquire()
- return [self.exitcode, "".join(self.outdata), "".join(self.errdata)]
--- a/ProjectController.py Wed May 09 00:00:50 2012 +0200
+++ b/ProjectController.py Wed May 09 00:12:40 2012 +0200
@@ -16,13 +16,13 @@
from util import MiniTextControler, opjimg, CheckPathPerm, GetClassImporter
-from ProcessLogger import ProcessLogger
+from util.ProcessLogger import ProcessLogger from PLCControler import PLCControler
from PLCOpenEditor import ProjectDialog
from TextViewer import TextViewer
from plcopen.structures import IEC_KEYWORDS
from targets.typemapping import DebugTypesSize
-from discovery import DiscoveryDialog
+from util.discovery import DiscoveryDialog from ConfigTreeNode import ConfigTreeNode
base_folder = os.path.split(sys.path[0])[0]
--- a/Zeroconf.py Wed May 09 00:00:50 2012 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,1559 +0,0 @@
-""" Multicast DNS Service Discovery for Python, v0.12
- Copyright (C) 2003, Paul Scott-Murphy
- This module provides a framework for the use of DNS Service Discovery
- using IP multicast. It has been tested against the JRendezvous
- implementation from <a href="http://strangeberry.com">StrangeBerry</a>,
- and against the mDNSResponder from Mac OS X 10.3.8.
- 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.
- 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-"""0.12 update - allow selection of binding interface
- typo fix - Thanks A. M. Kuchlingi
- removed all use of word 'Rendezvous' - this is an API change"""
-"""0.11 update - correction to comments for addListener method
- support for new record types seen from OS X
- ignore unknown DNS record types
- works alongside other processes using port 5353 (e.g. on Mac OS X)
- tested against Mac OS X 10.3.2's mDNSResponder
- corrections to removal of list entries for service browser"""
-"""0.10 update - Jonathon Paisley contributed these corrections:
- always multicast replies, even when query is unicast
- correct a pointer encoding problem
- can now write records in any order
- traceback shown on failure
- better TXT record parsing
- server is now separate from name
- can cancel a service browser
- modified some unit tests to accommodate these changes"""
-"""0.09 update - remove all records on service unregistration
- fix DOS security problem with readName"""
-"""0.08 update - changed licensing to LGPL"""
-"""0.07 update - faster shutdown on engine
- pointer encoding of outgoing names
- ServiceBrowser now works
-"""0.06 update - small improvements with unit tests
- added defined exception types
- fixed hostname/interface problem
- fixed socket timeout problem
- fixed addServiceListener() typo bug
- using select() for socket reads
- tested on Debian unstable with Python 2.2.2"""
-"""0.05 update - ensure case insensitivty on domain names
- support for unicast DNS queries"""
-"""0.04 update - added some unit tests
- added __ne__ adjuncts where required
- ensure names end in '.local.'
- timeout on receiving socket for clean shutdown"""
-__author__ = "Paul Scott-Murphy"
-__email__ = "paul at scott dash murphy dot com"
-__all__ = ["Zeroconf", "ServiceInfo", "ServiceBrowser"]
-globals()['_GLOBAL_DONE'] = 0
-_MDNS_ADDR = '224.0.0.251'
-_DNS_TTL = 60 * 60; # one hour default TTL
-_MAX_MSG_TYPICAL = 1460 # unused
-_MAX_MSG_ABSOLUTE = 8972
-_FLAGS_QR_MASK = 0x8000 # query response mask
-_FLAGS_QR_QUERY = 0x0000 # query
-_FLAGS_QR_RESPONSE = 0x8000 # response
-_FLAGS_AA = 0x0400 # Authorative answer
-_FLAGS_TC = 0x0200 # Truncated
-_FLAGS_RD = 0x0100 # Recursion desired
-_FLAGS_RA = 0x8000 # Recursion available
-_FLAGS_Z = 0x0040 # Zero
-_FLAGS_AD = 0x0020 # Authentic data
-_FLAGS_CD = 0x0010 # Checking disabled
-# Mapping constants to names
-_CLASSES = { _CLASS_IN : "in",
-_TYPES = { _TYPE_A : "a",
-def currentTimeMillis():
- """Current system time in milliseconds"""
- return time.time() * 1000
-class NonLocalNameException(Exception):
-class NonUniqueNameException(Exception):
-class NamePartTooLongException(Exception):
-class AbstractMethodException(Exception):
-class BadTypeInNameException(Exception):
-# implementation classes
- def __init__(self, name, type, clazz):
- self.key = string.lower(name)
- self.clazz = clazz & _CLASS_MASK
- self.unique = (clazz & _CLASS_UNIQUE) != 0
- def __eq__(self, other):
- """Equality test on name, type, and class"""
- if isinstance(other, DNSEntry):
- return self.name == other.name and self.type == other.type and self.clazz == other.clazz
- def __ne__(self, other):
- """Non-equality test"""
- return not self.__eq__(other)
- def getClazz(self, clazz):
- return "?(%s)" % (clazz)
- def getType(self, type):
- return "?(%s)" % (type)
- def toString(self, hdr, other):
- """String representation with additional information"""
- result = "%s[%s,%s" % (hdr, self.getType(self.type), self.getClazz(self.clazz))
- result += ",%s]" % (other)
-class DNSQuestion(DNSEntry):
- """A DNS question entry"""
- def __init__(self, name, type, clazz):
- if not name.endswith(".local."):
- raise NonLocalNameException
- DNSEntry.__init__(self, name, type, clazz)
- def answeredBy(self, rec):
- """Returns true if the question is answered by the record"""
- return self.clazz == rec.clazz and (self.type == rec.type or self.type == _TYPE_ANY) and self.name == rec.name
- """String representation"""
- return DNSEntry.toString(self, "question", None)
-class DNSRecord(DNSEntry):
- """A DNS record - like a DNS entry, but has a TTL"""
- def __init__(self, name, type, clazz, ttl):
- DNSEntry.__init__(self, name, type, clazz)
- self.created = currentTimeMillis()
- def __eq__(self, other):
- """Tests equality as per DNSRecord"""
- if isinstance(other, DNSRecord):
- return DNSEntry.__eq__(self, other)
- def suppressedBy(self, msg):
- """Returns true if any answer in a message can suffice for the
- information held in this record."""
- for record in msg.answers:
- if self.suppressedByAnswer(record):
- def suppressedByAnswer(self, other):
- """Returns true if another record has same name, type and class,
- and if its TTL is at least half of this record's."""
- if self == other and other.ttl > (self.ttl / 2):
- def getExpirationTime(self, percent):
- """Returns the time at which this record will have expired
- by a certain percentage."""
- return self.created + (percent * self.ttl * 10)
- def getRemainingTTL(self, now):
- """Returns the remaining TTL in seconds."""
- return max(0, (self.getExpirationTime(100) - now) / 1000)
- def isExpired(self, now):
- """Returns true if this record has expired."""
- return self.getExpirationTime(100) <= now
- def isStale(self, now):
- """Returns true if this record is at least half way expired."""
- return self.getExpirationTime(50) <= now
- def resetTTL(self, other):
- """Sets this record's TTL and created time to that of
- self.created = other.created
- raise AbstractMethodException
- def toString(self, other):
- """String representation with addtional information"""
- arg = "%s/%s,%s" % (self.ttl, self.getRemainingTTL(currentTimeMillis()), other)
- return DNSEntry.toString(self, "record", arg)
-class DNSAddress(DNSRecord):
- """A DNS address record"""
- def __init__(self, name, type, clazz, ttl, address):
- DNSRecord.__init__(self, name, type, clazz, ttl)
- """Used in constructing an outgoing packet"""
- out.writeString(self.address, len(self.address))
- def __eq__(self, other):
- """Tests equality on address"""
- if isinstance(other, DNSAddress):
- return self.address == other.address
- """String representation"""
- return socket.inet_ntoa(self.address)
-class DNSHinfo(DNSRecord):
- """A DNS host information record"""
- def __init__(self, name, type, clazz, ttl, cpu, os):
- DNSRecord.__init__(self, name, type, clazz, ttl)
- """Used in constructing an outgoing packet"""
- out.writeString(self.cpu, len(self.cpu))
- out.writeString(self.os, len(self.os))
- def __eq__(self, other):
- """Tests equality on cpu and os"""
- if isinstance(other, DNSHinfo):
- return self.cpu == other.cpu and self.os == other.os
- """String representation"""
- return self.cpu + " " + self.os
-class DNSPointer(DNSRecord):
- """A DNS pointer record"""
- def __init__(self, name, type, clazz, ttl, alias):
- DNSRecord.__init__(self, name, type, clazz, ttl)
- """Used in constructing an outgoing packet"""
- out.writeName(self.alias)
- def __eq__(self, other):
- """Tests equality on alias"""
- if isinstance(other, DNSPointer):
- return self.alias == other.alias
- """String representation"""
- return self.toString(self.alias)
-class DNSText(DNSRecord):
- """A DNS text record"""
- def __init__(self, name, type, clazz, ttl, text):
- DNSRecord.__init__(self, name, type, clazz, ttl)
- """Used in constructing an outgoing packet"""
- out.writeString(self.text, len(self.text))
- def __eq__(self, other):
- """Tests equality on text"""
- if isinstance(other, DNSText):
- return self.text == other.text
- """String representation"""
- if len(self.text) > 10:
- return self.toString(self.text[:7] + "...")
- return self.toString(self.text)
-class DNSService(DNSRecord):
- """A DNS service record"""
- def __init__(self, name, type, clazz, ttl, priority, weight, port, server):
- DNSRecord.__init__(self, name, type, clazz, ttl)
- self.priority = priority
- """Used in constructing an outgoing packet"""
- out.writeShort(self.priority)
- out.writeShort(self.weight)
- out.writeShort(self.port)
- out.writeName(self.server)
- def __eq__(self, other):
- """Tests equality on priority, weight, port and server"""
- if isinstance(other, DNSService):
- return self.priority == other.priority and self.weight == other.weight and self.port == other.port and self.server == other.server
- """String representation"""
- return self.toString("%s:%s" % (self.server, self.port))
-class DNSIncoming(object):
- """Object representation of an incoming DNS packet"""
- def __init__(self, data):
- """Constructor from string holding bytes of packet"""
- self.numAuthorities = 0
- self.numAdditionals = 0
- """Reads header portion of packet"""
- length = struct.calcsize(format)
- info = struct.unpack(format, self.data[self.offset:self.offset+length])
- self.numQuestions = info[2]
- self.numAnswers = info[3]
- self.numAuthorities = info[4]
- self.numAdditionals = info[5]
- def readQuestions(self):
- """Reads questions section of packet"""
- length = struct.calcsize(format)
- for i in range(0, self.numQuestions):
- info = struct.unpack(format, self.data[self.offset:self.offset+length])
- question = DNSQuestion(name, info[0], info[1])
- self.questions.append(question)
- """Reads an integer from the packet"""
- length = struct.calcsize(format)
- info = struct.unpack(format, self.data[self.offset:self.offset+length])
- def readCharacterString(self):
- """Reads a character string from the packet"""
- length = ord(self.data[self.offset])
- return self.readString(length)
- def readString(self, len):
- """Reads a string of a given length from the packet"""
- format = '!' + str(len) + 's'
- length = struct.calcsize(format)
- info = struct.unpack(format, self.data[self.offset:self.offset+length])
- def readUnsignedShort(self):
- """Reads an unsigned short from the packet"""
- length = struct.calcsize(format)
- info = struct.unpack(format, self.data[self.offset:self.offset+length])
- """Reads the answers, authorities and additionals section of the packet"""
- length = struct.calcsize(format)
- n = self.numAnswers + self.numAuthorities + self.numAdditionals
- domain = self.readName()
- info = struct.unpack(format, self.data[self.offset:self.offset+length])
- rec = DNSAddress(domain, info[0], info[1], info[2], self.readString(4))
- elif info[0] == _TYPE_CNAME or info[0] == _TYPE_PTR:
- rec = DNSPointer(domain, info[0], info[1], info[2], self.readName())
- elif info[0] == _TYPE_TXT:
- rec = DNSText(domain, info[0], info[1], info[2], self.readString(info[3]))
- elif info[0] == _TYPE_SRV:
- rec = DNSService(domain, info[0], info[1], info[2], self.readUnsignedShort(), self.readUnsignedShort(), self.readUnsignedShort(), self.readName())
- elif info[0] == _TYPE_HINFO:
- rec = DNSHinfo(domain, info[0], info[1], info[2], self.readCharacterString(), self.readCharacterString())
- elif info[0] == _TYPE_AAAA:
- rec = DNSAddress(domain, info[0], info[1], info[2], self.readString(16))
- # Try to ignore types we don't know about
- # this may mean the rest of the name is
- # unable to be parsed, and may show errors
- # so this is left for debugging. New types
- # encountered need to be parsed properly.
- #print "UNKNOWN TYPE = " + str(info[0])
- #raise BadTypeInNameException
- self.answers.append(rec)
- """Returns true if this is a query"""
- return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_QUERY
- """Returns true if this is a response"""
- return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_RESPONSE
- def readUTF(self, offset, len):
- """Reads a UTF-8 string of a given length from the packet"""
- result = self.data[offset:offset+len].decode('utf-8')
- """Reads a domain name from the packet"""
- len = ord(self.data[off])
- result = ''.join((result, self.readUTF(off, len) + '.'))
- off = ((len & 0x3F) << 8) | ord(self.data[off])
- raise _("Bad domain name (circular) at ") + str(off)
- raise _("Bad domain name at ") + str(off)
-class DNSOutgoing(object):
- """Object representation of an outgoing packet"""
- def __init__(self, flags, multicast = 1):
- self.multicast = multicast
- def addQuestion(self, record):
- self.questions.append(record)
- def addAnswer(self, inp, record):
- if not record.suppressedBy(inp):
- self.addAnswerAtTime(record, 0)
- def addAnswerAtTime(self, record, now):
- """Adds an answer if if does not expire by a certain time"""
- if now == 0 or not record.isExpired(now):
- self.answers.append((record, now))
- def addAuthorativeAnswer(self, record):
- """Adds an authoritative answer"""
- self.authorities.append(record)
- def addAdditionalAnswer(self, record):
- """Adds an additional answer"""
- self.additionals.append(record)
- def writeByte(self, value):
- """Writes a single byte to the packet"""
- self.data.append(struct.pack(format, chr(value)))
- def insertShort(self, index, value):
- """Inserts an unsigned short in a certain position in the packet"""
- self.data.insert(index, struct.pack(format, value))
- def writeShort(self, value):
- """Writes an unsigned short to the packet"""
- self.data.append(struct.pack(format, value))
- def writeInt(self, value):
- """Writes an unsigned integer to the packet"""
- self.data.append(struct.pack(format, int(value)))
- def writeString(self, value, length):
- """Writes a string to the packet"""
- format = '!' + str(length) + 's'
- self.data.append(struct.pack(format, value))
- """Writes a UTF-8 string of a given length to the packet"""
- utfstr = s.encode('utf-8')
- raise NamePartTooLongException
- self.writeString(utfstr, length)
- def writeName(self, name):
- """Writes a domain name to the packet"""
- # Find existing instance of this name in packet
- index = self.names[name]
- # No record of this name already, so write it
- # out as normal, recording the location of the name
- # for future pointers to it.
- self.names[name] = self.size
- parts = name.split('.')
- # An index was found, so write a pointer to it
- self.writeByte((index >> 8) | 0xC0)
- def writeQuestion(self, question):
- """Writes a question to the packet"""
- self.writeName(question.name)
- self.writeShort(question.type)
- self.writeShort(question.clazz)
- def writeRecord(self, record, now):
- """Writes a record (answer, authoritative answer, additional) to
- self.writeName(record.name)
- self.writeShort(record.type)
- if record.unique and self.multicast:
- self.writeShort(record.clazz | _CLASS_UNIQUE)
- self.writeShort(record.clazz)
- self.writeInt(record.ttl)
- self.writeInt(record.getRemainingTTL(now))
- # Adjust size for the short we will write before this record
- length = len(''.join(self.data[index:]))
- self.insertShort(index, length) # Here is the short we adjusted for
- """Returns a string containing the packet's bytes
- No further parts should be added to the packet once this
- for question in self.questions:
- self.writeQuestion(question)
- for answer, time in self.answers:
- self.writeRecord(answer, time)
- for authority in self.authorities:
- self.writeRecord(authority, 0)
- for additional in self.additionals:
- self.writeRecord(additional, 0)
- self.insertShort(0, len(self.additionals))
- self.insertShort(0, len(self.authorities))
- self.insertShort(0, len(self.answers))
- self.insertShort(0, len(self.questions))
- self.insertShort(0, self.flags)
- self.insertShort(0, self.id)
- return ''.join(self.data)
- """A cache of DNS entries"""
- list = self.cache[entry.key]
- list = self.cache[entry.key] = []
- def remove(self, entry):
- list = self.cache[entry.key]
- """Gets an entry by key. Will return None if there is no
- list = self.cache[entry.key]
- return list[list.index(entry)]
- def getByDetails(self, name, type, clazz):
- """Gets an entry by details. Will return None if there is
- entry = DNSEntry(name, type, clazz)
- def entriesWithName(self, name):
- """Returns a list of entries whose key matches the name."""
- return self.cache[name]
- """Returns a list of all entries"""
- def add(x, y): return x+y
- return reduce(add, self.cache.values())
-class Engine(threading.Thread):
- """An engine wraps read access to sockets, allowing objects that
- need to receive data from sockets to be called back when the
- A reader needs a handle_read() method, which is called when the socket
- it is interested in is ready for reading.
- Writers are not implemented here, because we only send short
- def __init__(self, zeroconf):
- threading.Thread.__init__(self)
- self.zeroconf = zeroconf
- self.readers = {} # maps socket to reader
- self.condition = threading.Condition()
- while not globals()['_GLOBAL_DONE']:
- # No sockets to manage, but we wait for the timeout
- # or addition of a socket
- self.condition.acquire()
- self.condition.wait(self.timeout)
- self.condition.release()
- rr, wr, er = select.select(rs, [], [], self.timeout)
- self.readers[socket].handle_read()
- # Ignore errors that occur on shutdown
- self.condition.acquire()
- result = self.readers.keys()
- self.condition.release()
- def addReader(self, reader, socket):
- self.condition.acquire()
- self.readers[socket] = reader
- self.condition.notify()
- self.condition.release()
- def delReader(self, socket):
- self.condition.acquire()
- del(self.readers[socket])
- self.condition.notify()
- self.condition.release()
- self.condition.acquire()
- self.condition.notify()
- self.condition.release()
- """A Listener is used by this module to listen on the multicast
- group to which DNS messages are sent, allowing the implementation
- to cache information as it arrives.
- It requires registration with an Engine object in order to have
- the read() method called when a socket is availble for reading."""
- def __init__(self, zeroconf):
- self.zeroconf = zeroconf
- self.zeroconf.engine.addReader(self, self.zeroconf.socket)
- data, (addr, port) = self.zeroconf.socket.recvfrom(_MAX_MSG_ABSOLUTE)
- msg = DNSIncoming(data)
- # Always multicast responses
- self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT)
- # If it's not a multicast query, reply via unicast
- elif port == _DNS_PORT:
- self.zeroconf.handleQuery(msg, addr, port)
- self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT)
- self.zeroconf.handleResponse(msg)
-class Reaper(threading.Thread):
- """A Reaper is used by this module to remove cache entries that
- def __init__(self, zeroconf):
- threading.Thread.__init__(self)
- self.zeroconf = zeroconf
- self.zeroconf.wait(10 * 1000)
- if globals()['_GLOBAL_DONE']:
- now = currentTimeMillis()
- for record in self.zeroconf.cache.entries():
- if record.isExpired(now):
- self.zeroconf.updateRecord(now, record)
- self.zeroconf.cache.remove(record)
-class ServiceBrowser(threading.Thread):
- """Used to browse for a service of a specific type.
- The listener object will have its addService() and
- removeService() methods called when this browser
- discovers changes in the services availability."""
- def __init__(self, zeroconf, type, listener):
- """Creates a browser for a specific type"""
- threading.Thread.__init__(self)
- self.zeroconf = zeroconf
- self.listener = listener
- self.nextTime = currentTimeMillis()
- self.delay = _BROWSER_TIME
- self.zeroconf.addListener(self, DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN))
- def updateRecord(self, zeroconf, now, record):
- """Callback invoked by Zeroconf when new information arrives.
- Updates information required by browser in the Zeroconf cache."""
- if record.type == _TYPE_PTR and record.name == self.type:
- expired = record.isExpired(now)
- oldrecord = self.services[record.alias.lower()]
- oldrecord.resetTTL(record)
- del(self.services[record.alias.lower()])
- callback = lambda x: self.listener.removeService(x, self.type, record.alias)
- self.list.append(callback)
- self.services[record.alias.lower()] = record
- callback = lambda x: self.listener.addService(x, self.type, record.alias)
- self.list.append(callback)
- expires = record.getExpirationTime(75)
- if expires < self.nextTime:
- self.nextTime = expires
- self.zeroconf.notifyAll()
- now = currentTimeMillis()
- if len(self.list) == 0 and self.nextTime > now:
- self.zeroconf.wait(self.nextTime - now)
- if globals()['_GLOBAL_DONE'] or self.done:
- now = currentTimeMillis()
- if self.nextTime <= now:
- out = DNSOutgoing(_FLAGS_QR_QUERY)
- out.addQuestion(DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN))
- for record in self.services.values():
- if not record.isExpired(now):
- out.addAnswerAtTime(record, now)
- self.zeroconf.send(out)
- self.nextTime = now + self.delay
- self.delay = min(20 * 1000, self.delay * 2)
- event = self.list.pop(0)
-class ServiceInfo(object):
- """Service information"""
- def __init__(self, type, name, address=None, port=None, weight=0, priority=0, properties=None, server=None):
- """Create a service description.
- type: fully qualified service type name
- name: fully qualified service name
- address: IP address as unsigned short, network byte order
- port: port that the service runs on
- weight: weight of the service
- priority: priority of the service
- properties: dictionary of properties (or a string holding the bytes for the text field)
- server: fully qualified name for service host (defaults to name)"""
- if not name.endswith(type):
- raise BadTypeInNameException
- self.priority = priority
- self.setProperties(properties)
- def setProperties(self, properties):
- """Sets properties and text of this info from a dictionary"""
- if isinstance(properties, dict):
- self.properties = properties
- value = properties[key]
- suffix = ''.encode('utf-8')
- elif isinstance(value, str):
- suffix = value.encode('utf-8')
- elif isinstance(value, int):
- suffix = ''.encode('utf-8')
- list.append('='.join((key, suffix)))
- result = ''.join((result, struct.pack('!c', chr(len(item))), item))
- def setText(self, text):
- """Sets properties and text given a text field"""
- length = ord(text[index])
- strs.append(text[index:index+length])
- # No equals sign at all
- elif value == 'false' or not value:
- # Only update non-existent properties
- if key and result.get(key) == None:
- self.properties = result
- if self.type is not None and self.name.endswith("." + self.type):
- return self.name[:len(self.name) - len(self.type) - 1]
- """Pirority accessor"""
- def getProperties(self):
- """Properties accessor"""
- def updateRecord(self, zeroconf, now, record):
- """Updates service information from a DNS record"""
- if record is not None and not record.isExpired(now):
- if record.type == _TYPE_A:
- if record.name == self.server:
- self.address = record.address
- elif record.type == _TYPE_SRV:
- if record.name == self.name:
- self.server = record.server
- self.port = record.port
- self.weight = record.weight
- self.priority = record.priority
- self.updateRecord(zeroconf, now, zeroconf.cache.getByDetails(self.server, _TYPE_A, _CLASS_IN))
- elif record.type == _TYPE_TXT:
- if record.name == self.name:
- self.setText(record.text)
- def request(self, zeroconf, timeout):
- """Returns true if the service could be discovered on the
- network, and updates this object with details discovered.
- now = currentTimeMillis()
- zeroconf.addListener(self, DNSQuestion(self.name, _TYPE_ANY, _CLASS_IN))
- while self.server is None or self.address is None or self.text is None:
- out = DNSOutgoing(_FLAGS_QR_QUERY)
- out.addQuestion(DNSQuestion(self.name, _TYPE_SRV, _CLASS_IN))
- out.addAnswerAtTime(zeroconf.cache.getByDetails(self.name, _TYPE_SRV, _CLASS_IN), now)
- out.addQuestion(DNSQuestion(self.name, _TYPE_TXT, _CLASS_IN))
- out.addAnswerAtTime(zeroconf.cache.getByDetails(self.name, _TYPE_TXT, _CLASS_IN), now)
- if self.server is not None:
- out.addQuestion(DNSQuestion(self.server, _TYPE_A, _CLASS_IN))
- out.addAnswerAtTime(zeroconf.cache.getByDetails(self.server, _TYPE_A, _CLASS_IN), now)
- zeroconf.wait(min(next, last) - now)
- now = currentTimeMillis()
- zeroconf.removeListener(self)
- def __eq__(self, other):
- """Tests equality of service name"""
- if isinstance(other, ServiceInfo):
- return other.name == self.name
- def __ne__(self, other):
- """Non-equality test"""
- return not self.__eq__(other)
- """String representation"""
- result = "service[%s,%s:%s," % (self.name, socket.inet_ntoa(self.getAddress()), self.port)
- if len(self.text) < 20:
- result += self.text[:17] + "..."
- """Implementation of Zeroconf Multicast DNS Service Discovery
- Supports registration, unregistration, queries and browsing.
- def __init__(self, bindaddress=None):
- """Creates an instance of the Zeroconf class, establishing
- multicast communications, listening and reaping threads."""
- globals()['_GLOBAL_DONE'] = 0
- self.intf = bindaddress
- self.group = ('', _MDNS_PORT)
- self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
- # SO_REUSEADDR should be equivalent to SO_REUSEPORT for
- # multicast UDP sockets (p 731, "TCP/IP Illustrated,
- # Volume 2"), but some BSD-derived systems require
- # SO_REUSEPORT to be specified explicity. Also, not all
- # versions of Python have SO_REUSEPORT available. So
- # if you're on a BSD-based system, and haven't upgraded
- # to Python 2.3 yet, you may find this library doesn't
- self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_TTL, 255)
- self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1)
- self.socket.bind(self.group)
- # Some versions of linux raise an exception even though
- # the SO_REUSE* options have been set, so ignore it
- if self.intf is not None:
- self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_IF, socket.inet_aton(self.intf) + socket.inet_aton('0.0.0.0'))
- self.socket.setsockopt(socket.SOL_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0'))
- self.cache = DNSCache()
- self.condition = threading.Condition()
- self.engine = Engine(self)
- self.listener = Listener(self)
- self.reaper = Reaper(self)
- if self.intf is not None:
- return self.intf.startswith("127.0.0.1")
- if self.intf is not None:
- return self.intf.startswith("169.254.")
- def wait(self, timeout):
- """Calling thread waits for a given number of milliseconds or
- self.condition.acquire()
- self.condition.wait(timeout/1000)
- self.condition.release()
- """Notifies all waiting threads"""
- self.condition.acquire()
- self.condition.notifyAll()
- self.condition.release()
- def getServiceInfo(self, type, name, timeout=3000):
- """Returns network's service information for a particular
- name and type, or None if no service matches by the timeout,
- which defaults to 3 seconds."""
- info = ServiceInfo(type, name)
- if info.request(self, timeout):
- def addServiceListener(self, type, listener):
- """Adds a listener for a particular service type. This object
- will then have its updateRecord method called when information
- arrives for that type."""
- self.removeServiceListener(listener)
- self.browsers.append(ServiceBrowser(self, type, listener))
- def removeServiceListener(self, listener):
- """Removes a listener from the set that is currently listening."""
- for browser in self.browsers:
- if browser.listener == listener:
- def registerService(self, info, ttl=_DNS_TTL):
- """Registers service information to the network with a default TTL
- of 60 seconds. Zeroconf will then respond to requests for
- information for that service. The name of the service may be
- changed if needed to make it unique on the network."""
- self.checkService(info)
- self.services[info.name.lower()] = info
- now = currentTimeMillis()
- self.wait(nextTime - now)
- now = currentTimeMillis()
- out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
- out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, ttl, info.name), 0)
- out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, ttl, info.priority, info.weight, info.port, info.server), 0)
- out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, ttl, info.text), 0)
- out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, ttl, info.address), 0)
- nextTime += _REGISTER_TIME
- def unregisterService(self, info):
- """Unregister a service."""
- del(self.services[info.name.lower()])
- now = currentTimeMillis()
- self.wait(nextTime - now)
- now = currentTimeMillis()
- out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
- out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0)
- out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, 0, info.priority, info.weight, info.port, info.name), 0)
- out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0)
- out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, 0, info.address), 0)
- nextTime += _UNREGISTER_TIME
- def unregisterAllServices(self):
- """Unregister all registered services."""
- if len(self.services) > 0:
- now = currentTimeMillis()
- self.wait(nextTime - now)
- now = currentTimeMillis()
- out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
- for info in self.services.values():
- out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0)
- out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, 0, info.priority, info.weight, info.port, info.server), 0)
- out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0)
- out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, 0, info.address), 0)
- nextTime += _UNREGISTER_TIME
- def checkService(self, info):
- """Checks the network for a unique service name, modifying the
- ServiceInfo passed in if it is not unique."""
- now = currentTimeMillis()
- for record in self.cache.entriesWithName(info.type):
- if record.type == _TYPE_PTR and not record.isExpired(now) and record.alias == info.name:
- if (info.name.find('.') < 0):
- info.name = info.name + ".[" + info.address + ":" + info.port + "]." + info.type
- self.checkService(info)
- raise NonUniqueNameException
- self.wait(nextTime - now)
- now = currentTimeMillis()
- out = DNSOutgoing(_FLAGS_QR_QUERY | _FLAGS_AA)
- out.addQuestion(DNSQuestion(info.type, _TYPE_PTR, _CLASS_IN))
- out.addAuthorativeAnswer(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, info.name))
- nextTime += _CHECK_TIME
- def addListener(self, listener, question):
- """Adds a listener for a given question. The listener will have
- its updateRecord method called when information is available to
- answer the question."""
- now = currentTimeMillis()
- self.listeners.append(listener)
- if question is not None:
- for record in self.cache.entriesWithName(question.name):
- if question.answeredBy(record) and not record.isExpired(now):
- listener.updateRecord(self, now, record)
- def removeListener(self, listener):
- """Removes a listener."""
- self.listeners.remove(listener)
- def updateRecord(self, now, rec):
- """Used to notify listeners of new information that has updated
- for listener in self.listeners:
- listener.updateRecord(self, now, rec)
- def handleResponse(self, msg):
- """Deal with incoming response packets. All answers
- are held in the cache, and listeners are notified."""
- now = currentTimeMillis()
- for record in msg.answers:
- expired = record.isExpired(now)
- if record in self.cache.entries():
- self.cache.remove(record)
- entry = self.cache.get(record)
- self.updateRecord(now, record)
- def handleQuery(self, msg, addr, port):
- """Deal with incoming query packets. Provides a response if
- # Support unicast client responses
- out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA, 0)
- for question in msg.questions:
- out.addQuestion(question)
- for question in msg.questions:
- if question.type == _TYPE_PTR:
- for service in self.services.values():
- if question.name == service.type:
- out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
- out.addAnswer(msg, DNSPointer(service.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, service.name))
- out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
- # Answer A record queries for any service addresses we know
- if question.type == _TYPE_A or question.type == _TYPE_ANY:
- for service in self.services.values():
- if service.server == question.name.lower():
- out.addAnswer(msg, DNSAddress(question.name, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address))
- service = self.services.get(question.name.lower(), None)
- if not service: continue
- if question.type == _TYPE_SRV or question.type == _TYPE_ANY:
- out.addAnswer(msg, DNSService(question.name, _TYPE_SRV, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.priority, service.weight, service.port, service.server))
- if question.type == _TYPE_TXT or question.type == _TYPE_ANY:
- out.addAnswer(msg, DNSText(question.name, _TYPE_TXT, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.text))
- if question.type == _TYPE_SRV:
- out.addAdditionalAnswer(DNSAddress(service.server, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address))
- if out is not None and out.answers:
- self.send(out, addr, port)
- def send(self, out, addr = _MDNS_ADDR, port = _MDNS_PORT):
- """Sends an outgoing packet."""
- # This is a quick test to see if we can parse the packets we generate
- #temp = DNSIncoming(out.packet())
- bytes_sent = self.socket.sendto(out.packet(), 0, (addr, port))
- # Ignore this, it may be a temporary loss of network connection
- """Ends the background threads, and prevent this instance from
- servicing further queries."""
- if globals()['_GLOBAL_DONE'] == 0:
- globals()['_GLOBAL_DONE'] = 1
- self.unregisterAllServices()
- self.socket.setsockopt(socket.SOL_IP, socket.IP_DROP_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0'))
-# Test a few module features, including service registration, service
-# query (for Zoe), and service unregistration.
-if __name__ == '__main__':
- print "Multicast DNS Service Discovery for Python, version", __version__
- print "1. Testing registration of a service..."
- desc = {'version':'0.10','a':'test value', 'b':'another value'}
- info = ServiceInfo("_http._tcp.local.", "My Service Name._http._tcp.local.", socket.inet_aton("127.0.0.1"), 1234, 0, 0, desc)
- print " Registering service..."
- r.registerService(info)
- print " Registration done."
- print "2. Testing query of service information..."
- print " Getting ZOE service:", str(r.getServiceInfo("_http._tcp.local.", "ZOE._http._tcp.local."))
- print "3. Testing query of own service..."
- print " Getting self:", str(r.getServiceInfo("_http._tcp.local.", "My Service Name._http._tcp.local."))
- print "4. Testing unregister of service information..."
- r.unregisterService(info)
- print " Unregister done."
--- a/discovery.py Wed May 09 00:00:50 2012 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,232 +0,0 @@
-#This file is part of Beremiz, a Integrated Development Environment for
-#programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
-#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
-#See COPYING file for copyrights details.
-#This library is free software; you can redistribute it and/or
-#modify it under the terms of the GNU 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
-#General Public License for more details.
-#You should have received a copy of the GNU General Public
-#License along with this library; if not, write to the Free Software
-#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-import wx.lib.mixins.listctrl as listmix
-class AutoWidthListCtrl(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin):
- def __init__(self, parent, id, name, pos=wx.DefaultPosition,
- size=wx.DefaultSize, style=0):
- wx.ListCtrl.__init__(self, parent, id, pos, size, style, name=name)
- listmix.ListCtrlAutoWidthMixin.__init__(self)
-[ID_DISCOVERYDIALOG, ID_DISCOVERYDIALOGSTATICTEXT1,
- ID_DISCOVERYDIALOGSERVICESLIST, ID_DISCOVERYDIALOGREFRESHBUTTON,
- ID_DISCOVERYDIALOGLOCALBUTTON,
-] = [wx.NewId() for _init_ctrls in range(5)]
-class DiscoveryDialog(wx.Dialog, listmix.ColumnSorterMixin):
- def _init_coll_MainSizer_Items(self, parent):
- parent.AddWindow(self.staticText1, 0, border=20, flag=wx.TOP|wx.LEFT|wx.RIGHT|wx.GROW)
- parent.AddWindow(self.ServicesList, 0, border=20, flag=wx.LEFT|wx.RIGHT|wx.GROW)
- parent.AddSizer(self.ButtonGridSizer, 0, border=20, flag=wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.GROW)
- def _init_coll_MainSizer_Growables(self, parent):
- parent.AddGrowableCol(0)
- parent.AddGrowableRow(1)
- def _init_coll_ButtonGridSizer_Items(self, parent):
- parent.AddWindow(self.RefreshButton, 0, border=0, flag=0)
- parent.AddWindow(self.LocalButton, 0, border=0, flag=0)
- parent.AddSizer(self.ButtonSizer, 0, border=0, flag=0)
- def _init_coll_ButtonGridSizer_Growables(self, parent):
- parent.AddGrowableCol(0)
- parent.AddGrowableCol(1)
- parent.AddGrowableRow(1)
- def _init_sizers(self):
- self.MainSizer = wx.FlexGridSizer(cols=1, hgap=0, rows=3, vgap=10)
- self.ButtonGridSizer = wx.FlexGridSizer(cols=3, hgap=5, rows=1, vgap=0)
- self._init_coll_MainSizer_Items(self.MainSizer)
- self._init_coll_MainSizer_Growables(self.MainSizer)
- self._init_coll_ButtonGridSizer_Items(self.ButtonGridSizer)
- self._init_coll_ButtonGridSizer_Growables(self.ButtonGridSizer)
- self.SetSizer(self.MainSizer)
- def _init_ctrls(self, prnt):
- wx.Dialog.__init__(self, id=ID_DISCOVERYDIALOG,
- name='DiscoveryDialog', parent=prnt,
- size=wx.Size(600, 600), style=wx.DEFAULT_DIALOG_STYLE,
- title='Service Discovery')
- self.staticText1 = wx.StaticText(id=ID_DISCOVERYDIALOGSTATICTEXT1,
- label=_('Services available:'), name='staticText1', parent=self,
- pos=wx.Point(0, 0), size=wx.DefaultSize, style=0)
- self.ServicesList = AutoWidthListCtrl(id=ID_DISCOVERYDIALOGSERVICESLIST,
- name='ServicesList', parent=self, pos=wx.Point(0, 0), size=wx.Size(0, 0),
- style=wx.LC_REPORT|wx.LC_EDIT_LABELS|wx.LC_SORT_ASCENDING|wx.LC_SINGLE_SEL)
- self.ServicesList.InsertColumn(0, 'NAME')
- self.ServicesList.InsertColumn(1, 'TYPE')
- self.ServicesList.InsertColumn(2, 'IP')
- self.ServicesList.InsertColumn(3, 'PORT')
- self.ServicesList.SetColumnWidth(0, 150)
- self.ServicesList.SetColumnWidth(1, 150)
- self.ServicesList.SetColumnWidth(2, 150)
- self.ServicesList.SetColumnWidth(3, 150)
- self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected, id=ID_DISCOVERYDIALOGSERVICESLIST)
- self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemActivated, id=ID_DISCOVERYDIALOGSERVICESLIST)
- listmix.ColumnSorterMixin.__init__(self, 4)
- self.RefreshButton = wx.Button(id=ID_DISCOVERYDIALOGREFRESHBUTTON,
- label=_('Refresh'), name='RefreshButton', parent=self,
- pos=wx.Point(0, 0), size=wx.DefaultSize, style=0)
- self.Bind(wx.EVT_BUTTON, self.OnRefreshButton, id=ID_DISCOVERYDIALOGREFRESHBUTTON)
- self.LocalButton = wx.Button(id=ID_DISCOVERYDIALOGLOCALBUTTON,
- label=_('Local'), name='LocalButton', parent=self,
- pos=wx.Point(0, 0), size=wx.DefaultSize, style=0)
- self.Bind(wx.EVT_BUTTON, self.OnLocalButton, id=ID_DISCOVERYDIALOGLOCALBUTTON)
- self.ButtonSizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTER)
- def __init__(self, parent):
- self._init_ctrls(parent)
- self.ZeroConfInstance = Zeroconf()
- for browser in self.Browsers:
- self.ZeroConfInstance.close()
- for browser in self.Browsers:
- for t in connectors.dnssd_connectors.keys():
- self.Browsers.append(ServiceBrowser(self.ZeroConfInstance, t, self))
- def OnRefreshButton(self, event):
- self.ServicesList.DeleteAllItems()
- def OnLocalButton(self, event):
- self.EndModal(wx.ID_OK)
- # Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py
- return self.ServicesList
- def getColumnText(self, index, col):
- item = self.ServicesList.GetItem(index, col)
- def OnItemSelected(self, event):
- self.SetURI(event.m_itemIndex)
- def OnItemActivated(self, event):
- self.SetURI(event.m_itemIndex)
- self.EndModal(wx.ID_OK)
- connect_type = self.getColumnText(idx, 1)
- connect_address = self.getColumnText(idx, 2)
- connect_port = self.getColumnText(idx, 3)
- self.URI = "%s://%s:%s"%(connect_type, connect_address, connect_port)
- def removeService(self, zeroconf, type, name):
- wx.CallAfter(self._removeService, name)
- def _removeService(self, name):
- called when a service with the desired type goes offline.
- # loop through the list items looking for the service that went offline
- for idx in xrange(self.ServicesList.GetItemCount()):
- # this is the unique identifier assigned to the item
- item_id = self.ServicesList.GetItemData(idx)
- # this is the full typename that was received by addService
- item_name = self.itemDataMap[item_id][4]
- self.ServicesList.DeleteItem(idx)
- def addService(self, zeroconf, type, name):
- wx.CallAfter(self._addService, type, name)
- def _addService(self, type, name):
- called when a service with the desired type is discovered.
- info = self.ZeroConfInstance.getServiceInfo(type, name)
- svcname = name.split(".")[0]
- typename = type.split(".")[0][1:]
- ip = str(socket.inet_ntoa(info.getAddress()))
- num_items = self.ServicesList.GetItemCount()
- # display the new data in the list
- new_item = self.ServicesList.InsertStringItem(num_items, svcname)
- self.ServicesList.SetStringItem(new_item, 1, "%s" % typename)
- self.ServicesList.SetStringItem(new_item, 2, "%s" % ip)
- self.ServicesList.SetStringItem(new_item, 3, "%s" % port)
- # record the new data for the ColumnSorterMixin
- # we assign every list item a unique id (that won't change when items
- # are added or removed)
- self.ServicesList.SetItemData(new_item, self.nextItemId)
- # the value of each column has to be stored in the itemDataMap
- # so that ColumnSorterMixin knows how to sort the column.
- # "name" is included at the end so that self.removeService
- self.itemDataMap[self.nextItemId] = [ svcname, typename, ip, port, name ]
--- a/runtime/ServicePublisher.py Wed May 09 00:00:50 2012 +0200
+++ b/runtime/ServicePublisher.py Wed May 09 00:12:40 2012 +0200
@@ -22,7 +22,8 @@
#License along with this library; if not, write to the Free Software
#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-import Zeroconf, socket, threading
+import socket, threading +from util import Zeroconf class ServicePublisher():
--- a/targets/Xenomai/__init__.py Wed May 09 00:00:50 2012 +0200
+++ b/targets/Xenomai/__init__.py Wed May 09 00:12:40 2012 +0200
@@ -6,7 +6,7 @@
""" Get xeno-config from target parameters """
xeno_config=self.CTRInstance.GetTarget().getcontent()["value"].getXenoConfig()
- from ProcessLogger import ProcessLogger
+ from util.ProcessLogger import ProcessLogger status, result, err_result = ProcessLogger(self.CTRInstance.logger,
xeno_config + " --skin=native --"+flagsname,
--- a/targets/toolchain_gcc.py Wed May 09 00:00:50 2012 +0200
+++ b/targets/toolchain_gcc.py Wed May 09 00:12:40 2012 +0200
@@ -1,5 +1,5 @@
-from ProcessLogger import ProcessLogger
+from util.ProcessLogger import ProcessLogger includes_re = re.compile('\s*#include\s*["<]([^">]*)[">].*')
--- a/targets/toolchain_makefile.py Wed May 09 00:00:50 2012 +0200
+++ b/targets/toolchain_makefile.py Wed May 09 00:12:40 2012 +0200
@@ -1,5 +1,5 @@
-from ProcessLogger import ProcessLogger
+from util.ProcessLogger import ProcessLogger --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/util/ProcessLogger.py Wed May 09 00:12:40 2012 +0200
@@ -0,0 +1,199 @@
+#This file is part of Beremiz, a Integrated Development Environment for +#programming IEC 61131-3 automates supporting plcopen standard and CanFestival. +#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD +#See COPYING file for copyrights details. +#This library is free software; you can redistribute it and/or +#modify it under the terms of the GNU 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 +#General Public License for more details. +#You should have received a copy of the GNU General Public +#License along with this library; if not, write to the Free Software +#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +import subprocess, ctypes +from threading import Timer, Lock, Thread, Semaphore + from signal import SIGTERM, SIGKILL +class outputThread(Thread): + Thread is used to print the output of a command to the stdout + def __init__(self, Proc, fd, callback=None, endcallback=None): + self.callback = callback + self.endcallback = endcallback + while outchunk != '' and not self.killed : + outchunk = self.fd.readline() + if self.callback : self.callback(outchunk) + while self.retval is None and not self.killed : + self.retval = self.Proc.poll() + outchunk = self.fd.readline() + if self.callback : self.callback(outchunk) + while outchunk != '' and not self.killed : + outchunk = self.fd.readline() + if self.callback : self.callback(outchunk) + self.endcallback(self.Proc.pid, err) + def __init__(self, logger, Command, finish_callback = None, + no_stdout = False, no_stderr = False, no_gui = True, + timeout = None, outlimit = None, errlimit = None, + endlog = None, keyword = None, kill_it = False): + if not isinstance(Command, list): + self.Command_str = Command + for i,word in enumerate(Command.replace("'",'"').split('"')): + self.Command.extend(word.split()) + self.Command.append(word) + self.Command_str = subprocess.list2cmdline(self.Command) + self.finish_callback = finish_callback + self.no_stdout = no_stdout + self.no_stderr = no_stderr + self.startupinfo = None + self.errlimit = errlimit + self.outlimit = outlimit + self.finishsem = Semaphore(0) + "stdin":subprocess.PIPE, + "stdout":subprocess.PIPE, + "stderr":subprocess.PIPE} + if no_gui == True and wx.Platform == '__WXMSW__': + self.startupinfo = subprocess.STARTUPINFO() + self.startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + popenargs["startupinfo"] = self.startupinfo + elif wx.Platform == '__WXGTK__': + popenargs["shell"] = False + self.Proc = subprocess.Popen( self.Command, **popenargs ) + self.outt = outputThread( + self.errt = outputThread( + self.timeout = Timer(timeout,self.endlog) + if (self.keyword and v.find(self.keyword)!=-1) or (self.outlimit and self.outlen > self.outlimit): + self.logger.write_warning(v) + if self.errlimit and self.errlen > self.errlimit: + def log_the_end(self,ecode,pid): + self.logger.write(self.Command_str + "\n") + self.logger.write_warning(_("exited with status %s (pid %s)\n")%(str(ecode),str(pid))) + def finish(self, pid,ecode): + if self.timeout: self.timeout.cancel() + self.log_the_end(ecode,pid) + if self.finish_callback is not None: + self.finish_callback(self,ecode,pid) + self.finishsem.release() + def kill(self,gently=True): + self.outt.killed = True + self.errt.killed = True + if wx.Platform == '__WXMSW__': + handle = ctypes.windll.kernel32.OpenProcess(PROCESS_TERMINATE, False, self.Proc.pid) + ctypes.windll.kernel32.TerminateProcess(handle, -1) + ctypes.windll.kernel32.CloseHandle(handle) + os.kill(self.Proc.pid, sig) + if self.endlock.acquire(False): + self.finishsem.release() + if not self.outt.finished and self.kill_it: + self.finishsem.acquire() + return [self.exitcode, "".join(self.outdata), "".join(self.errdata)] --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/util/Zeroconf.py Wed May 09 00:12:40 2012 +0200
@@ -0,0 +1,1559 @@
+""" Multicast DNS Service Discovery for Python, v0.12 + Copyright (C) 2003, Paul Scott-Murphy + This module provides a framework for the use of DNS Service Discovery + using IP multicast. It has been tested against the JRendezvous + implementation from <a href="http://strangeberry.com">StrangeBerry</a>, + and against the mDNSResponder from Mac OS X 10.3.8. + 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. + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +"""0.12 update - allow selection of binding interface + typo fix - Thanks A. M. Kuchlingi + removed all use of word 'Rendezvous' - this is an API change""" +"""0.11 update - correction to comments for addListener method + support for new record types seen from OS X + ignore unknown DNS record types + works alongside other processes using port 5353 (e.g. on Mac OS X) + tested against Mac OS X 10.3.2's mDNSResponder + corrections to removal of list entries for service browser""" +"""0.10 update - Jonathon Paisley contributed these corrections: + always multicast replies, even when query is unicast + correct a pointer encoding problem + can now write records in any order + traceback shown on failure + better TXT record parsing + server is now separate from name + can cancel a service browser + modified some unit tests to accommodate these changes""" +"""0.09 update - remove all records on service unregistration + fix DOS security problem with readName""" +"""0.08 update - changed licensing to LGPL""" +"""0.07 update - faster shutdown on engine + pointer encoding of outgoing names + ServiceBrowser now works +"""0.06 update - small improvements with unit tests + added defined exception types + fixed hostname/interface problem + fixed socket timeout problem + fixed addServiceListener() typo bug + using select() for socket reads + tested on Debian unstable with Python 2.2.2""" +"""0.05 update - ensure case insensitivty on domain names + support for unicast DNS queries""" +"""0.04 update - added some unit tests + added __ne__ adjuncts where required + ensure names end in '.local.' + timeout on receiving socket for clean shutdown""" +__author__ = "Paul Scott-Murphy" +__email__ = "paul at scott dash murphy dot com" +__all__ = ["Zeroconf", "ServiceInfo", "ServiceBrowser"] +globals()['_GLOBAL_DONE'] = 0 +_MDNS_ADDR = '224.0.0.251' +_DNS_TTL = 60 * 60; # one hour default TTL +_MAX_MSG_TYPICAL = 1460 # unused +_MAX_MSG_ABSOLUTE = 8972 +_FLAGS_QR_MASK = 0x8000 # query response mask +_FLAGS_QR_QUERY = 0x0000 # query +_FLAGS_QR_RESPONSE = 0x8000 # response +_FLAGS_AA = 0x0400 # Authorative answer +_FLAGS_TC = 0x0200 # Truncated +_FLAGS_RD = 0x0100 # Recursion desired +_FLAGS_RA = 0x8000 # Recursion available +_FLAGS_Z = 0x0040 # Zero +_FLAGS_AD = 0x0020 # Authentic data +_FLAGS_CD = 0x0010 # Checking disabled +# Mapping constants to names +_CLASSES = { _CLASS_IN : "in", +_TYPES = { _TYPE_A : "a", +def currentTimeMillis(): + """Current system time in milliseconds""" + return time.time() * 1000 +class NonLocalNameException(Exception): +class NonUniqueNameException(Exception): +class NamePartTooLongException(Exception): +class AbstractMethodException(Exception): +class BadTypeInNameException(Exception): +# implementation classes + def __init__(self, name, type, clazz): + self.key = string.lower(name) + self.clazz = clazz & _CLASS_MASK + self.unique = (clazz & _CLASS_UNIQUE) != 0 + def __eq__(self, other): + """Equality test on name, type, and class""" + if isinstance(other, DNSEntry): + return self.name == other.name and self.type == other.type and self.clazz == other.clazz + def __ne__(self, other): + """Non-equality test""" + return not self.__eq__(other) + def getClazz(self, clazz): + return "?(%s)" % (clazz) + def getType(self, type): + return "?(%s)" % (type) + def toString(self, hdr, other): + """String representation with additional information""" + result = "%s[%s,%s" % (hdr, self.getType(self.type), self.getClazz(self.clazz)) + result += ",%s]" % (other) +class DNSQuestion(DNSEntry): + """A DNS question entry""" + def __init__(self, name, type, clazz): + if not name.endswith(".local."): + raise NonLocalNameException + DNSEntry.__init__(self, name, type, clazz) + def answeredBy(self, rec): + """Returns true if the question is answered by the record""" + return self.clazz == rec.clazz and (self.type == rec.type or self.type == _TYPE_ANY) and self.name == rec.name + """String representation""" + return DNSEntry.toString(self, "question", None) +class DNSRecord(DNSEntry): + """A DNS record - like a DNS entry, but has a TTL""" + def __init__(self, name, type, clazz, ttl): + DNSEntry.__init__(self, name, type, clazz) + self.created = currentTimeMillis() + def __eq__(self, other): + """Tests equality as per DNSRecord""" + if isinstance(other, DNSRecord): + return DNSEntry.__eq__(self, other) + def suppressedBy(self, msg): + """Returns true if any answer in a message can suffice for the + information held in this record.""" + for record in msg.answers: + if self.suppressedByAnswer(record): + def suppressedByAnswer(self, other): + """Returns true if another record has same name, type and class, + and if its TTL is at least half of this record's.""" + if self == other and other.ttl > (self.ttl / 2): + def getExpirationTime(self, percent): + """Returns the time at which this record will have expired + by a certain percentage.""" + return self.created + (percent * self.ttl * 10) + def getRemainingTTL(self, now): + """Returns the remaining TTL in seconds.""" + return max(0, (self.getExpirationTime(100) - now) / 1000) + def isExpired(self, now): + """Returns true if this record has expired.""" + return self.getExpirationTime(100) <= now + def isStale(self, now): + """Returns true if this record is at least half way expired.""" + return self.getExpirationTime(50) <= now + def resetTTL(self, other): + """Sets this record's TTL and created time to that of + self.created = other.created + raise AbstractMethodException + def toString(self, other): + """String representation with addtional information""" + arg = "%s/%s,%s" % (self.ttl, self.getRemainingTTL(currentTimeMillis()), other) + return DNSEntry.toString(self, "record", arg) +class DNSAddress(DNSRecord): + """A DNS address record""" + def __init__(self, name, type, clazz, ttl, address): + DNSRecord.__init__(self, name, type, clazz, ttl) + """Used in constructing an outgoing packet""" + out.writeString(self.address, len(self.address)) + def __eq__(self, other): + """Tests equality on address""" + if isinstance(other, DNSAddress): + return self.address == other.address + """String representation""" + return socket.inet_ntoa(self.address) +class DNSHinfo(DNSRecord): + """A DNS host information record""" + def __init__(self, name, type, clazz, ttl, cpu, os): + DNSRecord.__init__(self, name, type, clazz, ttl) + """Used in constructing an outgoing packet""" + out.writeString(self.cpu, len(self.cpu)) + out.writeString(self.os, len(self.os)) + def __eq__(self, other): + """Tests equality on cpu and os""" + if isinstance(other, DNSHinfo): + return self.cpu == other.cpu and self.os == other.os + """String representation""" + return self.cpu + " " + self.os +class DNSPointer(DNSRecord): + """A DNS pointer record""" + def __init__(self, name, type, clazz, ttl, alias): + DNSRecord.__init__(self, name, type, clazz, ttl) + """Used in constructing an outgoing packet""" + out.writeName(self.alias) + def __eq__(self, other): + """Tests equality on alias""" + if isinstance(other, DNSPointer): + return self.alias == other.alias + """String representation""" + return self.toString(self.alias) +class DNSText(DNSRecord): + """A DNS text record""" + def __init__(self, name, type, clazz, ttl, text): + DNSRecord.__init__(self, name, type, clazz, ttl) + """Used in constructing an outgoing packet""" + out.writeString(self.text, len(self.text)) + def __eq__(self, other): + """Tests equality on text""" + if isinstance(other, DNSText): + return self.text == other.text + """String representation""" + if len(self.text) > 10: + return self.toString(self.text[:7] + "...") + return self.toString(self.text) +class DNSService(DNSRecord): + """A DNS service record""" + def __init__(self, name, type, clazz, ttl, priority, weight, port, server): + DNSRecord.__init__(self, name, type, clazz, ttl) + self.priority = priority + """Used in constructing an outgoing packet""" + out.writeShort(self.priority) + out.writeShort(self.weight) + out.writeShort(self.port) + out.writeName(self.server) + def __eq__(self, other): + """Tests equality on priority, weight, port and server""" + if isinstance(other, DNSService): + return self.priority == other.priority and self.weight == other.weight and self.port == other.port and self.server == other.server + """String representation""" + return self.toString("%s:%s" % (self.server, self.port)) +class DNSIncoming(object): + """Object representation of an incoming DNS packet""" + def __init__(self, data): + """Constructor from string holding bytes of packet""" + self.numAuthorities = 0 + self.numAdditionals = 0 + """Reads header portion of packet""" + length = struct.calcsize(format) + info = struct.unpack(format, self.data[self.offset:self.offset+length]) + self.numQuestions = info[2] + self.numAnswers = info[3] + self.numAuthorities = info[4] + self.numAdditionals = info[5] + def readQuestions(self): + """Reads questions section of packet""" + length = struct.calcsize(format) + for i in range(0, self.numQuestions): + info = struct.unpack(format, self.data[self.offset:self.offset+length]) + question = DNSQuestion(name, info[0], info[1]) + self.questions.append(question) + """Reads an integer from the packet""" + length = struct.calcsize(format) + info = struct.unpack(format, self.data[self.offset:self.offset+length]) + def readCharacterString(self): + """Reads a character string from the packet""" + length = ord(self.data[self.offset]) + return self.readString(length) + def readString(self, len): + """Reads a string of a given length from the packet""" + format = '!' + str(len) + 's' + length = struct.calcsize(format) + info = struct.unpack(format, self.data[self.offset:self.offset+length]) + def readUnsignedShort(self): + """Reads an unsigned short from the packet""" + length = struct.calcsize(format) + info = struct.unpack(format, self.data[self.offset:self.offset+length]) + """Reads the answers, authorities and additionals section of the packet""" + length = struct.calcsize(format) + n = self.numAnswers + self.numAuthorities + self.numAdditionals + domain = self.readName() + info = struct.unpack(format, self.data[self.offset:self.offset+length]) + rec = DNSAddress(domain, info[0], info[1], info[2], self.readString(4)) + elif info[0] == _TYPE_CNAME or info[0] == _TYPE_PTR: + rec = DNSPointer(domain, info[0], info[1], info[2], self.readName()) + elif info[0] == _TYPE_TXT: + rec = DNSText(domain, info[0], info[1], info[2], self.readString(info[3])) + elif info[0] == _TYPE_SRV: + rec = DNSService(domain, info[0], info[1], info[2], self.readUnsignedShort(), self.readUnsignedShort(), self.readUnsignedShort(), self.readName()) + elif info[0] == _TYPE_HINFO: + rec = DNSHinfo(domain, info[0], info[1], info[2], self.readCharacterString(), self.readCharacterString()) + elif info[0] == _TYPE_AAAA: + rec = DNSAddress(domain, info[0], info[1], info[2], self.readString(16)) + # Try to ignore types we don't know about + # this may mean the rest of the name is + # unable to be parsed, and may show errors + # so this is left for debugging. New types + # encountered need to be parsed properly. + #print "UNKNOWN TYPE = " + str(info[0]) + #raise BadTypeInNameException + self.answers.append(rec) + """Returns true if this is a query""" + return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_QUERY + """Returns true if this is a response""" + return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_RESPONSE + def readUTF(self, offset, len): + """Reads a UTF-8 string of a given length from the packet""" + result = self.data[offset:offset+len].decode('utf-8') + """Reads a domain name from the packet""" + len = ord(self.data[off]) + result = ''.join((result, self.readUTF(off, len) + '.')) + off = ((len & 0x3F) << 8) | ord(self.data[off]) + raise _("Bad domain name (circular) at ") + str(off) + raise _("Bad domain name at ") + str(off) +class DNSOutgoing(object): + """Object representation of an outgoing packet""" + def __init__(self, flags, multicast = 1): + self.multicast = multicast + def addQuestion(self, record): + self.questions.append(record) + def addAnswer(self, inp, record): + if not record.suppressedBy(inp): + self.addAnswerAtTime(record, 0) + def addAnswerAtTime(self, record, now): + """Adds an answer if if does not expire by a certain time""" + if now == 0 or not record.isExpired(now): + self.answers.append((record, now)) + def addAuthorativeAnswer(self, record): + """Adds an authoritative answer""" + self.authorities.append(record) + def addAdditionalAnswer(self, record): + """Adds an additional answer""" + self.additionals.append(record) + def writeByte(self, value): + """Writes a single byte to the packet""" + self.data.append(struct.pack(format, chr(value))) + def insertShort(self, index, value): + """Inserts an unsigned short in a certain position in the packet""" + self.data.insert(index, struct.pack(format, value)) + def writeShort(self, value): + """Writes an unsigned short to the packet""" + self.data.append(struct.pack(format, value)) + def writeInt(self, value): + """Writes an unsigned integer to the packet""" + self.data.append(struct.pack(format, int(value))) + def writeString(self, value, length): + """Writes a string to the packet""" + format = '!' + str(length) + 's' + self.data.append(struct.pack(format, value)) + """Writes a UTF-8 string of a given length to the packet""" + utfstr = s.encode('utf-8') + raise NamePartTooLongException + self.writeString(utfstr, length) + def writeName(self, name): + """Writes a domain name to the packet""" + # Find existing instance of this name in packet + index = self.names[name] + # No record of this name already, so write it + # out as normal, recording the location of the name + # for future pointers to it. + self.names[name] = self.size + parts = name.split('.') + # An index was found, so write a pointer to it + self.writeByte((index >> 8) | 0xC0) + def writeQuestion(self, question): + """Writes a question to the packet""" + self.writeName(question.name) + self.writeShort(question.type) + self.writeShort(question.clazz) + def writeRecord(self, record, now): + """Writes a record (answer, authoritative answer, additional) to + self.writeName(record.name) + self.writeShort(record.type) + if record.unique and self.multicast: + self.writeShort(record.clazz | _CLASS_UNIQUE) + self.writeShort(record.clazz) + self.writeInt(record.ttl) + self.writeInt(record.getRemainingTTL(now)) + # Adjust size for the short we will write before this record + length = len(''.join(self.data[index:])) + self.insertShort(index, length) # Here is the short we adjusted for + """Returns a string containing the packet's bytes + No further parts should be added to the packet once this + for question in self.questions: + self.writeQuestion(question) + for answer, time in self.answers: + self.writeRecord(answer, time) + for authority in self.authorities: + self.writeRecord(authority, 0) + for additional in self.additionals: + self.writeRecord(additional, 0) + self.insertShort(0, len(self.additionals)) + self.insertShort(0, len(self.authorities)) + self.insertShort(0, len(self.answers)) + self.insertShort(0, len(self.questions)) + self.insertShort(0, self.flags) + self.insertShort(0, self.id) + return ''.join(self.data) + """A cache of DNS entries""" + list = self.cache[entry.key] + list = self.cache[entry.key] = [] + def remove(self, entry): + list = self.cache[entry.key] + """Gets an entry by key. Will return None if there is no + list = self.cache[entry.key] + return list[list.index(entry)] + def getByDetails(self, name, type, clazz): + """Gets an entry by details. Will return None if there is + entry = DNSEntry(name, type, clazz) + def entriesWithName(self, name): + """Returns a list of entries whose key matches the name.""" + return self.cache[name] + """Returns a list of all entries""" + def add(x, y): return x+y + return reduce(add, self.cache.values()) +class Engine(threading.Thread): + """An engine wraps read access to sockets, allowing objects that + need to receive data from sockets to be called back when the + A reader needs a handle_read() method, which is called when the socket + it is interested in is ready for reading. + Writers are not implemented here, because we only send short + def __init__(self, zeroconf): + threading.Thread.__init__(self) + self.zeroconf = zeroconf + self.readers = {} # maps socket to reader + self.condition = threading.Condition() + while not globals()['_GLOBAL_DONE']: + # No sockets to manage, but we wait for the timeout + # or addition of a socket + self.condition.acquire() + self.condition.wait(self.timeout) + self.condition.release() + rr, wr, er = select.select(rs, [], [], self.timeout) + self.readers[socket].handle_read() + # Ignore errors that occur on shutdown + self.condition.acquire() + result = self.readers.keys() + self.condition.release() + def addReader(self, reader, socket): + self.condition.acquire() + self.readers[socket] = reader + self.condition.notify() + self.condition.release() + def delReader(self, socket): + self.condition.acquire() + del(self.readers[socket]) + self.condition.notify() + self.condition.release() + self.condition.acquire() + self.condition.notify() + self.condition.release() + """A Listener is used by this module to listen on the multicast + group to which DNS messages are sent, allowing the implementation + to cache information as it arrives. + It requires registration with an Engine object in order to have + the read() method called when a socket is availble for reading.""" + def __init__(self, zeroconf): + self.zeroconf = zeroconf + self.zeroconf.engine.addReader(self, self.zeroconf.socket) + data, (addr, port) = self.zeroconf.socket.recvfrom(_MAX_MSG_ABSOLUTE) + msg = DNSIncoming(data) + # Always multicast responses + self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT) + # If it's not a multicast query, reply via unicast + elif port == _DNS_PORT: + self.zeroconf.handleQuery(msg, addr, port) + self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT) + self.zeroconf.handleResponse(msg) +class Reaper(threading.Thread): + """A Reaper is used by this module to remove cache entries that + def __init__(self, zeroconf): + threading.Thread.__init__(self) + self.zeroconf = zeroconf + self.zeroconf.wait(10 * 1000) + if globals()['_GLOBAL_DONE']: + now = currentTimeMillis() + for record in self.zeroconf.cache.entries(): + if record.isExpired(now): + self.zeroconf.updateRecord(now, record) + self.zeroconf.cache.remove(record) +class ServiceBrowser(threading.Thread): + """Used to browse for a service of a specific type. + The listener object will have its addService() and + removeService() methods called when this browser + discovers changes in the services availability.""" + def __init__(self, zeroconf, type, listener): + """Creates a browser for a specific type""" + threading.Thread.__init__(self) + self.zeroconf = zeroconf + self.listener = listener + self.nextTime = currentTimeMillis() + self.delay = _BROWSER_TIME + self.zeroconf.addListener(self, DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN)) + def updateRecord(self, zeroconf, now, record): + """Callback invoked by Zeroconf when new information arrives. + Updates information required by browser in the Zeroconf cache.""" + if record.type == _TYPE_PTR and record.name == self.type: + expired = record.isExpired(now) + oldrecord = self.services[record.alias.lower()] + oldrecord.resetTTL(record) + del(self.services[record.alias.lower()]) + callback = lambda x: self.listener.removeService(x, self.type, record.alias) + self.list.append(callback) + self.services[record.alias.lower()] = record + callback = lambda x: self.listener.addService(x, self.type, record.alias) + self.list.append(callback) + expires = record.getExpirationTime(75) + if expires < self.nextTime: + self.nextTime = expires + self.zeroconf.notifyAll() + now = currentTimeMillis() + if len(self.list) == 0 and self.nextTime > now: + self.zeroconf.wait(self.nextTime - now) + if globals()['_GLOBAL_DONE'] or self.done: + now = currentTimeMillis() + if self.nextTime <= now: + out = DNSOutgoing(_FLAGS_QR_QUERY) + out.addQuestion(DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN)) + for record in self.services.values(): + if not record.isExpired(now): + out.addAnswerAtTime(record, now) + self.zeroconf.send(out) + self.nextTime = now + self.delay + self.delay = min(20 * 1000, self.delay * 2) + event = self.list.pop(0) +class ServiceInfo(object): + """Service information""" + def __init__(self, type, name, address=None, port=None, weight=0, priority=0, properties=None, server=None): + """Create a service description. + type: fully qualified service type name + name: fully qualified service name + address: IP address as unsigned short, network byte order + port: port that the service runs on + weight: weight of the service + priority: priority of the service + properties: dictionary of properties (or a string holding the bytes for the text field) + server: fully qualified name for service host (defaults to name)""" + if not name.endswith(type): + raise BadTypeInNameException + self.priority = priority + self.setProperties(properties) + def setProperties(self, properties): + """Sets properties and text of this info from a dictionary""" + if isinstance(properties, dict): + self.properties = properties + value = properties[key] + suffix = ''.encode('utf-8') + elif isinstance(value, str): + suffix = value.encode('utf-8') + elif isinstance(value, int): + suffix = ''.encode('utf-8') + list.append('='.join((key, suffix))) + result = ''.join((result, struct.pack('!c', chr(len(item))), item)) + def setText(self, text): + """Sets properties and text given a text field""" + length = ord(text[index]) + strs.append(text[index:index+length]) + # No equals sign at all + elif value == 'false' or not value: + # Only update non-existent properties + if key and result.get(key) == None: + self.properties = result + if self.type is not None and self.name.endswith("." + self.type): + return self.name[:len(self.name) - len(self.type) - 1] + """Pirority accessor""" + def getProperties(self): + """Properties accessor""" + def updateRecord(self, zeroconf, now, record): + """Updates service information from a DNS record""" + if record is not None and not record.isExpired(now): + if record.type == _TYPE_A: + if record.name == self.server: + self.address = record.address + elif record.type == _TYPE_SRV: + if record.name == self.name: + self.server = record.server + self.port = record.port + self.weight = record.weight + self.priority = record.priority + self.updateRecord(zeroconf, now, zeroconf.cache.getByDetails(self.server, _TYPE_A, _CLASS_IN)) + elif record.type == _TYPE_TXT: + if record.name == self.name: + self.setText(record.text) + def request(self, zeroconf, timeout): + """Returns true if the service could be discovered on the + network, and updates this object with details discovered. + now = currentTimeMillis() + zeroconf.addListener(self, DNSQuestion(self.name, _TYPE_ANY, _CLASS_IN)) + while self.server is None or self.address is None or self.text is None: + out = DNSOutgoing(_FLAGS_QR_QUERY) + out.addQuestion(DNSQuestion(self.name, _TYPE_SRV, _CLASS_IN)) + out.addAnswerAtTime(zeroconf.cache.getByDetails(self.name, _TYPE_SRV, _CLASS_IN), now) + out.addQuestion(DNSQuestion(self.name, _TYPE_TXT, _CLASS_IN)) + out.addAnswerAtTime(zeroconf.cache.getByDetails(self.name, _TYPE_TXT, _CLASS_IN), now) + if self.server is not None: + out.addQuestion(DNSQuestion(self.server, _TYPE_A, _CLASS_IN)) + out.addAnswerAtTime(zeroconf.cache.getByDetails(self.server, _TYPE_A, _CLASS_IN), now) + zeroconf.wait(min(next, last) - now) + now = currentTimeMillis() + zeroconf.removeListener(self) + def __eq__(self, other): + """Tests equality of service name""" + if isinstance(other, ServiceInfo): + return other.name == self.name + def __ne__(self, other): + """Non-equality test""" + return not self.__eq__(other) + """String representation""" + result = "service[%s,%s:%s," % (self.name, socket.inet_ntoa(self.getAddress()), self.port) + if len(self.text) < 20: + result += self.text[:17] + "..." + """Implementation of Zeroconf Multicast DNS Service Discovery + Supports registration, unregistration, queries and browsing. + def __init__(self, bindaddress=None): + """Creates an instance of the Zeroconf class, establishing + multicast communications, listening and reaping threads.""" + globals()['_GLOBAL_DONE'] = 0 + self.intf = bindaddress + self.group = ('', _MDNS_PORT) + self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + # SO_REUSEADDR should be equivalent to SO_REUSEPORT for + # multicast UDP sockets (p 731, "TCP/IP Illustrated, + # Volume 2"), but some BSD-derived systems require + # SO_REUSEPORT to be specified explicity. Also, not all + # versions of Python have SO_REUSEPORT available. So + # if you're on a BSD-based system, and haven't upgraded + # to Python 2.3 yet, you may find this library doesn't + self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_TTL, 255) + self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1) + self.socket.bind(self.group) + # Some versions of linux raise an exception even though + # the SO_REUSE* options have been set, so ignore it + if self.intf is not None: + self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_IF, socket.inet_aton(self.intf) + socket.inet_aton('0.0.0.0')) + self.socket.setsockopt(socket.SOL_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0')) + self.cache = DNSCache() + self.condition = threading.Condition() + self.engine = Engine(self) + self.listener = Listener(self) + self.reaper = Reaper(self) + if self.intf is not None: + return self.intf.startswith("127.0.0.1") + if self.intf is not None: + return self.intf.startswith("169.254.") + def wait(self, timeout): + """Calling thread waits for a given number of milliseconds or + self.condition.acquire() + self.condition.wait(timeout/1000) + self.condition.release() + """Notifies all waiting threads""" + self.condition.acquire() + self.condition.notifyAll() + self.condition.release() + def getServiceInfo(self, type, name, timeout=3000): + """Returns network's service information for a particular + name and type, or None if no service matches by the timeout, + which defaults to 3 seconds.""" + info = ServiceInfo(type, name) + if info.request(self, timeout): + def addServiceListener(self, type, listener): + """Adds a listener for a particular service type. This object + will then have its updateRecord method called when information + arrives for that type.""" + self.removeServiceListener(listener) + self.browsers.append(ServiceBrowser(self, type, listener)) + def removeServiceListener(self, listener): + """Removes a listener from the set that is currently listening.""" + for browser in self.browsers: + if browser.listener == listener: + def registerService(self, info, ttl=_DNS_TTL): + """Registers service information to the network with a default TTL + of 60 seconds. Zeroconf will then respond to requests for + information for that service. The name of the service may be + changed if needed to make it unique on the network.""" + self.checkService(info) + self.services[info.name.lower()] = info + now = currentTimeMillis() + self.wait(nextTime - now) + now = currentTimeMillis() + out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) + out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, ttl, info.name), 0) + out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, ttl, info.priority, info.weight, info.port, info.server), 0) + out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, ttl, info.text), 0) + out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, ttl, info.address), 0) + nextTime += _REGISTER_TIME + def unregisterService(self, info): + """Unregister a service.""" + del(self.services[info.name.lower()]) + now = currentTimeMillis() + self.wait(nextTime - now) + now = currentTimeMillis() + out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) + out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0) + out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, 0, info.priority, info.weight, info.port, info.name), 0) + out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0) + out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, 0, info.address), 0) + nextTime += _UNREGISTER_TIME + def unregisterAllServices(self): + """Unregister all registered services.""" + if len(self.services) > 0: + now = currentTimeMillis() + self.wait(nextTime - now) + now = currentTimeMillis() + out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) + for info in self.services.values(): + out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0) + out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, 0, info.priority, info.weight, info.port, info.server), 0) + out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0) + out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, 0, info.address), 0) + nextTime += _UNREGISTER_TIME + def checkService(self, info): + """Checks the network for a unique service name, modifying the + ServiceInfo passed in if it is not unique.""" + now = currentTimeMillis() + for record in self.cache.entriesWithName(info.type): + if record.type == _TYPE_PTR and not record.isExpired(now) and record.alias == info.name: + if (info.name.find('.') < 0): + info.name = info.name + ".[" + info.address + ":" + info.port + "]." + info.type + self.checkService(info) + raise NonUniqueNameException + self.wait(nextTime - now) + now = currentTimeMillis() + out = DNSOutgoing(_FLAGS_QR_QUERY | _FLAGS_AA) + out.addQuestion(DNSQuestion(info.type, _TYPE_PTR, _CLASS_IN)) + out.addAuthorativeAnswer(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, info.name)) + nextTime += _CHECK_TIME + def addListener(self, listener, question): + """Adds a listener for a given question. The listener will have + its updateRecord method called when information is available to + answer the question.""" + now = currentTimeMillis() + self.listeners.append(listener) + if question is not None: + for record in self.cache.entriesWithName(question.name): + if question.answeredBy(record) and not record.isExpired(now): + listener.updateRecord(self, now, record) + def removeListener(self, listener): + """Removes a listener.""" + self.listeners.remove(listener) + def updateRecord(self, now, rec): + """Used to notify listeners of new information that has updated + for listener in self.listeners: + listener.updateRecord(self, now, rec) + def handleResponse(self, msg): + """Deal with incoming response packets. All answers + are held in the cache, and listeners are notified.""" + now = currentTimeMillis() + for record in msg.answers: + expired = record.isExpired(now) + if record in self.cache.entries(): + self.cache.remove(record) + entry = self.cache.get(record) + self.updateRecord(now, record) + def handleQuery(self, msg, addr, port): + """Deal with incoming query packets. Provides a response if + # Support unicast client responses + out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA, 0) + for question in msg.questions: + out.addQuestion(question) + for question in msg.questions: + if question.type == _TYPE_PTR: + for service in self.services.values(): + if question.name == service.type: + out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) + out.addAnswer(msg, DNSPointer(service.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, service.name)) + out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) + # Answer A record queries for any service addresses we know + if question.type == _TYPE_A or question.type == _TYPE_ANY: + for service in self.services.values(): + if service.server == question.name.lower(): + out.addAnswer(msg, DNSAddress(question.name, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address)) + service = self.services.get(question.name.lower(), None) + if not service: continue + if question.type == _TYPE_SRV or question.type == _TYPE_ANY: + out.addAnswer(msg, DNSService(question.name, _TYPE_SRV, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.priority, service.weight, service.port, service.server)) + if question.type == _TYPE_TXT or question.type == _TYPE_ANY: + out.addAnswer(msg, DNSText(question.name, _TYPE_TXT, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.text)) + if question.type == _TYPE_SRV: + out.addAdditionalAnswer(DNSAddress(service.server, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address)) + if out is not None and out.answers: + self.send(out, addr, port) + def send(self, out, addr = _MDNS_ADDR, port = _MDNS_PORT): + """Sends an outgoing packet.""" + # This is a quick test to see if we can parse the packets we generate + #temp = DNSIncoming(out.packet()) + bytes_sent = self.socket.sendto(out.packet(), 0, (addr, port)) + # Ignore this, it may be a temporary loss of network connection + """Ends the background threads, and prevent this instance from + servicing further queries.""" + if globals()['_GLOBAL_DONE'] == 0: + globals()['_GLOBAL_DONE'] = 1 + self.unregisterAllServices() + self.socket.setsockopt(socket.SOL_IP, socket.IP_DROP_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0')) +# Test a few module features, including service registration, service +# query (for Zoe), and service unregistration. +if __name__ == '__main__': + print "Multicast DNS Service Discovery for Python, version", __version__ + print "1. Testing registration of a service..." + desc = {'version':'0.10','a':'test value', 'b':'another value'} + info = ServiceInfo("_http._tcp.local.", "My Service Name._http._tcp.local.", socket.inet_aton("127.0.0.1"), 1234, 0, 0, desc) + print " Registering service..." + r.registerService(info) + print " Registration done." + print "2. Testing query of service information..." + print " Getting ZOE service:", str(r.getServiceInfo("_http._tcp.local.", "ZOE._http._tcp.local.")) + print "3. Testing query of own service..." + print " Getting self:", str(r.getServiceInfo("_http._tcp.local.", "My Service Name._http._tcp.local.")) + print "4. Testing unregister of service information..." + r.unregisterService(info) + print " Unregister done." --- a/util/__init__.py Wed May 09 00:00:50 2012 +0200
+++ b/util/__init__.py Wed May 09 00:12:40 2012 +0200
@@ -1,4 +1,7 @@
import TextCtrlAutoComplete
import BrowseValuesLibraryDialog
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/util/discovery.py Wed May 09 00:12:40 2012 +0200
@@ -0,0 +1,232 @@
+#This file is part of Beremiz, a Integrated Development Environment for +#programming IEC 61131-3 automates supporting plcopen standard and CanFestival. +#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD +#See COPYING file for copyrights details. +#This library is free software; you can redistribute it and/or +#modify it under the terms of the GNU 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 +#General Public License for more details. +#You should have received a copy of the GNU General Public +#License along with this library; if not, write to the Free Software +#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +import wx.lib.mixins.listctrl as listmix +from util.Zeroconf import * +class AutoWidthListCtrl(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin): + def __init__(self, parent, id, name, pos=wx.DefaultPosition, + size=wx.DefaultSize, style=0): + wx.ListCtrl.__init__(self, parent, id, pos, size, style, name=name) + listmix.ListCtrlAutoWidthMixin.__init__(self) +[ID_DISCOVERYDIALOG, ID_DISCOVERYDIALOGSTATICTEXT1, + ID_DISCOVERYDIALOGSERVICESLIST, ID_DISCOVERYDIALOGREFRESHBUTTON, + ID_DISCOVERYDIALOGLOCALBUTTON, +] = [wx.NewId() for _init_ctrls in range(5)] +class DiscoveryDialog(wx.Dialog, listmix.ColumnSorterMixin): + def _init_coll_MainSizer_Items(self, parent): + parent.AddWindow(self.staticText1, 0, border=20, flag=wx.TOP|wx.LEFT|wx.RIGHT|wx.GROW) + parent.AddWindow(self.ServicesList, 0, border=20, flag=wx.LEFT|wx.RIGHT|wx.GROW) + parent.AddSizer(self.ButtonGridSizer, 0, border=20, flag=wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.GROW) + def _init_coll_MainSizer_Growables(self, parent): + parent.AddGrowableCol(0) + parent.AddGrowableRow(1) + def _init_coll_ButtonGridSizer_Items(self, parent): + parent.AddWindow(self.RefreshButton, 0, border=0, flag=0) + parent.AddWindow(self.LocalButton, 0, border=0, flag=0) + parent.AddSizer(self.ButtonSizer, 0, border=0, flag=0) + def _init_coll_ButtonGridSizer_Growables(self, parent): + parent.AddGrowableCol(0) + parent.AddGrowableCol(1) + parent.AddGrowableRow(1) + def _init_sizers(self): + self.MainSizer = wx.FlexGridSizer(cols=1, hgap=0, rows=3, vgap=10) + self.ButtonGridSizer = wx.FlexGridSizer(cols=3, hgap=5, rows=1, vgap=0) + self._init_coll_MainSizer_Items(self.MainSizer) + self._init_coll_MainSizer_Growables(self.MainSizer) + self._init_coll_ButtonGridSizer_Items(self.ButtonGridSizer) + self._init_coll_ButtonGridSizer_Growables(self.ButtonGridSizer) + self.SetSizer(self.MainSizer) + def _init_ctrls(self, prnt): + wx.Dialog.__init__(self, id=ID_DISCOVERYDIALOG, + name='DiscoveryDialog', parent=prnt, + size=wx.Size(600, 600), style=wx.DEFAULT_DIALOG_STYLE, + title='Service Discovery') + self.staticText1 = wx.StaticText(id=ID_DISCOVERYDIALOGSTATICTEXT1, + label=_('Services available:'), name='staticText1', parent=self, + pos=wx.Point(0, 0), size=wx.DefaultSize, style=0) + self.ServicesList = AutoWidthListCtrl(id=ID_DISCOVERYDIALOGSERVICESLIST, + name='ServicesList', parent=self, pos=wx.Point(0, 0), size=wx.Size(0, 0), + style=wx.LC_REPORT|wx.LC_EDIT_LABELS|wx.LC_SORT_ASCENDING|wx.LC_SINGLE_SEL) + self.ServicesList.InsertColumn(0, 'NAME') + self.ServicesList.InsertColumn(1, 'TYPE') + self.ServicesList.InsertColumn(2, 'IP') + self.ServicesList.InsertColumn(3, 'PORT') + self.ServicesList.SetColumnWidth(0, 150) + self.ServicesList.SetColumnWidth(1, 150) + self.ServicesList.SetColumnWidth(2, 150) + self.ServicesList.SetColumnWidth(3, 150) + self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected, id=ID_DISCOVERYDIALOGSERVICESLIST) + self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemActivated, id=ID_DISCOVERYDIALOGSERVICESLIST) + listmix.ColumnSorterMixin.__init__(self, 4) + self.RefreshButton = wx.Button(id=ID_DISCOVERYDIALOGREFRESHBUTTON, + label=_('Refresh'), name='RefreshButton', parent=self, + pos=wx.Point(0, 0), size=wx.DefaultSize, style=0) + self.Bind(wx.EVT_BUTTON, self.OnRefreshButton, id=ID_DISCOVERYDIALOGREFRESHBUTTON) + self.LocalButton = wx.Button(id=ID_DISCOVERYDIALOGLOCALBUTTON, + label=_('Local'), name='LocalButton', parent=self, + pos=wx.Point(0, 0), size=wx.DefaultSize, style=0) + self.Bind(wx.EVT_BUTTON, self.OnLocalButton, id=ID_DISCOVERYDIALOGLOCALBUTTON) + self.ButtonSizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTER) + def __init__(self, parent): + self._init_ctrls(parent) + self.ZeroConfInstance = Zeroconf() + for browser in self.Browsers: + self.ZeroConfInstance.close() + for browser in self.Browsers: + for t in connectors.dnssd_connectors.keys(): + self.Browsers.append(ServiceBrowser(self.ZeroConfInstance, t, self)) + def OnRefreshButton(self, event): + self.ServicesList.DeleteAllItems() + def OnLocalButton(self, event): + self.EndModal(wx.ID_OK) + # Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py + return self.ServicesList + def getColumnText(self, index, col): + item = self.ServicesList.GetItem(index, col) + def OnItemSelected(self, event): + self.SetURI(event.m_itemIndex) + def OnItemActivated(self, event): + self.SetURI(event.m_itemIndex) + self.EndModal(wx.ID_OK) + connect_type = self.getColumnText(idx, 1) + connect_address = self.getColumnText(idx, 2) + connect_port = self.getColumnText(idx, 3) + self.URI = "%s://%s:%s"%(connect_type, connect_address, connect_port) + def removeService(self, zeroconf, type, name): + wx.CallAfter(self._removeService, name) + def _removeService(self, name): + called when a service with the desired type goes offline. + # loop through the list items looking for the service that went offline + for idx in xrange(self.ServicesList.GetItemCount()): + # this is the unique identifier assigned to the item + item_id = self.ServicesList.GetItemData(idx) + # this is the full typename that was received by addService + item_name = self.itemDataMap[item_id][4] + self.ServicesList.DeleteItem(idx) + def addService(self, zeroconf, type, name): + wx.CallAfter(self._addService, type, name) + def _addService(self, type, name): + called when a service with the desired type is discovered. + info = self.ZeroConfInstance.getServiceInfo(type, name) + svcname = name.split(".")[0] + typename = type.split(".")[0][1:] + ip = str(socket.inet_ntoa(info.getAddress())) + num_items = self.ServicesList.GetItemCount() + # display the new data in the list + new_item = self.ServicesList.InsertStringItem(num_items, svcname) + self.ServicesList.SetStringItem(new_item, 1, "%s" % typename) + self.ServicesList.SetStringItem(new_item, 2, "%s" % ip) + self.ServicesList.SetStringItem(new_item, 3, "%s" % port) + # record the new data for the ColumnSorterMixin + # we assign every list item a unique id (that won't change when items + # are added or removed) + self.ServicesList.SetItemData(new_item, self.nextItemId) + # the value of each column has to be stored in the itemDataMap + # so that ColumnSorterMixin knows how to sort the column. + # "name" is included at the end so that self.removeService + self.itemDataMap[self.nextItemId] = [ svcname, typename, ip, port, name ]