Advertisement
Guest User

stdin

a guest
Feb 1st, 2009
119
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 9.90 KB | None | 0 0
  1. #!/usr/bin/python
  2. # -*- coding: UTF-8 -*-
  3. # Copyright (C) 2008 Terence Simpson <tsimpson@ubuntu.com>
  4. # License:
  5. #  This program is free software; you can redistribute it and/or modify
  6. #  it under the terms of the GNU General Public License as published by
  7. #  the Free Software Foundation; either version 2 of the License, or
  8. #  (at your option) any later version.
  9. #
  10. #  This program is distributed in the hope that it will be useful,
  11. #  but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. #  GNU General Public License for more details.
  14. #
  15. #  You should have received a copy of the GNU General Public License along
  16. #  with this program; if not, write to the Free Software Foundation, Inc.,
  17. #  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  18. #
  19. # This script simulates «dget»'s behaviour for files hosted at
  20. # launchpadlibrarian.net.
  21. #
  22. # Detailed description:
  23. # This script attempts to download the source package in the same
  24. # way as dget does, but from launchpadlibrarian.net, which doesn't
  25. # store all the files in the same directory. It (the script) assumes
  26. # that the files are stored in sequential directories on Launchpad
  27. # Librarian and attempts to download and then unpack them.
  28. # This is a Python rewrite of the original bash script
  29.  
  30. import sys, os
  31. from optparse import OptionParser
  32. import urllib2
  33. import md5
  34. import subprocess
  35. import GnuPGInterface
  36. from cStringIO import StringIO
  37. from email import FeedParser
  38.  
  39. Usage = u"""Usage: %prog [-d|(-v|-q)] <Launchpad URL>
  40.  
  41. This scripts simulates «dget»'s behaviour for files hosted at
  42. launchpadlibrarian.net.
  43.  
  44. If you specify the -d option then it won't do anything, except download the
  45. .dsc file, but just print the commands it would run otherwise.
  46.  
  47. Example:
  48.     %prog http://launchpadlibrarian.net/10348157/coreutils_5.97-5.4ubuntu1.dsc
  49. """
  50.  
  51. unpack_cmd = "dpkg-source -x "
  52. base_url = "http://launchpadlibrarian.net/"
  53.  
  54. Debug = Verbose = Quiet = False
  55.  
  56. def Unsign(data):
  57.     if data.splitlines()[0] != "-----BEGIN PGP SIGNED MESSAGE-----":
  58.         return data
  59.     oldstdout = sys.stdout
  60.     oldstderr = sys.stderr
  61.     sys.stdout = sys.__stdout__
  62.     sys.stderr = sys.__stderr__
  63.     gpg = GnuPGInterface.GnuPG()
  64.     proc = gpg.run(["--decrypt"], create_fhs=['stdin', 'stdout'])
  65.     proc.handles['stdin'].write(data)
  66.     proc.handles['stdin'].close()
  67.     plain = proc.handles['stdout'].read()
  68.     proc.handles['stdout'].close()
  69.     try:
  70.         proc.wait()
  71.     except:
  72.         pass
  73.     sys.stdout = oldstdout
  74.     sys.stderr = oldstderr
  75.     return plain
  76.  
  77. def getEntries(data):
  78.     parser = FeedParser.FeedParser()
  79.     parser.feed(data)
  80.     return parser.close()
  81.  
  82. class DscParse(object):
  83.     """Attempt to get the file list from the .dsc file"""
  84.     def __init__(self, data):
  85.         """
  86.        __init__(data)
  87.        Given the contents of a .dsc, parse it and extract it's content
  88.        """
  89.         self.entries = getEntries(Unsign(data))
  90.         self.files = [x.strip().split() for x in self.entries['Files'].splitlines()]
  91.  
  92.     def verify_all(self):
  93.         """
  94.        verify_all()
  95.        Verifies all the files, first checking the size, then the md5 sum.
  96.        Currently not used in this utility.
  97.        """
  98.         assert self.files, "I have no files"
  99.         ret = []
  100.         for f in self.files:
  101.             ret.append(self.verify(f))
  102.         return ret
  103.  
  104.     def verify(self, name):
  105.         """
  106.        verify(name)
  107.        Verify the file 'name', first checking the size, then the md5 sum.
  108.        """
  109.         assert self.files, "I have no files"
  110.         f = None
  111.         if isinstance(name, list):
  112.             f = name
  113.         else:
  114.             for i in self.files:
  115.                 if i[2] == name:
  116.                     f = i
  117.         if not f:
  118.             raise ValueError, "%s is not in the .dsc" % name
  119.         (sum, size, name) = tuple(f)
  120.         stat = os.stat(name)
  121.         if str(stat.st_size) != size:
  122.             return (False, name, "Expected a size of %s, got %s" % \
  123.                 (size, stat.st_size))
  124.         return self.getsum(name, sum)
  125.  
  126.     def getsum(self, name, sum=None):
  127.         """
  128.        getsum(name[, sum])
  129.        Read the file 'name' (in 1MB chunks) and generate an md5 sum,
  130.        then compares that to the md5 sum in the .dsc file.
  131.        """
  132.         chunk_size = 1073741824
  133.         fd = open(name, 'rb')
  134.         res = md5.new()
  135.         if not sum:
  136.             assert self.files, "I have no files"
  137.             sum = [x[0] for x in self.files if x[2] == name][0]
  138.         data = fd.read(chunk_size)
  139.         while data:
  140.             res.update(data)
  141.             data = fd.read(chunk_size)
  142.         if res.hexdigest() != sum:
  143.             return (False, name, "Expected md5sum of %r, got %r" % \
  144.                 (sum, res.hexdigest()) )
  145.         return (True, name, None)
  146.  
  147.     def isNative(self):
  148.         """
  149.        isNative()
  150.        Returns True if this .dsc describes a native debian package;
  151.        else false.
  152.        """
  153.         return len(self.files) == 1
  154.  
  155.     # Access to fields in the .dsc via a dict-like interface
  156.     def __getitem__(self, item):
  157.         """
  158.        x.__getitem(item) -> x[item]
  159.        """
  160.         return self.entries.__getitem__(item)
  161.  
  162.     def __contains__(self, item):
  163.         """
  164.        x.__contains__(item) -> item in x
  165.        """
  166.         return self.entries.__contains__(item)
  167.  
  168.     def __getattr__(self, attr):
  169.         """
  170.        x.__getattr__(attr) -> item.attr
  171.        """
  172.         return getattr(self.entries, attr)
  173.  
  174. def error(ret, msg, *args):
  175.     """Prints an error message, unless quiet is set, and exits with ret"""
  176.     if not Quiet:
  177.         print >> sys.stderr, msg % args
  178.     sys.exit(ret)
  179.  
  180. def debug(msg, *args):
  181.     """If debugging is enabled, print a message"""
  182.     if Debug:
  183.         print >> sys.stderr, msg % args
  184.  
  185. def info(msg, *args):
  186.     """If verbose is enabled, print a message"""
  187.     if Verbose:
  188.         print msg % tuple(args)
  189.  
  190. def status(msg, *args):
  191.     """Prints a message, unless quiet is enabled"""
  192.     if not Quiet:
  193.         print msg % tuple(args)
  194.  
  195. def Download(dscinfo, number, filename, verify=True):
  196.     """Download filename"""
  197.     ftype = filename.endswith(".diff.gz") and "diff.gz" or \
  198.         filename.endswith(".orig.tar.gz") and "orig.tar.gz" or \
  199.         filename.endswith(".dsc") and "dsc" or "tar.gz"
  200.     if verify and os.path.exists(filename):
  201.         info('Verifying "%s"', filename)
  202.         res = dscinfo.verify(filename)
  203.         if not res[0]:
  204.             error(104, "Verification of %s failed: %s", filename, res[2])
  205.     status("Getting %s", filename)
  206.     debug("%s%s/%s", base_url,number,filename)
  207.     try:
  208.         fd = urllib2.urlopen("%s%s/%s" % (base_url, number, filename))
  209.         outfd = open(filename, 'wb')
  210.         outfd.write(fd.read())
  211.         fd.close()
  212.         outfd.close()
  213.     except urllib2.URLError, e:
  214.         status("Failed to fetch «%s» file, aborting.", ftype)
  215.         error(105, "Error: %s", e)
  216.     except urllib2.HTTPError, e:
  217.         status("Failed to fetch «%s» file, aborting.", ftype)
  218.         error(106, "Error: (%d %s)", e.code, e.msg)
  219.     except IOError, e:
  220.         status('Could not create "%s"', filename)
  221.         error(107, "Error: %s", e)
  222.  
  223. def unpack():
  224.     out = open('/dev/null', 'w')
  225.     err = open('/dev/null', 'w')
  226.     ret = subprocess.call(unpack_cmd.split(), stdout=out, stderr=err)
  227.     out.close()
  228.     err.close()
  229.     if ret:
  230.         status("Failed to unpack source, aborting.")
  231.         sys.exit(108)
  232.  
  233. def getHost(url):
  234.     return urllib2.splithost(urllib2.splittype(url)[1])[0]
  235.  
  236. if __name__ == "__main__":
  237.     parser = OptionParser(usage=Usage)
  238.     parser.add_option("-d", "--debug", action="store_true", dest="debug",
  239.         default=False, help="Enable debugging")
  240.     parser.add_option("-v", "--verbose", action="store_true", dest="verbose",
  241.         default=False, help="Enable verbose output")
  242.     parser.add_option("-q", "--quiet", action="store_true", dest="quiet",
  243.         default=False, help="Never print any output")
  244.  
  245.     (options, args) = parser.parse_args()
  246.     Debug = options.debug
  247.     Verbose = options.verbose
  248.     Quiet = options.quiet
  249.     if Verbose and Quiet:
  250.         error(4, "Specifying both --verbose and --quiet does not make sense")
  251.     if Quiet:
  252.         sys.stderr = StringIO()
  253.         sys.stdout = StringIO()
  254.  
  255.     url = args[0]
  256.  
  257.     if url.startswith("https://"):
  258.         url = url.replace("https://", "http://", 1)
  259.  
  260.     if not url.startswith("http://"):
  261.         url = "http://" + url
  262.  
  263.     if getHost(url).startswith("www."):
  264.         url = url.replace("www.", "", 1)
  265.  
  266.     if getHost(url) != getHost(base_url):
  267.         error(1, "Error: This utility only works for files on %s.\nMaybe you want to try dget?", base_url)
  268.  
  269.     (number, filename) = url.split('/')[3:]
  270.  
  271.     if not filename.endswith('.dsc'):
  272.         error(2, "You have to provide the URL for the .dsc file.")
  273.  
  274.     try:
  275.         number = int(number)
  276.     except:
  277.         error(3, "Bad URL format")
  278.  
  279.     unpack_cmd += filename
  280.  
  281.     if os.path.exists(filename):
  282.         os.remove(filename)
  283.  
  284.     Download(None, number, filename, False)
  285.     try:
  286.         fd = open(filename)
  287.         dsc_data = fd.read()
  288.         fd.close()
  289.     except Exception, e:
  290.         status("Error: Please report this bug, providing the URL and attach"\
  291.                " the following backtrace")
  292.         raise
  293.  
  294.     dscinfo = DscParse(dsc_data)
  295.  
  296. # launchpadlibrarian.net seems to store in this order:
  297. # For native packages:
  298. # <number>/.changes
  299. # <number>+1/.tar.gz
  300. # <number>+2/.dsc
  301. # For non-native packages:
  302. # <number>/.changes
  303. # <number>+1/.orig.tar.gz
  304. # <number>+2/.diff.gz
  305. # <number>+3/.dsc
  306. ##
  307. # *Assuming* this does not change, we can figure out where the files are on
  308. # launchpadlibrarian.net relative to the .dsc file we're given.
  309.  
  310. # Only one file listed in the .dsc means it's native package
  311.     if len(dscinfo.files) == 1:
  312.         Download(dscinfo, number-1, dscinfo.files[0][-1]) # .tar.gz
  313.     else:
  314.         Download(dscinfo, number-1, dscinfo.files[1][-1]) # .diff.gz
  315.         Download(dscinfo, number-2, dscinfo.files[0][-1]) # .orig.tar.gz
  316.  
  317.     status("Unpacking")
  318.     unpack()
  319.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement