1  """SpecMessage module 
  2   
  3  This module defines classes and functions for creating messages 
  4  from data received from Spec, and for generating messages to be 
  5  sent to Spec. 
  6   
  7  It handles the different message versions (headers 2, 3 and 4). 
  8  """ 
  9   
 10  __author__ = 'Matias Guijarro' 
 11  __version__ = '1.0' 
 12   
 13  import struct 
 14  import time 
 15  import types 
 16   
 17  import SpecArray 
 18  import SpecReply 
 19   
 20  (DOUBLE, STRING, ERROR, ASSOC) = (1,2,3,4) 
 21   
 22  MAGIC_NUMBER=4277009102 
 23  NATIVE_HEADER_VERSION=4 
 24  NULL='\000' 
 25   
 26   
 27  (CLOSE, ABORT, CMD, CMD_WITH_RETURN, REGISTER, UNREGISTER, EVENT,  \ 
 28   FUNC, FUNC_WITH_RETURN, CHAN_READ, CHAN_SEND, REPLY, HELLO, HELLO_REPLY) = \ 
 29                                           (1,2,3,4,6,7,8,9,10,11,12,13,14,15) 
 30   
 31   
 32  DELETED = 0x0001 
 33   
 34   
 36      """Return a new SpecMessage object 
 37   
 38      The returned SpecMessage object can be of any of the available message 
 39      class. You can specify the desired message class with the 'version' keyword 
 40      argument. If not specified, defaults to NATIVE_HEADER_VERSION. 
 41   
 42      Arguments are passed to the appropriate message constructor. 
 43   
 44      Keyword arguments: 
 45      version -- message version, defaults to NATIVE_HEADER_VERSION. When 
 46      reading messages from stream, you can set it to None and it will try 
 47      to guess the suitable message class by reading the header version 
 48      from Spec. 
 49      """ 
 50      version = kwargs.get('version', NATIVE_HEADER_VERSION) 
 51      order = kwargs.get('order', '<') 
 52      if len(order) == 0: 
 53        order = "<" 
 54   
 55      if version == 4: 
 56          m = message4(*args) 
 57      elif version == 3: 
 58          m = message3(*args) 
 59      elif version == 2: 
 60          m = message2(*args) 
 61      else: 
 62          m = anymessage(*args)  
 63   
 64      m.packedHeaderDataFormat=order+m.packedHeaderDataFormat[1:] 
 65   
 66      return m 
  67   
 68   
 70      """Transform a list as coming from a SPEC associative array 
 71      to a dictonary - 2dim arrays are transformed top dict with dict 
 72      entries. In SPEC the key contains \x1c""" 
 73      raw = rawstring.split(NULL)[:-2] 
 74   
 75      data = {} 
 76      for i in range(0,len(raw) - 1,2): 
 77          key,val = raw[i], raw[i+1] 
 78          keyel = key.split("\x1c") 
 79          if len(keyel) == 1: 
 80              if key in data: 
 81                data[key][None] = val 
 82              else: 
 83                data[key]=val 
 84          else: 
 85              if keyel[0] in data and type(data[keyel[0]])!=types.DictType: 
 86                data[keyel[0]]={ None: data[keyel[0]] } 
 87   
 88              try: 
 89                  data[keyel[0]][keyel[1]] = val 
 90              except TypeError: 
 91                  data[keyel[0]] = {keyel[1] : val} 
 92              except KeyError: 
 93                  data[keyel[0]] = {keyel[1] : val} 
 94      return data 
  95   
 96   
 98      """Transform a Python dictionary object to the string format 
 99      expected by Spec""" 
100      data = "" 
101      for key, val in dict.items(): 
102          if type(val) == types.DictType: 
103              for kkey, vval in val.iteritems(): 
104                  if kkey is None: 
105                    data += str(key) + NULL + str(vval) + NULL 
106                  else: 
107                    data += ''.join([str(key), '\x1c', str(kkey), NULL, str(vval), NULL]) 
108          else: 
109              data += str(key) + NULL + str(val) + NULL 
110   
111      return (len(data) > 0 and data) or NULL 
 112   
113   
115      """Base class for messages.""" 
117          """Constructor 
118   
119          Arguments: 
120          packedHeader -- string representing the packed header format for the message, 
121          use the same syntax as the 'struct' Python module 
122          """ 
123          self.packedHeaderDataFormat = packedHeader 
124          self.headerLength = struct.calcsize(self.packedHeaderDataFormat) 
125          self.bytesToRead = self.headerLength 
126          self.readheader = True 
127          self.data = '' 
128          self.type = None 
129   
130           
131          self.magic = None 
132          self.vers = None 
133          self.size = None 
134          self.sn = None 
135          self.sec = None 
136          self.usec = None 
137          self.cmd = None 
138          self.type = None 
139          self.rows = 0 
140          self.cols = 0 
141          self.name = None 
142          self.err = 0 
143          self.flags = 0 
 144   
145   
147          """Return wether a message read from stream has been fully received or not.""" 
148          return self.bytesToRead == 0 
 149   
150   
152          """Read buffer from stream and try to create a message from it 
153   
154          Arguments: 
155          streamBuf - string buffer of the last bytes received from Spec 
156   
157          Return value : 
158          the number of consumed bytes 
159          """ 
160          consumedBytes = 0 
161   
162          while self.bytesToRead > 0 and len(streamBuf[consumedBytes:]) >= self.bytesToRead: 
163              if self.readheader: 
164                  self.readheader = False 
165                  self.type, self.bytesToRead = self.readHeader(streamBuf[:self.headerLength]) 
166                  consumedBytes = self.headerLength 
167              else: 
168                  rawdata = streamBuf[consumedBytes:consumedBytes+self.bytesToRead] 
169                  consumedBytes += self.bytesToRead 
170                  self.bytesToRead = 0 
171   
172                  self.data = self.readData(rawdata, self.type) 
173   
174          return consumedBytes 
 175   
176   
178          """Read the header of the message coming from stream 
179   
180          Arguments: 
181          rawstring -- raw bytes of the header 
182   
183          Return value: 
184          (message data type, message data len) tuple 
185          """ 
186          return (None, 0) 
 187   
188   
189 -    def readData(self, rawstring, datatype): 
 190          """Read the data part of the message coming from stream 
191   
192          Arguments: 
193          rawstring -- raw data bytes 
194          datatype -- data type 
195   
196          Return value: 
197          the data read 
198          """ 
199          data = rawstring[:-1]  
200   
201          if datatype == ERROR: 
202              return data 
203          elif datatype == STRING or datatype == DOUBLE: 
204               
205              try: 
206                  data = int(data) 
207              except: 
208                  try: 
209                      data = float(data) 
210                  except: 
211                      pass 
212   
213              return data 
214          elif datatype == ASSOC: 
215              return rawtodictonary(rawstring) 
216          elif SpecArray.isArrayType(datatype): 
217               
218               
219              return SpecArray.SpecArray(rawstring, datatype, self.rows, self.cols) 
220          else: 
221              raise TypeError 
 222   
223   
225          """Try to guess data type 
226   
227          Works for obvious cases only 
228            - it is a hard job guessing ARRAY_* types, we ignore this case (user has to provide a suitable datatype) 
229            - we cannot make a difference between ERROR type and STRING type 
230          """ 
231          if type(data) == types.StringType: 
232              return STRING 
233          elif type(data) == types.DictType: 
234              return ASSOC 
235          elif type(data) == types.IntType or type(data) == types.LongType or type(data) == types.FloatType: 
236              return STRING 
237               
238          elif isinstance(data, SpecArray.SpecArrayData): 
239              self.rows, self.cols = data.shape 
240              return data.type 
 241   
242   
244          """Return the string representing the data part of the message.""" 
245          rawstring = '' 
246   
247          if datatype in (ERROR, STRING, DOUBLE): 
248              rawstring = str(data) 
249          elif datatype == ASSOC: 
250              rawstring = dictionarytoraw(data) 
251          elif SpecArray.isArrayType(datatype): 
252              rawstring = data.tostring() 
253   
254          if len(rawstring) > 0: 
255              rawstring += NULL 
256   
257          return rawstring 
 258   
259   
261          """Create a string representing the message which can be send 
262          over the socket.""" 
263          return '' 
  264   
265   
267      """Version 2 message class""" 
269          """Constructor 
270   
271          If called without arguments, message is supposed to be read from stream. 
272          Otherwise, the 'init' method is called with the specified arguments, for 
273          creating a message from arguments. 
274          """ 
275          SpecMessage.__init__(self, '<IiiiIIiiIII80s') 
276   
277          if len(args) > 0: 
278              self.init(*args, **kwargs) 
 279   
280   
281 -    def init(self, ser, cmd, name, data, datatype = None, rows = 0, cols = 0): 
 282          """ Create a message from the arguments""" 
283          self.vers = 2  
284          self.size = self.headerLength 
285          self.magic = MAGIC_NUMBER 
286          self.rows = rows 
287          self.cols = cols 
288          self.data = data 
289          self.type = datatype or self.dataType(self.data) 
290          self.time = time.time() 
291          self.sec = int(self.time) 
292          self.usec = int((self.time-self.sec)*1E6) 
293          self.sn, self.cmd, self.name = ser, cmd, str(name) 
 294   
295   
297          self.magic, self.vers, self.size, self.sn, \ 
298                      self.sec, self.usec, self.cmd, \ 
299                      datatype, self.rows, self.cols, \ 
300                      datalen, name  = struct.unpack(self.packedHeaderDataFormat, rawstring) 
301          if self.magic != MAGIC_NUMBER: 
302              self.packedHeaderDataFormat=">"+self.packedHeaderDataFormat[1:] 
303              self.magic, self.vers, self.size, self.sn, \ 
304                      self.sec, self.usec, self.cmd, \ 
305                      datatype, self.rows, self.cols, \ 
306                      datalen, name  = struct.unpack(self.packedHeaderDataFormat, rawstring) 
307           
308          self.time = self.sec + float(self.usec) / 1E6 
309          self.name = name.replace(NULL, '')  
310   
311          return (datatype, datalen) 
 312   
313   
315          if self.type is None: 
316               
317              return '' 
318   
319          data = self.sendingDataString(self.data, self.type) 
320          datalen = len(data) 
321   
322          header = struct.pack(self.packedHeaderDataFormat, self.magic, self.vers, self.size, 
323                               self.sn, self.sec, self.usec, self.cmd, self.type, 
324                               self.rows, self.cols, datalen, str(self.name)) 
325           
326           
327          return header + data 
  328   
329   
336   
337   
338 -    def init(self, ser, cmd, name, data, datatype = None, rows = 0, cols = 0): 
 339          """ Create a message from the arguments """ 
340          self.vers = 3  
341          self.size = self.headerLength 
342          self.magic = MAGIC_NUMBER 
343          self.rows = rows 
344          self.cols = cols 
345          self.data = data 
346          self.type = datatype or self.dataType(self.data) 
347          self.time = time.time() 
348          self.sec = int(self.time) 
349          self.usec = int((self.time-self.sec)*1E6) 
350          self.sn, self.cmd, self.name = ser, cmd, str(name) 
 351   
352   
354          self.magic, self.vers, self.size, self.sn, \ 
355                      self.sec, self.usec, self.cmd, \ 
356                      datatype, self.rows, self.cols, \ 
357                      datalen, self.err, name  = struct.unpack(self.packedHeaderDataFormat, rawstring) 
358          if self.magic != MAGIC_NUMBER: 
359              self.packedHeaderDataFormat=">"+self.packedHeaderDataFormat[1:] 
360              self.magic, self.vers, self.size, self.sn, \ 
361                      self.sec, self.usec, self.cmd, \ 
362                      datatype, self.rows, self.cols, \ 
363                      datalen, self.err, name  = struct.unpack(self.packedHeaderDataFormat, rawstring) 
364           
365          self.time = self.sec + float(self.usec) / 1E6 
366          self.name = name.replace(NULL, '')  
367   
368          if self.err > 0: 
369              datatype = ERROR  
370   
371          return (datatype, datalen) 
 372   
373   
375          if self.type is None: 
376               
377              return '' 
378   
379          data = self.sendingDataString(self.data, self.type) 
380          datalen = len(data) 
381   
382          header = struct.pack(self.packedHeaderDataFormat, self.magic, self.vers, self.size, 
383                               self.sn, self.sec, self.usec, self.cmd, self.type, 
384                               self.rows, self.cols, datalen, self.err, str(self.name)) 
385   
386           
387           
388          return header + data 
  389   
390   
397   
398   
399 -    def init(self, ser, cmd, name, data, datatype = None, rows = 0, cols = 0): 
 400          """ Create a message from the arguments """ 
401          self.vers = 4  
402          self.size = self.headerLength 
403          self.magic = MAGIC_NUMBER 
404          self.rows = rows 
405          self.cols = cols 
406          self.data = data 
407          self.type = datatype or self.dataType(self.data) 
408          self.time = time.time() 
409          self.sec = int(self.time) 
410          self.usec = int((self.time-self.sec)*1E6) 
411          self.sn, self.cmd, self.name = ser, cmd, str(name) 
 412   
413   
415          self.magic, self.vers, self.size, self.sn, \ 
416                      self.sec, self.usec, self.cmd, \ 
417                      datatype, self.rows, self.cols, \ 
418                      datalen, self.err, self.flags, name  = struct.unpack(self.packedHeaderDataFormat, rawstring) 
419          if self.magic != MAGIC_NUMBER: 
420              self.packedHeaderDataFormat=">"+self.packedHeaderDataFormat[1:] 
421              self.magic, self.vers, self.size, self.sn, \ 
422                      self.sec, self.usec, self.cmd, \ 
423                      datatype, self.rows, self.cols, \ 
424                      datalen, self.err, self.flags, name  = struct.unpack(self.packedHeaderDataFormat, rawstring) 
425           
426          self.time = self.sec + float(self.usec) / 1E6 
427          self.name = name.replace(NULL, '')  
428   
429          if self.err > 0: 
430              datatype = ERROR  
431   
432          return (datatype, datalen) 
 433   
434   
436          if self.type is None: 
437               
438              return '' 
439   
440          data = self.sendingDataString(self.data, self.type) 
441          datalen = len(data) 
442   
443           
444           
445          header = struct.pack(self.packedHeaderDataFormat, self.magic, self.vers, self.size, 
446                               self.sn, self.sec, self.usec, self.cmd, self.type, 
447                               self.rows, self.cols, datalen, self.err, self.flags, str(self.name)) 
448   
449          return header + data 
  450   
451   
455   
456   
458          if len(streamBuf) >= self.bytesToRead: 
459              magic, version = struct.unpack(self.packedHeaderDataFormat, streamBuf[:self.headerLength]) 
460   
461              if magic != MAGIC_NUMBER: 
462                  self.packedHeaderDataFormat=">"+self.packedHeaderDataFormat[1:] 
463                  magic, version = struct.unpack(self.packedHeaderDataFormat, streamBuf[:self.headerLength]) 
464   
465               
466              if version == 2: 
467                  self.__class__ = message2 
468                  message2.__init__(self) 
469                  return self.readFromStream(streamBuf) 
470              elif version == 3: 
471                  self.__class__ = message3 
472                  message3.__init__(self) 
473                  return self.readFromStream(streamBuf) 
474              elif version >= 4: 
475                  self.__class__ = message4 
476                  message4.__init__(self) 
477                  return self.readFromStream(streamBuf) 
478   
479          return 0 
  480   
481   
483      """Convert a command list to a Spec command string.""" 
484      if type(cmdlist) == types.ListType and len(cmdlist) > 0: 
485          cmd = [str(cmdlist[0])] 
486   
487          for arg in cmdlist[1:]: 
488              argstr = repr(arg) 
489   
490              if type(arg) == types.DictType: 
491                  argstr = argstr.replace('{', '[') 
492                  argstr = argstr.replace('}', ']') 
493   
494              cmd.append(argstr) 
495   
496          return NULL.join(cmd) 
497      else: 
498          return '' 
 499   
500   
504   
505   
510   
511   
513      """Return a command without reply message""" 
514      return message_no_reply(CMD, "", cmd, version, order) 
 515   
516   
521   
522   
526   
527   
531   
532   
536   
537   
541   
542   
546   
547   
551   
552   
556   
557   
561   
562   
564      return message(replyID, HELLO_REPLY, serverName, serverName, version = version, order=order) 
 565   
566   
567   
569      """ Lower level call to send a message of a certain type """ 
570      newReply = SpecReply.SpecReply() 
571      replyID = newReply.id 
572   
573      m = message(replyID, cmd, name, data, version = version, order=order) 
574   
575      return (newReply, m) 
 576   
577   
579      """ Send a message which will not result in a reply from the server. 
580      If a reply is sent depends only on the cmd and not on the method 
581      to send the message """ 
582      return message(0, cmd, name, data, version = version, order=order) 
 583   
584   
586      return message(replyID, REPLY, name, data, version = version, order=order) 
 587   
588   
590      return message(replyID, REPLY, name, data, ERROR, version = version, order=order) 
 591