Merge lp:~cjwatson/launchpad/archive-unambiguous-files-traversals into lp:launchpad

Proposed by Colin Watson
Status: Merged
Merged at revision: 18638
Proposed branch: lp:~cjwatson/launchpad/archive-unambiguous-files-traversals
Merge into: lp:launchpad
Diff against target: 695 lines (+262/-59)
18 files modified
lib/lp/services/librarian/browser.py (+9/-6)
lib/lp/soyuz/adapters/proxiedsourcefiles.py (+36/-0)
lib/lp/soyuz/browser/archive.py (+27/-1)
lib/lp/soyuz/browser/distributionsourcepackagerelease.py (+4/-4)
lib/lp/soyuz/browser/publishing.py (+7/-4)
lib/lp/soyuz/browser/tests/distributionsourcepackagerelease-views.txt (+2/-2)
lib/lp/soyuz/browser/tests/publishing-views.txt (+3/-3)
lib/lp/soyuz/browser/tests/test_publishing_webservice.py (+4/-4)
lib/lp/soyuz/interfaces/archive.py (+16/-1)
lib/lp/soyuz/model/archive.py (+24/-1)
lib/lp/soyuz/model/publishing.py (+10/-3)
lib/lp/soyuz/stories/ppa/xx-ppa-files.txt (+21/-14)
lib/lp/soyuz/stories/ppa/xx-ppa-packages.txt (+1/-1)
lib/lp/soyuz/stories/soyuz/xx-distributionsourcepackagerelease-pages.txt (+1/-1)
lib/lp/soyuz/stories/soyuz/xx-distroseries-sources.txt (+8/-8)
lib/lp/soyuz/stories/webservice/xx-source-package-publishing.txt (+4/-4)
lib/lp/soyuz/tests/test_archive.py (+83/-0)
lib/lp/soyuz/tests/test_publishing_models.py (+2/-2)
To merge this branch: bzr merge lp:~cjwatson/launchpad/archive-unambiguous-files-traversals
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+345118@code.launchpad.net

Commit message

Disambiguate URLs to source package files in the face of filename clashes in imported archives.

Description of the change

This gives us a way to cope with fetching the source files for e.g. d3-format 1.0.2-1 vs. 1:1.0.2-1 in the imported Debian archive, which would be helpful to merge-o-matic.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) wrote :

I guess we'll find all the clients that match on /+files/ paths.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/services/librarian/browser.py'
2--- lib/lp/services/librarian/browser.py 2015-07-09 20:06:17 +0000
3+++ lib/lp/services/librarian/browser.py 2018-05-08 18:13:25 +0000
4@@ -1,4 +1,4 @@
5-# Copyright 2010 Canonical Ltd. This software is licensed under the
6+# Copyright 2010-2018 Canonical Ltd. This software is licensed under the
7 # GNU Affero General Public License version 3 (see the file LICENSE).
8
9 """Browser file for LibraryFileAlias."""
10@@ -125,6 +125,13 @@
11 self.parent = parent
12
13 @property
14+ def request(self):
15+ request = get_current_browser_request()
16+ if WebServiceLayer.providedBy(request):
17+ request = IWebBrowserOriginatingRequest(request)
18+ return request
19+
20+ @property
21 def http_url(self):
22 """Return the webapp URL for the context `LibraryFileAlias`.
23
24@@ -137,11 +144,7 @@
25 if self.context.deleted:
26 return None
27
28- request = get_current_browser_request()
29- if WebServiceLayer.providedBy(request):
30- request = IWebBrowserOriginatingRequest(request)
31-
32- parent_url = canonical_url(self.parent, request=request)
33+ parent_url = canonical_url(self.parent, request=self.request)
34 traversal_url = urlappend(parent_url, '+files')
35 url = urlappend(
36 traversal_url,
37
38=== added file 'lib/lp/soyuz/adapters/proxiedsourcefiles.py'
39--- lib/lp/soyuz/adapters/proxiedsourcefiles.py 1970-01-01 00:00:00 +0000
40+++ lib/lp/soyuz/adapters/proxiedsourcefiles.py 2018-05-08 18:13:25 +0000
41@@ -0,0 +1,36 @@
42+# Copyright 2018 Canonical Ltd. This software is licensed under the
43+# GNU Affero General Public License version 3 (see the file LICENSE).
44+
45+"""Proxied source files."""
46+
47+from __future__ import absolute_import, print_function, unicode_literals
48+
49+__metaclass__ = type
50+__all__ = [
51+ 'ProxiedSourceLibraryFileAlias',
52+ ]
53+
54+from lp.services.librarian.browser import ProxiedLibraryFileAlias
55+from lp.services.librarian.client import url_path_quote
56+from lp.services.webapp.publisher import canonical_url
57+from lp.services.webapp.url import urlappend
58+
59+
60+class ProxiedSourceLibraryFileAlias(ProxiedLibraryFileAlias):
61+ """A `ProxiedLibraryFileAlias` variant that traverses via +sourcefiles.
62+
63+ This can be used to construct unambiguous source file URLs even for
64+ imports from upstream archives without robust historical filename
65+ uniqueness checks.
66+ """
67+
68+ @property
69+ def http_url(self):
70+ if self.context.deleted:
71+ return None
72+
73+ url = canonical_url(self.parent.archive, request=self.request)
74+ return urlappend(url, '/'.join([
75+ '+sourcefiles', self.parent.source_package_name,
76+ self.parent.source_package_version,
77+ url_path_quote(self.context.filename.encode('utf-8'))]))
78
79=== modified file 'lib/lp/soyuz/browser/archive.py'
80--- lib/lp/soyuz/browser/archive.py 2017-04-22 13:13:22 +0000
81+++ lib/lp/soyuz/browser/archive.py 2018-05-08 18:13:25 +0000
82@@ -1,4 +1,4 @@
83-# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
84+# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
85 # GNU Affero General Public License version 3 (see the file LICENSE).
86
87 """Browser views for archive."""
88@@ -455,6 +455,32 @@
89
90 return self.context.getArchiveDependency(archive)
91
92+ @stepthrough('+sourcefiles')
93+ def traverse_sourcefiles(self, sourcepackagename):
94+ """Traverse to a source file in the archive.
95+
96+ Normally, files in an archive are unique by filename, so the +files
97+ traversal is sufficient. Unfortunately, a gina-imported archive may
98+ contain the same filename with different contents due to a
99+ combination of epochs and less stringent checks applied by the
100+ upstream archive software. (In practice this only happens for
101+ source packages because that's normally all we import using gina.)
102+ This provides an unambiguous way to traverse to such files even with
103+ this problem.
104+
105+ The path scheme is::
106+
107+ +sourcefiles/:sourcename/:sourceversion/:filename
108+ """
109+ if len(self.request.stepstogo) < 2:
110+ return None
111+
112+ version = self.request.stepstogo.consume()
113+ filename = self.request.stepstogo.consume()
114+
115+ return self.context.getSourceFileByName(
116+ sourcepackagename, version, filename)
117+
118
119 class ArchiveMenuMixin:
120
121
122=== modified file 'lib/lp/soyuz/browser/distributionsourcepackagerelease.py'
123--- lib/lp/soyuz/browser/distributionsourcepackagerelease.py 2014-12-18 13:05:10 +0000
124+++ lib/lp/soyuz/browser/distributionsourcepackagerelease.py 2018-05-08 18:13:25 +0000
125@@ -1,4 +1,4 @@
126-# Copyright 2009 Canonical Ltd. This software is licensed under the
127+# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
128 # GNU Affero General Public License version 3 (see the file LICENSE).
129
130 __metaclass__ = type
131@@ -18,7 +18,6 @@
132 from lp.registry.browser.distributionsourcepackage import (
133 PublishingHistoryViewMixin,
134 )
135-from lp.services.librarian.browser import ProxiedLibraryFileAlias
136 from lp.services.propertycache import cachedproperty
137 from lp.services.webapp import (
138 canonical_url,
139@@ -27,6 +26,7 @@
140 stepthrough,
141 )
142 from lp.services.webapp.breadcrumb import Breadcrumb
143+from lp.soyuz.adapters.proxiedsourcefiles import ProxiedSourceLibraryFileAlias
144 from lp.soyuz.browser.build import get_build_by_id_str
145 from lp.soyuz.enums import PackagePublishingStatus
146 from lp.soyuz.interfaces.binarypackagebuild import IBinaryPackageBuildSet
147@@ -101,8 +101,8 @@
148 """The source package release files as `ProxiedLibraryFileAlias`."""
149 last_publication = self._cached_publishing_history[0]
150 return [
151- ProxiedLibraryFileAlias(
152- source_file.libraryfile, last_publication.archive)
153+ ProxiedSourceLibraryFileAlias(
154+ source_file.libraryfile, last_publication)
155 for source_file in self.context.files]
156
157 @cachedproperty
158
159=== modified file 'lib/lp/soyuz/browser/publishing.py'
160--- lib/lp/soyuz/browser/publishing.py 2015-07-09 20:06:17 +0000
161+++ lib/lp/soyuz/browser/publishing.py 2018-05-08 18:13:25 +0000
162@@ -1,4 +1,4 @@
163-# Copyright 2009-2013 Canonical Ltd. This software is licensed under the
164+# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
165 # GNU Affero General Public License version 3 (see the file LICENSE).
166
167 """Browser views for Soyuz publishing records."""
168@@ -30,6 +30,7 @@
169 canonical_url,
170 LaunchpadView,
171 )
172+from lp.soyuz.adapters.proxiedsourcefiles import ProxiedSourceLibraryFileAlias
173 from lp.soyuz.enums import PackagePublishingStatus
174 from lp.soyuz.interfaces.binarypackagebuild import BuildSetStatus
175 from lp.soyuz.interfaces.packagediff import IPackageDiff
176@@ -270,8 +271,7 @@
177 def published_source_and_binary_files(self):
178 """Return list of dictionaries representing published files."""
179 files = sorted(
180- (ProxiedLibraryFileAlias(lfa, self.context.archive)
181- for lfa in self.context.getSourceAndBinaryLibraryFiles()),
182+ self.context.getSourceAndBinaryLibraryFiles(),
183 key=attrgetter('filename'))
184 result = []
185 urls = set()
186@@ -285,14 +285,17 @@
187 urls.add(url)
188
189 custom_dict = {}
190- custom_dict["url"] = url
191 custom_dict["filename"] = library_file.filename
192 custom_dict["filesize"] = library_file.content.filesize
193 if (library_file.filename.endswith('.deb') or
194 library_file.filename.endswith('.udeb')):
195 custom_dict['class'] = 'binary'
196+ custom_dict["url"] = ProxiedLibraryFileAlias(
197+ library_file, self.context.archive).http_url
198 else:
199 custom_dict['class'] = 'source'
200+ custom_dict["url"] = ProxiedSourceLibraryFileAlias(
201+ library_file, self.context).http_url
202
203 result.append(custom_dict)
204
205
206=== modified file 'lib/lp/soyuz/browser/tests/distributionsourcepackagerelease-views.txt'
207--- lib/lp/soyuz/browser/tests/distributionsourcepackagerelease-views.txt 2014-11-27 22:13:36 +0000
208+++ lib/lp/soyuz/browser/tests/distributionsourcepackagerelease-views.txt 2018-05-08 18:13:25 +0000
209@@ -24,14 +24,14 @@
210 u'testing-dspr 1.0 source package in ubuntutest'
211
212 The 'files' property returns a list of files included in the source
213-upload encapsulated as `ProxiedLibraryFileAlias` objects. Their
214+upload encapsulated as `ProxiedSourceLibraryFileAlias` objects. Their
215 'http_url' points to the LP proxied url which normalizes the path
216 tofiles allowing them to be downloaded using `dget`.
217
218 >>> for source_file in dspr_view.files:
219 ... print source_file.filename, source_file.http_url
220 testing-dspr_1.0.dsc
221- http://.../ubuntutest/+archive/primary/+files/testing-dspr_1.0.dsc
222+ http://.../ubuntutest/+archive/primary/+sourcefiles/testing-dspr/1.0/testing-dspr_1.0.dsc
223
224 The 'sponsor' property indicates whether the upload was 'sponsored' or
225 not. When the upload was signed by someone else than the source
226
227=== modified file 'lib/lp/soyuz/browser/tests/publishing-views.txt'
228--- lib/lp/soyuz/browser/tests/publishing-views.txt 2014-07-24 09:37:03 +0000
229+++ lib/lp/soyuz/browser/tests/publishing-views.txt 2018-05-08 18:13:25 +0000
230@@ -63,7 +63,7 @@
231 >>> view = create_initialized_view(alsa_pub, "+listing-archive-detailed")
232
233 >>> view.published_source_and_binary_files
234- [{'url': u'http://launchpad.dev/ubuntutest/+archive/primary/+files/alsa-utils-test_666.dsc',
235+ [{'url': u'http://launchpad.dev/ubuntutest/+archive/primary/+sourcefiles/alsa-utils-test/666/alsa-utils-test_666.dsc',
236 'class': 'source',
237 'filesize': 28,
238 'filename': u'alsa-utils-test_666.dsc'}]
239@@ -81,11 +81,11 @@
240 ... iceweasel_source_pub, "+listing-archive-detailed")
241
242 >>> ppa_source_view.published_source_and_binary_files
243- [{'url': u'http://launchpad.dev/~cprov/+archive/ubuntu/ppa/+files/firefox_0.9.2.orig.tar.gz',
244+ [{'url': u'http://launchpad.dev/~cprov/+archive/ubuntu/ppa/+sourcefiles/iceweasel/1.0/firefox_0.9.2.orig.tar.gz',
245 'class': 'source',
246 'filesize': 9922560,
247 'filename': u'firefox_0.9.2.orig.tar.gz'},
248- {'url': u'http://launchpad.dev/~cprov/+archive/ubuntu/ppa/+files/iceweasel-1.0.dsc',
249+ {'url': u'http://launchpad.dev/~cprov/+archive/ubuntu/ppa/+sourcefiles/iceweasel/1.0/iceweasel-1.0.dsc',
250 'class': 'source',
251 'filesize': 123,
252 'filename': u'iceweasel-1.0.dsc'},
253
254=== modified file 'lib/lp/soyuz/browser/tests/test_publishing_webservice.py'
255--- lib/lp/soyuz/browser/tests/test_publishing_webservice.py 2018-02-01 18:44:21 +0000
256+++ lib/lp/soyuz/browser/tests/test_publishing_webservice.py 2018-05-08 18:13:25 +0000
257@@ -9,6 +9,7 @@
258
259 from lp.services.librarian.browser import ProxiedLibraryFileAlias
260 from lp.services.webapp.interfaces import OAuthPermission
261+from lp.soyuz.adapters.proxiedsourcefiles import ProxiedSourceLibraryFileAlias
262 from lp.testing import (
263 api_url,
264 login_person,
265@@ -47,8 +48,7 @@
266 with person_logged_in(person):
267 sprf = spph.sourcepackagerelease.files[0]
268 expected_urls = [
269- ProxiedLibraryFileAlias(
270- sprf.libraryfile, spph.archive).http_url]
271+ ProxiedSourceLibraryFileAlias(sprf.libraryfile, spph).http_url]
272 self.assertEqual(expected_urls, urls)
273
274 def test_sourceFileUrls_include_meta(self):
275@@ -75,8 +75,8 @@
276 info = response.jsonBody()
277 with person_logged_in(person):
278 expected_info = [{
279- "url": ProxiedLibraryFileAlias(
280- sprf.libraryfile, spph.archive).http_url,
281+ "url": ProxiedSourceLibraryFileAlias(
282+ sprf.libraryfile, spph).http_url,
283 "size": sprf.libraryfile.content.filesize,
284 "sha256": sprf.libraryfile.content.sha256,
285 } for sprf in spph.sourcepackagerelease.files]
286
287=== modified file 'lib/lp/soyuz/interfaces/archive.py'
288--- lib/lp/soyuz/interfaces/archive.py 2017-09-28 14:06:20 +0000
289+++ lib/lp/soyuz/interfaces/archive.py 2018-05-08 18:13:25 +0000
290@@ -1,4 +1,4 @@
291-# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
292+# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
293 # GNU Affero General Public License version 3 (see the file LICENSE).
294
295 """Archive interfaces."""
296@@ -936,6 +936,21 @@
297 :return the corresponding `ILibraryFileAlias` is the file was found.
298 """
299
300+ def getSourceFileByName(name, version, filename):
301+ """Return the `ILibraryFileAlias` for a source name/version/filename.
302+
303+ This can be used to avoid ambiguities with `getFileByName` in
304+ imported archives, where the upstream archive software may not
305+ always have had robust historical filename uniqueness checks.
306+
307+ :param name: The name of the source package.
308+ :param version: The version of the source package.
309+ :param filename: The exact filename to look up.
310+
311+ :raises NotFoundError: if no matching file could be found.
312+ :return: the corresponding `ILibraryFileAlias`.
313+ """
314+
315 def getBinaryPackageRelease(name, version, archtag):
316 """Find the specified `IBinaryPackageRelease` in the archive.
317
318
319=== modified file 'lib/lp/soyuz/model/archive.py'
320--- lib/lp/soyuz/model/archive.py 2018-01-30 16:19:15 +0000
321+++ lib/lp/soyuz/model/archive.py 2018-05-08 18:13:25 +0000
322@@ -1,4 +1,4 @@
323-# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
324+# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
325 # GNU Affero General Public License version 3 (see the file LICENSE).
326
327 """Database class for table Archive."""
328@@ -1688,6 +1688,29 @@
329
330 return archive_file
331
332+ def getSourceFileByName(self, name, version, filename):
333+ """See `IArchive`."""
334+ result = IStore(LibraryFileAlias).find(
335+ LibraryFileAlias,
336+ SourcePackagePublishingHistory.archive == self,
337+ SourcePackagePublishingHistory.sourcepackagereleaseID ==
338+ SourcePackageRelease.id,
339+ SourcePackageRelease.sourcepackagename == SourcePackageName.id,
340+ SourcePackageName.name == name,
341+ SourcePackageRelease.version == version,
342+ SourcePackageRelease.id ==
343+ SourcePackageReleaseFile.sourcepackagereleaseID,
344+ SourcePackageReleaseFile.libraryfileID == LibraryFileAlias.id,
345+ LibraryFileAlias.filename == filename,
346+ LibraryFileAlias.content != None)
347+ result = result.config(distinct=True).order_by(LibraryFileAlias.id)
348+ # Unlike `getFileByName`, we are guaranteed at most one match even
349+ # for files in imported archives.
350+ archive_file = result.one()
351+ if archive_file is None:
352+ raise NotFoundError(filename)
353+ return archive_file
354+
355 def getBinaryPackageRelease(self, name, version, archtag):
356 """See `IArchive`."""
357 from lp.soyuz.model.distroarchseries import DistroArchSeries
358
359=== modified file 'lib/lp/soyuz/model/publishing.py'
360--- lib/lp/soyuz/model/publishing.py 2017-06-02 21:46:50 +0000
361+++ lib/lp/soyuz/model/publishing.py 2018-05-08 18:13:25 +0000
362@@ -1,4 +1,4 @@
363-# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
364+# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
365 # GNU Affero General Public License version 3 (see the file LICENSE).
366
367 __metaclass__ = type
368@@ -76,6 +76,7 @@
369 ScriptRequest,
370 )
371 from lp.services.worlddata.model.country import Country
372+from lp.soyuz.adapters.proxiedsourcefiles import ProxiedSourceLibraryFileAlias
373 from lp.soyuz.enums import (
374 BinaryPackageFormat,
375 PackagePublishingPriority,
376@@ -142,6 +143,12 @@
377 return [ProxiedLibraryFileAlias(file, parent).http_url for file in files]
378
379
380+def proxied_source_urls(files, parent):
381+ """Return the files passed through `ProxiedSourceLibraryFileAlias`."""
382+ return [
383+ ProxiedSourceLibraryFileAlias(file, parent).http_url for file in files]
384+
385+
386 class ArchivePublisherBase:
387 """Base class for `IArchivePublisher`."""
388
389@@ -548,8 +555,8 @@
390 SourcePackageReleaseFile.sourcepackagerelease ==
391 SourcePackageRelease.id,
392 SourcePackageRelease.id == self.sourcepackagereleaseID)
393- source_urls = proxied_urls(
394- [source for source, _ in sources], self.archive)
395+ source_urls = proxied_source_urls(
396+ [source for source, _ in sources], self)
397 if include_meta:
398 meta = [
399 (content.filesize, content.sha256) for _, content in sources]
400
401=== modified file 'lib/lp/soyuz/stories/ppa/xx-ppa-files.txt'
402--- lib/lp/soyuz/stories/ppa/xx-ppa-files.txt 2016-01-26 15:47:37 +0000
403+++ lib/lp/soyuz/stories/ppa/xx-ppa-files.txt 2018-05-08 18:13:25 +0000
404@@ -90,18 +90,19 @@
405
406 >>> ppa_links = [
407 ... ('(changes file)',
408- ... another_test_source.sourcepackagerelease.upload_changesfile),
409+ ... another_test_source.sourcepackagerelease.upload_changesfile,
410+ ... None, None),
411 ... ]
412
413 >>> ppa_1_0_links = [
414- ... ('test-pkg_1.0.dsc', dsc_file),
415- ... ('test-pkg_1.0.tar.gz', tar_gz),
416- ... ('test-bin_1.0_all.deb', deb_file),
417+ ... ('test-pkg_1.0.dsc', dsc_file, 'test-pkg', '1.0'),
418+ ... ('test-pkg_1.0.tar.gz', tar_gz, 'test-pkg', '1.0'),
419+ ... ('test-bin_1.0_all.deb', deb_file, None, None),
420 ... ]
421
422 >>> ppa_1_1_links = [
423- ... ('test-pkg_1.1.dsc', another_dsc_file),
424- ... ('1.0 to 1.1', package_diff.diff_content),
425+ ... ('test-pkg_1.1.dsc', another_dsc_file, 'test-pkg', '1.1'),
426+ ... ('1.0 to 1.1', package_diff.diff_content, None, None),
427 ... ]
428
429 Links to files accessible via +files/ proxy in the Build page.
430@@ -109,13 +110,14 @@
431 >>> build_id = build.id
432
433 >>> builds_links = [
434- ... ('see the log', build.log),
435+ ... ('see the log', build.log, None, None),
436 ... ]
437
438 >>> build_links = [
439- ... ('test-bin_1.0_i386.changes', build.upload_changesfile),
440- ... ('buildlog', build.log),
441- ... ('uploadlog', build.upload_log),
442+ ... ('test-bin_1.0_i386.changes', build.upload_changesfile,
443+ ... None, None),
444+ ... ('buildlog', build.log, None, None),
445+ ... ('uploadlog', build.upload_log, None, None),
446 ... ]
447
448 >>> logout()
449@@ -124,15 +126,20 @@
450
451 >>> from mechanize import LinkNotFoundError
452 >>> def check_urls(browser, links, base_url):
453- ... for link, libraryfile in links:
454+ ... for link, libraryfile, source_name, source_version in links:
455 ... try:
456 ... found_url = browser.getLink(link).url
457 ... except LinkNotFoundError:
458 ... print '%s: NOT FOUND' % libraryfile.filename
459 ... continue
460 ... found_url = found_url.replace('%7E', '~')
461- ... expected_url = '/'.join(
462- ... (base_url, '+files', libraryfile.filename))
463+ ... if source_name is not None:
464+ ... expected_url = '/'.join(
465+ ... (base_url, '+sourcefiles', source_name,
466+ ... source_version, libraryfile.filename))
467+ ... else:
468+ ... expected_url = '/'.join(
469+ ... (base_url, '+files', libraryfile.filename))
470 ... if found_url == expected_url:
471 ... print '%s: OK' % libraryfile.filename
472 ... else:
473@@ -184,7 +191,7 @@
474 ... 'http://launchpad.dev/~no-priv/+archive/ubuntu/p3a/+packages')
475
476 Source and binary files, in the expandable-row area, are served via
477-the PPA '+files' traversal.
478+the PPA '+sourcefiles' and '+files' traversals.
479
480 >>> expander_id = find_tags_by_class(
481 ... no_priv_browser.contents, 'expander')[1]['id']
482
483=== modified file 'lib/lp/soyuz/stories/ppa/xx-ppa-packages.txt'
484--- lib/lp/soyuz/stories/ppa/xx-ppa-packages.txt 2015-04-09 05:16:37 +0000
485+++ lib/lp/soyuz/stories/ppa/xx-ppa-packages.txt 2018-05-08 18:13:25 +0000
486@@ -383,7 +383,7 @@
487 >>> expander_url = foo_browser.getLink(id=expander_id).url
488 >>> anon_browser.open(expander_url)
489 >>> print anon_browser.getLink("orig").url
490- http://.../+files/foo.orig.tar.gz
491+ http://.../+sourcefiles/.../foo.orig.tar.gz
492
493 The uploader name is linkified to that user's home page:
494
495
496=== modified file 'lib/lp/soyuz/stories/soyuz/xx-distributionsourcepackagerelease-pages.txt'
497--- lib/lp/soyuz/stories/soyuz/xx-distributionsourcepackagerelease-pages.txt 2016-03-30 10:01:57 +0000
498+++ lib/lp/soyuz/stories/soyuz/xx-distributionsourcepackagerelease-pages.txt 2018-05-08 18:13:25 +0000
499@@ -201,7 +201,7 @@
500 View changes file
501
502 >>> print anon_browser.getLink('testing-dspr_1.0.dsc').url
503- http://.../ubuntutest/+archive/primary/+files/testing-dspr_1.0.dsc
504+ http://.../ubuntutest/+archive/primary/+sourcefiles/testing-dspr/1.0/testing-dspr_1.0.dsc
505
506 The 'Downloads' section also lists and link to package diffs when they
507 are available.
508
509=== modified file 'lib/lp/soyuz/stories/soyuz/xx-distroseries-sources.txt'
510--- lib/lp/soyuz/stories/soyuz/xx-distroseries-sources.txt 2016-03-30 10:01:57 +0000
511+++ lib/lp/soyuz/stories/soyuz/xx-distroseries-sources.txt 2018-05-08 18:13:25 +0000
512@@ -90,7 +90,7 @@
513 firefox_0.9.2.orig.tar.gz 9.5 MiB ...
514
515 >>> print browser.getLink("firefox_0.9.2.orig.tar.gz").url
516- http://launchpad.dev/ubuntu/+archive/primary/+files/firefox_0.9.2.orig.tar.gz
517+ http://launchpad.dev/ubuntu/+archive/primary/+sourcefiles/mozilla-firefox/0.9/firefox_0.9.2.orig.tar.gz
518
519 This page also provides links to the binary packages generated by this
520 source in a specfic architecture:
521@@ -285,7 +285,7 @@
522 firefox_0.9.2.orig.tar.gz 9.5 MiB ...
523
524 >>> print browser.getLink("firefox_0.9.2.orig.tar.gz").url
525- http://launchpad.dev/ubuntu/+archive/primary/+files/firefox_0.9.2.orig.tar.gz
526+ http://launchpad.dev/ubuntu/+archive/primary/+sourcefiles/mozilla-firefox/0.9/firefox_0.9.2.orig.tar.gz
527
528 If we go to the same page for alsa-utils, the changelog has text that is
529 linkified.
530@@ -353,11 +353,11 @@
531 commercialpackage_1.0-1.dsc 567 bytes ...
532
533 >>> print browser.getLink("commercialpackage_1.0.orig.tar.gz").url
534- http://launchpad.dev/ubuntu/+archive/partner/+files/commercialpackage_1.0.orig.tar.gz
535+ http://launchpad.dev/ubuntu/+archive/partner/+sourcefiles/commercialpackage/1.0-1/commercialpackage_1.0.orig.tar.gz
536 >>> print browser.getLink("commercialpackage_1.0-1.diff.gz").url
537- http://launchpad.dev/ubuntu/+archive/partner/+files/commercialpackage_1.0-1.diff.gz
538+ http://launchpad.dev/ubuntu/+archive/partner/+sourcefiles/commercialpackage/1.0-1/commercialpackage_1.0-1.diff.gz
539 >>> print browser.getLink("commercialpackage_1.0-1.dsc").url
540- http://launchpad.dev/ubuntu/+archive/partner/+files/commercialpackage_1.0-1.dsc
541+ http://launchpad.dev/ubuntu/+archive/partner/+sourcefiles/commercialpackage/1.0-1/commercialpackage_1.0-1.dsc
542
543 This page also provides links to the binary packages generated by this
544 source in a specfic architecture:
545@@ -433,11 +433,11 @@
546 commercialpackage_1.0-1.dsc 567 bytes ...
547
548 >>> print browser.getLink("commercialpackage_1.0.orig.tar.gz").url
549- http://launchpad.dev/ubuntu/+archive/partner/+files/commercialpackage_1.0.orig.tar.gz
550+ http://launchpad.dev/ubuntu/+archive/partner/+sourcefiles/commercialpackage/1.0-1/commercialpackage_1.0.orig.tar.gz
551 >>> print browser.getLink("commercialpackage_1.0-1.diff.gz").url
552- http://launchpad.dev/ubuntu/+archive/partner/+files/commercialpackage_1.0-1.diff.gz
553+ http://launchpad.dev/ubuntu/+archive/partner/+sourcefiles/commercialpackage/1.0-1/commercialpackage_1.0-1.diff.gz
554 >>> print browser.getLink("commercialpackage_1.0-1.dsc").url
555- http://launchpad.dev/ubuntu/+archive/partner/+files/commercialpackage_1.0-1.dsc
556+ http://launchpad.dev/ubuntu/+archive/partner/+sourcefiles/commercialpackage/1.0-1/commercialpackage_1.0-1.dsc
557
558
559 Tracing copied sources
560
561=== modified file 'lib/lp/soyuz/stories/webservice/xx-source-package-publishing.txt'
562--- lib/lp/soyuz/stories/webservice/xx-source-package-publishing.txt 2016-03-02 15:52:33 +0000
563+++ lib/lp/soyuz/stories/webservice/xx-source-package-publishing.txt 2018-05-08 18:13:25 +0000
564@@ -375,11 +375,11 @@
565 ... source_urls = webservice.named_get(
566 ... pub_link, 'sourceFileUrls').jsonBody()
567 ... print source_urls
568- [u'http://.../~cprov/+archive/ubuntu/ppa/+files/foobar-1.0.dsc']
569- [u'http://.../~cprov/+archive/ubuntu/ppa/+files/firefox_0.9.2.orig.tar.gz',
570- u'http://.../~cprov/+archive/ubuntu/ppa/+files/iceweasel-1.0.dsc']
571+ [u'http://.../~cprov/+archive/ubuntu/ppa/+sourcefiles/cdrkit/1.0/foobar-1.0.dsc']
572+ [u'http://.../~cprov/+archive/ubuntu/ppa/+sourcefiles/iceweasel/1.0/firefox_0.9.2.orig.tar.gz',
573+ u'http://.../~cprov/+archive/ubuntu/ppa/+sourcefiles/iceweasel/1.0/iceweasel-1.0.dsc']
574 []
575- [u'http://.../~cprov/+archive/ubuntu/ppa/+files/testwebservice_666.dsc']
576+ [u'http://.../~cprov/+archive/ubuntu/ppa/+sourcefiles/testwebservice/666/testwebservice_666.dsc']
577
578 binaryFileUrls() is similar:
579
580
581=== modified file 'lib/lp/soyuz/tests/test_archive.py'
582--- lib/lp/soyuz/tests/test_archive.py 2018-02-14 11:13:47 +0000
583+++ lib/lp/soyuz/tests/test_archive.py 2018-05-08 18:13:25 +0000
584@@ -2425,6 +2425,89 @@
585 self.archive.getFileByName(pu.changesfile.filename))
586
587
588+class TestGetSourceFileByName(TestCaseWithFactory):
589+ """Tests for Archive.getSourceFileByName."""
590+
591+ layer = LaunchpadZopelessLayer
592+
593+ def setUp(self):
594+ super(TestGetSourceFileByName, self).setUp()
595+ self.archive = self.factory.makeArchive()
596+
597+ def test_source_file_is_found(self):
598+ # A file from a published source package can be retrieved.
599+ pub = self.factory.makeSourcePackagePublishingHistory(
600+ archive=self.archive)
601+ dsc = self.factory.makeLibraryFileAlias(filename='foo_1.0.dsc')
602+ self.assertRaises(
603+ NotFoundError, self.archive.getSourceFileByName,
604+ pub.source_package_name, pub.source_package_version, dsc.filename)
605+ pub.sourcepackagerelease.addFile(dsc)
606+ self.assertEqual(
607+ dsc, self.archive.getSourceFileByName(
608+ pub.source_package_name, pub.source_package_version,
609+ dsc.filename))
610+
611+ def test_nonexistent_source_file_is_not_found(self):
612+ # Something that looks like a source file but isn't is not
613+ # found.
614+ pub = self.factory.makeSourcePackagePublishingHistory(
615+ archive=self.archive)
616+ self.assertRaises(
617+ NotFoundError, self.archive.getSourceFileByName,
618+ pub.source_package_name, pub.source_package_version,
619+ 'foo_1.0.dsc')
620+
621+ def test_nonexistent_source_package_version_is_not_found(self):
622+ # The source package version must match exactly.
623+ pub = self.factory.makeSourcePackagePublishingHistory(
624+ archive=self.archive)
625+ pub2 = self.factory.makeSourcePackagePublishingHistory(
626+ archive=self.archive, sourcepackagename=pub.source_package_name)
627+ dsc = self.factory.makeLibraryFileAlias(filename='foo_1.0.dsc')
628+ pub2.sourcepackagerelease.addFile(dsc)
629+ self.assertRaises(
630+ NotFoundError, self.archive.getSourceFileByName,
631+ pub.source_package_name, pub.source_package_version,
632+ 'foo_1.0.dsc')
633+
634+ def test_nonexistent_source_package_name_is_not_found(self):
635+ # The source package name must match exactly.
636+ pub = self.factory.makeSourcePackagePublishingHistory(
637+ archive=self.archive)
638+ pub2 = self.factory.makeSourcePackagePublishingHistory(
639+ archive=self.archive)
640+ dsc = self.factory.makeLibraryFileAlias(filename='foo_1.0.dsc')
641+ pub2.sourcepackagerelease.addFile(dsc)
642+ self.assertRaises(
643+ NotFoundError, self.archive.getSourceFileByName,
644+ pub.source_package_name, pub.source_package_version,
645+ 'foo_1.0.dsc')
646+
647+ def test_epoch_stripping_collision(self):
648+ # Even if the archive contains two source packages with identical
649+ # names and versions apart from epochs which have the same filenames
650+ # with different contents (the worst case), getSourceFileByName
651+ # returns the correct files.
652+ pub = self.factory.makeSourcePackagePublishingHistory(
653+ archive=self.archive, version='1.0-1')
654+ dsc = self.factory.makeLibraryFileAlias(filename='foo_1.0.dsc')
655+ pub.sourcepackagerelease.addFile(dsc)
656+ pub2 = self.factory.makeSourcePackagePublishingHistory(
657+ archive=self.archive, sourcepackagename=pub.source_package_name,
658+ version='1:1.0-1')
659+ dsc2 = self.factory.makeLibraryFileAlias(filename='foo_1.0.dsc')
660+ pub2.sourcepackagerelease.addFile(dsc2)
661+ self.assertEqual(
662+ dsc, self.archive.getSourceFileByName(
663+ pub.source_package_name, pub.source_package_version,
664+ dsc.filename))
665+ self.assertEqual(
666+ dsc2, self.archive.getSourceFileByName(
667+ pub2.source_package_name, pub2.source_package_version,
668+ dsc2.filename))
669+
670+
671 class TestGetPublishedSources(TestCaseWithFactory):
672
673 layer = DatabaseFunctionalLayer
674
675=== modified file 'lib/lp/soyuz/tests/test_publishing_models.py'
676--- lib/lp/soyuz/tests/test_publishing_models.py 2018-02-02 03:14:35 +0000
677+++ lib/lp/soyuz/tests/test_publishing_models.py 2018-05-08 18:13:25 +0000
678@@ -14,6 +14,7 @@
679 from lp.services.database.constants import UTC_NOW
680 from lp.services.librarian.browser import ProxiedLibraryFileAlias
681 from lp.services.webapp.publisher import canonical_url
682+from lp.soyuz.adapters.proxiedsourcefiles import ProxiedSourceLibraryFileAlias
683 from lp.soyuz.enums import (
684 BinaryPackageFileType,
685 BinaryPackageFormat,
686@@ -159,8 +160,7 @@
687
688 def getURLsForSPPH(self, spph, include_meta=False):
689 spr = spph.sourcepackagerelease
690- archive = spph.archive
691- urls = [ProxiedLibraryFileAlias(f.libraryfile, archive).http_url
692+ urls = [ProxiedSourceLibraryFileAlias(f.libraryfile, spph).http_url
693 for f in spr.files]
694
695 if include_meta: