source: lliurex-store/trunk/fuentes/python3-lliurex-store.install/usr/share/lliurexstore/storeManager.py @ 3653

Last change on this file since 3653 was 3653, checked in by Juanma, 3 years ago

WIP on CLI

File size: 16.2 KB
Line 
1#!/usr/bin/python3
2import sys
3import os
4import threading
5import syslog
6import pkgutil
7import lliurexstore.plugins as plugins
8######
9#Ver. 1.0 of storeManager.py
10# This class manages the store and the related plugins
11# It's implemented as an action-drived class.
12# There're four(five) main actions and each of them could execute and undeterminated number of subprocess in their respective thread
13# Each of these actions returns EVER a list of dictionaries.
14#####
15
16class StoreManager:
17        def __init__(self,args=()):
18                #               self.store=appstream.Store()
19                self.store=None
20                self.relatedActions={
21                                        'load':['load'],
22                                        'search':['search','info','pkginfo'],
23                                        'list':['list','info','pkginfo'],
24                                        'list_sections':['list_sections'],
25                                        'install':['search','info','pkginfo','install'],
26                                        'remove':['search','info','pkginfo','remove']
27                                        }
28                self.dbg=0
29                if args:
30                        self.dbg=args
31                self.threads={}                         #Dict that stores the functions that must execute each action
32                self.threadsProgress={}                 #"" "" "" the progress for each launched thread
33                self.threadsRunning={}                  #"" "" "" the running threads
34                self.registeredPlugins={}               #Dict that have the relation between plugins and actions
35                self.registerProcessProgress={}         #Dict that stores the progress for each function/parentAction pair
36                self.progressActions={}                 #Progress of global actions based on average progress of individual processes
37                self.result={}                          #Result of the actions
38                self.lock=threading.Lock()              #locker for functions related to threads (get_progress, is_action_running...)
39                self.main()
40        #def __init__
41
42        def main(self):
43                self._define_functions_for_threads()    #Function that loads the dictionary self.threads
44                self.__init_plugins__()                 #Function that loads the plugins
45                self.execute_action('load')             #Initial load of the store
46        #def main
47
48        ####
49        #Load and register the plugins from plugin dir
50        ####
51        def __init_plugins__(self):
52                package=plugins
53                for importer, mod, ispkg in pkgutil.walk_packages(path=package.__path__, prefix=package.__name__+'.',onerror=lambda x: None):
54                        strImport='from '+mod+' import *'
55                        try:
56                                self._debug("Importing "+str(mod))
57                                exec (strImport)
58                        except Exception as e:
59                                print(str(e))
60
61                for mod in (sys.modules.keys()):
62                        if 'plugins.' in mod:
63                                definedActions={}
64                                pluginNameUp=mod.split('.')[-1]
65                                pluginName=pluginNameUp.lower()
66                                self._debug("Initializing "+str(pluginName))
67                                try:
68                                        loadedClass=eval(pluginName)()
69                                        definedActions=loadedClass.register()
70                                except Exception as e:
71                                        print ("Can't initialize "+str(mod)+' '+str(loadedClass))
72                                        print ("Reason: "+str(e))
73                                        pass
74                       
75                                for action in definedActions.keys():
76                                        if action not in self.registeredPlugins:
77                                                self.registeredPlugins[action]={}
78                                        self.registeredPlugins[action].update({definedActions[action]:'plugins.'+pluginNameUp+'.'+pluginName})
79                self._debug(str(self.registeredPlugins))
80        #def __init_plugins__
81
82        def set_debug(self,dbg='1'):
83                self.dbg=int(dbg)
84                self._debug ("Debug enabled")
85        #def set_debug
86
87        def _debug(self,msg=''):
88                if self.dbg==1:
89                        print ('DEBUG Store: '+msg)
90        #def _debug
91
92        def _log(self,msg=None):
93                if msg:
94                        syslog.openlog('lliurex-store')
95                        syslog.syslog(msg)
96        ####
97        #dict of actions/related functions for threading
98        ####
99        def _define_functions_for_threads(self):
100                self.threads['load']="threading.Thread(target=self._load_Store)"
101                self.threads['info']="threading.Thread(target=self._get_App_Info,args=[args])"
102                self.threads['pkginfo']="threading.Thread(target=self._get_Extended_App_Info,args=[args])"
103                self.threads['search']='threading.Thread(target=self._search_Store,args=[args,action])'
104                self.threads['list']='threading.Thread(target=self._search_Store,args=[args,action])'
105                self.threads['install']='threading.Thread(target=self._install_remove_App,args=[args,action])'
106                self.threads['remove']='threading.Thread(target=self._install_remove_App,args=[args,action])'
107                self.threads['list_sections']='threading.Thread(target=self._list_sections,args=[args,action])'
108        #def _define_functions_for_threads
109
110        ####
111        #Launch the appropiate threaded function for the desired action
112        #Input:
113        #  - action to be executed
114        #  - parms for the action
115        ####
116        def execute_action(self,action,args=None):
117                self._debug("Launching action: "+action)
118                if self.is_action_running('load'):
119                        self._join_action('load')
120                if action in self.threads.keys():
121                        if self.is_action_running(action):
122                                #join thread if we're performing the same action
123                                self._debug("Waiting for current action "+action+" to end")
124                                self.threadsRunning[action].join()
125                        try:
126                                if action in self.progressActions.keys():
127                                        self.progressActions[action]=0
128                                self.progressActions[action]=0
129                                self.threadsRunning[action]=eval(self.threads[action])
130                                self.threadsRunning[action].start()
131                                self.result[action]={}
132                                self.result[action]['status']={'status':-1,'msg':''}
133                                self._debug("Thread "+str(self.threadsRunning[action])+" for action "+action+" launched")
134
135                        except Exception as e:
136                                self._debug("Can't launch thread for action: "+action)
137                                self._debug("Reason: "+str(e))
138                                pass
139                else:
140                        self._debug("No function associated with action "+action)
141        #def execute_action
142
143        ####
144        #Launch the appropiate class function
145        #Input:
146        #  - class action to be executed
147        #  - parms for the action
148        #  - parent action that demands the execution
149        #Output
150        #  - The class method to execute
151        ####
152        def _execute_class_method(self,action,parms=None,launchedby=None):
153                exeFunction=None
154                if not parms:
155                        parms="*"
156                if action in self.registeredPlugins:
157                        self._debug("Plugin for "+action+": "+self.registeredPlugins[action][parms])
158                        exeFunction=eval(self.registeredPlugins[action][parms]+"()")
159                        if self.dbg:
160                                exeFunction.set_debug(1)
161                        self._registerProcessProgress(action,exeFunction,launchedby)
162                else:
163                        self._debug("No plugin for action: "+action)
164
165                return (exeFunction)
166        #def _execute_class_method
167
168        ###
169        #Tell if a a action is running
170        #Input:
171        #  - action to monitorize
172        #Output:
173        #  - status true/false
174        ###
175        def is_action_running(self,action=None):
176                status=False
177                if action:
178                        if action in self.threadsRunning:
179                                if self.threadsRunning[action].is_alive():
180                                        status=True
181                                else:
182                                        if action in self.relatedActions.keys():
183                                                for relatedAction in self.relatedActions[action]:
184                                                        if relatedAction in self.threadsRunning.keys():
185                                                                if self.threadsRunning[relatedAction].is_alive():
186                                                                        status=True
187                                                                        break
188#                                       self._debug(action+" running: "+str(status))
189                else:
190                        if (threading.active_count()-1):
191                                status=True
192#                               self._debug("Running: "+str(threading.active_count()) + " threads")
193                return(status)
194        #def is_action_running
195
196        ####
197        #Joins an action till finish
198        #Input:
199        #  - action to join
200        ####
201        def _join_action(self,action):
202                self._debug("Joining action: "+action)
203                try:
204                        self.threadsRunning[action].join()
205                except Exception as e:
206                        self._debug("Unable to join thread for: "+action)
207                        self._debug("Reason: "+ str(e))
208                finally:               
209                        if action in self.threadsRunning.keys():
210                                del(self.threadsRunning[action])
211        #def _join_action
212
213        ####
214        #Register the method and action/parentAction pair in the progress dict
215        #Input:
216        #  - action launched
217        #  - function (a reference to the function)
218        #  - parentAction that owns the action (if any)
219        ####
220        def _registerProcessProgress(self,action,function,parentAction=None):
221                if action in self.registerProcessProgress.keys():
222                        self._debug("Appended process for action: "+action +" and function: "+str(function))
223                        self.registerProcessProgress[action].append(function)
224                else:
225                        self._debug("Registered process for action: "+action+" and function: "+str(function))
226                        self.registerProcessProgress[action]=[function]
227                if parentAction:
228                        self._debug("Registered process for Parent Action: "+action+"-"+parentAction+" and function: "+str(function))
229                        if parentAction in self.threadsProgress.keys():
230                                self.threadsProgress[parentAction].update({action:function})
231                        else:
232                                self.threadsProgress[parentAction]={action:function}
233        #def _registerProcessProgress
234
235        ####
236        #Get the progress of the executed actions
237        #Input
238        #  - action or none if we want all of the progress
239        #Output:
240        #  - Dict of results indexed by actions
241        ####
242        def get_progress(self,action=None):
243                progress={'search':0,'list':0,'install':0,'remove':0,'load':0,'list_sections':0}
244                actionList=[]
245                if action and action in self.registerProcessProgress:
246                        actionList=[action]
247                else:
248                        actionList=self.registerProcessProgress.keys()
249                self.lock.acquire() #prevent that any thread attempts to change the iterator
250                for parentAction in self.relatedActions.keys():
251                        if self.is_action_running(parentAction):
252                                for action in actionList:
253                                        if parentAction in self.threadsProgress.keys():
254#                                               self._debug(str(len(self.threadsProgress[parentAction]))+" Threads for action "+parentAction+": "+str(self.threadsProgress[parentAction]))
255                                                acumProgress=0
256                                                for threadfunction in self.threadsProgress[parentAction]:
257                                                        function=self.threadsProgress[parentAction][threadfunction]
258#                                                       self._debug(str(function)+" "+ str(threadfunction) + " "+parentAction)
259                                                        acumProgress=acumProgress+function.progress
260#                                                       self._debug("Acum for process "+parentAction+": "+str(acumProgress))
261       
262                                                count=len(self.relatedActions[parentAction])
263#                                               self._debug("Assign result for action" +action)
264                                                self.progressActions[parentAction]=round(acumProgress/count,0)
265                                                progress[parentAction]=self.progressActions[parentAction]
266                        else:
267                                #put a 100% just in case
268                                if parentAction in self.progressActions.keys():
269#                                       if self.progressActions[parentAction]:
270#                                               self.progressActions[parentAction]=100
271                                        self.progressActions[parentAction]=100
272                self.lock.release()
273#               self._debug("Progress :"+str(progress))
274                return(self.progressActions)
275        #def get_progress
276
277        ####
278        #Gets the result of an action
279        #Input:
280        #  - action
281        #Output:
282        #  - Dict of results indexed by actions
283        ####
284        def get_result(self,action=None):
285                self.lock.acquire() #Prevent changes on results from threads
286                result={}
287                if action==None:
288                        for res in self.result.keys():
289                                if res!='load':
290                                        result[res]=self.result[res]['data']
291                else:
292                        if self.is_action_running(action):
293                                self._join_action(action)
294                        result[action]=None
295                        if action in self.result:
296                                result[action]=self.result[action]['data']
297                self.lock.release()
298                return(result)
299        #def get_result
300
301        ####
302        #Gets the status of an action
303        #Input.
304        # - action
305        #Output:
306        # - Status dict of the action
307        ####
308        def get_status(self,action=None):
309                self.lock.acquire()
310                result={}
311                if action in self.result:
312                        result=self.result[action]['status']
313                self.lock.release()
314                return(result)
315        #def get_status
316
317        ####
318        #Loads the store
319        ####
320        def _load_Store(self):
321                action='load'
322                loadFunction=self._execute_class_method(action)
323                self.store=loadFunction.execute_action(action)
324        #def _load_Store
325
326        ####
327        #Loads the info related to one app
328        #Input:
329        #  - List of App objects
330        #Output:
331        #  - Dict with the related info
332        ####
333        def _get_App_Info(self,applist,launchedby=None):
334                action='info'
335                infoFunction=self._execute_class_method(action,None,launchedby)
336                applistInfo=infoFunction.execute_action(self.store,action,applist)
337                return(applistInfo)
338        #def _get_App_Info
339
340        ####
341        #Loads the extended info related to one app (slower)
342        #Input:
343        #  - Dict off Apps (as returned by _get_app_info)
344        #Output:
345        #  - Dict with the related info
346        ####
347        def _get_Extended_App_Info(self,applistInfo,launchedby=None,fullsearch=True):
348                #Check if there's any plugin for the distinct type of packages
349                action='pkginfo'
350                typeDict={}
351                for appInfo in applistInfo:
352                        package_type=self._check_package_type(appInfo)
353                        if package_type in typeDict:
354                                typeDict[package_type].append(appInfo)
355                        else:
356                                typeDict[package_type]=[appInfo]
357                for package_type in typeDict:
358                        self._debug("Checking plugin for "+action+ " "+package_type)
359                        if package_type in self.registeredPlugins[action]:
360                                #Only search deb's full info if it's required
361                                if (package_type=='deb' and fullsearch==False):
362                                        continue
363                                pkgInfoFunction=self._execute_class_method(action,package_type,launchedby)
364                                pkgInfoFunction.execute_action(action,typeDict[package_type])
365                return(applistInfo)
366        #def _get_Extended_App_Info
367
368        def _list_sections(self,searchItem='',action='list_sections',launchedby=None):
369                result={}
370                self._debug("Retrieving all sections")
371                data={}
372                status={}
373                if action in self.registeredPlugins.keys():
374                        self._debug("Plugin for generic search: "+self.registeredPlugins[action]['*'])
375                        finder=self.registeredPlugins[action][('*')]
376                        searchFunction=eval(finder+"()")
377                        result=searchFunction.execute_action(self.store,action,searchItem)
378                        status=result['status']
379                        data=result['data']
380                else:
381                        print("No plugin for action "+action)
382                self.result[action]['data']=data
383                self.result[action]['status']=status
384                self._debug("Sections: "+str(self.result[action]['data']))
385                self._debug("Status: "+str(self.result[action]['status']))
386
387        ####
388        #Search the store
389        #Input:
390        #  - string search
391        #Output:
392        #  - List of dicts with all the info
393        ####
394        def _search_Store(self,searchItem='',action='search',fullsearch=True,launchedby=None):
395                applist={}
396                aux_applist=[]
397                if action=='list':
398                        try:
399                                searchItem=' '.join(searchItem)
400                        except:
401                                searchItem=''
402                if action=='list_sections':
403                        searchItem=''
404                if not launchedby:
405                        launchedby=action
406
407                searchFunction=self._execute_class_method(action,'*',launchedby)
408                aux_result=searchFunction.execute_action(self.store,action,searchItem)
409                aux_applist=aux_result['data']
410                status=aux_result['status']
411                realAction=action
412                if status['status']==0:
413                        #1.- Get appstream metadata (faster)
414                        partialAction='info'
415                        self.result[partialAction]={}
416                        applist=self._get_App_Info(aux_applist,launchedby)
417                        self._debug("Add result for "+partialAction)
418                        self.result[partialAction]['data']=applist
419                        #2.- Get rest of metadata (slower)
420                        partialAction='pkginfo'
421                        applist=self._get_Extended_App_Info(applist,launchedby,fullsearch)
422                        if launchedby:
423                                realAction=launchedby
424                                self._debug("Assigned results of "+action+" to "+realAction)
425                        if (len(applist)):
426                                return_msg=True
427                        else:
428                                return_msg=False
429                else:
430                        return_msg=False
431                self.result[launchedby]['data']=applist
432                self.result[launchedby]['status']=status
433                return(return_msg)
434        #def _search_Store
435
436        ####
437        #Install or remove an app
438        #Input:
439        #  - String with the app name
440        #Output:
441        #  - Result of the operation
442        ####
443        def _install_remove_App(self,appName,action='install',launchedby=None):
444                self._debug("Attempting to "+action +" "+appName)
445                self._log("Attempting to "+action +" "+appName)
446                result={}
447                return_msg=False
448                if (self._search_Store(appName,'search',False,action)):
449                        applistInfo=self.result[action]['data']
450                        typeDict={}
451                        #Check if package is installed if we want to remove it or vice versa
452                        for appInfo in applistInfo:
453                                if (action=='install' and appInfo['state']=='installed') or (action=='remove' and appInfo['state']=='available'):
454                                        if action=='remove':
455#                                               self.result[action]['status']={appInfo['package']:3}
456                                                self.result[action]['status']={'status':3}
457                                        else:
458#                                               self.result[action]['status']={appInfo['package']:4}
459                                                self.result[action]['status']={'status':4}
460                                        return_msg=False
461                                        typeDict={}
462                                        break
463                               
464                                package_type=self._check_package_type(appInfo)
465                                if package_type in typeDict:
466                                        typeDict[package_type].append(appInfo)
467                                else:
468                                        typeDict[package_type]=[appInfo]
469
470                        for package_type in typeDict:
471                                self._debug("Checking plugin for "+action+ " "+package_type)
472                                if package_type in self.registeredPlugins[action]:
473                                        installFunction=self._execute_class_method(action,package_type,action)
474                                        result=installFunction.execute_action(action,typeDict[package_type])
475                                        self.result[action]=result
476                                        return_msg=True
477
478                self._log("Result "+action +": "+str(self.result[action]))
479                return(return_msg)
480        #def install_App
481       
482        ####
483        #Check the package type
484        #Input:
485        # - AppInfo dict (element of the list returned by _get_app_info)
486        #Output:
487        # - String with the type (deb, sh, zmd...)
488        ####
489        def _check_package_type(self,appInfo):
490                #Standalone installers must have the subcategory "installer"
491                #Zomandos must have the subcategory "Zomando"
492                self._debug("Checking package type for app "+appInfo['name'])
493                if "Zomando" in appInfo['categories']:
494                        return_msg="zmd"
495                else:
496                        if "Installer" in appInfo['categories']:
497                                return_msg="sh"
498                        else:
499                                return_msg="deb"
500                return(return_msg)
501        #def _check_package_type
Note: See TracBrowser for help on using the repository browser.