Merge lp:~nshepperd/apt-zeroconf/parse-hacking into lp:apt-zeroconf

Proposed by nshepperd
Status: Needs review
Proposed branch: lp:~nshepperd/apt-zeroconf/parse-hacking
Merge into: lp:apt-zeroconf
Diff against target: None lines
To merge this branch: bzr merge lp:~nshepperd/apt-zeroconf/parse-hacking
Reviewer Review Type Date Requested Status
Apt Zeroconf Team Pending
Review via email: mp+10888@code.launchpad.net
To post a comment you must log in.
Revision history for this message
nshepperd (nshepperd) wrote :

I fixed a bug preventing apt-zeroconf from downloading locally packages with an epoch in the version. The problem involved http unquoting ("%3a" -> ":") of package names in the list, and requoting, which changed the quoted part to uppercase. I felt it was unsafe to do this sort of manipulation in the package list anyway, so I removed that quote/unquoting.

I also changed a bit of the way the list parser works, mostly because I think it makes it more readable. However that is subjective, of course.

I'd like some feedback on these changes, please, and if everyone thinks they're okay, I'll merge them.

Revision history for this message
nshepperd (nshepperd) wrote :

Eek, launchpad doesn't seem to be line-wrapping my summary... posted again:

> I fixed a bug preventing apt-zeroconf from downloading locally packages with
> an epoch in the version. The problem involved http unquoting ("%3a" -> ":") of
> package names in the list, and requoting, which changed the quoted part to
> uppercase. I felt it was unsafe to do this sort of manipulation in the package
> list anyway, so I removed that quote/unquoting.
>
> I also changed a bit of the way the list parser works, mostly because I think
> it makes it more readable. However that is subjective, of course.
>
> I'd like some feedback on these changes, please, and if everyone thinks
> they're okay, I'll merge them.

128. By nshepperd

Merge back from trunk.

Unmerged revisions

128. By nshepperd

Merge back from trunk.

127. By nshepperd

Remove unused 'V3' epoch pattern regular expression.

126. By nshepperd

Add test for downloading a deb with an epoch, which the parse.py changes
probably broke, and add fixes so that test passes.

125. By nshepperd

Major disruption of parse.py to simplify extremely.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'aptzeroconf/parse.py'
--- aptzeroconf/parse.py 2009-08-09 02:39:51 +0000
+++ aptzeroconf/parse.py 2009-08-09 04:00:35 +0000
@@ -21,116 +21,70 @@
21from xml.sax import parseString, ContentHandler21from xml.sax import parseString, ContentHandler
22#from xml.sax.handler import feature_namespaces22#from xml.sax.handler import feature_namespaces
23from xml.sax._exceptions import SAXParseException23from xml.sax._exceptions import SAXParseException
24from apt_pkg import VersionCompare
24import logging25import logging
25import re26import re
2627
27__version__ = float(0.3)28__version__ = float(0.3)
2829
29V2, V3 = 0.2, 0.330V2, V3 = 0.2, 0.3
30EPOCH_PATTERN = {31EPOCH_PATTERN = re.compile(r'\_\d+\%3a')
31 V2 : re.compile(r'\_\d+\%3a'),
32 V3 : re.compile(r'\_\d+\:')
33}
3432
35def normalize_whitespace(text):33def normalize_whitespace(text):
36 """Remove excessive whitespace in spaces."""34 """Remove excessive whitespace in spaces."""
37 return ' '.join(text.split())35 return ' '.join(text.split())
3836
39def _is_equal(pkg, find, ver):37def _is_equal(pkg, find):
40 """Are the packages equal disregarding the epoch?"""38 """Are the packages equal disregarding the epoch?"""
41 return EPOCH_PATTERN[ver].sub('_', pkg) == find or pkg == find39 return EPOCH_PATTERN.sub('_', pkg) == find or pkg == find
4240
4341
44class _PackageList(list):42class Package(object):
45 """43 """Represents a single package."""
46 Found Package list container.44 def __init__(self, name=''):
47 """45 self.name = name
48
49 def found(self, item):
50 """
51 Callback to add a found package to contatiner
52
53 @param item: Package Name
54 @type item: basestring
55 """
56 self.append(item)
57
58 def __str__(self):
59 """
60 String representaion of found package container. Displays the best
61 package found in container if multiples were added.
62
63 @return: Best or only package
64 @rtype: basestring
65 """
66 from apt_pkg import VersionCompare
67 from urllib2 import quote
68 if len(self) > 1:
69 self.sort(cmp=VersionCompare)
70 return quote(self[-1])
71
72 elif len(self) == 1:
73 return quote(self[0])
74
75 else:
76 return ""
77
78 def __repr__(self):46 def __repr__(self):
79 return '<Package name="%s" count="%d">' % (str(self), len(self))47 return '<Package name=' + self.name + '>'
8048
8149
82class _PackageFinder(ContentHandler):50class _XMLDecoder(ContentHandler):
51 """Decodes an xml package list, calling a callback for each package."""
83 callback = None #: Callback function to be set when item found52 callback = None #: Callback function to be set when item found
84 inPkgElemCnt = {}
8553
86 def __init__(self, package):54 def __init__(self):
87 """55 """
88 Initialize sax content handler.56 Initialize sax content handler.
8957
90 @param search_name: Package to locate in search data.58 @param search_name: Package to locate in search data.
91 @type search_name: basestring59 @type search_name: basestring
92 """60 """
93 self.packageSearch = normalize_whitespace(package)61 self.text = ''
9462 self.package = None
95 self.inPkgName = False #: Initialize the content flag to False63 self.list_version = ''
96 self.inPkg = False #: Initialize the content flag to False
97 self.list_version = '' #: Initialize the version value to None
9864
99 def startElement(self, name, attrs):65 def startElement(self, name, attrs):
100 """66 """
101 Check start element tags for packagelist or name.67 Check start element tags for packagelist or name.
10268
103 @param name: element tag.69 @param name: element tag
104 @type name: basestring70 @type name: basestring
105 @param name: element tag attrs.71 @param name: element attributes
106 @type name: basestring72 @type name: dict
107 """73 """
108 if name == 'packagelist': #: handle element by getting ver. attribute74 if name == 'packagelist': #: handle element by getting ver. attribute
109 self.list_version = normalize_whitespace(attrs.get('version', ""))75 self.list_version = normalize_whitespace(attrs.get('version', ""))
11076 if name == 'package':
111 if float(self.list_version) == __version__:77 self.package = Package()
11278 self.text = ''
113 if name == 'package':
114 self.inPkg = True
115 self.inPkgElemCnt = {'name': 0}
116
117 elif name == 'name': #: handle element by set flag, reset value
118 if self.inPkgElemCnt[name] > 1:
119 raise SAXParseException
120
121 self.inPkgName = True
122 self.packageName = ""
123 self.inPkgElemCnt[name] += 1
12479
125 def characters(self, char):80 def characters(self, char):
126 """81 """
127 Check characters for name value.82 Add character input to text buffer.
12883
129 @param char: text chunk84 @param char: text chunk
130 @type name: basestring85 @type name: basestring
131 """86 """
132 if self.inPkgName:87 self.text += char
133 self.packageName = self.packageName + char
13488
135 def endElement(self, name):89 def endElement(self, name):
136 """90 """
@@ -140,70 +94,71 @@
140 @param name: element tag.94 @param name: element tag.
141 @type name: basestring95 @type name: basestring
142 """96 """
143 if float(self.list_version) == __version__:97 if name == 'package':
144 if name == 'package':98 self.callback(self.package)
145 self.inPkgName = False99 elif name == 'name':
146 elif name == 'name':100 self.package.name = self.text
147 self.inPkgName = False101
148 self.packageName = normalize_whitespace(self.packageName)102def decode_list(data):
149
150 #{ Check search_name for match with packagename
151 if _is_equal(self.packageName, self.packageSearch, V3):
152 self.callback(self.packageName)
153
154
155def has_package(package, data):
156 """103 """
157 Locate package in data string.104 @param data: xml/raw package data
158
159 @param package: package name
160 @type package: basestring
161 @param data: xml package data
162 @type data: basestring105 @type data: basestring
163 @return: PkgList object106 @return: list of Packages
164 @rtype: object107 @rtype: list
165 """108 """
166 pkglist = _PackageList()
167 if not data.startswith('<?xml'):109 if not data.startswith('<?xml'):
168 for name in data.split():110 data = data.split()
169 if _is_equal(name, package, V2):111 if data[0] == str(V2):
170 pkglist.found(name)112 return [Package(name) for name in data[1:]]
171 return pkglist
172113
173 handler = _PackageFinder(package)114 pkglist = []
174 handler.callback = pkglist.found115 handler = _XMLDecoder()
116 handler.callback = pkglist.append
175117
176 try:118 try:
177 parseString(data, handler)119 parseString(data, handler)
178
179 except SAXParseException:120 except SAXParseException:
180 #{ Non-conforming list121 #{ Non-conforming list
181 logging.warning(122 logging.warning(
182 '[PARSER] Parse Error, Ignoring: malformed package list.')123 '[PARSER] Parse Error, Ignoring: malformed package list.')
183 return _PackageList()124 return []
184
185 return pkglist125 return pkglist
186126
187127
128def has_package(package, list):
129 """
130 Locate package (disregarding the epoch) in package list.
131
132 Returns the package name, including the epoch if there is one.
133
134 @param package: package name
135 @type package: basestring
136 @param data: list of Packages
137 @type data: list
138 @return: package name
139 @rtype: basestring
140 """
141 results = []
142 for pkg in list:
143 if _is_equal(pkg.name, package):
144 results.append(pkg.name)
145
146 results.sort(cmp=VersionCompare)
147 if results:
148 return results[-1]
149 else:
150 return ''
151
152
188def store_packages(url, port):153def store_packages(url, port):
189 """154 """
190 Fetch non-local AZC Cache service host cache listings.155 Fetch non-local AZC Cache service host cache listings.
191
192 @param url:
193 @type url:
194 """156 """
195 from urllib2 import Request, urlopen157 from urllib2 import Request, urlopen
196158
197 pkglist = urlopen(Request(url="http://%s:%d/list" % (url, port)))159 pkglist = urlopen("http://%s:%d/list" % (url, port))
198 pkgdata = pkglist.read(4)160 data = pkglist.read()
199161 return decode_list(data)
200 if pkgdata[:3] == V2:
201 return pkglist.read()
202
203 elif pkgdata == '<?xm':
204 return pkgdata + pkglist.read()
205
206 return ''
207162
208163
209def make_packagelist(packages):164def make_packagelist(packages):
@@ -215,12 +170,10 @@
215 @return: xml package data170 @return: xml package data
216 @rtype: basestring171 @rtype: basestring
217 """172 """
218 from urllib2 import unquote
219
220 form = ("<?xml version='1.0' ?>\n"173 form = ("<?xml version='1.0' ?>\n"
221 "<?xml-stylesheet href='list.xsl' type='text/xsl' ?>\n"174 "<?xml-stylesheet href='list.xsl' type='text/xsl' ?>\n"
222 "<packagelist version='%.1f'>\n" % __version__)175 "<packagelist version='%.1f'>\n" % __version__)
223 for pkg in packages:176 for pkg in packages:
224 form += " <package>\n <name>%s</name>\n </package>" % unquote(pkg)177 form += " <package>\n <name>%s</name>\n </package>" % pkg
225 form += "</packagelist>\n"178 form += "</packagelist>\n"
226 return form179 return form
227180
=== modified file 'test/test_general.py'
--- test/test_general.py 2009-07-24 23:24:41 +0000
+++ test/test_general.py 2009-08-09 03:52:46 +0000
@@ -124,6 +124,33 @@
124 PORT, DEBNAME)).read()124 PORT, DEBNAME)).read()
125 # make sure the data is equal125 # make sure the data is equal
126 assert data == '*contents*'126 assert data == '*contents*'
127
128def test_download_epoch_local_server(tmpdir):
129 """
130 Test fetching a file with an epoch from an AZC instance on the local network.
131 """
132 # cachedir is the directory to serve debs out of
133 one = path.join(str(tmpdir), 'one')
134 two = path.join(str(tmpdir), 'two')
135 cacheone = path.join(one, 'debs')
136 cachetwo = path.join(two, 'debs')
137 os.mkdir(one)
138 os.mkdir(two)
139 os.mkdir(cacheone)
140 os.mkdir(cachetwo)
141 touch(path.join(cachetwo, DEBEPOCHNAME), '*contents*')
142
143 one_config = {('apt-zeroconf', 'restrict'): 'Off',
144 ('apt-zeroconf', 'hostname'): 'one'}
145 two_config = {('apt-zeroconf', 'restrict'): 'Off',
146 ('apt-zeroconf', 'hostname'): 'two'}
147 with azc_server(one, cacheone, PORT, one_config):
148 with azc_server(two, cachetwo, PORT+1, two_config):
149 time.sleep(3.0) # wait for one to discover two
150 data = urlopen('http://localhost:%i/http://example.com/%s' % (
151 PORT, DEBNAME)).read()
152 # make sure the data is equal
153 assert data == '*contents*'
127154
128155
129def test_download_proxy(tmpdir):156def test_download_proxy(tmpdir):

Subscribers

People subscribed via source and target branches