source: n4d/trunk/fuentes/install-files/usr/share/n4d/python-plugins/VariablesManager.py @ 4407

Last change on this file since 4407 was 4407, checked in by hectorgh, 3 years ago

small changes in variables manager remote clientes logic

File size: 19.3 KB
Line 
1import json
2import os.path
3import os
4import time
5import xmlrpclib
6import socket
7import netifaces
8import re
9import importlib
10import sys
11import tarfile
12import threading
13import subprocess
14import time
15import string
16
17class VariablesManager:
18
19        VARIABLES_FILE="/var/lib/n4d/variables"
20        VARIABLES_DIR="/var/lib/n4d/variables-dir/"
21        LOCK_FILE="/tmp/.llxvarlock"
22        INBOX="/var/lib/n4d/variables-inbox/"
23        TRASH="/var/lib/n4d/variables-trash/"
24        CUSTOM_INSTALLATION_DIR="/usr/share/n4d/variablesmanager-funcs/"
25        LOG="/var/log/n4d/variables-manager"
26       
27        def __init__(self):
28               
29                self.instance_id="".join(random.sample(string.letters+string.digits, 50))
30                self.server_instance_id=None
31                self.variables={}
32                self.variables_ok=False
33                self.variables_clients={}
34                self.variables_triggers={}
35                t=threading.Thread(target=self.check_clients,args=())
36                t.daemon=True
37                t.start()
38               
39                if os.path.exists(VariablesManager.LOCK_FILE):
40                        os.remove(VariablesManager.LOCK_FILE)
41                       
42                       
43                if os.path.exists(VariablesManager.VARIABLES_FILE):
44                        self.variables_ok,ret=self.load_json(VariablesManager.VARIABLES_FILE)
45                        try:
46                                os.remove(VariablesManager.VARIABLES_FILE)
47                        except:
48                                pass
49                else:
50                        self.variables_ok,ret=self.load_json(None)
51                       
52                if self.variables_ok:
53                        #print "\nVARIABLES FILE"
54                        #print "=============================="
55                        #self.listvars()
56                        self.read_inbox(False)
57                        #print "\nAFTER INBOX"
58                        #print "=============================="
59                        #print self.listvars(True)
60                        self.empty_trash(False)
61                        #print "\nAFTER TRASH"
62                        #print "=============================="
63                        #print self.listvars(True)
64                        self.add_volatile_info()
65                        self.write_file()
66                else:
67                        print("[VariablesManager] Loading variables failed because: " + str(ret))
68
69               
70        #def __init__
71       
72       
73        def startup(self,options):
74
75                if "REMOTE_VARIABLES_SERVER" in self.variables:
76                        self.register_n4d_instance_to_server()
77                       
78        #def startup
79       
80       
81        def is_ip_in_range(self,ip,network):
82               
83                try:
84                        return netaddr.ip.IPAddress(ip) in netaddr.IPNetwork(network).iter_hosts()
85                except:
86                        return False
87                       
88        #def is_ip_in_range
89       
90
91        def get_net_size(self,netmask):
92               
93                netmask=netmask.split(".")
94                binary_str=""
95                for octet in netmask:
96                        binary_str += bin(int(octet))[2:].zfill(8)
97                       
98                return str(len(binary_str.rstrip('0')))
99               
100        #def get_net_size
101
102
103        def get_ip(self):
104               
105                for item in netifaces.interfaces():
106                        tmp=netifaces.ifaddresses(item)
107                        if tmp.has_key(netifaces.AF_INET):
108                                if tmp[netifaces.AF_INET][0].has_key("broadcast") and tmp[netifaces.AF_INET][0]["broadcast"]=="10.0.2.255":
109                                        return tmp[netifaces.AF_INET][0]["addr"]
110                return None
111               
112        #def get_ip
113       
114
115        def route_get_ip(self,ip):
116               
117                p=subprocess.Popen(["ip route get %s"%ip],shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE).communicate()
118                if "dev" in p[0]:
119                        dev=p[0].split("dev ")[1].split(" ")[0]
120                else:
121                        dev=None
122                return dev
123               
124        #def route_get_ip
125               
126
127        def get_mac_from_device(self,dev):
128
129                for item in netifaces.interfaces():
130                       
131                        try:
132                                i=netifaces.ifaddresses(item)
133                                mac=i[17][0]["addr"]
134                                broadcast=i[2][0]["broadcast"]
135                                network=broadcast
136                                netmask=i[2][0]["netmask"]
137                                network+="/%s"%self.get_net_size(netmask)
138                                ip=i[2][0]["addr"]
139                        except Exception as e:
140                                continue
141                       
142                        if dev=="lo":
143                                return mac
144                       
145                        if item==dev:
146                                return mac
147                               
148                return None
149
150        #def get_mac_from_device_in_server_network
151       
152       
153        def register_instance(self,autocompleted_secured_ip,mac):
154
155                client={}
156                client["last_check"]=int(time.time())
157                client["missed_pings"]=0
158                client["ip"]=autocompleted_secured_ip
159                self.variables_clients[mac]=client
160               
161                return self.instance_id
162
163        #def register_instance
164       
165
166        def register_n4d_instance_to_server(self):
167               
168                try:
169                        server_ip=socket.gethostbyname(self.variables["REMOTE_VARIABLES_SERVER"][u"value"])
170                        if self.get_ip()!=server_ip:
171                       
172                                c=xmlrpclib.ServerProxy("https://%s:9779"%server_ip)
173                                mac=self.get_mac_from_device(self.route_get_ip(server_ip))
174                                self.server_instance_id=c.register_instance("","VariablesManager","",mac)
175                       
176                except Exception as e:
177
178                                self.server_instance_id=None
179
180                        return None
181
182        #def register_n4d_instance_to_server   
183       
184       
185        def check_clients(self):
186               
187                while True:
188                       
189                        for item in self.variables_clients:
190                                ip=self.variables_clients[item]["ip"]
191                                sys.stdout.write("[VariablesManager] Checking client { MAC:%s IP:%s } ... "%(item,ip))
192                                c=xmlrpclib.ServerProxy("https://%s:9779"%ip)
193                                try:
194                                        c.get_methods()
195                                        self.variables_clients[item]["last_check"]=time.time()
196                                        self.variables_clients[item]["missed_pings"]=0
197                                        print("OK")
198                                except:
199                                        self.variables_clients[item]["missed_pings"]+=1
200                                        print("FAILED")
201                                        if self.variables_clients[item]["missed_pings"] >=3:
202                                                print "[VariablesManager] Removing client due to too many missed pings."
203                                                self.variables_clients.pop(item)
204                       
205                        time.sleep(60*5)
206               
207               
208        #def check_clients
209       
210        def notify_changes(self,variable):
211               
212               
213                if len(self.variables_clients) > 0:
214               
215                        print "[VariablesManager] Notifying changes... "
216                        for mac in self.variables_clients:
217                               
218                                ip=self.variables_clients[mac]["ip"]
219                                c=xmlrpclib.ServerProxy("https://%s:9779"%ip)
220                                try:
221                                        c.server_changed("","VariablesManager","",self.instance_id,variable)
222                                       
223                                except:
224                                        self.variables_clients[mac]["missed_pings"]+=1
225                                       
226                                self.variables_clients[mac]["last_check"]=time.time()
227               
228        #def announce_changes
229       
230       
231        def server_changed(self,autocompleted_server_ip,server_instance_id,variable_name):
232
233                if server_instance_id==self.server_instance_id:
234
235                        if self.variables["REMOTE_VARIABLES_SERVER"] [u'value']!=autocompleted_server_ip:
236                                return False
237
238                        print "[VariablesManager] Server instance ID validated"
239
240                        t=threading.Thread(target=self.execute_trigger,args=(variable_name,))
241                        t.daemon=True
242                        t.start()
243                       
244                        return True
245                       
246                else:
247                       
248                        return False
249               
250        #def server_changed
251       
252       
253        def execute_trigger(self,variable_name):
254               
255                if variable_name in self.variables_triggers:
256                        for i in self.variables_triggers[variable_name]:
257                                class_name,function=i
258                                try:
259                                        print "[VariablesManager] Executing %s.%s trigger..."%(class_name,function.im_func.func_name)
260                                        function()
261                                except Exception as e:
262                                        print e
263                                        pass
264               
265        #def execute_trigger
266       
267       
268        def register_trigger(self,variable_name,class_name,function):
269               
270                if variable_name not in self.variables_triggers:
271                        self.variables_triggers[variable_name]=[]
272                       
273                self.variables_triggers[variable_name].append((class_name,function))
274               
275        #def register_trigger
276       
277       
278        def backup(self,dir="/backup"):
279               
280                try:
281               
282                        #file_path=dir+"/"+self.get_time()+"_VariablesManager.tar.gz"
283                        file_path=dir+"/"+get_backup_name("VariablesManager")
284                               
285                        tar=tarfile.open(file_path,"w:gz")
286                       
287                        tar.add(VariablesManager.VARIABLES_DIR)
288                       
289                        tar.close()
290                       
291                        return [True,file_path]
292                       
293                except Exception as e:
294                        return [False,str(e)]
295               
296        #def backup
297
298       
299        def restore(self,file_path=None):
300
301
302                if file_path==None:
303                        for f in sorted(os.listdir("/backup"),reverse=True):
304                                if "VariablesManager" in f:
305                                        file_path="/backup/"+f
306                                        break
307
308                try:
309
310                        if os.path.exists(file_path):
311                               
312                                tmp_dir=tempfile.mkdtemp()
313                                tar=tarfile.open(file_path)
314                                tar.extractall(tmp_dir)
315                                tar.close()
316                               
317                                if not os.path.exists(VariablesManager.VARIABLES_DIR):
318                                        os.mkdir(VariablesManager.VARIABLES_DIR)
319                               
320                                for f in os.listdir(tmp_dir+VariablesManager.VARIABLES_DIR):
321                                        tmp_path=tmp_dir+VariablesManager.VARIABLES_DIR+f
322                                        shutil.copy(tmp_path,VariablesManager.VARIABLES_DIR)
323                                       
324                                self.load_json(None)
325                                               
326                                return [True,""]
327                               
328                except Exception as e:
329                               
330                        return [False,str(e)]
331               
332        #def restore
333       
334        def log(self,txt):
335               
336                try:
337                        f=open(VariablesManager.LOG,"a")
338                        txt=str(txt)
339                        f.write(txt+"\n")
340                        f.close()
341                except Exception as e:
342                        pass
343               
344        #def log
345       
346        def listvars(self,extra_info=False,custom_dic=None):
347                ret=""
348               
349                try:
350               
351                        if custom_dic==None:
352                                custom_dic=self.variables
353                        for variable in custom_dic:
354                                if type(custom_dic[variable])==type({}) and "root_protected" in custom_dic[variable] and custom_dic[variable]["root_protected"]:
355                                        continue
356                                value=self.get_variable(variable)
357                                if value==None:
358                                        continue
359                                ret+=variable+ "='" + str(value).encode("utf-8") + "';\n"
360                                if extra_info:
361                                        ret+= "\tDescription: " + self.variables[variable][u"description"] + "\n"
362                                        ret+="\tUsed by:\n"
363                                        for depend in self.variables[variable][u"packages"]:
364                                                ret+= "\t\t" + depend.encode("utf-8") + "\n"
365                       
366                        return ret.strip("\n")
367                except Exception as e:
368                        return str(e)
369                                       
370        #def listvars
371       
372        def calculate_variable(self,value):
373                pattern="_@START@_.*?_@END@_"
374                variables=[]
375               
376                ret=re.findall(pattern,value)
377               
378                for item in ret:
379                        tmp=item.replace("_@START@_","")
380                        tmp=tmp.replace("_@END@_","")
381                        variables.append(tmp)
382               
383                for var in variables:
384                        value=value.replace("_@START@_"+var+"_@END@_",self.get_variable(var))
385                       
386                return value
387               
388        #def remove_calculated_chars
389       
390        def add_volatile_info(self):
391               
392                for item in self.variables:
393               
394                        if not self.variables[item].has_key("volatile"):
395                                self.variables[item]["volatile"]=False
396               
397        #def add_volatile_info
398
399       
400        def showvars(self,var_list,extra_info=False):
401               
402                ret=""
403               
404                for var in var_list:
405                        ret+=var+"='"
406                        if self.variables.has_key(var):
407                                try:
408                                        ret+=self.variables[var][u'value'].encode("utf-8")+"';\n"
409                                except Exception as e:
410                                        #it's probably something old showvars couldn't have stored anyway
411                                        ret+="';\n"
412                                if extra_info:
413                                        ret+= "\tDescription: " + self.variables[var][u"description"] + "\n"
414                                        ret+="\tUsed by:\n"
415                                        for depend in self.variables[var][u"packages"]:
416                                                ret+= "\t\t" + depend.encode("utf-8") + "\n"
417                        else:
418                                ret+="'\n"
419                                               
420                return ret.strip("\n")
421               
422        #def  showvars
423
424       
425        def get_variables(self):
426
427                return self.variables
428               
429        #def get_variables
430               
431       
432        def load_json(self, file=None):
433
434                self.variables={}
435               
436                if file!=None:
437               
438                        try:
439                               
440                                f=open(file,"r")
441                                data=json.load(f)
442                                f.close()
443                                self.variables=data
444                               
445                                #return [True,""]
446                               
447                        except Exception as e:
448                                print(str(e))
449                                #return [False,e.message]
450                               
451                for file in os.listdir(VariablesManager.VARIABLES_DIR):
452                        try:
453                                sys.stdout.write("\t[VariablesManager] Loading " + file + " ... ")
454                                f=open(VariablesManager.VARIABLES_DIR+file)     
455                                data=json.load(f)
456                                f.close()
457                                self.variables[file]=data[file]
458                                print("OK")
459                        except Exception as e:
460                                print("FAILED ["+str(e)+"]")
461                               
462                return [True,""]
463               
464        #def load_json
465       
466        def read_inbox(self, force_write=False):
467               
468                '''
469                        value
470                        function
471                        description
472                        packages
473                '''
474               
475                if self.variables_ok:
476               
477                        if os.path.exists(VariablesManager.INBOX):
478                               
479                                for file in os.listdir(VariablesManager.INBOX):
480                                        file_path=VariablesManager.INBOX+file
481                                        print "[VariablesManager] Adding " + file_path + " info..."
482                                        try:
483                                                f=open(file_path,"r")
484                                                data=json.load(f)
485                                                f.close()
486                                               
487                                                for item in data:
488                                                        if self.variables.has_key(item):
489                                                                for key in data[item].keys():
490                                                                        if not self.variables[item].has_key(unicode(key)):
491                                                                                self.variables[item][unicode(key)] = data[item][key]
492                                                                if data[item].has_key(unicode('function')):
493                                                                        self.variables[item][unicode('function')] = data[item][u'function']
494                                                                for depend in data[item][u'packages']:
495                                                                        if depend not in self.variables[item][u'packages']:
496                                                                                self.variables[item][u'packages'].append(depend)
497                                                               
498                                                                if "force_update" in data[item] and data[item]["force_update"]:
499                                                                        self.variables[item][u'value']=data[item][u'value']
500                                                        else:
501                                                                self.variables[item]=data[item]
502
503                                       
504                                        except Exception as e:
505                                                print e
506                                                #return [False,e.message]
507                                        os.remove(file_path)
508                               
509                                if force_write:
510                                        try:
511                                                self.add_volatile_info()
512                                                self.write_file()
513                                        except Exception as e:
514                                                print(e)
515                                               
516               
517                return [True,""]
518                               
519        #def read_inbox
520
521       
522        def empty_trash(self,force_write=False):
523               
524               
525                if self.variables_ok:
526               
527                        for file in os.listdir(VariablesManager.TRASH):
528                                file_path=VariablesManager.TRASH+file
529                                #print "[VariablesManager] Removing " + file_path + " info..."
530                                try:
531                                        f=open(file_path,"r")
532                                        data=json.load(f)
533                                        f.close()
534                                       
535                                        for item in data:
536                                                if self.variables.has_key(item):
537                                                        if data[item][u'packages'][0] in self.variables[item][u'packages']:
538                                                                count=0
539                                                                for depend in self.variables[item][u'packages']:
540                                                                        if depend==data[item][u'packages'][0]:
541                                                                                self.variables[item][u'packages'].pop(count)
542                                                                                if len(self.variables[item][u'packages'])==0:
543                                                                                        self.variables.pop(item)
544                                                                                break
545                                                                        else:
546                                                                                count+=1
547                                                       
548                                        #os.remove(file_path)
549                                       
550                                       
551                                except Exception as e:
552                                        print e
553                                        pass
554                                        #return [False,e.message]
555                                os.remove(file_path)
556                       
557                        if force_write:
558                                try:   
559                                        self.write_file()
560                                except Exception as e:
561                                        print(e)
562                               
563                return [True,'']
564                       
565               
566        #def empty_trash
567       
568
569        def get_variable_list(self,variable_list,store=False,full_info=False):
570               
571                ret={}
572                if variable_list!=None:
573                        for item in variable_list:
574                                try:
575                                        ret[item]=self.get_variable(item,store,full_info)
576                                        #if ret[item]==None:
577                                        #       ret[item]=""
578                                except Exception as e:
579                                        print e
580
581                return ret
582               
583        #def get_variable_list
584       
585
586        def get_variable(self,name,store=False,full_info=False,key=None):
587       
588                global master_password
589               
590                if name in self.variables and self.variables[name].has_key("root_protected") and self.variables[name]["root_protected"] and key!=master_password:
591                        return None
592                       
593                if name in self.variables and self.variables[name].has_key("function"):
594                        try:
595                                if not full_info:
596                                        if (type(self.variables[name][u"value"])==type("") or  type(self.variables[name][u"value"])==type(u"")) and self.variables[name][u"value"].find("_@START@_")!=-1:
597                                                #print "I have to ask for " + name + " which has value: " + self.variables[name][u'value']
598                                                value=self.calculate_variable(self.variables[name][u"value"])
599                                        else:
600                                                value=self.variables[name][u"value"]
601                                        #return str(value.encode("utf-8")
602                                        if type(value)==type(u""):
603                                                try:
604                                                        ret=value.encode("utf-8")
605                                                        return ret
606                                                except:
607                                                        return value
608                                        else:
609                                                return value
610                                else:
611                                        variable=self.variables[name].copy()
612                                        variable["remote"]=False
613                                        if type(variable[u"value"])==type(""):
614                                                if variable[u"value"].find("_@START@_")!=-1:
615                                                        variable["original_value"]=variable[u"value"]
616                                                        variable[u"value"]=self.calculate_variable(self.variables[name][u"value"])
617                                                        variable["calculated"]=True
618                                        return variable
619                        except:
620                                return None
621                else:
622                        if self.variables.has_key("REMOTE_VARIABLES_SERVER") and self.variables["REMOTE_VARIABLES_SERVER"][u"value"]!="" and self.variables["REMOTE_VARIABLES_SERVER"][u"value"]!=None:
623                                try:
624                                        server_ip=socket.gethostbyname(self.variables["REMOTE_VARIABLES_SERVER"][u"value"])
625                                except:
626                                        return None
627                                if self.get_ip()!=server_ip:
628                                        for count in range(0,3):
629                                                try:
630
631                                                        server=xmlrpclib.ServerProxy("https://"+server_ip+":9779",allow_none=True)
632                                                        var=server.get_variable("","VariablesManager",name,True,True)
633                                                       
634                                                        if var==None:
635                                                                return None
636                                                        if (var!=""  or type(var)!=type("")) and store:
637
638                                                                self.add_variable(name,var[u"value"],var[u"function"],var[u"description"],var[u"packages"],False)
639                                                                return self.get_variable(name,store,full_info)
640                                                        else:
641                                                                if full_info:
642                                                                        var["remote"]=True
643                                                                        return var
644                                                                else:
645                                                                        return var["value"]
646                                                               
647                                                except Exception as e:
648                                                        time.sleep(1)
649                                       
650                                        return None
651                                else:
652                                        return None
653                        else:
654                               
655                                return None
656                       
657        #def get_variable
658
659       
660        def set_variable(self,name,value,depends=[],force_volatile_flag=False):
661
662                if name in self.variables:
663                       
664                        if value == self.variables[name][u"value"]:
665                                return [True,"Variable already contained that value"]
666                       
667                        if type(value)==type(""):
668                                self.variables[name][u"value"]=unicode(value).encode("utf-8")
669                        else:
670                                self.variables[name][u"value"]=value
671
672                        if len(depends)>0:
673                                for depend in depends:
674                                        self.variables[unicode(name).encode("utf-8")][u"packages"].append(depend)
675                       
676                        if not force_volatile_flag:
677                                self.write_file()
678                        else:
679                               
680                                self.variables[name]["volatile"]=True
681                                if "function" not in self.variables["name"]:
682                                        self.variables[name]["function"]=""
683                                if "description" not in self.variables["name"]:
684                                        self.variables[name]["description"]=""
685                       
686                        t=threading.Thread(target=self.notify_changes,args=(name,))
687                        t.daemon=True
688                        t.start()
689                       
690                        return [True,""]
691                else:
692                        return [False,"Variable not found. Use add_variable"]
693               
694               
695        #def set_variable
696
697       
698        def add_variable(self,name,value,function,description,depends,volatile=False,root_protected=False):
699
700                if name not in self.variables:
701                        dic={}
702                        if type(value)==type(""):
703                                dic[u"value"]=unicode(value).encode("utf-8")
704                        else:
705                                dic[u"value"]=value
706                        dic[u"function"]=function
707                        dic[u"description"]=unicode(description).encode("utf-8")
708                        if type(depends)==type(""):
709                                dic[u"packages"]=[unicode(depends).encode("utf-8")]
710                        elif type(depends)==type([]):
711                                dic[u"packages"]=depends
712                        else:
713                                dic[u"packages"]=[]
714                        dic["volatile"]=volatile
715                        dic["root_protected"]=root_protected
716                        self.variables[unicode(name)]=dic
717                        if not volatile:
718                                self.write_file()
719                        return [True,""]
720                else:
721                        return [False,"Variable already exists. Use set_variable"]
722                       
723        #def add_variable
724
725
726        def write_file(self,fname=None):
727               
728                try:
729                        while os.path.exists(VariablesManager.LOCK_FILE):
730                                time.sleep(2)
731                               
732                        f=open(VariablesManager.LOCK_FILE,"w")
733                        f.close()
734                        tmp_vars={}
735                        for item in self.variables:
736                                if self.variables[item].has_key("volatile") and self.variables[item]["volatile"]==False:
737                                        tmp_vars[item]=self.variables[item]
738                                       
739                        for item in tmp_vars:
740                               
741                                tmp={}
742                                tmp[item]=tmp_vars[item]
743                                f=open(VariablesManager.VARIABLES_DIR+item,"w")
744                                data=unicode(json.dumps(tmp,indent=4,encoding="utf-8",ensure_ascii=False)).encode("utf-8")
745                                f.write(data)
746                                f.close()
747                               
748                                if "root_protected" in tmp_vars[item]:
749                                        if tmp_vars[item]["root_protected"]:
750                                                self.chmod(VariablesManager.VARIABLES_DIR+item,0600)
751                                               
752                                               
753                        os.remove(VariablesManager.LOCK_FILE)
754                        return True
755                               
756                       
757                except Exception as e:
758                        os.remove(VariablesManager.LOCK_FILE)
759                        print (e)
760                        return False
761               
762        #def write_file
763
764
765        def chmod(self,file,mode):
766                prevmask = os.umask(0)
767                try:
768                        os.chmod(file,mode)
769                        os.umask(prevmask)
770                        return True
771                except Exception as e:
772                        print e
773                        os.umask(prevmask)
774                        return False
775                       
776        #def chmod
777       
778       
779        def init_variable(self,variable,args={},force=False,full_info=False):
780
781                try:
782                        funct=self.variables[variable]["function"]
783                        mod_name=funct[:funct.rfind(".")]
784                        funct_name=funct[funct.rfind(".")+1:]
785                        funct_name=funct_name.replace("(","")
786                        funct_name=funct_name.replace(")","")
787                        mod=importlib.import_module(mod_name)
788                        ret=getattr(mod,funct_name)(args)
789                        ok,exc=self.set_variable(variable,ret)
790                        if ok:
791                                return (True,ret)
792                        else:
793                                return (False,ret)
794                except Exception as e:
795                        return (False,e)
796               
797        #def init_variable
798       
799       
800#class VariablesManager
801
802
803if __name__=="__main__":
804       
805        vm=VariablesManager()
806       
807        print vm.listvars()
808        print vm.init_variable("name_center")
809        args={}
810        args["iface"]="eth0"
811        print vm.init_variable("SERVER_IP",args)
812        print vm.write_file()
813        #print vm.get_variable("VARIABLE2",full_info=True)
814        #print vm.get_variable("VARIABLE3",full_info=True)
815        #print vm.showvars(var_list)
816       
817       
818               
819               
820               
821       
822       
823       
Note: See TracBrowser for help on using the repository browser.