Merge lp:~mvo/click/repository into lp:click/devel

Proposed by Michael Vogt on 2014-10-08
Status: Needs review
Proposed branch: lp:~mvo/click/repository
Merge into: lp:click/devel
Diff against target: 546 lines (+416/-16)
8 files modified
click/commands/__init__.py (+2/-0)
click/commands/info.py (+58/-5)
click/commands/search.py (+46/-0)
click/commands/update.py (+78/-0)
click/install.py (+7/-6)
click/repository.py (+220/-0)
click/tests/test_install.py (+2/-2)
debian/control (+3/-3)
To merge this branch: bzr merge lp:~mvo/click/repository
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve on 2014-11-20
Colin Watson 2014-10-08 Approve on 2014-10-10
Review via email: mp+237604@code.launchpad.net

Commit message

Add support for remote repositories to click.

Description of the change

This branch adds a new "repository" concept for packages coming from a remote location. It also adds "click search", "click update", "click info --remote" to query the repository.

At this point its mostly up for review to get feedback on the approach used and if the CLI interface fits the vision of click. But once the acquire branch lands and my sso branch gets into better shape this will allow managing clicks on a server image from the commandline easily.

Feedback welcome!

To post a comment you must log in.
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:527
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https://code.launchpad.net/~mvo/click/repository/+merge/237604/+edit-commit-message

http://jenkins.qa.ubuntu.com/job/click-devel-ci/93/
Executed test runs:
    FAILURE: http://jenkins.qa.ubuntu.com/job/click-devel-utopic-amd64-ci/95/console
    FAILURE: http://jenkins.qa.ubuntu.com/job/click-devel-utopic-armhf-ci/93/console
    FAILURE: http://jenkins.qa.ubuntu.com/job/click-devel-utopic-i386-ci/93/console

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/click-devel-ci/93/rebuild

review: Needs Fixing (continuous-integration)
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:529
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https://code.launchpad.net/~mvo/click/repository/+merge/237604/+edit-commit-message

http://jenkins.qa.ubuntu.com/job/click-devel-ci/94/
Executed test runs:
    FAILURE: http://jenkins.qa.ubuntu.com/job/click-devel-utopic-amd64-ci/96/console
    FAILURE: http://jenkins.qa.ubuntu.com/job/click-devel-utopic-armhf-ci/94/console
    FAILURE: http://jenkins.qa.ubuntu.com/job/click-devel-utopic-i386-ci/94/console

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/click-devel-ci/94/rebuild

review: Needs Fixing (continuous-integration)
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:530
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https://code.launchpad.net/~mvo/click/repository/+merge/237604/+edit-commit-message

http://jenkins.qa.ubuntu.com/job/click-devel-ci/95/
Executed test runs:
    FAILURE: http://jenkins.qa.ubuntu.com/job/click-devel-utopic-amd64-ci/97/console
    FAILURE: http://jenkins.qa.ubuntu.com/job/click-devel-utopic-armhf-ci/95/console
    FAILURE: http://jenkins.qa.ubuntu.com/job/click-devel-utopic-i386-ci/95/console

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/click-devel-ci/95/rebuild

review: Needs Fixing (continuous-integration)
lp:~mvo/click/repository updated on 2014-10-09
528. By Michael Vogt on 2014-10-08

debian/control: add python3-pycurl

529. By Michael Vogt on 2014-10-08

click/commands/update.py: add missing file

530. By Michael Vogt on 2014-10-08

pep8 fixes

531. By Michael Vogt on 2014-10-09

merge lp:click/devel

PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:531
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https://code.launchpad.net/~mvo/click/repository/+merge/237604/+edit-commit-message

http://jenkins.qa.ubuntu.com/job/click-devel-ci/96/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/click-devel-utopic-amd64-ci/98
    SUCCESS: http://jenkins.qa.ubuntu.com/job/click-devel-utopic-armhf-ci/96
        deb: http://jenkins.qa.ubuntu.com/job/click-devel-utopic-armhf-ci/96/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/click-devel-utopic-i386-ci/96

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/click-devel-ci/96/rebuild

review: Needs Fixing (continuous-integration)
Colin Watson (cjwatson) wrote :

I'm happy with the general direction, yes, thanks. Some specific comments / nitpicking below.

Presumably this could ultimately replace the current app updates handling on the phone as well, perhaps by way of exposing some of this via D-Bus?

review: Approve
lp:~mvo/click/repository updated on 2014-10-13
532. By Michael Vogt on 2014-10-13

use the bulk interface that click-update-manager is using to find available updates

533. By Michael Vogt on 2014-10-13

address review comments by Colin (thanks!)

Michael Vogt (mvo) wrote :

> I'm happy with the general direction, yes, thanks. Some specific comments /
> nitpicking below.
>
> Presumably this could ultimately replace the current app updates handling on
> the phone as well, perhaps by way of exposing some of this via D-Bus?

Thanks a bunch! I replied to your comments inline, the ones I did not reply to are fixed in my branch now.

Michael Vogt (mvo) wrote :

See the inline replies.

lp:~mvo/click/repository updated on 2014-10-13
534. By Michael Vogt on 2014-10-13

click/repository.py: fix crash

lp:~mvo/click/repository updated on 2014-10-14
535. By Michael Vogt on 2014-10-14

add docstrings/cleanup to ClickRepository

lp:~mvo/click/repository updated on 2014-10-14
536. By Michael Vogt on 2014-10-14

click/commands/update.py. use prettytable to generate table output

537. By Michael Vogt on 2014-10-14

click/commands/update.py: add --machine-readable

lp:~mvo/click/repository updated on 2014-10-14
538. By Michael Vogt on 2014-10-14

click/repository.py: add description property to a click repository

539. By Michael Vogt on 2014-10-14

click/commands/info.py: always search local/remote click names in click info

lp:~mvo/click/repository updated on 2014-10-14
540. By Michael Vogt on 2014-10-14

merged from lp:click/devel

lp:~mvo/click/repository updated on 2014-10-21
541. By Michael Vogt on 2014-10-21

click/repository.py: do not hit the network on update if nothing is installed

lp:~mvo/click/repository updated on 2014-10-27
542. By Michael Vogt on 2014-10-27

add new Repository.credentails property

lp:~mvo/click/repository updated on 2014-11-05
543. By Michael Vogt on 2014-11-05

click/commands/update.py: support "click update click1 click2" style invocation

lp:~mvo/click/repository updated on 2014-11-05
544. By Michael Vogt on 2014-11-05

click/commands/update.py: add --user/--all-users

545. By Michael Vogt on 2014-11-05

click/commands/info.py: fix pep8 failure

lp:~mvo/click/repository updated on 2014-11-05
546. By Michael Vogt on 2014-11-05

add missing python3-prettytable build dependency

lp:~mvo/click/repository updated on 2014-11-12
547. By Michael Vogt on 2014-11-12

set the right search headers

lp:~mvo/click/repository updated on 2014-11-12
548. By Michael Vogt on 2014-11-12

fix tests

lp:~mvo/click/repository updated on 2014-11-20
549. By Michael Vogt on 2014-11-20

click/repository.py: use application/hal+json instead of application/json

lp:~mvo/click/repository updated on 2015-02-24
550. By Michael Vogt on 2014-12-05

click/repository.py: the ClickRepositoryAppsUbuntuCom.details() call needs to send framework info in the headers too)

551. By Michael Vogt on 2014-12-05

click/repository.py: fix headers in get_upgradable

552. By Michael Vogt on 2014-12-05

revert r551 - the myapps.developer.u.c API breaks with that

553. By Michael Vogt on 2015-02-23

merged lp:click/devel

554. By Michael Vogt on 2015-02-23

click/commands/info.py: add info -v to address Colins review comment

555. By Michael Vogt on 2015-02-24

click/repository.py: use the bulk uri

556. By Michael Vogt on 2015-02-24

click/repository.py: silly typo

Unmerged revisions

556. By Michael Vogt on 2015-02-24

click/repository.py: silly typo

555. By Michael Vogt on 2015-02-24

click/repository.py: use the bulk uri

554. By Michael Vogt on 2015-02-23

click/commands/info.py: add info -v to address Colins review comment

553. By Michael Vogt on 2015-02-23

merged lp:click/devel

552. By Michael Vogt on 2014-12-05

revert r551 - the myapps.developer.u.c API breaks with that

551. By Michael Vogt on 2014-12-05

click/repository.py: fix headers in get_upgradable

550. By Michael Vogt on 2014-12-05

click/repository.py: the ClickRepositoryAppsUbuntuCom.details() call needs to send framework info in the headers too)

549. By Michael Vogt on 2014-11-20

click/repository.py: use application/hal+json instead of application/json

548. By Michael Vogt on 2014-11-12

fix tests

547. By Michael Vogt on 2014-11-12

set the right search headers

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'click/commands/__init__.py'
2--- click/commands/__init__.py 2014-05-20 09:03:05 +0000
3+++ click/commands/__init__.py 2015-02-24 16:14:00 +0000
4@@ -31,6 +31,8 @@
5 "list",
6 "pkgdir",
7 "register",
8+ "search",
9+ "update",
10 "unregister",
11 "verify",
12 )
13
14=== modified file 'click/commands/info.py'
15--- click/commands/info.py 2014-10-07 08:56:03 +0000
16+++ click/commands/info.py 2015-02-24 16:14:00 +0000
17@@ -28,6 +28,10 @@
18
19 from click.install import DebFile
20 from click.json_helpers import json_object_to_python
21+from click.repository import (
22+ get_repository,
23+ ClickRepositoryError,
24+)
25
26
27 def _load_manifest(manifest_file):
28@@ -73,14 +77,63 @@
29 "--user", metavar="USER",
30 help="look up PACKAGE-NAME for USER (if you have permission; "
31 "default: current user)")
32+ parser.add_option(
33+ "--repository", default=False, action="store_true",
34+ help="Search in remote repository only")
35+ parser.add_option(
36+ "--no-repository", default=False, action="store_true",
37+ help="Never search in a remote repository")
38+ parser.add_option(
39+ "-v", "--verbose", default=False, action="store_true",
40+ help="Show verbose information")
41 options, args = parser.parse_args(argv)
42+ pkgname = args[0]
43 if len(args) < 1:
44 parser.error("need file name")
45- try:
46- manifest = get_manifest(options, args[0])
47- except Exception as e:
48- print(e, file=sys.stderr)
49- return 1
50+
51+ # see if its available locally: pkgname, click-path, path-to-unpacked-click
52+ manifest = None
53+ if not options.repository:
54+ try:
55+ manifest = get_manifest(options, pkgname)
56+ except Exception as e:
57+ pass
58+
59+ # now check remote
60+ if manifest is None and not options.no_repository:
61+ repo = get_repository()
62+ try:
63+ details = repo.details(pkgname)
64+ if options.verbose:
65+ # all information from the store, lots of stuff
66+ manifest = details
67+ else:
68+ # by default show only a subset of all the store information
69+ # by default, it contains a lot of additional info
70+ # like hashes
71+ manifest = {k: details.get(k)
72+ for k in ('name',
73+ 'title',
74+ 'description',
75+ 'binary_filesize',
76+ 'publisher',
77+ 'framework',
78+ 'version',
79+ 'last_updated',
80+ 'architecture',
81+ 'department',
82+ 'changelog',
83+ 'ratings_average',
84+ 'price',
85+ 'keywords',
86+ 'license',
87+ )}
88+ manifest["_repository"] = repo.description
89+ except ClickRepositoryError as e:
90+ print(
91+ "Can not get details for %s:%s" % (pkgname, e), file=sys.stderr)
92+ return 1
93+
94 json.dump(
95 manifest, sys.stdout, ensure_ascii=False, sort_keys=True, indent=4,
96 separators=(",", ": "))
97
98=== added file 'click/commands/search.py'
99--- click/commands/search.py 1970-01-01 00:00:00 +0000
100+++ click/commands/search.py 2015-02-24 16:14:00 +0000
101@@ -0,0 +1,46 @@
102+# Copyright (C) 2014 Canonical Ltd.
103+# Author: Michael Vogt
104+
105+# This program is free software: you can redistribute it and/or modify
106+# it under the terms of the GNU General Public License as published by
107+# the Free Software Foundation; version 3 of the License.
108+#
109+# This program is distributed in the hope that it will be useful,
110+# but WITHOUT ANY WARRANTY; without even the implied warranty of
111+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
112+# GNU General Public License for more details.
113+#
114+# You should have received a copy of the GNU General Public License
115+# along with this program. If not, see <http://www.gnu.org/licenses/>.
116+
117+"""Search for Click packages"""
118+
119+from __future__ import print_function
120+
121+from optparse import OptionParser
122+import sys
123+from textwrap import dedent
124+
125+from click.repository import (
126+ get_repository,
127+ ClickRepositoryError,
128+)
129+
130+
131+def run(argv):
132+ parser = OptionParser(dedent("""\
133+ %prog search search-term(s)
134+
135+ """))
136+ options, args = parser.parse_args(argv)
137+ if len(args) < 1:
138+ parser.error("need package file name")
139+ repo = get_repository()
140+ try:
141+ result = repo.search(args[0])
142+ except ClickRepositoryError as e:
143+ print("Search failed %s" % e, file=sys.stderr)
144+ return 1
145+ for app in result:
146+ print("%s - %s" % (app["name"], app["title"]))
147+ return 0
148
149=== added file 'click/commands/update.py'
150--- click/commands/update.py 1970-01-01 00:00:00 +0000
151+++ click/commands/update.py 2015-02-24 16:14:00 +0000
152@@ -0,0 +1,78 @@
153+# Copyright (C) 2014 Canonical Ltd.
154+# Author: Michael Vogt
155+
156+# This program is free software: you can redistribute it and/or modify
157+# it under the terms of the GNU General Public License as published by
158+# the Free Software Foundation; version 3 of the License.
159+#
160+# This program is distributed in the hope that it will be useful,
161+# but WITHOUT ANY WARRANTY; without even the implied warranty of
162+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
163+# GNU General Public License for more details.
164+#
165+# You should have received a copy of the GNU General Public License
166+# along with this program. If not, see <http://www.gnu.org/licenses/>.
167+
168+"""Update Click packages"""
169+
170+from __future__ import print_function
171+
172+from optparse import OptionParser
173+import sys
174+from textwrap import dedent
175+
176+from prettytable import PrettyTable, PLAIN_COLUMNS
177+
178+from click.repository import (
179+ get_repository,
180+ ClickRepositoryError,
181+)
182+
183+
184+def run(argv):
185+ parser = OptionParser(dedent("""\
186+ %prog update
187+
188+ """))
189+ parser.add_option(
190+ "--list", default=False, action="store_true",
191+ help="Just list the update")
192+ parser.add_option(
193+ "--machine-readable", default=False, action="store_true",
194+ help="output in machine readable form")
195+ parser.add_option(
196+ "--user", metavar="USER", help="register package for USER")
197+ parser.add_option(
198+ "--all-users", default=False, action="store_true",
199+ help="register package for all users")
200+ options, args = parser.parse_args(argv)
201+ repo = get_repository()
202+ try:
203+ all_updates = repo.get_upgradable()
204+ except ClickRepositoryError as e:
205+ print("Update failed: %s" % e, file=sys.stderr)
206+ return 1
207+ if not all_updates:
208+ return 0
209+
210+ if len(args) > 0:
211+ updates = [up for up in all_updates if up[0]["name"] in args]
212+ else:
213+ updates = all_updates
214+
215+ if options.machine_readable:
216+ for (current, new) in updates:
217+ print("%s|%s|%s" % (
218+ new["name"], current["version"], new["version"]))
219+ elif options.list:
220+ t = PrettyTable(["Part","Installed", "Available"])
221+ t.set_style(PLAIN_COLUMNS)
222+ t.align["Part"] = "l"
223+ for (current, new) in updates:
224+ t.add_row((new["name"], current["version"], new["version"]))
225+ print(t)
226+ else:
227+ for (current, new) in updates:
228+ print("downloading %s" % new["download_url"])
229+
230+ return 0
231
232=== modified file 'click/install.py'
233--- click/install.py 2014-12-03 12:42:21 +0000
234+++ click/install.py 2015-02-24 16:14:00 +0000
235@@ -76,6 +76,12 @@
236 apt_pkg.init_system()
237
238
239+def _dpkg_architecture():
240+ return subprocess.check_output(
241+ ["dpkg", "--print-architecture"],
242+ universal_newlines=True).rstrip("\n")
243+
244+
245 class DebsigVerifyError(Exception):
246 pass
247
248@@ -145,11 +151,6 @@
249 return os.path.abspath(preload)
250 return preload_path
251
252- def _dpkg_architecture(self):
253- return subprocess.check_output(
254- ["dpkg", "--print-architecture"],
255- universal_newlines=True).rstrip("\n")
256-
257 def extract(self, path, target):
258 command = ["dpkg-deb", "-R", path, target]
259 with open(path, "rb") as fd:
260@@ -263,7 +264,7 @@
261 if check_arch:
262 architecture = manifest.get("architecture", "all")
263 if architecture != "all":
264- dpkg_architecture = self._dpkg_architecture()
265+ dpkg_architecture = _dpkg_architecture()
266 if isinstance(architecture, list):
267 if dpkg_architecture not in architecture:
268 raise ClickInstallerAuditError(
269
270=== added file 'click/repository.py'
271--- click/repository.py 1970-01-01 00:00:00 +0000
272+++ click/repository.py 2015-02-24 16:14:00 +0000
273@@ -0,0 +1,220 @@
274+# Copyright (C) 2014 Canonical Ltd.
275+# Author: Michael Vogt
276+
277+# This program is free software: you can redistribute it and/or modify
278+# it under the terms of the GNU General Public License as published by
279+# the Free Software Foundation; version 3 of the License.
280+#
281+# This program is distributed in the hope that it will be useful,
282+# but WITHOUT ANY WARRANTY; without even the implied warranty of
283+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
284+# GNU General Public License for more details.
285+#
286+# You should have received a copy of the GNU General Public License
287+# along with this program. If not, see <http://www.gnu.org/licenses/>.
288+
289+"""Search Click packages."""
290+
291+from __future__ import print_function
292+
293+__metaclass__ = type
294+__all__ = [
295+ 'ClickRepository',
296+ 'ClickRepositoryError',
297+ ]
298+
299+from io import BytesIO, StringIO
300+import json
301+import platform
302+import os
303+
304+import apt_pkg
305+from gi.repository import Click
306+import pycurl
307+
308+from six.moves.urllib.parse import quote as url_quote
309+
310+from click.json_helpers import json_array_to_python
311+import click.install
312+
313+class ClickRepositoryError(Exception):
314+ pass
315+
316+
317+class HttpResponse:
318+ """Http Response with status and data members"""
319+
320+ def __init__(self, status, data):
321+ self.status = status
322+ self.data = data
323+
324+ def __str__(self):
325+ return self.data
326+
327+
328+# similar to httplib2.Http().request()
329+def http_request(url, data=None, headers=None):
330+ """Send a http request to the given url and return (response, data)
331+
332+ param url The url to send the request to
333+ param data (Optional) data to POST to the url
334+ param headers (Optional) headers to include with the request
335+ return A tuple of (response, body)
336+ rtype (HttpResponse, bytes)
337+ """
338+ def write_header(line):
339+ header.write(line.decode("utf-8"))
340+ return len(line)
341+ def write_body(data):
342+ body.write(data)
343+ return len(data)
344+ def post_data(num):
345+ return data_stream.read(num)
346+ header = StringIO()
347+ body = BytesIO()
348+ curl = pycurl.Curl()
349+ curl.setopt(curl.URL, url)
350+ if os.environ.get("CLICK_DEBUG_HTTP", ""):
351+ curl.setopt(curl.VERBOSE, 1)
352+ curl.setopt(pycurl.WRITEFUNCTION, write_body)
353+ curl.setopt(pycurl.HEADERFUNCTION, write_header)
354+ if data is not None:
355+ data_stream = BytesIO(data)
356+ curl.setopt(pycurl.READFUNCTION, post_data)
357+ curl.setopt(pycurl.POSTFIELDSIZE, len(data))
358+ curl.setopt(pycurl.POST, 1)
359+ if headers is not None:
360+ header_list = []
361+ for k, v in headers.items():
362+ header_list.append("%s: %s" % (k, v))
363+ curl.setopt(pycurl.HTTPHEADER, header_list)
364+ # ssl: no need to set extra options, pycurl has sensible defaults
365+ curl.perform()
366+ resp = HttpResponse(curl.getinfo(pycurl.HTTP_CODE), header.getvalue())
367+ return resp, body.getvalue()
368+
369+
370+def get_repository():
371+ """Factory to get a click repository for the current distro"""
372+ distro = platform.dist()[0]
373+ if distro.startswith('Ubuntu'):
374+ return ClickRepositoryAppsUbuntuCom()
375+ raise ClickRepositoryError("No repository for distribution '%s'" % distro)
376+
377+
378+class ClickRepository:
379+
380+ def search(self, search_string):
381+ """Search the repository for the given search string
382+
383+ Returns a list of click manifest from the repository
384+ """
385+ raise NotImplementedError()
386+
387+ def get_upgradable(self):
388+ """Get all click packages where a newer version is available
389+
390+ Returns a list of (old_manifest, new_manifest) pairs
391+ """
392+ raise NotImplementedError()
393+
394+ def details(self, name):
395+ """Get the details for the given click name
396+
397+ Returns a click manifest
398+ """
399+ raise NotImplementedError()
400+
401+ @property
402+ def description(self):
403+ """The description of the repository as a URL"""
404+ raise NotImplementedError()
405+
406+ @property
407+ def credentials(self):
408+ """The credentials for the repository (or None)"""
409+ return None
410+
411+
412+def get_store_headers():
413+ frameworks = ["%s-%s" % (f.get_base_name(), f.get_base_version())
414+ for f in Click.Framework.get_frameworks()]
415+ headers = {
416+ "Accept": "application/hal+json",
417+ "X-Ubuntu-Frameworks": ",".join(frameworks),
418+ "X-Ubuntu-Architecture": click.install._dpkg_architecture(),
419+ }
420+ return headers
421+
422+
423+class ClickRepositoryAppsUbuntuCom(ClickRepository):
424+ """Interface to click repository from apps.ubuntu.com"""
425+
426+ SEARCH_URI = "https://search.apps.ubuntu.com/api/v1/search?q=%s"
427+ DETAILS_URI = "https://search.apps.ubuntu.com/api/v1/package/%s"
428+ BULK_APPS_URI = "https://search.apps.ubuntu.com/api/v1/click-metadata"
429+
430+ @property
431+ def credentials(self):
432+ return None
433+
434+ @property
435+ def description(self):
436+ return "https://myapps.developer.ubuntu.com/dev/click-apps/"
437+
438+ def search(self, search_term):
439+ result = []
440+ url = self.SEARCH_URI % url_quote(search_term)
441+ if not self.credentials:
442+ url += ",allow_unauthenticated:True"
443+ resp, raw_content = http_request(url, headers=get_store_headers())
444+ content = raw_content.decode("utf-8")
445+ if resp.status != 200:
446+ raise ClickRepositoryError(content)
447+ data = json.loads(content)
448+ if not "_embedded" in data:
449+ return result
450+ for item in data["_embedded"]["clickindex:package"]:
451+ result.append(item)
452+ return result
453+
454+ def details(self, name):
455+ resp, raw_content = http_request(
456+ self.DETAILS_URI % url_quote(name), headers=get_store_headers())
457+ content = raw_content.decode("utf-8")
458+ if resp.status != 200:
459+ raise ClickRepositoryError(content)
460+ return json.loads(content)
461+
462+ def get_upgradable(self):
463+ result = []
464+ db = Click.DB()
465+ db.read(db_dir=None)
466+ registry = Click.User.for_user(db, name=None)
467+ # build the dict of installed apps for the POST
468+ current_apps = {}
469+ for app in json_array_to_python(registry.get_manifests()):
470+ current_apps[app["name"]] = app
471+ # if we have nothing installed, do not query the server
472+ if not current_apps:
473+ return result
474+ json_post_data = json.dumps({
475+ "name": list(current_apps.keys()),
476+ }).encode("utf-8")
477+ # query the API
478+ resp, raw_content = http_request(
479+ self.BULK_APPS_URI,
480+ json_post_data,
481+ headers={"content-type": "application/json"})
482+ content = raw_content.decode("utf-8")
483+ if resp.status != 200:
484+ raise ClickRepositoryError(
485+ "Failed to get upgradable packages: %s" % resp.content)
486+ # return a list of (old, new) pairs to the caller
487+ server_apps = json.loads(content)
488+ result = []
489+ for new in server_apps:
490+ current = current_apps[new["name"]]
491+ if apt_pkg.version_compare(current["version"], new["version"]) < 0:
492+ result.append( (current, new) )
493+ return result
494
495=== modified file 'click/tests/test_install.py'
496--- click/tests/test_install.py 2014-12-03 12:42:21 +0000
497+++ click/tests/test_install.py 2015-02-24 16:14:00 +0000
498@@ -605,7 +605,7 @@
499 os.path.exists(ClickInstaller(None)._preload_path()),
500 "preload bits not built; installing packages will fail")
501 @mock.patch("gi.repository.Click.package_install_hooks")
502- @mock.patch("click.install.ClickInstaller._dpkg_architecture")
503+ @mock.patch("click.install._dpkg_architecture")
504 def test_single_architecture(self, mock_dpkg_architecture,
505 mock_package_install_hooks):
506 with self.run_in_subprocess(
507@@ -642,7 +642,7 @@
508 os.path.exists(ClickInstaller(None)._preload_path()),
509 "preload bits not built; installing packages will fail")
510 @mock.patch("gi.repository.Click.package_install_hooks")
511- @mock.patch("click.install.ClickInstaller._dpkg_architecture")
512+ @mock.patch("click.install._dpkg_architecture")
513 def test_multiple_architectures(self, mock_dpkg_architecture,
514 mock_package_install_hooks):
515 with self.run_in_subprocess(
516
517=== modified file 'debian/control'
518--- debian/control 2014-10-10 07:10:23 +0000
519+++ debian/control 2015-02-24 16:14:00 +0000
520@@ -3,7 +3,7 @@
521 Priority: optional
522 Maintainer: Colin Watson <cjwatson@ubuntu.com>
523 Standards-Version: 3.9.5
524-Build-Depends: debhelper (>= 9~), dh-autoreconf, intltool, python3:any (>= 3.2), python3-all:any, python3-setuptools, python3-apt, python3-debian, python3-gi, python3:any (>= 3.3) | python3-mock, pep8, python3-pep8, pyflakes, python3-sphinx, pkg-config, valac, gobject-introspection (>= 0.6.7), libgirepository1.0-dev (>= 0.6.7), libglib2.0-dev (>= 2.34), gir1.2-glib-2.0, libjson-glib-dev (>= 0.10), libgee-0.8-dev, libpackagekit-glib2-dev (>= 0.7.2), python3-coverage, python3-six, dh-systemd (>= 1.3)
525+Build-Depends: debhelper (>= 9~), dh-autoreconf, intltool, python3:any (>= 3.2), python3-all:any, python3-setuptools, python3-apt, python3-debian, python3-gi, python3:any (>= 3.3) | python3-mock, pep8, python3-pep8, pyflakes, python3-sphinx, pkg-config, valac, gobject-introspection (>= 0.6.7), libgirepository1.0-dev (>= 0.6.7), libglib2.0-dev (>= 2.34), gir1.2-glib-2.0, libjson-glib-dev (>= 0.10), libgee-0.8-dev, libpackagekit-glib2-dev (>= 0.7.2), python3-coverage, python3-six, dh-systemd (>= 1.3), python3-pycurl, python3-prettytable
526 Vcs-Bzr: https://code.launchpad.net/~ubuntu-managed-branches/click/click
527 Vcs-Browser: http://bazaar.launchpad.net/~ubuntu-managed-branches/click/click/files
528 X-Auto-Uploader: no-rewrite-version
529@@ -14,7 +14,7 @@
530 Package: click
531 Architecture: any
532 Pre-Depends: ${misc:Pre-Depends}
533-Depends: ${shlibs:Depends}, ${misc:Depends}, ${python3:Depends}, python3-click (= ${binary:Version}), adduser
534+Depends: ${shlibs:Depends}, ${misc:Depends}, ${python3:Depends}, python3-click (= ${binary:Version}), adduser, python3-prettytable
535 Recommends: click-apparmor
536 Suggests: click-reviewers-tools (>= 0.9), ubuntu-app-launch-tools | upstart-app-launch-tools
537 Conflicts: click-package
538@@ -40,7 +40,7 @@
539 Package: python3-click
540 Section: python
541 Architecture: any
542-Depends: ${misc:Depends}, ${python3:Depends}, gir1.2-click-0.4 (= ${binary:Version}), gir1.2-glib-2.0, python3-apt, python3-debian, python3-gi
543+Depends: ${misc:Depends}, ${python3:Depends}, gir1.2-click-0.4 (= ${binary:Version}), gir1.2-glib-2.0, python3-apt, python3-debian, python3-gi, python3-pycurl
544 Conflicts: python3-click-package
545 Replaces: python3-click-package
546 Provides: python3-click-package

Subscribers

People subscribed via source and target branches

to all changes: