checkversions.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. #
  2. # checkversions.py - Find if the installed version of a package is the latest
  3. #
  4. # Written by Chris Lawrence <lawrencc@debian.org>
  5. # (C) 2002-08 Chris Lawrence
  6. # Copyright (C) 2008-2016 Sandro Tosi <morph@debian.org>
  7. #
  8. # This program is freely distributable per the following license:
  9. #
  10. # Permission to use, copy, modify, and distribute this software and its
  11. # documentation for any purpose and without fee is hereby granted,
  12. # provided that the above copyright notice appears in all copies and that
  13. # both that copyright notice and this permission notice appear in
  14. # supporting documentation.
  15. #
  16. # I DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
  17. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL I
  18. # BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
  19. # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
  20. # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
  21. # ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
  22. # SOFTWARE.
  23. import sys
  24. import os
  25. import re
  26. import urllib2
  27. import sgmllib
  28. import gc
  29. import time
  30. import utils
  31. from urlutils import open_url
  32. from reportbug.exceptions import (
  33. NoNetwork,
  34. )
  35. # needed to parse new.822
  36. from debian.deb822 import Deb822
  37. from debian import debian_support
  38. RMADISON_URL = 'http://qa.debian.org/madison.php?package=%s&text=on'
  39. INCOMING_URL = 'http://incoming.debian.org/'
  40. NEWQUEUE_URL = 'http://ftp-master.debian.org/new.822'
  41. # The format is an unordered list
  42. class BaseParser(sgmllib.SGMLParser):
  43. def __init__(self):
  44. sgmllib.SGMLParser.__init__(self)
  45. self.savedata = None
  46. # --- Formatter interface, taking care of 'savedata' mode;
  47. # shouldn't need to be overridden
  48. def handle_data(self, data):
  49. if self.savedata is not None:
  50. self.savedata = self.savedata + data
  51. # --- Hooks to save data; shouldn't need to be overridden
  52. def save_bgn(self):
  53. self.savedata = ''
  54. def save_end(self, mode=0):
  55. data = self.savedata
  56. self.savedata = None
  57. if not mode and data is not None:
  58. data = ' '.join(data.split())
  59. return data
  60. class IncomingParser(sgmllib.SGMLParser):
  61. def __init__(self, package, arch='i386'):
  62. sgmllib.SGMLParser.__init__(self)
  63. self.found = []
  64. self.savedata = None
  65. arch = r'(?:all|' + re.escape(arch) + ')'
  66. self.package = re.compile(re.escape(package) + r'_([^_]+)_' + arch + '.deb')
  67. def start_a(self, attrs):
  68. for attrib, value in attrs:
  69. if attrib.lower() != 'href':
  70. continue
  71. mob = self.package.match(value)
  72. if mob:
  73. self.found.append(mob.group(1))
  74. def compare_versions(current, upstream):
  75. """Return 1 if upstream is newer than current, -1 if current is
  76. newer than upstream, and 0 if the same."""
  77. if not current or not upstream:
  78. return 0
  79. return debian_support.version_compare(upstream, current)
  80. def later_version(a, b):
  81. if compare_versions(a, b) > 0:
  82. return b
  83. return a
  84. def get_versions_available(package, timeout, dists=None, http_proxy=None, arch='i386'):
  85. if not dists:
  86. dists = ('oldstable', 'stable', 'testing', 'unstable', 'experimental')
  87. arch = utils.get_arch()
  88. url = RMADISON_URL % package
  89. url += '&s=' + ','.join(dists)
  90. # select only those lines that refers to source pkg
  91. # or to binary packages available on the current arch
  92. url += '&a=source,all,' + arch
  93. try:
  94. page = open_url(url)
  95. except NoNetwork:
  96. return {}
  97. except urllib2.HTTPError, x:
  98. print >> sys.stderr, "Warning:", x
  99. return {}
  100. if not page:
  101. return {}
  102. # read the content of the page, remove spaces, empty lines
  103. content = page.read().replace(' ', '').strip()
  104. page.close()
  105. versions = {}
  106. for line in content.split('\n'):
  107. l = line.split('|')
  108. # skip lines not having the right number of fields
  109. if len(l) != 4:
  110. continue
  111. # map suites name (returned by madison) to dist name
  112. dist = utils.CODENAME2SUITE.get(l[2], l[2])
  113. versions[dist] = l[1]
  114. return versions
  115. def get_newqueue_available(package, timeout, dists=None, http_proxy=None, arch='i386'):
  116. if dists is None:
  117. dists = ('unstable (new queue)',)
  118. try:
  119. page = open_url(NEWQUEUE_URL, http_proxy, timeout)
  120. except NoNetwork:
  121. return {}
  122. except urllib2.HTTPError, x:
  123. print >> sys.stderr, "Warning:", x
  124. return {}
  125. if not page:
  126. return {}
  127. versions = {}
  128. # iter over the entries, one paragraph at a time
  129. for para in Deb822.iter_paragraphs(page):
  130. if para['Source'] == package:
  131. k = para['Distribution'] + ' (' + para['Queue'] + ')'
  132. # in case of multiple versions, choose the bigger
  133. versions[k] = max(para['Version'].split())
  134. return versions
  135. def get_incoming_version(package, timeout, http_proxy=None, arch='i386'):
  136. try:
  137. page = open_url(INCOMING_URL, http_proxy, timeout)
  138. except NoNetwork:
  139. return None
  140. except urllib2.HTTPError, x:
  141. print >> sys.stderr, "Warning:", x
  142. return None
  143. if not page:
  144. return None
  145. parser = IncomingParser(package, arch)
  146. for line in page:
  147. parser.feed(line)
  148. parser.close()
  149. try:
  150. page.fp._sock.recv = None
  151. except:
  152. pass
  153. page.close()
  154. if parser.found:
  155. found = parser.found
  156. del parser
  157. return reduce(later_version, found, '0')
  158. del page
  159. del parser
  160. return None
  161. def check_available(package, version, timeout, dists=None,
  162. check_incoming=True, check_newqueue=True,
  163. http_proxy=None, arch='i386'):
  164. avail = {}
  165. if check_incoming:
  166. iv = get_incoming_version(package, timeout, http_proxy, arch)
  167. if iv:
  168. avail['incoming'] = iv
  169. stuff = get_versions_available(package, timeout, dists, http_proxy, arch)
  170. avail.update(stuff)
  171. if check_newqueue:
  172. srcpackage = utils.get_source_name(package)
  173. if srcpackage is None:
  174. srcpackage = package
  175. stuff = get_newqueue_available(srcpackage, timeout, dists, http_proxy, arch)
  176. avail.update(stuff)
  177. # print gc.garbage, stuff
  178. new = {}
  179. newer = 0
  180. for dist in avail:
  181. if dist == 'incoming':
  182. if ':' in version:
  183. ver = version.split(':', 1)[1]
  184. else:
  185. ver = version
  186. comparison = compare_versions(ver, avail[dist])
  187. else:
  188. comparison = compare_versions(version, avail[dist])
  189. if comparison > 0:
  190. new[dist] = avail[dist]
  191. elif comparison < 0:
  192. newer += 1
  193. too_new = (newer and newer == len(avail))
  194. return new, too_new