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

Last change on this file since 4962 was 4962, checked in by Juanma, 2 years ago

remove duplicated debs when there's a zmd managing the deb installation

File size: 18.6 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                        self._debug(msg)
99        ####
100        #dict of actions/related functions for threading
101        ####
102        def _define_functions_for_threads(self):
103                self.threads['load']="threading.Thread(target=self._load_Store)"
104                self.threads['get_info']="threading.Thread(target=self._get_App_Info,args=[args])"
105                self.threads['pkginfo']="threading.Thread(target=self._get_Extended_App_Info,args=[args])"
106                self.threads['search']='threading.Thread(target=self._search_Store,args=[args,action])'
107                self.threads['list']='threading.Thread(target=self._search_Store,args=[args,action])'
108                self.threads['info']='threading.Thread(target=self._search_Store,args=[args,action])'
109                self.threads['install']='threading.Thread(target=self._install_remove_App,args=[args,action])'
110                self.threads['remove']='threading.Thread(target=self._install_remove_App,args=[args,action])'
111                self.threads['list_sections']='threading.Thread(target=self._list_sections,args=[args,action])'
112        #def _define_functions_for_threads
113
114        ####
115        #Launch the appropiate threaded function for the desired action
116        #Input:
117        #  - action to be executed
118        #  - parms for the action
119        ####
120        def execute_action(self,action,args=None):
121                self._debug("Launching action: "+action)
122                if self.is_action_running('load'):
123                        self._join_action('load')
124                if action in self.threads.keys():
125                        if self.is_action_running(action):
126                                #join thread if we're performing the same action
127                                self._debug("Waiting for current action "+action+" to end")
128                                self.threadsRunning[action].join()
129                        try:
130                                if action in self.progressActions.keys():
131                                        self.progressActions[action]=0
132                                self.progressActions[action]=0
133                                self.threadsRunning[action]=eval(self.threads[action])
134                                self.threadsRunning[action].start()
135                                self.result[action]={}
136                                self.result[action]['status']={'status':-1,'msg':''}
137                                self._debug("Thread "+str(self.threadsRunning[action])+" for action "+action+" launched")
138
139                        except Exception as e:
140                                self._debug("Can't launch thread for action: "+action)
141                                self._debug("Reason: "+str(e))
142                                pass
143                else:
144                        self._debug("No function associated with action "+action)
145        #def execute_action
146
147        ####
148        #Launch the appropiate class function
149        #Input:
150        #  - class action to be executed
151        #  - parms for the action
152        #  - parent action that demands the execution
153        #Output
154        #  - The class method to execute
155        ####
156        def _execute_class_method(self,action,parms=None,launchedby=None):
157                exeFunction=None
158                if not parms:
159                        parms="*"
160                if action in self.registeredPlugins:
161                        self._debug("Plugin for "+action+": "+self.registeredPlugins[action][parms])
162                        exeFunction=eval(self.registeredPlugins[action][parms]+"()")
163                        if self.dbg:
164                                exeFunction.set_debug(1)
165                        self._registerProcessProgress(action,exeFunction,launchedby)
166                else:
167                        self._debug("No plugin for action: "+action)
168
169                return (exeFunction)
170        #def _execute_class_method
171
172        ###
173        #Tell if a a action is running
174        #Input:
175        #  - action to monitorize
176        #Output:
177        #  - status true/false
178        ###
179        def is_action_running(self,actionSearched=None):
180                status=False
181                actionList=[]
182                if actionSearched:
183                        actionList.append(actionSearched)
184                else:
185                        actionList=self.relatedActions.keys()
186
187                for action in actionList:
188                        if action in self.threadsRunning:
189                                if self.threadsRunning[action].is_alive():
190                                        status=True
191                                        break
192                                else:
193                                        if action in self.relatedActions.keys():
194                                                for relatedAction in self.relatedActions[action]:
195                                                        if relatedAction in self.threadsRunning.keys():
196                                                                if self.threadsRunning[relatedAction].is_alive():
197                                                                        status=True
198                                                                        break
199#                       self._debug(action+" running: "+str(status))
200                return(status)
201        #def is_action_running
202
203        ####
204        #Joins an action till finish
205        #Input:
206        #  - action to join
207        ####
208        def _join_action(self,action):
209                self._debug("Joining action: "+action)
210                try:
211                        self.threadsRunning[action].join()
212                except Exception as e:
213                        self._debug("Unable to join thread for: "+action)
214                        self._debug("Reason: "+ str(e))
215                finally:               
216                        if action in self.threadsRunning.keys():
217                                del(self.threadsRunning[action])
218        #def _join_action
219
220        ####
221        #Register the method and action/parentAction pair in the progress dict
222        #Input:
223        #  - action launched
224        #  - function (a reference to the function)
225        #  - parentAction that owns the action (if any)
226        ####
227        def _registerProcessProgress(self,action,function,parentAction=None):
228                if action in self.registerProcessProgress.keys():
229                        self._debug("Appended process for action: "+action +" and function: "+str(function))
230                        self.registerProcessProgress[action].append(function)
231                else:
232                        self._debug("Registered process for action: "+action+" and function: "+str(function))
233                        self.registerProcessProgress[action]=[function]
234                if parentAction:
235                        self._debug("Registered process for Parent Action: "+action+"-"+parentAction+" and function: "+str(function))
236                        if parentAction in self.threadsProgress.keys():
237                                self.threadsProgress[parentAction].update({action:function})
238                        else:
239                                self.threadsProgress[parentAction]={action:function}
240        #def _registerProcessProgress
241
242        ####
243        #Get the progress of the executed actions
244        #Input
245        #  - action or none if we want all of the progress
246        #Output:
247        #  - Dict of results indexed by actions
248        ####
249        def get_progress(self,action=None):
250                progress={'search':0,'list':0,'install':0,'remove':0,'load':0,'list_sections':0}
251                actionList=[]
252                if action and action in self.registerProcessProgress:
253                        actionList=[action]
254                else:
255                        actionList=self.registerProcessProgress.keys()
256                self.lock.acquire() #prevent that any thread attempts to change the iterator
257                for parentAction in self.relatedActions.keys():
258                        if self.is_action_running(parentAction):
259                                for action in actionList:
260                                        if parentAction in self.threadsProgress.keys():
261#                                               self._debug(str(len(self.threadsProgress[parentAction]))+" Threads for action "+parentAction+": "+str(self.threadsProgress[parentAction]))
262                                                acumProgress=0
263                                                for threadfunction in self.threadsProgress[parentAction]:
264                                                        function=self.threadsProgress[parentAction][threadfunction]
265#                                                       self._debug(str(function)+" "+ str(threadfunction) + " "+parentAction)
266                                                        acumProgress=acumProgress+function.progress
267#                                                       self._debug("Acum for process "+parentAction+": "+str(acumProgress))
268       
269                                                count=len(self.relatedActions[parentAction])
270#                                               self._debug("Assign result for action" +action)
271                                                self.progressActions[parentAction]=round(acumProgress/count,0)
272                                                progress[parentAction]=self.progressActions[parentAction]
273                        else:
274                                #put a 100% just in case
275                                if parentAction in self.progressActions.keys():
276#                                       if self.progressActions[parentAction]:
277#                                               self.progressActions[parentAction]=100
278                                        self.progressActions[parentAction]=100
279                self.lock.release()
280#               self._debug("Progress :"+str(progress))
281                return(self.progressActions)
282        #def get_progress
283
284        ####
285        #Gets the result of an action
286        #Input:
287        #  - action
288        #Output:
289        #  - Dict of results indexed by actions
290        ####
291        def get_result(self,action=None):
292                self.lock.acquire() #Prevent changes on results from threads
293                result={}
294                if action==None:
295                        for res in self.result.keys():
296                                if res!='load':
297                                        if 'data' in self.result[res]:
298                                                result[res]=self.result[res]['data']
299                                        else:
300                                                result[res]=[]
301                else:
302                        self._debug("Checking result for action "+action)
303                        if self.is_action_running(action):
304                                self._join_action(action)
305                        result[action]=None
306                        if action in self.result:
307                                if 'data' in self.result[action]:
308                                        result[action]=self.result[action]['data']
309                                else:
310                                        result[action]=[]
311                self.lock.release()
312                return(result)
313        #def get_result
314
315        ####
316        #Gets the status of an action
317        #Input.
318        # - action
319        #Output:
320        # - Status dict of the action
321        ####
322        def get_status(self,action=None):
323                self.lock.acquire()
324                self._debug("Checking status for action "+str(action))
325                result={}
326                if action in self.result:
327                        result=self.result[action]['status']
328                        errorFile=open('/usr/share/lliurex-store/files/error.json').read()
329                        errorCodes=json.loads(errorFile)
330                        errCode=str(result['status'])
331                        if errCode in errorCodes:
332                                result['msg']=errorCodes[errCode]
333                        else:
334                                result['msg']=u"Unknown error"
335                self.lock.release()
336                return(result)
337        #def get_status
338
339        ####
340        #Loads the store
341        ####
342        def _load_Store(self):
343                action='load'
344                loadFunction=self._execute_class_method(action)
345                self.store=loadFunction.execute_action(action)
346        #def _load_Store
347
348        ####
349        #Loads the info related to one app
350        #Input:
351        #  - List of App objects
352        #Output:
353        #  - Dict with the related info
354        ####
355        def _get_App_Info(self,applist,launchedby=None):
356                action='get_info'
357                infoFunction=self._execute_class_method(action,None,launchedby)
358                applistInfo=infoFunction.execute_action(self.store,action,applist)
359                return(applistInfo)
360        #def _get_App_Info
361
362        ####
363        #Loads the extended info related to one app (slower)
364        #Input:
365        #  - Dict off Apps (as returned by _get_app_info)
366        #Output:
367        #  - Dict with the related info
368        ####
369        def _get_Extended_App_Info(self,applistInfo,launchedby=None,fullsearch=True):
370                #Check if there's any plugin for the distinct type of packages
371                action='pkginfo'
372                typeDict={}
373                result={}
374                result['data']=[]
375                result['status']={'status':0,'msg':''}
376                for appInfo in applistInfo:
377#                       result['data'].append(appInfo)
378                        package_type=self._check_package_type(appInfo)
379                        if package_type in typeDict:
380                                typeDict[package_type].append(appInfo)
381                        else:
382                                typeDict[package_type]=[appInfo]
383                for package_type in typeDict:
384                        self._debug("Checking plugin for "+action+ " "+package_type)
385                        if package_type in self.registeredPlugins[action]:
386#                               result['data']=[]
387                                #Only search full info if it's required
388                                if (fullsearch==False and package_type=='deb'):
389                                        result['data'].extend(typeDict[package_type])
390                                        continue
391                                self._debug("Retrieving info for "+str(typeDict[package_type]))
392                                pkgInfoFunction=self._execute_class_method(action,package_type,launchedby)
393#                               result.update(pkgInfoFunction.execute_action(action,typeDict[package_type]))
394                                result['data'].extend(pkgInfoFunction.execute_action(action,typeDict[package_type])['data'])
395                                result['status']=pkgInfoFunction.execute_action(action,typeDict[package_type])['status']
396                return(result)
397        #def _get_Extended_App_Info
398
399        def _list_sections(self,searchItem='',action='list_sections',launchedby=None):
400                result={}
401                self._debug("Retrieving all sections")
402                data={}
403                status={}
404                if action in self.registeredPlugins.keys():
405                        self._debug("Plugin for generic search: "+self.registeredPlugins[action]['*'])
406                        finder=self.registeredPlugins[action][('*')]
407                        searchFunction=eval(finder+"()")
408                        result=searchFunction.execute_action(self.store,action,searchItem)
409                        status=result['status']
410                        data=result['data']
411                else:
412                        print("No plugin for action "+action)
413                self.result[action]['data']=data
414                self.result[action]['status']=status
415                self._debug("Sections: "+str(self.result[action]['data']))
416                self._debug("Status: "+str(self.result[action]['status']))
417
418        ####
419        #Search the store
420        #Input:
421        #  - string search
422        #Output:
423        #  - List of dicts with all the info
424        ####
425        def _search_Store(self,searchItem='',action='search',fullsearch=False,launchedby=None):
426                applist={}
427                result={}
428                aux_applist=[]
429                if action=='list':
430                        try:
431                                searchItem=' '.join(searchItem)
432                        except:
433                                searchItem=''
434                elif action=='list_sections':
435                        searchItem=''
436                elif action=='info':
437                        fullsearch=True
438                if not launchedby:
439                        launchedby=action
440                #Set the exact match to false for search method
441                exact_match=True
442                if (launchedby=='search'):
443                                exact_match=False
444                searchFunction=self._execute_class_method(action,'*',launchedby)
445                result=searchFunction.execute_action(self.store,action,searchItem,exact_match)
446                aux_applist=result['data']
447                status=result['status']
448                realAction=action
449                if status['status']==0:
450                        #1.- Get appstream metadata (faster)
451                        partialAction='get_info'
452                        self.result[partialAction]={}
453                        result=self._get_App_Info(aux_applist,launchedby)
454                        self._debug("Add result for "+partialAction)
455                        self.result[partialAction]=result
456                        #2.- Get rest of metadata (slower)
457                        partialAction='pkginfo'
458                        result=self._get_Extended_App_Info(result['data'],launchedby,fullsearch)
459                        if launchedby:
460                                realAction=launchedby
461                                self._debug("Assigned results of "+action+" to "+realAction)
462                        if (result['status']['status']==0) or (result['status']['status']==9):
463                                return_msg=True
464                                if fullsearch:
465                                        result['status']['status']=0
466                        else:
467                                return_msg=False
468                else:
469                        return_msg=False
470                self.result[launchedby]['data']=result['data']
471                self.result[launchedby]['status']=result['status']
472                return(return_msg)
473        #def _search_Store
474
475        ####
476        #Install or remove an app
477        #Input:
478        #  - String with the app name
479        #Output:
480        #  - Result of the operation
481        ####
482        def _install_remove_App(self,appName,action='install',launchedby=None):
483                self._log("Attempting to "+action +" "+appName)
484                result={}
485                return_msg=False
486                if (self._search_Store(appName,'search',True,action)):
487                        applistInfo=self.result[action]['data']
488                        typeDict={}
489                        #Check if package is installed if we want to remove it or vice versa
490                        for appInfo in applistInfo:
491#Deactivated as appstream don't get the right status in all cases. Also it only returns two values: Installed and Unknown. Perhaps in some newer versions....
492#                               if (action=='install' and appInfo['state']=='installed') or (action=='remove' and appInfo['state']=='available'):
493#                               if (action=='remove' and appInfo['state']=='available'):
494#                                       if action=='remove':
495#                                               self.result[action]['status']={appInfo['package']:3}
496#                                               self.result[action]['status']={'status':3}
497#                                       else:
498#                                               self.result[action]['status']={appInfo['package']:4}
499#                                               self.result[action]['status']={'status':4}
500#                                               pass
501#                                       return_msg=False
502#                                       typeDict={}
503#                                       break
504                               
505                                package_type=self._check_package_type(appInfo)
506                                if package_type in typeDict:
507                                        typeDict[package_type].append(appInfo)
508                                else:
509                                        typeDict[package_type]=[appInfo]
510
511                        for package_type in typeDict:
512                                self._debug("Checking plugin for "+action+ " "+package_type)
513                                if package_type in self.registeredPlugins[action]:
514                                        installFunction=self._execute_class_method(action,package_type,action)
515                                        if package_type=='zmd':
516                                        #If it's a zmd the zomando must be present in the system
517                                                zmdAppInfo=[]
518                                                for zmdPackage in typeDict[package_type]:
519                                                        zmdInfo={}
520                                                        self._debug("Cheking presence of zmd "+ zmdPackage['package'])
521                                                        zmd='/usr/share/zero-center/zmds/'+appInfo['package']+'.zmd'
522                                                        if not os.path.exists(zmd):
523                                                                zmdInfo['package']=zmdPackage['package']
524                                                                zmdAppInfo.append(zmdInfo)
525                                                if zmdAppInfo:
526                                                        self._debug("Installing needed packages")
527                                                        installAuxFunction=self._execute_class_method(action,"deb",action)
528                                                        result=installAuxFunction.execute_action(action,zmdAppInfo)
529                                                       
530                                        result=installFunction.execute_action(action,typeDict[package_type])
531                                        self.result[action]=result
532                                        return_msg=True
533                        self._debug("Regenerating the cache...")
534                        self._load_Store()
535                self._log("Result "+action +": "+str(self.result[action]))
536                return(return_msg)
537        #def install_App
538       
539        ####
540        #Check the package type
541        #Input:
542        # - AppInfo dict (element of the list returned by _get_app_info)
543        #Output:
544        # - String with the type (deb, sh, zmd...)
545        ####
546        def _check_package_type(self,appInfo):
547                #Standalone installers must have the subcategory "installer"
548                #Zomandos must have the subcategory "Zomando"
549                self._debug("Checking package type for app "+appInfo['name'])
550                if "Zomando" in appInfo['categories']:
551                        return_msg="zmd"
552                else:
553                #Standalone installers must have an installerUrl field loaded from a bundle type=script description
554                        if appInfo['installerUrl']!='':
555                                return_msg="sh"
556                        else:
557                                return_msg="deb"
558                return(return_msg)
559        #def _check_package_type
Note: See TracBrowser for help on using the repository browser.