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

Last change on this file since 8100 was 8100, checked in by mabarracus, 15 months ago

Fix bug closing wallpaper fileselector
Fix bug selecting local desktop items
Add compatibility with old profiles

  • Property svn:executable set to *
File size: 103.6 KB
Line 
1#!/usr/bin/env python3
2###################################################
3#                                                 #
4# User interface for syncer-plank                 #
5# Author: M.Angel Juan <m.angel.juan@gmail.com>   #
6# License: GPLv3                                  #
7#                                                 #
8###################################################
9
10##############################
11#
12# Logging & debug helper functions
13#
14##############################
15#import logging
16#from colorlog import ColoredFormatter
17#from functools import wraps
18import time
19#
20#def 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#    )
42#
43#    logger = logging.getLogger(__name__)
44#    handler = logging.StreamHandler()
45#    handler.setFormatter(formatter)
46#    logger.addHandler(handler)
47#    logger.setLevel(logging.DEBUG)
48#
49#    return logger
50#
51#def 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])
69#            else:
70#                out[x]='Skipped'
71#        i=0
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
76#            else:
77#                out['arg' + str(i)] = 'Skipped'
78#            i += 1
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
85#
86#start_time=time.time()
87#logger = setup_logger()
88#
89def dbg(*args,**kwargs):
90    enable=False
91    if enable:
92        for x in args:
93            print(str(x))
94
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
101
102import ctypes
103
104def terminate_thread(thread):
105    """Terminates a python thread from another thread.
106
107    :param thread: a threading.Thread instance
108    """
109    if not thread.isAlive():
110        return
111
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")
122
123##############################
124#
125# Imports section
126#
127##############################
128
129import sys,os
130import gi
131import re
132import threading
133import time
134import glob
135import xmlrpc.client as x
136import ssl
137import subprocess
138import tempfile
139import base64
140import shutil
141
142gi.require_version('Gtk','3.0')
143from gi.repository import Gtk,GdkPixbuf,GObject,Gdk,GLib,Gio
144
145#
146# i18n
147#
148import gettext
149from gettext import gettext as _
150gettext.bindtextdomain('syncer_plank_gui','/usr/share/locale')
151gettext.textdomain('syncer_plank_gui')
152
153##############################
154#
155# Helper functions, threading & n4d mainly
156#
157##############################
158class Helper:
159    #
160    # Helper initialization
161    #
162    def __init__(self,*args,**kwargs):
163        check_args('controller',**kwargs)
164        self.ctl=kwargs['controller']
165        #
166        # Thread pool initialization
167        #
168        self._thread = [None] * 10
169        # self.get_used_thread = lambda : self._thread.index(list(filter(None.__ne__,self._thread))[0])
170        self.get_thread = lambda: self._thread.index(None)
171        #
172        # N4d initialization
173        #
174        self.n4d = x.ServerProxy("https://server:9779", verbose=False, use_datetime=True,
175                             context=ssl._create_unverified_context())
176        self.n4d_sepaphore = threading.BoundedSemaphore(value=1)
177
178    #
179    # Method to create threaded job, with or without callback
180    #
181    #@trace
182    def do_call(self, func, args, callback, nthread=None, prio=GLib.PRIORITY_HIGH_IDLE,block=False):
183        # Local thread number
184        thread = None
185
186        # If we are checking finalization of already created thread
187        if nthread == None:
188            # Is a new call
189            try:
190                try:
191                    # Get a free thread pool space
192                    thread = self.get_thread()
193                    namethread='thread_num_{}'.format(thread)
194                    dbg('Launch {} with {}'.format(namethread,func.__name__))
195                except:
196                    raise Exception('No more threads available')
197
198                # Create thread on pool
199                if  args!= None:
200                    # Thread needs args
201                    self._thread[thread] = threading.Thread(target=func, args=(args,),name=namethread)
202                else:
203                    # Thread without args
204                    self._thread[thread] = threading.Thread(target=func,name=namethread)
205
206                # Need a threaded blocking call?
207                if block:
208                    # Start blocking thread
209                    self._thread[thread].start()
210                    self._thread[thread].join()
211                    # Free thread
212                    self._thread[thread] = None
213
214                    # If has callback, call passing result or return the result of thread directly
215                    if callback == None:
216                        return args['result']
217                    else:
218                        return callback(**args) and False
219                else:
220                    # Start normal thread out of gtk loop (non-blocking)
221                    GObject.idle_add(self._thread[thread].start, priority=prio)
222                    # Program next check thread finalization
223                    GObject.timeout_add(50, self.do_call, func, args, callback, thread, prio,block)
224
225                # Finalize main call, no need to re-initialize timeouted checking
226                return False
227            except:
228                # Something goes wrong :-(
229                raise Exception('Error calling threads')
230        else:
231            # No new call, check if its finalized
232
233            # we use the old thread in pool, already started
234            thread = nthread
235
236            # Check if the job is done
237            if self._thread[thread].isAlive() or not self._thread[thread]._started._flag:
238                # If not done, initialize timeout checking
239                return True
240            else:
241                # Job is done
242                # Free the thread pool space
243                self._thread[thread] = None
244                try:
245                    # If has callback, call passing result or return the result of thread directly
246                    if callback != None:
247                        dbg('Callback from thread_num_{}: running function \'{}\''.format(nthread,callback.__name__))
248                        return callback(**args) and False
249                    else:
250                        # No direct return value is possible due to non blocking call, result only can be catched by asynchronous function
251                        # Finalize main call, no need to re-initialize timeouted checking
252                        return False
253                except Exception as e:
254                    # Something goes wrong :-(
255                    raise e
256
257        # In normal conditions no need to reach this point
258        # Finalize main call, no need to re-initialize timeouted checking
259        return False
260
261
262
263    ##############################
264    #
265    # Search plank elements into user home
266    #
267    ##############################
268    def search_local_plank(self,*args,**kwargs):
269        check_args('files_cache',**kwargs)
270        files_cache=kwargs['files_cache']
271
272        list_apps=[]
273        theme='Vibrancy-Colors'
274
275        try:
276            file_names = [filename for filename in glob.iglob(os.path.expanduser('~') + '/.config/plank/dock1/launchers/*.dockitem', recursive=True)]
277        except Exception as e:
278            return list_apps
279        for filename in file_names:
280            try:
281                basename=os.path.splitext(os.path.basename(filename))[0]
282                if basename == 'desktop':
283                    icon_file='preferences-desktop-wallpaper.png'
284                    complete_filename = [filename for filename in glob.iglob('/usr/share/icons/' + theme + '/**/' + icon_file, recursive=True)][0]
285                    name='Show desktop'
286                    if complete_filename:
287                        list_apps.append((complete_filename,basename,basename,filename,name))
288                elif basename == 'matecc':
289                    icon_file='preferences-desktop.png'
290                    complete_filename = [filename for filename in glob.iglob('/usr/share/icons/' + theme + '/**/'+icon_file, recursive=True)][0]
291                    name='Control center'
292                    if complete_filename:
293                        list_apps.append((complete_filename,basename,basename,filename,name))
294                else:
295                    complete_filename=[filename for filename in glob.iglob('/usr/share/applications/'+basename+'.desktop', recursive=True)][0]
296                    result=self.scan_desktop_file(filename=complete_filename,files_cache=files_cache)
297                    if result:
298                        list_apps.append(result)
299            except Exception as e:
300                dbg('{} processing local filename \'{}\''.format(str(e),filename))
301                pass
302
303        return list_apps
304
305    ##############################
306    #
307    # Search desktop elements into user desktop
308    #
309    ##############################
310    def get_desktop_folder(self,*args,**kwargs):
311        name = None
312        try:
313            name = subprocess.check_output(['xdg-user-dir','DESKTOP']).decode().strip()
314        except Exception as e:
315            dbg('{} getting desktop folder from xdg'.format(str(e), filename))
316        return name
317
318    ##############################
319    #
320    # Search desktop elements into user desktop
321    #
322    ##############################
323    def search_local_desktops(self, *args, **kwargs):
324        check_args('files_cache', **kwargs)
325        files_cache = kwargs['files_cache']
326
327        list_apps = []
328        theme = 'Vibrancy-Colors'
329
330        desktop_folder = self.get_desktop_folder()
331
332        try:
333            file_names = [filename for filename in
334                          glob.iglob(desktop_folder+'/*.desktop',
335                                     recursive=True)]
336        except Exception as e:
337            return list_apps
338
339        for filename in file_names:
340            try:
341                basename = os.path.splitext(os.path.basename(filename))[0]
342                if basename == 'desktop':
343                    icon_file = 'preferences-desktop-wallpaper.png'
344                    complete_filename = [filename for filename in
345                                         glob.iglob('/usr/share/icons/' + theme + '/**/' + icon_file,
346                                                    recursive=True)][0]
347                    name = 'Show desktop'
348                    if complete_filename:
349                        list_apps.append((complete_filename, basename, basename, filename, name))
350                elif basename == 'matecc':
351                    icon_file = 'preferences-desktop.png'
352                    complete_filename = [filename for filename in
353                                         glob.iglob('/usr/share/icons/' + theme + '/**/' + icon_file,
354                                                    recursive=True)][0]
355                    name = 'Control center'
356                    if complete_filename:
357                        list_apps.append((complete_filename, basename, basename, filename, name))
358                else:
359                    result = self.scan_desktop_file(filename=filename, files_cache=files_cache)
360                    if result:
361                        list_apps.append(result)
362            except Exception as e:
363                dbg('{} processing local filename \'{}\''.format(str(e), filename))
364
365        return list_apps
366
367    ##############################
368    #
369    # Search location of icon appname
370    #
371    ##############################
372    def search_icon_file(self,*args,**kwargs):
373        check_args('files_cache','icon',**kwargs)
374        files_cache=kwargs['files_cache']
375        nameicon=kwargs['icon']
376
377        try:
378            file_names=''
379            txt='(.*'+nameicon+'.png)'
380            regexp=re.compile(txt)
381            for filename in files_cache:
382                m=regexp.match(filename)
383                if m:
384                    file_names=m.group(1)
385                    break
386        except Exception as e:
387            raise(e)
388        if len(file_names) == 0:
389            raise Exception('Icon file {} not found'.format(nameicon))
390        return file_names
391
392    ##############################
393    #
394    # Parse & extract info from desktop file
395    #
396    ##############################
397    def scan_desktop_file(self,*args,**kwargs):
398        check_args('files_cache','filename',**kwargs)
399        filename=kwargs['filename']
400        files_cache=kwargs['files_cache']
401
402        regexp1 = re.compile('Icon=(.*)',re.IGNORECASE)
403        regexp2 = re.compile('Name=(.*)', re.IGNORECASE)
404        regexp3 = re.compile('Comment=(.*)',re.IGNORECASE)
405        try:
406            with open(filename, 'r') as f:
407                lines = f.readlines()
408                icon = None
409                appname = None
410                description = None
411                for line in lines:
412                    m1 = regexp1.match(line)
413                    m2 = regexp2.match(line)
414                    m3 = regexp3.match(line)
415                    if m1 and icon == None:
416                        icon = m1.group(1)
417                    if m2 and appname == None:
418                        appname = m2.group(1)
419                    if m3 and m3.group(1):
420                        description=m3.group(1)
421
422                #
423                # Commented due to allow desktops with no icon or description
424                #
425                #if icon and appname and description:
426                if appname:
427                    desktop_basename = os.path.splitext(os.path.basename(filename))[0]
428                    if icon != None:
429                        if 'png' in icon.split('.'):
430                            icon_path = icon
431                        else:
432                            try:
433                                icon_path = self.search_icon_file(icon=icon,files_cache=files_cache)
434                            except Exception as e:
435                                # Allow tolerance if no icon is found
436                                icon_path = None
437                        if desktop_basename:
438                            return (icon_path, appname, desktop_basename,filename,description)
439                    else:
440                        # icon or description are None
441                        return (icon,appname,desktop_basename,filename,description)
442                else:
443                #    raise Exception('Can\'t get icon/appname/description from desktop file \'{}\''.format(filename))
444                     raise Exception('Can\'t get appname from desktop file \'{}\''.format(filename))
445        except Exception as e:
446            raise(e)
447
448    ##############################
449    #
450    # Search system applications
451    #
452    ##############################
453    def search_in_applications(self,*args,**kwargs):
454        check_args('cache_info','files_cache',**kwargs)
455        info=kwargs['cache_info']
456        files_cache=kwargs['files_cache']
457
458        try:
459            file_names = [filename for filename in glob.iglob('/usr/share/applications/*.desktop', recursive=True)]
460        except:
461            return []
462        # LIMIT
463        #file_names = file_names[1:50]
464        outlist=[]
465        procesed_items=0
466        total_items=len(file_names)
467
468        info['msg'] = _("Scanning system apps...")
469        # FILTER SOME ELEMENTS WITH SAME NAME
470        not_repeated_list = {}
471        filter_column = 1
472        column4change_if_seems_the_same = 2
473        for desktop in file_names:
474            try:
475                result=self.scan_desktop_file(filename=desktop,files_cache=files_cache)
476                if result:
477                    if result[filter_column] not in not_repeated_list:
478                        not_repeated_list[result[filter_column]]=result
479                    else:
480                        # if seems the same element change 'name' with 'basename'
481                        # First change old element found
482                        temp1=list(not_repeated_list[result[filter_column]])
483                        del not_repeated_list[result[filter_column]]
484                        temp1[filter_column]=temp1[column4change_if_seems_the_same]
485                        not_repeated_list[result[column4change_if_seems_the_same]]=tuple(temp1)
486                        # Second insert new element found
487                        temp2=list(result)
488                        temp2[filter_column]=temp2[column4change_if_seems_the_same]
489                        not_repeated_list[result[column4change_if_seems_the_same]]=tuple(temp2)
490            except Exception as e:
491                dbg(e)
492                pass
493
494            procesed_items += 1
495            info['level']=procesed_items/total_items
496
497        for x in not_repeated_list:
498            outlist.append(not_repeated_list[x])
499        return outlist
500
501    ##############################
502    #
503    # Clean status_bar_with_timeout
504    #
505    ##############################
506    def clean_status_bar(self,*args,**kwargs):
507        def sleep_and_clean(x):
508            time.sleep(x.get('sleep'))
509            bar_info = {str(x.get('num_bar')): ''}
510            self.ctl.window.set_txt_status_bars(bar_info=bar_info)
511
512        self.do_call(sleep_and_clean,{'sleep':3,'num_bar':args[0]},None)
513
514        return
515
516    ##############################
517    #
518    # N4D validation call
519    #
520    ##############################
521    #@trace
522    def n4d_validate(self,*args,**kwargs):
523        check_args('user','pwd',**kwargs)
524        d = {'args': (kwargs['user'], kwargs['pwd']),'action':'validation_end'}
525        #login_func = lambda x: x.update({'result': self.n4d.login(x.get('args'), 'Golem', x.get('args'))})
526        #@trace
527        def login_func(x):
528            try:
529                self.n4d_sepaphore.acquire()
530                x.update({'result': self.n4d.login(x.get('args'), 'Golem', x.get('args'))})
531                self.n4d_sepaphore.release()
532            except Exception as e:
533                raise Exception(e)
534        return self.do_call(login_func, d, self.ctl.process)
535
536    ##############################
537    #
538    # N4D call to get ldap groups
539    #
540    ##############################
541    #@trace
542    def n4d_get_system_groups(self,*args,**kwargs):
543        check_args('user','pwd',**kwargs)
544        d = {'args': (kwargs['user'],kwargs['pwd']),'action':'set_groups'}
545        #get_groups = lambda x: x.update({'result': self.n4d.get_available_groups(x.get('args'), 'Golem')})
546        #@trace
547        def get_groups(x):
548            try:
549                self.n4d_sepaphore.acquire()
550                x.update({'result': self.n4d.get_available_groups(x.get('args'), 'Golem')})
551                self.n4d_sepaphore.release()
552            except Exception as e:
553                raise Exception(e)
554        return self.do_call(get_groups, d, self.ctl.process)
555
556    ##############################
557    #
558    # N4D call to get currently stored profiles
559    #
560    ##############################
561    #@trace
562    def n4d_get_profiles_stored(self,*args,**kwargs):
563        check_args('user','pwd',**kwargs)
564        d = {'args': (kwargs['user'],kwargs['pwd']),'action':'set_profiles'}
565        #get_profiles = lambda x: x.update({'result': self.n4d.get_profiles(x.get('args'),'PlankSync')})
566        #@trace
567        def get_profiles(x):
568            try:
569                self.n4d_sepaphore.acquire()
570                x.update({'result': self.n4d.get_profiles(x.get('args'), 'PlankSync')})
571                self.n4d_sepaphore.release()
572            except Exception as e:
573                raise Exception(e)
574        return self.do_call(get_profiles, d, self.ctl.process)
575
576    ##############################
577    #
578    # N4D call to remove profile on filesystem
579    #
580    ##############################
581    #@trace
582    def n4d_remove_profile(self,*args,**kwargs):
583        check_args('user','pwd','name',**kwargs)
584        d = {'args': (kwargs['user'],kwargs['pwd']), 'name': kwargs['name'],'action':'remove_profile_done'}
585        #remove_profile = lambda x: x.update({'result': self.n4d.remove_profile(x.get('args'), 'PlankSync', x.get('name'))})
586        #@trace
587        def remove_profile(x):
588            try:
589                self.n4d_sepaphore.acquire()
590                x.update({'result': self.n4d.remove_profile(x.get('args'), 'PlankSync', x.get('name'))})
591                self.n4d_sepaphore.release()
592            except Exception as e:
593                raise Exception(e)
594        return self.do_call(remove_profile, d, self.ctl.process)
595
596    ##############################
597    #
598    # N4D call to get details about profile
599    #
600    ##############################
601    #@trace
602    def n4d_read_profile(self,*args,**kwargs):
603        #
604        # Option to set new parameters
605        #
606        d = {}
607        try:
608            check_args('extraparam',**kwargs)
609            for nameparam in kwargs['extraparam']:
610                d[nameparam]=kwargs['extraparam'][nameparam]
611        except:
612            pass
613        check_args('user', 'pwd', 'name', **kwargs)
614        d['args'] = (kwargs['user'],kwargs['pwd'])
615        d['name'] = kwargs['name']
616        d['action'] = 'load_profile_into_liststore'
617        #get_profile = lambda x: x.update({'result': self.window.n4d.read_profile(x.get('args'), 'PlankSync', x.get('name'))})
618        #@trace
619        def get_profile(x):
620            try:
621                self.n4d_sepaphore.acquire()
622                x.update({'result': self.n4d.read_profile(x.get('args'), 'PlankSync', x.get('name'))})
623                self.n4d_sepaphore.release()
624            except Exception as e:
625                raise Exception(e)
626        #
627        # Multiple callback is possible if not needed to do normal processing using 'noprocess' parameter
628        #
629        try:
630            check_args('noprocess',**kwargs)
631            new_callback_func=kwargs['noprocess']
632            callback=new_callback_func
633        except:
634            callback=self.ctl.process
635
636        return self.do_call(get_profile, d, callback)
637
638    ##############################
639    #
640    # N4D call to rename profile
641    #
642    ##############################
643    # def n4d_rename_profile(self,*args,**kwargs):
644    #     check_args('user','pwd','old','new',**kwargs)
645    #     d = {'args': (kwargs['user'],kwargs['pwd']), 'oldname': kwargs['old'], 'newname': kwargs['new'],'action':'profile_is_renamed'}
646    #     #rename_profile = lambda x: x.update({'result': self.n4d.rename_profile(x.get('args'), 'PlankSync', x.get('oldname'), x.get('newname'))})
647    #     def rename_profile(x):
648    #         try:
649    #             self.n4d_sepaphore.acquire()
650    #             x.update({'result': self.n4d.rename_profile(x.get('args'), 'PlankSync', x.get('oldname'), x.get('newname'))})
651    #             self.n4d_sepaphore.release()
652    #         except Exception as e:
653    #             raise Exception(e)
654    #     return self.do_call(rename_profile, d, self.ctl.process)
655
656    ##############################
657    #
658    # N4D call to create or update profile
659    #
660    ##############################
661    def n4d_update_profile(self,*args,**kwargs):
662        check_args('user','pwd','name','newname','grouplist','applist',**kwargs)
663        d = {'args': (kwargs['user'],kwargs['pwd']), 'name': kwargs['name'], 'newname': kwargs['newname'], 'group': kwargs['grouplist'], 'applist': kwargs['applist'], 'action':'profile_is_updated'}
664        try:
665            check_args('status_bar',**kwargs)
666            d.update({'status_bar':kwargs['status_bar']})
667        except:
668            pass
669        #update_profile = lambda x: x.update({'result': self.n4d.update_profile(x.get('args'), 'PlankSync', x.get('name'), x.get('group'),x.get('applist'))})
670        def update_profile(x):
671            try:
672                self.n4d_sepaphore.acquire()
673                x.update({'result': self.n4d.update_profile(x.get('args'), 'PlankSync', x.get('name'), x.get('newname'), x.get('group'),
674                                                            x.get('applist'))})
675                self.n4d_sepaphore.release()
676            except Exception as e:
677                raise Exception(e)
678        #
679        # Multiple callback is possible if not needed to do normal processing using 'noprocess' parameter
680        #
681        try:
682            check_args('noprocess', **kwargs)
683            new_callback_func = kwargs['noprocess']
684            callback = new_callback_func
685        except:
686            callback = self.ctl.process
687        return self.do_call(update_profile, d, callback)
688
689    def n4d_update_profile_groups(self,*args,**kwargs):
690        check_args('user','pwd','name','grouplist',**kwargs)
691        d = {'args': (kwargs['user'],kwargs['pwd']), 'name': kwargs['name'], 'group': kwargs['grouplist'], 'action':'profile_is_updated'}
692        try:
693            check_args('status_bar',**kwargs)
694            d.update({'status_bar':kwargs['status_bar']})
695        except:
696            pass
697        #update_profile = lambda x: x.update({'result': self.n4d.update_profile(x.get('args'), 'PlankSync', x.get('name'), x.get('group'),x.get('applist'))})
698        def update_profile_groups(x):
699            try:
700                self.n4d_sepaphore.acquire()
701                x.update({'result': self.n4d.update_profile_groups(x.get('args'), 'PlankSync', x.get('name'), x.get('group'))})
702                self.n4d_sepaphore.release()
703            except Exception as e:
704                raise Exception(e)
705        #
706        # Multiple callback is possible if not needed to do normal processing using 'noprocess' parameter
707        #
708        try:
709            check_args('noprocess', **kwargs)
710            new_callback_func = kwargs['noprocess']
711            callback = new_callback_func
712        except:
713            callback = self.ctl.process
714        return self.do_call(update_profile_groups, d, callback)
715
716    def prepare_workspace(self,*args,**kwargs):
717        check_args('profile',**kwargs)
718        profile = kwargs['profile']
719        workspace = []
720        try:
721            tempdir = tempfile.mkdtemp()
722            for list_item in profile:
723                type_item = list_item[0]
724                content_item = list_item[1]
725                if type_item == 'users':
726                    workspace.append(['users',content_item])
727                elif type_item == 'file':
728                    if content_item[0] == 'encoded':
729                        fp = tempfile.NamedTemporaryFile(mode='wb',delete=False, dir=tempdir)
730                        the_file = fp.name
731                        fp.write(base64.b64decode(content_item[2]))
732                        if not os.path.exists(tempdir+'/'+os.path.basename(content_item[1])):
733                            shutil.move(the_file,tempdir+'/'+os.path.basename(content_item[1]))
734                            the_file = tempdir+'/'+os.path.basename(content_item[1])
735                        workspace.append(['file',the_file])
736                elif type_item == 'comm':
737                    workspace.append('comm')
738                elif type_item == 'dconf':
739                    workspace.append('dconf')
740                elif type_item in ['desktop','plank']:
741                    if content_item[0] == 'addcontent':
742                        workspace.append(type_item)
743                    if content_item[0] == 'add':
744                        workspace.append(['oldplank',content_item[1]])
745            to_remove=[]
746            for i in range(len(workspace)-1,-1,-1):
747                try:
748                    if workspace[i-1] == 'comm' and workspace[i-2][0] == 'file':
749                        workspace[i]=[workspace[i],workspace[i-2][1]]
750                        to_remove.append(i-1)
751                        to_remove.append(i-2)
752                except:
753                    pass
754            for i in sorted(to_remove,reverse=True):
755                del workspace[i]
756            to_remove = []
757            for i in range(len(workspace)):
758                if workspace[i][0] in ['desktop','plank']:
759                    content = self.scan_desktop_file(filename=workspace[i][1], files_cache=self.ctl.files_cache)
760                    workspace[i].extend(list(content))
761                if workspace[i][0] == 'oldplank':
762                    done=False
763                    for cl in self.ctl.model.cache_lists:
764                        for clt in self.ctl.model.cache_lists[cl]:
765                            if clt[2] == workspace[i][1]:
766                                workspace[i]= ['plank']
767                                workspace[i].extend([clt[3]])
768                                workspace[i].extend(list(clt))
769                                done=True
770                            if done:
771                                break
772                        if done:
773                            break
774                    if not done:
775                        to_remove.append(i)
776            for i in sorted(to_remove,reverse=True):
777                del workspace[i]
778            return workspace
779        except Exception as e:
780            return None
781
782##############################
783#
784# Main visualization class
785#
786##############################
787class MainWindow:
788
789    def __init__(self,*args,**kwargs):
790        check_args('controller','handler',**kwargs)
791        self.ctl = kwargs['controller']
792        self.handler = kwargs['handler']
793
794        #
795        # Show main window
796        #
797
798        self.obj = self.ctl.getobj('window_main')
799
800        #
801        # build stack components for the window
802        #
803        self.boxes_main = ['box_login','box_loading','box_main_btn','box_profiles2','box_dialog','box_pselector','box_group_edit','box_warning','box_edit_elements']
804        self.boxes_bottom = ['box_lvl','box_status']
805        self.stack_main = {'obj': self.build_stack(boxes=self.boxes_main),'stack':[]}
806        self.stack_bottom = { 'obj': self.build_stack(boxes=self.boxes_bottom), 'stack':[]}
807        self.dialog_filechooser = self.ctl.getobj('filechooser')
808        #
809        # get place to put stack
810        #
811        self.box_main_center=self.ctl.getobj('box_main_center')
812        self.box_main_bottom=self.ctl.getobj('box_main_bottom')
813
814        #
815        # Add stacks
816        #
817        self.box_main_center.pack_start(self.stack_main['obj'],True,True,0)
818        self.box_main_bottom.pack_start(self.stack_bottom['obj'],True,True,0)
819
820        #
821        # Store references to programatically created objects
822        #   boxes with labels and combos storing relations
823        #
824        self.group_profile_boxes = {}
825        self.profiles_need_refresh = False
826        # Show window
827        self.obj.show_all()
828
829    ##############################
830    #
831    # Builds dynamically boxes with an entry and comboboxes because
832    #   treeview with comboboxrenderer isn't a valid option
833    #
834    ##############################
835    def build_group_profile_window(self,*args,**kwargs):
836        # Get params from controller
837        check_args('profiles','groups',**kwargs)
838        profiles=kwargs['profiles']
839        groups=kwargs['groups']
840
841        # Get glade main object from builder
842        container=self.ctl.getobj('box_container_grp_profiles')
843
844        if self.profiles_need_refresh or len(self.group_profile_boxes) == 0:
845            #
846            # Profiles changed, need complete refresh
847            #
848            if self.profiles_need_refresh:
849                self.profiles_need_refresh = False
850                for box in container:
851                    box.destroy()
852                self.group_profile_boxes = {}
853            #
854            # if it's the first time called build the structure
855            # if it isn't the first time the objects are already stored
856            #
857
858            # Build ListStore 4 showing into ComboBox
859            lst = Gtk.ListStore(GObject.TYPE_INT,GObject.TYPE_STRING)
860            # Add fake empty first element
861            lst.append([0,'[None]'])
862            # Id entry into combobox model
863            i=1
864            # Reverse mapping group -> profile association from list elements ['profile','group_associated']
865            grp_profile={}
866            # Complete ListStore & do the mapping
867            for profile in profiles:
868                pname=profile[0]
869                pgrp=profile[1]
870                lst.append([i,pname])
871                if pgrp != '':
872                    for g in pgrp.split(','):
873                        grp_profile[g] = i
874                i += 1
875
876            # Create Boxes (rows) per group
877            for gname in groups:
878                # A Box
879                box=Gtk.Box(Gtk.Orientation.HORIZONTAL,10)
880                box.set_property('homogeneous',True)
881                box.set_property('halign',Gtk.Align.FILL)
882                # With Label
883                lbl=Gtk.Label(gname)
884                lbl.set_property('halign',Gtk.Align.CENTER)
885                # And ComboBox
886                combo=Gtk.ComboBox.new_with_model_and_entry(lst)
887                combo.set_property('halign',Gtk.Align.START)
888                combo.set_property('width-request',200)
889                combo.set_property('entry-text-column',1)
890                combo.set_property('id-column', 0)
891                # Set active element if already associated
892                if gname in grp_profile:
893                    combo.set_property('active', grp_profile[gname])
894                else:
895                    combo.set_property('active', 0)
896                # Connect signals
897                combo.connect("changed", self.handler.combo_changed,lbl)
898                # Pack everything
899                box.pack_start(lbl,True,True,0)
900                box.pack_start(combo,True,True,0)
901                # Save references to avoid redo the objects
902                self.group_profile_boxes.update({gname:[lbl,combo]})
903                container.pack_start(box,False,False,0)
904        else:
905            #
906            # Objects are already created, they only need to be updated
907            #
908
909            # Id entry into combobox model
910            i = 1
911            # Reverse mapping group -> profile association from list elements ['profile','group_associated']
912            grp_profile = {}
913            # Do the mapping
914            for profile in profiles:
915                pgrp=profile[1]
916                if pgrp != '':
917                    for g in pgrp.split(','):
918                        grp_profile[g] = i
919                i += 1
920            for group in groups:
921                if group in grp_profile:
922                    self.group_profile_boxes[group][1].set_property('active',grp_profile[group])
923                else:
924                    self.group_profile_boxes[group][1].set_property('active', 0)
925
926
927        # Show all widgets
928        container.show_all()
929
930    ##############################
931    #
932    # Build stack components with boxes from glade
933    #
934    ##############################
935    def build_stack(self,*args,**kwargs):
936        stack=Gtk.Stack()
937        for box_name in kwargs['boxes']:
938            box=self.ctl.getobj(box_name)
939            stack.add_named(box,box_name)
940        stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE)
941        stack.set_transition_duration(400)
942        return stack
943
944    ##############################
945    #
946    # Change visualization elements from stacks
947    #
948    ##############################
949    def change_stack(self,*args,**kwargs):
950        # Get the stack and properties
951        if 'stack' not in kwargs:
952            stack=self.stack_main['obj']
953            history=self.stack_main['stack']
954            lstack=self.boxes_main
955            visible_name = stack.get_visible_child_name()
956        else:
957            if type(kwargs['stack']) == type(str()):
958                if kwargs['stack'] == 'main':
959                    stack=self.stack_main['obj']
960                    history = self.stack_main['stack']
961                    lstack = self.boxes_main
962                    visible_name = stack.get_visible_child_name()
963                else:
964                    stack=self.stack_bottom['obj']
965                    history = self.stack_bottom['stack']
966                    lstack = self.boxes_bottom
967                    visible_name = stack.get_visible_child_name()
968            else:
969                stack=kwargs['stack']
970                visible_name = stack.get_visible_child_name()
971                if visible_name in self.boxes_main:
972                    lstack=self.boxes_main
973                else:
974                    lstack=self.boxes_bottom
975
976        lindex=lstack.index(visible_name)
977        next=lindex
978        for to in args:
979            try:
980                if to == 'next':
981                    next += (lindex + 1) % len(lstack)
982                elif to == 'prev':
983                    next += (lindex - 1) % len(lstack)
984                elif to == 'back':
985                    next = lstack.index(history[1])
986                elif type(to) == type(int()):
987                    next += (lindex + to) % len(lstack)
988                else:
989                    if to in lstack:
990                        next = lstack.index(to)
991            except:
992                pass
993
994        stack.set_visible_child_name(lstack[next])
995        self.ctl.last_window = lstack[next]
996
997        if to != 'back':
998            history.insert(0,lstack[next])
999        else:
1000            del history[0]
1001
1002        if len(history) > 10:
1003            del history[10]
1004
1005
1006    ##############################
1007    #
1008    # Change button label
1009    #
1010    ##############################
1011    def change_button_text(self,*args,**kwargs):
1012        if not check_args('button','text',**kwargs):
1013            return
1014        btn=self.ctl.getobj(kwargs['button'])
1015        btn.set_label(kwargs['text'])
1016
1017    ##############################
1018    #
1019    # Update level bar & label
1020    #
1021    ##############################
1022    #@trace
1023    def update_status_bar(self,*args,**kwargs):
1024
1025        bar=self.ctl.getobj('lvlbar')
1026        label=self.ctl.getobj('label_lvlbar')
1027        info=args[0]
1028        done=None
1029        while not done:
1030            time.sleep(0.1)
1031            bar.set_value(info['level'])
1032            label.set_text(info['msg'])
1033            done=info['done']
1034        return
1035
1036    ##############################
1037    #
1038    # Update with text any status bar
1039    #
1040    ##############################
1041    def set_txt_status_bars(self,*args,**kwargs):
1042        bars=[]
1043        contexts=[]
1044        for i in range(3):
1045            bars.insert(i,self.ctl.getobj('stbar'+str(i+1)))
1046            contexts.insert(i,bars[i].get_context_id(str(i)))
1047        if 'clean' in kwargs:
1048            for nbar in range(3):
1049                bars[nbar].remove_all(contexts[nbar])
1050            return
1051        for i in range(3):
1052            if len(args) > i:
1053                bars[i].remove_all(contexts[i])
1054                bars[i].push(contexts[i],args[i])
1055        if 'bar_info' in kwargs:
1056            bar_info = kwargs['bar_info']
1057            for num in bar_info:
1058                if num in ['0','1','2']:
1059                    bars[int(num)].remove_all(contexts[int(num)])
1060                    bars[int(num)].push(contexts[int(num)],bar_info[num])
1061        return
1062
1063    ##############################
1064    #
1065    # Get values from entries or labels
1066    #
1067    ##############################
1068    def get_entry(self, *args, **kwargs):
1069        lout = []
1070        for name in args:
1071            entry = self.ctl.getobj(name)
1072            lout.append(entry.get_text())
1073        return lout
1074
1075    ##############################
1076    #
1077    # Set values into entries or labels
1078    #
1079    ##############################
1080    def set_entry(self, *args, **kwargs):
1081        for entry in args:
1082            for name_entry in entry:
1083                value = entry[name_entry]
1084                self.ctl.getobj(name_entry).set_text(value)
1085        return
1086
1087    def show_edit_mode(self,*args,**kwargs):
1088        if 'hide' in args:
1089            self.ctl.getobj('rbtn_move').hide()
1090            self.ctl.getobj('rbtn_copy').hide()
1091        elif 'show' in args:
1092            self.ctl.getobj('rbtn_move').show()
1093            self.ctl.getobj('rbtn_copy').show()
1094        else:
1095            self.ctl.getobj('rbtn_move').show()
1096            self.ctl.getobj('rbtn_copy').show()
1097
1098##############################
1099#
1100# Event handler main class
1101#
1102##############################
1103class Handler():
1104    def __init__(self,*args,**kwargs):
1105        check_args('controller',**kwargs)
1106        self.ctl=kwargs['controller']
1107
1108    def get_selected_rowid(self,*args,**kwargs):
1109        check_args('selected_obj','type',**kwargs)
1110        #
1111        # Get the selected element, returns elements id or none
1112        #
1113        treeview_selection = kwargs['selected_obj']
1114        typearg = kwargs['type']
1115        model, path = treeview_selection.get_selected_rows()
1116        # If nothing is selected abort actions
1117        if (len(path) < 1):
1118            return None
1119        selected = None
1120        if typearg == 'apps':
1121            selected = model[path][APPCOL.ID]
1122        if typearg == 'profiles':
1123            selected = model[path][PROFCOL.ID]
1124        return selected
1125
1126    def destroy(self, *args, **kwargs):
1127        self.ctl.kill_all_pending_operations()
1128        Gtk.main_quit(*args)
1129
1130    def delete_event(self,*args, **kwargs):
1131        for w in args:
1132            w.hide()
1133        return True
1134
1135    def validate_clicked(self,*args,**kwargs):
1136        self.ctl.process(action='validation_init')
1137
1138    def go_to_new_profile(self,*args,**kwargs):
1139        self.ctl.process(action='goto',to='new_profile')
1140
1141    def go_to_edit_profile(self,*args,**kwargs):
1142        self.ctl.process(action='goto', to='edit_profile')
1143
1144    def go_to_group_mapping(self,*args,**kwargs):
1145        self.ctl.process(action='goto', to='group_mapping')
1146
1147    def close_window_and_go_back(self,*args,**kwargs):
1148        self.ctl.process(action='goto', to='back')
1149
1150    def entry_filter_changed(self,*args,**kwargs):
1151        self.ctl.process(action='refilter_elements')
1152
1153    def put_current_items(self,*args,**kwargs):
1154        self.ctl.process(action='put_current_items')
1155
1156    def swap(self,*args,**kwargs):
1157        id=self.get_selected_rowid(selected_obj=args[0],type='apps')
1158        self.ctl.process(action='swap',id=id)
1159
1160    def swap_local_elements(self,*args,**kwargs):
1161        pass
1162        # deprecated since v3
1163        #self.ctl.process(action='swap_locals')
1164
1165    def go_to_ask_remove_profile(self,*args,**kwargs):
1166        id = self.get_selected_rowid(selected_obj=args[0], type='profiles')
1167        if id != None:
1168            self.ctl.process(action='goto', to='ask_remove',id=id)
1169
1170    def load_selected_profile(self,*args,**kwargs):
1171        id = self.get_selected_rowid(selected_obj=args[0], type='profiles')
1172        if id != None:
1173            self.ctl.process(action='goto',to='editing_profile',id=id)
1174
1175    def confirm_yes(self,*args,**kwargs):
1176        id = self.get_selected_rowid(selected_obj=args[0], type='profiles')
1177        if id != None:
1178            self.ctl.process(action='goto',to='remove_confirmed',id=id)
1179
1180    def profile_name_changed(self,*args,**kwargs):
1181        self.ctl.profile_name_changed = True
1182
1183    def go_to_main_window(self,*args,**kwargs):
1184        self.ctl.process(action='goto',to='main_window')
1185
1186    def cancel_profile(self,*args,**kwargs):
1187        self.ctl.process(action='cancel_profile')
1188
1189    def combo_changed(self,*args,**kwargs):
1190        #
1191        # Omit handler when view is building to avoid fake checkings & processing
1192        #
1193        if not self.ctl.building_view:
1194            combo = args[0]
1195            label = args[1]
1196            textlabel = label.get_text()
1197            iter = combo.get_active_iter()
1198            model = combo.get_model()
1199            selected = model[iter][1]
1200            self.ctl.process(action='change_relations',group=textlabel,profile=selected)
1201
1202    def go_to_filechooser(self,*args,**kwargs):
1203        self.ctl.process(action='goto', to='filechooser')
1204
1205    def close_btn_filechooser(self,*args,**kwargs):
1206        self.ctl.process(action='close_filechooser')
1207        return True
1208
1209    def selection_changed(self,*args,**kwargs):
1210        print('use file direct'+self.ctl.resource_previewed)
1211        self.ctl.process(action='selected_resource')
1212
1213    def select_file(self,*args,**kwargs):
1214        print('use file '+self.ctl.resource_previewed)
1215        self.ctl.process(action='selected_resource')
1216
1217    def selection_preview(self,*args,**kwargs):
1218        filechooser = args[0].get_preview_file()
1219        if filechooser:
1220            file = filechooser.get_path()
1221            ext = file.split('.')
1222            if ext[-1].lower() in ['png','jpg']:
1223                print('previewed '+file)
1224                self.ctl.process(action='selection_changed',file=file)
1225
1226    def profile_accepted(self,*args,**kwargs):
1227        self.ctl.process(action='profile_accepted')
1228
1229    def go_edit_plank_items(self, *args, **kwargs):
1230        self.ctl.process(action='edit_plank_items')
1231
1232    def go_edit_desktop_items(self, *args, **kwargs):
1233        self.ctl.process(action='edit_desktop_items')
1234
1235    def cancel_edit_elements(self, *args, **kwargs):
1236        self.ctl.process(action='cancel_edit_elements')
1237
1238    def accept_edit_elements(self, *args, **kwargs):
1239        self.ctl.process(action='accept_edit_items')
1240
1241    def remove_background(self, *args, **kwargs):
1242        self.ctl.process(action='remove_selected_background')
1243
1244    def preview_background(self, *args, **kwargs):
1245        self.ctl.process(action='preview_background')
1246
1247    def close_preview_window(self, *args, **kwargs):
1248        self.ctl.process(action='close_preview')
1249
1250    def change_edit_mode(self,*args,**kwargs):
1251        name=[r for r in args[0].get_group() if r.get_active()][0].get_name()
1252        self.ctl.process(action='change_edit_mode',name=name)
1253
1254
1255##############################
1256#
1257# Liststore enumerations
1258#
1259##############################
1260class APPCOL():
1261    VISIBLE = 0
1262    TYPE = 1
1263    ID = 2
1264    ICON = 3
1265    NAME = 4
1266    DESCRIPTION = 5
1267    SELECTED_DESKTOP = 6
1268    SELECTED_PLANK = 7
1269    BASENAME = 8
1270    FULLPATH = 9
1271
1272class PROFCOL():
1273    VISIBLE = 0
1274    ID = 1
1275    NAME = 2
1276    SELECTED = 3
1277##############################
1278#
1279# Data model main class
1280#
1281##############################
1282class Model:
1283    def __init__(self, *args, **kwargs):
1284        check_args('controller',**kwargs)
1285        self.ctl=kwargs['controller']
1286
1287        self.user=None
1288        self.pwd=None
1289        self.is_validated=False
1290        #self.groups=None
1291        #self.profiles=None
1292
1293        self.profile_list=[]
1294        self.group_list=[]
1295
1296        self.cache_lists={}
1297        self.cache_info={'level':0.0,'msg':'','done':False}
1298
1299        self.store_elements = self.ctl.getobj('liststore_elements')
1300        self.store_profiles = self.ctl.getobj('liststore_profiles')
1301
1302        self.n_selected = 0
1303        self.current_relations = {}
1304
1305        self.init_sorted_filtering(type='all')
1306        pass
1307
1308    def build_liststore(self,*args,**kwargs):
1309        #
1310        # Build liststore
1311        #
1312        check_args('type',**kwargs)
1313        typearg=kwargs['type']
1314        self.clear_liststore(type=typearg)
1315        if typearg == 'apps':
1316            for t in ['local','system','desktop']:
1317                self.add_to_liststore(list=self.cache_lists[t], type=t)
1318        if 'profiles' == typearg:
1319             self.add_to_liststore(list=self.profile_list, type=typearg)
1320
1321
1322    ##############################
1323    #
1324    # Initialize liststore filters & sorting
1325    #
1326    ##############################
1327    def init_sorted_filtering(self, *args,**kwargs):
1328        if 'type' not in kwargs:
1329            type == 'all'
1330        else:
1331            type = kwargs['type']
1332        if type not in ['elements','selected_plank','selected_desktop','all']:
1333            return False
1334        all = ['elements','selected_plank','selected_desktop']
1335        if type == 'all':
1336            for t in all:
1337                self.init_sorted_filtering(type=t)
1338            return
1339        obj=self.ctl.getobj('sort_'+type)
1340        # Init sorting
1341        sort_column_in_model=APPCOL.NAME
1342        obj.set_sort_column_id(sort_column_in_model,Gtk.SortType.ASCENDING)
1343        model_filter=self.ctl.getobj('filter_'+type)
1344        # Init filters
1345        if type == 'selected_desktop':
1346            model_filter.set_visible_func(self.filter_func_selected_desktop)
1347        if type == 'selected_plank':
1348            model_filter.set_visible_func(self.filter_func_selected_plank)
1349        if type == 'elements':
1350            model_filter.set_visible_func(self.filter_func_apps)
1351
1352    ##############################
1353    #
1354    # Empty liststores
1355    #
1356    ##############################
1357    def clear_liststore(self,*args,**kwargs):
1358        check_args('type', **kwargs)
1359        store=kwargs['type']
1360        if store == 'apps':
1361            self.store_elements.clear()
1362        if 'profiles' == store:
1363            self.store_profiles.clear()
1364
1365    ##############################
1366    #
1367    # Set selections into liststore
1368    #
1369    ##############################
1370    def set_liststore_selections(self,*args,**kwargs):
1371        check_args('list','type', **kwargs)
1372        if kwargs['type'] not in ['desktop','plank']:
1373            return None
1374        store = kwargs['type']
1375        app_list = list(kwargs['list'])
1376        current_store = None
1377        id_element_selection = None
1378        id_element_name = None
1379
1380        if store == 'desktop':
1381            current_store = self.store_elements
1382            id_element_selection = APPCOL.SELECTED_DESKTOP
1383            id_element_name = APPCOL.BASENAME
1384        if store == 'plank':
1385            current_store = self.store_elements
1386            id_element_selection = APPCOL.SELECTED_PLANK
1387            id_element_name = APPCOL.BASENAME
1388        if 'profiles' == store:
1389            current_store = self.store_profiles
1390            id_element_selection = PROFCOL.SELECTED
1391            id_element_name = PROFCOL.NAME
1392        # If name not exist, all selections are cleared
1393        for x in current_store:
1394            if x[id_element_name].lower() in app_list:
1395                x[id_element_selection] = True
1396                app_list.remove(x[id_element_name].lower())
1397            elif x[id_element_selection]:
1398                x[id_element_selection] = False
1399        return
1400
1401    def put_current_items(self, *args, **kwargs):
1402        current_desktop = self.get_liststore_selections(type='desktop')
1403        current_plank = self.get_liststore_selections(type='plank')
1404        for x in self.store_elements:
1405            if x[APPCOL.TYPE] == 'system':
1406                continue
1407            if x[APPCOL.TYPE] == 'desktop':
1408                if x[APPCOL.BASENAME].lower() not in current_desktop:
1409                    x[APPCOL.SELECTED_DESKTOP] = True
1410            if x[APPCOL.TYPE] == 'local':
1411                if x[APPCOL.BASENAME].lower() not in current_plank:
1412                    x[APPCOL.SELECTED_PLANK] = True
1413
1414    ##############################
1415    #
1416    # Get current selections from liststore
1417    #
1418    ##############################
1419    def get_liststore_selections(self,*args,**kwargs):
1420        check_args('type',**kwargs)
1421        store=kwargs['type']
1422
1423        current_store = None
1424        if store == 'desktop':
1425            current_store = self.store_elements
1426            id_element_selection = APPCOL.SELECTED_DESKTOP
1427            id_element_target = APPCOL.BASENAME
1428        if store == 'plank':
1429            current_store = self.store_elements
1430            id_element_selection = APPCOL.SELECTED_PLANK
1431            id_element_target = APPCOL.BASENAME
1432        if store == 'profiles':
1433            current_store = self.store_profiles
1434            id_element_selection = PROFCOL.SELECTED
1435            id_element_target = PROFCOL.NAME
1436
1437        applist = []
1438        for x in current_store:
1439            if x[id_element_selection]:
1440                applist.append(x[id_element_target])
1441        return applist
1442
1443    ##############################
1444    #
1445    # Get current assigned group from profile or None
1446    #
1447    ##############################
1448    def get_group_from_profile(self,*args,**kwargs):
1449        check_args('nameprofile',**kwargs)
1450        name=kwargs['nameprofile']
1451        name=name.strip()
1452
1453        for p in self.profile_list:
1454            if p[0].lower() == name.lower():
1455                return p[1]
1456        return None
1457
1458    ##############################
1459    #
1460    # Add list with data into liststore
1461    #
1462    ##############################
1463    def add_to_liststore(self,*args,**kwargs):
1464        check_args('list','type',**kwargs)
1465        lapps=kwargs['list']
1466        typearg=kwargs['type']
1467        if typearg in ['local','system','desktop']:
1468            if typearg == 'local':
1469                i = 1
1470            elif typearg == 'desktop':
1471                i = 1000
1472            else:
1473                i = 10000
1474            for item in lapps:
1475                #
1476                # Item
1477                # [0]icon_path(string), [1]name(string), [2]desktop_basename(string), [3]path_to_desktop_or_dockitem(string), [4]description(string)
1478                # Liststore:
1479                # [0]visible(bool), [1]type(string), [2]id(int(1<local<1000<system)), [3]icon(GdkPixbuf), [4]name(string), [5]description(string), [6]selected_desktop(bool), [7]selected_plank(bool), [8]desktop_basename(string) [9]full_path(string)
1480                #
1481
1482                # Create GdkPixbuf
1483                if item[0] == None:
1484                    # ICON is NONE
1485                    pix = GdkPixbuf.Pixbuf.new_from_file_at_size('/usr/lib/syncer-plank/noicon.png', 24, 24)
1486                else:
1487                    try:
1488                        pix = GdkPixbuf.Pixbuf.new_from_file_at_size(item[0], 24, 24)
1489                    except:
1490                        dbg('Error generating pixbuf {}'.format(item[0]))
1491                        pix = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, False, 8, 24, 24)
1492                if item[4] == None:
1493                    # DESCRIPTION is NONE
1494                    desc = 'No description in desktop file'
1495                else:
1496                    desc = item[4]
1497                self.store_elements.append([True,typearg,i,pix,item[1],desc,False,False,item[2],item[3]])
1498                i += 1
1499        elif typearg == 'profiles':
1500            i=0
1501            for p in self.profile_list:
1502                self.store_profiles.append([True,i,p[0],False])
1503                i += 1
1504
1505    ##############################
1506    #
1507    # Purge duplicated items from cache lists
1508    #
1509    ##############################
1510    def purge_duplicated_cache_items(self,*args,**kwargs):
1511        l = []
1512        # Item
1513        # [0]icon_path(string), [1]name(string), [2]desktop_basename(string), [3]path_to_desktop_or_dockitem(string), [4]description(string)
1514        FILTER_COL=1
1515        for type in ['system','local','desktop']:
1516            l = []
1517            for i in self.cache_lists[type]:
1518                if not i[FILTER_COL] in l:
1519                    l.append(i[FILTER_COL])
1520                else:
1521                    dbg('Duplicated entry {}'.format(i[FILTER_COL]))
1522                    self.cache_lists[type].remove(i)
1523
1524    ##############################
1525    #
1526    # Visible functions 4 selected treeview
1527    #
1528    ##############################
1529    def filter_func_selected_desktop(self,*args,**kwargs):
1530        model=args[0]
1531        iter=args[1]
1532        is_selected = model.get_value(iter,APPCOL.SELECTED_DESKTOP)
1533        if is_selected:
1534            return True
1535        return False
1536
1537    def filter_func_selected_plank(self,*args,**kwargs):
1538        model=args[0]
1539        iter=args[1]
1540        is_selected = model.get_value(iter,APPCOL.SELECTED_PLANK)
1541        if is_selected:
1542            return True
1543        return False
1544
1545    ##############################
1546    #
1547    # Visible function 4 system elements treeview
1548    #
1549    ##############################
1550    def filter_func_apps(self,*args,**kwargs):
1551        model = args[0]
1552        iter = args[1]
1553
1554        type_item = model.get_value(iter,APPCOL.TYPE)
1555        if type_item not in  ['system','local']:
1556            return False
1557        selected = None
1558        if self.ctl.current_selection == 'desktop':
1559            if type_item == 'local':
1560                return False
1561            else:
1562                selected = model.get_value(iter, APPCOL.SELECTED_DESKTOP)
1563
1564        if self.ctl.current_selection == 'plank':
1565            selected = model.get_value(iter, APPCOL.SELECTED_PLANK)
1566        if selected:
1567            return False
1568
1569        name = model.get_value(iter,APPCOL.NAME)
1570        description = model.get_value(iter,APPCOL.DESCRIPTION)
1571
1572        entry_text=self.ctl.window.get_entry('entry_filter_elements')
1573        entry_text=entry_text[0].lower().strip()
1574        if entry_text == '':
1575            return True
1576
1577        if entry_text in name.lower() or entry_text in description:
1578            return True
1579        return False
1580
1581    ##############################
1582    #
1583    # Trigger refilter on treeview
1584    #
1585    ##############################
1586    def refilter(self,*args,**kwargs):
1587        check_args('type',**kwargs)
1588        type_refilter=kwargs['type']
1589
1590        model_filter_elements=self.ctl.getobj('filter_elements')
1591        model_filter_elements.refilter()
1592        if type_refilter == 'elements':
1593            return
1594        if type_refilter == 'selected_plank' or type_refilter == 'all':
1595            model_filter_selected=self.ctl.getobj('filter_selected_plank')
1596            model_filter_selected.refilter()
1597        if type_refilter == 'selected_desktop' or type_refilter == 'all':
1598            model_filter_selected = self.ctl.getobj('filter_selected_desktop')
1599            model_filter_selected.refilter()
1600        return
1601
1602    ##############################
1603    #
1604    # Count selected apps
1605    #
1606    ##############################
1607    def count_selected(self,*args,**kwargs):
1608        check_args('type',**kwargs)
1609        type = kwargs['type']
1610        i=0
1611        j=0
1612        col = None
1613        if type == 'plank':
1614            col = APPCOL.SELECTED_PLANK
1615        if type == 'desktop':
1616            col = APPCOL.SELECTED_DESKTOP
1617        for a in self.store_elements:
1618            if type == 'all':
1619                if a[APPCOL.SELECTED_DESKTOP]:
1620                    i+=1
1621                if a[APPCOL.SELECTED_PLANK]:
1622                    j+=1
1623            else:
1624                if a[col]:
1625                    i+=1
1626        if type == 'all':
1627            self.n_selected=i
1628            self.n_selected2 = j
1629            return (i,j)
1630        else:
1631            self.n_selected = i
1632            return i
1633
1634    ##############################
1635    #
1636    # Load apps readed from filesystem into liststore before show profile window
1637    #
1638    ##############################
1639    def load_profile_into_liststore(self,*args,**kwargs):
1640        check_args('workspace',**kwargs)
1641        workspace=kwargs['workspace']
1642        desktops=[]
1643        planks=[]
1644        background=[]
1645        for x in workspace:
1646            if x[0] == 'desktop':
1647                desktops.append(x[4])
1648                pass
1649            elif x[0]== 'plank':
1650                planks.append(x[4])
1651                pass
1652            elif x[0] == 'dconf':
1653                self.ctl.resource_previewed = x[1]
1654                background.append(x[1])
1655                self.ctl.process(action='selected_resource')
1656                pass
1657
1658        # Clear selections is done when setting selections
1659        self.set_liststore_selections(list=desktops,type='desktop')
1660        self.set_liststore_selections(list=planks, type='plank')
1661        return {'desktop': desktops, 'plank': planks, 'background': background}
1662
1663    ##############################
1664    #
1665    # Check valid profile name
1666    #
1667    ##############################
1668    def check_profile_name(self,*args,**kwargs):
1669        check_args('name','type',**kwargs)
1670        name = kwargs['name']
1671        typearg = kwargs['type']
1672        name = name.strip()
1673        name = name.lower()
1674
1675        if name == '':
1676            return False
1677
1678        if typearg == 'available':
1679            ret_pr = True
1680        else:
1681            ret_pr = False
1682
1683        for p in self.profile_list:
1684            if typearg == 'available':
1685                if p[0].lower() == name.lower():
1686                    ret_pr = False
1687            elif typearg == 'valid':
1688                if p[0].lower() == name.lower():
1689                    ret_pr = True
1690        return ret_pr
1691
1692    ##############################
1693    #
1694    # Sort list with format [['profile_name','grp_list_with_commas']['profile_name','grp_list_with_commas']]
1695    #
1696    ##############################
1697    def sort_profiles(self,*args,**kwargs):
1698        self.profile_list.sort(key=lambda x: x[0])
1699
1700    def change_model_selected(self,*args,**kwargs):
1701        check_args('type',**kwargs)
1702        type = kwargs['type']
1703        tv= self.ctl.getobj('treeview_selected_elements')
1704
1705        if type == 'desktop':
1706            model = self.ctl.getobj('sort_selected_desktop')
1707            tv.set_model(model)
1708        if type == 'plank':
1709            model = self.ctl.getobj('sort_selected_plank')
1710            tv.set_model(model)
1711        return
1712
1713##############################
1714#
1715# Controller class
1716#
1717##############################
1718class Controller:
1719    def __init__(self,*args,**kwargs):
1720        self.aborting_all_operations = False
1721        self.builder=self.load_glade(**kwargs)
1722        #self.test = None
1723        #
1724        # Main objects
1725        #
1726        self.helper=Helper(controller=self)
1727        self.model=Model(controller=self)
1728        self.handler=Handler(controller=self)
1729        self.window=MainWindow(controller=self,handler=self.handler)
1730        #
1731        # Connect signals
1732        #
1733        self.builder.connect_signals(self.handler)
1734
1735        # Bypass combobox signal handler
1736        self.building_view = False
1737
1738        self.files_cache = None
1739        #
1740        # Remember what user are doing
1741        #
1742        self.last_window = None
1743        # new_profile or the profile_name what is currently being modified
1744        self.user_is_editing = None
1745        self.initial_liststore = {}
1746        self.initial_profile_name = ''
1747        # Flags to remember what changed into window
1748        self.profile_name_changed = None
1749        self.applist_changed = None
1750        self.current_selection = None
1751        # Current file selected
1752        self.resource_previewed = None
1753        self.edit_mode = None
1754        # Backup data selections
1755        self.backup_selections = {}
1756        #
1757        # Init actions
1758        #
1759        self.reset_memories()
1760        self.process(action='initialized')
1761        return
1762
1763    ##############################
1764    #
1765    # Emergency exit, stops pending threads & silence errors when called destroy from gui
1766    #    (not catching TERM or KILL signals from terminal)
1767    #
1768    ##############################
1769    def kill_all_pending_operations(self,*args,**kwargs):
1770        self.aborting_all_operations = True
1771        for t in self.helper._thread:
1772            if t != None:
1773                terminate_thread(t)
1774    #
1775    # Reset the status flags
1776    #
1777    def reset_memories(self,*args,**kwargs):
1778        self.last_window = None
1779        self.user_is_editing = None
1780        self.profile_name_changed = False
1781        self.applist_changed = False
1782        self.initial_liststore = {}
1783        self.initial_profile_name = ''
1784        self.edit_mode = 'move'
1785        self.resource_previewed = None
1786    #
1787    # Load glade file
1788    #
1789    def load_glade(self,*args,**kwargs):
1790        if kwargs['localpath']:
1791            gladefile = kwargs['localpath']+'/'+"gui_v3.glade"
1792        builder = Gtk.Builder()
1793        dbg('Loading '+gladefile)
1794        builder.add_from_file(gladefile)
1795        return builder
1796    #
1797    # Get objects from builder
1798    #
1799    def getobj(self,*args,**kwargs):
1800        return self.builder.get_object(args[0])
1801    ##############################
1802    #
1803    # Build database cache process
1804    #
1805    ##############################
1806    #@trace
1807    def do_cache(self,*args,**kwargs):
1808        #
1809        # Get complete filelist from filesystem
1810        #
1811        self.model.cache_info['msg']=_('Building file cache...')
1812        self.files_cache = glob.glob('/usr/share/icons/**/*.png', recursive=True)
1813        #
1814        # Get current user Plank elements
1815        #
1816        self.model.cache_info['msg']=_("Searching local apps...")
1817        try:
1818            self.model.cache_lists['local'] = self.helper.search_local_plank(files_cache=self.files_cache)
1819        except:
1820            dbg('Error building local cache list')
1821            self.model.cache_lists['local']=[]
1822        #
1823        # Get data from applications from /usr/share/application/*.desktop
1824        #
1825        self.model.cache_info['msg'] = _("Searching system apps...")
1826        try:
1827            self.model.cache_lists['system'] = self.helper.search_in_applications(cache_info=self.model.cache_info,files_cache=self.files_cache)
1828        except:
1829            dbg('Error building system cache list')
1830            self.model.cache_lists['system'] = []
1831
1832        #
1833        # Get data from user desktop
1834        #
1835        self.model.cache_info['msg'] = _("Searching desktop apps...")
1836        try:
1837            self.model.cache_lists['desktop'] = self.helper.search_local_desktops(files_cache=self.files_cache)
1838        except:
1839            dbg('Error building desktops cache list')
1840            self.model.cache_lists['desktop'] = []
1841
1842        #
1843        # Purge possible duplicated entries
1844        #
1845        self.model.purge_duplicated_cache_items()
1846        #
1847
1848        #
1849        # End cache process
1850        #
1851        if len(self.model.cache_lists['system']) > 0:
1852            self.window.set_txt_status_bars('', '', '{} {}({}) {}({}) {}({})'.format(_('Detected apps:'), _('Plank'),
1853                                                                              len(self.model.cache_lists['local']),
1854                                                                              _('System'),
1855                                                                              len(self.model.cache_lists['system']),
1856                                                                              _('Desktops'),
1857                                                                              len(self.model.cache_lists['desktop'])))
1858            self.helper.clean_status_bar(2)
1859            self.model.cache_info['msg'] = _('Ready!')
1860            self.model.cache_info['done']=True
1861        else:
1862            self.model.cache_info['msg'] = _("Unable to build complete cache...")
1863        #
1864        # Hide level bar
1865        #
1866        self.window.change_stack('next',stack='bottom')
1867
1868    def debug_model(self,model):
1869        for i in model:
1870            li = list(i)
1871            print('{}'.format(li))
1872
1873    def check_profile_changes(self,*args,**kwargs):
1874        let_continue = True
1875        # def same_elements(la, lb):
1876        #     for x in la:
1877        #         if x not in lb:
1878        #             return False
1879        #     for x in lb:
1880        #         if x not in la:
1881        #             return False
1882        #     return True
1883        #
1884        changes = []
1885        # for x in self.initial_liststore:
1886        #     if x in ['desktop', 'plank']:
1887        #         ltest = self.model.get_liststore_selections(type=x)
1888        #         if not same_elements(ltest, self.initial_liststore[x]):
1889        #             changes.append('changed_'+x)
1890        #         if len(ltest) == 0:
1891        #             changes.append('empty_'+x)
1892        #     elif x == 'background':
1893        #         bg = self.window.get_entry('entry_desktop_background')[0]
1894        #         bg = bg.strip()
1895        #         if bg != self.initial_liststore[x][0]:
1896        #             changes.append('changed_'+x)
1897        #         if bg == '':
1898        #             changes.append('empty_'+x)
1899        pname = self.window.get_entry('profile_name_entry')[0]
1900        pname = pname.strip()
1901        if pname == '':
1902            changes.append('empty_pname')
1903        if pname != self.initial_profile_name:
1904            changes.append('changed_pname')
1905        if self.user_is_editing == '_____new_profile_____':
1906            changes.append('new_profile')
1907        else:
1908            changes.append('editing_profile')
1909        #
1910        if 'empty_name' in changes:
1911            return False
1912        if 'new_profile' in changes:
1913            if 'changed_pname' in changes:
1914                if not self.model.check_profile_name(name=pname, type='available'):
1915                    self.window.set_entry({'lbl_msg_warning': _('Profile name not valid')})
1916                    self.window.change_stack('box_warning')
1917                    let_continue = False
1918        elif 'editing_profile' in changes:
1919            if 'changed_pname' in changes:
1920                if not self.model.check_profile_name(name=pname, type='available'):
1921                    self.window.set_entry({'lbl_msg_warning': _('Profile name not valid')})
1922                    self.window.change_stack('box_warning')
1923                    let_continue = False
1924        #         else:
1925        #             changes.append('need_remove')
1926        #
1927        # if 'changed_desktop' in changes:
1928        #     desktops = self.model.get_liststore_selections(type='desktop')
1929        # else:
1930        #     desktops = self.initial_liststore['desktop']
1931        # if 'changed_plank' in changes:
1932        #     planks = self.model.get_liststore_selections(type='plank')
1933        # else:
1934        #     planks = self.initial_liststore['plank']
1935        # if 'changed_background' in changes:
1936        #     bg = self.window.get_entry('entry_desktop_background')[0]
1937        # else:
1938        #     bg = self.initial_liststore['background'][0]
1939
1940        #pname = self.window.get_entry('profile_name_entry')[0]
1941        #pname = pname.strip()
1942        if let_continue:
1943            desktops = self.model.get_liststore_selections(type='desktop')
1944            planks = self.model.get_liststore_selections(type='plank')
1945            bg = self.window.get_entry('entry_desktop_background')[0]
1946
1947            desktop_pack = []
1948            plank_pack = []
1949            bg_pack = []
1950            for x in desktops:
1951                for info in self.model.store_elements:
1952                    if x == info[APPCOL.BASENAME]:
1953                        content = None
1954                        try:
1955                            with open(info[APPCOL.FULLPATH],'rb') as fp:
1956                                content = base64.b64encode(fp.read())
1957                        except Exception as e:
1958                            continue
1959                        desktop_pack.append([info[APPCOL.FULLPATH],info[APPCOL.BASENAME],content.decode()])
1960                        break
1961            for x in planks:
1962                for info in self.model.store_elements:
1963                    if x == info[APPCOL.BASENAME]:
1964                        content = None
1965                        try:
1966                            with open(info[APPCOL.FULLPATH], 'rb') as fp:
1967                                content = base64.b64encode(fp.read())
1968                        except Exception as e:
1969                            continue
1970                        plank_pack.append([info[APPCOL.FULLPATH], info[APPCOL.BASENAME],content.decode()])
1971                        break
1972            content = None
1973            try:
1974                with open(bg, 'rb') as fp:
1975                    content = base64.b64encode(fp.read())
1976                bg_pack = [bg,content.decode()]
1977            except:
1978                pass
1979
1980
1981            grp = self.model.get_group_from_profile(nameprofile=pname)
1982            #     applist = self.model.get_liststore_selections(type='apps')
1983            #     grp = self.model.get_group_from_profile(nameprofile=profile_name)
1984            glist = []
1985            if grp != None:
1986                glist.append(grp)
1987            #
1988            if self.edit_mode == 'copy':
1989                self.user_is_editing = '_copy_mode_'
1990            elif self.edit_mode == 'move':
1991                pass
1992            else:
1993                raise Exception('Error edit mode not set!')
1994            self.helper.n4d_update_profile(user=self.model.user,pwd=self.model.pwd,name=self.user_is_editing,newname=pname,grouplist=glist,applist={'desktop':desktop_pack,'plank':plank_pack,'background':bg_pack})
1995            #if 'need_remove' in changes:
1996            #    self.helper.n4d_remove_profile(user=self.model.user,pwd=self.model.pwd,name=self.user_is_editing)
1997
1998        return let_continue
1999        #
2000        #   OLD CODE FROM GOTO(BACK)
2001        #
2002        # #
2003        # # Check if need to save something
2004        # #
2005        # changes_made = False
2006        # if self.last_window == 'box_profiles2':
2007        #     if self.user_is_editing == 'new_profile':
2008        #         if self.profile_name_changed or self.applist_changed:
2009        #             pname = self.window.get_entry('profile_name_entry')[0]
2010        #             # If modified and no changes are done really skip warning msg
2011        #             if pname.strip() != self.initial_profile_name:  # initial_profile_name always must be '' when is a new_profile
2012        #                 if not self.model.check_profile_name(name=pname, type='available'):
2013        #                     self.window.set_entry({'lbl_msg_warning': _('Profile name not valid')})
2014        #                     self.window.change_stack('box_warning')
2015        #                     changes_made = True
2016        #                 else:
2017        #                     # Store new profile (with valid name)
2018        #                     # Not allowed empty applist
2019        #                     applist = self.model.get_liststore_selections(type='desktop')
2020        #                     # if len(applist) > 0:
2021        #                     self.update_current_profile()
2022        #                     changes_made = True
2023        #     elif self.user_is_editing != '':
2024        #         # User is editing existent profile
2025        #         if self.applist_changed and not self.profile_name_changed:
2026        #             # Update current profile (self.user_is_editing)
2027        #             applist = self.model.get_liststore_selections(type='apps')
2028        #             if len(applist) > 0:
2029        #                 list_initial = self.initial_liststore
2030        #                 # check if anything has changed
2031        #                 applist_copy = list(applist)
2032        #                 for app in applist_copy:
2033        #                     if app in list_initial:
2034        #                         applist.remove(app)
2035        #                         list_initial.remove(app)
2036        #                 # skip update if modified list is the same initially created
2037        #                 if len(applist) > 0 or len(list_initial) > 0:
2038        #                     self.update_current_profile()
2039        #                     changes_made = True
2040        #
2041        #         if not self.applist_changed and self.profile_name_changed:
2042        #             # Rename current profile (self.user_is_editing)
2043        #             newname = self.window.get_entry('entry_profile_name')[0]
2044        #             # Skip if modified name is the same as initially created
2045        #             if newname.strip() != self.initial_profile_name:
2046        #                 if not self.model.check_profile_name(name=newname, type='available'):
2047        #                     self.window.set_entry({'lbl_msg_warning': _('Profile name not valid')})
2048        #                     self.window.change_stack('box_warning')
2049        #                     changes_made = True
2050        #                 else:
2051        #                     self.helper.n4d_rename_profile(user=self.model.user, pwd=self.model.pwd,
2052        #                                                    old=self.user_is_editing, new=newname)
2053        #                     changes_made = True
2054        #
2055        #         if self.applist_changed and self.profile_name_changed:
2056        #             # create new profile & remove old profile (self.user_is_editing)
2057        #             applist = self.model.get_liststore_selections(type='apps')
2058        #             if len(applist) > 0:
2059        #                 list_initial = self.initial_liststore
2060        #                 # check if anything has changed
2061        #                 applist_copy = list(applist)
2062        #                 for app in applist_copy:
2063        #                     if app in list_initial:
2064        #                         applist.remove(app)
2065        #                         list_initial.remove(app)
2066        #                 # skip update if modified list is the same initially created
2067        #                 if len(applist) > 0 or len(list_initial) > 0:
2068        #                     newname = self.window.get_entry('entry_profile_name')[0]
2069        #                     if newname.strip() != self.initial_profile_name:
2070        #                         if not self.model.check_profile_name(name=newname, type='available'):
2071        #                             self.window.set_entry({'lbl_msg_warning': _('Profile name not valid')})
2072        #                             self.window.change_stack('box_warning')
2073        #                         else:
2074        #                             self.helper.n4d_rename_profile(user=self.model.user, pwd=self.model.pwd,
2075        #                                                            old=self.user_is_editing, new=newname)
2076        #                             changes_made = True
2077        #                     else:
2078        #                         #
2079        #                         # -Uncomment if it's desired to remove old profile when modifications of name and apps are done
2080        #                         # -If commented, new profile it's created making a copy of the current profile with other name
2081        #                         # and remove profile only it's possible from the remove menu, doing more explicitly the remove action
2082        #                         #
2083        #                         # self.helper.n4d_remove_profile(user=self.model.user,pwd=self.model.pwd,name=self.user_is_editing)
2084        #                         self.update_current_profile()
2085        #                         changes_made = True
2086        #
2087        #     if changes_made:
2088        #         return
2089    #
2090    # Receives events from handler
2091    #
2092    #@trace
2093    def process(self,*args,**kwargs):
2094        try:
2095            #
2096            # Check arguments and unify commands
2097            # Mandatory 'action'
2098            # Optional: 'result': if it comes from callback
2099            #           'to': changing windows from button menu
2100            #
2101            actions=[]
2102            try:
2103                check_args('action',**kwargs)
2104            except Exception as e:
2105                dbg(e)
2106                sys.exit(1)
2107
2108            if type(kwargs['action']) == type(list()):
2109                actions.extend(kwargs['action'])
2110            else:
2111                actions.append(kwargs['action'])
2112            for op in args:
2113                if type(op) == type(list()):
2114                    actions.extend(op)
2115                else:
2116                    actions.append(op)
2117
2118            for action in actions:
2119                if action == 'initialized':
2120                    #
2121                    # Run first actions
2122                    #
2123                    self.helper.do_call(self.window.update_status_bar,self.model.cache_info,None)
2124                    self.helper.do_call(self.do_cache,{'action':'cache_completed'},self.process)
2125
2126                elif action == 'cache_completed':
2127                    if self.model.is_validated:
2128                        # when we are validated and don't have cache, screen shows loading
2129                        # when cache_completed but not validated, do nothing, validation will update window
2130                        self.window.change_stack('box_main_btn')
2131
2132                elif action == 'validation_init':
2133                    #
2134                    # Start the validation process
2135                    #
2136                    self.window.change_button_text(text='Checking',button='btn_validate')
2137                    us,pw=self.window.get_entry('entry_usr','entry_pw')
2138                    self.helper.n4d_validate(user=us,pwd=pw)
2139
2140                elif action == 'validation_end':
2141                    #
2142                    # Callback from n4d validation process
2143                    #
2144                    try:
2145                        check_args('result', **kwargs)
2146                    except Exception as e:
2147                        dbg('N4D fail check system!')
2148                        sys.exit(1)
2149                    result = kwargs['result']
2150                    self.validation_end(result)
2151
2152                elif action == 'goto':
2153                    #
2154                    # Button from main menu pressed
2155                    #
2156                    self.goto(*args,**kwargs)
2157
2158                elif action == 'edit_plank_items':
2159                    self.current_selection = 'plank'
2160                    self.model.refilter(type='all')
2161                    self.backup_selections['plank'] = self.model.get_liststore_selections(type='plank')
2162                    self.goto(to='edit_plank_items')
2163                elif action == 'edit_desktop_items':
2164                    self.current_selection = 'desktop'
2165                    self.model.refilter(type='all')
2166                    self.backup_selections['desktop'] = self.model.get_liststore_selections(type='desktop')
2167                    self.goto(to='edit_desktop_items')
2168                elif action == 'cancel_edit_elements':
2169                    self.model.set_liststore_selections(type=self.current_selection,list=self.backup_selections[self.current_selection])
2170                    self.current_selection = None
2171                    self.goto(to='back')
2172                elif action == 'accept_edit_items':
2173                    self.current_selection = None
2174                    self.goto(to='back')
2175                elif action == 'cancel_profile':
2176                    self.model.build_liststore(type='apps')
2177                    self.resource_previewed = None
2178                    self.window.set_entry({'entry_desktop_background': ''})
2179                    self.window.set_entry({'profile_name_entry':''})
2180                    self.reset_memories()
2181                    self.goto(to='main_window')
2182                elif action == 'profile_accepted':
2183                    if self.check_profile_changes():
2184                        self.goto(to='main_window')
2185                elif action == 'put_current_items':
2186                    self.model.put_current_items()
2187                    self.model.refilter(type='all')
2188                elif action == 'preview_background':
2189                    file_preview = self.window.get_entry('entry_desktop_background')[0]
2190                    if os.path.isfile(file_preview):
2191                        w = self.getobj('window_preview')
2192                        w.add(Gtk.Image.new_from_pixbuf(GdkPixbuf.Pixbuf.new_from_file_at_scale(file_preview, 640, 480, True)))
2193                        w.show_all()
2194                    else:
2195                        self.window.set_txt_status_bars('Unable to preview image {}'.format(file_preview))
2196                        self.helper.clean_status_bar(0)
2197                elif action == 'change_edit_mode':
2198                    name = kwargs.get('name')
2199                    if name == 'm':
2200                        self.edit_mode = 'move'
2201                    if name == 'c':
2202                        self.edit_mode = 'copy'
2203                    pass
2204                elif action == 'close_preview':
2205                    w = self.getobj('window_preview')
2206                    w.hide()
2207
2208                elif action == 'close_filechooser':
2209                    #
2210                    # Cancel Filechooser from button
2211                    #
2212                    self.window.dialog_filechooser.hide()
2213
2214                elif action == 'selected_resource':
2215                    #
2216                    # Resource previewed is selected (button accept or double click)
2217                    #
2218                    self.window.dialog_filechooser.hide()
2219                    self.window.set_entry({'entry_desktop_background': self.resource_previewed})
2220
2221                elif action == 'selection_changed':
2222                    #
2223                    # Selected image to preview
2224                    #
2225                    resource = kwargs['file']
2226                    i=self.builder.get_object('image1')
2227                    p=GdkPixbuf.Pixbuf.new_from_file_at_scale(resource,200,200,True)
2228                    i.set_from_pixbuf(p)
2229                    self.resource_previewed = resource
2230
2231                elif action == 'remove_selected_background':
2232                    self.resource_previewed = None
2233                    self.window.set_entry({'entry_desktop_background': ''})
2234
2235                elif action == 'refilter_elements':
2236                    ##############################
2237                    #
2238                    # refilter when filter entry has changed
2239                    #
2240                    ##############################
2241                    self.model.refilter(type='elements')
2242                elif action == 'swap':
2243                    ##############################
2244                    #
2245                    # change model & refilter when swaps elements
2246                    #
2247                    ##############################
2248                    check_args('id',**kwargs)
2249                    id=kwargs['id']
2250                    #self.applist_changed = True
2251                    col = None
2252                    if self.current_selection == 'desktop':
2253                        col = APPCOL.SELECTED_DESKTOP
2254                    if self.current_selection == 'plank':
2255                        col = APPCOL.SELECTED_PLANK
2256                    for e in self.model.store_elements:
2257                        if e[APPCOL.ID]==id:
2258                            e[col] = not e[col]
2259                            break
2260                    self.model.refilter(type='all')
2261                    i,j=self.model.count_selected(type='all')
2262                    self.window.set_txt_status_bars('({},{}) {}'.format(i,j,_('apps selected')))
2263                # elif action == 'swap_locals':
2264                #     ##############################
2265                #     #
2266                #     # change model for local elements
2267                #     #
2268                #     ##############################
2269                #     self.applist_changed = True
2270                #     for e in self.model.store_elements:
2271                #         if e[APPCOL.TYPE]=='local':
2272                #             e[APPCOL.SELECTED] = True
2273                #     self.model.refilter(type='all')
2274                #     i,j=self.model.count_selected(type='all')
2275                #     self.window.set_txt_status_bars('({},{}) {}'.format(i,j,_('apps selected')))
2276                elif action == 'set_groups':
2277                    ##############################
2278                    #
2279                    # callback from n4d_get_system_groups
2280                    #
2281                    ##############################
2282                    try:
2283                        check_args('result',**kwargs)
2284                    except Exception as e:
2285                        dbg('N4D fail check system!')
2286                        sys.exit(1)
2287                    self.model.group_list = []
2288                    for g in [x['cn'][0] for x in kwargs['result']]:
2289                        self.model.group_list.append(g)
2290
2291                elif action == 'set_profiles':
2292                    ##############################
2293                    #
2294                    # callback from n4d_get_profiles_stored
2295                    #
2296                    ##############################
2297                    try:
2298                        check_args('result',**kwargs)
2299                    except Exception as e:
2300                        dbg('N4D fail check system!')
2301                        sys.exit(1)
2302                    self.model.profile_list = []
2303                    for p in kwargs['result']:
2304                        if type(p[1]) != type(str()) or type(p[0]) != type(str()):
2305                            continue
2306                        self.model.profile_list.append(p)
2307                        for g in p[1].split(','):
2308                            if g != '':
2309                                self.model.current_relations[g]=p[0]
2310                    self.model.sort_profiles()
2311
2312                elif action == 'remove_profile_done':
2313                    ##############################
2314                    #
2315                    # Callback from n4d_remove_profile
2316                    #
2317                    ##############################
2318
2319                    # Update information about existent profiles
2320                    self.model.clear_liststore(type='profiles')
2321                    self.window.profiles_need_refresh = True
2322                    self.helper.n4d_get_profiles_stored(user=self.model.user, pwd=self.model.pwd)
2323                    self.window.change_stack('box_main_btn')
2324
2325                elif action == 'profile_is_updated':
2326                    ##############################
2327                    #
2328                    # Callback from n4d_update_profile
2329                    #
2330                    ##############################
2331
2332                    # Update information about existent profiles
2333                    self.model.clear_liststore(type='profiles')
2334                    self.window.profiles_need_refresh = True
2335                    self.helper.n4d_get_profiles_stored(user=self.model.user, pwd=self.model.pwd)
2336                    check_args('name',**kwargs)
2337                    name = kwargs['name']
2338                    # If this is a (single) callback from edit profiles
2339                    if self.last_window != 'box_group_edit':
2340                        check_args('newname', **kwargs)
2341                        newname = kwargs['newname']
2342                        self.reset_memories()
2343                        #self.window.set_txt_status_bars(name + _(' updated!'))
2344                        self.window.set_txt_status_bars(newname + _(' updated!'))
2345                        self.helper.clean_status_bar(0)
2346                        self.window.change_stack('box_main_btn')
2347                    else:
2348                    # In this is a (multiple or single) callback from profile-group change relation
2349                        check_args('status_bar',**kwargs)
2350                        bar = kwargs['status_bar']
2351                        if bar == 'old':
2352                            # old profile msg is always showed into first statusbar
2353                            self.window.set_txt_status_bars(bar_info={'0': name + _(' updated!')})
2354                            self.helper.clean_status_bar(0)
2355                        if bar == 'new':
2356                            # old profile msg is always showed into second statusbar
2357                            self.window.set_txt_status_bars(bar_info={'1': name + _(' updated!')})
2358                            self.helper.clean_status_bar(1)
2359
2360
2361                # elif action == 'profile_is_renamed':
2362                #     ##############################
2363                #     #
2364                #     # Callback from n4d_rename_profile
2365                #     #
2366                #     ##############################
2367                #
2368                #     # Update information about existent profiles
2369                #     self.model.clear_liststore(type='profiles')
2370                #     self.window.profiles_need_refresh = True
2371                #     self.helper.n4d_get_profiles_stored(user=self.model.user, pwd=self.model.pwd)
2372                #     # Force redo liststore from combobox from profile window, next time will rebuild the view
2373                #     for boxname in self.window.group_profile_boxes:
2374                #         self.window.group_profile_boxes[boxname][0].destroy()
2375                #         self.window.group_profile_boxes[boxname][1].destroy()
2376                #     self.window.group_profile_boxes = {}
2377                #     self.reset_memories()
2378                #     # Show msg into status bar
2379                #     check_args('oldname', 'newname', **kwargs)
2380                #     oldname = kwargs['oldname']
2381                #     newname = kwargs['newname']
2382                #     self.window.set_txt_status_bars(_('Updated profile from ')+oldname+_(' to ')+newname)
2383                #     self.helper.clean_status_bar(0)
2384                #     # Change user's view
2385                #     self.window.change_stack('box_main_btn')
2386
2387                elif action == 'load_profile_into_liststore':
2388                    ##############################
2389                    #
2390                    # Callback from n4d_read_profile
2391                    #
2392                    ##############################
2393                    check_args('name','result',**kwargs)
2394                    profile_readed = kwargs['result']
2395                    workspace = self.helper.prepare_workspace(profile=profile_readed)
2396                    if not workspace:
2397                        self.window.set_txt_status_bars('{}'.format(_('Error reading profile')))
2398                    else:
2399                        self.reset_memories()
2400                        self.model.build_liststore(type='apps')
2401                        self.initial_liststore = self.model.load_profile_into_liststore(name=kwargs['name'], workspace=workspace)
2402                        i,j = self.model.count_selected(type='all')
2403                        self.window.set_txt_status_bars('({},{}) {}'.format(i,j,_('apps selected')))
2404                        self.window.set_entry({'profile_name_entry':kwargs['name']})
2405                        self.initial_profile_name = kwargs['name']
2406                        self.user_is_editing = kwargs['name']
2407                        self.edit_mode = 'move'
2408                        self.window.change_stack('box_profiles2')
2409
2410                elif action == 'change_relations':
2411                    check_args('group','profile',**kwargs)
2412                    group = kwargs['group']
2413                    profile = kwargs['profile']
2414                    oldrelation = ''
2415                    if group in self.model.current_relations:
2416                        oldrelation = self.model.current_relations[group]
2417                    #
2418                    # Sanity checks about relations
2419                    #
2420                    if self.check_sanity(group=group,profile=profile):
2421                        self.update_profile_relations(group=group, profile=profile, oldrelation=oldrelation)
2422
2423            return
2424        except Exception as e:
2425            if not self.aborting_all_operations:
2426                raise Exception(e)
2427            else:
2428                sys.exit(1)
2429    ##############################
2430    #
2431    # Validation checking actions
2432    #
2433    ##############################
2434    def validation_end(self,*args,**kwargs):
2435        result = args[0]
2436        self.window.change_button_text(text='Validate', button='btn_validate')
2437        #
2438        # Check the completed validation
2439        #
2440        return_str = result.split(' ')
2441        if return_str[0].lower() == 'true' and return_str[1].lower() in ['admin', 'adm', 'admins', 'teachers']:
2442            # Validation is done and sucessful
2443            us, pw = self.window.get_entry('entry_usr', 'entry_pw')
2444            self.model.user = us
2445            self.model.pwd = pw
2446            self.model.is_validated = True
2447            #
2448            # Get information about existent groups
2449            #
2450            self.helper.n4d_get_system_groups(user=us,pwd=pw)
2451            #
2452            # Get information about existent profiles
2453            #
2454            self.helper.n4d_get_profiles_stored(user=us,pwd=pw)
2455            #
2456            # Update content window
2457            #
2458            if self.model.cache_info['done']:
2459                self.window.change_stack('box_main_btn')
2460            else:
2461                self.window.change_stack('box_loading')
2462        else:
2463            # Validation is failed
2464            self.window.set_entry({'entry_usr': '', 'entry_pw': ''})
2465            self.getobj('img_emoji').set_from_icon_name('face-crying', 100)
2466    ##############################
2467    #
2468    # Change window
2469    #
2470    ##############################
2471    def goto(self,*args,**kwargs):
2472        check_args('to', **kwargs)
2473        to = kwargs['to']
2474        if to == 'main_window':
2475            self.reset_memories()
2476            self.window.set_txt_status_bars('')
2477            self.window.change_stack('box_main_btn')
2478        if to == 'new_profile':
2479            self.window.set_entry({'profile_name_entry':'','entry_desktop_background':''})
2480            # TODO: Chango to reset memories
2481            self.resource_previewed = None
2482            self.initial_profile_name = ''
2483            self.initial_liststore = {}
2484            self.model.build_liststore(type='apps')
2485            self.user_is_editing = '_____new_profile_____'
2486            self.window.show_edit_mode('hide')
2487            self.edit_mode = 'copy'
2488            self.window.change_stack('box_profiles2')
2489        if to == 'filechooser':
2490            self.window.dialog_filechooser.set_current_folder('/usr/share/backgrounds')
2491            self.window.dialog_filechooser.show_all()
2492        if to == 'edit_profile':
2493            self.model.build_liststore(type='profiles')
2494            self.window.show_edit_mode('show')
2495            self.window.change_stack('box_pselector')
2496        if to == 'edit_plank_items':
2497            self.model.change_model_selected(type='plank')
2498            self.window.change_stack('box_edit_elements')
2499        if to == 'edit_desktop_items':
2500            self.model.change_model_selected(type='desktop')
2501            self.window.change_stack('box_edit_elements')
2502        if to == 'group_mapping':
2503            #prevent trigger combo actions
2504            self.building_view = True
2505            self.window.build_group_profile_window(groups=self.model.group_list,profiles=self.model.profile_list)
2506            self.building_view = False
2507            self.window.change_stack('box_group_edit')
2508        if to == 'ask_remove':
2509            check_args('id', **kwargs)
2510            id = kwargs['id']
2511            name_profile = self.model.profile_list[id][0]
2512            txt="{} '{}'?".format(_('Are you sure to remove the profile'),name_profile)
2513            self.window.set_entry({'lbl_msg_dialog':txt})
2514            self.window.change_stack('box_dialog')
2515        if to == 'editing_profile':
2516            check_args('id', **kwargs)
2517            id = kwargs['id']
2518            name_profile=self.model.profile_list[id][0]
2519            self.helper.n4d_read_profile(user=self.model.user,pwd=self.model.pwd,name=name_profile)
2520        if to == 'remove_confirmed':
2521            check_args('id', **kwargs)
2522            id = kwargs['id']
2523            name_profile = self.model.profile_list[id][0]
2524            self.helper.n4d_remove_profile(user=self.model.user,pwd=self.model.pwd,name=name_profile)
2525        if to == 'back':
2526            # if nothing is saved do normal go back
2527            self.model.n_selected = 0
2528            self.window.set_txt_status_bars('','')
2529            self.window.change_stack('back')
2530        return
2531
2532    # ##############################
2533    # #
2534    # # Read store & create or update a profile
2535    # #
2536    # ##############################
2537    # def update_current_profile(self,*args,**kwargs):
2538    #     profile_name = self.window.get_entry('entry_profile_name')[0]
2539    #     #if self.user_is_editing == 'new_profile' and not self.model.check_profile_name(name=profile_name,type='available'):
2540    #     #    self.window.set_entry({'lbl_msg_warning':_('Profile name not valid')})
2541    #     #    self.window.change_stack('box_warning')
2542    #     #    self.reset_memories()
2543    #     #    return
2544    #     applist = self.model.get_liststore_selections(type='apps')
2545    #     grp = self.model.get_group_from_profile(nameprofile=profile_name)
2546    #     glist = []
2547    #     if grp != None:
2548    #         glist.append(grp)
2549    #
2550    #     self.helper.n4d_update_profile(user=self.model.user,pwd=self.model.pwd,name=profile_name,grouplist=glist,applist=applist)
2551    #     return
2552
2553    ##############################
2554    #
2555    # Update profile assigning one group when changed on combo and it's different
2556    #
2557    ##############################
2558    def update_profile_relations(self,*args,**kwargs):
2559        try:
2560            #
2561            # First call acts as a caller from N4D method setting itself as a callback method
2562            # if haven't group & profile parameters, method is doing callback with 'result' parameter
2563            #
2564            check_args('group', 'profile','oldrelation', **kwargs)
2565            group = kwargs['group']
2566            profile_selected = kwargs['profile']
2567            old_profile = kwargs['oldrelation']
2568            #
2569            # Check if it's needed to update old profile removing relation
2570            #
2571            if self.model.check_profile_name(name=old_profile,type='valid'):
2572                #
2573                # need to kwown the current apps and other groups applied from profile before updating
2574                #
2575                oldgroups = []
2576                for p in self.model.profile_list:
2577                    pname = p[0]
2578                    pgrps = p[1]
2579                    if pname == old_profile:
2580                        for g in pgrps.split(','):
2581                            if g != group and g != '':
2582                                oldgroups.append(g)
2583                self.helper.n4d_read_profile(user=self.model.user, pwd=self.model.pwd, name=old_profile,
2584                                             noprocess=self.update_profile_relations,
2585                                             extraparam={'grouplist': oldgroups,'status_bar':'old'})
2586            if self.model.check_profile_name(name=profile_selected,type='valid'):
2587                #
2588                # need to kwown the current apps and other groups applied from profile before updating
2589                #
2590                oldgroups = []
2591                for p in self.model.profile_list:
2592                    pname = p[0]
2593                    pgrps = p[1]
2594                    if pname == profile_selected:
2595                        for g in pgrps.split(','):
2596                            if g != group and g != '':
2597                                oldgroups.append(g)
2598                newglist = [group]
2599                newglist.extend(oldgroups)
2600                self.helper.n4d_read_profile(user=self.model.user, pwd=self.model.pwd, name=profile_selected,
2601                                             noprocess=self.update_profile_relations,
2602                                             extraparam={'grouplist': newglist,'status_bar':'new'})
2603        except Exception as e:
2604            #
2605            # Second call of this function, when callback from n4d_read_profile is done, 'result' is set into parameters with list applications of profile
2606            #
2607            check_args('grouplist','result','name','status_bar',**kwargs)
2608            profile_readed = kwargs['result']
2609            workspace = self.helper.prepare_workspace(profile=profile_readed)
2610            grouplist = kwargs['grouplist']
2611            name = kwargs['name']
2612            status_bar = kwargs['status_bar']
2613
2614            self.helper.n4d_update_profile_groups(user=self.model.user,pwd=self.model.pwd,name=name,grouplist=grouplist, status_bar=status_bar)
2615            # After this call return to normal callback and will update profile list with new relations
2616        return
2617
2618    ##############################
2619    #
2620    # Sanity logic about relations
2621    #
2622    ##############################
2623    def check_sanity(self,*args,**kwargs):
2624        check_args('group','profile',**kwargs)
2625        group = kwargs['group']
2626        profile = kwargs['profile']
2627
2628        if not group:
2629            return False
2630
2631        # Existence flags
2632        ret_gr = False
2633
2634        for g in self.model.group_list:
2635            if g == group:
2636                # Group must exist
2637                ret_gr = True
2638                break
2639        for p in self.model.profile_list:
2640            pname = p[0]
2641            pgroup = p[1]
2642
2643            for g in pgroup.split(','):
2644                if group == g:
2645                    # Fail if already relationed
2646                    if pname == profile:
2647                        return False
2648
2649        return ret_gr
2650
2651
2652
2653##############################
2654#
2655# Main program
2656#
2657##############################
2658
2659GObject.threads_init()
2660
2661if __name__ == "__main__":
2662    ctl = Controller(localpath=os.path.dirname(os.path.realpath(__file__))+'/../lib/syncer-plank')
2663    #ctl = Controller(localpath='/usr/lib/syncer-plank')
2664    sys.exit(Gtk.main())
Note: See TracBrowser for help on using the repository browser.