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

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

wip on packaging

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