"Bug 614173 Update pull changes, fix update command in devicemanager r=jmaher a=NPOTB"
authorMark Cote <mcote@mozilla.com>
Thu, 16 Dec 2010 15:28:35 -0800
changeset 59425 c6f86b5978e785136955204f24be6b8ed610f47c
parent 59424 18004ceae9ce0573eaaf6df1c2cc30a61fcd2401
child 59426 2020888cd34f6378fea834425c9524a9d7a47357
push id1
push usershaver@mozilla.com
push dateTue, 04 Jan 2011 17:58:04 +0000
reviewersjmaher, NPOTB
bugs614173
milestone2.0b9pre
"Bug 614173 Update pull changes, fix update command in devicemanager r=jmaher a=NPOTB"
build/mobile/devicemanager.py
--- a/build/mobile/devicemanager.py
+++ b/build/mobile/devicemanager.py
@@ -492,79 +492,106 @@ class DeviceManager:
 
   def catFile(self, remoteFile):
     data = self.sendCMD(['cat ' + remoteFile])
     if data == None:
         return None
     return self.stripPrompt(data)
   
   def pullFile(self, remoteFile):
+    """Returns contents of remoteFile using the "pull" command.
+    The "pull" command is different from other commands in that DeviceManager
+    has to read a certain number of bytes instead of just reading to the
+    next prompt.  This is more robust than the "cat" command, which will be
+    confused if the prompt string exists within the file being catted.
+    However it means we can't use the response-handling logic in sendCMD().
+    """
+    
     def err(error_msg):
-        err_str = 'bad response to pull: %s!' % error_msg
+        err_str = 'error returned from pull: %s' % error_msg
         print err_str
         self._sock = None
         raise FileError(err_str) 
 
-    def read(to_recv, error_msg):
+    # FIXME: We could possibly move these socket-reading functions up to
+    # the class level if we wanted to refactor sendCMD().  For now they are
+    # only used to pull files.
+    
+    def uread(to_recv, error_msg):
+      """ unbuffered read """
       data = self._sock.recv(to_recv)
       if not data:
         err(error_msg)
         return None
       return data
 
-    self.sendCMD(['pull ' + remoteFile])
+    def read_until_char(c, buffer, error_msg):
+      """ read until 'c' is found; buffer rest """
+      while not '\n' in buffer:
+        data = uread(1024, error_msg)
+        if data == None:
+          err(error_msg)
+          return ('', '', '')
+        buffer += data
+      return buffer.partition(c)
+
+    def read_exact(total_to_recv, buffer, error_msg):
+      """ read exact number of 'total_to_recv' bytes """
+      while len(buffer) < total_to_recv:
+        to_recv = min(total_to_recv - len(buffer), 1024)
+        data = uread(to_recv, error_msg)
+        if data == None:
+          return None
+        buffer += data
+      return buffer
+
+    prompt = self.base_prompt + self.prompt_sep
     buffer = ''
-    while not '\n' in buffer:
-      data = read(1024, 'could not find metadata')
-      if data == None:
-        return
-      buffer += data
-    nl = buffer.find('\n')
-    metadata = buffer[:nl]
-    print 'metadata: %s' % metadata
-    filedata = buffer[nl+1:]  # skip newline
-    sep = metadata.rfind(',')
-    if sep == -1:
-      err('could not find file size')
+    
+    # expected return value:
+    # <filename>,<filesize>\n<filedata>
+    # or, if error,
+    # <filename>,-1\n<error message>
+    self.sendCMD(['pull ' + remoteFile])
+    # read metadata; buffer the rest
+    metadata, sep, buffer = read_until_char('\n', buffer, 'could not find metadata')
+    if not metadata:
       return None
-    filename = metadata[:sep]
-    filesizestr = metadata[sep+1:]
-    prompt = self.base_prompt + self.prompt_sep
+    if self.debug >= 3:
+      print 'metadata: %s' % metadata
+
+    filename, sep, filesizestr = metadata.partition(',')
+    if sep == '':
+      err('could not find file size in returned metadata')
+      return None
     try:
         filesize = int(filesizestr)
     except ValueError:
-      err('invalid file size')
-      return None
-    if filesize == -1:
-      while not '\n' in filedata:
-        data = read(1024, 'could not find metadata')
-        if data == None:
-          return None
-        filedata += data
-      nl = filedata.find('\n')
-      error_str = filedata[:nl]
-      filedata = filedata[nl+1:]
-      while filedata < len(prompt):
-        data = read(1024, 'could not find metadata')
-        if data == None:
-          return None
-      print 'error pulling file: %s' % error_str
+      err('invalid file size in returned metadata')
       return None
 
-    total_to_recv = filesize + len(prompt)
-    while len(filedata) < total_to_recv:
-      to_recv = min(total_to_recv - len(filedata), 1024)
-      data = read(to_recv, 'could not get all file data')
-      if data == None:
+    if filesize == -1:
+      # read error message
+      error_str, sep, buffer = read_until_char('\n', buffer, 'could not find error message')
+      if not error_str:
         return None
-      filedata += data
-    if filedata[-len(prompt):] != prompt:
-      err('no prompt')
-      return filedata
-    return filedata[:-len(prompt)]
+      # prompt should follow
+      read_exact(len(prompt), buffer, 'could not find prompt')
+      print 'DeviceManager: error pulling file: %s' % error_str
+      return None
+
+    # read file data
+    total_to_recv = filesize + len(prompt)
+    buffer = read_exact(total_to_recv, buffer, 'could not get all file data')
+    if buffer == None:
+      return None
+    if buffer[-len(prompt):] != prompt:
+      err('no prompt found after file data--DeviceManager may be out of sync with agent')
+      return buffer
+    return buffer[:-len(prompt)]
 
   # copy file from device (remoteFile) to host (localFile)
   def getFile(self, remoteFile, localFile = ''):
     if localFile == '':
       localFile = os.path.join(self.tempRoot, "temp.txt")
   
     retVal = self.pullFile(remoteFile)
     if retVal == None:
@@ -581,90 +608,89 @@ class DeviceManager:
   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)
-  
+   
     for f in filelist:
       if f == '.' or f == '..':
         continue
       remotePath = remoteDir + '/' + f
       localPath = os.path.join(localDir, f)
-      print 'remotePath is %s' % remotePath
-      print 'localPath is %s' % localPath
       try:
         is_dir = self.isDir(remotePath)
       except FileError:
-        print 'bad file "%s"!' % remotePath
+        print 'isdir failed on file "%s"; continuing anyway...' % remotePath
         continue
       if is_dir:
         if (self.getDirectory(remotePath, localPath) == None):
-          print 'aborted when getting directory'
+          print 'failed to get directory "%s"' % remotePath
           return None
       else:
         # It's sometimes acceptable to have getFile() return None, such as
         # when the agent encounters broken symlinks.
         # FIXME: This should be improved so we know when a file transfer really
         # failed.
-        self.getFile(remotePath, localPath)
+        if self.getFile(remotePath, localPath) == None:
+          print 'failed to get file "%s"; continuing anyway...' % remotePath 
     return filelist
 
   def isDir(self, remotePath):
     data = self.sendCMD(['isdir ' + remotePath])
     retVal = self.stripPrompt(data).strip()
     if not retVal:
       raise FileError('isdir returned null')
     return retVal == 'TRUE'
 
   # 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 True
 
     return False
   
   # return the md5 sum of a remote file
   def getRemoteHash(self, filename):
-      data = self.sendCMD(['hash ' + filename])
-      if (data == None):
-          return ''
-      retVal = self.stripPrompt(data)
-      if (retVal != None):
-        retVal = retVal.strip('\n')
-      if (self.debug >= 3): print "remote hash returned: '" + retVal + "'"
-      return retVal
+    data = self.sendCMD(['hash ' + filename])
+    if (data == None):
+        return ''
+    retVal = self.stripPrompt(data)
+    if (retVal != None):
+      retVal = retVal.strip('\n')
+    if (self.debug >= 3): print "remote hash returned: '" + 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
+    file = open(filename, 'rb')
+    if (file == None):
+      return None
 
-      try:
-        mdsum = hashlib.md5()
-      except:
-        return None
+    try:
+      mdsum = hashlib.md5()
+    except:
+      return None
 
-      while 1:
-          data = file.read(1024)
-          if not data:
-              break
-          mdsum.update(data)
+    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 returned: '" + hexval + "'"
-      return hexval
+    file.close()
+    hexval = mdsum.hexdigest()
+    if (self.debug >= 3): print "local hash returned: '" + hexval + "'"
+    return hexval
 
   # Gets the device root for the testing area on the device
   # For all devices we will use / type slashes and depend on the device-agent
   # to sort those out.  The agent will return us the device location where we
   # should store things, we will then create our /tests structure relative to
   # that returned path.
   # Structure on the device is as follows:
   # /tests
@@ -872,28 +898,30 @@ class DeviceManager:
       # Then we pass '' for processName
       cmd += "'' " + appBundlePath
     else:
       cmd += processName + ' ' + appBundlePath
 
     if (destPath):
       cmd += " " + destPath
 
+    if (self.debug > 3): print "updateApp using command: " + str(cmd)
+
     if (ipAddr is not None):
       ip, port = self.getCallbackIpAndPort(ipAddr, port)
 
       cmd += " %s %s" % (ip, port)
 
-      if (self.debug > 3): print "updateApp using command: " + str(cmd)
-
       # Set up our callback server
       callbacksvr = callbackServer(ip, port, self.debug)
       data = self.sendCMD([cmd])
       status = callbacksvr.disconnect()
       if (self.debug > 3): print "got status back: " + str(status)
+    else:
+      status = self.sendCMD([cmd])
 
     return status
 
   """
     return the current time on the device
   """
   def getCurrentTime(self):
     data = self.sendCMD(['clok'])