JFIF   ( %!1!%)+...383,7(-.+  ++-+++++-++-++--+--+-+-------+-++-+--+---+++--+7+-+"F!1AQaq"2BRb#3Sr$CDsT&!Q1Aa"2Rbq ?򉄘ǷLR HR,nNb .&W)fJbMOYxj-\bT2(4CQ"qiC/ " %0Jl"e2V  0SDd2@TV^{cW&F͉x9#l,.XɳvRZ C8S 6ml!@!E! `FS!M #(d)Q lml1ml Ų&x(ʨ2NFmj@D<dN5UN˄uTB emLAy#` ` ` I!I 6āHBxL & J#7BQ.$hv h q+tC"EJ) 8R e2U2Y@j%6PF^4LnNBp"8)4JI-ֲvK ^؊)hz[T5˗",Rҥf8ڤS4ʘ!`D ` X+ L,(hl)*S##`6[`0*L T H*HA@I&&r1kr*r*)N$#L  1#ZFSl `[( ("((he`4 Ch [="A R / 0I`twCDcWh"i) cLad\BcLKHZ"ZEW$Ƚ@A~i^`S *A&h:+c Y6vϕGClRPs.`H`(@<$qDe pL@DpLX, E2MP A  `II m& AQ "AT rbg# g2!SiLj*3L \ G;TFL`K BMy 2S`YLh1 d >-"ZfD^Q DH" RAbEV#Lfq,(rETp64-IJ!*p4F$q;G8DQ/TKP2$jp3KW]FtLtƉ1ol]VBgػJH6 )h61GJR7Nj.Z4piJRDd]t]0dP]:N.b'⹙SvDSz]L,_#ugT&[~?cS^"{Bh{/=ۑxOk̳O59o dar793`)SeYM@\ "$E(Tm&)N2Ih)F5EDed(FS,Pa @!@#@lea HCD$11jCLJqcod S3yd*,lL+QEfsgW1nw)cT#dS HXkFJB"6(ʝH)H"#EZh:Y`khݳh%Sc<mlAko2]gDqQtro=3OƸU9_-t8UvW3sGəg*#:c)><"wc\ASmT|6Ę>9~#1Ƈ~ڒE1vVi# I MM#u$8W 5ǍfƬΜg*Qpi1ȩFOf۔S,/⎯(Lrմ`(Z LsbA \6 6dm[I=!r:REI.wgzG)ԇSbӑxuׇTyyL^e'x^ty4Z&eB]I|v59Jjhm;Ng񷫳n<ϞҼѝjk;׹DlY^ҍ\+x9V!j([cmS.NO6jxNζrm&oײizT$N>?~ Sl-:iڥk\at#E!CL`.O0a*w/WV7/r)DŽt7'Nĵ#7O1 ]{[/-2bA<$&Gm_4t)_>)mjG;V^'k59o>ɌM,ؾf9z6 4v_3T.5V/RD-5 %T5XTޫ4TaZ`U *ƱUƲ UG"5+sJJ2E9#܎kr2G3Bb,XM6H: ?@p!'\4V02aԙ) hbZ]:` ev3ʘ'}!ohȒ*TJjr[RFyQ*#{h{R]J]Lr-.D-.җfo$D ?X0%~1P.Og{cWϫ22&Ϭ_V.W3nmiOl}+!˫#`kR33aUb0-g:qmsέ+0HO|&nhOn+}n5QF_"gvLm/z'+r'n_oC语i|1}Gi|}_D~9JZ_%DVQp\koۅjAs~/c0ksUJi^W9W5!>?O:q|ˣSIB/&K<(lg(%Wg$|LW7vߤW߇q|jef3D H\S6(eJb*@&sTKTW/*@v:.N- @ITʓ1Zg&-eꓝM r]EMס{q$b]'7Z7N:O~lNlP7iͲk)$O^퉢<YSD*hr'Z#5e6t[Fdh AJǔP9P 1\R).Il+jI*,(ܢ22N*OwKFX gc?\mB7iA+εe8 "ġ/p5pW-$މ-[a 5ViAW/V{/&UsF./՞ҕ*)rZg.^_+gt_z-oAbqQn*WlHyZ*\TaEewlLR3ԹȭN}MM}aih"5ܕRT$:~'TcT|*)xGC>n+r{XU xuF"<~67у'fxlf`r3D*#Z1ђfH`2dIWo/qB| 63xxW6^m%Kvg>\>x>!H5Nr8J/FJ9Wx(Hou" S'kWاC\9ְ#^OaҮ+~gnkuЉ,aWU*1 읍jnb|e= :2.UL`Q}YS&gI.c=a`%j:C%2@^>])25/ܙ<lzwɛ)ݣS4h3=J tyϬ.E7 8ڞGZu\_JHsݢϑ}IZ"ӳ=X<Ɖ2{a:{7L+>V}c)*lo Yv&+|L;>+/Sj26K+澡*;>-s"}M2] Ig5aCL*r"&\} #^R.7_Mgf}.ߌy(}Z\gP&ʠHj%</{.]rߙQ`>;5g;u6dԛ %xb|oՋTJ5Ϥ(]XqP>f{Jk2,8'~ZU6tMQsg XKg^2ϓ3},[wo۴I|ܷ%[Ol\Pkr]Y//cg6U⧻/VПi8ys_n<\~cze!!H~x;QJZKȮ^ȧG|cS~8ji,Fo+,y~?pk)u /in3JmkX(Mj1N 4c Epc>BO *LfQO&` c;LjcYf 1ɻ)CLsY^Y5" lP/wuEln&dav,(;'W9ej ku`-KHI՟%ԁʁ 1\}?OjsF^Xn$Ё.օC>D:?I @aGE.ĩ1 $ et~T`߸Ir'RX.Zwc%~U=r>-UaFbǺ?R=Z?i'[ASS;siJrzy>nxu$[_B\4}:r'ҵj1_v-[;y?ֹ0I16 . M%4^!S&t ! h !zQð.bBT ?@]?CHq(rd!.$>/x+bnʎNN#w)` )*f!-ɂ\(طYLHzc`Uq7BfCcE0ԉ4Fم쏠ce5T r͸GVlФ?ѣ} mhrkly.Ts㷖)Mө S^%'g>wk%bP[}j~ǾV#K -Fgv켼ǨgɼeSz/6{M=BPZFu\Q75n3Iݤ.W9QfF{vJwF't[@iVj4G~KOnH߿_Do=.c.One?E+GfGN⧭H?4;u`ua|V-+j4?48n ɦ=-]puv&Jc}K>b%U x8pz6L8AXFsW]N55ҦbIWZQ7ï Ԗ3cjz匩ӺOTɖƴ%a'MI}cdR$ݚIζ̝ LIu>J3{^෠㜦˯xܿe\b"2y'x{ RDW b+o2KFhR0:U늞En>լRӉt Iڹ\ wշQEv"v;EJ)yl[5:F0=b4,\PqKtv4{bQz:>C7"8W#Zjdd| cjz%K %Z 9dD{=NFʳAƩtI)kS*s$`:A\ʬ*ֹ9{Nl|eJ١rQnM%z_#x_•TO><)kyD %GN<~y>vfǧB)F)c\lې(#\ h`fgfjTBdhhHL2Y0^ Y0^-"D!QaI15 m~ gՒd|;#gMn(P$l H.R2^PU")pN` N8󫅂OJ;^jz\uumJMF|ηq[]$Vrrt:Q^;QPkHՠ{]HwˆMuIr7!r&- j%"9LtUb56+^TWBqdhHAD7 HwKH^F3LIq #hK`]IWKiH?کǴeԥQ>g{^q^>HKoOB||8aݏS}{S_]ϸ/X~ܵw'OSPAf֩ܟ[>7 @[ֵ;G߇QU*Cթ *OKU^zz[fRnpcJX9u<iq8B]u8 ]I,;[G#2W.¸D8rPG Y%PBJ= wo;PJgx6;yB`3zZGPAͫy{5Nb_re*ONHR]Ji)U{Ӓ:qqɏ[mB4࢒I$ 2vpBADY`DIVAn"Bh$&&cMbdB 鮆wHR'E(ѸZA*H~{B M҅n\@N{7ISCp Vd( r+bg|ns:qg:|J|ɪV.UVaAS͓FyRuLѦT騬 `3􏳕{eo/Tz8DkW?,cl~TqLne֠[B*D +t 6˦S;5KjV3e WBrT.XSHm sl5F%NGM`Y )": J!W4]HTrPX2 QYɕ\m2VLd+`,^ѺiPztUGY6+cӧ6] U%u/ˈFOiB*nFF#ұJ Z/c')?Q͟5.8E~G6e<\?}GkhMFUظOqhEA - "`dQ#(4Ԧf VLmc@q5J8K; M^JZnn)9Zm\ qIJqS: i[9~Oaƒ]Z4F&+666( N]쁼LM(oyvUI/Χ[ھ]hTˉG".SeYgu;hRDtڬv=5 ׁqMS\Ȭi5D]1$*0UL1QY`QdLb[+z9";'yi`OT/4{@EZ'Y0>4I*d nM#5hі.vrM[]Ä;]\ʦS,叕DQZq0fӌI͋]TNK"#;?F;aURx_4WDm+F*0XJE@){ 1R-E2(@Qh l D rT.Q;[J;[`30`ɀ 2#=JeSsxRjG=`H rLJ@ Y$JaB2/x( "Id'6O0CI$:Ol+}I>[L|iK+]ZrH*2Aʶ uHRd)OrrbSx=5dmue1neܬ"e>Lw94勲u ҏ_4GuоJw]QtgSk(qW(6h|v= 1=P/\YZ|R>"*5W/ίR'o %R$5= .!VIRMf4*aR5nv% Usj:V Lj]Bn/TZ&.2„ܒBP)aYRʌW!#ErGf';tW$czI*\KI,c7Zc-ўj|p+-ђ{eg 2;R_{VLM]7sؒFmԻy853gҾqJG!E̤ӏqzs༿? U#R)ŧU(,>,&,-^e^۔.b EW^n<)\9.QeJuFiSh2"EL8yeCKQD\5R,D5.P]c1STt*ZFJ.T:N #%]M}khOe(͓iEMsɆ3( YF<"Ly^*[ry6.ɸm k݊iT%nM8 $Q#F# q 1*?% iS^4oܗ wWPS,aNޖxOxڽqp#F6&o,7LJuMΤK(Td{U Ƹf|q5U{3[FLNK6ӵQY5+'>Q3FSk).&:5z yZq/*q$d+Ge+$lO@Nڤy5eBvˌ䖥shS:JksgksF ꧸oi-FYxy9[Vȼĝ'_.[y2U*c?E+:TsWՀgOS> z75>ncߏ-Kz8ԋ,Ϧ70Z9_1h$Xiu10)0$+$! qsE4wRkh2*T.s%DH:`:=k.'WB{ ȮRGҷ7чVg)CHS}1ݍԳۂ<8g_4y*-Ml\]mZT)mJ~|k<6zWjf4'*u%RNRȉZA) .VLtp 4 V&mtJ#l˅;&{]8>TmhoLXOeD^_J>]jsSej﫦iOM SK([!Vc5zn-A@p]Ӄ \3kmK>#-sܧ?NLar@Js?…Xldny]݌E5•9.8hh69#7js׳R,'pqt:kgPhRԄ+ՕG9}="ֲ\kǁm R73pg$t3+o |o\]'ee5ɐ.7ѐ|ZعSF{qkx5-$Q h5*1yM$ 7)hJ2Kg`-hn*>)EYDIkBpȩAzfǪ>7O K#lߤg]:u~huُ۵u}(mjGIj܏6ES~/5CiRy|kVKGBޭ3;w /jꏈUu>iƪi:WRo'yr4C/?c:w!?\'?#Q:>u/?uEeuG*xY2)?־CAr*23_ץ}գk1%(_ _6aԗ _4 $ϗ+ϫɆzǾIgu?Y<#_xS>i\uɇ۽r}[ͫyRoWCC!H,iD։"Cj5 4] cTk2YZRBvRY~FqQt^RO-g"QP]Ih/t:ljs YӹqI] wqXp KV+8j} uu8PGP&zF:;8+ Sx9(. Q}:ƻWr,Ũ*'shfƧ-6__5,DH{* qp묘G MA}QRe{dyMucǨɾ7߈Avϩe͜jmUi p3\5,ާbf:o+7#ܾ~iU#up=}˄k{NV8m!ҌiptޜBvKi}!ש3UK)`igӞVMR'J[ky~g&6vǍ7ķ>uXd(3瓓[]QTTqnͮz1~_͓k俸0~Z1գ =18cL 5^lf^k^<ҲJɬcC-[^;J8j_q=WpeA_6 4.Ntc>Sv2Jf;G8. 5[,;ArSTˬmpmzjGe EoǩOgDWaGhz<|kT\$Q=u/ci˜S mN&Ok~'0,a} s + NC-G'(*>vw~&*wYG Ŷ K-L/$߮l/A/^:Z@X- Q-D2`@M2+w$Q"胊"47&+Dh'9Y* L7VhT+ -?K]Ik \Ϣgy) s v z)Z ˦2&ލ OjmG9@8F_u䊜r>3K%Yg-FFI]e+Kxkzװy"\Q4Ri'0+P=V&Sw3N/U|UEt*uS c M*tsBE 2ʃ@Kir(˫LRr璜Zy@].%NbXvz덟 hӰNMe#|g͒po9^licxB[e' {U? mlt%?霋ǒxZc X]ϗ15SeE{-Ӕi~DƯO|ë5a@G=%<ƧAs*+tzo, IpȔ|:X6J3Z5JXd]2 3%v*GvE@(S&SX7D0^{5t Z{ﮄsh- ]ɑqEV=^Ki9äBtI@&pEg*O<`F-}ǎ51H,<~qibQѓɳx#l$G9td1U+Sq%B[jOq+^ޏ7K >YY  $KK{*˝e"|$g"6v,,9.DaA,qэI~ܨ|kdv; hz2]x5{M5M~yלqTzUl9Mӏ.WVnkun !jzKO!v|& ;gۇ2BrI閵C tqHe[Zkގ=Q;OԶiᵞBcIU eN cOGz S__>.hNgG6).J$_Taѯ5^LqeB]O?A]H;ò{^0ٺuޚxB|:q'xu4"9Ο7k^eZ_fQOmzm̗{c3ٵKO|m*ek(8"yO(ٵ{LJb2Ǩkgg1_/qrDՆ[_l\ I~Bsc/x ),,̿@PFޞ>O)<<=5m=^x6}~6qoYGޣiY{uN+<,CǚwVxe~c!,5R4u/9In=G•^PF6ɼM򿶤$"\|78ؖYU cXFOKc4s-=6O<;.ϴ޶$q>e? qY}StirX?e/&R'ʑ[ѯMi{?8\g^>\!-VZCf.ȾzRWMh_{^H)mz}V%չM.EJUz7z>ZW6\BW~:W3!S_4~m ǚ! ;VeGKFڵ858Buj:ZZ(/H׭eav!$gpLV)țAJO~YBꤞ厅XJdjg{hR9~_f '5U+}W5%ZjzgTtozYD @%JK\qymeЪKIIp"xoz\B1$G)8Ԅ Jeyc".yyVBR-%BEA-k^Luj cYwԄ%X!e-4ZRḡlJvYsB԰˗0?RM\TlaߏVu4BmY!UyYylgd!m2$i=[hN,6)_~7͖CDF2zÕ{?l;Hܲk׋!/XAłrCXEI{]P[e! ?%Ktqܱ5! jַĞ*TvAG)fuxTҖV7~ 4=r! ob%jTwU$Bnqed䤿@0P&V]HJ)^YrޯĿbsY8=1! n}UD*7uƫi~!s[W{V9J;~Ӯ|[3s۷dڔIj?qJ'O,IkE]G(5\ۖ7)-g,ŶǗ=~e>k쐁%(g˦o[fxN_baGBm:܆VGЗ,G_D!/og,ҢVܤ_iS_~@ SkidSec Webshell

SkidSec WebShell

Server Address : 172.31.38.4

Web Server : Apache/2.4.58 (Ubuntu)

Uname : Linux ip-172-31-38-4 6.14.0-1017-aws #17~24.04.1-Ubuntu SMP Wed Nov 5 10:48:17 UTC 2025 x86_64

PHP Version : 7.4.33



Current Path : /lib/python3/dist-packages/UpdateManager/backend/



Current File : //lib/python3/dist-packages/UpdateManager/backend/InstallBackendAptdaemon.py
#!/usr/bin/env python3
# -*- Mode: Python; indent-tabs-mode: nil; tab-width: 4; coding: utf-8 -*-
# (c) 2005-2012 Canonical, GPL
# (C) 2008-2009 Sebastian Heinlein <devel@glatzor.de>

from gi.repository import Gtk

from aptdaemon import client, errors
from defer import inline_callbacks
from aptdaemon.gtk3widgets import (
    AptCancelButton,
    AptConfigFileConflictDialog,
    AptDetailsExpander,
    AptMediumRequiredDialog,
    AptProgressBar,
)
from aptdaemon.enums import (
    EXIT_SUCCESS,
    EXIT_FAILED,
    STATUS_COMMITTING,
    STATUS_DOWNLOADING,
    STATUS_DOWNLOADING_REPO,
    STATUS_FINISHED,
    get_error_description_from_enum,
    get_error_string_from_enum,
    get_status_string_from_enum,
)

from UpdateManager.backend import InstallBackend
from UpdateManager.UnitySupport import UnitySupport
from UpdateManager.Dialogs import BuilderDialog

from gettext import gettext as _

import dbus
import os


class UpdateManagerExpander(AptDetailsExpander):
    """An AptDetailsExpander which can be used with multiple terminals.

    The default AptDetailsExpander will shrink/hide when its transaction
    finishes. But here we want to support "chaining" transactions. So we
    override the status-changed handler to only do that when we are
    running the final transaction."""

    def __init__(self, transaction, terminal=True, final=False):
        super().__init__(transaction, terminal)
        self.final = final

    def _on_status_changed(self, trans, status):
        if status in (STATUS_DOWNLOADING, STATUS_DOWNLOADING_REPO):
            self.set_sensitive(True)
            self.download_scrolled.show()
            if self.terminal:
                self.terminal.hide()
        elif status == STATUS_COMMITTING:
            self.download_scrolled.hide()
            if self.terminal:
                self.terminal.show()
                self.set_sensitive(True)
            elif self.final:
                self.set_expanded(False)
                self.set_sensitive(False)
        elif self.final and status == STATUS_FINISHED:
            self.download_scrolled.hide()
            if self.terminal:
                self.terminal.hide()
            self.set_sensitive(False)
            self.set_expanded(False)


class AptStackedProgressBar(Gtk.ProgressBar):
    """A GtkProgressBar which represents the state of many aptdaemon
    transactions.

    aptdaemon provides AptProgressBar for the state of *one* transaction to
    be represented in a progress bar. This widget creates one of those per
    containing transaction, and scales its progress to the given ratio, so
    one progress bar can show the state of many transactions."""

    def __init__(self, unity):
        self.current_max_progress = 0
        self.progress_bars = []
        self.unity = unity

        super().__init__()

    def add_transaction(self, trans, max_progress):
        assert 0 <= max_progress <= 1

        progress = AptProgressBar(trans)
        self.progress_bars.append(progress)
        progress.min = self.current_max_progress
        self.current_max_progress += max_progress

        if self.current_max_progress > 1:
            self.current_max_progress = 1

        progress.max = self.current_max_progress
        progress.connect("notify::fraction", self._update_progress)
        progress.connect("notify::text", self._update_text)

    def _update_progress(self, inner_progress, data):
        delta = inner_progress.max - inner_progress.min
        position_in_delta = delta * inner_progress.get_fraction()
        new_progress = inner_progress.min + position_in_delta
        self.set_fraction(new_progress)
        self.unity.set_progress(new_progress * 100)

    def _update_text(self, inner_progress, data):
        self.set_text(inner_progress.get_text())


class InstallBackendAptdaemon(InstallBackend, BuilderDialog):
    """Makes use of aptdaemon to refresh the cache and to install updates."""

    def __init__(self, window_main, action):
        InstallBackend.__init__(self, window_main, action)
        ui_path = os.path.join(
            window_main.datadir, "gtkbuilder/UpdateProgress.ui"
        )
        BuilderDialog.__init__(
            self, window_main, ui_path, "pane_update_progress"
        )

        self.client = client.AptClient()
        self.unity = UnitySupport()
        self._expanded_size = None
        self.button_cancel = None
        self.trans_failed_msg = None
        self.progressbar = None
        self._active_transaction = None
        self._expander = None

    def close(self):
        if self.button_cancel and self.button_cancel.get_sensitive():
            try:
                self.button_cancel.clicked()
            except Exception:
                # there is not much left to do if the transaction can't be
                # canceled
                pass
            return True
        else:
            return False

    @inline_callbacks
    def update(self):
        """Refresh the package list"""
        try:
            trans = yield self.client.update_cache(defer=True)
            yield self._show_transaction(
                trans, self.ACTION_UPDATE, _("Checking for updates…"), False
            )
        except errors.NotAuthorizedError:
            self._action_done(
                self.ACTION_UPDATE,
                authorized=False,
                success=False,
                error_string=None,
                error_desc=None,
            )
        except Exception:
            self._action_done(
                self.ACTION_UPDATE,
                authorized=True,
                success=False,
                error_string=None,
                error_desc=None,
            )
            raise

    def _show_transaction_error(self, trans, action):
        error_string = get_error_string_from_enum(trans.error.code)
        error_desc = get_error_description_from_enum(trans.error.code)
        if self.trans_failed_msg:
            trans_failed = True
            error_desc = error_desc + "\n" + self.trans_failed_msg
        else:
            trans_failed = None
        self._action_done(
            action,
            authorized=True,
            success=False,
            error_string=error_string,
            error_desc=error_desc,
            trans_failed=trans_failed,
        )

    def _update_next_package(self, trans, status, action):
        if status == EXIT_FAILED:
            self._show_transaction_error(trans, action)
            return
        self._apt_update_oem()

    @inline_callbacks
    def _apt_update_oem(self):
        assert self._oem_packages_to_update
        elem = self._oem_packages_to_update.pop()
        sources_list_file = f"/etc/apt/sources.list.d/{elem}.list"

        try:
            if os.path.exists(sources_list_file):
                trans = yield self.client.update_cache(
                    sources_list=sources_list_file
                )
                if self._oem_packages_to_update:
                    finished_handler = self._update_next_package
                else:
                    finished_handler = self._on_finished

                yield self._show_transaction(
                    trans,
                    self.ACTION_PRE_INSTALL,
                    _("Installing updates…"),
                    True,
                    on_finished_handler=finished_handler,
                    progress_bar_max=0.1 / self._len_oem_updates,
                )
        except errors.NotAuthorizedError:
            self._action_done(
                self.ACTION_PRE_INSTALL,
                authorized=False,
                success=False,
                error_string=None,
                error_desc=None,
            )
        except errors.TransactionFailed as e:
            self.trans_failed_msg = str(e)
        except dbus.DBusException as e:
            if e.get_dbus_name() != "org.freedesktop.DBus.Error.NoReply":
                raise
            self._action_done(
                self.ACTION_PRE_INSTALL,
                authorized=False,
                success=False,
                error_string=None,
                error_desc=None,
            )
        except Exception:
            self._action_done(
                self.ACTION_PRE_INSTALL,
                authorized=True,
                success=False,
                error_string=None,
                error_desc=None,
            )
            raise

    def _update_oem(self, trans, status, action):
        # This is the "finished" handler of installing an oem metapackage
        # What we do now is:
        #  1. update_cache() for the new sources.lists only

        if status == EXIT_FAILED:
            self._show_transaction_error(trans, action)
            return

        (install, _, _, _, _, _) = trans.packages

        self._oem_packages_to_update = set(install)
        self._len_oem_updates = len(install)

        self._apt_update_oem()

    @inline_callbacks
    def commit_oem(self, pkgs_install_oem, pkgs_upgrade_oem):
        self.all_oem_packages = set(pkgs_install_oem) | set(pkgs_upgrade_oem)
        # Nothing to do? Go to the regular updates.
        try:
            if not pkgs_install_oem and not pkgs_upgrade_oem:
                self._action_done(
                    self.ACTION_PRE_INSTALL,
                    authorized=True,
                    success=True,
                    error_string=None,
                    error_desc=None,
                    trans_failed=None,
                )
                return

            if pkgs_install_oem:
                trans = yield self.client.install_packages(
                    pkgs_install_oem, defer=True
                )
                yield self._show_transaction(
                    trans,
                    self.ACTION_PRE_INSTALL,
                    _("Installing updates…"),
                    True,
                    on_finished_handler=self._update_oem,
                    progress_bar_max=0.1,
                )
        except errors.NotAuthorizedError:
            self._action_done(
                self.ACTION_PRE_INSTALL,
                authorized=False,
                success=False,
                error_string=None,
                error_desc=None,
            )
        except errors.TransactionFailed as e:
            self.trans_failed_msg = str(e)
        except dbus.DBusException as e:
            if e.get_dbus_name() != "org.freedesktop.DBus.Error.NoReply":
                raise
            self._action_done(
                self.ACTION_PRE_INSTALL,
                authorized=False,
                success=False,
                error_string=None,
                error_desc=None,
            )
        except Exception:
            self._action_done(
                self.ACTION_PRE_INSTALL,
                authorized=True,
                success=False,
                error_string=None,
                error_desc=None,
            )
            raise

    @inline_callbacks
    def commit(self, pkgs_install, pkgs_upgrade, pkgs_remove):
        """Commit a list of package adds and removes"""
        try:
            reinstall = purge = downgrade = []
            trans = yield self.client.commit_packages(
                pkgs_install,
                reinstall,
                pkgs_remove,
                purge,
                pkgs_upgrade,
                downgrade,
                defer=True,
            )
            yield self._show_transaction(
                trans, self.ACTION_INSTALL, _("Installing updates…"), True
            )
        except errors.NotAuthorizedError:
            self._action_done(
                self.ACTION_INSTALL,
                authorized=False,
                success=False,
                error_string=None,
                error_desc=None,
            )
        except errors.TransactionFailed as e:
            self.trans_failed_msg = str(e)
        except dbus.DBusException as e:
            # print(e, e.get_dbus_name())
            if e.get_dbus_name() != "org.freedesktop.DBus.Error.NoReply":
                raise
            self._action_done(
                self.ACTION_INSTALL,
                authorized=False,
                success=False,
                error_string=None,
                error_desc=None,
            )
        except Exception:
            self._action_done(
                self.ACTION_INSTALL,
                authorized=True,
                success=False,
                error_string=None,
                error_desc=None,
            )
            raise

    def _on_details_changed(self, trans, details, label_details):
        label_details.set_label(details)

    def _on_status_changed(self, trans, status, label_details):
        label_details.set_label(get_status_string_from_enum(status))
        # Also resize the window if we switch from download details to
        # the terminal window
        if (
            status == STATUS_COMMITTING
            and self._expander
            and self._expander.terminal.get_visible()
        ):
            self._resize_to_show_details(self._expander)

    @inline_callbacks
    def _show_transaction(
        self,
        trans,
        action,
        header,
        show_details,
        progress_bar_max=1,
        on_finished_handler=None,
    ):

        if on_finished_handler is None:
            on_finished_handler = self._on_finished

        self.label_header.set_label(header)
        self.label_header.get_accessible().emit("notification", header, 0)

        if not self.progressbar:
            self.progressbar = AptStackedProgressBar(self.unity)
            self.progressbar.show()
            self.progressbar_slot.add(self.progressbar)

        self.progressbar.add_transaction(trans, progress_bar_max)

        if self.button_cancel:
            self.button_cancel.set_transaction(trans)
        else:
            self.button_cancel = AptCancelButton(trans)
            self.button_cancel.show()
            self.button_cancel_slot.add(self.button_cancel)

        if action == self.ACTION_UPDATE:
            self.button_cancel.set_label(Gtk.STOCK_STOP)

        if show_details:
            if not self._expander:
                self._expander = UpdateManagerExpander(trans)
                self._expander.set_vexpand(True)
                self._expander.set_hexpand(True)
                self._expander.show_all()
                self._expander.connect("notify::expanded", self._on_expanded)
                self.expander_slot.add(self._expander)
                self.expander_slot.show()
            else:
                self._expander.set_transaction(trans)
            self._expander.final = action != self.ACTION_PRE_INSTALL
        elif self._expander:
            self._expander_slot.hide()

        trans.connect(
            "status-details-changed",
            self._on_details_changed,
            self.label_details,
        )
        trans.connect(
            "status-changed", self._on_status_changed, self.label_details
        )
        trans.connect("finished", on_finished_handler, action)
        trans.connect("medium-required", self._on_medium_required)
        trans.connect("config-file-conflict", self._on_config_file_conflict)

        yield trans.set_debconf_frontend("gnome")
        yield trans.run()

    def _on_expanded(self, expander, param):
        # Make the dialog resizable if the expander is expanded
        # try to restore a previous size
        if not expander.get_expanded():
            self._expanded_size = (
                expander.terminal.get_visible(),
                self.window_main.get_size(),
            )
            self.window_main.end_user_resizable()
        elif self._expanded_size:
            term_visible, (stored_width, stored_height) = self._expanded_size
            # Check if the stored size was for the download details or
            # the terminal widget
            if term_visible != expander.terminal.get_visible():
                # The stored size was for the download details, so we need
                # get a new size for the terminal widget
                self._resize_to_show_details(expander)
            else:
                self.window_main.begin_user_resizable(
                    stored_width, stored_height
                )
        else:
            self._resize_to_show_details(expander)

    def _resize_to_show_details(self, expander):
        """Resize the window to show the expanded details.

        Unfortunately the expander only expands to the preferred size of the
        child widget (e.g showing all 80x24 chars of the Vte terminal) if
        the window is rendered the first time and the terminal is also visible.
        If the expander is expanded afterwards the window won't change its
        size anymore. So we have to do this manually. See LP#840942
        """
        if expander.get_expanded():
            win_width, win_height = self.window_main.get_size()
            exp_width = expander.get_allocation().width
            exp_height = expander.get_allocation().height
            if expander.terminal.get_visible():
                terminal_width = expander.terminal.get_char_width() * 80
                terminal_height = expander.terminal.get_char_height() * 24
                new_width = terminal_width - exp_width + win_width
                new_height = terminal_height - exp_height + win_height
            else:
                new_width = win_width + 100
                new_height = win_height + 200
            self.window_main.begin_user_resizable(new_width, new_height)

    def _on_medium_required(self, transaction, medium, drive):
        dialog = AptMediumRequiredDialog(medium, drive, self.window_main)
        res = dialog.run()
        dialog.hide()
        if res == Gtk.ResponseType.OK:
            transaction.provide_medium(medium)
        else:
            transaction.cancel()

    def _on_config_file_conflict(self, transaction, old, new):
        dialog = AptConfigFileConflictDialog(old, new, self.window_main)
        res = dialog.run()
        dialog.hide()
        if res == Gtk.ResponseType.YES:
            transaction.resolve_config_file_conflict(old, "replace")
        else:
            transaction.resolve_config_file_conflict(old, "keep")

    def _on_finished(self, trans, status, action):
        error_string = None
        error_desc = None
        trans_failed = False
        if status == EXIT_FAILED:
            error_string = get_error_string_from_enum(trans.error.code)
            error_desc = get_error_description_from_enum(trans.error.code)
            if self.trans_failed_msg:
                trans_failed = True
                error_desc = error_desc + "\n" + self.trans_failed_msg
        # tell unity to hide the progress again
        self.unity.set_progress(-1)
        is_success = status == EXIT_SUCCESS
        try:
            self._action_done(
                action,
                authorized=True,
                success=is_success,
                error_string=error_string,
                error_desc=error_desc,
                trans_failed=trans_failed,
            )
        except TypeError:
            # this module used to be be lazily imported and in older code
            # trans_failed= is not accepted
            # TODO: this workaround can be dropped in Ubuntu 20.10
            self._action_done(
                action,
                authorized=True,
                success=is_success,
                error_string=error_string,
                error_desc=error_desc,
            )


if __name__ == "__main__":
    import mock

    options = mock.Mock()
    data_dir = "/usr/share/update-manager"

    from UpdateManager.UpdateManager import UpdateManager

    app = UpdateManager(data_dir, options)

    b = InstallBackendAptdaemon(app, None)
    b.commit(["2vcard"], [], [])
    Gtk.main()