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

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

Added cache regeneration after install or remove

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