1
# Copyright (C) 2009-2010 Canonical
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.
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
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
21
import dbus.mainloop.glib
23
from gi.repository import GObject
24
from gi.repository import PackageKitGlib as packagekit
26
from softwarecenter.enums import TransactionTypes
27
from softwarecenter.backend.transactionswatcher import (BaseTransactionsWatcher,
29
TransactionFinishedResult,
31
from softwarecenter.backend.installbackend import InstallBackend
33
# temporary, must think of better solution
34
from softwarecenter.db.pkginfo import get_pkg_info
36
LOG = logging.getLogger("softwarecenter.backend.packagekit")
38
class PackagekitTransaction(BaseTransaction):
41
def __init__(self, trans):
42
""" trans -- a PkProgress object """
43
GObject.GObject.__init__(self)
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
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')
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)
64
def _emit(self, *args):
65
prop, what = args[-1], args[-2]
66
self.emit(what, self._trans.get_property(prop))
70
return self._trans.get_property('transaction-id')
72
def status_details(self):
73
return self.get_status_description() # FIXME
76
return self._meta_data
78
def cancellable(self):
79
return self._trans.get_property('allow-cancel')
82
return self._trans.get_property('percentage')
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))
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)
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
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)
108
proxy = dbus.SystemBus().get_object('org.freedesktop.PackageKit', self.tid)
109
trans = dbus.Interface(proxy, 'org.freedesktop.PackageKit.Transaction')
113
""" delete transaction from _tlist """
114
# also notify pk install backend, so that this transaction gets removed
115
# from pending_transactions
117
if self.tid in PackagekitTransactionsWatcher._tlist.keys():
118
del PackagekitTransactionsWatcher._tlist[self.tid]
119
LOG.debug("Delete transaction %s" % self.tid)
121
class PackagekitTransactionsWatcher(BaseTransactionsWatcher):
125
super(PackagekitTransactionsWatcher, self).__init__()
126
self.client = packagekit.Client()
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)
136
def _on_transactions_changed(self, queued):
139
queued = queued[1:] if len(queued) > 1 else []
142
self.emit("lowlevel-transactions-changed", current, queued)
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)
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
154
return (PackagekitTransactionsWatcher._tlist[tid], False)
156
def get_transaction(self, tid):
157
if tid not in PackagekitTransactionsWatcher._tlist.keys():
158
trans, new = self.add_transaction(tid, None)
160
return PackagekitTransactionsWatcher._tlist[tid]
162
class PackagekitBackend(GObject.GObject, InstallBackend):
164
__gsignals__ = {'transaction-started':(GObject.SIGNAL_RUN_FIRST,
167
# emits a TransactionFinished object
168
'transaction-finished':(GObject.SIGNAL_RUN_FIRST,
170
(GObject.TYPE_PYOBJECT, )),
171
'transaction-stopped':(GObject.SIGNAL_RUN_FIRST,
173
(GObject.TYPE_PYOBJECT,)),
174
'transactions-changed':(GObject.SIGNAL_RUN_FIRST,
176
(GObject.TYPE_PYOBJECT, )),
177
'transaction-progress-changed':(GObject.SIGNAL_RUN_FIRST,
180
# the number/names of the available channels changed
181
# FIXME: not emitted.
182
'channels-changed':(GObject.SIGNAL_RUN_FIRST,
188
GObject.GObject.__init__(self)
189
InstallBackend.__init__(self)
191
# transaction details for setting as meta
192
self.new_pkgname, self.new_appname, self.new_iconname = '', '', ''
194
# this is public exposed
195
self.pending_transactions = {}
197
self.client = packagekit.Client()
198
self.pkginfo = get_pkg_info()
201
self._transactions_watcher = PackagekitTransactionsWatcher()
202
self._transactions_watcher.connect('lowlevel-transactions-changed',
203
self._on_lowlevel_transactions_changed)
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
214
def remove_multiple(self, pkgnames, appnames, iconnames,
215
addons_install=[], addons_remove=[], metadatas=None):
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]
221
pkgnames = self._fix_pkgnames(pkgnames)
223
self.client.remove_packages_async(pkgnames,
227
self._on_progress_changed,
228
None, # progress data
229
self._on_remove_ready, # callback ready
232
self.emit("transaction-started", pkgnames[0], appnames[0], 0, TransactionTypes.REMOVE)
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
239
self.install_multiple((pkgname,), (appname,), (iconname,),
240
addons_install, addons_remove, metadata
243
def install_multiple(self, pkgnames, appnames, iconnames,
244
addons_install=[], addons_remove=[], metadatas=None):
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]
250
pkgnames = self._fix_pkgnames(pkgnames)
252
self.client.install_packages_async(False, # only trusted
255
self._on_progress_changed,
256
None, # progress data
257
self._on_install_ready, # GAsyncReadyCallback
260
self.emit("transaction-started", pkgnames[0], appnames[0], 0, TransactionTypes.INSTALL)
262
def apply_changes(self, pkgname, appname, iconname,
263
addons_install=[], addons_remove=[], metadata=None):
265
def reload(self, sources_list=None, metadata=None):
266
""" reload package list """
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)
275
LOG.error("Could not delete: " + name + str(trans))
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)
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')
287
LOG.debug("Progress without transaction")
290
trans, new = self._transactions_watcher.add_transaction(tid, progress)
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
302
#LOG.debug("Progress update %s %s %s %s" % (status, ptype, progress.get_property('transaction-id'),progress.get_property('status')))
304
if status == packagekit.StatusEnum.FINISHED:
305
LOG.debug("Transaction finished %s" % tid)
306
self.emit("transaction-finished", TransactionFinishedResult(trans, True))
308
if status == packagekit.StatusEnum.CANCEL:
309
LOG.debug("Transaction canceled %s" % tid)
310
self.emit("transaction-stopped", TransactionFinishedResult(trans, True))
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)
319
if ptype == packagekit.ProgressType.PERCENTAGE:
320
pkgname = trans.meta_data.get('sc_pkgname', '')
321
prog = progress.get_property('percentage')
323
self.emit("transaction-progress-changed", pkgname, prog)
325
self.emit("transaction-progress-changed", pkgname, 0)
327
def _on_lowlevel_transactions_changed(self, watcher, current, pending):
328
# update self.pending_transactions
329
self.pending_transactions.clear()
331
for tid in [current] + pending:
334
trans = self._transactions_watcher.get_transaction(tid)
335
trans_progress = TransactionProgress(trans)
337
self.pending_transactions[trans_progress.pkgname] = trans_progress
339
self.pending_transactions[trans.tid] = trans_progress
341
self.emit('transactions-changed', self.pending_transactions)
343
def _on_install_ready(self, source, result, data=None):
344
LOG.debug("install done %s %s", source, result)
346
def _on_remove_ready(self, source, result, data=None):
347
LOG.debug("remove done %s %s", source, result)
349
def _fix_pkgnames(self, pkgnames):
350
is_pk_id = lambda a: ';' in a
354
version = self.pkginfo[p].candidate.version
355
p = '{name};{version};{arch};{source}'.format(name=p,
356
version=version, arch='', source=''
361
if __name__ == "__main__":
364
loop = dbus.mainloop.glib.DBusGMainLoop()
365
dbus.set_default_main_loop(loop)
367
backend = PackagekitBackend()
368
pkginfo = get_pkg_info()
369
if pkginfo[package].is_installed:
370
backend.remove(package, package, '')
371
backend.install(package, package, '')
373
backend.install(package, package, '')
374
backend.remove(package, package, '')
375
from gi.repository import Gtk
377
#print backend._fix_pkgnames(('cheese',))