~mvo/software-center/qml

« back to all changes in this revision

Viewing changes to softwarecenter/backend/installbackend_impl/packagekitd.py

  • Committer: Michael Vogt
  • Date: 2011-10-05 13:08:09 UTC
  • mfrom: (1887.1.603 software-center)
  • Revision ID: michael.vogt@ubuntu.com-20111005130809-0tin9nr00f0uw65b
mergedĀ fromĀ trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2009-2010 Canonical
 
2
#
 
3
# Authors:
 
4
#  Alex Eftimie
 
5
#
 
6
# This program is free software; you can redistribute it and/or modify it under
 
7
# the terms of the GNU General Public License as published by the Free Software
 
8
# Foundation; version 3.
 
9
#
 
10
# This program is distributed in the hope that it will be useful, but WITHOUT
 
11
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 
12
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 
13
# details.
 
14
#
 
15
# You should have received a copy of the GNU General Public License along with
 
16
# this program; if not, write to the Free Software Foundation, Inc.,
 
17
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
18
 
 
19
import logging
 
20
import dbus
 
21
import dbus.mainloop.glib
 
22
 
 
23
from gi.repository import GObject
 
24
from gi.repository import PackageKitGlib as packagekit
 
25
 
 
26
from softwarecenter.enums import TransactionTypes
 
27
from softwarecenter.backend.transactionswatcher import (BaseTransactionsWatcher,
 
28
                                                        BaseTransaction,
 
29
                                                        TransactionFinishedResult,
 
30
                                                        TransactionProgress)
 
31
from softwarecenter.backend.installbackend import InstallBackend
 
32
 
 
33
# temporary, must think of better solution
 
34
from softwarecenter.db.pkginfo import get_pkg_info
 
35
 
 
36
LOG = logging.getLogger("softwarecenter.backend.packagekit")
 
37
 
 
38
class PackagekitTransaction(BaseTransaction):
 
39
    _meta_data = {}
 
40
    
 
41
    def __init__(self, trans):
 
42
        """ trans -- a PkProgress object """
 
43
        GObject.GObject.__init__(self)
 
44
        self._trans = trans
 
45
        self._setup_signals()
 
46
 
 
47
    def _setup_signals(self):
 
48
        """ Connect signals to the PkProgress from libpackagekitlib,
 
49
        because PK DBus exposes only a generic Changed, without
 
50
        specifying the property changed
 
51
        """
 
52
        self._trans.connect('notify::role', self._emit, 'role-changed', 'role')
 
53
        self._trans.connect('notify::status', self._emit, 'status-changed', 'status')
 
54
        self._trans.connect('notify::percentage', self._emit, 'progress-changed', 'percentage')
 
55
        #self._trans.connect('notify::subpercentage', self._emit, 'progress-changed', 'subpercentage') # SC UI does not support subprogress
 
56
        self._trans.connect('notify::percentage', self._emit, 'progress-changed', 'percentage')
 
57
        self._trans.connect('notify::allow-cancel', self._emit, 'cancellable-changed', 'allow-cancel')
 
58
 
 
59
        # connect the delete:
 
60
        proxy = dbus.SystemBus().get_object('org.freedesktop.PackageKit', self.tid)
 
61
        trans = dbus.Interface(proxy, 'org.freedesktop.PackageKit.Transaction')
 
62
        trans.connect_to_signal("Destroy", self._remove)
 
63
        
 
64
    def _emit(self, *args):
 
65
        prop, what = args[-1], args[-2]
 
66
        self.emit(what, self._trans.get_property(prop))
 
67
 
 
68
    @property
 
69
    def tid(self):
 
70
        return self._trans.get_property('transaction-id')
 
71
    @property
 
72
    def status_details(self):
 
73
        return self.get_status_description() # FIXME
 
74
    @property
 
75
    def meta_data(self):
 
76
        return self._meta_data
 
77
    @property
 
78
    def cancellable(self):
 
79
        return self._trans.get_property('allow-cancel')
 
80
    @property
 
81
    def progress(self):
 
82
        return self._trans.get_property('percentage')
 
83
 
 
84
    def get_role_description(self, role=None):
 
85
        role = role if role is not None else self._trans.get_property('role')
 
86
        return self.meta_data.get('sc_appname', packagekit.role_enum_to_string(role))
 
87
 
 
88
    def get_status_description(self, status=None):
 
89
        status = status if status is not None else self._trans.get_property('status')
 
90
        return packagekit.status_enum_to_string(status)
 
91
 
 
92
    def is_waiting(self):
 
93
        """ return true if a time consuming task is taking place """
 
94
        #LOG.debug('is_waiting ' + str(self._trans.get_property('status')))
 
95
        status = self._trans.get_property('status')
 
96
        return status == packagekit.StatusEnum.WAIT or \
 
97
               status == packagekit.StatusEnum.LOADING_CACHE or \
 
98
               status == packagekit.StatusEnum.SETUP
 
99
 
 
100
    def is_downloading(self):
 
101
        #LOG.debug('is_downloading ' + str(self._trans.get_property('status')))
 
102
        status = self._trans.get_property('status')
 
103
        return status == packagekit.StatusEnum.DOWNLOAD or \
 
104
               (status >= packagekit.StatusEnum.DOWNLOAD_REPOSITORY and \
 
105
               status <= packagekit.StatusEnum.DOWNLOAD_UPDATEINFO)
 
106
 
 
107
    def cancel(self):
 
108
        proxy = dbus.SystemBus().get_object('org.freedesktop.PackageKit', self.tid)
 
109
        trans = dbus.Interface(proxy, 'org.freedesktop.PackageKit.Transaction')
 
110
        trans.Cancel()
 
111
 
 
112
    def _remove(self):
 
113
        """ delete transaction from _tlist """
 
114
        # also notify pk install backend, so that this transaction gets removed
 
115
        # from pending_transactions
 
116
        self.emit('deleted')
 
117
        if self.tid in PackagekitTransactionsWatcher._tlist.keys():
 
118
            del PackagekitTransactionsWatcher._tlist[self.tid]
 
119
            LOG.debug("Delete transaction %s" % self.tid)
 
120
 
 
121
class PackagekitTransactionsWatcher(BaseTransactionsWatcher):
 
122
    _tlist = {}
 
123
 
 
124
    def __init__(self):
 
125
        super(PackagekitTransactionsWatcher, self).__init__()
 
126
        self.client = packagekit.Client()
 
127
 
 
128
        bus = dbus.SystemBus()
 
129
        proxy = bus.get_object('org.freedesktop.PackageKit', '/org/freedesktop/PackageKit')
 
130
        daemon = dbus.Interface(proxy, 'org.freedesktop.PackageKit')
 
131
        daemon.connect_to_signal("TransactionListChanged", 
 
132
                                     self._on_transactions_changed)
 
133
        queued = daemon.GetTransactionList()
 
134
        self._on_transactions_changed(queued)
 
135
 
 
136
    def _on_transactions_changed(self, queued):
 
137
        if len(queued) > 0:
 
138
            current = queued[0]
 
139
            queued = queued[1:] if len(queued) > 1 else []
 
140
        else:
 
141
            current = None
 
142
        self.emit("lowlevel-transactions-changed", current, queued)
 
143
 
 
144
    def add_transaction(self, tid, trans):
 
145
        """ return a tuple, (transaction, is_new) """
 
146
        if tid not in PackagekitTransactionsWatcher._tlist.keys():
 
147
            LOG.debug("Trying to setup %s" % tid)
 
148
            if not trans:
 
149
                trans = self.client.get_progress(tid, None)
 
150
            trans = PackagekitTransaction(trans)
 
151
            LOG.debug("Add return new transaction %s %s" % (tid, trans))
 
152
            PackagekitTransactionsWatcher._tlist[tid] = trans
 
153
            return (trans, True)
 
154
        return (PackagekitTransactionsWatcher._tlist[tid], False)
 
155
 
 
156
    def get_transaction(self, tid):
 
157
        if tid not in PackagekitTransactionsWatcher._tlist.keys():
 
158
            trans, new = self.add_transaction(tid, None)
 
159
            return trans
 
160
        return PackagekitTransactionsWatcher._tlist[tid]
 
161
 
 
162
class PackagekitBackend(GObject.GObject, InstallBackend):
 
163
    
 
164
    __gsignals__ = {'transaction-started':(GObject.SIGNAL_RUN_FIRST,
 
165
                                            GObject.TYPE_NONE,
 
166
                                            (str,str,str,str)),
 
167
                    # emits a TransactionFinished object
 
168
                    'transaction-finished':(GObject.SIGNAL_RUN_FIRST,
 
169
                                            GObject.TYPE_NONE,
 
170
                                            (GObject.TYPE_PYOBJECT, )),
 
171
                    'transaction-stopped':(GObject.SIGNAL_RUN_FIRST,
 
172
                                            GObject.TYPE_NONE,
 
173
                                            (GObject.TYPE_PYOBJECT,)),
 
174
                    'transactions-changed':(GObject.SIGNAL_RUN_FIRST,
 
175
                                            GObject.TYPE_NONE,
 
176
                                            (GObject.TYPE_PYOBJECT, )),
 
177
                    'transaction-progress-changed':(GObject.SIGNAL_RUN_FIRST,
 
178
                                                    GObject.TYPE_NONE,
 
179
                                                    (str,int,)),
 
180
                    # the number/names of the available channels changed
 
181
                    # FIXME: not emitted.
 
182
                    'channels-changed':(GObject.SIGNAL_RUN_FIRST,
 
183
                                        GObject.TYPE_NONE,
 
184
                                        (bool,)),
 
185
                    }
 
186
 
 
187
    def __init__(self):
 
188
        GObject.GObject.__init__(self)
 
189
        InstallBackend.__init__(self)
 
190
 
 
191
        # transaction details for setting as meta
 
192
        self.new_pkgname, self.new_appname, self.new_iconname = '', '', ''
 
193
        
 
194
        # this is public exposed
 
195
        self.pending_transactions = {}
 
196
 
 
197
        self.client = packagekit.Client()
 
198
        self.pkginfo = get_pkg_info()
 
199
        self.pkginfo.open()
 
200
 
 
201
        self._transactions_watcher = PackagekitTransactionsWatcher()
 
202
        self._transactions_watcher.connect('lowlevel-transactions-changed',
 
203
                                self._on_lowlevel_transactions_changed)        
 
204
 
 
205
    def upgrade(self, pkgname, appname, iconname, addons_install=[],
 
206
                addons_remove=[], metadata=None):
 
207
        pass # FIXME implement it
 
208
    def remove(self, pkgname, appname, iconname, addons_install=[],
 
209
                addons_remove=[], metadata=None):
 
210
        self.remove_multiple((pkgname,), (appname,), (iconname,),
 
211
                addons_install, addons_remove, metadata
 
212
        )
 
213
 
 
214
    def remove_multiple(self, pkgnames, appnames, iconnames,
 
215
                addons_install=[], addons_remove=[], metadatas=None):
 
216
 
 
217
        # keep track of pkg, app and icon for setting them as meta
 
218
        self.new_pkgname, self.new_appname, self.new_iconname = pkgnames[0], appnames[0], iconnames[0]
 
219
 
 
220
        # temporary hack
 
221
        pkgnames = self._fix_pkgnames(pkgnames)
 
222
 
 
223
        self.client.remove_packages_async(pkgnames,
 
224
                    False, # allow deps
 
225
                    False, # autoremove
 
226
                    None, # cancellable
 
227
                    self._on_progress_changed,
 
228
                    None, # progress data
 
229
                    self._on_remove_ready, # callback ready
 
230
                    None # callback data
 
231
        )
 
232
        self.emit("transaction-started", pkgnames[0], appnames[0], 0, TransactionTypes.REMOVE)
 
233
 
 
234
    def install(self, pkgname, appname, iconname, filename=None,
 
235
                addons_install=[], addons_remove=[], metadata=None):
 
236
        if filename is not None:
 
237
            LOG.error("Filename not implemented") # FIXME
 
238
        else:
 
239
            self.install_multiple((pkgname,), (appname,), (iconname,),
 
240
                 addons_install, addons_remove, metadata
 
241
            )
 
242
 
 
243
    def install_multiple(self, pkgnames, appnames, iconnames,
 
244
        addons_install=[], addons_remove=[], metadatas=None):
 
245
 
 
246
        # keep track of pkg, app and icon for setting them as meta
 
247
        self.new_pkgname, self.new_appname, self.new_iconname = pkgnames[0], appnames[0], iconnames[0]
 
248
 
 
249
        # temporary hack
 
250
        pkgnames = self._fix_pkgnames(pkgnames)
 
251
 
 
252
        self.client.install_packages_async(False, # only trusted
 
253
                    pkgnames,
 
254
                    None, # cancellable
 
255
                    self._on_progress_changed,
 
256
                    None, # progress data
 
257
                    self._on_install_ready, # GAsyncReadyCallback
 
258
                    None  # ready data
 
259
        )
 
260
        self.emit("transaction-started", pkgnames[0], appnames[0], 0, TransactionTypes.INSTALL)
 
261
 
 
262
    def apply_changes(self, pkgname, appname, iconname,
 
263
        addons_install=[], addons_remove=[], metadata=None):
 
264
        pass
 
265
    def reload(self, sources_list=None, metadata=None):
 
266
        """ reload package list """
 
267
        pass
 
268
 
 
269
    def _on_transaction_deleted(self, trans):
 
270
        name = trans.meta_data.get('sc_pkgname', '')
 
271
        if name in self.pending_transactions:
 
272
            del self.pending_transactions[name]
 
273
            LOG.debug("Deleted transaction " + name)
 
274
        else:
 
275
            LOG.error("Could not delete: " + name + str(trans))
 
276
        # this is needed too
 
277
        self.emit('transactions-changed', self.pending_transactions)
 
278
        # also hack PackagekitInfo cache so that it emits a cache-ready signal
 
279
        if hasattr(self.pkginfo, '_reset_cache'):
 
280
            self.pkginfo._reset_cache(name)
 
281
 
 
282
    def _on_progress_changed(self, progress, ptype, data=None):
 
283
        """ de facto callback on transaction's progress change """
 
284
        tid = progress.get_property('transaction-id')
 
285
        status = progress.get_property('status')
 
286
        if not tid:
 
287
            LOG.debug("Progress without transaction")
 
288
            return
 
289
 
 
290
        trans, new = self._transactions_watcher.add_transaction(tid, progress)
 
291
        if new:
 
292
            trans.connect('deleted', self._on_transaction_deleted)
 
293
            LOG.debug("new transaction" + str(trans))
 
294
            # should add it to pending_transactions, but
 
295
            # i cannot get the pkgname here
 
296
            trans.meta_data['sc_appname'] = self.new_appname
 
297
            trans.meta_data['sc_pkgname'] = self.new_pkgname
 
298
            trans.meta_data['sc_iconname'] = self.new_iconname
 
299
            if self.new_pkgname not in self.pending_transactions:
 
300
                self.pending_transactions[self.new_pkgname] = trans
 
301
 
 
302
        #LOG.debug("Progress update %s %s %s %s" % (status, ptype, progress.get_property('transaction-id'),progress.get_property('status')))
 
303
 
 
304
        if status == packagekit.StatusEnum.FINISHED:
 
305
            LOG.debug("Transaction finished %s" % tid)
 
306
            self.emit("transaction-finished", TransactionFinishedResult(trans, True))
 
307
 
 
308
        if status == packagekit.StatusEnum.CANCEL:
 
309
            LOG.debug("Transaction canceled %s" % tid)
 
310
            self.emit("transaction-stopped", TransactionFinishedResult(trans, True))
 
311
 
 
312
        if ptype == packagekit.ProgressType.PACKAGE:
 
313
            # this should be done better
 
314
            # mvo: why getting package here at all?
 
315
            #package = progress.get_property('package')
 
316
            # fool sc ui about the name change
 
317
            trans.emit('role-changed', packagekit.RoleEnum.LAST)
 
318
 
 
319
        if ptype == packagekit.ProgressType.PERCENTAGE:
 
320
            pkgname = trans.meta_data.get('sc_pkgname', '')
 
321
            prog = progress.get_property('percentage')
 
322
            if prog >= 0:
 
323
                self.emit("transaction-progress-changed", pkgname, prog)
 
324
            else:
 
325
                self.emit("transaction-progress-changed", pkgname, 0)
 
326
 
 
327
    def _on_lowlevel_transactions_changed(self, watcher, current, pending):
 
328
        # update self.pending_transactions
 
329
        self.pending_transactions.clear()
 
330
 
 
331
        for tid in [current] + pending:
 
332
            if not tid:
 
333
                continue
 
334
            trans = self._transactions_watcher.get_transaction(tid)
 
335
            trans_progress = TransactionProgress(trans)
 
336
            try:
 
337
                self.pending_transactions[trans_progress.pkgname] = trans_progress
 
338
            except:
 
339
                self.pending_transactions[trans.tid] = trans_progress
 
340
 
 
341
        self.emit('transactions-changed', self.pending_transactions)
 
342
 
 
343
    def _on_install_ready(self, source, result, data=None):
 
344
        LOG.debug("install done %s %s", source, result)
 
345
 
 
346
    def _on_remove_ready(self, source, result, data=None):
 
347
        LOG.debug("remove done %s %s", source, result)
 
348
 
 
349
    def _fix_pkgnames(self, pkgnames):
 
350
        is_pk_id = lambda a: ';' in a
 
351
        res = []
 
352
        for p in pkgnames:
 
353
            if not is_pk_id(p):
 
354
                version = self.pkginfo[p].candidate.version
 
355
                p = '{name};{version};{arch};{source}'.format(name=p,
 
356
                            version=version, arch='', source=''
 
357
                )
 
358
            res.append(p)
 
359
        return res
 
360
 
 
361
if __name__ == "__main__":
 
362
    package = 'firefox'
 
363
 
 
364
    loop = dbus.mainloop.glib.DBusGMainLoop()
 
365
    dbus.set_default_main_loop(loop)
 
366
    
 
367
    backend = PackagekitBackend()
 
368
    pkginfo = get_pkg_info()
 
369
    if pkginfo[package].is_installed:
 
370
        backend.remove(package, package, '')
 
371
        backend.install(package, package, '')
 
372
    else:
 
373
        backend.install(package, package, '')
 
374
        backend.remove(package, package, '')
 
375
    from gi.repository import Gtk
 
376
    Gtk.main()
 
377
    #print backend._fix_pkgnames(('cheese',))
 
378