Changeset 7252 for bell-scheduler


Ignore:
Timestamp:
May 4, 2018, 1:13:38 PM (18 months ago)
Author:
jrpelegrina
Message:

WIP in integration of holiday-manager module

Location:
bell-scheduler/trunk/fuentes
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • bell-scheduler/trunk/fuentes/bell-scheduler/python3-bellscheduler/Core.py

    r6858 r7252  
    99from . import EditBox
    1010from . import settings
     11
     12import holidaymanager.HolidayBox as HolidayBox
    1113
    1214
     
    4042                self.sounds_path="/usr/local/share/bellScheduler/sounds"
    4143
    42 
     44               
    4345                self.bellmanager=bellmanager.BellManager()
    4446                self.bellBox=BellBox.BellBox()
    4547                self.editBox=EditBox.EditBox()
     48                self.holidayBox=HolidayBox.HolidayBox("BELL SCHEDULER")
    4649                self.mainWindow=MainWindow.MainWindow()
    4750                               
  • bell-scheduler/trunk/fuentes/bell-scheduler/python3-bellscheduler/MainWindow.py

    r7071 r7252  
    2525from . import settings
    2626import gettext
    27 gettext.textdomain(settings.TEXT_DOMAIN)
     27#gettext.textdomain(settings.TEXT_DOMAIN)
    2828_ = gettext.gettext
    2929
     
    3333
    3434                self.core=Core.Core.get_core()
     35                self.config_dir=os.path.expanduser("/etc/bellScheduler/")
     36                self.holiday_token=self.config_dir+"enabled_holiday_token"
     37
    3538
    3639        #def init
     
    3942        def load_gui(self):
    4043               
     44                gettext.textdomain(settings.TEXT_DOMAIN)
    4145                builder=Gtk.Builder()
    4246                builder.set_translation_domain(settings.TEXT_DOMAIN)
     
    6872                self.export_button=builder.get_object("export_button")
    6973                self.import_button=builder.get_object("import_button")
     74                self.manage_holiday_button=builder.get_object("manage_holiday_button")
     75                self.enable_holiday_label=builder.get_object("enable_holiday_label")
     76                self.enable_holiday_switch=builder.get_object("enable_holiday_switch")
    7077                self.search_entry=builder.get_object("search_entry")
    7178                self.msg_label=builder.get_object("msg_label")
    7279                self.save_button=builder.get_object("save_button")
    7380                self.cancel_button=builder.get_object("cancel_button")
    74                 #self.banner_box=builder.get_object("banner_box")
     81                self.return_button=builder.get_object("return_button")
    7582
    7683                self.waiting_window=builder.get_object("waiting_window")
     
    8188                self.bellBox=self.core.bellBox
    8289                self.editBox=self.core.editBox
     90                self.holidayBox=self.core.holidayBox
    8391
    8492                self.stack_window.add_titled(self.login, "login", "Login")
     
    9199                self.stack_opt.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT)
    92100
     101               
    93102                self.stack_opt.add_titled(self.bellBox,"bellBox", "Bell Box")
    94103                self.stack_opt.add_titled(self.editBox,"editBox", "Edit Box")
     104                self.stack_opt.add_titled(self.holidayBox,"holidayBox", "Holiday Box")
     105
    95106               
    96107                self.stack_opt.show_all()
     
    107118                self.stack_window.set_transition_type(Gtk.StackTransitionType.NONE)
    108119                self.stack_window.set_visible_child_name("login")
    109                
    110                 '''
    111                 self.manage_down_buttons(False)
    112                 self.load_info()
    113                 self.core.bellBox.draw_bell(False)
    114                 '''
     120                self.return_button.hide()
     121                #self.holiday_control=False
     122
    115123               
    116124        #def load_gui
     
    122130                self.import_bells_t=threading.Thread(target=self.import_bells)
    123131                self.recovery_bells_t=threading.Thread(target=self.recovery_bells)
     132                self.enable_holiday_control_t=threading.Thread(target=self.enable_holiday_control)
    124133
    125134                self.export_bells_t.daemon=True
    126135                self.import_bells_t.daemon=True
    127136                self.recovery_bells_t.daemon=True
     137                self.enable_holiday_control_t.daemon=True
    128138
    129139                self.export_bells_t.launched=False
    130140                self.import_bells_t.launched=False
    131141                self.recovery_bells_t.launched=False
     142                self.enable_holiday_control_t.launched=False
    132143
    133144                GObject.threads_init()
     
    144155                self.main_window.set_name("WINDOW")
    145156                self.waiting_label.set_name("WAITING_LABEL")
     157
    146158                #self.banner_box.set_name("BANNER_BOX")
    147159
     
    158170                self.import_button.connect("clicked",self.import_clicked)
    159171                self.search_entry.connect("changed",self.search_entry_changed)
     172                self.manage_holiday_button.connect("clicked",self.manage_holiday_button_clicked)
     173                self.return_button.connect("clicked",self.return_button_clicked)
     174                self.enable_holiday_switch.connect("notify::active",self.enable_holiday_switch_clicked)
    160175
    161176        #def connect_signals   
     
    165180
    166181                self.core.bellmanager.credentials=[user,pwd]
     182                self.core.holidayBox.credentials=[user,pwd]
     183                self._init_holiday_switch()
    167184                self.manage_down_buttons(False)
    168                 self.manage_menubar(True)
    169                 self.core.bellmanager.sync_with_cron()
    170                 self.load_info()
    171                 self.core.bellBox.draw_bell(False)
     185                result_sync=self.core.bellmanager.sync_with_cron()
     186                if result_sync["status"]:
     187                        self.load_info()
     188                        self.core.bellBox.draw_bell(False)
     189                else:
     190                        self.manage_menubar(False)
     191                        self.manage_message(True,result_sync["code"])
     192
    172193                self.stack_window.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT)
    173194                self.stack_window.set_visible_child_name("optionBox")
    174 
    175         #def _signin   
     195                #self.stack_opt.set_visible_child_name("bellBox")
     196
     197
     198        #def _signin
     199
     200        def _init_holiday_switch(self):
     201
     202                self.holiday_control=False
     203
     204                if os.path.exists(self.holiday_token):
     205                        self.enable_holiday_switch.set_active(True)
     206                else:
     207                        self.enable_holiday_switch.set_active(False)
     208
     209        #def _init_holiday_switch
     210       
    176211
    177212        def load_info(self):
     
    183218                        if self.cont==0:
    184219                                self.manage_message(True,self.read_conf['code'])
     220                                self.manage_menubar(False,False)       
     221                else:
     222                        self.manage_menubar(True)                       
    185223       
    186224        #def load_info 
     
    295333
    296334                else:
     335                        self._init_holiday_switch()
    297336                        self.waiting_window.hide()
    298337                        if self.import_result['status']:
     
    430469                        self.export_button.set_sensitive(True)
    431470                        self.search_entry.set_sensitive(True)
     471                        self.manage_holiday_button.set_sensitive(True)
     472                        self.enable_holiday_switch.set_sensitive(True)
    432473                else:
    433474                        self.add_button.set_sensitive(False)
     
    435476                        self.export_button.set_sensitive(False)
    436477                        self.search_entry.set_sensitive(False)
     478                        self.manage_holiday_button.set_sensitive(False)
     479                        self.enable_holiday_switch.set_sensitive(False)
    437480
    438481        #def manage_menubar             
     
    445488                        self.save_button.show()
    446489                        self.msg_label.hide()
     490                        self.enable_holiday_switch.hide()
     491                        self.enable_holiday_label.hide()
    447492                else:
    448493                        self.cancel_button.hide()
    449494                        self.save_button.hide()
    450495                        self.msg_label.hide()
     496                        self.enable_holiday_switch.show()
     497                        self.enable_holiday_label.show()
     498
    451499
    452500        #def manage_down_buttons                                       
     
    531579                        msg_text=_("Validating the data entered...")           
    532580                elif code==31:
    533                         msg_text=_("Detected alarms with errors")               
     581                        msg_text=_("Detected alarms with errors")
     582                elif code==32:
     583                        msg_text=_("Activating holiday control.Wait a moment...")
     584                elif code==33:
     585                        msg_text=_("Deactivating holiday control.Wait a moment...")
     586                elif code==34:
     587                        msg_text=_("Holiday control deactivated successfully")
     588                elif code==35:
     589                        msg_text=_("Holiday control activated successfully")
     590                elif code==36:
     591                        msg_text=_("Unabled to apply changes due to problems with cron sync")
     592                elif code==37:
     593                        msg_text=_("Unabled to load bell list due to problems with cron sync") 
     594
    534595                       
    535596                return msg_text
     
    537598        #def get_msg   
    538599                       
     600        def manage_holiday_button_clicked(self,widget):
     601
     602                self.core.holidayBox.start_api_connect()
     603                self.msg_label.hide()
     604                self.manage_menubar(False)
     605                self.enable_holiday_switch.hide()
     606                self.enable_holiday_label.hide()
     607                self.stack_opt.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT)
     608                self.stack_opt.set_visible_child_name("holidayBox")
     609                self.return_button.show()
     610
     611        #def manage_holiday_button_clicked     
     612
     613        def return_button_clicked(self,widget):
     614
     615                gettext.textdomain(settings.TEXT_DOMAIN)
     616                self.manage_menubar(True)
     617                self.stack_opt.set_transition_type(Gtk.StackTransitionType.SLIDE_RIGHT)
     618                self.stack_opt.set_visible_child_name("bellBox")
     619                self.return_button.hide()
     620                self.enable_holiday_switch.show()
     621                self.enable_holiday_label.show()
     622
     623
     624
     625        #def return_button_clicked
     626
     627        def enable_holiday_switch_clicked(self,switch,gparam):
     628
     629                if switch.get_active():
     630                        if not os.path.exists(self.holiday_token):
     631                                self.holiday_control=True
     632                                self.holiday_action="enable"
     633
     634                else:
     635                        if os.path.exists(self.holiday_token):
     636                                self.holiday_control=True
     637                                self.holiday_action="disable"
     638
     639                if self.holiday_control:
     640                        self.waiting_label.set_text(self.get_msg(32))                   
     641                        self.waiting_window.show_all()
     642                        self.init_threads()
     643                        self.enable_holiday_control_t.start()
     644                        GLib.timeout_add(100,self.pulsate_enable_holiday_control)
     645
     646
     647        #def enable_holiday_switch_clicked
     648
     649        def pulsate_enable_holiday_control(self):
     650
     651
     652                if self.enable_holiday_control_t.is_alive():
     653                        self.waiting_pbar.pulse()
     654                        return True
     655
     656                else:
     657                        self.waiting_window.hide()
     658                        if self.enable_holiday_result['status']:
     659                                self.manage_message(False,self.enable_holiday_result['code'])
     660                        else:
     661                                self.manage_message(True,self.enable_holiday_result['code'])
     662
     663                return False
     664
     665        #def pulsate_enable_holiday_control     
     666
     667        def enable_holiday_control(self):
     668
     669                self.enable_holiday_result=self.core.bellmanager.enable_holiday_control(self.holiday_action)           
     670       
     671        #def enable_holiday_control
     672
    539673
    540674        def quit(self,widget):
  • bell-scheduler/trunk/fuentes/bell-scheduler/python3-bellscheduler/bellmanager.py

    r6952 r7252  
    4040                result=self.n4d.sync_with_cron(self.credentials,'BellSchedulerManager')
    4141                self._debug("Sync_with_cron: ",result)
     42                return result
     43
    4244               
    4345        #def sync_with_cron
     
    136138                params=' -show_entries stream=codec_type,duration -of compact=p=0:nk=1'
    137139               
    138                 print(file)
    139140                if type=="file":
    140141                        cmd='ffprobe -i "'+file +'"'+ params
     
    237238        #def recovery_bells_conf       
    238239
     240        def enable_holiday_control(self,action):
     241
     242                result=self.n4d.enable_holiday_control(self.credentials,'BellSchedulerManager',action)
     243                self._debug("Enable holiday control: ",result) 
     244                return result
     245
     246        #def enable_holiday     
    239247
    240248#class BellManager             
  • bell-scheduler/trunk/fuentes/bell-scheduler/python3-bellscheduler/rsrc/bell-scheduler.ui

    r6952 r7252  
    595595  <object class="GtkWindow" id="main_window">
    596596    <property name="width_request">925</property>
    597     <property name="height_request">665</property>
     597    <property name="height_request">685</property>
    598598    <property name="can_focus">False</property>
    599599    <property name="window_position">center</property>
     
    657657                <property name="can_focus">False</property>
    658658                <property name="tooltip_text" translatable="yes">Load file with bells</property>
     659                <property name="margin_right">10</property>
    659660                <property name="use_underline">True</property>
    660661                <property name="icon_name">document-revert</property>
     662              </object>
     663              <packing>
     664                <property name="expand">False</property>
     665                <property name="homogeneous">True</property>
     666              </packing>
     667            </child>
     668            <child>
     669              <object class="GtkToolButton" id="manage_holiday_button">
     670                <property name="visible">True</property>
     671                <property name="can_focus">False</property>
     672                <property name="tooltip_text" translatable="yes">Manage holiday</property>
     673                <property name="use_underline">True</property>
     674                <property name="stock_id">gtk-index</property>
    661675              </object>
    662676              <packing>
     
    671685            <property name="position">0</property>
    672686          </packing>
     687        </child>
     688        <child>
     689          <placeholder/>
    673690        </child>
    674691        <child>
     
    684701            <property name="fill">True</property>
    685702            <property name="pack_type">end</property>
    686             <property name="position">1</property>
     703            <property name="position">2</property>
    687704          </packing>
    688705        </child>
     
    710727            <property name="visible">True</property>
    711728            <property name="can_focus">False</property>
    712             <property name="margin_bottom">5</property>
     729            <property name="margin_top">2</property>
     730            <property name="margin_bottom">10</property>
    713731          </object>
    714732          <packing>
     
    723741            <property name="can_focus">False</property>
    724742            <child>
     743              <object class="GtkLabel" id="enable_holiday_label">
     744                <property name="visible">True</property>
     745                <property name="can_focus">False</property>
     746                <property name="label" translatable="yes">Do not run the alarms on holidays set</property>
     747              </object>
     748              <packing>
     749                <property name="expand">False</property>
     750                <property name="fill">True</property>
     751                <property name="position">0</property>
     752              </packing>
     753            </child>
     754            <child>
     755              <object class="GtkSwitch" id="enable_holiday_switch">
     756                <property name="visible">True</property>
     757                <property name="can_focus">True</property>
     758              </object>
     759              <packing>
     760                <property name="expand">False</property>
     761                <property name="fill">True</property>
     762                <property name="position">1</property>
     763              </packing>
     764            </child>
     765            <child>
     766              <object class="GtkButton" id="return_button">
     767                <property name="label">gtk-go-back</property>
     768                <property name="visible">True</property>
     769                <property name="can_focus">True</property>
     770                <property name="receives_default">True</property>
     771                <property name="use_stock">True</property>
     772              </object>
     773              <packing>
     774                <property name="expand">False</property>
     775                <property name="fill">True</property>
     776                <property name="position">2</property>
     777              </packing>
     778            </child>
     779            <child>
    725780              <object class="GtkButton" id="save_button">
    726781                <property name="label">gtk-save</property>
     
    734789                <property name="fill">True</property>
    735790                <property name="pack_type">end</property>
    736                 <property name="position">0</property>
     791                <property name="position">3</property>
    737792              </packing>
    738793            </child>
     
    750805                <property name="fill">True</property>
    751806                <property name="pack_type">end</property>
    752                 <property name="position">1</property>
     807                <property name="position">4</property>
    753808              </packing>
    754809            </child>
  • bell-scheduler/trunk/fuentes/n4d-bellscheduler.install/etc/n4d/conf.d/BellSchedulerManager

    r6852 r7252  
    1010export_bells_conf=adm,admins,teachers
    1111import_bells_conf=adm,admins,teachers
     12enable_holiday_control=adm,admins,teachers
  • bell-scheduler/trunk/fuentes/n4d-bellscheduler.install/usr/share/n4d/python-plugins/BellSchedulerManager.py

    r6957 r7252  
    1515                self.config_dir=os.path.expanduser("/etc/bellScheduler/")
    1616                self.config_file=self.config_dir+"bell_list"
     17                self.holiday_token=self.config_dir+"enabled_holiday_token"
    1718                self.cron_dir="/etc/scheduler/tasks.d/"
    1819                self.cron_file=os.path.join(self.cron_dir,"BellScheduler")
     
    9394                        for item in tasks["data"]:
    9495                                if item=="BellScheduler":
    95                                                 tmp_tasks=tasks["data"][item]
     96                                        tmp_tasks=tasks["data"][item]
    9697
    9798                        if len(tmp_tasks)>0:
     
    109110       
    110111                bell_tasks=self.read_conf()["data"]
    111                 keys_cron=self._get_tasks_from_cron().keys()
     112                keys_bells=bell_tasks.keys()
     113
     114                bells_incron=self._get_tasks_from_cron()
     115                keys_cron=bells_incron.keys()
    112116                changes=0
    113117
     
    126130                                        else:
    127131                                                pass
     132
     133                        for item in keys_cron:
     134                                if item not in keys_bells:
     135                                        result=self._delete_from_cron(item)
     136                                        if not result["status"]:
     137                                                return {"status":False,"msg":"Unable to clear alarm from cron file","code":37}
    128138                else:
    129139                        for item in bell_tasks:
     
    132142                                        bell_tasks[item]["active"]=False
    133143                                       
     144
    134145                if changes>0:
    135                         self._write_conf(bell_tasks)
    136 
    137                 return {"status":True,"msg":"Sync with cron sucessfully","data":bell_tasks}                             
     146                        self._write_conf(bell_tasks,"BellList")
     147
     148                return {"status":True,"msg":"Sync with cron sucessfully","data":bell_tasks}     
     149                                       
    138150
    139151        #def sync_with_cron     
    140152
    141         def _write_conf(self,info):
    142                
    143                 self.bells_config=info
    144                
    145                 with codecs.open(self.config_file,'w',encoding="utf-8") as f:
     153        def _write_conf(self,info,type_list):
     154               
     155                if type_list=="BellList":
     156                        self.bells_config=info
     157                        file_to_write=self.config_file
     158                        msg="Bell list saved successfuly"
     159                else:
     160                        file_to_write=self.cron_file
     161                        msg="Cron list saved successfuly"
     162
     163               
     164                with codecs.open(file_to_write,'w',encoding="utf-8") as f:
    146165                        json.dump(info,f,ensure_ascii=False)
    147166                        f.close()       
    148167
    149                 return {"status":True,"msg":"Bell list saved successfuly"}     
     168                return {"status":True,"msg":msg}       
    150169
    151170
     
    168187
    169188                if result['status']:   
    170                         return self._write_conf(info)
     189                        return self._write_conf(info,"BellList")
    171190                else:
    172191                        if action=="edit":
     
    261280                if duration>0:
    262281                        fade_out=int(duration)-2
    263                         fade_effects='-af aformat=channel_layouts=mono -af afade=in:st=1:d=3,afade=out:st='+str(fade_out)+":d=2"
     282                        fade_effects='-af aformat=channel_layouts=mono -af afade=in:st=0:d=6,afade=out:st='+str(fade_out)+":d=2"
    264283                        cmd="ffplay -nodisp -autoexit -t "+str(duration)
    265284                else:
     
    269288                if sound_option !="url":
    270289                        if sound_option =="file":
    271                                 cmd=cmd+" '"+sound_path+"' "+fade_effects
     290                                cmd=cmd+' "'+ sound_path +'" '+fade_effects
    272291                        else:
    273                                 random_file="$(find"+ " '"+sound_path+"' -type f -print0 | xargs -0 file -i | awk -F ':' '{ if ($2 ~ /audio/ || $2 ~ /video/ ) print $1 }'| shuf -n 1)"
     292                                #random_file="$(find"+ " '"+sound_path+"' -type f -print0 | xargs -0 file -i | awk -F ':' '{ if ($2 ~ /audio/ || $2 ~ /video/ ) print $1 }'| shuf -n 1)"
     293                                random_file="$(randomaudiofile" + " '"+sound_path+"')"
    274294                                cmd=cmd+' "'+ random_file + '" '+fade_effects
    275295                                #cmd=cmd+" $(find"+ " '"+sound_path+"' -type f | shuf -n 1) "+fade_effects             
     
    278298                       
    279299                info_to_cron["BellScheduler"][key]["cmd"]=cmd
     300
     301                if os.path.exists(self.holiday_token):
     302                        info_to_cron["BellScheduler"][key]["holidays"]=True
     303                else:
     304                        info_to_cron["BellScheduler"][key]["holidays"]=False
     305       
    280306                       
    281307                return info_to_cron
     
    319345                        if os.path.exists(self.cron_file):
    320346                                shutil.copy2(self.cron_file,os.path.join(tmp_export,os.path.basename(self.cron_file)))
     347                        if os.path.exists(self.holiday_token):
     348                                shutil.copy2(self.holiday_token,os.path.join(tmp_export,os.path.basename(self.holiday_token)))
     349
    321350                        if os.path.exists(self.media_files_folder):
    322351                                shutil.copytree(self.media_files_folder,os.path.join(tmp_export,"media"))
     
    342371                unzip_tmp=tempfile.mkdtemp("_import_bells")
    343372                result={"status":True}
     373                action="disable"
    344374
    345375                if backup:
     
    371401                                                shutil.copy2(cron_file,self.cron_dir)
    372402                                                f.close()
     403                                                #self.n4d.process_tasks(self.n4dkey,'SchedulerClient')
    373404                                        except Exception as e:
    374405                                                result={"status":False,"msg":str(e),"code":9,"data":backup_file[1]}     
    375406                                                return result   
    376407
     408                                holiday_token=os.path.join(unzip_tmp,os.path.basename(self.holiday_token))     
     409                                if os.path.exists(holiday_token):
     410                                        action="enable"
     411                                        shutil.copyfile(holiday_token,self.holiday_token)       
     412                                else:
     413                                        if os.path.exists(self.holiday_token):
     414                                                os.remove(self.holiday_token)
     415
    377416                                if os.path.exists(self.images_folder):
    378417                                        shutil.rmtree(self.images_folder)
     
    383422                                        shutil.copytree(os.path.join(unzip_tmp,"media/sounds"),self.sounds_folder)
    384423               
    385 
    386                                 result={"status":True,"msg":"Bells imported successfullly","code":10,"data":backup_file[1]}
    387                                                                
     424                                update_holiday=self.enable_holiday_control(action)     
     425                               
     426                                if update_holiday["status"]:   
     427                                        result={"status":True,"msg":"Bells imported successfullly","code":10,"data":backup_file[1]}
     428                                else:
     429                                        result={"status":False,"msg":update_holiday["msg"],"code":9}                           
    388430                except Exception as e:
    389431                        result={"status":False,"msg":str(e),"code":9,"data":backup_file[1]}     
     
    393435
    394436        #def import_bells_conf
     437
     438        def enable_holiday_control(self,action):
     439
     440                result=self._update_holiday_control(action)
     441                if result['status']:
     442                        if action=="disable":
     443                                if os.path.exists(self.holiday_token):
     444                                        os.remove(self.holiday_token)   
     445                                        result={"status":True,"msg":"Holiday token removed","code":34}
     446                        else:
     447                                if not os.path.exists(self.holiday_token):
     448                                        if not os.path.exists(self.config_dir):
     449                                                os.makedirs(self.config_dir)
     450                                        f=open(self.holiday_token,'w')
     451                                        f.close()
     452                                        result={"status":True,"msg":"Holiday token created","code":35}         
     453               
     454                return result           
     455
     456        #def enable_holiday
     457
     458        def _update_holiday_control(self,action):
     459
     460               
     461                if os.path.exists(self.cron_file):
     462                        f=open(self.cron_file)
     463                        try:
     464                                tasks_cron=json.load(f)
     465                        except Exception as e:
     466                                result={"status":False,"msg":str(e),"code":36}
     467                                return result
     468
     469                       
     470                        for item in     tasks_cron["BellScheduler"]:
     471                                if action=="enable":
     472                                        tasks_cron["BellScheduler"][item]["holidays"]=True
     473                               
     474                                else:
     475                                        tasks_cron["BellScheduler"][item]["holidays"]=False
     476
     477                       
     478                        self._write_conf(tasks_cron,"CronList")
     479                        self.n4d.process_tasks(self.n4dkey,'SchedulerClient')
     480                        result={"status":True,"msg":"Cron file updated","code":37}
     481                else:
     482                        result={"status":True,"msg":"Cron file dosn't exists","code":37}                       
     483
     484                return result
     485
     486        #def _update_holiday_control           
     487               
     488                       
     489
Note: See TracChangeset for help on using the changeset viewer.