2016-02-18 02:53:35 +03:00
# Copyright (C) 2015-2016 Red Hat, Inc. All rights reserved.
#
# This copyrighted material is made available to anyone wishing to use,
# modify, copy, or redistribute it subject to the terms and conditions
# of the GNU General Public License v.2.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from . automatedproperties import AutomatedProperties
2017-09-22 17:59:50 +03:00
from . utils import job_obj_path_generate , mt_async_call
2016-02-18 02:53:35 +03:00
from . import cfg
from . cfg import JOB_INTERFACE
import dbus
import threading
2016-11-16 19:46:27 +03:00
# noinspection PyUnresolvedReferences
2016-11-02 22:14:56 +03:00
from gi . repository import GLib
# Class that handles a client waiting for something to be complete. We either
# get a timeout or the operation is done.
class WaitingClient ( object ) :
# A timeout occurred
@staticmethod
def _timeout ( wc ) :
with wc . rlock :
if wc . in_use :
wc . in_use = False
# Remove ourselves from waiting client
wc . job_state . remove_waiting_client ( wc )
wc . timer_id = - 1
2017-09-22 17:59:50 +03:00
mt_async_call ( wc . cb , wc . job_state . Complete )
2016-11-02 22:14:56 +03:00
wc . job_state = None
def __init__ ( self , job_state , tmo , cb , cbe ) :
self . rlock = threading . RLock ( )
self . job_state = job_state
self . cb = cb
self . cbe = cbe
self . in_use = True # Indicates if object is in play
self . timer_id = - 1
if tmo > 0 :
self . timer_id = GLib . timeout_add_seconds (
tmo , WaitingClient . _timeout , self )
2023-02-16 19:24:06 +03:00
# The job finished before the timer popped, and we are being notified that
2016-11-02 22:14:56 +03:00
# it's done
def notify ( self ) :
with self . rlock :
if self . in_use :
self . in_use = False
# Clear timer
if self . timer_id != - 1 :
GLib . source_remove ( self . timer_id )
self . timer_id = - 1
2017-09-22 17:59:50 +03:00
mt_async_call ( self . cb , self . job_state . Complete )
2016-11-02 22:14:56 +03:00
self . job_state = None
2016-02-18 02:53:35 +03:00
# noinspection PyPep8Naming
class JobState ( object ) :
2016-06-28 20:07:21 +03:00
def __init__ ( self , request = None ) :
2016-02-18 02:53:35 +03:00
self . rlock = threading . RLock ( )
self . _percent = 0
self . _complete = False
self . _request = request
self . _ec = 0
self . _stderr = ' '
2016-11-02 22:14:56 +03:00
self . _waiting_clients = [ ]
2016-02-18 02:53:35 +03:00
2023-02-16 19:24:06 +03:00
# This is a lvm command that is just taking too long and doesn't
2016-02-18 02:53:35 +03:00
# support background operation
if self . _request :
# Faking the percentage when we don't have one
self . _percent = 1
@property
def Percent ( self ) :
with self . rlock :
return self . _percent
@Percent.setter
def Percent ( self , value ) :
with self . rlock :
self . _percent = value
@property
def Complete ( self ) :
with self . rlock :
if self . _request :
self . _complete = self . _request . is_done ( )
return self . _complete
@Complete.setter
def Complete ( self , value ) :
with self . rlock :
self . _complete = value
2016-09-19 22:30:04 +03:00
self . _percent = 100
2016-11-02 22:14:56 +03:00
self . notify_waiting_clients ( )
2016-02-18 02:53:35 +03:00
@property
def GetError ( self ) :
with self . rlock :
if self . Complete :
if self . _request :
( rc , error ) = self . _request . get_errors ( )
return ( rc , str ( error ) )
else :
return ( self . _ec , self . _stderr )
else :
return ( - 1 , ' Job is not complete! ' )
def dtor ( self ) :
with self . rlock :
self . _request = None
@property
def Result ( self ) :
with self . rlock :
if self . _request :
return self . _request . result ( )
return ' / '
2016-11-02 22:14:56 +03:00
def add_waiting_client ( self , client ) :
with self . rlock :
# Avoid race condition where it goes complete before we get added
# to the list of waiting clients
if self . Complete :
client . notify ( )
else :
self . _waiting_clients . append ( client )
def remove_waiting_client ( self , client ) :
# If a waiting client timer pops before the job is done we will allow
# the client to remove themselves from the list. As we have a lock
# here and a lock in the waiting client too, and they can be obtained
2023-02-16 19:24:06 +03:00
# in different orders, a deadlock can occur.
2016-11-02 22:14:56 +03:00
# As this remove is really optional, we will try to acquire the lock
# and remove. If we are unsuccessful it's not fatal, we just delay
# the time when the objects can be garbage collected by python
if self . rlock . acquire ( False ) :
try :
self . _waiting_clients . remove ( client )
finally :
self . rlock . release ( )
def notify_waiting_clients ( self ) :
with self . rlock :
for c in self . _waiting_clients :
c . notify ( )
self . _waiting_clients = [ ]
2016-02-18 02:53:35 +03:00
2016-11-04 02:25:12 +03:00
2016-02-18 02:53:35 +03:00
# noinspection PyPep8Naming
class Job ( AutomatedProperties ) :
2016-08-25 06:39:30 +03:00
_Percent_meta = ( ' d ' , JOB_INTERFACE )
2016-02-18 02:53:35 +03:00
_Complete_meta = ( ' b ' , JOB_INTERFACE )
_Result_meta = ( ' o ' , JOB_INTERFACE )
_GetError_meta = ( ' (is) ' , JOB_INTERFACE )
def __init__ ( self , request , job_state = None ) :
super ( Job , self ) . __init__ ( job_obj_path_generate ( ) )
self . set_interface ( JOB_INTERFACE )
if job_state :
self . state = job_state
else :
self . state = JobState ( request )
@property
def Percent ( self ) :
2016-08-25 06:39:30 +03:00
return dbus . Double ( float ( self . state . Percent ) )
2016-02-18 02:53:35 +03:00
@property
def Complete ( self ) :
2016-08-25 02:31:15 +03:00
return dbus . Boolean ( self . state . Complete )
2016-02-18 02:53:35 +03:00
2016-11-30 22:39:48 +03:00
@staticmethod
def _signal_complete ( obj ) :
obj . PropertiesChanged (
JOB_INTERFACE , dict ( Complete = dbus . Boolean ( obj . state . Complete ) ) , [ ] )
2016-02-18 02:53:35 +03:00
@Complete.setter
def Complete ( self , value ) :
self . state . Complete = value
2017-09-22 17:59:50 +03:00
mt_async_call ( Job . _signal_complete , self )
2016-02-18 02:53:35 +03:00
@property
def GetError ( self ) :
2016-08-25 02:31:15 +03:00
return dbus . Struct ( self . state . GetError , signature = " (is) " )
2016-02-18 02:53:35 +03:00
@dbus.service.method ( dbus_interface = JOB_INTERFACE )
def Remove ( self ) :
if self . state . Complete :
cfg . om . remove_object ( self , True )
self . state . dtor ( )
else :
raise dbus . exceptions . DBusException (
JOB_INTERFACE , ' Job is not complete! ' )
@dbus.service.method ( dbus_interface = JOB_INTERFACE ,
in_signature = ' i ' ,
2016-07-28 02:27:58 +03:00
out_signature = ' b ' ,
async_callbacks = ( ' cb ' , ' cbe ' ) )
def Wait ( self , timeout , cb , cbe ) :
2016-11-02 22:14:56 +03:00
if timeout == 0 or self . state . Complete :
cb ( dbus . Boolean ( self . state . Complete ) )
else :
self . state . add_waiting_client (
WaitingClient ( self . state , timeout , cb , cbe ) )
2016-02-18 02:53:35 +03:00
@property
def Result ( self ) :
2016-08-25 02:31:15 +03:00
return dbus . ObjectPath ( self . state . Result )
2016-02-18 02:53:35 +03:00
@property
def lvm_id ( self ) :
return str ( id ( self ) )
@property
def Uuid ( self ) :
import uuid
return uuid . uuid1 ( )
2022-06-06 17:51:54 +03:00
# Override the property "getters" implementation for the job interface, so a user can query a job while the queue
# is processing items. Originally all the property get methods were this way, but we changed this in
# e53454d6de07de56736303dd2157c3859f6fa848
# Properties
# noinspection PyUnusedLocal
@dbus.service.method ( dbus_interface = dbus . PROPERTIES_IFACE ,
in_signature = ' ss ' , out_signature = ' v ' )
def Get ( self , interface_name , property_name ) :
# Note: If we get an exception in this handler we won't know about it,
# only the side effect of no returned value!
return AutomatedProperties . _get_prop ( self , interface_name , property_name )
@dbus.service.method ( dbus_interface = dbus . PROPERTIES_IFACE ,
in_signature = ' s ' , out_signature = ' a {sv} ' )
def GetAll ( self , interface_name ) :
return AutomatedProperties . _get_all_prop ( self , interface_name )