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

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

wip on lliurex_apps.xml

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