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

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

WIP on CLI

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