source: lliurex-store/trunk/fuentes/lliurex-appstore.install/usr/bin/storeManager.py @ 3171

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

wip on packaging

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