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

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

wip on packaging

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