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

Last change on this file since 8223 was 8223, checked in by Juanma, 14 months ago

Implemented cache

File size: 29.6 KB
Line 
1#!/usr/bin/python3
2import sys
3import os
4import threading
5import syslog
6import pkgutil
7import lliurexstore.plugins as plugins
8import json
9import random
10import time
11from queue import Queue as pool
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,**kwargs):
22                self.dbg=False
23                if 'dbg' in kwargs.keys() and self.dbg==False:
24                        self.dbg=kwargs['dbg']
25                self.autostart=True
26                if 'autostart' in kwargs.keys():
27                        self.autostart=kwargs['autostart']
28                        self._debug("Autostart actions: %s"%self.autostart)
29                self._propagate_dbg=False
30                self.store=None
31                self.stores={}
32                self.related_actions={
33                                        'load':['load'],
34                                        'search':['search','get_info','pkginfo'],
35                                        'list':['list','get_info','pkginfo'],
36                                        'info':['list','get_info','pkginfo'],
37                                        'list_sections':['list_sections'],
38                                        'install':['search','get_info','pkginfo','install'],
39                                        'remove':['search','get_info','pkginfo','remove']
40                                        }
41                self.cli_mode=[]                        #List that controls cli_mode for plugins
42                self.autostart_actions=[]       #List with actions marked as autostart by plugins
43                self.postaction_actions=[]      #List with actions that will be launched after other actions
44                self.required_parms={}
45                self.threads={}                         #Dict with the functions that must execute each action
46                self.static={}                          #Dict with the functions that must execute each action
47                self.threads_progress={}                        #"" "" "" the progress for each launched thread
48                self.running_threads={}                 #"" "" "" the running threads
49                self.plugins_registered={}              #Dict with the relation between plugins and actions
50                self.register_action_progress={}                #Dict with the progress for each function/parent_action pair
51                self.action_progress={}                 #Progress of global actions based on average progress of individual processes
52                self.extra_actions={}           #Dict with the actions managed by plugins and no defined on the main class as related_actions
53                self.result={}                          #Result of the actions
54                self.lock=threading.Lock()              #locker for functions related to threads (get_progress, is_action_running...)
55                self.main(**kwargs)
56        #def __init__
57
58        def main(self,**kwargs):
59                self._define_functions_for_threads()    #Function that loads the dictionary self.threads
60                self.__init_plugins__(**kwargs)                 #Function that loads the plugins
61                self.execute_action('load')             #Initial load of the store
62                th=threading.Thread(target=self._autostart_actions)
63                th.start()
64        #def main
65
66        def _autostart_actions(self):
67                if self.autostart:
68                        for autostart_action in self.autostart_actions:
69                                self._debug("Autostart %s"%(autostart_action))
70                                self.execute_action(autostart_action)
71
72        ####
73        #Load and register the plugins from plugin dir
74        ####
75        def __init_plugins__(self,**kwargs):
76                package=plugins
77                for importer, mod, ispkg in pkgutil.walk_packages(path=package.__path__, prefix=package.__name__+'.',onerror=lambda x: None):
78                        import_mod='from %s import *'%mod
79                        try:
80                                self._debug("Importing %s"%mod)
81                                exec (import_mod)
82                        except Exception as e:
83                                print("Import failed for %s"%mod)
84                                print("Reason; %s"%e)
85
86                for mod in (sys.modules.keys()):
87                        if 'plugins.' in mod:
88                                class_actions={}
89                                plugin_name_up=mod.split('.')[-1]
90                                plugin_name=plugin_name_up.lower()
91                                self._debug("Initializing %s"%plugin_name)
92                                sw_cli_mode=False
93                                try:
94                                        target_class=eval(plugin_name)()
95                                        class_actions=target_class.register()
96                                        if 'disabled' in target_class.__dict__.keys():
97                                                if target_class.disabled==True:
98                                                        self._debug("Disabling plugin %s"%plugin_name)
99                                                        continue
100                                                if target_class.disabled==None:
101                                                        self._debug("Plugin %s will set its status"%plugin_name)
102                                                        pass
103                                                else:
104                                                        #Time to check if plugin is disabled or enabled by parm
105                                                        #Values for the plugins_registered dict must be the same as the parm name that enables the plugin
106                                                        for class_action,class_plugin_name in class_actions.items():
107                                                                class_plugin=class_plugin_name
108                                                                break
109                                                        if class_plugin in kwargs.keys():
110                                                                if kwargs[class_plugin]==True:
111                                                                        if target_class.disabled:
112                                                                                self._debug("Disabling plugin %s"%plugin_name)
113                                                                                continue
114                                                                else:
115                                                                        self._debug("Disabling plugin %s"%plugin_name)
116                                                                        continue
117                                                        else:
118                                                                self._debug("Disabling plugin %s"%plugin_name)
119                                                                continue
120                                        if 'cli_mode' in target_class.__dict__.keys():
121                                                if 'cli' in kwargs.keys():
122                                                        sw_cli_mode=True
123                                                        self._debug("Enabling cli mode for %s"%plugin_name)
124                                        if 'autostart_actions' in target_class.__dict__.keys():
125                                                self.autostart_actions.append(target_class.__dict__['autostart_actions'])
126                                        if 'requires' in target_class.__dict__.keys():
127                                                self.required_parms.update(target_class.__dict__['requires'])
128                                        if 'postaction_actions' in target_class.__dict__.keys():
129                                                self.postaction_actions.append({target_class:target_class.__dict__['postaction_actions']})
130                                except Exception as e:
131                                        print ("Can't initialize %s %s"%(mod,target_class))
132                                        print ("Reason: %s"%e)
133                                        pass
134                       
135                                for action in class_actions.keys():
136                                        if action not in self.plugins_registered:
137                                                self.plugins_registered[action]={}
138                                        full_module_name='plugins.'+plugin_name_up+'.'+plugin_name
139                                        self.plugins_registered[action].update({class_actions[action]:full_module_name})
140                                        if sw_cli_mode:
141                                                self.cli_mode.append(full_module_name)
142
143                self._debug(str(self.plugins_registered))
144        #def __init_plugins__
145
146        def set_debug(self,dbg=True):
147                self.dbg=dbg
148                self._debug ("Debug enabled")
149        #def set_debug
150
151        def _debug(self,msg=''):
152                if self.dbg==1:
153                        print ('DEBUG Store: %s'%msg)
154        #def _debug
155
156        def _log(self,msg=None):
157                if msg:
158                        syslog.openlog('lliurex-store')
159                        syslog.syslog(msg)
160                        self._debug(msg)
161        ####
162        #dict of actions/related functions for threading
163        ####
164        def _define_functions_for_threads(self):
165                self.threads['load']="threading.Thread(target=self._load_Store)"
166                self.threads['get_info']="threading.Thread(target=self._get_App_Info,daemon=True,args=args,kwargs=kwargs)"
167                self.threads['pkginfo']="threading.Thread(target=self._get_Extended_App_Info,daemon=True,args=args,kwargs=kwargs)"
168                self.threads['search']='threading.Thread(target=self._search_Store,daemon=True,args=args,kwargs=kwargs)'
169                self.threads['list']='threading.Thread(target=self._search_Store,daemon=True,args=args,kwargs=kwargs)'
170#               self.threads['list']='threading.Thread(target=self._get_editors_pick,args=args,kwargs=kwargs)'
171                self.threads['info']='threading.Thread(target=self._search_Store,daemon=True,args=args,kwargs=kwargs)'
172                self.threads['install']='threading.Thread(target=self._install_remove_App,daemon=True,args=args,kwargs=kwargs)'
173                self.threads['remove']='threading.Thread(target=self._install_remove_App,daemon=True,args=args,kwargs=kwargs)'
174                self.threads['list_sections']='threading.Thread(target=self._list_sections,daemon=True,args=args,kwargs=kwargs)'
175                self.static['random']='self._get_editors_pick(kwargs=kwargs)'
176        #def _define_functions_for_threads
177
178        ####
179        #Launch the appropiate threaded function for the desired action
180        #Input:
181        #  - action to be executed
182        #  - parms for the action
183        #Action must be a kwarg but for retrocompatibility reasons we keep it as an arg
184        ####
185        def execute_action(self,action,*args,**kwargs):
186                autostart_action=False
187                #Check for autolaunchable actions
188                if type(action)==type({}):
189                        autostart_action=True
190                        aux_action=list(action.keys())[0]
191                        kwargs.update({"action":aux_action})
192                        (key,value)=action[aux_action].split('=')
193                        kwargs.update({key:value})
194                        action=aux_action
195                else:
196                        kwargs.update({"action":action})
197                        if action in self.required_parms.keys():
198                                (key,value)=self.required_parms[action].split('=')
199                                kwargs.update({key:value})
200                self._debug("Launching action: %s with args %s and kwargs %s"%(action,args,kwargs))
201                if self.is_action_running('load'):
202                        self._join_action('load')
203                        self._debug("Total apps: %s"%str(len(self.store.get_apps())))
204                        self._debug("Resumed action %s"%action)
205                sw_track_status=False
206                if action not in self.threads.keys():
207                        #Attempt to add a new action managed by a plugin
208                        self._debug("Attempting to find a plugin for action %s"%action)
209                        if action in self.plugins_registered.keys():
210                                for package_type,plugin in self.plugins_registered[action].items():
211                                        self.action_progress[action]=0
212                                        if kwargs:
213                                                kargs={}
214                                                for arg_name in kwargs:
215                                                        try:
216                                                                kargs.update({arg_name:eval(kwargs[arg_name])})
217                                                        except:
218                                                                kargs.update({arg_name:kwargs[arg_name]})
219                                                kwargs=kargs.copy()
220                                        self.threads[action]='threading.Thread(target=self._execute_class_method(action,package_type).execute_action,daemon=True,args=[],kwargs=kwargs)'
221                                        break
222                                self._debug('Plugin for %s found: %s'%(action,self.plugins_registered[action]))
223                                if not autostart_action:
224                                        self.related_actions.update({action:[action]})
225                                sw_track_status=True
226                if action in self.threads.keys():
227                        if self.is_action_running(action):
228                                #join thread if we're performing the same action
229                                self._debug("Waiting for current action %s to end"%s)
230                                self.running_threads[action].join()
231                        try:
232                                self.action_progress[action]=0
233                                self.result[action]={}
234                                self.running_threads.update({action:eval(self.threads[action])})
235                                self.running_threads[action].start()
236                                if not self.running_threads[action].is_alive():
237                                        self._debug("Relaunching!!!!!!")
238                                        self.running_threads.update({action:eval(self.threads[action])})
239                                        self.running_threads[action].start()
240                                if sw_track_status:
241                                        self.result[action]['status']={'status':0,'msg':''}
242                                else:
243                                        self.result[action]['status']={'status':-1,'msg':''}
244                                self._debug("Thread %s for action %s launched"%(self.running_threads[action],action))
245                                self._debug("Thread count: %s"%(threading.active_count()))
246
247                        except Exception as e:
248                                self._debug("Can't launch thread for action: %s"%action)
249                                self._debug("Reason: %s"%e)
250                                pass
251                elif action in self.static.keys():
252                                self.action_progress[action]=0
253                                self.result[action].update({'data':eval(self.static[action])})
254                                self.result[action].update({'status':{'status':0,'msg':''}})
255                                self.action_progress[action]=100
256
257                else:
258                        self._debug("No function associated with action %s"%action)
259                        pass
260        #def execute_action
261
262        def _execute_class_method(self,action,package_type,*args,launchedby=None,**kwargs):
263                exe_function=None
264                if not package_type:
265                        package_type="*"
266                if action in self.plugins_registered.keys():
267                        self._debug("Plugin for %s: %s"%(action,self.plugins_registered[action][package_type]))
268                        try:
269                                self._debug(self.plugins_registered[action][package_type]+"("+','.join(args)+")")
270                                exe_function=eval(self.plugins_registered[action][package_type]+"("+','.join(args)+")")
271                        except Exception as e:
272                                self._debug("Can't launch execute_method for class %s"%e)
273                                pass
274                        if self._propagate_dbg:
275                                exe_function.set_debug(self.dbg)
276                        if self.plugins_registered[action][package_type] in self.cli_mode:
277                                exe_function.cli_mode=True
278                        self._register_action_progress(action,exe_function,launchedby)
279                else:
280                        self._debug("No plugin for action: %s"%action)
281                        pass
282                if kwargs:
283                        self._debug("Parms: %s"%kwargs)
284                        pass
285                return (exe_function)
286        #def _execute_class_method
287
288        ###
289        #Tell if a a action is running
290        #Input:
291        #  - action to monitorize
292        #Output:
293        #  - status true/false
294        ###
295        def is_action_running(self,searched_action=None):
296                status=False
297                action_list=[]
298                if searched_action:
299                        action_list.append(searched_action)
300                else:
301                        action_list=self.related_actions.keys()
302
303                for action in action_list:
304                        if action in self.static.keys():
305                                if self.action_progress[action]!=100:
306                                        status=True
307                        if action in self.running_threads.keys():
308                                if self.running_threads[action].is_alive():
309                                        status=True
310                                        break
311                                else:
312                                        if action in self.related_actions.keys():
313                                                for related_action in self.related_actions[action]:
314                                                        if related_action in self.running_threads.keys():
315                                                                if self.running_threads[related_action].is_alive():
316                                                                        status=True
317                                                                        break
318                return(status)
319        #def is_action_running
320
321        ####
322        #Joins an action till finish
323        #Input:
324        #  - action to join
325        ####
326        def _join_action(self,action):
327                self._debug("Joining action: %s"%action)
328                try:
329                        self.running_threads[action].join()
330                except Exception as e:
331                        self._debug("Unable to join thread for: %s"%action)
332                        self._debug("Reason: %s"%e)
333                        pass
334                finally:               
335                        if action in self.running_threads.keys():
336                                del(self.running_threads[action])
337        #def _join_action
338
339        ####
340        #Register the method and action/parent_action pair in the progress dict
341        #Input:
342        #  - action launched
343        #  - function (a reference to the function)
344        #  - parent_action that owns the action (if any)
345        ####
346        def _register_action_progress(self,action,function,parent_action=None):
347                if action in self.register_action_progress.keys():
348                        self._debug("Appended process for action: %s and function: %s"%(action,function))
349                        self.register_action_progress[action].append(function)
350                else:
351                        self._debug("Registered process for action: %s and function %s"%(action,function))
352                        self.register_action_progress[action]=[function]
353                if parent_action:
354                        self._debug("Registered process for Parent Action: %s-%s and function: %s"%(action,parent_action,function))
355                        if parent_action in self.threads_progress.keys():
356                                self.threads_progress[parent_action].update({action:function})
357                        else:
358                                self.threads_progress[parent_action]={action:function}
359        #def _register_action_progress
360
361        ####
362        #Get the progress of the executed actions
363        #Input
364        #  - action or none if we want all of the progress
365        #Output:
366        #  - Dict of results indexed by actions
367        ####
368        def get_progress(self,action=None):
369                progress={'search':0,'list':0,'install':0,'remove':0,'load':0,'list_sections':0}
370                action_list=[]
371                if action in self.static.keys():
372                        pass
373                else:
374                        if action in self.register_action_progress.keys():
375                                action_list=[action]
376                        else:
377                                action_list=self.register_action_progress.keys()
378                        self.lock.acquire() #prevent that any thread attempts to change the iterator
379                        for parent_action in self.related_actions.keys():
380                                if self.is_action_running(parent_action):
381                                        for action in action_list:
382                                                if parent_action in self.threads_progress.keys():
383                                                        acum_progress=0
384                                                        for threadfunction,function in self.threads_progress[parent_action].items():
385                                                                acum_progress=acum_progress+function.progress
386               
387                                                        count=len(self.related_actions[parent_action])
388                                                        self.action_progress[parent_action]=round(acum_progress/count,0)
389                                                        progress[parent_action]=self.action_progress[parent_action]
390                                else:
391                                        #put a 100% just in case
392                                        if parent_action in self.action_progress.keys():
393                                                self.action_progress[parent_action]=100
394                        self.lock.release()
395                return(self.action_progress)
396        #def get_progress
397
398        ####
399        #Gets the result of an action
400        #Input:
401        #  - action
402        #Output:
403        #  - Dict of results indexed by actions
404        ####
405        def get_result(self,action=None):
406                self.lock.acquire() #Prevent changes on results from threads
407                result={}
408                if action==None:
409                        for res in self.result.keys():
410                                if res!='load':
411                                        if 'data' in self.result[res]:
412                                                result[res]=self.result[res]['data']
413                                        else:
414                                                result[res]=[]
415                else:
416                        self._debug("Checking result for action %s"%action)
417                        if self.is_action_running(action):
418                                self._join_action(action)
419                        result[action]=[]
420                        if action in self.result:
421                                if 'data' in self.result[action]:
422                                        result[action]=self.result[action]['data']
423                                        if len(self.result[action]['data'])<1:
424                                                self._debug("ERROR NO DATA")
425                                                self._debug("ERROR NO DATA")
426                                                self._debug("ERROR NO DATA")
427                                                self._debug("ERROR NO DATA")
428                                                result[action]=[""]
429                                else:
430                                        result[action]=[""]
431                self.lock.release()
432                if action in self.extra_actions.keys():
433                        self._load_Store()
434                return(result)
435        #def get_result
436
437        ####
438        #Gets the status of an action
439        #Input.
440        # - action
441        #Output:
442        # - Status dict of the action
443        ####
444        def get_status(self,action=None):
445                self.lock.acquire()
446                self._debug("Checking status for action %s"%action)
447                result={}
448                if action in self.result:
449                        result=self.result[action]['status']
450                        try:
451                                err_file=open('/usr/share/lliurex-store/files/error.json').read()
452                                err_codes=json.loads(err_file)
453                                err_code=str(result['status'])
454                                if err_code in err_codes:
455                                        result['msg']=err_codes[err_code]
456                                else:
457                                        result['msg']=u"Unknown error"
458                        except:
459                                        result['msg']=u"Unknown error"
460                self.lock.release()
461#               print("RESULT %s: %s"%(action,result))
462                return(result)
463        #def get_status
464
465        ####
466        #Loads the store
467        ####
468        def _load_Store(self):
469                action='load'
470                #Load appstream metada first
471                package_type='*'
472                load_function=self._execute_class_method(action,package_type,launchedby=None)
473                self.store=load_function.execute_action(action=action,store=self.store)['data']
474                #Once appstream is loaded load the appstream plugins for other package types (snap, appimage...)
475                store_pool=pool()
476                threads=[]
477                for package_type in self.plugins_registered[action]:
478                        if package_type!='*':
479                                th=threading.Thread(target=self._th_load_store, args = (store_pool,action,package_type))
480                                threads.append(th)
481                                th.start()
482                for thread in threads:
483                        try:
484                                thread.join()
485                        except:
486                                pass
487                while store_pool.qsize():
488                        self.store=store_pool.get()
489        #def _load_Store
490
491        def _th_load_store(self,store_pool,action,package_type):
492                load_function=self._execute_class_method(action,package_type,launchedby=None)
493                store_pool.put(load_function.execute_action(action=action,store=self.store)['data'])
494
495        ####
496        #Return a random array of applications
497        #Input:
498        #  - exclude_sections=[] -> Array of sections that will not be included
499        #  - include_sections=[] -> Array of sections that will be included
500        #  - max_results -> Max number of apps to include
501        #Output:
502        #  - Dict with the related info
503        ####
504        def _get_editors_pick(self,*args,**kwargs):
505               
506                def load_applist():
507                        attempts=0
508                        sw_include=True
509                        tmp_app=random.choice(tmp_applist)
510                        while tmp_app in applist or tmp_app.get_state()==1:
511                                tmp_app=random.choice(tmp_applist)
512                                attempts+=1
513                                if attempts==9:
514                                        tmp_app=None
515                                        break
516                        if tmp_app:
517                                if exclude_sections or include_sections:
518                                        tmp_app_sec=tmp_app.get_categories()
519                                        for sec in exclude_sections:
520                                                sw_include=True
521                                                if sec in tmp_app_sec:
522                                                        sw_include=False
523                                                        break
524                                        for sec in include_sections:
525                                                sw_include=False
526                                                if sec in tmp_app_sec:
527                                                        sw_include=True
528                                                        break
529
530                                if sw_include:
531                                        while tmp_app in applist or tmp_app.get_state()==1:
532                                                tmp_app=random.choice(tmp_applist)
533                                                attempts+=1
534                                                if attempts==9:
535                                                        tmp_app=None
536                                                        break
537                                else:
538                                        tmp_app=None
539                        return(tmp_app)
540
541                def select_applist():
542                        start_point=random.randint(0,total_apps)
543                        end_point=start_point+10
544                        if end_point>total_apps:
545                                diff=end_point-total_apps
546                                end_point=total_apps
547                                start_point-=diff
548                        tmp_applist=apps_in_store[start_point:end_point]
549                        return(tmp_applist)
550                exclude_sections=[]
551                include_sections=[]
552                max_results=10
553                kargs=kwargs['kwargs'].copy()
554                if 'exclude_sections' in kargs.keys():
555                        exclude_sections=kargs['exclude_sections'].split(',')
556                        self._debug("Exclude sections %s"%exclude_sections)
557                if 'include_sections' in kargs.keys():
558                        include_sections=kargs['include_sections'].split(',')
559                        self._debug("Only sections %s"%include_sections)
560                if 'max_results' in kargs.keys():
561                        if kargs['max_results']:
562                                max_results=kargs['max_results']
563                applist=[]
564                apps_in_store=self.store.get_apps()
565                cont=0
566                total_apps=len(apps_in_store)-1
567                tmp_applist=select_applist()
568                while cont<max_results:
569                        tmp_app=load_applist()
570                        attempts=0
571                        while tmp_app==None:
572                                tmp_applist=select_applist()
573                                tmp_app=load_applist()
574                                attempts+=1
575                                if attempts>max_results*10:
576                                        break
577                        if tmp_app:
578                                applist.append(tmp_app)
579                        cont+=1
580                #Now transform applist into an app_info list
581                appinfo=self._get_App_Info(applist)
582                return(appinfo)
583
584        ####
585        #Loads the info related to one app
586        #Input:
587        #  - List of App objects
588        #Output:
589        #  - Dict with the related info
590        ####
591        def _get_App_Info(self,applist,launchedby=None):
592                action='get_info'
593                info_function=self._execute_class_method(action,None,launchedby=launchedby)
594                info_applist=info_function.execute_action(action,applist)
595                self._debug("Info collected")
596                return(info_applist)
597        #def _get_App_Info
598
599        ####
600        #Loads the extended info related to one app (slower)
601        #Input:
602        #  - Dict off Apps (as returned by _get_app_info)
603        #Output:
604        #  - Dict with the related info
605        ####
606        def _get_Extended_App_Info(self,info_applist,launchedby=None,fullsearch=True,channel=''):
607                #Check if there's any plugin for the distinct type of packages
608                action='pkginfo'
609                types_dict={}
610                result={}
611                result['data']=[]
612                result['status']={'status':0,'msg':''}
613                processed=[]
614                for app_info in info_applist:
615                        info_function=self._execute_class_method(action,'*',launchedby=launchedby)
616                        info_result=info_function.execute_action(action,applist=[app_info])
617                        self._debug(info_result)
618                        if info_result['status']['status']==0 and info_result['data'][0]['state']:
619                                result['data'].extend(info_result['data'])
620                        elif info_result['status']['status']==0:
621                                app_info=info_result['data'][0]
622                        #Get channel
623                        available_channels=self._check_package_type(app_info)
624                        for package_type in available_channels:
625                                if app_info['component']!='':
626                                        if app_info['id'] in processed:
627                                                self._debug("App %s processed"%app_info['id'])
628                                                continue
629
630                                if package_type in types_dict:
631                                        types_dict[package_type].append(app_info)
632                                else:
633                                        types_dict[package_type]=[app_info]
634                                processed.append(app_info['id'])
635                for package_type in types_dict:
636                        self._debug("Checking plugin for %s %s"%(action,package_type))
637                        if package_type in self.plugins_registered[action]:
638                                #Only seach full info if it's required
639                                if (fullsearch==False and package_type=='deb'):
640                                        result['data'].extend(types_dict[package_type])
641                                        continue
642                                self._debug("Retrieving info for %s"%types_dict[package_type])
643                                info_function=self._execute_class_method(action,package_type,launchedby=launchedby)
644                                result['data'].extend(info_function.execute_action(action,types_dict[package_type])['data'])
645                        else:
646                                result['data'].append(app_info)
647                return(result)
648        #def _get_Extended_App_Info
649
650        def _list_sections(self,searchItem='',action='list_sections',launchedby=None):
651                result={}
652                self._debug("Retrieving all sections")
653                data={}
654                status={}
655                if action in self.plugins_registered.keys():
656                        self._debug("Plugin for generic search: %s"%self.plugins_registered[action]['*'])
657                        finder=self.plugins_registered[action][('*')]
658                        search_function=eval(finder+"()")
659                        result=search_function.execute_action(self.store,action,searchItem)
660                        status=result['status']
661                        data=result['data']
662                else:
663                        print("No plugin for action %s"%action)
664                self.result[action]['data']=data
665                self.result[action]['status']=status
666                self._debug("Sections: %s"%self.result[action]['data'])
667                self._debug("Status: %s"%self.result[action]['status'])
668
669        ####
670        #Search the store
671        #Input:
672        #  - string search
673        #Output:
674        #  - List of dicts with all the info
675        ####
676        def _search_Store(self,*args,**kwargs):
677                search_item=args[0]
678                return_msg=False
679                action='search'
680                if 'action' in kwargs.keys():
681                        action=kwargs['action']
682                launchedby=None
683                if 'launchedby' in kwargs.keys():
684                        launchedby=kwargs['launchedby']
685                max_results=0
686                if 'max_results' in kwargs.keys():
687                        max_results=kwargs['max_results'] 
688                fullsearch=False
689                if 'fullsearch' in kwargs.keys():
690                        fullsearch=kwargs['fullsearch']
691                result={}
692                tmp_applist=[]
693                if action=='list_sections':
694                        search_item=''
695                elif action=='info':
696                        fullsearch=True
697                if not launchedby:
698                        launchedby=action
699                #Set the exact match to false for search method
700                exact_match=True
701                if (launchedby=='search'):
702                                exact_match=False
703                target_channel=''
704                if '=' in search_item:
705                        target_channel=search_item.split('=')[-1]
706                        search_item=search_item.split('=')[0]
707                for package_type in self.plugins_registered[action]:
708                        self._debug("Searching package type %s"%package_type)
709                        search_function=self._execute_class_method(action,'*',launchedby=launchedby)
710                        result.update(search_function.execute_action(self.store,action,search_item,exact_match,max_results))
711                tmp_applist=result['data']
712                status=result['status']
713                realAction=action
714                if status['status']==0:
715                        #1.- Get appstream metadata (faster)
716                        subordinate_action='get_info'
717                        self.result[subordinate_action]={}
718                        result=self._get_App_Info(tmp_applist,launchedby)
719                        self._debug("Add result for %s"%subordinate_action)
720                        self.result[subordinate_action]=result
721                        if fullsearch:
722                                #2.- Get rest of metadata (slower)
723                                self._debug("Target channel: %s"%target_channel)
724                                result=self._get_Extended_App_Info(result['data'],launchedby,fullsearch,target_channel)
725                                if launchedby:
726                                        realAction=launchedby
727                                        self._debug("Assigned results of %s to %s"%(action,realAction))
728                                if (result['status']['status']==0) or (result['status']['status']==9):
729                                        return_msg=True
730                                        if fullsearch:
731                                                result['status']['status']=0
732                                else:
733                                        self._debug(result)
734                                        return_msg=False
735                else:
736                        return_msg=False
737                self.result[launchedby]['data']=result['data']
738                self.result[launchedby]['status']=result['status']
739                return(return_msg)
740        #def _search_Store
741
742        ####
743        #Install or remove an app
744        #Input:
745        #  - String with the app name
746        #Output:
747        #  - Result of the operation
748        ####
749        def _install_remove_App(self,*args,**kwargs):
750                appName=args[0]
751                if 'action' in kwargs.keys():
752                        action=kwargs['action']
753                self._log("Attempting to %s %s"%(action,appName))
754                result={}
755                return_msg=False
756                if (self._search_Store(appName,action='search',fullsearch=True,launchedby=action)):
757                        info_applist=self.result[action]['data']
758                        types_dict={}
759                        #Check if package is installed if we want to remove it or vice versa
760                        for app_info in info_applist:
761                        #Appstream doesn't get the right status in all cases so we rely on the mechanisms given by the different plugins.
762                                if (action=='install' and app_info['state']=='installed') or (action=='remove' and app_info['state']=='available'):
763                                        if (action=='remove' and app_info['state']=='available'):
764                                                        self.result[action]['status']={app_info['package']:3}
765                                                        self.result[action]['status']={'status':3}
766                                        else:
767                                                self.result[action]['status']={app_info['package']:4}
768                                                self.result[action]['status']={'status':4}
769                                                pass
770                                        return_msg=False
771                                        types_dict={}
772                                        break
773                                processed=[]
774                                available_channels=self._check_package_type(app_info)
775                                for package_type in available_channels:
776                                        if app_info['component']!='':
777                                                if app_info['id'] in processed:
778                                                        self._debug("App %s processed"%app_info['id'])
779                                                        continue
780
781                                        if package_type in types_dict:
782                                                types_dict[package_type].append(app_info)
783                                        else:
784                                                types_dict[package_type]=[app_info]
785                                        processed.append(app_info['id'])
786
787                        for package_type in types_dict:
788                                self._debug("Checking plugin for %s %s"%(action,package_type))
789                                if package_type in self.plugins_registered[action]:
790                                        install_function=self._execute_class_method(action,package_type,launchedby=action)
791                                        if package_type=='zmd':
792                                        #If it's a zmd the zomando must be present in the system
793                                                zmd_info=[]
794                                                for zmd_bundle in types_dict[package_type]:
795                                                        zmdInfo={}
796                                                        self._debug("Cheking presence of zmd %s"%zmd_bundle['package'])
797                                                        zmd='/usr/share/zero-center/zmds/'+app_info['package']+'.zmd'
798                                                        if not os.path.exists(zmd):
799                                                                zmdInfo['package']=zmd_bundle['package']
800                                                                zmd_info.append(zmdInfo)
801                                                if zmd_info:
802                                                        self._debug("Installing needed packages")
803                                                        install_depends_function=self._execute_class_method(action,"deb",launchedby=action)
804                                                        result=install_depends_function.execute_action(action,zmd_info)
805                                                       
806                                        result=install_function.execute_action(action,types_dict[package_type])
807                                        self.result[action]=result
808                                        #Deprecated. Earlier versions stored the "app" object so here the app could get marked as installed/removed without neeed of import or query anything
809                                        #Python>=3.6 don't let us to store the app object in a queue (needed for the GUI) so this code becames deprecated.
810#                                       if result['status']['status']==0:
811                                                #Mark the apps as installed or available
812#                                               for app in types_dict[package_type]:
813#                                                       if action=='install':
814#                                                               app['appstream_id'].set_state(1)
815#                                                               self._debug("App state changed to installed")
816#                                                       else:
817#                                                               app['appstream_id'].set_state(2)
818#                                                               self._debug("App state changed to available")
819                                        for app in types_dict[package_type]:
820                                                self._execute_postactions(action,app['package'])
821                                        return_msg=True
822                self._log("Result %s: %s"%(action,self.result[action]))
823                return(return_msg)
824        #def install_App
825       
826        ####
827        #Check the package type
828        #Input:
829        # - AppInfo dict (element of the list returned by _get_app_info)
830        #Output:
831        # - String with the type (deb, sh, zmd...)
832        ####
833        def _check_package_type(self,app_info):
834                #Standalone installers must have the subcategory "installer"
835                #Zomandos must have the subcategory "Zomando"
836                self._debug("Checking package type for app "+app_info['name'])
837                return_msg=[]
838                if app_info['bundle']:
839                        return_msg.extend(app_info['bundle'])
840                else:
841                        if "Zomando" in app_info['categories']:
842                                return_msg.append("zmd")
843                        if 'component' in app_info.keys():
844                                if app_info['component']!='':
845                                        return_msg.append('deb')
846                #Standalone installers must have an installerUrl field loaded from a bundle type=script description
847                        if app_info['installerUrl']!='':
848                                return_msg.append("sh")
849                return(return_msg)
850        #def _check_package_type
851
852        def _execute_postactions(self,action,app):
853                for postaction in self.postaction_actions:
854                        for plugin,actions in postaction.items():
855                                for key,val in actions.items():
856                                        if action==key:
857                                                self._debug("Application: %s"%app)
858                                                plugin.execute_action(key,applist=app)
Note: See TracBrowser for help on using the repository browser.