downloadkit/downloadkit.py
changeset 274 3b8bce67b574
parent 199 a99c74c53f62
child 276 c1b745b16f58
equal deleted inserted replaced
273:4a2d7d29da0f 274:3b8bce67b574
    24 from BeautifulSoup import BeautifulSoup
    24 from BeautifulSoup import BeautifulSoup
    25 from optparse import OptionParser
    25 from optparse import OptionParser
    26 import hashlib
    26 import hashlib
    27 import xml.etree.ElementTree as ET 
    27 import xml.etree.ElementTree as ET 
    28 
    28 
    29 version = '0.16'
    29 version = '0.17'
    30 user_agent = 'downloadkit.py script v' + version
    30 user_agent = 'downloadkit.py script v' + version
    31 headers = { 'User-Agent' : user_agent }
    31 headers = { 'User-Agent' : user_agent }
    32 top_level_url = "https://developer.symbian.org"
    32 top_level_url = "https://developer.symbian.org"
    33 passman = urllib2.HTTPPasswordMgrWithDefaultRealm()	# not relevant for live Symbian website
    33 passman = urllib2.HTTPPasswordMgrWithDefaultRealm()	# not relevant for live Symbian website
    34 download_list = []
    34 download_list = []
       
    35 failure_list = []
    35 unzip_list = []
    36 unzip_list = []
    36 
    37 
    37 def build_opener(debug=False):
    38 def build_opener(debug=False):
    38 	# Create a HTTP and HTTPS handler with the appropriate debug
    39 	# Create a HTTP and HTTPS handler with the appropriate debug
    39 	# level.  We intentionally create a new one because the
    40 	# level.  We intentionally create a new one because the
   251 		md5.update(data)
   252 		md5.update(data)
   252 	file.close()
   253 	file.close()
   253 	return md5.hexdigest().upper()
   254 	return md5.hexdigest().upper()
   254 
   255 
   255 checksums = {}
   256 checksums = {}
       
   257 filesizes = {}
   256 def parse_release_metadata(filename):
   258 def parse_release_metadata(filename):
   257 	if os.path.exists(filename):
   259 	if os.path.exists(filename):
   258 		tree = ET.parse(filename)
   260 		tree = ET.parse(filename)
   259 		iter = tree.getiterator('package')
   261 		iter = tree.getiterator('package')
   260 		for element in iter:
   262 		for element in iter:
   261 			if element.keys():
   263 			if element.keys():
   262 				file = element.get("name")
   264 				file = element.get("name")
   263 				md5 = element.get("md5checksum")
   265 				md5 = element.get("md5checksum")
   264 				checksums[file] = md5.upper()
   266 				checksums[file] = md5.upper()
       
   267 				size = element.get("size")
       
   268 				filesizes[file] = int(size)
   265 
   269 
   266 def download_file(filename,url):
   270 def download_file(filename,url):
   267 	global options
   271 	global options
   268 	global checksums
   272 	global checksums
       
   273 	global filesizes
       
   274 	resume_start = 0
   269 	if os.path.exists(filename):
   275 	if os.path.exists(filename):
   270 		if filename in checksums:
   276 		if filename in checksums:
   271 			print 'Checking existing ' + filename
   277 			print 'Checking existing ' + filename
   272 			file_checksum = md5_checksum(filename)
   278 			file_size = os.stat(filename).st_size
   273 			if file_checksum == checksums[filename]:
   279 			if file_size == filesizes[filename]:
       
   280 				file_checksum = md5_checksum(filename)
       
   281 				if file_checksum == checksums[filename]:
       
   282 					if options.progress:
       
   283 						print '- OK ' + filename
       
   284 					return True
       
   285 			elif file_size < filesizes[filename]:
   274 				if options.progress:
   286 				if options.progress:
   275 					print '- OK ' + filename
   287 					print '- %s is too short' % (filename)
   276 				return True
   288 				if options.debug:
       
   289 					print '- %s is %d bytes, should be %d bytes' % (filename, file_size, filesizes[filename])
       
   290 				if options.resume:
       
   291 					resume_start = file_size
   277 
   292 
   278 	if options.dryrun and not re.match(r"release_metadata", filename):
   293 	if options.dryrun and not re.match(r"release_metadata", filename):
   279 		global download_list
   294 		global download_list
   280 		download_info = "download %s %s" % (filename, url)
   295 		download_info = "download %s %s" % (filename, url)
   281 		download_list.append(download_info)
   296 		download_list.append(download_info)
   282 		return True
   297 		return True
   283 
   298 
   284 	print 'Downloading ' + filename
   299 	print 'Downloading ' + filename
   285 	global headers
   300 	global headers
   286 	req = urllib2.Request(url, None, headers)
   301 	request_headers = headers.copy()		# want a fresh copy for each request
       
   302 	if resume_start > 0:
       
   303 		request_headers['Range'] = "bytes=%d-%d" % (resume_start, filesizes[filename])
       
   304 	req = urllib2.Request(url, None, request_headers)
   287 	
   305 	
   288 	CHUNK = 128 * 1024
   306 	CHUNK = 128 * 1024
   289 	size = 0
   307 	size = 0
   290 	filesize = -1
   308 	filesize = -1
   291 	start_time = time.time()
   309 	start_time = time.time()
   295 		response = urllib2.urlopen(req)
   313 		response = urllib2.urlopen(req)
   296 		chunk = response.read(CHUNK)
   314 		chunk = response.read(CHUNK)
   297 		if chunk.find('<div id="sign_in_box">') != -1:
   315 		if chunk.find('<div id="sign_in_box">') != -1:
   298 			# our urllib2 cookies have gone awol - login again
   316 			# our urllib2 cookies have gone awol - login again
   299 			login(False)
   317 			login(False)
   300 			req = urllib2.Request(url, None, headers)
   318 			req = urllib2.Request(url, None, request_headers)
   301 			response = urllib2.urlopen(req)
   319 			response = urllib2.urlopen(req)
   302 			chunk = response.read(CHUNK)
   320 			chunk = response.read(CHUNK)
   303 			if chunk.find('<div id="sign_in_box">') != -1:
   321 			if chunk.find('<div id="sign_in_box">') != -1:
   304 				# still broken - give up on this one
   322 				# still broken - give up on this one
   305 				print "*** ERROR trying to download %s" % (filename)
   323 				print "*** ERROR trying to download %s" % (filename)
   306 				return False
   324 				return False
   307 		info = response.info()
   325 		info = response.info()
   308 		if 'Content-Length' in info:
   326 		if 'Content-Length' in info:
   309 			filesize = int(info['Content-Length'])
   327 			filesize = resume_start + int(info['Content-Length'])		# NB. length of the requested content, taking into account the range
       
   328 			if resume_start > 0 and 'Content-Range' not in info:
       
   329 				# server doesn't believe in our range
       
   330 				filesize = int(info['Content-Length'])
       
   331 				if options.debug:
       
   332 					print "Server reports filesize as %d, ignoring our range request (%d-%d)" % (filesize, resume_start, filesizes[filename])
       
   333 				resume_start = 0;	# will have to download from scratch
       
   334 			if filename in filesizes:
       
   335 				if filesize != filesizes[filename]:
       
   336 					print "WARNING:  %s size %d does not match release_metadata.xml (%d)" % ( filename, filesize, filesizes[filename])
   310 		else:
   337 		else:
   311 			match = re.search('>([^>]+Licen[^<]+)<', chunk, re.IGNORECASE)
   338 			match = re.search('>([^>]+Licen[^<]+)<', chunk, re.IGNORECASE)
   312 			if match:
   339 			if match:
   313 				license = match.group(1).replace('&amp;','&')
   340 				license = match.group(1).replace('&amp;','&')
   314 				print "*** %s is subject to the %s which you have not yet accepted\n" % (filename,license)
   341 				print "*** %s is subject to the %s which you have not yet accepted\n" % (filename,license)
   326 		elif hasattr(e, 'code'):
   353 		elif hasattr(e, 'code'):
   327 			print 'Error code: ', e.code
   354 			print 'Error code: ', e.code
   328 		return False
   355 		return False
   329 
   356 
   330 	# we are now up and running, and chunk contains the start of the download
   357 	# we are now up and running, and chunk contains the start of the download
   331 	
   358 	if options.debug:
       
   359 		print "\nReading %s from effective URL %s" % (filename, response.geturl())
       
   360 
   332 	try:
   361 	try:
   333 		fp = open(filename, 'wb')
   362 		if resume_start > 0:
       
   363 			fp = open(filename, 'a+b')	# append to existing content
       
   364 			if options.progress:
       
   365 				print " - Resuming at offset %d" % (resume_start)
       
   366 				size = resume_start
       
   367 				last_size = size
       
   368 		else:
       
   369 			fp = open(filename, 'wb')		# write new file
   334 		md5 = hashlib.md5()
   370 		md5 = hashlib.md5()
   335 		while True:
   371 		while True:
   336 			fp.write(chunk)
   372 			fp.write(chunk)
   337 			md5.update(chunk)
   373 			md5.update(chunk)
   338 			size += len(chunk)
   374 			size += len(chunk)
   352 				last_size = size
   388 				last_size = size
   353 			chunk = response.read(CHUNK)
   389 			chunk = response.read(CHUNK)
   354 			if not chunk: break
   390 			if not chunk: break
   355 
   391 
   356 		fp.close()
   392 		fp.close()
   357 		if options.progress:
       
   358 			now = time.time()
       
   359 			print "- Completed %s - %d Kb in %d seconds" % (filename, (filesize/1024)+0.5, now-start_time)
       
   360 
   393 
   361 	#handle errors
   394 	#handle errors
   362 	except urllib2.URLError, e:
   395 	except urllib2.URLError, e:
   363 		print '- ERROR: Failed while downloading ' + filename
   396 		print '- ERROR: Failed while downloading ' + filename
   364 		if hasattr(e, 'reason'):
   397 		if hasattr(e, 'reason'):
   365 			print 'Reason: ', e.reason
   398 			print 'Reason: ', e.reason
   366 		elif hasattr(e, 'code'):
   399 		elif hasattr(e, 'code'):
   367 			print 'Error code: ', e.code
   400 			print 'Error code: ', e.code
   368 		return False
   401 		return False
   369 
   402 
       
   403 	if options.debug:
       
   404 		info = response.info()
       
   405 		print "Info from final response of transfer:"
       
   406 		print response.info()
       
   407 
       
   408 	if filesize > 0 and size != filesize:
       
   409 		print "Incomplete transfer - only received %d bytes of the expected %d byte file" % (size, filesize)
       
   410 		return False
       
   411 	
       
   412 	if options.progress:
       
   413 		now = time.time()
       
   414 		print "- Completed %s - %d Kb in %d seconds" % (filename, (filesize/1024)+0.5, now-start_time)
       
   415 
   370 	if filename in checksums:
   416 	if filename in checksums:
   371 		download_checksum = md5.hexdigest().upper()
   417 		download_checksum = md5.hexdigest().upper()
       
   418 		if resume_start > 0:
       
   419 			# did a partial download, so need to checksum the whole file
       
   420 			download_checksum = md5_checksum(filename)
   372 		if download_checksum != checksums[filename]:
   421 		if download_checksum != checksums[filename]:
   373 			print '- WARNING: %s checksum does not match' % filename
   422 			if options.debug:
       
   423 				print '- Checksum for %s was %s, expected %s' % (filename, download_checksum, checksums[filename])
       
   424 			print '- ERROR: %s checksum does not match' % filename
       
   425 			return False
   374 
   426 
   375 	return True
   427 	return True
   376 
   428 
   377 def downloadkit(version):	
   429 def downloadkit(version):	
   378 	global headers
   430 	global headers
   379 	global options
   431 	global options
       
   432 	global failure_list
   380 	urlbase = top_level_url + '/main/tools_and_kits/downloads/'
   433 	urlbase = top_level_url + '/main/tools_and_kits/downloads/'
   381 
   434 
   382 	viewid = 5   # default to Symbian^3
   435 	viewid = 5   # default to Symbian^3
   383 	if version[0] == '2':
   436 	if version[0] == '2':
   384 		viewid= 1  # Symbian^2
   437 		viewid= 1  # Symbian^2
   437 		if options.noarmv5 and re.search(r"armv5", filename) :
   490 		if options.noarmv5 and re.search(r"armv5", filename) :
   438 			continue 	# no armv5 emulator...
   491 			continue 	# no armv5 emulator...
   439 		if options.noarmv5 and options.nowinscw and re.search(r"binaries_epoc.zip|binaries_epoc_sdk", filename) :
   492 		if options.noarmv5 and options.nowinscw and re.search(r"binaries_epoc.zip|binaries_epoc_sdk", filename) :
   440 			continue 	# skip binaries_epoc and binaries_sdk ...
   493 			continue 	# skip binaries_epoc and binaries_sdk ...
   441 		if download_file(filename, downloadurl) != True :
   494 		if download_file(filename, downloadurl) != True :
       
   495 			failure_list.append(filename)
   442 			continue # download failed
   496 			continue # download failed
   443 
   497 
   444 		# unzip the file (if desired)
   498 		# unzip the file (if desired)
   445 		if re.match(r"patch", filename):
   499 		if re.match(r"patch", filename):
   446 			complete_outstanding_unzips()	# ensure that the thing we are patching is completed first
   500 			complete_outstanding_unzips()	# ensure that the thing we are patching is completed first
   455 			schedule_unzip(filename, 1, 1)   # unpack then delete zip as it's not needed again
   509 			schedule_unzip(filename, 1, 1)   # unpack then delete zip as it's not needed again
   456 
   510 
   457 	# wait for the unzipping threads to complete
   511 	# wait for the unzipping threads to complete
   458 	complete_outstanding_unzips()  
   512 	complete_outstanding_unzips()  
   459 
   513 
       
   514 	if len(failure_list) > 0:
       
   515 		print "\n"
       
   516 		print "Downloading completed, with failures in %d files\n" % (len(failure_list))
       
   517 		print "\n\t".join(failure_list)
       
   518 		print "\n"
       
   519 	elif not options.dryrun:
       
   520 		print "\nDownloading completed successfully"
   460 	return 1
   521 	return 1
   461 
   522 
   462 parser = OptionParser(version="%prog "+version, usage="Usage: %prog [options] version")
   523 parser = OptionParser(version="%prog "+version, usage="Usage: %prog [options] version")
   463 parser.add_option("-n", "--dryrun", action="store_true", dest="dryrun",
   524 parser.add_option("-n", "--dryrun", action="store_true", dest="dryrun",
   464 	help="print the files to be downloaded, the 7z commands, and the recommended deletions")
   525 	help="print the files to be downloaded, the 7z commands, and the recommended deletions")
   480 	help="specify the account password")
   541 	help="specify the account password")
   481 parser.add_option("--debug", action="store_true", dest="debug", 
   542 parser.add_option("--debug", action="store_true", dest="debug", 
   482 	help="debug HTML traffic (not recommended!)")
   543 	help="debug HTML traffic (not recommended!)")
   483 parser.add_option("--webhost", dest="webhost", metavar="SITE",
   544 parser.add_option("--webhost", dest="webhost", metavar="SITE",
   484 	help="use alternative website (for testing!)")
   545 	help="use alternative website (for testing!)")
       
   546 parser.add_option("--noresume", action="store_false", dest="resume",
       
   547 	help="Do not attempt to continue a previous failed transfer")
   485 parser.set_defaults(
   548 parser.set_defaults(
   486 	dryrun=False, 
   549 	dryrun=False, 
   487 	nosrc=False, 
   550 	nosrc=False, 
   488 	nowinscw=False, 
   551 	nowinscw=False, 
   489 	noarmv5=False, 
   552 	noarmv5=False, 
   491 	nodelete=False, 
   554 	nodelete=False, 
   492 	progress=False,
   555 	progress=False,
   493 	username='',
   556 	username='',
   494 	password='',
   557 	password='',
   495 	webhost = 'developer.symbian.org',
   558 	webhost = 'developer.symbian.org',
       
   559 	resume=True,
   496 	debug=False
   560 	debug=False
   497 	)
   561 	)
   498 
   562 
   499 (options, args) = parser.parse_args()
   563 (options, args) = parser.parse_args()
   500 if len(args) != 1:
   564 if len(args) != 1: