devicemanager.py
author Joel Maher <joel.maher@gmail.com>
Thu, 29 Oct 2009 21:08:58 -0400
changeset 2 9747172ad123e38cc968138061e899a5eef8ea14
parent 0 7911486efdc7a92b0f315e92147f753877fe331c
child 3 6d3607d465e4d959e451d483842d5bcc9b3bb8f4
permissions -rwxr-xr-x
extra util functions, more robust devicemanager, getbuild.py utility

# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.   
#
# The Original Code is Test Automation Framework.
#
# The Initial Developer of the Original Code is Joel Maher.
#
# Portions created by the Initial Developer are Copyright (C) 2___
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Joel Maher <joel.maher@gmail.com> (Original Developer)
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****

import socket
import time, datetime
import os
import re
import hashlib
from threading import Thread


class myProc(Thread):
  def __init__(self, hostip, hostport, cmd, new_line = True, sleeptime = 0):
    self.cmdline = cmd
    self.newline = new_line
    self.sleep = sleeptime
    self.host = hostip
    self.port = hostport
    Thread.__init__(self)

  def run(self):
    promptre =re.compile('.*\$\>.$')
    data = ""
    try:
      s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    except:
      return None
      
    try:
      s.connect((self.host, self.port))
    except:
      s.close()
      return None
      
    try:
      s.recv(1024)
    except:
      s.close()
      return None
      
    for cmd in self.cmdline:
      if (cmd == 'quit'): break
      if self.newline: cmd += '\r\n'
      try:
        s.send(cmd)
      except:
        s.close()
        return None
        
      time.sleep(int(self.sleep))

      found = False
      while (found == False):
        try:
          temp = s.recv(1024)
        except:
          s.close()
          return None
          
        lines = temp.split('\n')
        for line in lines:
          if (promptre.match(line)):
            found = True
          data += temp

    try:
      s.send('quit\r\n')
    except:
      s.close()
      return None
    try:
      s.close()
    except:
      return None
    return data

class DeviceManager:
  host = ''
  port = 0
  debug = 2
  _redo = False


  def __init__(self, host, port = 27020):
    self.host = host
    self.port = port
    self._sock = None

  def sendCMD(self, cmdline, newline = True, sleep = 0):
    promptre = re.compile('.*\$\>.$')

    #TODO: any commands that don't output anything and quit need to match this RE
    pushre = re.compile('^push .*$')
    data = ""
    noQuit = False
    
    if (self._sock == None):
      try:
        print "reconnecting socket"
        self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      except:
        self._redo = True
        self._sock = None
        return None
      
      try:
        self._sock.connect((self.host, self.port))
        self._sock.recv(1024)
      except:
        self._redo = True
        self._sock.close()
        self._sock = None
        return None
      
    for cmd in cmdline:
      if (cmd == 'quit'): break
      if newline: cmd += '\r\n'
      
      try:
        self._sock.send(cmd)
      except:
        self._redo = True
        self._sock.close()
        self._sock = None
        return None
      
      time.sleep(int(sleep))
      if (pushre.match(cmd)):
        noQuit = True
      elif noQuit == False:
        found = False
        while (found == False):
          if (self.debug >= 3): print "recv'ing..."
          
          try:
            temp = self._sock.recv(1024)
          except:
            self._redo = True
            self._sock.close()
            self._sock = None
            return None
          lines = temp.split('\n')
          for line in lines:
            if (promptre.match(line)):
              found = True
          data += temp

#    if (noQuit == False):
#      try:
#        self._sock.send('quit\r\n')
#      except:
#        self._redo = True
#        self._sock.close()
#        self._sock = None
#        return None

    if (noQuit == True):
      try:
        self._sock.close()
        self._sock = None
      except:
        self._redo = True
        self._sock = None
        return None

    return data
  
  
  #take a data blob and strip instances of the prompt '$>\x00'
  def stripPrompt(self, data):
    promptre = re.compile('.*\$\>.*')
    retVal = []
    lines = data.split('\n')
    for line in lines:
      try:
        while (promptre.match(line)):
          pieces = line.split('\x00')
          index = pieces.index("$>")
          pieces.pop(index)
          line = '\x00'.join(pieces)
      except(ValueError):
        pass
      retVal.append(line)

    return '\n'.join(retVal)
  

  def pushFile(self, localname, destname):
    if (self.validateFile(destname, localname) == True):
      return ''

    self.mkDirs(destname)
    if (self.debug >= 2): print "sending: push " + destname
    f = open(localname, 'rb')
    data = f.read()
    f.close()
    retVal = self.sendCMD(['push ' + destname + '\r\n', data], newline = False)
    if (retVal == None):
      return None

    if (self.validateFile(destname, localname) == False):
      if (self.debug >= 2): print "file did not copy as expected"
      return None

    return retVal
  
  def mkDir(self, name):
    return self.sendCMD(['mkdr ' + name, 'quit'])
  
  
  #make directory structure on the device
  def mkDirs(self, filename):
    parts = filename.split('\\')
    name = ""
    for part in parts:
      if (part == parts[-1]): break
      if (part != ""):
        name += '\\' + part
        if (self.mkDir(name) == None):
          return None


  #push localDir from host to remoteDir on the device
  def pushDir(self, localDir, remoteDir):
    if (self.debug >= 2): print "pushing directory: " + localDir + " to " + remoteDir
    for root, dirs, files in os.walk(localDir):
      parts = root.split(localDir)
      for file in files:
        remoteRoot = remoteDir + '\\' + parts[1]
        remoteRoot = remoteRoot.replace('/', '\\')
        remoteRoot = remoteRoot.replace('\\\\', '\\')
        if (parts[1] == ""): remoteRoot = remoteDir
        if (self.pushFile(os.path.join(root, file), remoteRoot + '\\' + file) == None):
          if (self.pushFile(os.path.join(root, file), remoteRoot + '\\' + file) == None):
            return None
    return True


  def dirExists(self, dirname):
    match = ".*" + dirname.replace('\\', '\\\\') + "$"
    dirre = re.compile(match)
    data = self.sendCMD(['cd ' + dirname, 'cwd', 'quit'], sleep = 1)
    if (data == None):
      return None
    retVal = self.stripPrompt(data)
    data = retVal.split('\n')
    found = False
    for d in data:
      if (dirre.match(d)): 
        found = True

    return found


  #list files on the device, requires cd to directory first
  def listFiles(self, rootdir):
    if (self.dirExists(rootdir) == False): return []  
    data = self.sendCMD(['cd ' + rootdir, 'ls', 'quit'], sleep=1)
    if (data == None):
      return None
    retVal = self.stripPrompt(data)
    return retVal.split('\n')


  def removeFile(self, filename):
    if (self.debug>= 2): print "removing file: " + filename
    return self.sendCMD(['rm ' + filename, 'quit'])
  
  
  #does a recursive delete of directory on the device: rm -Rf remoteDir
  def removeDir(self, remoteDir):
    filelist = self.listFiles(remoteDir)
    if (filelist == None):
      return None
  
    #TODO: logic is way too simple and basic, make more robust
    isFile = re.compile('^([a-zA-Z0-9_\-\. ]+)\.([a-zA-Z0-9]+)$')
    for f in filelist:
      if (isFile.match(f)):
        if (self.removeFile(remoteDir + "\\" + f) == None):
          return None
      else:
        if (self.removeDir(remoteDir + "\\" + f) == None):
          return None

    if (self.debug >= 3): print "removing: " + remoteDir
    return self.sendCMD(['rmdr ' + remoteDir, 'rm ' + remoteDir, 'quit'], sleep = 1)


  def getProcessList(self):
    data = self.sendCMD(['ps', 'quit'], sleep = 3)
    if (data == None):
      return None
      
    retVal = self.stripPrompt(data)
    lines = retVal.split('\n')
    files = []
    for line in lines:
      if (line.strip() != ''):
        pidproc = line.strip().split(' ')
        if (len(pidproc) == 2):
          files += [[pidproc[0], pidproc[1]]]
      
    return files


  def getMemInfo(self):
    data = self.sendCMD(['mems', 'quit'])
    if (data == None):
      return None
    retVal = self.stripPrompt(data)
    #TODO: this is hardcoded for now
    fhandle = open("memlog.txt", 'a')
    fhandle.write("\n")
    fhandle.write(retVal)
    fhandle.close()

  def fireProcess(self, appname):
    if (self.debug >= 2): print "FIRE PROC: '" + appname + "'"
    self.process = myProc(self.host, self.port, ['exec ' + appname, 'quit'])
    self.process.start()  

  #iterates process list and returns pid if exists, otherwise ''
  def processExist(self, appname):
    pid = ''
  
    pieces = appname.split('\\')
    app = pieces[-1]
    procre = re.compile('.*' + app + '.*')
  
    procList = self.getProcessList()
    if (procList == None):
      return None
      
    for proc in procList:
      if (procre.match(proc[1])):
        pid = proc[0]
        break
    return pid


  def killProcess(self, appname):
    pid = "0xFEEDFACE"
    
    while (pid != ''):
      pid = self.processExist(appname)
      if (pid == None):
        return None
      
      if (pid != ''):
        if (self.debug >= 2): print "found pid, now kill: " + pid
        if (self.sendCMD(['kill ' + pid, 'quit']) == None):
          return None

    return True

  def getTempDir(self):
    promptre = re.compile('.*\$\>\x00.*')
    retVal = ''
    data = self.sendCMD(['tmpd', 'quit'])
    if (data == None):
      return None
    return self.stripPrompt(data)

  
  #copy file from device (remoteFile) to host (localFile)
  def getFile(self, remoteFile, localFile):
    promptre = re.compile('.*\$\>\x00.*')
    data = self.sendCMD(['cat ' + remoteFile, 'quit'], sleep = 5)
    if (data == None):
      return None
    retVal = self.stripPrompt(data)
    fhandle = open(localFile, 'wb')
    fhandle.write(retVal)
    fhandle.close()
    return retVal
  
  
  #copy directory structure from device (remoteDir) to host (localDir)
  def getDirectory(self, remoteDir, localDir):
    if (self.debug >= 2): print "getting files in '" + remoteDir + "'"
    filelist = self.listFiles(remoteDir)
    if (filelist == None):
      return None
    if (self.debug >= 3): print filelist
    if not os.path.exists(localDir):
      os.makedirs(localDir)
  
    #TODO: is this a comprehensive file regex?
    isFile = re.compile('^([a-zA-Z0-9_\-\. ]+)\.([a-zA-Z0-9]+)$')
    for f in filelist:
      if (isFile.match(f)):
        if (self.getFile(remoteDir + "\\" + f, os.path.join(localDir, f)) == None):
          return None
      else:
        if (self.getDirectory(remoteDir + "\\" + f, os.path.join(localDir, f)) == None):
          return None


  #true/false check if the two files have the same md5 sum
  def validateFile(self, remoteFile, localFile):
    remoteHash = self.getRemoteHash(remoteFile)
    localHash = self.getLocalHash(localFile)

    if (remoteHash == localHash):
        return True

    return False

  
  #return the md5 sum of a remote file
  def getRemoteHash(self, filename):
      filename = filename.replace("/", "\\")
      filename = filename.replace("\\\\", "\\")
      data = self.sendCMD(['hash ' + filename, 'quit'], sleep = 1)
      if (data == None):
          return ''
      retVal = self.stripPrompt(data)
      if (retVal != None):
        retVal = retVal.strip('\n')
      if (self.debug >= 3): 
        print "remote hash: '" + retVal + "'"
      return retVal
    

  #return the md5 sum of a file on the host
  def getLocalHash(self, filename):
      file = open(filename, 'rb')
      if (file == None):
          return None
      
      mdsum = hashlib.md5()

      while 1:
          data = file.read(1024)
          if not data:
              break
          mdsum.update(data)

      file.close()
      hexval = mdsum.hexdigest()
      if (self.debug >= 3):
        print "local hash: '" + hexval + "'"
      return hexval