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