source: syncer-plank/trunk/fuentes/syncer-plank.install/usr/bin/syncer-plank-gui @ 4303

Last change on this file since 4303 was 4303, checked in by mabarracus, 3 years ago

Updated UI
Add i18n localization
Add desktop file & icon

  • Property svn:executable set to *
File size: 72.0 KB
RevLine 
[4079]1#!/usr/bin/env python3
[4303]2###################################################
3#                                                 #
4# User interface for syncer-plank                 #
5# Author: M.Angel Juan <m.angel.juan@gmail.com>   #
6# License: GPLv3                                  #
7#                                                 #
8###################################################
[4079]9
[4303]10##############################
[4079]11#
[4303]12# Logging & debug helper functions
[4079]13#
[4303]14##############################
15import logging
16from colorlog import ColoredFormatter
17from functools import wraps
18import time
[4079]19
[4303]20def setup_logger():
21    """Return a logger with a default ColoredFormatter."""
22    formatter = ColoredFormatter(
23        "(%(threadName)-9s) %(log_color)s%(levelname)-8s%(reset)s %(message_log_color)s%(message)s",
24        datefmt=None,
25        reset=True,
26        log_colors={
27            'DEBUG': 'cyan',
28            'INFO': 'green',
29            'WARNING': 'yellow',
30            'ERROR': 'red',
31            'CRITICAL': 'red',
32        },
33        secondary_log_colors={
34            'message': {
35                'ERROR': 'red',
36                'CRITICAL': 'red',
37                'DEBUG': 'yellow'
38            }
39        },
40        style='%'
41    )
[4079]42
[4303]43    logger = logging.getLogger(__name__)
44    handler = logging.StreamHandler()
45    handler.setFormatter(formatter)
46    logger.addHandler(handler)
47    logger.setLevel(logging.DEBUG)
[4079]48
[4303]49    return logger
[4079]50
[4303]51def trace(func):
52    """Tracing wrapper to log when function enter/exit happens.
53    :param func: Function to wrap
54    :type func: callable
55    """
56    @wraps(func)
57    def wrapper(*args, **kwargs):
58        t=time.time()
59        strdebug='{0:0.3f} Start {1!r} '. format(t-start_time,func.__name__)
60        #strdebug+='KWARGS={} ARGS={}'.format(kwargs,args)
61        #
62        # Filter parameters
63        #
64        out={}
65        for x in kwargs:
66            text=str(kwargs[x])
67            if len(text) < 30 and 'object at' not in text:
68                out[x]=str(kwargs[x])
[4079]69            else:
[4303]70                out[x]='Skipped'
[4079]71        i=0
[4303]72        for x in args:
73            text=str(x)
74            if len(text) < 30 and 'object at' not in text:
75                out['arg' + str(i)] = x
[4079]76            else:
[4303]77                out['arg' + str(i)] = 'Skipped'
[4079]78            i += 1
[4303]79        strdebug += 'Parameters: {}'.format(out)
80        logger.debug(strdebug)
81        result = func(*args, **kwargs)
82        logger.debug('+{0:0.3f} End {1!r}'. format(time.time()-t,func.__name__))
83        return result
84    return wrapper
[4079]85
[4303]86start_time=time.time()
87logger = setup_logger()
[4079]88
[4303]89def dbg(*args,**kwargs):
90    enable=False
91    if enable:
92        for x in args:
93            print(str(x))
[4079]94
[4303]95def check_args(*args,**kwargs):
96    for a in args:
97        if a not in kwargs:
98            str='{} not in kwargs!'.format(a)
99            raise Exception(str)
100    return True
[4079]101
[4303]102import ctypes
[4079]103
[4303]104def terminate_thread(thread):
105    """Terminates a python thread from another thread.
[4079]106
[4303]107    :param thread: a threading.Thread instance
108    """
109    if not thread.isAlive():
110        return
[4079]111
[4303]112    exc = ctypes.py_object(SystemExit)
113    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(
114        ctypes.c_long(thread.ident), exc)
115    if res == 0:
116        raise ValueError("nonexistent thread id")
117    elif res > 1:
118        # """if it returns a number greater than one, you're in trouble,
119        # and you should call it again with exc=NULL to revert the effect"""
120        ctypes.pythonapi.PyThreadState_SetAsyncExc(thread.ident, None)
121        raise SystemError("PyThreadState_SetAsyncExc failed")
[4079]122
[4303]123##############################
124#
125# Imports section
126#
127##############################
[4079]128
[4303]129import sys,os
130import gi
131import re
132import threading
133import time
134import glob
135import xmlrpc.client as x
136import ssl
[4079]137
[4303]138gi.require_version('Gtk','3.0')
139from gi.repository import Gtk,GdkPixbuf,GObject,Gdk,GLib,Gio
[4079]140
[4303]141#
142# i18n
143#
144import gettext
145from gettext import gettext as _
146gettext.bindtextdomain('syncer_plank_gui','/usr/share/locale')
147gettext.textdomain('syncer_plank_gui')
[4079]148
[4303]149##############################
150#
151# Helper functions, threading & n4d mainly
152#
153##############################
154class Helper:
155    #
156    # Helper initialization
157    #
[4079]158    def __init__(self,*args,**kwargs):
[4303]159        check_args('controller',**kwargs)
160        self.ctl=kwargs['controller']
161        #
162        # Thread pool initialization
163        #
[4079]164        self._thread = [None] * 10
165        # self.get_used_thread = lambda : self._thread.index(list(filter(None.__ne__,self._thread))[0])
166        self.get_thread = lambda: self._thread.index(None)
[4303]167        #
168        # N4d initialization
169        #
[4079]170        self.n4d = x.ServerProxy("https://server:9779", verbose=False, use_datetime=True,
171                             context=ssl._create_unverified_context())
[4303]172        self.n4d_sepaphore = threading.BoundedSemaphore(value=1)
[4079]173
[4303]174    #
175    # Method to create threaded job, with or without callback
176    #
177    #@trace
[4079]178    def do_call(self, func, args, callback, nthread=None, prio=GLib.PRIORITY_HIGH_IDLE,block=False):
[4303]179        # Local thread number
[4079]180        thread = None
[4303]181
182        # If we are checking finalization of already created thread
[4079]183        if nthread == None:
[4303]184            # Is a new call
[4079]185            try:
186                try:
[4303]187                    # Get a free thread pool space
[4079]188                    thread = self.get_thread()
[4303]189                    namethread='thread_num_{}'.format(thread)
190                    dbg('Launch {} with {}'.format(namethread,func.__name__))
[4079]191                except:
192                    raise Exception('No more threads available')
[4303]193
194                # Create thread on pool
[4079]195                if  args!= None:
[4303]196                    # Thread needs args
197                    self._thread[thread] = threading.Thread(target=func, args=(args,),name=namethread)
[4079]198                else:
[4303]199                    # Thread without args
200                    self._thread[thread] = threading.Thread(target=func,name=namethread)
[4079]201
[4303]202                # Need a threaded blocking call?
[4079]203                if block:
[4303]204                    # Start blocking thread
[4079]205                    self._thread[thread].start()
206                    self._thread[thread].join()
[4303]207                    # Free thread
[4079]208                    self._thread[thread] = None
[4303]209
210                    # If has callback, call passing result or return the result of thread directly
[4079]211                    if callback == None:
212                        return args['result']
213                    else:
214                        return callback(**args) and False
215                else:
[4303]216                    # Start normal thread out of gtk loop (non-blocking)
[4079]217                    GObject.idle_add(self._thread[thread].start, priority=prio)
[4303]218                    # Program next check thread finalization
[4079]219                    GObject.timeout_add(50, self.do_call, func, args, callback, thread, prio,block)
[4303]220
221                # Finalize main call, no need to re-initialize timeouted checking
[4079]222                return False
223            except:
[4303]224                # Something goes wrong :-(
[4079]225                raise Exception('Error calling threads')
226        else:
[4303]227            # No new call, check if its finalized
228
229            # we use the old thread in pool, already started
[4079]230            thread = nthread
[4303]231
232            # Check if the job is done
[4079]233            if self._thread[thread].isAlive() or not self._thread[thread]._started._flag:
[4303]234                # If not done, initialize timeout checking
[4079]235                return True
236            else:
[4303]237                # Job is done
238                # Free the thread pool space
[4079]239                self._thread[thread] = None
240                try:
[4303]241                    # If has callback, call passing result or return the result of thread directly
[4079]242                    if callback != None:
[4303]243                        dbg('Callback from thread_num_{}: running function \'{}\''.format(nthread,callback.__name__))
[4079]244                        return callback(**args) and False
245                    else:
[4303]246                        # No direct return value is possible due to non blocking call, result only can be catched by asynchronous function
247                        # Finalize main call, no need to re-initialize timeouted checking
[4079]248                        return False
249                except Exception as e:
[4303]250                    # Something goes wrong :-(
[4079]251                    raise e
252
[4303]253        # In normal conditions no need to reach this point
254        # Finalize main call, no need to re-initialize timeouted checking
[4079]255        return False
256
257
258
[4303]259    ##############################
260    #
261    # Search plank elements into user home
262    #
263    ##############################
264    def search_local_plank(self,*args,**kwargs):
265        check_args('files_cache',**kwargs)
266        files_cache=kwargs['files_cache']
[4079]267
268        list_apps=[]
269        theme='Vibrancy-Colors'
270
271        try:
272            file_names = [filename for filename in glob.iglob(os.path.expanduser('~') + '/.config/plank/dock1/launchers/*.dockitem', recursive=True)]
273        except Exception as e:
274            return None
275        for filename in file_names:
276            basename=filename.split('/')[-1].split('.')[0]
277            if basename == 'desktop':
278                icon_file='preferences-desktop-wallpaper.png'
279                complete_filename = [filename for filename in glob.iglob('/usr/share/icons/' + theme + '/**/' + icon_file, recursive=True)][0]
280                name='Show desktop'
281                if complete_filename:
[4303]282                    list_apps.append((complete_filename,basename,basename,filename,name))
[4079]283            elif basename == 'matecc':
284                icon_file='preferences-desktop.png'
285                complete_filename = [filename for filename in glob.iglob('/usr/share/icons/' + theme + '/**/'+icon_file, recursive=True)][0]
286                name='Control center'
287                if complete_filename:
[4303]288                    list_apps.append((complete_filename,basename,basename,filename,name))
[4079]289            else:
290                complete_filename=[filename for filename in glob.iglob('/usr/share/applications/'+basename+'.desktop', recursive=True)][0]
[4303]291                result=self.scan_desktop_file(filename=complete_filename,files_cache=files_cache)
[4079]292                if result:
293                    list_apps.append(result)
294
295        return list_apps
296
[4303]297    ##############################
298    #
299    # Search location of icon appname
300    #
301    ##############################
302    def search_icon_file(self,*args,**kwargs):
303        check_args('files_cache','icon',**kwargs)
304        files_cache=kwargs['files_cache']
305        nameicon=kwargs['icon']
[4079]306
307        try:
308            file_names=''
309            txt='(.*'+nameicon+'.png)'
310            regexp=re.compile(txt)
[4303]311            for filename in files_cache:
[4079]312                m=regexp.match(filename)
313                if m:
314                    file_names=m.group(1)
315                    break
316        except Exception as e:
[4303]317            raise(e)
[4079]318        if len(file_names) == 0:
[4303]319            raise Exception('Icon file {} not found'.format(nameicon))
[4079]320        return file_names
321
[4303]322    ##############################
323    #
324    # Parse & extract info from desktop file
325    #
326    ##############################
327    def scan_desktop_file(self,*args,**kwargs):
328        check_args('files_cache','filename',**kwargs)
329        filename=kwargs['filename']
330        files_cache=kwargs['files_cache']
331
[4079]332        regexp1 = re.compile('Icon=(.*)',re.IGNORECASE)
333        regexp2 = re.compile('Name=(.*)', re.IGNORECASE)
[4303]334        regexp3 = re.compile('Comment=(.*)',re.IGNORECASE)
[4079]335        try:
336            with open(filename, 'r') as f:
337                lines = f.readlines()
338                icon = None
339                appname = None
[4303]340                description = None
[4079]341                for line in lines:
342                    m1 = regexp1.match(line)
343                    m2 = regexp2.match(line)
[4303]344                    m3 = regexp3.match(line)
[4079]345                    if m1 and icon == None:
346                        icon = m1.group(1)
347                    if m2 and appname == None:
348                        appname = m2.group(1)
[4303]349                    if m3 and m3.group(1):
350                        description=m3.group(1)
[4079]351
[4303]352                #
353                # Commented due to allow desktops with no icon or description
354                #
355                #if icon and appname and description:
356                if appname:
[4079]357                    desktop_basename = filename.split('/')[-1].split('.')[0]
[4303]358                    if icon != None:
359                        if 'png' in icon.split('.'):
360                            icon_path = icon
361                        else:
362                            try:
363                                icon_path = self.search_icon_file(icon=icon,files_cache=files_cache)
364                            except Exception as e:
365                                # Allow tolerance if no icon is found
366                                icon_path = None
367                        if desktop_basename:
368                            return (icon_path, appname, desktop_basename,filename,description)
[4079]369                    else:
[4303]370                        # icon or description are None
371                        return (icon,appname,desktop_basename,filename,description)
372                else:
373                #    raise Exception('Can\'t get icon/appname/description from desktop file \'{}\''.format(filename))
374                     raise Exception('Can\'t get appname from desktop file \'{}\''.format(filename))
375        except Exception as e:
376            raise(e)
[4079]377
[4303]378    ##############################
379    #
380    # Search system applications
381    #
382    ##############################
[4079]383    def search_in_applications(self,*args,**kwargs):
[4303]384        check_args('cache_info','files_cache',**kwargs)
385        info=kwargs['cache_info']
386        files_cache=kwargs['files_cache']
387
[4079]388        try:
389            file_names = [filename for filename in glob.iglob('/usr/share/applications/*.desktop', recursive=True)]
390        except:
391            return None
[4303]392
[4079]393        outlist=[]
394        procesed_items=0
395        total_items=len(file_names)
[4303]396
397        info['msg'] = _("Scanning system apps...")
398        # FILTER SOME ELEMENTS WITH SAME NAME
399        not_repeated_list = {}
400        filter_column = 1
401        column4change_if_seems_the_same = 2
[4079]402        for desktop in file_names:
[4303]403            try:
404                result=self.scan_desktop_file(filename=desktop,files_cache=files_cache)
405                if result:
406                    if result[filter_column] not in not_repeated_list:
407                        not_repeated_list[result[filter_column]]=result
408                    else:
409                        # if seems the same element change 'name' with 'basename'
410                        # First change old element found
411                        temp1=list(not_repeated_list[result[filter_column]])
412                        del not_repeated_list[result[filter_column]]
413                        temp1[filter_column]=temp1[column4change_if_seems_the_same]
414                        not_repeated_list[result[column4change_if_seems_the_same]]=tuple(temp1)
415                        # Second insert new element found
416                        temp2=list(result)
417                        temp2[filter_column]=temp2[column4change_if_seems_the_same]
418                        not_repeated_list[result[column4change_if_seems_the_same]]=tuple(temp2)
419            except Exception as e:
420                dbg(e)
421                pass
422
[4079]423            procesed_items += 1
[4303]424            info['level']=procesed_items/total_items
[4079]425
[4303]426        for x in not_repeated_list:
427            outlist.append(not_repeated_list[x])
[4079]428        return outlist
429
[4303]430    ##############################
431    #
432    # Clean status_bar_with_timeout
433    #
434    ##############################
435    def clean_status_bar(self,*args,**kwargs):
436        def sleep_and_clean(x):
437            time.sleep(x.get('sleep'))
438            bar_info = {str(x.get('num_bar')): ''}
439            self.ctl.window.set_txt_status_bars(bar_info=bar_info)
440
441        self.do_call(sleep_and_clean,{'sleep':3,'num_bar':args[0]},None)
442
443        return
444
445    ##############################
446    #
447    # N4D validation call
448    #
449    ##############################
450    #@trace
451    def n4d_validate(self,*args,**kwargs):
452        check_args('user','pwd',**kwargs)
453        d = {'args': (kwargs['user'], kwargs['pwd']),'action':'validation_end'}
454        #login_func = lambda x: x.update({'result': self.n4d.login(x.get('args'), 'Golem', x.get('args'))})
455        #@trace
456        def login_func(x):
457            try:
458                self.n4d_sepaphore.acquire()
459                x.update({'result': self.n4d.login(x.get('args'), 'Golem', x.get('args'))})
460                self.n4d_sepaphore.release()
461            except Exception as e:
462                raise Exception(e)
463        return self.do_call(login_func, d, self.ctl.process)
464
465    ##############################
466    #
467    # N4D call to get ldap groups
468    #
469    ##############################
470    #@trace
471    def n4d_get_system_groups(self,*args,**kwargs):
472        check_args('user','pwd',**kwargs)
473        d = {'args': (kwargs['user'],kwargs['pwd']),'action':'set_groups'}
474        #get_groups = lambda x: x.update({'result': self.n4d.get_available_groups(x.get('args'), 'Golem')})
475        #@trace
476        def get_groups(x):
477            try:
478                self.n4d_sepaphore.acquire()
479                x.update({'result': self.n4d.get_available_groups(x.get('args'), 'Golem')})
480                self.n4d_sepaphore.release()
481            except Exception as e:
482                raise Exception(e)
483        return self.do_call(get_groups, d, self.ctl.process)
484
485    ##############################
486    #
487    # N4D call to get currently stored profiles
488    #
489    ##############################
490    #@trace
491    def n4d_get_profiles_stored(self,*args,**kwargs):
492        check_args('user','pwd',**kwargs)
493        d = {'args': (kwargs['user'],kwargs['pwd']),'action':'set_profiles'}
494        #get_profiles = lambda x: x.update({'result': self.n4d.get_profiles(x.get('args'),'PlankSync')})
495        #@trace
496        def get_profiles(x):
497            try:
498                self.n4d_sepaphore.acquire()
499                x.update({'result': self.n4d.get_profiles(x.get('args'), 'PlankSync')})
500                self.n4d_sepaphore.release()
501            except Exception as e:
502                raise Exception(e)
503        return self.do_call(get_profiles, d, self.ctl.process)
504
505    ##############################
506    #
507    # N4D call to remove profile on filesystem
508    #
509    ##############################
510    #@trace
511    def n4d_remove_profile(self,*args,**kwargs):
512        check_args('user','pwd','name',**kwargs)
513        d = {'args': (kwargs['user'],kwargs['pwd']), 'name': kwargs['name'],'action':'remove_profile_done'}
514        #remove_profile = lambda x: x.update({'result': self.n4d.remove_profile(x.get('args'), 'PlankSync', x.get('name'))})
515        #@trace
516        def remove_profile(x):
517            try:
518                self.n4d_sepaphore.acquire()
519                x.update({'result': self.n4d.remove_profile(x.get('args'), 'PlankSync', x.get('name'))})
520                self.n4d_sepaphore.release()
521            except Exception as e:
522                raise Exception(e)
523        return self.do_call(remove_profile, d, self.ctl.process)
524
525    ##############################
526    #
527    # N4D call to get details about profile
528    #
529    ##############################
530    #@trace
531    def n4d_read_profile(self,*args,**kwargs):
532        #
533        # Option to set new parameters
534        #
535        d = {}
536        try:
537            check_args('extraparam',**kwargs)
538            for nameparam in kwargs['extraparam']:
539                d[nameparam]=kwargs['extraparam'][nameparam]
540        except:
541            pass
542        check_args('user', 'pwd', 'name', **kwargs)
543        d['args'] = (kwargs['user'],kwargs['pwd'])
544        d['name'] = kwargs['name']
545        d['action'] = 'load_profile_into_liststore'
546        #get_profile = lambda x: x.update({'result': self.window.n4d.read_profile(x.get('args'), 'PlankSync', x.get('name'))})
547        #@trace
548        def get_profile(x):
549            try:
550                self.n4d_sepaphore.acquire()
551                x.update({'result': self.n4d.read_profile(x.get('args'), 'PlankSync', x.get('name'))})
552                self.n4d_sepaphore.release()
553            except Exception as e:
554                raise Exception(e)
555        #
556        # Multiple callback is possible if not needed to do normal processing using 'noprocess' parameter
557        #
558        try:
559            check_args('noprocess',**kwargs)
560            new_callback_func=kwargs['noprocess']
561            callback=new_callback_func
562        except:
563            callback=self.ctl.process
564
565        return self.do_call(get_profile, d, callback)
566
567    ##############################
568    #
569    # N4D call to rename profile
570    #
571    ##############################
572    def n4d_rename_profile(self,*args,**kwargs):
573        check_args('user','pwd','old','new',**kwargs)
574        d = {'args': (kwargs['user'],kwargs['pwd']), 'oldname': kwargs['old'], 'newname': kwargs['new'],'action':'profile_is_renamed'}
575        #rename_profile = lambda x: x.update({'result': self.n4d.rename_profile(x.get('args'), 'PlankSync', x.get('oldname'), x.get('newname'))})
576        def rename_profile(x):
577            try:
578                self.n4d_sepaphore.acquire()
579                x.update({'result': self.n4d.rename_profile(x.get('args'), 'PlankSync', x.get('oldname'), x.get('newname'))})
580                self.n4d_sepaphore.release()
581            except Exception as e:
582                raise Exception(e)
583        return self.do_call(rename_profile, d, self.ctl.process)
584
585    ##############################
586    #
587    # N4D call to create or update profile
588    #
589    ##############################
590    def n4d_update_profile(self,*args,**kwargs):
591        check_args('user','pwd','name','grouplist','applist',**kwargs)
592        d = {'args': (kwargs['user'],kwargs['pwd']), 'name': kwargs['name'], 'group': kwargs['grouplist'], 'applist': kwargs['applist'], 'action':'profile_is_updated'}
593        try:
594            check_args('status_bar',**kwargs)
595            d.update({'status_bar':kwargs['status_bar']})
596        except:
597            pass
598        #update_profile = lambda x: x.update({'result': self.n4d.update_profile(x.get('args'), 'PlankSync', x.get('name'), x.get('group'),x.get('applist'))})
599        def update_profile(x):
600            try:
601                self.n4d_sepaphore.acquire()
602                x.update({'result': self.n4d.update_profile(x.get('args'), 'PlankSync', x.get('name'), x.get('group'),
603                                                            x.get('applist'))})
604                self.n4d_sepaphore.release()
605            except Exception as e:
606                raise Exception(e)
607        #
608        # Multiple callback is possible if not needed to do normal processing using 'noprocess' parameter
609        #
610        try:
611            check_args('noprocess', **kwargs)
612            new_callback_func = kwargs['noprocess']
613            callback = new_callback_func
614        except:
615            callback = self.ctl.process
616        return self.do_call(update_profile, d, callback)
617
618##############################
619#
620# Main visualization class
621#
622##############################
623class MainWindow:
624
625    def __init__(self,*args,**kwargs):
626        check_args('controller','handler',**kwargs)
627        self.ctl = kwargs['controller']
628        self.handler = kwargs['handler']
629
630        #
631        # Show main window
632        #
633
634        self.obj = self.ctl.getobj('window_main')
635
636        #
637        # build stack components for the window
638        #
639        self.boxes_main = ['box_login','box_loading','box_main_btn','box_profiles','box_dialog','box_pselector','box_group_edit','box_warning']
640        self.boxes_bottom = ['box_lvl','box_status']
641        self.stack_main = {'obj': self.build_stack(boxes=self.boxes_main),'stack':[]}
642        self.stack_bottom = { 'obj': self.build_stack(boxes=self.boxes_bottom), 'stack':[]}
643
644        #
645        # get place to put stack
646        #
647        self.box_main_center=self.ctl.getobj('box_main_center')
648        self.box_main_bottom=self.ctl.getobj('box_main_bottom')
649
650        #
651        # Add stacks
652        #
653        self.box_main_center.pack_start(self.stack_main['obj'],True,True,0)
654        self.box_main_bottom.pack_start(self.stack_bottom['obj'],True,True,0)
655
656        #
657        # Store references to programatically created objects
658        #   boxes with labels and combos storing relations
659        #
660        self.group_profile_boxes = {}
661
662        # Show window
663        self.obj.show_all()
664
665    ##############################
666    #
667    # Builds dynamically boxes with an entry and comboboxes because
668    #   treeview with comboboxrenderer isn't a valid option
669    #
670    ##############################
671    def build_group_profile_window(self,*args,**kwargs):
672        # Get params from controller
673        check_args('profiles','groups',**kwargs)
674        profiles=kwargs['profiles']
675        groups=kwargs['groups']
676
677        # Get glade main object from builder
678        container=self.ctl.getobj('box_container_grp_profiles')
679
680        if len(self.group_profile_boxes) == 0:
681            #
682            # if it's the first time called build the structure
683            # if it isn't the first time the objects are already stored
684            #
685
686            # Build ListStore 4 showing into ComboBox
687            lst = Gtk.ListStore(GObject.TYPE_INT,GObject.TYPE_STRING)
688            # Add fake empty first element
689            lst.append([0,'[None]'])
690            # Id entry into combobox model
691            i=1
692            # Reverse mapping group -> profile association from list elements ['profile','group_associated']
693            grp_profile={}
694            # Complete ListStore & do the mapping
695            for profile in profiles:
696                pname=profile[0]
697                pgrp=profile[1]
698                lst.append([i,pname])
699                if pgrp != '':
700                    for g in pgrp.split(','):
701                        grp_profile[g] = i
702                i += 1
703
704            # Create Boxes (rows) per group
705            for gname in groups:
706                # A Box
707                box=Gtk.Box(Gtk.Orientation.HORIZONTAL,10)
708                box.set_property('homogeneous',True)
709                box.set_property('halign',Gtk.Align.FILL)
710                # With Label
711                lbl=Gtk.Label(gname)
712                lbl.set_property('halign',Gtk.Align.CENTER)
713                # And ComboBox
714                combo=Gtk.ComboBox.new_with_model_and_entry(lst)
715                combo.set_property('halign',Gtk.Align.START)
716                combo.set_property('width-request',200)
717                combo.set_property('entry-text-column',1)
718                combo.set_property('id-column', 0)
719                # Set active element if already associated
720                if gname in grp_profile:
721                    combo.set_property('active', grp_profile[gname])
722                else:
723                    combo.set_property('active', 0)
724                # Connect signals
725                combo.connect("changed", self.handler.combo_changed,lbl)
726                # Pack everything
727                box.pack_start(lbl,True,True,0)
728                box.pack_start(combo,True,True,0)
729                # Save references to avoid redo the objects
730                self.group_profile_boxes.update({gname:[lbl,combo]})
731                container.pack_start(box,False,False,0)
[4079]732        else:
[4303]733            #
734            # Objects are already created, they only need to be updated
735            #
[4079]736
[4303]737            # Id entry into combobox model
738            i = 1
739            # Reverse mapping group -> profile association from list elements ['profile','group_associated']
740            grp_profile = {}
741            # Do the mapping
742            for profile in profiles:
743                pgrp=profile[1]
744                if pgrp != '':
745                    for g in pgrp.split(','):
746                        grp_profile[g] = i
747                i += 1
748            for group in groups:
749                if group in grp_profile:
750                    self.group_profile_boxes[group][1].set_property('active',grp_profile[group])
751                else:
752                    self.group_profile_boxes[group][1].set_property('active', 0)
[4079]753
[4303]754
755        # Show all widgets
756        container.show_all()
757
758    ##############################
759    #
760    # Build stack components with boxes from glade
761    #
762    ##############################
763    def build_stack(self,*args,**kwargs):
764        stack=Gtk.Stack()
765        for box_name in kwargs['boxes']:
766            box=self.ctl.getobj(box_name)
767            stack.add_named(box,box_name)
768        stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE)
769        stack.set_transition_duration(400)
770        return stack
771
772    ##############################
773    #
774    # Change visualization elements from stacks
775    #
776    ##############################
777    def change_stack(self,*args,**kwargs):
778        # Get the stack and properties
779        if 'stack' not in kwargs:
780            stack=self.stack_main['obj']
781            history=self.stack_main['stack']
782            lstack=self.boxes_main
783            visible_name = stack.get_visible_child_name()
[4079]784        else:
[4303]785            if type(kwargs['stack']) == type(str()):
786                if kwargs['stack'] == 'main':
787                    stack=self.stack_main['obj']
788                    history = self.stack_main['stack']
789                    lstack = self.boxes_main
790                    visible_name = stack.get_visible_child_name()
791                else:
792                    stack=self.stack_bottom['obj']
793                    history = self.stack_bottom['stack']
794                    lstack = self.boxes_bottom
795                    visible_name = stack.get_visible_child_name()
796            else:
797                stack=kwargs['stack']
798                visible_name = stack.get_visible_child_name()
799                if visible_name in self.boxes_main:
800                    lstack=self.boxes_main
801                else:
802                    lstack=self.boxes_bottom
[4079]803
[4303]804        lindex=lstack.index(visible_name)
805        next=lindex
806        for to in args:
807            try:
808                if to == 'next':
809                    next += (lindex + 1) % len(lstack)
810                elif to == 'prev':
811                    next += (lindex - 1) % len(lstack)
812                elif to == 'back':
813                    next = lstack.index(history[1])
814                elif type(to) == type(int()):
815                    next += (lindex + to) % len(lstack)
816                else:
817                    if to in lstack:
818                        next = lstack.index(to)
819            except:
820                pass
[4079]821
[4303]822        stack.set_visible_child_name(lstack[next])
823        self.ctl.last_window = lstack[next]
[4079]824
[4303]825        if to != 'back':
826            history.insert(0,lstack[next])
827        else:
828            del history[0]
[4079]829
[4303]830        if len(history) > 10:
831            del history[10]
[4079]832
833
[4303]834    ##############################
835    #
836    # Change button label
837    #
838    ##############################
839    def change_button_text(self,*args,**kwargs):
840        if not check_args('button','text',**kwargs):
841            return
842        btn=self.ctl.getobj(kwargs['button'])
843        btn.set_label(kwargs['text'])
[4079]844
[4303]845    ##############################
846    #
847    # Update level bar & label
848    #
849    ##############################
850    #@trace
851    def update_status_bar(self,*args,**kwargs):
852
853        bar=self.ctl.getobj('lvlbar')
854        label=self.ctl.getobj('label_lvlbar')
855        info=args[0]
856        done=None
857        while not done:
858            time.sleep(0.1)
859            bar.set_value(info['level'])
860            label.set_text(info['msg'])
861            done=info['done']
862        return
863
864    ##############################
865    #
866    # Update with text any status bar
867    #
868    ##############################
869    def set_txt_status_bars(self,*args,**kwargs):
870        bars=[]
871        contexts=[]
872        for i in range(3):
873            bars.insert(i,self.ctl.getobj('stbar'+str(i+1)))
874            contexts.insert(i,bars[i].get_context_id(str(i)))
875        if 'clean' in kwargs:
876            for nbar in range(3):
877                bars[nbar].remove_all(contexts[nbar])
878            return
879        for i in range(3):
880            if len(args) > i:
881                bars[i].remove_all(contexts[i])
882                bars[i].push(contexts[i],args[i])
883        if 'bar_info' in kwargs:
884            bar_info = kwargs['bar_info']
885            for num in bar_info:
886                if num in ['0','1','2']:
887                    bars[int(num)].remove_all(contexts[int(num)])
888                    bars[int(num)].push(contexts[int(num)],bar_info[num])
889        return
890
891    ##############################
892    #
893    # Get values from entries or labels
894    #
895    ##############################
896    def get_entry(self, *args, **kwargs):
897        lout = []
898        for name in args:
899            entry = self.ctl.getobj(name)
900            lout.append(entry.get_text())
901        return lout
902
903    ##############################
904    #
905    # Set values into entries or labels
906    #
907    ##############################
908    def set_entry(self, *args, **kwargs):
909        for entry in args:
910            for name_entry in entry:
911                value = entry[name_entry]
912                self.ctl.getobj(name_entry).set_text(value)
913        return
914
915##############################
916#
917# Event handler main class
918#
919##############################
920class Handler():
921    def __init__(self,*args,**kwargs):
922        check_args('controller',**kwargs)
923        self.ctl=kwargs['controller']
924
925    def get_selected_rowid(self,*args,**kwargs):
926        check_args('selected_obj','type',**kwargs)
927        #
928        # Get the selected element, returns elements id or none
929        #
930        treeview_selection = kwargs['selected_obj']
931        typearg = kwargs['type']
932        model, path = treeview_selection.get_selected_rows()
933        # If nothing is selected abort actions
934        if (len(path) < 1):
935            return None
936        selected = None
937        if typearg == 'apps':
938            selected = model[path][APPCOL.ID]
939        if typearg == 'profiles':
940            selected = model[path][PROFCOL.ID]
941        return selected
942
943    def destroy(self, *args, **kwargs):
944        self.ctl.kill_all_pending_operations()
945        Gtk.main_quit(*args)
946
947    def delete_event(self,*args, **kwargs):
948        for w in args:
949            w.hide()
950        return True
951
952    def validate_clicked(self,*args,**kwargs):
953        self.ctl.process(action='validation_init')
954
955    def go_to_new_profile(self,*args,**kwargs):
956        self.ctl.process(action='goto',to='new_profile')
957
958    def go_to_edit_profile(self,*args,**kwargs):
959        self.ctl.process(action='goto', to='edit_profile')
960
961    def go_to_group_mapping(self,*args,**kwargs):
962        self.ctl.process(action='goto', to='group_mapping')
963
964    def close_window_and_go_back(self,*args,**kwargs):
965        self.ctl.process(action='goto', to='back')
966
967    def entry_filter_changed(self,*args,**kwargs):
968        self.ctl.process(action='refilter_elements')
969
970    def swap(self,*args,**kwargs):
971        id=self.get_selected_rowid(selected_obj=args[0],type='apps')
972        self.ctl.process(action='swap',id=id)
973
974    def swap_local_elements(self,*args,**kwargs):
975        self.ctl.process(action='swap_locals')
976
977    def go_to_ask_remove_profile(self,*args,**kwargs):
978        id = self.get_selected_rowid(selected_obj=args[0], type='profiles')
979        if id != None:
980            self.ctl.process(action='goto', to='ask_remove',id=id)
981
982    def load_selected_profile(self,*args,**kwargs):
983        id = self.get_selected_rowid(selected_obj=args[0], type='profiles')
984        if id != None:
985            self.ctl.process(action='goto',to='editing_profile',id=id)
986
987    def confirm_yes(self,*args,**kwargs):
988        id = self.get_selected_rowid(selected_obj=args[0], type='profiles')
989        if id != None:
990            self.ctl.process(action='goto',to='remove_confirmed',id=id)
991
992    def profile_name_changed(self,*args,**kwargs):
993        self.ctl.profile_name_changed = True
994
995    def go_to_main_window(self,*args,**kwargs):
996        self.ctl.process(action='goto',to='main_window')
997
998    def combo_changed(self,*args,**kwargs):
999        #
1000        # Omit handler when view is building to avoid fake checkings & processing
1001        #
1002        if not self.ctl.building_view:
1003            combo = args[0]
1004            label = args[1]
1005            textlabel = label.get_text()
1006            iter = combo.get_active_iter()
1007            model = combo.get_model()
1008            selected = model[iter][1]
1009            self.ctl.process(action='change_relations',group=textlabel,profile=selected)
1010
1011##############################
1012#
1013# Liststore enumerations
1014#
1015##############################
1016class APPCOL():
1017    VISIBLE = 0
1018    TYPE = 1
1019    ID = 2
1020    ICON = 3
1021    NAME = 4
1022    DESCRIPTION = 5
1023    SELECTED = 6
1024    BASENAME = 7
1025class PROFCOL():
1026    VISIBLE = 0
1027    ID = 1
1028    NAME = 2
1029    SELECTED = 3
1030##############################
1031#
1032# Data model main class
1033#
1034##############################
1035class Model:
1036    def __init__(self, *args, **kwargs):
1037        check_args('controller',**kwargs)
1038        self.ctl=kwargs['controller']
1039
1040        self.user=None
1041        self.pwd=None
1042        self.is_validated=False
1043        #self.groups=None
1044        #self.profiles=None
1045
1046        self.profile_list=[]
1047        self.group_list=[]
1048
1049        self.cache_lists={}
1050        self.cache_info={'level':0.0,'msg':'','done':False}
1051
1052        self.store_elements = self.ctl.getobj('liststore_elements')
1053        self.store_profiles = self.ctl.getobj('liststore_profiles')
1054        self.n_selected = 0
1055        self.current_relations = {}
1056
1057        self.init_sorted_filtering()
1058        pass
1059
1060    def build_liststore(self,*args,**kwargs):
1061        #
1062        # Build liststore
1063        #
1064        check_args('type',**kwargs)
1065        typearg=kwargs['type']
1066        self.clear_liststore(type=typearg)
1067        if typearg == 'apps':
1068            for t in ['local','system']:
1069                self.add_to_liststore(list=self.cache_lists[t], type=t)
1070        if 'profiles' == typearg:
1071             self.add_to_liststore(list=self.profile_list, type=typearg)
1072
1073
1074    ##############################
1075    #
1076    # Initialize liststore filters & sorting
1077    #
1078    ##############################
1079    def init_sorted_filtering(self,*args,**kwargs):
1080        for objname in ['elements','selected']:
1081            obj=self.ctl.getobj('sort_'+objname)
1082            # Init sorting
1083            sort_column_in_model=APPCOL.NAME
1084            obj.set_sort_column_id(sort_column_in_model,Gtk.SortType.ASCENDING)
1085            model_filter=self.ctl.getobj('filter_'+objname)
1086            # Init filters
1087            if objname == 'selected':
1088                model_filter.set_visible_func(self.filter_func_selected)
1089            else:
1090                model_filter.set_visible_func(self.filter_func_apps)
1091
1092
1093    ##############################
1094    #
1095    # Empty liststores
1096    #
1097    ##############################
1098    def clear_liststore(self,*args,**kwargs):
1099        check_args('type', **kwargs)
1100        store=kwargs['type']
1101        if store == 'apps':
1102            self.store_elements.clear()
1103        if 'profiles' == store:
1104            self.store_profiles.clear()
1105
1106    ##############################
1107    #
1108    # Set selections into liststore
1109    #
1110    ##############################
1111    def set_liststore_selections(self,*args,**kwargs):
1112        check_args('list','type', **kwargs)
1113        store = kwargs['type']
1114        app_list = kwargs['list']
1115        app_list = [name.lower() for name in app_list]
1116        current_store = None
1117        id_element_selection = None
1118        id_element_name = None
1119
1120        if store == 'apps':
1121            current_store = self.store_elements
1122            id_element_selection = APPCOL.SELECTED
1123            id_element_name = APPCOL.BASENAME
1124        if 'profiles' == store:
1125            current_store = self.store_profiles
1126            id_element_selection = PROFCOL.SELECTED
1127            id_element_name = PROFCOL.NAME
1128        # If name not exist, all selections are cleared
1129        for x in current_store:
1130            if x[id_element_name].lower() in app_list:
1131                x[id_element_selection] = True
1132                app_list.remove(x[id_element_name].lower())
1133            elif x[id_element_selection]:
1134                x[id_element_selection] = False
1135            if len(app_list) == 0:
1136                break
1137        return
1138
1139    ##############################
1140    #
1141    # Get current selections from liststore
1142    #
1143    ##############################
1144    def get_liststore_selections(self,*args,**kwargs):
1145        check_args('type',**kwargs)
1146        store=kwargs['type']
1147
1148        current_store = None
1149        if store == 'apps':
1150            current_store = self.store_elements
1151            id_element_selection = APPCOL.SELECTED
1152            id_element_target = APPCOL.BASENAME
1153        if store == 'profiles':
1154            current_store = self.store_profiles
1155            id_element_selection = PROFCOL.SELECTED
1156            id_element_target = PROFCOL.NAME
1157
1158        applist = []
1159        for x in current_store:
1160            if x[id_element_selection]:
1161                applist.append(x[id_element_target])
1162        return applist
1163
1164    ##############################
1165    #
1166    # Get current assigned group from profile or None
1167    #
1168    ##############################
1169    def get_group_from_profile(self,*args,**kwargs):
1170        check_args('nameprofile',**kwargs)
1171        name=kwargs['nameprofile']
1172        name=name.strip()
1173
1174        for p in self.profile_list:
1175            if p[0].lower() == name.lower():
1176                return p[1]
1177        return None
1178
1179    ##############################
1180    #
1181    # Add list with data into liststore
1182    #
1183    ##############################
1184    def add_to_liststore(self,*args,**kwargs):
1185        check_args('list','type',**kwargs)
1186        lapps=kwargs['list']
1187        typearg=kwargs['type']
1188        if typearg == 'local' or typearg == 'system':
1189            if typearg == 'local':
1190                i = 1
1191            else:
1192                i = 1000
1193            for item in lapps:
1194                #
1195                # Item
1196                # [0]icon_path(string), [1]name(string), [2]desktop_basename(string), [3]path_to_desktop_or_dockitem(string), [4]description(string)
1197                # Liststore:
1198                # [0]visible(bool), [1]type(string), [2]id(int(1<local<1000<system)), [3]icon(GdkPixbuf), [4]name(string), [5]description(string), [6]selected(bool), [7]desktop_basename(string)
1199                #
1200
1201                # Create GdkPixbuf
1202                if item[0] == None:
1203                    # ICON is NONE
1204                    pix = GdkPixbuf.Pixbuf.new_from_file_at_size('noicon.png', 24, 24)
1205                else:
1206                    try:
1207                        pix = GdkPixbuf.Pixbuf.new_from_file_at_size(item[0], 24, 24)
1208                    except:
1209                        dbg('Error generating pixbuf {}'.format(item[0]))
1210                        pix = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, False, 8, 24, 24)
1211                if item[4] == None:
1212                    # DESCRIPTION is NONE
1213                    desc = 'No description in desktop file'
1214                else:
1215                    desc = item[4]
1216                self.store_elements.append([True,typearg,i,pix,item[1],desc,False,item[2]])
1217                i += 1
1218        elif typearg == 'profiles':
1219            i=0
1220            for p in self.profile_list:
1221                self.store_profiles.append([True,i,p[0],False])
1222                i += 1
1223
1224    ##############################
1225    #
1226    # Purge duplicated items from cache lists
1227    #
1228    ##############################
1229    def purge_duplicated_cache_items(self,*args,**kwargs):
1230        l = []
1231        # Item
1232        # [0]icon_path(string), [1]name(string), [2]desktop_basename(string), [3]path_to_desktop_or_dockitem(string), [4]description(string)
1233        FILTER_COL=1
1234        for type in ['system','local']:
1235            for i in self.cache_lists[type]:
1236                if not i[FILTER_COL] in l:
1237                    l.append(i[FILTER_COL])
1238                else:
1239                    dbg('Duplicated entry {}'.format(i[FILTER_COL]))
1240                    self.cache_lists[type].remove(i)
1241
1242    ##############################
1243    #
1244    # Visible function 4 selected treeview
1245    #
1246    ##############################
1247    def filter_func_selected(self,*args,**kwargs):
1248        model=args[0]
1249        iter=args[1]
1250        is_selected = model.get_value(iter,APPCOL.SELECTED)
1251        if is_selected:
1252            return True
1253        return False
1254    ##############################
1255    #
1256    # Visible function 4 system elements treeview
1257    #
1258    ##############################
1259    def filter_func_apps(self,*args,**kwargs):
1260        model = args[0]
1261        iter = args[1]
1262
1263        selected = model.get_value(iter, APPCOL.SELECTED)
1264        if selected:
1265            return False
1266
1267        name = model.get_value(iter,APPCOL.NAME)
1268        description = model.get_value(iter,APPCOL.DESCRIPTION)
1269
1270        entry_text=self.ctl.window.get_entry('entry_filter')
1271        entry_text=entry_text[0].lower().strip()
1272        if entry_text == '':
1273            return True
1274
1275        if entry_text in name.lower() or entry_text in description:
1276            return True
1277        return False
1278
1279    ##############################
1280    #
1281    # Trigger refilter on treeview
1282    #
1283    ##############################
1284    def refilter(self,*args,**kwargs):
1285        check_args('type',**kwargs)
1286        type_refilter=kwargs['type']
1287
1288        model_filter_elements=self.ctl.getobj('filter_elements')
1289        model_filter_elements.refilter()
1290        if type_refilter == 'elements':
1291            return
1292        model_filter_selected=self.ctl.getobj('filter_selected')
1293        model_filter_selected.refilter()
1294        return
1295
1296    ##############################
1297    #
1298    # Count selected apps
1299    #
1300    ##############################
1301    def count_selected(self,*args,**kwargs):
1302        i=0
1303        for a in self.store_elements:
1304           if a[APPCOL.SELECTED]:
1305               i+=1
1306        self.n_selected=i
1307        return i
1308
1309    ##############################
1310    #
1311    # Load apps readed from filesystem into liststore before show profile window
1312    #
1313    ##############################
1314    def load_profile_into_liststore(self,*args,**kwargs):
1315        check_args('apps_selected',**kwargs)
1316        apps_selected=kwargs['apps_selected']
1317
1318        # Clear selections is done when setting selections
1319        self.set_liststore_selections(list=apps_selected,type='apps')
1320        return
1321
1322    ##############################
1323    #
1324    # Check valid profile name
1325    #
1326    ##############################
1327    def check_profile_name(self,*args,**kwargs):
1328        check_args('name','type',**kwargs)
1329        name = kwargs['name']
1330        typearg = kwargs['type']
1331        name = name.strip()
1332        name = name.lower()
1333
1334        if name == '':
1335            return False
1336
1337        if typearg == 'available':
1338            ret_pr = True
1339        else:
1340            ret_pr = False
1341
1342        for p in self.profile_list:
1343            if typearg == 'available':
1344                if p[0].lower() == name.lower():
1345                    ret_pr = False
1346            elif typearg == 'valid':
1347                if p[0].lower() == name.lower():
1348                    ret_pr = True
1349        return ret_pr
1350
1351##############################
1352#
1353# Controller class
1354#
1355##############################
1356class Controller:
1357    def __init__(self,*args,**kwargs):
1358        self.aborting_all_operations = False
1359        self.builder=self.load_glade(**kwargs)
1360
1361        #
1362        # Main objects
1363        #
1364        self.helper=Helper(controller=self)
1365        self.model=Model(controller=self)
1366        self.handler=Handler(controller=self)
1367        self.window=MainWindow(controller=self,handler=self.handler)
1368        #
1369        # Connect signals
1370        #
1371        self.builder.connect_signals(self.handler)
1372
1373        # Bypass combobox signal handler
1374        self.building_view = False
1375
1376        #
1377        # Remember what user are doing
1378        #
1379        self.last_window = None
1380        # new_profile or the profile_name what is currently being modified
1381        self.user_is_editing=None
1382        # Flags to remember what changed into window
1383        self.profile_name_changed = None
1384        self.applist_changed = None
1385
1386        #
1387        # Init actions
1388        #
1389        self.reset_memories()
1390        self.process(action='initialized')
1391        return
1392
1393    ##############################
1394    #
1395    # Emergency exit, stops pending threads & silence errors when called destroy from gui
1396    #    (not catching TERM or KILL signals from terminal)
1397    #
1398    ##############################
1399    def kill_all_pending_operations(self,*args,**kwargs):
1400        self.aborting_all_operations = True
1401        for t in self.helper._thread:
1402            if t != None:
1403                terminate_thread(t)
1404    #
1405    # Reset the status flags
1406    #
1407    def reset_memories(self,*args,**kwargs):
1408        self.last_window = None
1409        self.user_is_editing = None
1410        self.profile_name_changed = False
1411        self.applist_changed = False
1412    #
1413    # Load glade file
1414    #
1415    def load_glade(self,*args,**kwargs):
1416        if kwargs['localpath']:
1417            gladefile = kwargs['localpath']+'/'+"gui_v2b.glade"
1418        builder = Gtk.Builder()
1419        dbg('Loading '+gladefile)
1420        builder.add_from_file(gladefile)
1421        return builder
1422    #
1423    # Get objects from builder
1424    #
1425    def getobj(self,*args,**kwargs):
1426        return self.builder.get_object(args[0])
1427    ##############################
1428    #
1429    # Build database cache process
1430    #
1431    ##############################
1432    #@trace
1433    def do_cache(self,*args,**kwargs):
1434        #
1435        # Get complete filelist from filesystem
1436        #
1437        self.model.cache_info['msg']=_('Building file cache...')
1438        files_cache = glob.glob('/usr/share/icons/**/*.png', recursive=True)
1439        #
1440        # Get current user Plank elements
1441        #
1442        self.model.cache_info['msg']="Searching local apps..."
1443        self.model.cache_lists['local']=self.helper.search_local_plank(files_cache=files_cache)
1444        #
1445        # Get data from applications from /usr/share/application/*.desktop
1446        #
1447        self.model.cache_info['msg'] = "Searching system apps..."
1448        self.model.cache_lists['system']=self.helper.search_in_applications(cache_info=self.model.cache_info,files_cache=files_cache)
1449        #
1450        # Purge possible duplicated entries
1451        #
1452        self.model.purge_duplicated_cache_items()
1453        #
1454        self.window.set_txt_status_bars('','','Detected apps: Plank({}) System({})'.format(len(self.model.cache_lists['local']),len(self.model.cache_lists['system'])))
1455        self.helper.clean_status_bar(2)
1456        #
1457        # End cache process
1458        #
1459        self.model.cache_info['msg'] = _('Ready!')
1460        self.model.cache_info['done']=True
1461        #
1462        # Hide level bar
1463        #
1464        self.window.change_stack('next',stack='bottom')
1465
1466    #
1467    # Receives events from handler
1468    #
1469    #@trace
1470    def process(self,*args,**kwargs):
1471        try:
1472            #
1473            # Check arguments and unify commands
1474            # Mandatory 'action'
1475            # Optional: 'result': if it comes from callback
1476            #           'to': changing windows from button menu
1477            #
1478            actions=[]
1479            try:
1480                check_args('action',**kwargs)
1481            except Exception as e:
1482                dbg(e)
1483                sys.exit(1)
1484
1485            if type(kwargs['action']) == type(list()):
1486                actions.extend(kwargs['action'])
1487            else:
1488                actions.append(kwargs['action'])
1489            for op in args:
1490                if type(op) == type(list()):
1491                    actions.extend(op)
1492                else:
1493                    actions.append(op)
1494
1495            for action in actions:
1496                if action == 'initialized':
1497                    #
1498                    # Run first actions
1499                    #
1500                    self.helper.do_call(self.window.update_status_bar,self.model.cache_info,None)
1501                    self.helper.do_call(self.do_cache,{'action':'cache_completed'},self.process)
1502
1503                elif action == 'cache_completed':
1504                    if self.model.is_validated:
1505                        # when we are validated and don't have cache, screen shows loading
1506                        # when cache_completed but not validated, do nothing, validation will update window
1507                        self.window.change_stack('box_main_btn')
1508
1509                elif action == 'validation_init':
1510                    #
1511                    # Start the validation process
1512                    #
1513                    self.window.change_button_text(text='Checking',button='btn_validate')
1514                    us,pw=self.window.get_entry('entry_usr','entry_pw')
1515                    self.helper.n4d_validate(user=us,pwd=pw)
1516
1517                elif action == 'validation_end':
1518                    #
1519                    # Callback from n4d validation process
1520                    #
1521                    try:
1522                        check_args('result', **kwargs)
1523                    except Exception as e:
1524                        dbg('N4D fail check system!')
1525                        sys.exit(1)
1526                    result = kwargs['result']
1527                    self.validation_end(result)
1528
1529                elif action == 'goto':
1530                    #
1531                    # Button from main menu pressed
1532                    #
1533                    self.goto(*args,**kwargs)
1534                elif action == 'refilter_elements':
1535                    ##############################
1536                    #
1537                    # refilter when filter entry has changed
1538                    #
1539                    ##############################
1540                    self.model.refilter(type='elements')
1541                elif action == 'swap':
1542                    ##############################
1543                    #
1544                    # change model & refilter when swaps elements
1545                    #
1546                    ##############################
1547                    check_args('id',**kwargs)
1548                    id=kwargs['id']
1549                    self.applist_changed = True
1550                    for e in self.model.store_elements:
1551                        if e[APPCOL.ID]==id:
1552                            e[APPCOL.SELECTED] = not e[APPCOL.SELECTED]
1553                            break
1554                    self.model.refilter(type='all')
1555                    i=self.model.count_selected()
1556                    self.window.set_txt_status_bars('{} {}'.format(i,_('apps selected')))
1557                elif action == 'swap_locals':
1558                    ##############################
1559                    #
1560                    # change model for local elements
1561                    #
1562                    ##############################
1563                    self.applist_changed = True
1564                    for e in self.model.store_elements:
1565                        if e[APPCOL.TYPE]=='local':
1566                            e[APPCOL.SELECTED] = True
1567                    self.model.refilter(type='all')
1568                    i=self.model.count_selected()
1569                    self.window.set_txt_status_bars('{} {}'.format(i,_('apps selected')))
1570                elif action == 'set_groups':
1571                    ##############################
1572                    #
1573                    # callback from n4d_get_system_groups
1574                    #
1575                    ##############################
1576                    try:
1577                        check_args('result',**kwargs)
1578                    except Exception as e:
1579                        dbg('N4D fail check system!')
1580                        sys.exit(1)
1581                    self.model.group_list = []
1582                    for g in [x['cn'][0] for x in kwargs['result']]:
1583                        self.model.group_list.append(g)
1584
1585                elif action == 'set_profiles':
1586                    ##############################
1587                    #
1588                    # callback from n4d_get_profiles_stored
1589                    #
1590                    ##############################
1591                    try:
1592                        check_args('result',**kwargs)
1593                    except Exception as e:
1594                        dbg('N4D fail check system!')
1595                        sys.exit(1)
1596                    self.model.profile_list = []
1597                    for p in kwargs['result']:
1598                        self.model.profile_list.append(p)
1599                        for g in p[1].split(','):
1600                            if g != '':
1601                                self.model.current_relations[g]=p[0]
1602
1603                elif action == 'remove_profile_done':
1604                    ##############################
1605                    #
1606                    # Callback from n4d_remove_profile
1607                    #
1608                    ##############################
1609
1610                    # Update information about existent profiles
1611                    self.model.clear_liststore(type='profiles')
1612                    self.helper.n4d_get_profiles_stored(user=self.model.user, pwd=self.model.pwd)
1613                    self.window.change_stack('box_main_btn')
1614
1615                elif action == 'profile_is_updated':
1616                    ##############################
1617                    #
1618                    # Callback from n4d_update_profile
1619                    #
1620                    ##############################
1621
1622                    # Update information about existent profiles
1623                    self.model.clear_liststore(type='profiles')
1624                    self.helper.n4d_get_profiles_stored(user=self.model.user, pwd=self.model.pwd)
1625                    check_args('name',**kwargs)
1626                    name = kwargs['name']
1627                    # If this is a (single) callback from edit profiles
1628                    if self.last_window != 'box_group_edit':
1629                        self.reset_memories()
1630                        self.window.set_txt_status_bars(name + _(' updated!'))
1631                        self.helper.clean_status_bar(0)
1632                        self.window.change_stack('box_main_btn')
1633                    else:
1634                    # In this is a (multiple or single) callback from profile-group change relation
1635                        check_args('status_bar',**kwargs)
1636                        bar = kwargs['status_bar']
1637                        if bar == 'old':
1638                            # old profile msg is always showed into first statusbar
1639                            self.window.set_txt_status_bars(bar_info={'0': name + _(' updated!')})
1640                            self.helper.clean_status_bar(0)
1641                        if bar == 'new':
1642                            # old profile msg is always showed into second statusbar
1643                            self.window.set_txt_status_bars(bar_info={'1': name + _(' updated!')})
1644                            self.helper.clean_status_bar(1)
1645
1646
1647                elif action == 'profile_is_renamed':
1648                    ##############################
1649                    #
1650                    # Callback from n4d_rename_profile
1651                    #
1652                    ##############################
1653
1654                    # Update information about existent profiles
1655                    self.model.clear_liststore(type='profiles')
1656                    self.helper.n4d_get_profiles_stored(user=self.model.user, pwd=self.model.pwd)
1657                    # Force redo liststore from combobox from profile window, next time will rebuild the view
1658                    for boxname in self.window.group_profile_boxes:
1659                        self.window.group_profile_boxes[boxname][0].destroy()
1660                        self.window.group_profile_boxes[boxname][1].destroy()
1661                    self.window.group_profile_boxes = {}
1662                    self.reset_memories()
1663                    # Show msg into status bar
1664                    check_args('oldname','newname', **kwargs)
1665                    oldname = kwargs['oldname']
1666                    newname = kwargs['newname']
1667                    self.window.set_txt_status_bars(_('Updated profile from ')+oldname+_(' to ')+newname)
1668                    self.helper.clean_status_bar(0)
1669                    # Change user's view
1670                    self.window.change_stack('box_main_btn')
1671
1672                elif action == 'load_profile_into_liststore':
1673                    ##############################
1674                    #
1675                    # Callback from n4d_read_profile
1676                    #
1677                    ##############################
1678                    check_args('name','result',**kwargs)
1679                    apps_selected=kwargs['result']
1680                    self.model.build_liststore(type='apps')
1681                    self.model.load_profile_into_liststore(name=kwargs['name'], apps_selected=apps_selected)
1682                    i = self.model.count_selected()
1683                    self.window.set_txt_status_bars('{} {}'.format(i,_('apps selected')))
1684                    self.window.set_entry({'entry_profile_name':kwargs['name']})
1685                    self.reset_memories()
1686                    self.user_is_editing=kwargs['name']
1687                    self.window.change_stack('box_profiles')
1688
1689                elif action == 'change_relations':
1690                    check_args('group','profile',**kwargs)
1691                    group = kwargs['group']
1692                    profile = kwargs['profile']
1693                    oldrelation = ''
1694                    if group in self.model.current_relations:
1695                        oldrelation = self.model.current_relations[group]
1696                    #
1697                    # Sanity checks about relations
1698                    #
1699                    if self.check_sanity(group=group,profile=profile):
1700                        self.update_profile_relations(group=group, profile=profile, oldrelation=oldrelation)
1701
1702
1703            return
1704        except Exception as e:
1705            if not self.aborting_all_operations:
1706                raise Exception(e)
1707            else:
1708                sys.exit(1)
1709    ##############################
1710    #
1711    # Validation checking actions
1712    #
1713    ##############################
1714    def validation_end(self,*args,**kwargs):
1715        result = args[0]
1716        self.window.change_button_text(text='Validate', button='btn_validate')
1717        #
1718        # Check the completed validation
1719        #
1720        return_str = result.split(' ')
1721        if return_str[0].lower() == 'true' and return_str[1].lower() in ['admin', 'adm', 'admins', 'teachers']:
1722            # Validation is done and sucessful
1723            us, pw = self.window.get_entry('entry_usr', 'entry_pw')
1724            self.model.user = us
1725            self.model.pwd = pw
1726            self.model.is_validated = True
1727            #
1728            # Get information about existent groups
1729            #
1730            self.helper.n4d_get_system_groups(user=us,pwd=pw)
1731            #
1732            # Get information about existent profiles
1733            #
1734            self.helper.n4d_get_profiles_stored(user=us,pwd=pw)
1735            #
1736            # Update content window
1737            #
1738            if self.model.cache_info['done']:
1739                self.window.change_stack('box_main_btn')
1740            else:
1741                self.window.change_stack('box_loading')
1742        else:
1743            # Validation is failed
1744            self.window.set_entry({'entry_usr': '', 'entry_pw': ''})
1745            self.getobj('img_emoji').set_from_icon_name('face-crying', 100)
1746    ##############################
1747    #
1748    # Change window
1749    #
1750    ##############################
1751    def goto(self,*args,**kwargs):
1752        check_args('to', **kwargs)
1753        to = kwargs['to']
1754        if to == 'main_window':
1755            self.reset_memories()
1756            self.window.set_txt_status_bars('')
1757            self.window.change_stack('box_main_btn')
1758        if to == 'new_profile':
1759            self.window.set_entry({'entry_profile_name':''})
1760            self.model.build_liststore(type='apps')
1761            self.user_is_editing='new_profile'
1762            self.window.change_stack('box_profiles')
1763        if to == 'edit_profile':
1764            self.model.build_liststore(type='profiles')
1765            self.window.change_stack('box_pselector')
1766        if to == 'group_mapping':
1767            self.building_view = True
1768            self.window.build_group_profile_window(groups=self.model.group_list,profiles=self.model.profile_list)
1769            self.building_view = False
1770            self.window.change_stack('box_group_edit')
1771        if to == 'ask_remove':
1772            check_args('id', **kwargs)
1773            id = kwargs['id']
1774            name_profile = self.model.profile_list[id][0]
1775            txt="{} '{}'?".format(_('Are you sure to remove the profile'),name_profile)
1776            self.window.set_entry({'lbl_msg_dialog':txt})
1777            self.window.change_stack('box_dialog')
1778        if to == 'editing_profile':
1779            check_args('id', **kwargs)
1780            id = kwargs['id']
1781            name_profile=self.model.profile_list[id][0]
1782            self.helper.n4d_read_profile(user=self.model.user,pwd=self.model.pwd,name=name_profile)
1783        if to == 'remove_confirmed':
1784            check_args('id', **kwargs)
1785            id = kwargs['id']
1786            name_profile = self.model.profile_list[id][0]
1787            self.helper.n4d_remove_profile(user=self.model.user,pwd=self.model.pwd,name=name_profile)
1788        if to == 'back':
1789            #
1790            # Check if need to save something
1791            #
1792            if self.last_window == 'box_profiles':
1793                if self.user_is_editing == 'new_profile':
1794                    if self.profile_name_changed or self.applist_changed:
1795                        pname = self.window.get_entry('entry_profile_name')[0]
1796                        if not self.model.check_profile_name(name=pname,type='available'):
1797                            self.window.set_entry({'lbl_msg_warning': _('Profile name not valid')})
1798                            self.window.change_stack('box_warning')
1799                            return
1800                        # Store new profile
1801                        self.update_current_profile()
1802                        return
1803                elif self.user_is_editing != '':
1804                    # User is editing existent profile
1805                    if self.applist_changed and not self.profile_name_changed:
1806                        # Update current profile (self.user_is_editing)
1807                        self.update_current_profile()
1808                    if not self.applist_changed and self.profile_name_changed:
1809                        # Rename current profile (self.user_is_editing)
1810                        newname=self.window.get_entry('entry_profile_name')[0]
1811                        if not self.model.check_profile_name(name=newname,type='available'):
1812                            self.window.set_entry({'lbl_msg_warning': _('Profile name not valid')})
1813                            self.window.change_stack('box_warning')
1814                            return
1815                        self.helper.n4d_rename_profile(user=self.model.user,pwd=self.model.pwd,old=self.user_is_editing,new=newname)
1816                    if self.applist_changed and self.profile_name_changed:
1817                        # create new profile & remove old profile (self.user_is_editing)
1818                        self.update_current_profile()
1819                        #
1820                        # -Uncomment if it's desired to remove old profile when modifications of name and apps are done
1821                        # -If commented, new profile it's created making a copy of the current profile with other name
1822                        # and remove profile only it's possible from the remove menu, doing more explicitly the remove action
1823                        #
1824                        #self.helper.n4d_remove_profile(user=self.model.user,pwd=self.model.pwd,name=self.user_is_editing)
1825                    if self.applist_changed or self.profile_name_changed:
1826                        return
1827
1828            # if nothing is saved do normal go back
1829            self.model.n_selected = 0
1830            self.window.set_txt_status_bars('','')
1831            self.window.change_stack('back')
1832        return
1833
1834    ##############################
1835    #
1836    # Read store & create or update a profile
1837    #
1838    ##############################
1839    def update_current_profile(self,*args,**kwargs):
1840        profile_name = self.window.get_entry('entry_profile_name')[0]
1841        if self.user_is_editing == 'new_profile' and not self.model.check_profile_name(name=profile_name,type='available'):
1842            self.window.set_entry({'lbl_msg_warning':_('Profile name not valid')})
1843            self.window.change_stack('box_warning')
1844            self.reset_memories()
1845            return
1846        applist = self.model.get_liststore_selections(type='apps')
1847        grp = self.model.get_group_from_profile(nameprofile=profile_name)
1848        glist = []
1849        if grp != None:
1850            glist.append(grp)
1851
1852        self.helper.n4d_update_profile(user=self.model.user,pwd=self.model.pwd,name=profile_name,grouplist=glist,applist=applist)
1853        return
1854
1855    ##############################
1856    #
1857    # Update profile assigning one group when changed on combo and it's different
1858    #
1859    ##############################
1860    def update_profile_relations(self,*args,**kwargs):
1861        try:
1862            #
1863            # First call acts as a caller from N4D method setting itself as a callback method
1864            # if haven't group & profile parameters, method is doing callback with 'result' parameter
1865            #
1866            check_args('group', 'profile','oldrelation', **kwargs)
1867            group = kwargs['group']
1868            profile_selected = kwargs['profile']
1869            old_profile = kwargs['oldrelation']
1870            #
1871            # Check if it's needed to update old profile removing relation
1872            #
1873            if self.model.check_profile_name(name=old_profile,type='valid'):
1874                #
1875                # need to kwown the current apps and other groups applied from profile before updating
1876                #
1877                oldgroups = []
1878                for p in self.model.profile_list:
1879                    pname = p[0]
1880                    pgrps = p[1]
1881                    if pname == old_profile:
1882                        for g in pgrps.split(','):
1883                            if g != group and g != '':
1884                                oldgroups.append(g)
1885                self.helper.n4d_read_profile(user=self.model.user, pwd=self.model.pwd, name=old_profile,
1886                                             noprocess=self.update_profile_relations,
1887                                             extraparam={'grouplist': oldgroups,'status_bar':'old'})
1888            if self.model.check_profile_name(name=profile_selected,type='valid'):
1889                #
1890                # need to kwown the current apps and other groups applied from profile before updating
1891                #
1892                oldgroups = []
1893                for p in self.model.profile_list:
1894                    pname = p[0]
1895                    pgrps = p[1]
1896                    if pname == profile_selected:
1897                        for g in pgrps.split(','):
1898                            if g != group and g != '':
1899                                oldgroups.append(g)
1900                newglist = [group]
1901                newglist.extend(oldgroups)
1902                self.helper.n4d_read_profile(user=self.model.user, pwd=self.model.pwd, name=profile_selected,
1903                                             noprocess=self.update_profile_relations,
1904                                             extraparam={'grouplist': newglist,'status_bar':'new'})
1905        except Exception as e:
1906            #
1907            # Second call of this function, when callback from n4d_read_profile is done, 'result' is set into parameters with list applications of profile
1908            #
1909            check_args('grouplist','result','name','status_bar',**kwargs)
1910            applist = kwargs['result']
1911            grouplist = kwargs['grouplist']
1912            name = kwargs['name']
1913            status_bar = kwargs['status_bar']
1914
1915            self.helper.n4d_update_profile(user=self.model.user,pwd=self.model.pwd,applist=applist,name=name, grouplist=grouplist, status_bar=status_bar)
1916            # After this call return to normal callback and will update profile list with new relations
1917        return
1918
1919    ##############################
1920    #
1921    # Sanity logic about relations
1922    #
1923    ##############################
1924    def check_sanity(self,*args,**kwargs):
1925        check_args('group','profile',**kwargs)
1926        group = kwargs['group']
1927        profile = kwargs['profile']
1928
1929        if not group:
1930            return False
1931
1932        # Existence flags
1933        ret_gr = False
1934
1935        for g in self.model.group_list:
1936            if g == group:
1937                # Group must exist
1938                ret_gr = True
1939                break
1940        for p in self.model.profile_list:
1941            pname = p[0]
1942            pgroup = p[1]
1943
1944            for g in pgroup.split(','):
1945                if group == g:
1946                    # Fail if already relationed
1947                    if pname == profile:
1948                        return False
1949
1950        return ret_gr
1951
1952
1953
1954##############################
1955#
1956# Main program
1957#
1958##############################
1959
1960GObject.threads_init()
[4079]1961if __name__ == "__main__":
[4303]1962    ctl = Controller(localpath=os.path.dirname(os.path.realpath(__file__)))
[4079]1963    sys.exit(Gtk.main())
1964
Note: See TracBrowser for help on using the repository browser.