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

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

wip on packaging

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