From 020a4a5ea2be725b155cae3a2cadc9aba3911b9b Mon Sep 17 00:00:00 2001 From: Andrew DeFaria Date: Mon, 15 Jul 2013 11:30:43 -0700 Subject: [PATCH] Initial commit --- CCDB/ccdb.pl | 217 + CCDB/ccdbservice.pl | 170 + CCDB/etc/ccdb.conf | 17 + CCDB/etc/ccdb.my.cnf | 6 + CCDB/etc/ccdb.sql | 235 + CCDB/etc/ccdbservice | 160 + CCDB/etc/ccdbservice.conf | 18 + CCDB/lib/CCDB.pm | 1293 +++ CCDB/lib/CCDBService.pm | 680 ++ CCDB/mspb.pl | 852 ++ CCDB/triggers/Activity.pl | 221 + CCDB/triggers/Baseline.pl | 249 + CCDB/triggers/Element.pl | 372 + CCDB/triggers/Stream.pl | 206 + CCDB/triggers/TriggerUtils.pm | 198 + CCDB/update.pl | 884 ++ Makefile | 78 + bin/.cvsignore | 2 + bin/.perldb.hist | 100 + bin/backup | 61 + bin/bice.pl | 398 + bin/bigfiles.pl | 100 + bin/checkdns | 205 + bin/diskspace | 86 + bin/httpdwho | 131 + bin/mkplaylist | 214 + bin/nag.pl | 278 + bin/raid | 1361 +++ bin/rexec | 326 + bin/root | 42 + bin/setbg | 130 + bin/setup_cron | 29 + bin/setup_ssmtp | 80 + cc/DiffBLUI.pm | 1038 +++ cc/bin_merge | 112 + cc/bin_rebase | 108 + cc/diffbl.gif | Bin 0 -> 1240 bytes cc/diffbl.pl | 331 + cc/dominos | 205 + cc/etf.pl | 471 ++ cc/findview | 90 + cc/findvob | 88 + cc/lockvobs | 283 + cc/log_activity | 168 + cc/lscset | 118 + cc/lsnusers | 111 + cc/mknusers | 129 + cc/mktriggers.pl | 472 ++ cc/msl/delete.gif | Bin 0 -> 331 bytes cc/msl/index.php | 67 + cc/msl/lsnusers.php | 75 + cc/msl/private/addnuser.php | 66 + cc/msl/private/mknusers.php | 98 + cc/msl/private/rmnusers.php | 80 + cc/msl/streams.php | 199 + cc/perf/pulse | 265 + cc/rmnusers | 148 + cc/stats | 142 + cc/testcc.conf | 21 + cc/testcc.pl | 562 ++ cc/triggers/AddExecute.pl | 24 + cc/triggers/CheckComment.pl | 29 + cc/triggers/EvilTwin.pl | 151 + cc/triggers/Notify.pl | 155 + cc/triggers/NotifyCheckin.msg | 14 + cc/triggers/NotifyTrigger.pl | 159 + cc/triggers/Protect.pl | 71 + cc/triggers/RemoveEmptyBranch.pl | 116 + clearadm/.-_hist | 0 clearadm/.clearexec_hist | 7 + clearadm/.cvsignore | 4 + clearadm/.perldb.hist | 100 + clearadm/.project | 17 + clearadm/README | 108 + clearadm/add.png | Bin 0 -> 566 bytes clearadm/alert.png | Bin 0 -> 756 bytes clearadm/alertlog.cgi | 154 + clearadm/alerts.cgi | 146 + clearadm/banner.jpg | Bin 0 -> 34080 bytes clearadm/clearadm.css | 303 + clearadm/clearadm.js | 231 + clearadm/clearadmscrub.pl | 181 + clearadm/clearagent.pl | 157 + clearadm/clearexec.pl | 190 + clearadm/clearmenu.css | 305 + clearadm/cleartasks.pl | 596 ++ clearadm/delete.png | Bin 0 -> 311 bytes clearadm/deletealertlog.cgi | 159 + clearadm/discovery.pl | 199 + clearadm/down.png | Bin 0 -> 823 bytes clearadm/edit.png | Bin 0 -> 451 bytes clearadm/etc/clearadm.conf | 23 + clearadm/etc/clearexec.conf | 18 + clearadm/etc/clearuser.conf | 18 + clearadm/etc/conf.d/clearadm | 24 + clearadm/etc/init.d/clearagent | 156 + clearadm/etc/init.d/cleartasks | 156 + clearadm/filesystems.cgi | 154 + clearadm/getFilesystems.cgi | 120 + clearadm/getTimestamp.cgi | 168 + clearadm/index.cgi | 206 + clearadm/left.png | Bin 0 -> 925 bytes clearadm/lib/Clearadm.pm | 2100 +++++ clearadm/lib/ClearadmWeb.pm | 2758 ++++++ clearadm/lib/Clearexec.pm | 425 + clearadm/lib/User.pm | 253 + clearadm/lib/clearadm.sql | 320 + clearadm/lib/load.sql | 190 + clearadm/lib/users.sql | 27 + clearadm/lib/views.sql | 64 + clearadm/load.vbs | 52 + clearadm/log/clearagent.pl.log | 91 + clearadm/log/cleartasks.pl.log | 62 + clearadm/notes | 27 + clearadm/notifications.cgi | 146 + clearadm/packages.vbs | 16 + clearadm/plot.cgi | 320 + clearadm/plotfs.cgi | 226 + clearadm/plotloadavg.cgi | 215 + clearadm/processalert.cgi | 212 + clearadm/processfilesystem.cgi | 208 + clearadm/processnotification.cgi | 226 + clearadm/processrunning.pl | 189 + clearadm/processschedule.cgi | 225 + clearadm/processsystem.cgi | 202 + clearadm/processtask.cgi | 213 + clearadm/readme.cgi | 126 + clearadm/right.png | Bin 0 -> 918 bytes clearadm/runlog.cgi | 155 + clearadm/schedule.cgi | 142 + clearadm/setup.pl | 329 + clearadm/systemdetails.cgi | 282 + clearadm/systems.cgi | 289 + clearadm/tasks.cgi | 145 + clearadm/test.pl | 240 + clearadm/up.png | Bin 0 -> 846 bytes clearadm/updatefs.pl | 288 + clearadm/updatela.pl | 248 + clearadm/updatesystem.pl | 366 + clearadm/var/run/.cvsignore | 3 + clearadm/var/run/clearagent.pl.pid | 1 + clearadm/var/run/cleartasks.pl.pid | 1 + clearadm/viewager.cgi | 749 ++ clearadm/viewdetails.cgi | 325 + clearadm/viewservers.cgi | 224 + clearadm/vobservers.cgi | 227 + cq/.-_hist | 0 cq/CheckCodePage.pl | 132 + cq/PQA.pm | 1040 +++ cq/check_attachments | 122 + cq/convertList.pl | 213 + cq/cqaction.pl | 230 + cq/cqd.pl | 175 + cq/cqd/CheckinPreop.pl | 293 + cq/cqd/cqc | 147 + cq/cqd/cqc.pm | 169 + cq/cqd/cqc.pm.php | 51 + cq/cqd/cqd | 336 + cq/cqd/releasenotes.cgi | 224 + cq/cqinfo.pl | 202 + cq/cqquery.pl | 438 + cq/enable_ldap | 610 ++ cq/ldap_settings.cfg | 26 + cq/listdynlists | 75 + cq/pqaclean | 118 + cq/pqamerge | 656 ++ cvsbin/cvsims | 244 + ecrc/ecrc | 177 + ecrc/ecrc.php | 182 + ecrc/ecrd | 564 ++ ecrc/ecrdesc | 65 + etc/cq.conf | 30 + etc/doskey.mac | 57 + etc/mail.conf | 15 + etc/triggers.dat | 80 + functions/common | 42 + functions/date64 | 175 + functions/display | 58 + functions/logs | 31 + functions/tmpfiles | 50 + functions/utils | 34 + lib/BinMerge.pm | 895 ++ lib/Clearcase.pm | 1379 +++ lib/Clearcase/Element.pm | 1387 +++ lib/Clearcase/Server.pm | 270 + lib/Clearcase/UCM.pm | 168 + lib/Clearcase/UCM/Activity.pm | 862 ++ lib/Clearcase/UCM/Baseline.pm | 492 ++ lib/Clearcase/UCM/Pvob.pm | 208 + lib/Clearcase/UCM/Stream.pm | 392 + lib/Clearcase/View.pm | 1862 ++++ lib/Clearcase/Views.pm | 373 + lib/Clearcase/Vob.pm | 1360 +++ lib/Clearcase/Vobs.pm | 315 + lib/Clearquest.pm | 2713 ++++++ lib/Clearquest/Admin.pm | 499 ++ lib/Clearquest/Client.pm | 504 ++ lib/Clearquest/DBService.pm | 633 ++ lib/Clearquest/LDAP.pm | 168 + lib/Clearquest/REST.pm | 2172 +++++ lib/Clearquest/Server.pm | 672 ++ lib/CmdLine.pm | 1490 ++++ lib/DateUtils.pm | 1293 +++ lib/Display.pm | 1264 +++ lib/GetConfig.pm | 270 + lib/Logger.pm | 954 +++ lib/Machines.pm | 214 + lib/Mail.pm | 399 + lib/OSDep.pm | 215 + lib/Rexec.pm | 1176 +++ lib/SpreadSheet.pm | 248 + lib/TimeUtils.pm | 365 + lib/TriggerUtils.pm | 86 + lib/Utils.pm | 889 ++ maps/JavaScript/CheckAddress.js | 40 + maps/JavaScript/CheckEditProfile.js | 95 + maps/JavaScript/CheckLogin.js | 39 + maps/JavaScript/CheckRegistration.js | 42 + maps/JavaScript/CheckSignup.js | 97 + maps/JavaScript/ListActions.js | 132 + maps/JavaScript/MAPSUtils.js | 61 + maps/JavaScript/Register.js | 28 + maps/MAPS.png | Bin 0 -> 658 bytes maps/Reports.html | 41 + maps/SignupForm.html | 159 + maps/adm/index.html | 93 + maps/bin/MAPS.pm | 792 ++ maps/bin/MAPSDB.pm | 1504 ++++ maps/bin/MAPSDB.sql | 96 + maps/bin/MAPSDeliver | 93 + maps/bin/MAPSFile.pm | 43 + maps/bin/MAPSLog.pm | 115 + maps/bin/MAPSUtil.pm | 265 + maps/bin/MAPSWeb.pm | 338 + maps/bin/Search.gif | Bin 0 -> 948 bytes maps/bin/add2blacklist.cgi | 108 + maps/bin/add2nulllist.cgi | 110 + maps/bin/add2nulllist.pl | 96 + maps/bin/add2whitelist.cgi | 122 + maps/bin/checkaddress | 67 + maps/bin/checkaddress.cgi | 96 + maps/bin/detail.cgi | 313 + maps/bin/display.cgi | 191 + maps/bin/domains | 72 + maps/bin/editprofile.cgi | 213 + maps/bin/exportlist.cgi | 68 + maps/bin/list.cgi | 186 + maps/bin/main.cgi | 109 + maps/bin/maps | 224 + maps/bin/mapsscrub | 101 + maps/bin/mapsutil | 548 ++ maps/bin/modifyentries.cgi | 72 + maps/bin/nuke | 115 + maps/bin/processaction.cgi | 420 + maps/bin/register.cgi | 94 + maps/bin/registerform.cgi | 127 + maps/bin/search.cgi | 176 + maps/bin/signup.cgi | 108 + maps/bin/stats.cgi | 137 + maps/bin/updateprofile.cgi | 82 + maps/bin/weed | 169 + maps/bin/world.gif | Bin 0 -> 94570 bytes maps/blacklist.html | 31 + maps/css/MAPSPlain.css | 317 + maps/css/MAPSStyle.css | 577 ++ maps/doc/CommonProblems.html | 16 + maps/doc/Costs.html | 16 + maps/doc/Details.html | 16 + maps/doc/Download.html | 16 + maps/doc/FAQ.html | 16 + maps/doc/ForgotPassword.html | 16 + maps/doc/Forwarding.html | 16 + maps/doc/Lists.html | 38 + maps/doc/MAPSLocal.html | 16 + maps/doc/MailLoops.html | 16 + maps/doc/Popsettings.html | 16 + maps/doc/RegExs.html | 16 + maps/doc/Requirements.php | 44 + maps/doc/SPAM.php | 86 + maps/doc/Signup.html | 16 + maps/doc/Using.php | 33 + maps/doc/Whitelist.html | 16 + maps/doc/add2blacklist.html | 99 + maps/doc/add2nulllist.html | 99 + maps/doc/detail.html | 524 ++ maps/doc/index.php | 88 + maps/doc/maps.css | 290 + maps/doc/world.gif | Bin 0 -> 94570 bytes maps/etc/mail.conf | 15 + maps/favicon.ico | Bin 0 -> 658 bytes maps/forward | 1 + maps/images/Pattern1.gif | Bin 0 -> 175 bytes maps/images/next.gif | Bin 0 -> 683 bytes maps/images/previous.gif | Bin 0 -> 685 bytes maps/images/world.gif | Bin 0 -> 89866 bytes maps/images/world.jpg | Bin 0 -> 26424 bytes maps/index.php | 95 + maps/next.gif | Bin 0 -> 683 bytes maps/null.list | 41 + maps/php/ForgotPassword.php | 62 + maps/php/ListDomains.php | 49 + maps/php/MAPS.php | 519 ++ maps/php/Reports.php | 47 + maps/php/Space.php | 55 + maps/php/emailpassword.php | 93 + maps/php/list.php | 127 + maps/php/main.php | 86 + maps/previous.gif | Bin 0 -> 685 bytes maps/register.html | 62 + maps/world.gif | Bin 0 -> 94570 bytes rc/Xdefaults | 68 + rc/bash_login | 245 + rc/clearcase | 4129 +++++++++ rc/clearcase.conf | 21 + rc/clearcase_profile | 17 + rc/client_scripts/Broadcom | 33 + rc/client_scripts/GD | 54 + rc/client_scripts/GE | 15 + rc/dircolors | 107 + rc/functions | 252 + rc/inputrc | 56 + rc/logout | 17 + rc/multisite | 87 + rc/perlcriticrc | 52 + rc/perldb | 2 + rc/perltidyrc | 28 + rc/set_colors | 66 + rc/set_path | 137 + rc/setup_rc | 64 + rc/signatures | 1483 ++++ rc/signatures.clearscm | 91 + rc/sshconfig | 3 + rc/system | 22 + rc/toprc | 14 + rc/vimrc | 8 + rc/vueprofile | 2 + rc/xemacs/clearcase.el | 7970 ++++++++++++++++++ rc/xemacs/custom.el | 9 + rc/xemacs/init.el | 124 + rc/xemacs/mwheel.el | 53 + rc/xemacs/perlcritic.el | 687 ++ rc/xemacs/perltidy.el | 150 + rc/xemacs/visual-basic-mode.el | 933 ++ tcl/Display.tcl | 59 + test/.-_hist | 0 test/.cvsignore | 2 + test/testclearcase.pl | 68 + test/testclearquest.pl | 386 + test/testclearquestServer.pl | 38 + test/testcmdline.pl | 30 + test/testelement.pl | 72 + test/testmail.pl | 88 + test/testrest.pl | 202 + test/testrexec.pl | 42 + test/testspreadsheet.pl | 95 + test/testspreadsheet.xls | Bin 0 -> 12763 bytes test/testview.pl | 82 + test/testviews.pl | 32 + test/testvob.pl | 65 + test/testvobs.pl | 40 + web/.htaccess | 1 + web/Contract Addendum - Mindteck.doc | Bin 0 -> 24064 bytes web/Contract Addendum.doc | Bin 0 -> 24064 bytes web/Icons/Download.jpg | Bin 0 -> 1152 bytes web/Icons/HomeSmall.gif | Bin 0 -> 1004 bytes web/Icons/arrow_down.gif | Bin 0 -> 874 bytes web/Icons/arrow_right.gif | Bin 0 -> 872 bytes web/Icons/orange_arrow_down.gif | Bin 0 -> 842 bytes web/Icons/orange_arrow_right.gif | Bin 0 -> 827 bytes web/Images/AndrewDeFaria.jpg | Bin 0 -> 9532 bytes web/Images/BMLeft.jpg | Bin 0 -> 33173 bytes web/Images/BMRight.jpg | Bin 0 -> 47683 bytes web/Images/Background.jpg | Bin 0 -> 146507 bytes web/Images/Clouds.jpg | Bin 0 -> 6701 bytes web/Images/TopOfTheWorld.jpg | Bin 0 -> 66290 bytes web/Images/orange_gradient.gif | Bin 0 -> 855 bytes web/Images/tbg-bl-mg.jpg | Bin 0 -> 1584 bytes web/Images/tbg-mg-bl.jpg | Bin 0 -> 1898 bytes web/JavaScript/Menus.js | 968 +++ web/JavaScript/common.js | 67 + web/Logos/Ameriquest.gif | Bin 0 -> 3274 bytes web/Logos/Broadcom.gif | Bin 0 -> 4539 bytes web/Logos/Cisco.gif | Bin 0 -> 1968 bytes web/Logos/ClearSCM.jpg | Bin 0 -> 11203 bytes web/Logos/HPLogo.gif | Bin 0 -> 997 bytes web/Logos/LynuxWorks.gif | Bin 0 -> 2421 bytes web/Logos/Salira.gif | Bin 0 -> 2173 bytes web/Logos/Sun.jpg | Bin 0 -> 4021 bytes web/Logos/TexasInstruments.jpg | Bin 0 -> 2718 bytes web/Resumes/Andrew/Ameriquest.gif | Bin 0 -> 2682 bytes web/Resumes/Andrew/Broadcom.gif | Bin 0 -> 5728 bytes web/Resumes/Andrew/Cisco.gif | Bin 0 -> 1231 bytes web/Resumes/Andrew/GEHealthcare.gif | Bin 0 -> 1726 bytes web/Resumes/Andrew/General_Dynamics_logo.jpg | Bin 0 -> 23841 bytes web/Resumes/Andrew/HPLogo.gif | Bin 0 -> 997 bytes web/Resumes/Andrew/LynuxWorks.gif | Bin 0 -> 2387 bytes web/Resumes/Andrew/Resume.doc | Bin 0 -> 141312 bytes web/Resumes/Andrew/Salira.gif | Bin 0 -> 2387 bytes web/Resumes/Andrew/Sun.jpg | Bin 0 -> 4021 bytes web/Resumes/Andrew/Tellabs.gif | Bin 0 -> 1762 bytes web/Resumes/Andrew/TexasInstruments.jpg | Bin 0 -> 2718 bytes web/Resumes/Andrew/index.php | 642 ++ web/Resumes/Don/Aspen.gif | Bin 0 -> 5969 bytes web/Resumes/Don/DonSkanes.doc | Bin 0 -> 52224 bytes web/Resumes/Don/Edentree.jpg | Bin 0 -> 4624 bytes web/Resumes/Don/Nortel.gif | Bin 0 -> 1588 bytes web/Resumes/Don/Vpacket.png | Bin 0 -> 3046 bytes web/Resumes/Don/index.php | 424 + web/Resumes/Kevin/Resume.doc | Bin 0 -> 28160 bytes web/Resumes/Kevin/index.php | 198 + web/Resumes/Mohammed/Resume.doc | Bin 0 -> 103936 bytes web/Resumes/Mohammed/index.php | 711 ++ web/Resumes/Ron/Resume.doc | Bin 0 -> 69120 bytes web/Resumes/Ron/index.php | 464 + web/Resumes/Tom/Resume.doc | Bin 0 -> 35328 bytes web/Resumes/Tom/Tom.png | Bin 0 -> 44434 bytes web/Resumes/Tom/index.php | 606 ++ web/addendum.php | 165 + web/businesscard.html | 96 + web/clearcase/EvilTwin.php | 152 + web/clearcase/OpenSourceBuild.php | 206 + web/clearcase/RemoveEmptyBranch.php | 53 + web/clearcase/index.php | 124 + web/clearcase/triggers.php | 66 + web/clearquest/CheckCodePage.php | 50 + web/clearquest/PQA.pm.php | 49 + web/clearquest/check_attachments.php | 50 + web/clearquest/cqd/BeforeCQD.jpg | Bin 0 -> 233360 bytes web/clearquest/cqd/CQD.jpg | Bin 0 -> 634625 bytes web/clearquest/cqd/CheckinPreop.php | 50 + web/clearquest/cqd/Releasenotes.html | 33 + web/clearquest/cqd/cqc.php | 52 + web/clearquest/cqd/cqc.pm.php | 50 + web/clearquest/cqd/cqd.php | 51 + web/clearquest/cqd/index.php | 334 + web/clearquest/cqd/rn.php | 48 + web/clearquest/db.php | 94 + web/clearquest/enable_ldap.php | 52 + web/clearquest/index.php | 94 + web/clearquest/ldap_settings.cfg | 26 + web/clearquest/listdynlists.php | 49 + web/clearquest/pqaclean.php | 50 + web/clearquest/pqamerge.php | 50 + web/contact.php | 47 + web/css/Article.css | 38 + web/css/ArticleLayout.css | 174 + web/css/Code.css | 34 + web/css/ColoredBoxesRoundedCorners.css | 86 + web/css/FrontPage.css | 34 + web/css/LevelThePlayingField.css | 191 + web/css/Main.css | 413 + web/css/ManPage.css | 34 + web/css/ManPageLayout.css | 174 + web/css/Menus.css | 228 + web/css/Plain.css | 462 + web/css/Print.css | 176 + web/css/TableBorders.css | 253 + web/error404.php | 44 + web/favicon.ico | Bin 0 -> 2551 bytes web/index.php | 128 + web/people.php | 164 + web/php/clearscm.php | 395 + web/php/cvs_man.php | 41 + web/phpinfo.php | 1 + web/scripts/ecrd/ecr23184.html | 70 + web/scripts/ecrd/ecrc.php | 48 + web/scripts/ecrd/ecrc.php.php | 48 + web/scripts/ecrd/ecrd.php | 48 + web/scripts/ecrd/index.php | 134 + web/scripts/index.php | 55 + web/scripts/perl.php | 166 + web/services/consultancy.php | 128 + web/services/custom_software.php | 155 + web/services/customers.php | 206 + web/services/index.php | 125 + web/services/scm.php | 129 + web/services/sysadmin.php | 131 + web/services/web.php | 134 + web/sysadm/env/index.php | 437 + web/sysadm/index.php | 61 + 480 files changed, 107120 insertions(+) create mode 100644 CCDB/ccdb.pl create mode 100644 CCDB/ccdbservice.pl create mode 100644 CCDB/etc/ccdb.conf create mode 100644 CCDB/etc/ccdb.my.cnf create mode 100644 CCDB/etc/ccdb.sql create mode 100644 CCDB/etc/ccdbservice create mode 100644 CCDB/etc/ccdbservice.conf create mode 100644 CCDB/lib/CCDB.pm create mode 100644 CCDB/lib/CCDBService.pm create mode 100644 CCDB/mspb.pl create mode 100644 CCDB/triggers/Activity.pl create mode 100644 CCDB/triggers/Baseline.pl create mode 100644 CCDB/triggers/Element.pl create mode 100644 CCDB/triggers/Stream.pl create mode 100644 CCDB/triggers/TriggerUtils.pm create mode 100644 CCDB/update.pl create mode 100644 Makefile create mode 100644 bin/.cvsignore create mode 100644 bin/.perldb.hist create mode 100755 bin/backup create mode 100755 bin/bice.pl create mode 100755 bin/bigfiles.pl create mode 100755 bin/checkdns create mode 100755 bin/diskspace create mode 100755 bin/httpdwho create mode 100755 bin/mkplaylist create mode 100755 bin/nag.pl create mode 100755 bin/raid create mode 100755 bin/rexec create mode 100755 bin/root create mode 100755 bin/setbg create mode 100755 bin/setup_cron create mode 100755 bin/setup_ssmtp create mode 100644 cc/DiffBLUI.pm create mode 100644 cc/bin_merge create mode 100644 cc/bin_rebase create mode 100644 cc/diffbl.gif create mode 100755 cc/diffbl.pl create mode 100644 cc/dominos create mode 100755 cc/etf.pl create mode 100644 cc/findview create mode 100644 cc/findvob create mode 100644 cc/lockvobs create mode 100644 cc/log_activity create mode 100644 cc/lscset create mode 100644 cc/lsnusers create mode 100644 cc/mknusers create mode 100755 cc/mktriggers.pl create mode 100644 cc/msl/delete.gif create mode 100644 cc/msl/index.php create mode 100644 cc/msl/lsnusers.php create mode 100644 cc/msl/private/addnuser.php create mode 100644 cc/msl/private/mknusers.php create mode 100644 cc/msl/private/rmnusers.php create mode 100644 cc/msl/streams.php create mode 100644 cc/perf/pulse create mode 100644 cc/rmnusers create mode 100644 cc/stats create mode 100644 cc/testcc.conf create mode 100644 cc/testcc.pl create mode 100644 cc/triggers/AddExecute.pl create mode 100644 cc/triggers/CheckComment.pl create mode 100644 cc/triggers/EvilTwin.pl create mode 100644 cc/triggers/Notify.pl create mode 100644 cc/triggers/NotifyCheckin.msg create mode 100644 cc/triggers/NotifyTrigger.pl create mode 100644 cc/triggers/Protect.pl create mode 100644 cc/triggers/RemoveEmptyBranch.pl create mode 100644 clearadm/.-_hist create mode 100644 clearadm/.clearexec_hist create mode 100644 clearadm/.cvsignore create mode 100644 clearadm/.perldb.hist create mode 100644 clearadm/.project create mode 100644 clearadm/README create mode 100644 clearadm/add.png create mode 100644 clearadm/alert.png create mode 100755 clearadm/alertlog.cgi create mode 100755 clearadm/alerts.cgi create mode 100644 clearadm/banner.jpg create mode 100644 clearadm/clearadm.css create mode 100644 clearadm/clearadm.js create mode 100755 clearadm/clearadmscrub.pl create mode 100755 clearadm/clearagent.pl create mode 100755 clearadm/clearexec.pl create mode 100644 clearadm/clearmenu.css create mode 100755 clearadm/cleartasks.pl create mode 100644 clearadm/delete.png create mode 100755 clearadm/deletealertlog.cgi create mode 100755 clearadm/discovery.pl create mode 100644 clearadm/down.png create mode 100644 clearadm/edit.png create mode 100644 clearadm/etc/clearadm.conf create mode 100644 clearadm/etc/clearexec.conf create mode 100644 clearadm/etc/clearuser.conf create mode 100644 clearadm/etc/conf.d/clearadm create mode 100755 clearadm/etc/init.d/clearagent create mode 100755 clearadm/etc/init.d/cleartasks create mode 100755 clearadm/filesystems.cgi create mode 100755 clearadm/getFilesystems.cgi create mode 100755 clearadm/getTimestamp.cgi create mode 100755 clearadm/index.cgi create mode 100644 clearadm/left.png create mode 100644 clearadm/lib/Clearadm.pm create mode 100644 clearadm/lib/ClearadmWeb.pm create mode 100644 clearadm/lib/Clearexec.pm create mode 100644 clearadm/lib/User.pm create mode 100644 clearadm/lib/clearadm.sql create mode 100644 clearadm/lib/load.sql create mode 100644 clearadm/lib/users.sql create mode 100644 clearadm/lib/views.sql create mode 100755 clearadm/load.vbs create mode 100644 clearadm/log/clearagent.pl.log create mode 100644 clearadm/log/cleartasks.pl.log create mode 100644 clearadm/notes create mode 100755 clearadm/notifications.cgi create mode 100755 clearadm/packages.vbs create mode 100755 clearadm/plot.cgi create mode 100755 clearadm/plotfs.cgi create mode 100755 clearadm/plotloadavg.cgi create mode 100755 clearadm/processalert.cgi create mode 100755 clearadm/processfilesystem.cgi create mode 100755 clearadm/processnotification.cgi create mode 100755 clearadm/processrunning.pl create mode 100755 clearadm/processschedule.cgi create mode 100755 clearadm/processsystem.cgi create mode 100755 clearadm/processtask.cgi create mode 100755 clearadm/readme.cgi create mode 100644 clearadm/right.png create mode 100755 clearadm/runlog.cgi create mode 100755 clearadm/schedule.cgi create mode 100755 clearadm/setup.pl create mode 100755 clearadm/systemdetails.cgi create mode 100755 clearadm/systems.cgi create mode 100755 clearadm/tasks.cgi create mode 100755 clearadm/test.pl create mode 100644 clearadm/up.png create mode 100755 clearadm/updatefs.pl create mode 100755 clearadm/updatela.pl create mode 100755 clearadm/updatesystem.pl create mode 100644 clearadm/var/run/.cvsignore create mode 100644 clearadm/var/run/clearagent.pl.pid create mode 100644 clearadm/var/run/cleartasks.pl.pid create mode 100755 clearadm/viewager.cgi create mode 100755 clearadm/viewdetails.cgi create mode 100755 clearadm/viewservers.cgi create mode 100755 clearadm/vobservers.cgi create mode 100644 cq/.-_hist create mode 100644 cq/CheckCodePage.pl create mode 100644 cq/PQA.pm create mode 100644 cq/check_attachments create mode 100644 cq/convertList.pl create mode 100644 cq/cqaction.pl create mode 100644 cq/cqd.pl create mode 100644 cq/cqd/CheckinPreop.pl create mode 100644 cq/cqd/cqc create mode 100644 cq/cqd/cqc.pm create mode 100644 cq/cqd/cqc.pm.php create mode 100644 cq/cqd/cqd create mode 100644 cq/cqd/releasenotes.cgi create mode 100644 cq/cqinfo.pl create mode 100644 cq/cqquery.pl create mode 100644 cq/enable_ldap create mode 100644 cq/ldap_settings.cfg create mode 100644 cq/listdynlists create mode 100644 cq/pqaclean create mode 100644 cq/pqamerge create mode 100644 cvsbin/cvsims create mode 100644 ecrc/ecrc create mode 100644 ecrc/ecrc.php create mode 100644 ecrc/ecrd create mode 100644 ecrc/ecrdesc create mode 100644 etc/cq.conf create mode 100644 etc/doskey.mac create mode 100644 etc/mail.conf create mode 100644 etc/triggers.dat create mode 100644 functions/common create mode 100644 functions/date64 create mode 100644 functions/display create mode 100644 functions/logs create mode 100644 functions/tmpfiles create mode 100644 functions/utils create mode 100644 lib/BinMerge.pm create mode 100644 lib/Clearcase.pm create mode 100644 lib/Clearcase/Element.pm create mode 100644 lib/Clearcase/Server.pm create mode 100644 lib/Clearcase/UCM.pm create mode 100644 lib/Clearcase/UCM/Activity.pm create mode 100644 lib/Clearcase/UCM/Baseline.pm create mode 100644 lib/Clearcase/UCM/Pvob.pm create mode 100644 lib/Clearcase/UCM/Stream.pm create mode 100644 lib/Clearcase/View.pm create mode 100644 lib/Clearcase/Views.pm create mode 100644 lib/Clearcase/Vob.pm create mode 100644 lib/Clearcase/Vobs.pm create mode 100644 lib/Clearquest.pm create mode 100644 lib/Clearquest/Admin.pm create mode 100644 lib/Clearquest/Client.pm create mode 100644 lib/Clearquest/DBService.pm create mode 100644 lib/Clearquest/LDAP.pm create mode 100644 lib/Clearquest/REST.pm create mode 100644 lib/Clearquest/Server.pm create mode 100644 lib/CmdLine.pm create mode 100644 lib/DateUtils.pm create mode 100644 lib/Display.pm create mode 100644 lib/GetConfig.pm create mode 100644 lib/Logger.pm create mode 100644 lib/Machines.pm create mode 100644 lib/Mail.pm create mode 100644 lib/OSDep.pm create mode 100644 lib/Rexec.pm create mode 100644 lib/SpreadSheet.pm create mode 100644 lib/TimeUtils.pm create mode 100644 lib/TriggerUtils.pm create mode 100644 lib/Utils.pm create mode 100644 maps/JavaScript/CheckAddress.js create mode 100644 maps/JavaScript/CheckEditProfile.js create mode 100644 maps/JavaScript/CheckLogin.js create mode 100644 maps/JavaScript/CheckRegistration.js create mode 100644 maps/JavaScript/CheckSignup.js create mode 100644 maps/JavaScript/ListActions.js create mode 100644 maps/JavaScript/MAPSUtils.js create mode 100644 maps/JavaScript/Register.js create mode 100644 maps/MAPS.png create mode 100644 maps/Reports.html create mode 100644 maps/SignupForm.html create mode 100755 maps/adm/index.html create mode 100644 maps/bin/MAPS.pm create mode 100644 maps/bin/MAPSDB.pm create mode 100644 maps/bin/MAPSDB.sql create mode 100755 maps/bin/MAPSDeliver create mode 100644 maps/bin/MAPSFile.pm create mode 100644 maps/bin/MAPSLog.pm create mode 100644 maps/bin/MAPSUtil.pm create mode 100644 maps/bin/MAPSWeb.pm create mode 100644 maps/bin/Search.gif create mode 100755 maps/bin/add2blacklist.cgi create mode 100755 maps/bin/add2nulllist.cgi create mode 100755 maps/bin/add2nulllist.pl create mode 100755 maps/bin/add2whitelist.cgi create mode 100755 maps/bin/checkaddress create mode 100755 maps/bin/checkaddress.cgi create mode 100755 maps/bin/detail.cgi create mode 100755 maps/bin/display.cgi create mode 100755 maps/bin/domains create mode 100755 maps/bin/editprofile.cgi create mode 100755 maps/bin/exportlist.cgi create mode 100755 maps/bin/list.cgi create mode 100755 maps/bin/main.cgi create mode 100755 maps/bin/maps create mode 100755 maps/bin/mapsscrub create mode 100755 maps/bin/mapsutil create mode 100755 maps/bin/modifyentries.cgi create mode 100755 maps/bin/nuke create mode 100755 maps/bin/processaction.cgi create mode 100755 maps/bin/register.cgi create mode 100755 maps/bin/registerform.cgi create mode 100755 maps/bin/search.cgi create mode 100755 maps/bin/signup.cgi create mode 100755 maps/bin/stats.cgi create mode 100755 maps/bin/updateprofile.cgi create mode 100755 maps/bin/weed create mode 100644 maps/bin/world.gif create mode 100644 maps/blacklist.html create mode 100644 maps/css/MAPSPlain.css create mode 100644 maps/css/MAPSStyle.css create mode 100644 maps/doc/CommonProblems.html create mode 100644 maps/doc/Costs.html create mode 100644 maps/doc/Details.html create mode 100644 maps/doc/Download.html create mode 100644 maps/doc/FAQ.html create mode 100644 maps/doc/ForgotPassword.html create mode 100644 maps/doc/Forwarding.html create mode 100644 maps/doc/Lists.html create mode 100644 maps/doc/MAPSLocal.html create mode 100644 maps/doc/MailLoops.html create mode 100644 maps/doc/Popsettings.html create mode 100644 maps/doc/RegExs.html create mode 100644 maps/doc/Requirements.php create mode 100644 maps/doc/SPAM.php create mode 100644 maps/doc/Signup.html create mode 100644 maps/doc/Using.php create mode 100644 maps/doc/Whitelist.html create mode 100644 maps/doc/add2blacklist.html create mode 100644 maps/doc/add2nulllist.html create mode 100644 maps/doc/detail.html create mode 100644 maps/doc/index.php create mode 100644 maps/doc/maps.css create mode 100644 maps/doc/world.gif create mode 100755 maps/etc/mail.conf create mode 100644 maps/favicon.ico create mode 100755 maps/forward create mode 100644 maps/images/Pattern1.gif create mode 100644 maps/images/next.gif create mode 100644 maps/images/previous.gif create mode 100644 maps/images/world.gif create mode 100644 maps/images/world.jpg create mode 100755 maps/index.php create mode 100644 maps/next.gif create mode 100644 maps/null.list create mode 100755 maps/php/ForgotPassword.php create mode 100755 maps/php/ListDomains.php create mode 100755 maps/php/MAPS.php create mode 100755 maps/php/Reports.php create mode 100755 maps/php/Space.php create mode 100755 maps/php/emailpassword.php create mode 100755 maps/php/list.php create mode 100755 maps/php/main.php create mode 100644 maps/previous.gif create mode 100644 maps/register.html create mode 100644 maps/world.gif create mode 100644 rc/Xdefaults create mode 100755 rc/bash_login create mode 100644 rc/clearcase create mode 100644 rc/clearcase.conf create mode 100644 rc/clearcase_profile create mode 100644 rc/client_scripts/Broadcom create mode 100644 rc/client_scripts/GD create mode 100644 rc/client_scripts/GE create mode 100644 rc/dircolors create mode 100644 rc/functions create mode 100644 rc/inputrc create mode 100644 rc/logout create mode 100644 rc/multisite create mode 100644 rc/perlcriticrc create mode 100644 rc/perldb create mode 100644 rc/perltidyrc create mode 100644 rc/set_colors create mode 100644 rc/set_path create mode 100755 rc/setup_rc create mode 100644 rc/signatures create mode 100644 rc/signatures.clearscm create mode 100644 rc/sshconfig create mode 100644 rc/system create mode 100644 rc/toprc create mode 100644 rc/vimrc create mode 100644 rc/vueprofile create mode 100644 rc/xemacs/clearcase.el create mode 100644 rc/xemacs/custom.el create mode 100644 rc/xemacs/init.el create mode 100644 rc/xemacs/mwheel.el create mode 100644 rc/xemacs/perlcritic.el create mode 100644 rc/xemacs/perltidy.el create mode 100644 rc/xemacs/visual-basic-mode.el create mode 100644 tcl/Display.tcl create mode 100644 test/.-_hist create mode 100644 test/.cvsignore create mode 100755 test/testclearcase.pl create mode 100644 test/testclearquest.pl create mode 100644 test/testclearquestServer.pl create mode 100755 test/testcmdline.pl create mode 100755 test/testelement.pl create mode 100755 test/testmail.pl create mode 100644 test/testrest.pl create mode 100644 test/testrexec.pl create mode 100644 test/testspreadsheet.pl create mode 100644 test/testspreadsheet.xls create mode 100755 test/testview.pl create mode 100755 test/testviews.pl create mode 100755 test/testvob.pl create mode 100755 test/testvobs.pl create mode 100644 web/.htaccess create mode 100644 web/Contract Addendum - Mindteck.doc create mode 100644 web/Contract Addendum.doc create mode 100644 web/Icons/Download.jpg create mode 100644 web/Icons/HomeSmall.gif create mode 100644 web/Icons/arrow_down.gif create mode 100644 web/Icons/arrow_right.gif create mode 100644 web/Icons/orange_arrow_down.gif create mode 100644 web/Icons/orange_arrow_right.gif create mode 100644 web/Images/AndrewDeFaria.jpg create mode 100644 web/Images/BMLeft.jpg create mode 100644 web/Images/BMRight.jpg create mode 100644 web/Images/Background.jpg create mode 100644 web/Images/Clouds.jpg create mode 100644 web/Images/TopOfTheWorld.jpg create mode 100644 web/Images/orange_gradient.gif create mode 100644 web/Images/tbg-bl-mg.jpg create mode 100644 web/Images/tbg-mg-bl.jpg create mode 100644 web/JavaScript/Menus.js create mode 100644 web/JavaScript/common.js create mode 100644 web/Logos/Ameriquest.gif create mode 100644 web/Logos/Broadcom.gif create mode 100644 web/Logos/Cisco.gif create mode 100644 web/Logos/ClearSCM.jpg create mode 100644 web/Logos/HPLogo.gif create mode 100644 web/Logos/LynuxWorks.gif create mode 100644 web/Logos/Salira.gif create mode 100644 web/Logos/Sun.jpg create mode 100644 web/Logos/TexasInstruments.jpg create mode 100644 web/Resumes/Andrew/Ameriquest.gif create mode 100644 web/Resumes/Andrew/Broadcom.gif create mode 100644 web/Resumes/Andrew/Cisco.gif create mode 100644 web/Resumes/Andrew/GEHealthcare.gif create mode 100644 web/Resumes/Andrew/General_Dynamics_logo.jpg create mode 100644 web/Resumes/Andrew/HPLogo.gif create mode 100644 web/Resumes/Andrew/LynuxWorks.gif create mode 100644 web/Resumes/Andrew/Resume.doc create mode 100644 web/Resumes/Andrew/Salira.gif create mode 100644 web/Resumes/Andrew/Sun.jpg create mode 100644 web/Resumes/Andrew/Tellabs.gif create mode 100644 web/Resumes/Andrew/TexasInstruments.jpg create mode 100644 web/Resumes/Andrew/index.php create mode 100644 web/Resumes/Don/Aspen.gif create mode 100644 web/Resumes/Don/DonSkanes.doc create mode 100644 web/Resumes/Don/Edentree.jpg create mode 100644 web/Resumes/Don/Nortel.gif create mode 100644 web/Resumes/Don/Vpacket.png create mode 100644 web/Resumes/Don/index.php create mode 100644 web/Resumes/Kevin/Resume.doc create mode 100644 web/Resumes/Kevin/index.php create mode 100644 web/Resumes/Mohammed/Resume.doc create mode 100644 web/Resumes/Mohammed/index.php create mode 100755 web/Resumes/Ron/Resume.doc create mode 100755 web/Resumes/Ron/index.php create mode 100644 web/Resumes/Tom/Resume.doc create mode 100644 web/Resumes/Tom/Tom.png create mode 100644 web/Resumes/Tom/index.php create mode 100644 web/addendum.php create mode 100644 web/businesscard.html create mode 100644 web/clearcase/EvilTwin.php create mode 100644 web/clearcase/OpenSourceBuild.php create mode 100644 web/clearcase/RemoveEmptyBranch.php create mode 100644 web/clearcase/index.php create mode 100644 web/clearcase/triggers.php create mode 100644 web/clearquest/CheckCodePage.php create mode 100644 web/clearquest/PQA.pm.php create mode 100644 web/clearquest/check_attachments.php create mode 100644 web/clearquest/cqd/BeforeCQD.jpg create mode 100644 web/clearquest/cqd/CQD.jpg create mode 100644 web/clearquest/cqd/CheckinPreop.php create mode 100644 web/clearquest/cqd/Releasenotes.html create mode 100644 web/clearquest/cqd/cqc.php create mode 100644 web/clearquest/cqd/cqc.pm.php create mode 100644 web/clearquest/cqd/cqd.php create mode 100644 web/clearquest/cqd/index.php create mode 100644 web/clearquest/cqd/rn.php create mode 100644 web/clearquest/db.php create mode 100644 web/clearquest/enable_ldap.php create mode 100644 web/clearquest/index.php create mode 100644 web/clearquest/ldap_settings.cfg create mode 100644 web/clearquest/listdynlists.php create mode 100644 web/clearquest/pqaclean.php create mode 100644 web/clearquest/pqamerge.php create mode 100644 web/contact.php create mode 100644 web/css/Article.css create mode 100644 web/css/ArticleLayout.css create mode 100644 web/css/Code.css create mode 100644 web/css/ColoredBoxesRoundedCorners.css create mode 100644 web/css/FrontPage.css create mode 100644 web/css/LevelThePlayingField.css create mode 100644 web/css/Main.css create mode 100644 web/css/ManPage.css create mode 100644 web/css/ManPageLayout.css create mode 100644 web/css/Menus.css create mode 100644 web/css/Plain.css create mode 100644 web/css/Print.css create mode 100644 web/css/TableBorders.css create mode 100644 web/error404.php create mode 100755 web/favicon.ico create mode 100755 web/index.php create mode 100644 web/people.php create mode 100644 web/php/clearscm.php create mode 100644 web/php/cvs_man.php create mode 100644 web/phpinfo.php create mode 100644 web/scripts/ecrd/ecr23184.html create mode 100644 web/scripts/ecrd/ecrc.php create mode 100644 web/scripts/ecrd/ecrc.php.php create mode 100644 web/scripts/ecrd/ecrd.php create mode 100644 web/scripts/ecrd/index.php create mode 100644 web/scripts/index.php create mode 100644 web/scripts/perl.php create mode 100644 web/services/consultancy.php create mode 100644 web/services/custom_software.php create mode 100644 web/services/customers.php create mode 100644 web/services/index.php create mode 100644 web/services/scm.php create mode 100644 web/services/sysadmin.php create mode 100644 web/services/web.php create mode 100644 web/sysadm/env/index.php create mode 100644 web/sysadm/index.php diff --git a/CCDB/ccdb.pl b/CCDB/ccdb.pl new file mode 100644 index 0000000..6704243 --- /dev/null +++ b/CCDB/ccdb.pl @@ -0,0 +1,217 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: ccdb.pl,v $ + +Request Clearcase metadata from CCDB + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.4 $ + +=item Created: + +Fri Mar 11 19:09:52 PST 2011 + +=item Modified: + +$Date: 2011/05/05 18:33:33 $ + +=back + +=head1 SYNOPSIS + + Usage ccdb.pl: [-u|sage] [-ve|rbose] [-deb|ug] + [-h|ost ] [-p|ort ] [] + + Where: + -u|sage: Displays usage + + -ve|rbose: Be verbose + -deb|ug: Output debug messages + + -h|ost : Host to contact (Default: localhost) + -p|ort : Port to connect to (Default: 25327) + Request to perform + +=head1 DESCRIPTION + +This script exercises the ccdbserver.pl daemon by requesting Clearcase metadata +from the remote host:port that the ccdbserver.pl daemon is running on. + +Requests are of the variety: + + + +=cut + +use strict; +use warnings; + +use Getopt::Long; +use FindBin; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use CCDBService; +use Display; +use Utils; + +my $VERSION = '$Revision: 1.4 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +my $me = $FindBin::Script; + $me =~ s/\.pl$//; + +local $0 = $me; + +my $CCDBService; + +sub DisplayOutput ($$) { + my ($status, $output) = @_; + + if ($status) { + error "Unable to service request (Status: $status)"; + display join "\n", @$output; + } else { + if (ref $output eq 'HASH') { + foreach (keys %$output) { + display "$_:$$output{$_}"; + } # foreach + } elsif (ref $output eq 'ARRAY') { + foreach (@$output) { + my %rec = %$_; + + display '-' x 80; + + foreach (keys %rec) { + my $data = "$_:"; + $data .= $rec{$_} ? $rec{$_} : ''; + + display $data; + } # foreach + } # foreach + + display '=' x 80; + display_nolf scalar @$output; + display_nolf ' record'; + display_nolf 's' if @$output > 1; + display ' qualified'; + } # if + } # if + + return; +} # DisplayOutput + +sub CmdLoop () { + while () { + display_nolf "CCDB:"; + + my $request = ; + + chomp $request; + + last if $request =~ /^exit|^quit/i; + + my ($status, $output) = $CCDBService->execute ($request); + + DisplayOutput ($status, $output); + + last if $request =~ /stopserver/i; + } # while + + return; +} # CmdLoop + +# Main +GetOptions ( + 'usage' => sub { Usage }, + 'verbose' => sub { set_verbose }, + 'debug' => sub { set_debug }, + 'host=s' => \$CCDBService::OPTS{CCDB_HOST}, + 'port=s' => \$CCDBService::OPTS{CCDB_PORT}, +) or Usage "Invalid parameter"; + +my $request = join ' ', @ARGV; + +display "$FindBin::Script V$VERSION"; + +$CCDBService = CCDBService->new; + +my ($status, $output); + +$status = $CCDBService->connectToServer ( + $CCDBService::OPTS{CCDB_HOST}, + $CCDBService::OPTS{CCDB_PORT} +); + +error 'Unable to connect to ' + . "$CCDBService::OPTS{CCDB_HOST}:$CCDBService::OPTS{CCDB_PORT}", 1 + unless $status; + +if ($request ne '') { + ($status, $output) = $CCDBService->execute ($request); + + DisplayOutput $status, $output; +} else { + CmdLoop; +} # if + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Clearexec + Display + Utils + +=end man + +=begin html + +
+Clearexec
+Display
+Utils
+
+ +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, Tellabs, Inc. All rights reserved. + +=cut diff --git a/CCDB/ccdbservice.pl b/CCDB/ccdbservice.pl new file mode 100644 index 0000000..513e575 --- /dev/null +++ b/CCDB/ccdbservice.pl @@ -0,0 +1,170 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: ccdbservice.pl,v $ + +ClearCase DataBase Service: Respond to requests for Clearcase metadata from +CCDB. + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.1 $ + +=item Created: + +Fri Mar 11 17:45:57 PST 2011 + +=item Modified: + +$Date: 2011/03/22 19:18:04 $ + +=back + +=head1 SYNOPSIS + + Usage ccdbservice.pl: [-u|sage] [-ve|rbose] [-de|bug] + [-da|emon] [-m|ultithreaded] [-p|idfile] + [-l|ogfile ] + + Where: + -u|sage: Displays usage + + -ve|rbose: Be verbose + -de|bug: Output debug messages + + -da|emon: Run in daemon mode. Use -nod|aemon to run in foreground + (Default: -daemon) + -m|ultithreaded: Multithread requests. Use -nom|ultithreaded to single + thread request handline (Default: -multithreaded) + -p|idfile: File to be created with the pid written to it (Default: + ccddservice.pid). Note: pidfile is only written if -daemon + is specified. + -l|ogfile: Specify alternative logfile name. Note that .log will be + appended. (Default: ccdbservice.log). + +Note: Certain options can be set in ../etc/ccdbserver.conf. See ccdbserver.conf +for more info. + +=head1 DESCRIPTION + +This script normally runs as a daemon and accepts requests from other hosts to +retrieve Clearcase metadata from CCDB. + +=cut + +use strict; +use warnings; + +use Getopt::Long; +use FindBin; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use CCDB; +use CCDBService; +use Display; +use Utils; + +my $VERSION = '$Revision: 1.1 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +# Extract relative path and basename from script name. +my $me = $FindBin::Script; + +# Remove .pl for Perl scripts that have that extension +$me =~ s/\.pl$//; + +my $pidfile = + "$CCDBService::OPTS{CCDB_RUNDIR}/$me.pid"; +my $logfile = + "$CCDBService::OPTS{CCDB_LOGDIR}/$me.log"; + +# Main +my $multithreaded = $CCDBService::OPTS{CCDB_MULTITHREADED}; +my $daemon = 1; + +GetOptions ( + 'usage' => sub { Usage }, + 'verbose' => sub { set_verbose }, + 'debug' => sub { set_debug }, + 'daemon!' => \$daemon, + 'multithreaded!' => \$multithreaded, + 'pidfile=s' => \$pidfile, + 'logfile=s' => \$logfile, +) or Usage "Invalid parameter"; + +Usage 'Extraneous options: ' . join ' ', @ARGV + if @ARGV; + +my $CCDBService = CCDBService->new; + +$CCDBService->setMultithreaded ($multithreaded); + +EnterDaemonMode $logfile, $logfile, $pidfile + if $daemon; + +display "$FindBin::Script V$VERSION started at " . localtime; + +$CCDBService->startServer; + +verbose "Server running"; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Clearexec + Display + Utils + +=end man + +=begin html + +
+Clearexec
+Display
+Utils
+
+ +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, Tellabs, Inc. All rights reserved. + +=cut + diff --git a/CCDB/etc/ccdb.conf b/CCDB/etc/ccdb.conf new file mode 100644 index 0000000..80f999b --- /dev/null +++ b/CCDB/etc/ccdb.conf @@ -0,0 +1,17 @@ +############################################################################### +# +# File: $RCSfile: ccdb.conf,v $ +# Revision: $Revision: 1.1 $ +# Description: Config file for CCDB +# Author: Andrew@ClearSCM.com +# Created: Wed Mar 9 17:31:45 PST 2011 +# Modified: $Date: 2011/03/22 19:18:04 $ +# Language: conf +# +# (c) Copyright 2011, Tellabs, Inc., all rights reserved +# +############################################################################### +CCDB_USERNAME: ccdb +CCDB_PASSWORD: clearcase +CCDB_SERVER: localhost +CCDB_MY_CNF: ccdb.my.cnf diff --git a/CCDB/etc/ccdb.my.cnf b/CCDB/etc/ccdb.my.cnf new file mode 100644 index 0000000..501d9fc --- /dev/null +++ b/CCDB/etc/ccdb.my.cnf @@ -0,0 +1,6 @@ +[mysql] +no-auto-rehash + +[client] +socket=/data/tools/ccdb/mysqltemp/mysql.sock +port=3305 \ No newline at end of file diff --git a/CCDB/etc/ccdb.sql b/CCDB/etc/ccdb.sql new file mode 100644 index 0000000..65a34f1 --- /dev/null +++ b/CCDB/etc/ccdb.sql @@ -0,0 +1,235 @@ +-- ----------------------------------------------------------------------------- +-- +-- File: $RCSfile: ccdb.sql,v $ +-- Revision: $Revision: 1.3 $ +-- Description: Clearcase DB +-- Author: Andrew@ClearSCM.com +-- Created: Wed Mar 14 15:53:12 PDT 2011 +-- Modified: $Date: 2011/04/15 22:19:21 $ +-- Language: SQL +-- +-- Copyright (c) 2011, Tellabs, Inc., all rights reserved +-- +-- ----------------------------------------------------------------------------- +-- Warning: The following line will delete the old database! +drop database if exists ccdb; + +-- Create a new database +create database ccdb; + +-- Now let's focus on this new database +use ccdb; + +-- registry: Defines a registry region +create table registry ( + name varchar (767) collate latin1_general_cs not null, + + primary key (name) +) type=innodb; -- registry + +-- region: Defines a region within a registry +create table region ( + name varchar (767) collate latin1_general_cs not null, + registry varchar (767) collate latin1_general_cs not null, + + primary key (registry, name), + key regionIndex (name), + foreign key registryLink (registry) references registry (name) + on delete cascade + on update cascade +) type=innodb; -- region + +-- vob: Defines a vob +create table vob ( + oid char (41), + name varchar (767) collate latin1_general_cs not null, + epoch bigint default 0, + type enum ( + 'base', + 'ucm' + ) not null default 'base', + + primary key (oid), + key nameIndex (name) +) type=innodb; -- vob + +-- folder: Defines a UCM folder +create table folder ( + oid char (41), + name varchar (767) collate latin1_general_cs not null, + pvob varchar (767) collate latin1_general_cs not null, + + primary key (oid), + key nameIndex (name), + foreign key pvobLink (pvob) references vob (name) + on delete cascade + on update cascade +) type=innodb; -- folder + +-- subfolder: Defines a UCM subfolder +create table subfolder ( + parent varchar (767) collate latin1_general_cs not null, + subfolder varchar (767) collate latin1_general_cs not null, + pvob varchar (767) collate latin1_general_cs not null, + + primary key (parent, subfolder, pvob), + foreign key parentLink (parent) references folder (name) + on delete cascade + on update cascade, + foreign key subfolderLink (subfolder) references folder (name) + on delete cascade + on update cascade, + foreign key pvobLink (pvob) references vob (name) + on delete cascade + on update cascade +) type=innodb; -- subfolder + +-- project: Defines a UCM project +create table project ( + oid char (41), + name varchar (767) collate latin1_general_cs not null, + folder varchar (767) collate latin1_general_cs not null, + pvob varchar (767) collate latin1_general_cs not null, + + primary key (oid), + key projectIndex (name), + key folderIndex (folder), + foreign key folderLink (folder) references folder (name) + on delete cascade + on update cascade, + foreign key pvobLink (pvob) references vob (name) + on delete cascade + on update cascade +) type=innodb; -- project + +-- stream: Defines a UCM stream +create table stream ( + oid char (41), + name varchar (767) collate latin1_general_cs not null, + pvob varchar (767) collate latin1_general_cs not null, + project varchar (767) collate latin1_general_cs not null, + type enum ( + 'integration', + 'regular' + ) not null default 'regular', + + primary key (oid), + key streamIndex (name), + foreign key pvobLink (pvob) references vob (name) + on delete cascade + on update cascade, + foreign key projectLink (project) references project (name) + on delete cascade + on update cascade +) type=innodb; -- stream + +-- activity: Defines an activity +create table activity ( + oid char (41), + name varchar (767) collate latin1_general_cs not null, + pvob varchar (767) collate latin1_general_cs not null, + type enum ( + 'integration', + 'regular' + ) not null default 'regular', + submitted datetime, + + primary key (oid), + key activityIndex (name), + foreign key pvobLink (pvob) references vob (name) + on delete cascade + on update cascade +) type=innodb; -- activity + +-- baseline: Defines a baseline +create table baseline ( + oid char (41), + name varchar (767) collate latin1_general_cs not null, + pvob varchar (767) collate latin1_general_cs not null, + + primary key (oid), + key baselineIndex (name), + foreign key pvobLink (pvob) references vob (name) + on delete cascade + on update cascade +) type=innodb; -- baseline + +-- Cross references +create table stream_activity_xref ( + stream varchar (767) collate latin1_general_cs not null, + activity varchar (767) collate latin1_general_cs not null, + pvob varchar (767) collate latin1_general_cs not null, + + primary key (stream, activity, pvob), + key streamIndex (stream), + key activityIndex (activity), + key pvobIndex (pvob), + foreign key streamLink (stream) references stream (name) + on delete cascade + on update cascade, + foreign key activityLink (activity) references activity (name) + on delete cascade + on update cascade, + foreign key pvobLink (pvob) references vob (name) + on delete cascade + on update cascade +) type=innodb; -- stream_activity_xref + +create table stream_baseline_xref ( + stream varchar (767) collate latin1_general_cs not null, + baseline varchar (767) collate latin1_general_cs not null, + pvob varchar (767) collate latin1_general_cs not null, + + primary key (stream, baseline, pvob), + key streamIndex (stream), + key baselineIndex (baseline), + key pvobIndex (pvob), + foreign key streamLink (stream) references stream (name) + on delete cascade + on update cascade, + foreign key baselineLink (baseline) references baseline (name) + on delete cascade + on update cascade, + foreign key pvobLink (pvob) references vob (name) + on delete cascade + on update cascade +) type=innodb; -- stream_baseline_xref + + +create table changeset ( + activity varchar (767) collate latin1_general_cs not null, + element varchar (767) collate latin1_general_cs not null, + version varchar (767) collate latin1_general_cs not null, + pvob varchar (767) collate latin1_general_cs not null, + created datetime, + + primary key (activity, element, version, pvob), + key activityIndex (activity), + key elementIndex (element), + key elementVersionIndex (version), + foreign key activityLink (activity) references activity (name) + on delete cascade + on update cascade, + foreign key pvobLink (pvob) references vob (name) + on delete cascade + on update cascade +) type=innodb; -- changeset + +create table baseline_activity_xref ( + baseline varchar (767) collate latin1_general_cs not null, + activity varchar (767) collate latin1_general_cs not null, + pvob varchar (767) collate latin1_general_cs not null, + + primary key (baseline, activity, pvob), + key baselineIndex (baseline), + key activityIndex (activity), + foreign key baselineLink (baseline) references baseline (name) + on delete cascade + on update cascade, + foreign key activityLink (activity) references activity (name) + on delete cascade + on update cascade, + foreign key pvobLink (pvob) references vob (name) + on delete cascade + on update cascade +) type=innodb; -- baseline_activity_xref diff --git a/CCDB/etc/ccdbservice b/CCDB/etc/ccdbservice new file mode 100644 index 0000000..436ef88 --- /dev/null +++ b/CCDB/etc/ccdbservice @@ -0,0 +1,160 @@ +#!/bin/sh +### BEGIN INIT INFO +# Provides: ccdbservice +# Required-Start: $network +# Required-Stop: none +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Starts the ccdbservice daemon +# Description: CCDBService is part of the CCDB package. It is a daemon +# that runs in the background and responds to triggers which +# indicate updates to Clearcase UCM data. Such data is stored +# in CCDB in order to provide quicker access to UCM metadata. +### END INIT INFO + +# Author: Andrew DeFaria +# +# Do NOT "set -e" + +# PATH should only include /usr/* if it runs after the mountnfs.sh script +PATH=/sbin:/usr/sbin:/bin:/usr/bin +DESC="CCDB Service" +NAME=ccdbservice.pl + +# Need to determine where this gets place. For now this is the path into my +# development view +CCDBBASE=/view/adefaria_tools/vob/adpscmtools/CCDB +DAEMON=$CCDBBASE/$NAME +PIDFILE=$CCDBBASE/$NAME.pid +DAEMON_ARGS="" +SCRIPTNAME=/etc/init.d/$NAME +RUNASUSER="ccdb" + +# Exit if the package is not installed +[ -x "$DAEMON" ] || exit 0 + +# Read configuration variable file if it is present +[ -r /etc/default/$NAME ] && . /etc/default/$NAME + +# Load the VERBOSE setting and other rcs variables +. /lib/init/vars.sh + +# Define LSB log_* functions. +# Depend on lsb-base (>= 3.0-6) to ensure that this file is present. +. /lib/lsb/init-functions + +# +# Function that starts the daemon/service +# +do_start() +{ + # Return + # 0 if daemon has been started + # 1 if daemon was already running + # 2 if daemon could not be started + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ + || return 1 + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON \ + --chuid $RUNASUSER \ + -- $DAEMON_ARGS \ + || return 2 +} + +# +# Function that stops the daemon/service +# +do_stop() +{ + # Return + # 0 if daemon has been stopped + # 1 if daemon was already stopped + # 2 if daemon could not be stopped + # other if a failure occurred + start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME + RETVAL="$?" + [ "$RETVAL" = 2 ] && return 2 + # Wait for children to finish too if this is a daemon that forks + # and if the daemon is only ever run from this initscript. + # If the above conditions are not satisfied then add some other code + # that waits for the process to drop all resources that could be + # needed by services started subsequently. A last resort is to + # sleep for some time. + start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON + [ "$?" = 2 ] && return 2 + # Many daemons don't delete their pidfiles when they exit. + rm -f $PIDFILE + return "$RETVAL" +} + +# +# Function that sends a SIGHUP to the daemon/service +# +do_reload() { + # + # If the daemon can reload its configuration without + # restarting (for example, when it is sent a SIGHUP), + # then implement that here. + # + start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME + return 0 +} + +case "$1" in + start) + [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" + do_start + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + stop) + [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" + do_stop + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + status) + status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? + ;; + #reload|force-reload) + # + # If do_reload() is not implemented then leave this commented out + # and leave 'force-reload' as an alias for 'restart'. + # + #log_daemon_msg "Reloading $DESC" "$NAME" + #do_reload + #log_end_msg $? + #;; + restart|force-reload) + # + # If the "reload" option is implemented then remove the + # 'force-reload' alias + # + log_daemon_msg "Restarting $DESC" "$NAME" + do_stop + case "$?" in + 0|1) + do_start + case "$?" in + 0) log_end_msg 0 ;; + 1) log_end_msg 1 ;; # Old process is still running + *) log_end_msg 1 ;; # Failed to start + esac + ;; + *) + # Failed to stop + log_end_msg 1 + ;; + esac + ;; + *) + #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2 + echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 + exit 3 + ;; +esac + +: diff --git a/CCDB/etc/ccdbservice.conf b/CCDB/etc/ccdbservice.conf new file mode 100644 index 0000000..ba4ba9b --- /dev/null +++ b/CCDB/etc/ccdbservice.conf @@ -0,0 +1,18 @@ +############################################################################### +# +# File: $RCSfile: ccdbservice.conf,v $ +# Revision: $Revision: 1.1 $ +# Description: Config file for Clearexec +# Author: Andrew@ClearSCM.com +# Created: Fri Mar 11 17:58:31 PST 2011 +# Modified: $Date: 2011/03/22 19:18:04 $ +# Language: conf +# +# (c) Copyright 2011, Tellabs, Inc., all rights reserved +# +############################################################################### +CCDB_HOST: lnxsc021 +CCDB_PORT: 8355 +CCDB_MULTITHREADED: 1 +CCDB_LOGDIR: . +CCDB_RUNDIR: . \ No newline at end of file diff --git a/CCDB/lib/CCDB.pm b/CCDB/lib/CCDB.pm new file mode 100644 index 0000000..9202285 --- /dev/null +++ b/CCDB/lib/CCDB.pm @@ -0,0 +1,1293 @@ +=pod + +=head1 NAME $RCSfile: CCDB.pm,v $ + +Object oriented interface to CCDB. + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.4 $ + +=item Created + +Wed Mar 9 17:03:48 PST 2011 + +=item Modified + +$Date: 2011/04/15 22:27:45 $ + +=back + +=head1 SYNOPSIS + +Provides the CCDB object which handles all interaction with the CCDB +database. Similar add/change/delete/update methods for other record types. In +general you must orient your record hashs to have the appropriately named +keys that correspond to the database. Also see method documentation for +specifics about the method you are envoking. + + # Create new CCDB object + my $ccdb= new CCDB; + + # Add a new system + my %project= ( + name => 'The Next Thing', + pvob => '8800_projects', + description => 'This is the greatest thing since sliced bread', + ); + + my ($err, $msg) = $CCDB->AddProject (%project); + + # Find projects matching '8800' + my @projects = $ccdb->FindProject ('8800'); + + # Get a project by name + my %project = $ccdb->GetProject ('8800_projects'); + + # Update project + my %update = ( + 'description' => 'Greatest thing since the net!', + ); + + my ($err, $msg) = $ccdb->UpdateProject ('8800_projects', %update); + + # Delete project (Warning: will delete all related records regarding this + # project). + my ($err, $msg) = $ccdb->DeleteProject ('8800_projects'); + +=head1 DESCRIPTION + +This package provides and object oriented interface to the CCDB database. +Methods are provided to manipulate records by adding, updating and deleting +them. In general you need to specify a hash which contains keys and values +corresponding to the database field names and values. + +=head1 ROUTINES + +The following methods are available: + +=cut + +package CCDB; + +use strict; +use warnings; + +use Carp; +use DBI; + +use FindBin; + +use lib "$FindBin::Bin/../../lib"; + +use Clearcase; +use DateUtils; +use Display; +use GetConfig; + +our %CCDBOPTS = GetConfig ("$FindBin::Bin/../etc/ccdb.conf"); + +$CCDBOPTS{CCDB_MY_CNF} = "$FindBin::Bin/etc/$CCDBOPTS{CCDB_MY_CNF}"; + +# Globals +our $VERSION = '$Revision: 1.4 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +$CCDBOPTS{CCDB_USERNAME} = $ENV{CCDB_USERNAME} + ? $ENV{CCDB_USERNAME} + : $CCDBOPTS{CCDB_USERNAME} + ? $CCDBOPTS{CCDB_USERNAME} + : ''; +$CCDBOPTS{CCDB_PASSWORD} = $ENV{CCDB_PASSWORD} + ? $ENV{CCDB_PASSWORD} + : $CCDBOPTS{CCDB_PASSWORD} + ? $CCDBOPTS{CCDB_PASSWORD} + : ''; +$CCDBOPTS{CCDB_SERVER} = $ENV{CCDB_SERVER} + ? $ENV{CCDB_SERVER} + : $CCDBOPTS{CCDB_SERVER} + ? $CCDBOPTS{CCDB_SERVER} + : ''; + +# Internal methods +sub _dberror ($$) { + my ($self, $msg, $statement) = @_; + + my $dberr = $self->{db}->err; + my $dberrmsg = $self->{db}->errstr; + + $dberr ||= 0; + $dberrmsg ||= 'Success'; + + my $message = ''; + + if ($dberr) { + my $function = (caller (1)) [3]; + + $message = "$function: $msg\nError #$dberr: $dberrmsg\n" + . "SQL Statement: $statement"; + } # if + + return $dberr, $message; +} # _dberror + +sub _formatValues (@) { + my ($self, @values) = @_; + + my @returnValues; + + # Quote data values + foreach (@values) { + if ($_) { + unless ($_ eq '') { + push @returnValues, $self->{db}->quote ($_); + next; + } # unless + } # if + + push @returnValues, 'null'; + } # foreach + + return @returnValues; +} # _formatValues + +sub _formatNameValues (%) { + my ($self, %rec) = @_; + + my @nameValueStrs; + + push @nameValueStrs, "$_=" . $self->{db}->quote ($rec{$_}) + foreach (keys %rec); + + return @nameValueStrs; +} # _formatNameValues + +sub _addRecord ($%) { + my ($self, $table, %rec) = @_; + + my $statement = "insert into $table ("; + $statement .= join ',', keys %rec; + $statement .= ') values ('; + $statement .= join ',', $self->_formatValues (values %rec); + $statement .= ')'; + + $self->{db}->do ($statement); + + return $self->_dberror ("Unable to add record to $table", $statement); +} # _addRecord + +sub _deleteRecord ($;$) { + my ($self, $table, $condition) = @_; + + my $count; + + my $statement = "select count(*) from $table "; + $statement .= "where $condition" + if $condition; + + my $sth = $self->{db}->prepare ($statement) + or return $self->_dberror ('Unable to prepare statement', $statement); + + $sth->execute + or return $self->_dberror ('Unable to execute statement', $statement); + + my @row = $sth->fetchrow_array; + + $sth->finish; + + if ($row[0]) { + $count = $row[0]; + } else { + $count = 0; + } # if + + return ($count, 'Records deleted') + if $count == 0; + + $statement = "delete from $table "; + $statement .= "where $condition" + if $condition; + + $self->{db}->do ($statement); + + if ($self->{db}->err) { + return $self->_dberror ("Unable to delete record from $table", $statement); + } else { + return $count, 'Records deleted'; + } # if +} # _deleteRecord + +sub _updateRecord ($$%) { + my ($self, $table, $condition, %rec) = @_; + + my $statement = "update $table set "; + $statement .= join ',', $self->_formatNameValues (%rec); + $statement .= " where $condition" + if $condition; + + $self->{db}->do ($statement); + + return $self->_dberror ("Unable to update record in $table", $statement); +} # _updateRecord + +sub _checkRequiredFields ($$) { + my ($fields, $rec) = @_; + + foreach my $fieldname (@$fields) { + my $found = 0; + + foreach (keys %$rec) { + if ($fieldname eq $_) { + $found = 1; + last; + } # if + } # foreach + + return "$fieldname is required" + unless $found; + } # foreach + + return; +} # _checkRequiredFields + +sub _getRecords ($$) { + my ($self, $table, $condition) = @_; + + my ($err, $msg); + + my $statement = "select * from $table where $condition"; + + my $sth = $self->{db}->prepare ($statement); + + unless ($sth) { + ($err, $msg) = $self->_dberror ('Unable to prepare statement', $statement); + + croak $msg; + } # if + + my $status = $sth->execute; + + unless ($status) { + ($err, $msg) = $self->_dberror ('Unable to execute statement', $statement); + + croak $msg; + } # if + + my @records; + + while (my $row = $sth->fetchrow_hashref) { + push @records, $row; + } # while + + return @records; +} # _getRecord + +sub _getLastID () { + my ($self) = @_; + + my $statement = 'select last_insert_id()'; + + my $sth = $self->{db}->prepare ($statement); + + my ($err, $msg); + + unless ($sth) { + ($err, $msg) = $self->_dberror ('Unable to prepare statement', $statement); + + croak $msg; + } # if + + my $status = $sth->execute; + + unless ($status) { + ($err, $msg) = $self->_dberror ('Unable to execute statement', $statement); + + croak $msg; + } # if + + my @records; + + my @row = $sth->fetchrow_array; + + return $row[0]; +} # _getLastID + +sub new (;$) { + my ($class, $dbserver) = @_; + + $dbserver ||= $CCDBOPTS{CCDB_SERVER}; + + my $self = bless {}, $class; + + my $dbname = 'ccdb'; + my $dbdriver = 'mysql'; + + $self->{db} = DBI->connect ( + "DBI:$dbdriver:$dbname:$dbserver;" + . "mysql_read_default_file=$CCDBOPTS{CCDB_MY_CNF}", + $CCDBOPTS{CCDB_USERNAME}, + $CCDBOPTS{CCDB_PASSWORD}, + {PrintError => 0}, + ) or croak ( + "Couldn't connect to $dbname database " + . "as $CCDBOPTS{CCDB_USERNAME}\@$dbserver\nDBERR: $DBI::errstr" + ); + + return $self; +} # new + +sub AddRecord ($$$) { + my ($self, $record, $required, $data) = @_; + + my $Record = ucfirst $record; + my @requiredFields = @$required; + + unless (ref $data eq 'HASH') { + my $VAR1; + + eval $data; + + $data = $VAR1; + } # unless + + my %data = %$data; + + # Determine oid if necessary + unless ($data{oid}) { + if ($record eq 'activity' + or $record eq 'baseline' + or $record eq 'folder', + or $record eq 'project' + or $record eq 'stream' + or $record eq 'replica' + or $record eq 'vob') { + + if ($record eq 'vob') { + $data{oid} = $Clearcase::CC->name2oid ( + 'vob:' . Clearcase::vobtag ($data{name}) + ); + } elsif ($record eq 'replica') { + $data{oid} = $Clearcase::CC->name2oid ( + "replica:$data{replica}", $data{vob} + ); + } else { + $data{oid} = $Clearcase::CC->name2oid ( + "$record:$data{name}", $data{pvob} + ); + } # if + } # if + } # unless + + my $result = _checkRequiredFields \@requiredFields, \%data; + + return -1, "Add$Record: $result" + if $result; + + return $self->_addRecord ($record, %data); +} # AddRecord + +sub DeleteRecord ($$$) { + my ($self, $table, $keyname, $keyvalue) = @_; + + # If $keyname is an array then we have multiple keys in the database. When + # this is the case we assume that both $keyname and $keyvalue are references + # to equal sized name/value pairs and we construct the condition in the form + # of "= and =..." + my $condition; + + if (ref $keyname eq 'ARRAY') { + for (my $i = 0; $i < @$keyname; $i++) { + unless ($condition) { + $condition = "$$keyname[$i]='$$keyvalue[$i]'" + } else { + $condition .= " and $$keyname[$i]='$$keyvalue[$i]'" + } # if + } # for + } else { + $condition = "$keyname='$keyvalue'"; + } # if + + return $self->_deleteRecord ($table, $condition); +} # DeleteRecord + +sub UpdateRecord ($$$$) { + my ($self, $table, $keyname, $keyvalue, $update) = @_; + + # If $keyname is an array then we have multiple keys in the database. When + # this is the case we assume that both $keyname and $keyvalue are references + # to equal sized name/value pairs and we construct the condition in the form + # of "= and =..." + my $condition; + + if (ref $keyname eq 'ARRAY') { + for (my $i = 0; $i < @$keyname; $i++) { + unless ($condition) { + $condition = "$$keyname[$i] like '$$keyvalue[$i]'" + } else { + $condition .= " and $$keyname[$i] like '$$keyvalue[$i]'" + } # if + } # for + } else { + $condition = "$keyname like '$keyvalue'"; + } # if + + unless (ref $update eq 'HASH') { + my $VAR1; + + eval $update; + + $update = $VAR1; + } # unless + + my %update = %$update; + + return $self->_updateRecord ($table, $condition, %update); +} # UpdateRecord + +sub GetRecord ($$$) { + my ($self, $table, $keyname, $keyvalue) = @_; + + # If $keyname is an array then we have multiple keys in the database. When + # this is the case we assume that both $keyname and $keyvalue are references + # to equal sized name/value pairs and we construct the condition in the form + # of "= and =..." + my $condition; + + if (ref $keyname eq 'ARRAY') { + for (my $i = 0; $i < @$keyname; $i++) { + $$keyvalue[$i] ||= ''; + + unless ($condition) { + $condition = "$$keyname[$i]='$$keyvalue[$i]'" + } else { + $condition .= " and $$keyname[$i]='$$keyvalue[$i]'" + } # if + } # for + } else { + $condition = "$keyname='$keyvalue'"; + } # if + + my @records = $self->_getRecords ($table, $condition); + + if ($records[0]) { + return %{$records[0]}; + } else { + return; + } # if +} # GetRecord + +sub FindRecord ($$$;$) { + my ($self, $table, $keyname, $keyvalue, $additional) = @_; + + # If $keyname is an array then we have multiple keys in the database. When + # this is the case we assume that both $keyname and $keyvalue are references + # to equal sized name/value pairs and we construct the condition in the form + # of " like and like ..." + my $condition; + + if (ref $keyname eq 'ARRAY') { + for (my $i = 0; $i < @$keyname; $i++) { + $$keyvalue[$i] ||= ''; + $$keyvalue[$i] = '' if $$keyvalue[$i] eq '*'; + + unless ($condition) { + $condition = "$$keyname[$i] like '%$$keyvalue[$i]%'" + } else { + $condition .= " and $$keyname[$i] like '%$$keyvalue[$i]%'" + } # if + } # for + } else { + $keyvalue ||= ''; + $keyvalue = '' if $keyvalue eq '*'; + $condition = "$keyname like '%$keyvalue%'"; + } # if + + return $self->_getRecords ($table, $condition); +} # FindRecord + +sub AddProject ($) { + my ($self, $data) = @_; + + return $self->AddRecord ( + 'project', + ['name', 'folder', 'pvob'], + $data + ); +} # AddProject + +sub DeleteProject ($$$) { + my ($self, $name, $folder, $pvob) = @_; + + return $self->DeleteRecord ( + 'project', + ['name', 'folder', 'pvob'], + [$name, $folder, $pvob] + ); +} # DeleteProject + +sub UpdateProject ($$$$) { + my ($self, $name, $folder, $pvob, $update) = @_; + + return $self->UpdateRecord ( + 'project', + ['name', 'folder', 'pvob'], + [$name, $folder, $pvob], + $update + ); +} # UpdateRegistry + +sub GetProject ($) { + my ($self, $name, $folder, $pvob) = @_; + + return $self->GetRecord ( + 'project', + ['name', 'folder', 'pvob'], + [$name, $folder, $pvob] + ); +} # GetProject + +sub FindProject (;$$$) { + my ($self, $name, $folder, $project, $pvob) = @_; + + return $self->FindRecord ( + 'project', + ['name', 'folder', 'pvob'], + [$name, $folder, $pvob] + ); +} # FindProject + +sub AddRegistry ($) { + my ($self, $data) = @_; + + return $self->AddRecord ( + 'registry', + ['name'], + $data + ); +} # AddRegistry + +sub DeleteRegistry ($) { + my ($self, $name) = @_; + + return $self->DeleteRecord ('registry', 'name', $name); +} # DeleteRegistry + +sub UpdateRegistry ($$) { + my ($self, $name, $update) = @_; + + return $self->UpdateRecord ('registry', 'name', $name, $update); +} # UpdateRegistry + +sub GetRegistry ($) { + my ($self, $name) = @_; + + return $self->GetRecord ('registry', 'name', $name); +} # GetRegistry + +sub FindRegistry (;$) { + my ($self, $name) = @_; + + return $self->FindRecord ('registry', 'name', $name); +} # FindRegistry + +sub AddStream ($) { + my ($self, $data) = @_; + + # TODO: We should probably make sure that things like $$data{pvob} and + # $$data{name} exist in $data first. Maybe add the record (which checks for + # required fields) then perform an update to update the type to intergration + # IFF this is an intergration stream. + + # Determine the integration stream for this stream's project. First get + # project for the stream. + my $pvobTag = Clearcase::vobtag ($$data{pvob}); + my $cmd = "lsstream -fmt \"%[project]p\" $$data{name}\@$pvobTag"; + + my ($status, @output) = $Clearcase::CC->execute ($cmd); + + if ($status == 0) { + my $project = $output[0]; + + # Now get the intergration stream for this project + $cmd = "lsproject -fmt \"%[istream]p\" $project\@$pvobTag"; + + ($status, @output) = $Clearcase::CC->execute ($cmd); + + if ($status == 0) { + $$data{type} = 'integration' + if $$data{name} eq $output[0]; + } # if + } # if + + return $self->AddRecord ( + 'stream', + ['name', 'pvob'], + $data + ); +} # AddStream + +sub DeleteStream ($$) { + my ($self, $name, $pvob) = @_; + + return $self->DeleteRecord ( + 'stream', + ['name', 'pvob'], + [$name, $pvob], + ); +} # DeleteStream + +sub DeleteStreamOID ($) { + my ($self, $oid) = @_; + + return $self->DeleteRecord ( + 'stream', + 'oid', + $oid + ); +} # DeleteStreamOID + +sub UpdateStream ($$$) { + my ($self, $name, $pvob, $update) = @_; + + return $self->UpdateRecord ( + 'stream', + ['name', 'pvob'], + [$name, $pvob], + $update + ); +} # UpdateStream + +sub GetStream ($$) { + my ($self, $name, $pvob) = @_; + + return $self->GetRecord ( + 'stream', + ['name', 'pvob'], + [$name, $pvob], + ); +} # GetRegistry + +sub FindStream (;$$) { + my ($self, $name, $pvob) = @_; + + return $self->FindRecord ( + 'stream', + ['name', 'pvob'], + [$name, $pvob] + ); +} # FindRegistry + +sub AddSubfolder ($) { + my ($self, $data) = @_; + + return $self->AddRecord ( + 'subfolder', + ['parent', 'subfolder', 'pvob'], + $data + ); +} # AddSubfolder + +sub DeleteSubfolder ($$$) { + my ($self, $parent, $subfolder, $pvob) = @_; + + return $self->DeleteRecord ( + 'subfolder', + ['parent', 'subfolder', 'pvob'], + [$parent, $subfolder, $pvob], + ); +} # DeleteSubfolder + +sub UpdateSubfolder ($$$$) { + my ($self, $parent, $subfolder, $pvob, $update) = @_; + + return $self->UpdateRecord ( + 'subfolder', + ['parent', 'subfolder', 'pvob'], + [$parent, $subfolder, $pvob], + $update + ); +} # UpdateSubfolder + +sub GetSubfolder ($$$) { + my ($self, $parent, $subfolder, $pvob) = @_; + + return $self->GetRecord ( + 'subfolder', + ['parent', 'subfolder', 'pvob'], + [$parent, $subfolder, $pvob], + ); +} # GetSubfolder + +sub FindSubfolder (;$$$) { + my ($self, $parent, $subfolder, $pvob) = @_; + + return $self->FindRecord ( + 'subfolder', + ['parent', 'subfolder', 'pvob'], + [$parent, $subfolder, $pvob] + ); +} # FindFolder + +sub AddActivity ($) { + my ($self, $data) = @_; + + if ($$data{name}) { + $$data{type} = 'integration' + if $$data{name} =~ /^(deliver|rebase|integrate|revert|tlmerge)/i; + } # if + + return $self->AddRecord ( + 'activity', + ['name', 'pvob'], + $data + ); +} # AddActivity + +sub DeleteActivity ($$) { + my ($self, $name, $pvob) = @_; + + return $self->DeleteRecord ( + 'activity', + ['name', 'pvob'], + [$name, $pvob], + ); +} # DeleteActivity + +sub DeleteActivityOID ($) { + my ($self, $oid) = @_; + + return $self->DeleteRecord ( + 'activity', + 'name', + $oid + ); +} # DeleteActivityOID + +sub UpdateActivity ($$$) { + my ($self, $name, $pvob, $update) = @_; + + return $self->UpdateRecord ( + 'activity', + ['name', 'pvob'], + [$name, $pvob], + $update + ); +} # UpdateActivity + +sub GetActivity ($$) { + my ($self, $name, $pvob) = @_; + + return $self->GetRecord ( + 'activity', + ['name', 'pvob'], + [$name, $pvob], + ); +} # GetActivity + +sub FindActivity (;$$) { + my ($self, $name, $pvob) = @_; + + return $self->FindRecord ( + 'activity', + ['name', 'pvob'], + [$name, $pvob] + ); +} # FindActivity + +sub AddBaseline ($) { + my ($self, $data) = @_; + + return $self->AddRecord ( + 'baseline', + ['name', 'pvob'], + $data + ); +} # AddBaseline + +sub DeleteBaseline ($$) { + my ($self, $name, $pvob) = @_; + + return $self->DeleteRecord ( + 'baseline', + ['name', 'pvob'], + [$name, $pvob], + ); +} # DeleteBaseline + +sub DeleteBaselineOID ($) { + my ($self, $oid) = @_; + + return $self->DeleteRecord ( + 'baseline', + 'oid', + $oid, + ); +} # DeleteBaselineOID + +sub UpdateBaseline ($$$) { + my ($self, $name, $pvob, $update) = @_; + + return $self->UpdateRecord ( + 'baseline', + ['name', 'pvob'], + [$name, $pvob], + $update + ); +} # UpdateBaseline + +sub GetBaseline ($$) { + my ($self, $name, $pvob) = @_; + + return $self->GetRecord ( + 'baseline', + ['name', 'pvob'], + [$name, $pvob], + ); +} # GetBaseline + +sub FindBaseline (;$$) { + my ($self, $name, $pvob) = @_; + + return $self->FindRecord ( + 'baseline', + ['name', 'pvob'], + [$name, $pvob] + ); +} # FindBaseline + +sub DeleteElementAll ($) { + my ($self, $name) = @_; + + my ($total, $err, $msg); + + foreach ($self->FindChangeset (undef, $name)) { + my %changeset = %$_; + + ($err, $msg) = $self->DeleteChangeset ( + $changeset{activity}, + $changeset{name}, + $changeset{version}, + $changeset{pvob}, + ); + + return ($err, $msg) + if $msg ne 'Records deleted'; + + $total += $err; + } # foreach + + return ($total, $msg); +} # DeleteElementAll + +sub AddChangeset ($) { + my ($self, $data) = @_; + + return $self->AddRecord ( + 'changeset', + ['activity', 'element', 'version', 'pvob'], + $data + ); +} # AddChangeset + +sub DeleteChangeset ($$$$) { + my ($self, $activity, $element, $version, $pvob) = @_; + + return $self->DeleteRecord ( + 'changeset', + ['activity', 'element', 'version', 'pvob'], + [$activity, $element, $version, $pvob], + ); +} # DeleteChangeset + +sub UpdateChangeset ($$$$$) { + my ($self, $activity, $element, $version, $pvob, $update) = @_; + + return $self->UpdateRecord ( + 'changeset', + ['activity', 'element', 'version', 'pvob'], + [$activity, $element, $version, $pvob], + $update + ); +} # UpdateChangeset + +sub GetChangeset ($$$$) { + my ($self, $activity, $element, $version, $pvob) = @_; + + return $self->GetRecord ( + 'changeset', + ['activity', 'element', 'version', 'pvob'], + [$activity, $element, $version, $pvob], + ); +} # GetChangeset + +sub FindChangeset (;$$$$) { + my ($self, $activity, $element, $version, $pvob) = @_; + + return $self->FindRecord ( + 'changeset', + ['activity', 'element', 'version', 'pvob'], + [$activity, $element, $version, $pvob] + ); +} # FindChangeset + +sub AddFolder ($) { + my ($self, $data) = @_; + + return $self->AddRecord ( + 'folder', + ['name', 'pvob'], + $data + ); +} # AddFolder + +sub DeleteFolder ($$) { + my ($self, $folder, $pvob) = @_; + + return $self->DeleteRecord ( + 'folder', + ['name', 'pvob'], + [$folder, $pvob], + ); +} # DeleteFolder + +sub UpdateFolder ($$$) { + my ($self, $name, $pvob, $update) = @_; + + return $self->UpdateRecord ( + 'folder', + ['name', 'pvob'], + [$name, $pvob], + $update + ); +} # UpdateFolder + +sub GetFolder ($$) { + my ($self, $name, $pvob) = @_; + + return $self->GetRecord ( + 'folder', + ['name', 'pvob'], + [$name, $pvob], + ); +} # GetFolder + +sub FindFolder (;$$) { + my ($self, $name, $pvob) = @_; + + return $self->FindRecord ( + 'folder', + ['name', 'pvob'], + [$name, $pvob] + ); +} # FindFolder + +sub AddVob ($) { + my ($self, $data) = @_; + + return $self->AddRecord ( + 'vob', + ['name'], + $data + ); +} # AddVob + +sub DeleteVob ($) { + my ($self, $name) = @_; + + return $self->DeleteRecord ( + 'vob', + ['name'], + $name, + ); +} # DeleteVob + +sub UpdateVob ($$) { + my ($self, $name, $update) = @_; + + return $self->UpdateRecord ('vob', 'name', $name, $update); +} # UpdateVob + +sub GetVob ($) { + my ($self, $name) = @_; + + return $self->GetRecord ( + 'vob', + 'name', + $name, + ); +} # GetVob + +sub FindVob (;$$) { + my ($self, $name, $type) = @_; + + $type ||= ''; + + return $self->FindRecord ( + 'vob', + ['name', 'type'], + [$name, $type], + ); +} # FindVob + +sub AddStreamActivityXref ($) { + my ($self, $data) = @_; + + return $self->AddRecord ( + 'stream_activity_xref', + ['stream', 'activity', 'pvob'], + $data + ); +} # AddStreamActivityXref + +sub DeleteStreamActivityXref ($$$) { + my ($self, $stream, $activity, $pvob) = @_; + + return $self->DeleteRecord ( + 'stream_activity_xref', + ['stream', 'activity', 'pvob'], + [$stream, $activity, $pvob], + ); +} # DeleteStreamActivityXref + +sub UpdateStreamActivityXref ($$$$) { + my ($self, $stream, $activity, $pvob, $update) = @_; + + return $self->UpdateRecord ( + 'stream_activity_xref', + ['stream', 'activity', 'pvob'], + [$stream, $activity, $pvob], + $update + ); +} # UpdateStreamActivityXref + +sub GetStreamActivityXref ($$$) { + my ($self, $stream, $activity, $pvob) = @_; + + return $self->GetRecord ( + 'stream_activity_xref', + ['stream', 'activity', 'pvob'], + [$stream, $activity, $pvob], + ); +} # GetStreamActivityXref + +sub FindStreamActivityXref (;$$$) { + my ($self, $stream, $activity, $pvob) = @_; + + return $self->FindRecord ( + 'stream_activity_xref', + ['stream', 'activity', 'pvob'], + [$stream, $activity, $pvob] + ); +} # FindStreamActivityXref + +sub AddStreamBaselineXref ($) { + my ($self, $data) = @_; + + return $self->AddRecord ( + 'stream_baseline_xref', + ['stream', 'baseline', 'pvob'], + $data + ); +} # AddStreamBaselineXref + +sub DeleteStreamBaselineXref ($$$) { + my ($self, $stream, $baseline, $pvob) = @_; + + return $self->DeleteRecord ( + 'stream_baseline_xref', + ['stream', 'baseline', 'pvob'], + [$stream, $baseline, $pvob], + ); +} # DeleteStreamBaselineXref + +sub UpdateStreamBaselineXref ($$$$) { + my ($self, $stream, $baseline, $pvob, $update) = @_; + + return $self->UpdateRecord ( + 'stream_baseline_xref', + ['stream', 'baseline', 'pvob'], + [$stream, $baseline, $pvob], + $update + ); +} # UpdateStreamBaselineXref + +sub GetStreamBaselineXref ($$$) { + my ($self, $stream, $baseline, $pvob) = @_; + + return $self->GetRecord ( + 'stream_baseline_xref', + ['stream', 'baseline', 'pvob'], + [$stream, $baseline, $pvob], + ); +} # GetStreamBaselineXref + +sub FindStreamBaselineXref (;$$$) { + my ($self, $stream, $baseline, $pvob) = @_; + + return $self->FindRecord ( + 'stream_baseline_xref', + ['stream', 'baseline', 'pvob'], + [$stream, $baseline, $pvob] + ); +} # FindStreamBaselineXref + +sub AddBaselineActivityXref ($) { + my ($self, $data) = @_; + + return $self->AddRecord ( + 'baseline_activity_xref', + ['baseline', 'activity', 'pvob'], + $data + ); +} # AddBaselineActivityXref + +sub DeleteBaselineActivityXref ($$$) { + my ($self, $baseline, $activity, $pvob) = @_; + + return $self->DeleteRecord ( + 'baseline_activity_xref', + ['baseline', 'activity', 'pvob'], + [$baseline, $activity, $pvob], + ); +} # DeleteBaselineActivityXref + +sub UpdateBaselineActivityXref ($$$$) { + my ($self, $baseline, $activity, $pvob, $update) = @_; + + return $self->UpdateRecord ( + 'baseline_activity_xref', + ['baseline', 'activity', 'pvob'], + [$baseline, $activity, $pvob], + $update + ); +} # UpdateBaselineActivityXref + +sub GetBaselineActivityXref ($$$$) { + my ($self, $baseline, $activity, $pvob) = @_; + + return $self->GetRecord ( + 'baseline_activity_xref', + ['baseline', 'activity', 'pvob'], + [$baseline, $activity, $pvob], + ); +} # GetBaselineActivityXref + +sub FindBaselineActivityXref (;$$$$) { + my ($self, $baseline, $activity, $pvob) = @_; + + return $self->FindRecord ( + 'baseline_activity_xref', + ['baseline', 'activity', 'pvob'], + [$baseline, $activity, $pvob] + ); +} # FindBaselineActivityXref + +sub FindActivities ($$$) { + my ($self, $pvob, $stream, $element) = @_; + + my $statement = <<"END"; +select + aex.activity +from + changeset as cs, + stream_activity_xref as sax +where + cs.pvob = sax.pvob and + cs.activity = sax.activity and + cs.pvob = '$pvob' and + sax.stream = '$stream' and + cs.element like '$element%' +group by + cs.activity +END + + my $sth = $self->{db}->prepare ($statement); + + my ($err, $msg); + + unless ($sth) { + ($err, $msg) = $self->_dberror ('Unable to prepare statement', $statement); + + croak $msg; + } # if + + my $status = $sth->execute; + + unless ($status) { + ($err, $msg) = $self->_dberror ('Unable to execute statement', $statement); + + croak $msg; + } # if + + my @records; + + while (my $row = $sth->fetchrow_hashref) { + push @records, $row; + } # while + + return @records; +} # FindActivities + +1; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + DateUtils + Display + GetConfig + +=end man + +=begin html + +
+DateUtils
+Display
+GetConfig
+
+ +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this module + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2011, ClearSCM, Inc. All rights reserved. + +=cut diff --git a/CCDB/lib/CCDBService.pm b/CCDB/lib/CCDBService.pm new file mode 100644 index 0000000..33985f0 --- /dev/null +++ b/CCDB/lib/CCDBService.pm @@ -0,0 +1,680 @@ +=pod + +=head1 NAME $RCSfile: CCDBService.pm,v $ + +CCDBService - ClearCase DataBase Service + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.6 $ + +=item Created + +Fri Mar 11 15:37:34 PST 2011 + +=item Modified + +$Date: 2011/05/05 18:41:44 $ + +=back + +=head1 SYNOPSIS + +Provides an interface to the CCDB object over the netwok. This is useful as +neither ccperl nor cqperl have DBI installed so if clients want to talk to an +SQL database such as MySQL they generally can't. + +This library implements both the daemon portion of the server and the client +API. + +=head1 DESCRIPTION + +This client/server process (ccdbc and ccdbd) serves only an informational +purpose. By that I mean the client can request information as described below +but it cannot request to add/delete or update information. In other words the +client has read only access. + +The caller makes requests in the form of: + + + +Different methods will return different values. See CCDB.pm. + +=head1 ROUTINES + +The following methods are available: + +=cut + +package CCDBService; + +use strict; +use warnings; + +use Carp; +use FindBin; +use IO::Socket; +use Net::hostent; +use POSIX ":sys_wait_h"; + +use lib "$FindBin::Bin/../../lib"; + +use DateUtils; +use Display; +use GetConfig; + +# Seed options from config file +our %OPTS = GetConfig ("$FindBin::Bin/../etc/ccdbservice.conf"); + +our $VERSION = '$Revision: 1.6 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +# Override options if in the environment +$OPTS{CCDB_HOST} = $ENV{CCDB_HOST} + if $ENV{CCDB_HOST}; +$OPTS{CCDB_PORT} = $ENV{CCDB_PORT} + if $ENV{CCDB_PORT}; +$OPTS{CCDB_MULTITHREADED} = $ENV{CCDB_MULTITHREADED} + if $ENV{CCDB_MULTITHREADED}; + +sub new () { + my ($class) = @_; + + my $ccdbservice = bless {}, $class; + + $ccdbservice->{multithreaded} = $OPTS{CCDB_MULTITHREADED}; + + return $ccdbservice; +} # new + +sub _tag ($) { + my ($self, $msg) = @_; + + my $tag = YMDHMS; + $tag .= ' '; + $tag .= $self->{pid} ? "[$self->{pid}] " : ''; + + return "$tag$msg"; +} # _tag + +sub _verbose ($) { + my ($self, $msg) = @_; + + verbose $self->_tag ($msg); + + return; +} # _verbose + +sub _debug ($) { + my ($self, $msg) = @_; + + debug $self->_tag ($msg); + + return; +} # _debug + +sub _log ($) { + my ($self, $msg) = @_; + + display $self->_tag ($msg); + + return; +} # log + +sub _funeral () { + debug 'Entered _funeral'; + + while (my $childpid = waitpid (-1, WNOHANG) > 0) { + my $status = $?; + + debug "childpid: $childpid - status: $status"; + + if ($childpid != -1) { + local $SIG{CHLD} = \&_funeral; + + my $msg = 'Child has died'; + $msg .= $status ? " with status $status" : ''; + + verbose "[$childpid] $msg" + if $status; + } else { + debug "All children reaped"; + } # if + } # while + + return; +} # _funeral + +sub _endServer () { + display "CCDBService V$VERSION shutdown at " . localtime; + + # Kill process group + kill 'TERM', -$$; + + # Wait for all children to die + while (wait != -1) { + # do nothing + } # while + + # Now that we are alone, we can simply exit + exit; +} # _endServer + +sub _restartServer () { + # Not sure what to do on a restart server + display 'Entered _restartServer'; + + return; +} # _restartServer + +sub setMultithreaded ($) { + my ($self, $value) = @_; + + my $oldValue = $self->{multithreaded}; + + $self->{multithreaded} = $value; + + return $oldValue; +} # setMultithreaded + +sub getMultithreaded () { + my ($self) = @_; + + return $self->{multithreaded}; +} # getMultithreaded + +sub connectToServer (;$$) { + my ($self, $host, $port) = @_; + + $host ||= $OPTS{CCDB_HOST}; + $port ||= $OPTS{CCDB_PORT}; + + $self->{socket} = IO::Socket::INET->new ( + Proto => 'tcp', + PeerAddr => $host, + PeerPort => $port, + ); + + return unless $self->{socket}; + + $self->{socket}->autoflush + if $self->{socket}; + + $self->{host} = $host; + $self->{port} = $port; + + if ($self->{socket}) { + return 1; + } else { + return; + } # if + + return; +} # connectToServer + +sub disconnectFromServer () { + my ($self) = @_; + + undef $self->{socket}; + + return; +} # disconnectFromServer + +sub _serviceClient ($$) { + my ($self, $host, $client) = @_; + + $self->_verbose ("Serving requests from $host"); + + # Set autoflush for client + $client->autoflush + if $client; + + my $ccdb = CCDB->new; + + while () { + # Read command from client + my $cmd = <$client>; + + last unless $cmd; + + chomp $cmd; + + next if $cmd eq ''; + + last if $cmd =~ /^quit|^exit/i; + + $self->_debug ("$host wants us to do $cmd"); + + my $status = 0; + my ($method, $rec, @keys, @values); + + if ($cmd =~ /stopserver/i) { + if ($self->{server}) { + $self->_verbose ("$host requested to stop server [$self->{server}]"); + + # Send server hangup signal + kill 'HUP', $self->{server}; + } else { + $self->_verbose ('Shutting down server'); + + print $client "CCDBService Status: 0\n"; + + exit; + } # if + + $self->_debug ("Returning 0, undef"); + } else { + # Parse command + @values = split /[^\S]+/, $cmd; + + if (@values < 2) { + print $client "ERROR: I don't understand the command: $cmd\n"; + print $client "Request must be of the form: \n"; + print $client "CCDB Status: 1\n"; + next; + } # if + + $method = shift @values; + + my $values = join ' ', @values; + + unless ( + $method =~ /^get/i + or $method =~ /^find/i + or $method =~ /^add/i + or $method =~ /^delete/i + or $method =~ /^update/i) { + print $client "I only understand get, find, add, delete and "; + print $client "update operations "; + print $client "- not '$method'\n"; + print $client "CCDB Status: 1\n"; + next; + } # unless + + $self->_debug ("Executing CCDB::$method"); + + my (%rec, @recs); + + if ($method =~ /^get/i) { + eval { + %rec = $ccdb->$method (@values); + }; # eval + + if ($@) { + print $client "$@\n"; + print $client "CCDB Status: 1\n"; + next; + } else { + $rec = \%rec; + } # if + } elsif ($method =~ /^find/i) { + eval { + @recs = $ccdb->$method (@values); + }; # eval + + if ($@) { + print $client "$@\n"; + print $client "CCDB Status: 1\n"; + next; + } else { + $rec = \@recs; + } # if + } elsif ($method =~ /^add/i) { + my ($err, $msg); + + eval { + ($err, $msg) = $ccdb->$method ($values); + }; # eval + + if ($@) { + print $client "$@\n"; + print $client "CCDB Status: 1\n"; + next; + } else { + $msg = "Success" + if $msg eq ''; + $rec = "Err:$err;Msg:$msg"; + } # if + } elsif ($method =~ /^update/i) { + # Updates are tricky because there is an unknown number of parms then + # a hash. We will look for $VAR1 in the @values array and if we find + # that then that is the start of the hash. + my @parms; + + # Since we're gonna shift off of @values we don't want to use $#values + # in the for loop because it's value is dynamic and will change. + my $valuesSize = $#values; + + # Shift off each parm into @parms until we find $VAR1 + for (my $i = 0; $i < $valuesSize; $i++) { + last if $values[0] =~ /^\$VAR1/; + + push @parms, shift @values; + } # for + + # Now just join the rest of the @values together + push @parms, join ' ', @values; + + my ($err, $msg); + + eval { + ($err, $msg) = $ccdb->$method (@parms); + }; # eval + + if ($@) { + print $client "$@\n"; + print $client "CCDB Status: 1\n"; + next; + } else { + $msg = "Success" + if $msg eq ''; + $rec = "Err:$err;Msg:$msg"; + } # if + } elsif ($method =~ /^delete/i) { + my ($err, $msg); + + eval { + ($err, $msg) = $ccdb->$method (@values); + }; # eval + + if ($@) { + print $client "$@\n"; + print $client "CCDB Status: 1\n"; + next; + } else { + # A little messy here. Normally a delete method returns the number of + # records deleted as its status. But the caller will sense non-zero as + # an error. So if the $msg simply says 'Records deleted' then we flip + # the $err to 0. + $err = 0 + if $msg eq 'Records deleted'; + + $rec = "Err:$err;Msg:$msg"; + } # if + } # if + } # if + + if (ref $rec eq 'HASH') { + if (%$rec) { + foreach (keys %$rec) { + $self->_debug ("Get: Found record"); + + my $data = "$_~"; + $data .= $$rec{$_} ? $$rec{$_} : ''; + + print $client "$data\n"; + } # foreach + + print $client "CCDB Status: 0\n"; + } else { + $self->_debug ("Get: No record found"); + + print $client "CCDB::$method: No record found\n"; + print $client "CCDB Status: 1\n"; + } # if + } elsif (ref $rec eq 'ARRAY') { + if (@$rec > 0) { + $self->_debug ("Find: Records found: " . scalar @$rec); + + foreach my $entry (@$rec) { + my %rec = %$entry; + + print $client '-' x 80 . "\n"; + + foreach (keys %rec) { + my $data = "$_~"; + $data .= $rec{$_} ? $rec{$_} : ''; + + print $client "$data\n"; + } # foreach + } # foreach + + print $client '=' x 80 . "\n"; + print $client "CCDB Status: 0\n"; + } else { + $self->_debug ("Find: Records not found"); + + print $client "CCDB::$method: No records found\n"; + print $client "CCDB Status: 1\n"; + } # if + } elsif (ref \$rec eq 'SCALAR') { + my ($err, $msg); + + if ($rec =~ /Err:(-*\d+);Msg:(.*)/ms) { + $err = $1; + $msg = $2; + } # if + + print $client "$msg\n" + if $msg; + print $client "CCDB Status: $err\n"; + } # if + + $self->_debug ("Looping around for next command"); + } # while + + close $client; + + $self->_verbose ("Serviced requests from $host"); + + return; +} # _serviceClient + +sub execute ($) { + my ($self, $request) = @_; + + return (-1, 'Unable to talk to server') + unless $self->{socket}; + + my ($status, @output) = (-1, ()); + + my $server = $self->{socket}; + + print $server "$request\n"; + + my $response; + + while (defined ($response = <$server>)) { + if ($response =~ /CCDB Status: (-*\d+)/) { + $status = $1; + last; + } # if + + push @output, $response; + } # while + + chomp @output; + + my (@recs, $output); + + return ($status, \@output) + if $status; + + if ($output[0] eq '-' x 80) { + shift @output; + + while ($_ = shift @output) { + last if $_ eq '=' x 80; + + my %rec; + + while ($_) { + last if $_ eq '-' x 80; + + if (/^(\S+)~(.*)$/) { + $rec{$1} = $2; + } # if + + $_ = shift @output; + } # while + + push @recs, \%rec; + } # while + + $output = \@recs; + } else { + my %rec; + + foreach (@output) { + if (/^(\S+):(.*)$/) { + $rec{$1} = $2; + } # if + } # foreach + + $output = \%rec; + } # if + + return ($status, $output); +} # execute + +sub startServer (;$) { + my ($self, $port) = @_; + + $port ||= $OPTS{CCDB_PORT}; + + # Create new socket to communicate to clients with + $self->{socket} = IO::Socket::INET->new( + Proto => 'tcp', + LocalPort => $port, + Listen => SOMAXCONN, + Reuse => 1 + ); + + error "Could not create socket - $!", 1 + unless $self->{socket}; + + # Announce ourselves + $self->_log ("Clearexec V$VERSION accepting clients at " . localtime); + + # Now wait for an incoming request + LOOP: + my $client; + + while ($client = $self->{socket}->accept) { + my $hostinfo = gethostbyaddr $client->peeraddr; + my $host = $hostinfo ? $hostinfo->name : $client->peerhost; + + $self->_verbose ("$host is requesting service"); + + if ($self->getMultithreaded) { + $self->{server} = $$; + + my $childpid; + + $self->_debug ("Spawning child to handle request"); + + error "Can't fork: $!" + unless defined ($childpid = fork); + + if ($childpid) { + $self->{pid} = $$; + + $SIG{CHLD} = \&_funeral; + $SIG{HUP} = \&_endServer; + $SIG{USR2} = \&_restartServer; + + $self->_debug ("Parent produced child [$childpid]"); + } else { + # In child process - ServiceClient + $self->{pid} = $$; + + $self->_debug ("Calling _serviceClient"); + $self->_serviceClient ($host, $client); + $self->_debug ("Returned from _serviceClient - exiting..."); + + exit; + } # if + } else { + $self->_serviceClient ($host, $client); + } # if + } # while + + # This works but I really don't like it. The parent should have looped back to + # the while statement thus waiting for the next client. But it doesn't seem to + # do that. Instead, when multithreaded, the child exits above and then the + # parent breaks out of the while loop. I'm not sure why this is happening. + # This goto fixes this up but it's sooooo ugly! + goto LOOP; +} # startServer + +1; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + DateUtils + Display + GetConfig + +=end man + +=begin html + +
+DateUtils
+Display
+GetConf
+
+ +=end html + +=head1 SEE ALSO + +=begin man + +See also: CCDB + +=end man + +=begin html + +
+CCDB
+
+ +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this module. + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2011, ClearSCM, Inc. All rights reserved. + +=cut diff --git a/CCDB/mspb.pl b/CCDB/mspb.pl new file mode 100644 index 0000000..498461d --- /dev/null +++ b/CCDB/mspb.pl @@ -0,0 +1,852 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: mspb.pl,v $ + +MultiSite PlayBack: This script updates the CCDB database by playing back +multisite transcations. + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.2 $ + +=item Created: + +Fri Mar 11 19:09:52 PST 2011 + +=item Modified: + +$Date: 2011/05/05 18:39:56 $ + +=back + +=head1 SYNOPSIS + + Usage mspb.pl: [-u|sage] [-ve|rbose] [-deb|ug] [-vo|b ] + + Where: + -u|sage: Displays usage + + -ve|rbose: Be verbose + -deb|ug: Output debug messages + + -vo|b : Vob to process (Default: All vobs) + +=head1 DESCRIPTION + +This script updates the CCDB database with Clearcase UCM meta data by playing +back multisite transactions. + +If no parameters are specified then mspb attempts to replay all transactions +from all vobs listed in CCDB. To add a new vob use -vob. Epoch numbers are kept +in CCDB to keep track of the last oplog operation that had been played back for +the vob. + +Note that only certain transactions are played back, those that correspond to +actions important to the metadata kept in CCDB. Also, if a transaction fails, +i.e. the add or deletion of a record fails, then the error is silently ignored. +This allows you to playback transactions without worry that replaying an +already played transcation will cause the data to become out of sync. This is +much like multisite's syncreplica itself. + +=cut + +use strict; +use warnings; + +use FindBin; +use Getopt::Long; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use CCDB; +use Clearcase; +use Clearcase::Element; +use Clearcase::UCM::Activity; +use Clearcase::Vob; +use DateUtils; +use Display; +use Logger; +use TimeUtils; +use Utils; + +my $VERSION = '$Revision: 1.2 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +my (%opts, %totals, $log); + +my $ccdb = CCDB->new; + +sub ParseOplog ($) { + my ($oplog) = @_; + + my %record; + + while (<$oplog>) { + last if /^$/; + + if (/(\S+)= (.*)/) { + my $key = $1; + my $value = $2; + + # Special casing op_time. For some odd reason we have more than one value + # on a single line. One case of this is where the keyword is "op_time". + # We've seen lines like this: + # + # op_time= 2008-10-15T18:48:39Z create_time= 2008-10-15T18:48:39Z + # + # So by now $value = '2008-10-15T18:48:39Z create_time= 2008-10-15T18:48:39Z' + # so we'll split it based on ' '. + if ($key eq 'op_time') { + # Note: 2 spaces! + ($value, $_) = split / /, $value; + + # Set op_time + $record{$key} = $value; + + # Now parse $create time + if (/(\S+)= (.*)/) { + $key = $1; + $value = $2; + } # if + } # if + + # Some values are wrapped in quotes + if ($value =~ /(\'|\")(.*)(\'|\")/) { + $value = $2; + } # if + + # If a key occurs multiple times then make its value an array + if ($record{$key}) { + if (ref $record{$key} eq 'ARRAY') { + push @{$record{$key}}, $value; + } else { + $record{$key} = [ $record{$key}, $value]; + } # if + } else { + $record{$key} = $value; + } # if + } # if + } # while + + return %record; +} # ParseOplog + +sub GetSubmittedDate ($$) { + my ($activity, $pvob) = @_; + + $pvob = Clearcase::vobtag $pvob; + + my $cmd = "describe -fmt \"%Na\" activity:$activity\@$pvob"; + + my ($status, @output) = $Clearcase::CC->execute ($cmd); + + unless ($status) { + if ($output[0]) { + foreach (split / /, $output[0]) { + if (/(\w+)=(.+)/) { + if ($1 =~ /submit_time/i) { + my ($year, $mon, $mday, $hour, $min, $sec) = ymdhms $2; + return "$year-$mon-$mday $hour:$min:$sec"; + } # if + } # if + } # foreach + } # if + } # unless + + return; +} # GetSubmittedDate + +sub AddActivity ($%) { + my ($pvob, %oplog) = @_; + + my ($cmd, $err, $status, $msg, @output, %existingRec); + + $totals{'Activities processed'}++; + + undef %existingRec; + + # Add an activity (if not already existing) + %existingRec = $ccdb->GetActivity ($oplog{name_p}, $pvob); + + unless (%existingRec) { + my $submitted = GetSubmittedDate $oplog{name_p}, $pvob; + + my $cmd = "describe -fmt \"%[owner]p\" activity:$oplog{name_p}\@" + . Clearcase::vobtag $pvob; + + my ($status, @output) = $Clearcase::CC->execute ($cmd); + + ($err, $msg) = $ccdb->AddActivity ({ + oid => $oplog{activity_oid}, + name => $oplog{name_p}, + pvob => $pvob, + owner => $output[0], + submitted => $submitted, + }); + + return ($err, $msg) + if $err; + + $totals{'Activities added'}++; + } # unless + + # Add a stream (if not already existing) + $cmd = "lsactivity -fmt \"%[stream]p\" $oplog{name_p}\@" + . Clearcase::vobtag $pvob; + + ($status, @output) = $Clearcase::CC->execute ($cmd); + + if ($status) { + # There are times when an activity is subsequently deleted. Since we are + # playing back Multisite transactions we will see the mkactivity first, then + # later see a corresponding rmactivity. Since Multisite has already played + # all of these transactions to Clearcase itself the activity is gone - and + # therefore the lsactivity will fail to find the stream. If that's the case + # then just return. If not then issue a warning. + unless ($output[0] =~ /activity not found/) { + $log->warn ("Can't find stream for activity:$oplog{name_p}\@" + . Clearcase::vobtag $pvob + ); + } # unless + + return (1, "Unable to execute command: $cmd (Status: $status)\n" + . join ("\n", @output)) + } # if + + undef %existingRec; + + %existingRec = $ccdb->GetStream ($output[0], $pvob); + + unless (%existingRec) { + ($err, $msg) = $ccdb->AddStream ({ + name => $output[0], + pvob => $pvob + }); + + if ($err) { + $log->warn ("Unable to add stream:$output[0]\@" + . Clearcase::vobtag $pvob + . " (Error: $err)\n$msg"); + + return ($err, $msg); + } # if + + $totals{'Streams added'}++; + } # unless + + undef %existingRec; + + # Link them (if not already linked) + %existingRec = $ccdb->GetStreamActivityXref ( + $output[0], + $oplog{name_p}, + $pvob + ); + + unless (%existingRec) { + ($err, $msg) = $ccdb->AddStreamActivityXref ({ + stream => $output[0], + activity => $oplog{name_p}, + pvob => $pvob, + }); + + if ($err) { + $log->warn ("Unable to add stream_activity_xref:$output[0]\@" + . Clearcase::vobtag $pvob + . " activity:$oplog{name_p} (Error: $err)\n$msg"); + + return ($err, $msg); + } # if + + $totals{'Stream/Activity Xrefs added'}++; + } # unless + + return; +} # AddActivity + +sub AddStream ($%) { + my ($pvob, %oplog) = @_; + + my ($err, $msg); + + $totals{'Streams processed'}++; + + # Add a stream (if not already existing) + my %existingRec = $ccdb->GetStream ($oplog{name_p}, $pvob); + + unless (%existingRec) { + my $pvobTag = Clearcase::vobtag $pvob; + my $cmd = "lsstream -fmt \"%[project]p\" $oplog{name_p}\@$pvobTag"; + + my ($status, @output) = $Clearcase::CC->execute ($cmd); + + if ($status) { + $log->err ("Unable to execute command: $cmd (Status: $status)" + . join ("\n", @output)); + return ($status, join ("\n", @output)); + } # if + + ($err, $msg) = $ccdb->AddStream ({ + oid => $oplog{activity_oid}, + name => $oplog{name_p}, + project => $output[0], + pvob => $pvob, + }); + + unless ($err) { + $totals{'Streams added'}++; + } # unless + } # unless + + return ($err, $msg); +} # AddStream + +sub ProcessActivity ($$%) { + my ($operation, $pvob, %oplog) = @_; + + # Many operations in Multisite's oplog have an op of mkactivity but are + # actually operations on other objects based on actype_oid. The following are + # actype_oid values: + my @validActypes = ( + 'activity', + 'folder', + 'project', + 'stream', + 'timeline', + 'internal', + ); + + # We only handle activity and stream here + my $actype; + + if ($oplog{actype_oid}) { + $actype = $Clearcase::CC->oid2name ($oplog{actype_oid}, $pvob); + } else { + if ($operation eq 'rmactivity' and $oplog{comment}) { + $actype = 'activity'; + } else { + return; + } # if + } # if + + my ($err, $msg); + + if ($operation eq 'mkactivity') { + if ($actype eq 'activity') { + AddActivity $pvob, %oplog; + } elsif ($actype eq 'stream') { + AddStream $pvob, %oplog; + } # if + } elsif ($operation eq 'rmactivity') { + if ($actype eq 'activity') { + # For rmactivity there's nothing but the comment to go on to get the + # activity's name. The comment must be of a format of "Destroyed activity + # "@"." complete with nested double quotes. + my ($activity, $pvob); + + # Note: There are rmactivity's that lack the comment! Nothing we can do + # with these except to ignore them! + return + unless $oplog{comment}; + + # Note: is a vob tag of the variety of the client. So, for example, + # it can be a Windows style pvob (e.g. \\pvob) even though we are running + # on a Linux machine where the pvob needs to be /vob/pvob! + if ($oplog{comment} =~ /Destroyed activity \"activity:(\S+)\@(\S+)\"/) { + $activity = $1; + $pvob = Clearcase::vobname ($2); + } # if + + return + unless ($activity or $pvob); + + $totals{'Activities processed'}++; + $totals{'Activities deleted'}++; + + return $ccdb->DeleteActivity ($activity, $pvob); + } elsif ($actype eq 'stream') { + # Note: I have yet to see an rmactivity stream with even an actype_oid! + $totals{'Streams processed'}++; + $totals{'Streams deleted'}++; + + return $ccdb->DeleteStreamOID ($oplog{activity_oid}); + } # if + } # if + + return; +} # ProcessActivity + +sub ProcessBaseline ($$%) { + my ($operation, $pvob, %oplog) = @_; + + my ($cmd, $err, $status, $msg, @output, %existingRec); + + my $pvobTag = Clearcase::vobtag $pvob; + + $totals{'Baselines processed'}++; + + if ($operation eq 'mkcheckpoint') { + undef %existingRec; + + # Add an activity (if not already existing) + %existingRec = $ccdb->GetBaseline ($oplog{name_p}, $pvob); + + unless (%existingRec) { + ($err, $msg) = $ccdb->AddBaseline ({ + oid => $oplog{checkpoint_oid}, + name => $oplog{name_p}, + pvob => $pvob, + }); + + return ($err, $msg) + if $err; + + $totals{'Baselines added'}++; + } # unless + + # Add a stream_baseline_xref entry + $cmd = "lsbl -fmt \"%[bl_stream]p\" $oplog{name_p}\@$pvobTag"; + + ($status, @output) = $Clearcase::CC->execute ($cmd); + + if ($status) { + $log->err ("Unable to execute command: $cmd (Status: $status)" + . join ("\n", @output)); + return; + } # if + + ($err, $msg) = $ccdb->AddStreamBaselineXref ({ + stream => $output[0], + baseline => $oplog{name_p}, + pvob => $pvob, + }); + + return ($err, $msg) + if $err; + + $totals{'Stream/Baseline Xrefs added'}++; + + return + unless $oplog{activity_oid}; + + # Loop through activities + my @activities = ref $oplog{activity_oid} eq 'ARRAY' + ? @{$oplog{activity_oid}} + : ($oplog{activity_oid}); + + foreach (@activities) { + my $activity = $Clearcase::CC->oid2name ($_, $pvob); + + # I think $activity will be blank if after this mkcheckpoint somebody + # did an rmactivity... + next + unless $activity; + + # Check to see if the activity exists + undef %existingRec; + + %existingRec = $ccdb->GetActivity ($activity, $pvob); + + unless (%existingRec) { + ($err, $msg) = $ccdb->AddActivity ({ + name => $activity, + pvob => $pvob, + }); + + return ($err, $msg) + if $err; + } # unless + + # Link them (if not already linked) + %existingRec = $ccdb->GetBaselineActivityXref ( + $oplog{name_p}, $activity, $pvob + ); + + unless (%existingRec) { + ($err, $msg) = $ccdb->AddBaselineActivityXref ({ + baseline => $oplog{name_p}, + activity => $activity, + pvob => $pvob, + }); + + if ($err) { + $log->warn ("Unable to add baseline_activity_xref:$output[0]\@" + . "$pvobTag baseline:$oplog{name_p} activity:$_ (Error:" + . "$err)\n$msg"); + + return ($err, $msg); + } # if + + $totals{'Baseline/Activity Xrefs added'}++; + } # unless + } # foreach + } elsif ($operation eq 'rmcheckpoint') { + $totals{'Baselines deleted'}++; + + return $ccdb->DeleteBaselineOID ($oplog{checkpoint_oid}); + } # if + + return; +} # ProcessBaseline + +sub ProcessElement ($$%) { + my ($operation, $vob, %oplog) = @_; + + return + unless $oplog{version_oid}; + + my $elementVersion = $Clearcase::CC->oid2name ($oplog{version_oid}, $vob); + my ($element, $version) = split /$Clearcase::SFX/, $elementVersion; + + # Remove VOBTAG_PREFIX from $element + $element = '/' . Clearcase::vobname $element; + + my $cmd = "describe -fmt \"%[activity]Xp\" oid:$oplog{version_oid}\@" + . Clearcase::vobtag $vob; + + my ($status, @output) = $Clearcase::CC->execute ($cmd); + + if ($status) { + $log->err ("Unable to execute command: $cmd (Status: $status)" + . join ("\n", @output)); + return; + } # if + + # If this operation is not attached to an activity then we're not interested. + return + unless $output[0]; + + my ($activity, $pvob) = split /\@/, $output[0]; + + # Remove leading "activity:" + $activity = substr $activity, 9; + + # Fix $pvob + $pvob = Clearcase::vobname $pvob; + + my ($err, $msg, %existingRec); + + if ($operation eq 'checkin' + or $operation eq 'checkout') { + %existingRec = $ccdb->GetChangeset ($activity, $element, $version, $pvob); + + unless (%existingRec) { + my $create_time = $oplog{create_time}; + + # Create time from Multisite are of the format: 2008-10-15T18:48:39Z + $create_time =~ s/T/ /; + $create_time =~ s/Z//; + + ($err, $msg) = $ccdb->AddChangeset ({ + activity => $activity, + element => $element, + version => $version, + pvob => $pvob, + created => $create_time, + }); + + if ($err) { + $log->err ("Unable to AddChangeset ($activity, $element, $version, " + . "$pvob)\n$msg"); + + return ($err, $msg); + } # if + + # Update Activity's submitted field (if this create time gt submitted) + my %activity = $ccdb->GetActivity ($activity, $pvob); + + if (%activity) { + $activity{submitted} ||= $create_time; + + if ($create_time ge $activity{submitted}) { + $activity{submitted} = $create_time; + + my ($err, $msg) = $ccdb->UpdateActivity ( + $activity, + $pvob, + \%activity, + ); + + $log->err ("Unable to update activity: $activity pvob: $pvob - " + . " submitted: $create_time") + if $err; + } # if + } # if + + $totals{'Changesets added'}++; + } # unless + } elsif ($operation eq 'uncheckout' + or $operation eq 'rmver') { + %existingRec = $ccdb->GetChangeset ($activity, $element, $version, $pvob); + + if (%existingRec) { + ($err, $msg) = $ccdb->DeleteChangeset ( + $activity, + $element, + $version, + $pvob, + ); + + if ($err) { + $log->err ("Unable to DeleteChangeset ($activity, $element, $version, " + . "$pvob)\n$msg"); + + return ($err, $msg); + } # if + + $totals{'Changesets deleted'}++; + } # if + } elsif ($operation eq 'rmelem') { + %existingRec = $ccdb->GetChangeset ($activity, $element, $version, $pvob); + + if (%existingRec) { + ($err, $msg) = $ccdb->DeleteElementAll ($element); + + if ($err) { + $log->err ("Unable to DeleteElementAll ($element)\n$msg"); + + return ($err, $msg); + } # if + + $totals{'Elements removed'}++; + } # if + } # if + + return; +} # ProcessElement + +sub ProcessRename ($%) { + my ($vob, %oplog) = @_; + + return + unless $oplog{comment}; + + my $object; + + # Parse comment to find what got renamed and the from and to names + if ($oplog{comment} =~ /Changed name of (.+?) from \"(\S+)\" to \"(\S+)\"/) { + $object = $1; + my $from = $2; + my $to = $3; + + # Only interested in these objects + return + unless $object =~ /activity/i or + $object =~ /baseline/i or + $object =~ /stream/i; + + my %update = ( + name => $to, + ); + + my $method = 'Update' . ucfirst $object; + + my ($err, $str) = $ccdb->$method ($from, $vob, \%update); + + if ($err) { + $log->err ("Unable to rename $object from $from -> $to (pvob:$vob"); + + return; + } # if; + } # if + + if ($object eq 'activity') { + $totals{'Activities renamed'}++; + } elsif ($object eq 'baseline') { + $totals{'Baselines renamed'}++; + } elsif ($object eq 'stream') { + $totals{'Streams renamed'}++; + } # if + + return; +} # ProcessRename + +sub ProcessOperation ($$%) { + my ($operation, $vob, %oplog) = @_; + + # For now let's only process the activity opcodes... We'll add more later. + my @interestingOpcodes = ( + 'checkin', + 'checkout', + 'mkactivity', + 'mkcheckpoint', + 'rename', + 'rmactivity', + 'rmcheckpoint', + 'rmelem', + 'rmver', + 'uncheckout', +# 'mkattr', +# 'mkhlink', +# 'setpvar', + ); + + return + unless InArray $operation, @interestingOpcodes; + + if ($operation eq 'mkactivity' + or $operation eq 'rmactivity') { + return ProcessActivity ($operation, $vob, %oplog); + } elsif ($operation eq 'mkcheckpoint' + or $operation eq 'rmcheckpoint') { + return ProcessBaseline ($operation, $vob, %oplog); + } elsif ($operation eq 'checkin' + or $operation eq 'checkout' + or $operation eq 'rmelem' + or $operation eq 'rmver' + or $operation eq 'uncheckout') { + return ProcessElement ($operation, $vob, %oplog); + } elsif ($operation eq 'rename') { + return ProcessRename ($vob, %oplog); + } # if +} # ProcessOperation + +sub ProcessOplog (%) { + my (%vob) = @_; + + # Start dumpoplog off at the appropriate oplog number + my $cmd = 'multitool dumpoplog -long -invob ' + . Clearcase::vobtag ($vob{name}) + . " -from $vob{epoch}"; + + # Start a pipe + open my $oplog, "$cmd|" + or error "Cannot execute $cmd", 1; + + my $inRecord; + + while (<$oplog>) { + # Look for the next oplog entry + if (/(\d+):/) { + $vob{epoch} = $1; + $inRecord = 1; + next; + } elsif (/^$/) { + $inRecord = 0; + next; + } elsif (!$inRecord) { + next; + } # if + + my ($operation, $status, @output); + + if (/op= (\S+)/) { + $operation = $1; + } else { + $operation = ''; + } # if + + ProcessOperation $operation, $vob{name}, ParseOplog $oplog; + + # Update vob's last_oplog + my ($err, $msg) = $ccdb->UpdateVob ($vob{name}, \%vob); + + $log->err ("Unable to update vob:$vob{name}\'s epoch to " + . $vob{epoch}) + if $err; + } # while + + close $oplog; + + return; +} # ProcessOplog + +sub ProcessVob ($) { + my ($name) = @_; + + my ($err, $msg); + + my %vob = $ccdb->GetVob ($name); + + $log->msg ("Processing vob:$name ($vob{type})"); + + unless (%vob) { + my $vob = Clearcase::Vob->new (Clearcase::vobtag $name); + + ($err, $msg) = $ccdb->AddVob ({ + name => $name, + type => $vob->vob_registry_attributes !~ /ucmvob/ ? 'base' : 'ucm', + }); + + if ($err) { + $log->err ("Unable to add vob $name (Error: $err)\n$msg"); + } else { + $totals{'Vobs added'}++; + } # if + + %vob = $ccdb->GetVob ($name); + } # unless + + ProcessOplog %vob; +} # ProcessVob + +sub EndProcess { + $totals{Errors} = $log->errors; + $totals{Warnings} = $log->warnings; + + Stats \%totals, $log; +} # EndProcess + +# Main +local $| = 1; + +my $startTime = time; + +GetOptions ( + \%opts, + 'verbose' => sub { set_verbose }, + 'usage' => sub { Usage }, + 'vob=s', +) or Usage "Unknown option"; + +$log = Logger->new; + +$SIG{__DIE__} = $SIG{INT} = $SIG{ABRT} = $SIG{QUIT} = $SIG{USR2} = 'EndProcess'; + +my @vobs; + +if ($opts{vob}) { + push @vobs, $opts{vob}; +} else { + # Do UCM vobs first + my (@ucmvobs, @basevobs); + + push @ucmvobs, $$_{name} + foreach ($ccdb->FindVob ('*', 'ucm')); + + # Add on base vobs + push @basevobs, $$_{name} + foreach ($ccdb->FindVob ('*', 'base')); + + push @vobs, $_ foreach (sort @ucmvobs); + push @vobs, $_ foreach (sort @basevobs); +} # if + +if (@vobs == 1) { + $log->msg ('1 vob to process'); +} else { + $log->msg (scalar @vobs . ' vobs to process'); +} # if + +foreach (@vobs) { + ProcessVob $_; + + $totals{'Vobs processed'}++; +} # foreach + +display_duration $startTime, $log; + +$totals{Errors} = $log->errors; +$totals{Warnings} = $log->warnings; + +Stats \%totals, $log; diff --git a/CCDB/triggers/Activity.pl b/CCDB/triggers/Activity.pl new file mode 100644 index 0000000..ea2f767 --- /dev/null +++ b/CCDB/triggers/Activity.pl @@ -0,0 +1,221 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: Activity.pl,v $ + +This trigger will update CCDB when activities are added or removed. + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.6 $ + +=item Created: + +Fri Mar 11 17:45:57 PST 2011 + +=item Modified: + +$Date: 2011/04/02 00:28:21 $ + +=back + +=head1 DESCRIPTION + +This trigger will update the CCDB when UCM activities are added or removed. It +is implemented as a post operation trigger on the mkactivity and rmactivity +Clearcase operations. It should be attached to all UCM vobs (i.e. pvobs) that +you wish CCDB to monitor. If using mktriggers.pl the trigger defintion is: + + Trigger: CCDB_ACTIVITY + Description: Updates CCDB when activities are made or removed + Type: -element -all + Opkinds: -postop mkactivity,rmactivity,chactivity + ScriptEngine: Perl + Script: Activity.pl + Vobs: ucm + EndTrigger + +=cut + +use strict; +use warnings; + +use FindBin; +use Data::Dumper; + +$Data::Dumper::Indent = 0; + +use lib $FindBin::Bin, "$FindBin::Bin/../lib", "$FindBin::Bin/../../lib"; + +use TriggerUtils; +use CCDBService; + +my $VERSION = '$Revision: 1.6 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +triglog 'Starting trigger'; + +TriggerUtils::dumpenv; + +my ($name, $pvob) = split /\@/, $ENV{CLEARCASE_ACTIVITY}; +my ($stream) = split /\@/, $ENV{CLEARCASE_STREAM}; + +trigdie 'Activity name not known', 1 + unless $name; + +trigdie 'Pvob name not known', 1 + unless $pvob; + +$pvob = vobname $pvob; + +my $CCDBService = CCDBService->new; + +trigdie 'Unable to connect to CCDBService', 1 + unless $CCDBService->connectToServer; + +my ($err, $msg, $request); + +triglog "CLEARCASE_OP_KIND: $ENV{CLEARCASE_OP_KIND}"; + +if ($ENV{CLEARCASE_OP_KIND} eq 'mkactivity') { + my $activity = Dumper { + name => $name, + pvob => $pvob, + type => $name !~ /^(deliver|rebase|integrate|revert|tlmerge)/i + ? 'regular' + : 'integration', + }; + + # Squeeze out extra spaces + $activity =~ s/ = /=/g; + $activity =~ s/ => /=>/g; + + $request = "AddActivity $activity"; + + triglog "Executing request: $request"; + + ($err, $msg) = $CCDBService->execute ($request); + + trigdie "Activity: Unable to execute request: $request\n" + . join ("\n", @$msg), $err + if $err; + + triglog "Success"; + + my $streamActivityXref = Dumper { + stream => $stream, + activity => $name, + pvob => $pvob, + }; + + # Squeeze out extra spaces + $streamActivityXref =~ s/ = /=/g; + $streamActivityXref =~ s/ => /=>/g; + + $request = "AddStreamActivityXref $streamActivityXref" +} elsif ($ENV{CLEARCASE_OP_KIND} eq 'rmactivity') { + # Note: The delete on cascade option in the MySQL database for CCDB should + # handle clean up of any associated records like any stream_activity_xref + # records. + $request = "DeleteActivity $name $pvob"; +} elsif ($ENV{CLEARCASE_OP_KIND} eq 'chactivity') { + # Need to move changeset items from $ENV{CLEARCASE_ACTIVITY} -> + # $ENV{CLEARCASE_TO_ACTIVITY}. I believe we will be called once for each + # element version since it says that CLEARCASE_ID_STR will be set and + # CLEARCASE_ID_STR uniquely identifies an element/version + triglog "Processing chactivity"; + + my ($fromActivity) = split /@/, $ENV{CLEARCASE_ACTIVITY}; + + my ($toActivity) = split /@/, $ENV{CLEARCASE_TO_ACTIVITY}; + + my $update = Dumper { + activity => $toActivity + }; + + # Squeeze out extra spaces + $update =~ s/ = /=/g; + $update =~ s/ => /=>/g; + + my $elementName = $ENV{CLEARCASE_PN}; + $elementName =~ s/\\/\//g; + $elementName = removeViewTag $elementName; + my $version = $ENV{CLEARCASE_ID_STR}; + $version =~ s/\\/\//g; + + $request = "UpdateChangeset $fromActivity $elementName "; + $request .= "$version $pvob $update"; +} # if + +triglog "Executing request: $request"; + +($err, $msg) = $CCDBService->execute ($request); + +trigdie "Activity: Unable to execute request: $request\n" + . join ("\n", @$msg), $err + if $err; + +triglog "Success"; + +$CCDBService->disconnectFromServer; + +triglog 'Ending trigger'; + +exit 0; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + CCDBSerivce + TriggerUtils + +=end man + +=begin html + +
+CCDBService
+TriggerUtils
+
+ +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2011, ClearSCM, Inc. All rights reserved. + +=cut \ No newline at end of file diff --git a/CCDB/triggers/Baseline.pl b/CCDB/triggers/Baseline.pl new file mode 100644 index 0000000..8958f72 --- /dev/null +++ b/CCDB/triggers/Baseline.pl @@ -0,0 +1,249 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: Baseline.pl,v $ + +This trigger will update CCDB when baselines are completed or removed. + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.6 $ + +=item Created: + +Fri Mar 11 17:45:57 PST 2011 + +=item Modified: + +$Date: 2011/04/02 00:29:15 $ + +=back + +=head1 DESCRIPTION + +This trigger will update the CCDB when UCM baselines are completed or removed. +It is implemented as a post operation trigger on the mkbl_complete and rmbl +Clearcase operations. It should be attached to all UCM vobs (i.e. pvobs) that +you wish CCDB to monitor. If using mktriggers.pl the trigger defintion is: + + Trigger: CCDB_BASELINE + Description: Updates CCDB when baselines are completed or removed + Type: -element -all + Opkinds: -postop mkbl_complete,rmbl + ScriptEngine: Perl + Script: Baseline.pl + Vobs: ucm + EndTrigger + +=cut + +use strict; +use warnings; + +use FindBin; +use Data::Dumper; + +$Data::Dumper::Indent = 0; + +use lib $FindBin::Bin, "$FindBin::Bin/../lib", "$FindBin::Bin/../../lib"; + +# I would like to use Clearcase but doing so causes a problem when the trigger +# is run from Clearcase Explorer - something about my use of open3 :-( + +use TriggerUtils; +use CCDBService; + +triglog 'Starting trigger'; + +TriggerUtils::dumpenv; + +my $VERSION = '$Revision: 1.6 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +my $CCDBService = CCDBService->new; + +trigdie 'Unable to connect to CCDBService', 1 + unless $CCDBService->connectToServer; + +my ($err, $msg, $request); + +triglog "CLEARCASE_OP_KIND: $ENV{CLEARCASE_OP_KIND}"; + +foreach (split / /, $ENV{CLEARCASE_BASELINES}) { + my ($name, $pvob) = split /\@/; + + trigdie 'Baseline name not known', 1 + unless $name; + + trigdie 'Pvob name not known', 1 + unless $pvob; + + triglog "Processing Baseline: $name\@$pvob"; + + my $pvobName = vobname $pvob; + + if ($ENV{CLEARCASE_OP_KIND} eq 'mkbl_complete') { + triglog "Hit mkbl_complete!"; + + TriggerUtils::dumpenv; + + my $cmd = "lsbl -fmt \"%[activities]p\" $name\@$pvob"; + + my @output = `cleartool $cmd`; chomp @output; + my $status = $?; + + trigdie "Unable to execute $cmd (Status: $status)\n" + . join ("\n", @output), $status + if $status; + + foreach my $activity (split / /, $output[0]) { + my $baselineActivityXref = Dumper { + baseline => $name, + activity => $activity, + pvob => $pvobName, + }; + + # Squeeze out extra spaces + $baselineActivityXref =~ s/ = /=/g; + $baselineActivityXref =~ s/ => /=>/g; + + $request = "AddBaselineActivityXref $baselineActivityXref"; + + triglog "Executing the request: $request"; + + ($err, $msg) = $CCDBService->execute ($request); + + trigdie "Baseline: Unable to execute request: $request\n" + . join ("\n", @$msg), $err + if $err; + } # foreach + + next; + } elsif ($ENV{CLEARCASE_OP_KIND} eq 'mkbl') { + my $baseline = Dumper { + name => $name, + pvob => $pvobName, + }; + + # Squeeze out extra spaces + $baseline =~ s/ = /=/g; + $baseline =~ s/ => /=>/g; + + $request = "AddBaseline $baseline"; + + triglog "Executing request: $request"; + + ($err, $msg) = $CCDBService->execute ($request); + + trigdie "Unable to execute request: $request\n" + . join ("\n", @$msg), $err + if $err; + + my $cmd = "lsstream -fmt \"%[activities]p\" $ENV{CLEARCASE_STREAM}"; + + my @output = `cleartool $cmd`; chomp @output; + my $status = $?; + + trigdie "Unable to execute $cmd (Status: $status)\n" + . join ("\n", @output), $status + if $status; + + foreach (split / /, $output[0]) { + my $baselineActivityXref = Dumper { + baseline => $name, + activity => $_, + pvob => $pvobName, + }; + + # Squeeze out extra spaces + $baselineActivityXref =~ s/ = /=/g; + $baselineActivityXref =~ s/ => /=>/g; + + $request = "AddBaselineActivityXref $baselineActivityXref"; + + triglog "Executing request: $request"; + + ($err, $msg) = $CCDBService->execute ($request); + + # Just ignore dups + trigdie "Unable to execute request: $request\n" + . join ("\n", @$msg), $err + unless $err == 0 or $err == 1062; + } # foreach + + next; + } elsif ($ENV{CLEARCASE_OP_KIND} eq 'rmbl') { + $request = "DeleteBaseline $name $pvobName"; + } # if + + triglog "Executing request: $request"; + + ($err, $msg) = $CCDBService->execute ($request); + + trigdie "Unable to execute request: $request\n" + . join ("\n", @$msg), $err + if $err; +} # foreach + +$CCDBService->disconnectFromServer; + +triglog 'Ending trigger'; + +exit 0; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + CCDBSerivce + TriggerUtils + +=end man + +=begin html + +
+CCDBService
+TriggerUtils
+
+ +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2011, ClearSCM, Inc. All rights reserved. + +=cut \ No newline at end of file diff --git a/CCDB/triggers/Element.pl b/CCDB/triggers/Element.pl new file mode 100644 index 0000000..83d054c --- /dev/null +++ b/CCDB/triggers/Element.pl @@ -0,0 +1,372 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: Element.pl,v $ + +This trigger will update CCDB when element versions are added or removed or +otherwise changed. The intent of this trigger is to keep CCDB's changeset table +up to date with respect to the element. + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.6 $ + +=item Created: + +Fri Mar 11 17:45:57 PST 2011 + +=item Modified: + +$Date: 2011/04/02 00:34:01 $ + +=back + +=head1 DESCRIPTION + +This trigger will update the CCDB when element versions are added or removed. It +is implemented as a post operation trigger on the checkin, checkout, lnname +and rmelem as well as a pre operation trigger on checkin, uncheckout and rmver. +This is because Clearcase creates a version that contains the string +"CHECKEDOUT" in order to list it in the change set. Thus we add it to CCDB. +However when a check in occurs for this element we need to remove the +"CHECKEDOUT" record and add the newly versioned version. + +Also, lnname is trapped to handle when elments are moved, either through the +cleartool move command or in the odd circumstance of orphaning an element. You +can orphan an element in various ways. For example, if you check out a +directory, add an element to source control (mkelem) then cancel the directory +checkout there is no place for this new element to go! It's orphaned. In such +cases Clearcase will move the element to the vobs lost+found directory, +attaching the element's oid to the end of the element name. + +This trigger should be attached to all UCM component vobs (i.e. vobs that have +UCM components but not pvobs) that you wish CCDB to monitor. If using +mktriggers.pl the triggers defintion are: + + Trigger: CCDB_ELEMENT_PRE + Description: Updates CCDB when an element's version is changed + Type: -element -all + Opkinds: -preop checkin,uncheckout,rmver + ScriptEngine: Perl + Script: Element.pl + Vobs: base + EndTrigger + + Trigger: CCDB_ELEMENT_POST + Description: Updates CCDB when an element's version is changed + Type: -element -all + Opkinds: -postop checkin,checkout,lnname,rmelem + ScriptEngine: Perl + Script: Element.pl + Vobs: base + EndTrigger + +=cut + +use strict; +use warnings; + +use FindBin; +use File::Basename; +use Data::Dumper; + +$Data::Dumper::Indent = 0; + +use lib $FindBin::Bin, "$FindBin::Bin/../lib", "$FindBin::Bin/../../lib"; + +use TriggerUtils; +use CCDBService; + +# I would like to use Clearcase but doing so causes a problem when the trigger +# is run from Clearcase Explorer - something about my use of open3 :-( + +my $VERSION = '$Revision: 1.6 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +triglog 'Starting trigger'; + +my ($activity, $pvob); + +if ($ENV{CLEARCASE_ACTIVITY}) { + ($activity, $pvob) = split /\@/, $ENV{CLEARCASE_ACTIVITY}; + + trigdie 'Activity name not known', 1 + unless $activity; + + trigdie 'Pvob name not known', 1 + unless $pvob; + + $pvob = vobname $pvob; +} # if + +my ($elementName) = + split /$ENV{CLEARCASE_XN_SFX}/, $ENV{CLEARCASE_XPN}; + +my ($cmd, $status, @output, $currVersion, $prevVersion); + +unless ($ENV{CLEARCASE_OP_KIND} eq 'rmelem') { + triglog "Getting current version for $elementName"; + + # Get the current, real version using describe; + $cmd = "describe -fmt \"%Vn\" $elementName"; + + @output = `cleartool $cmd`; chomp @output; + $status = $?; + + trigdie "Unable to execute $cmd (Status: $status)\n" + . join ("\n", @output), $status + if $status; + + $output[0] =~ s/\\/\//g; + + $currVersion = $output[0]; + + triglog "currVersion = $currVersion"; + + triglog "Getting previous version for $elementName"; + + $cmd = "describe -fmt \"%PVn\" $elementName"; + + @output = `cleartool $cmd`; chomp @output; + $status = $?; + + trigdie "Unable to execute $cmd\n" + . join ("\n", @output), $status + if $status; + + $output[0] ||= ''; + + $output[0] =~ s/\\/\//g; + + $prevVersion = $output[0]; + + triglog "prevVersion = $prevVersion"; +} # unless + +# Flip '\' -> '/' +$elementName =~ s/\\/\//g; + +# Remove any trailing '/' or '/.' in $elementName +$elementName =~ s/(.*)\/\.*$/$1/; + +# Collapse any '/./' -> '/' +$elementName =~ s/\/\.\//\//g; + +# Remove VIEWTAG_PREFIX +$elementName = removeViewTag $elementName; + +triglog "elementName: $elementName"; + +my $CCDBService = CCDBService->new; + +trigdie 'Unable to connect to CCDBService', 1 + unless $CCDBService->connectToServer; + +my ($err, $msg, $request); + +triglog "CLEARCASE_OP_KIND: $ENV{CLEARCASE_OP_KIND}"; + +if ($ENV{CLEARCASE_OP_KIND} eq 'checkin' or + $ENV{CLEARCASE_OP_KIND} eq 'checkout') { + triglog "Processing $ENV{CLEARCASE_OP_KIND}"; + + # If checking in a version then we used to have a "CHECKEDOUT" version. We + # need to remove that if found first. Unfortunately a checkin can fail so + # we'll scribble on the filesystem to tell the postop to remove it. + if ($ENV{CLEARCASE_OP_KIND} eq 'checkin' and + $ENV{CLEARCASE_TRTYPE_KIND} eq 'pre-operation') { + exit 0 + if $currVersion !~ /CHECKEDOUT/; + + # Create a file ending in .CHECKEDOUT that indicates the version of the of + # the previously checked out element that we need to remove from the + # database in the postop. However elements can be files or directories. + # For a directory create a ".CHECKEDOUT" file in the directory element. + my $filename = $TriggerUtils::VIEWTAG_PREFIX; + $filename .= "$ENV{CLEARCASE_VIEW_TAG}$elementName"; + $filename .= '/' if -d $filename; + $filename .= '.CHECKEDOUT'; + + open my $file, '>', $filename + or trigdie "Unable to open $filename for writing - $!", 1; + + print $file "$currVersion\n"; + + close $file; + + exit 0; + } else { + # Look for CHECKEDOUT file to indicate we must remove that from the database + my $checkedOutFile = $TriggerUtils::VIEWTAG_PREFIX; + $checkedOutFile .= "$ENV{CLEARCASE_VIEW_TAG}$elementName"; + $checkedOutFile .= '/' if -d $checkedOutFile; + $checkedOutFile .= '.CHECKEDOUT'; + + if (-e $checkedOutFile) { + open my $file, '<', $checkedOutFile + or trigdie "Unable to open $checkedOutFile - $!", 1; + + my $version = <$file>; chomp $version; + + close $file; + + unlink $checkedOutFile; + + $request = "DeleteChangeset $activity $elementName $version $pvob"; + + triglog "Executing request: $request"; + + ($err, $msg) = $CCDBService->execute ($request); + + trigdie "Unable to execute request: $request\n" + . join ("\n", @$msg), $err + if $err; + } # if + + # Add this to the changeset + my $changeset = Dumper { + activity => $activity, + element => $elementName, + version => $currVersion, + pvob => $pvob, + }; + + # Squeeze out extra spaces + $changeset =~ s/ = /=/g; + $changeset =~ s/ => /=>/g; + + $request = "AddChangeset $changeset"; + } # if +} elsif ($ENV{CLEARCASE_OP_KIND} eq 'uncheckout' or + $ENV{CLEARCASE_OP_KIND} eq 'rmver') { + triglog "Processing $ENV{CLEARCASE_OP_KIND}"; + + $request = "DeleteChangeset $activity $elementName $currVersion $pvob"; +} elsif ($ENV{CLEARCASE_OP_KIND} eq 'lnname') { + triglog "Processing $ENV{CLEARCASE_OP_KIND}"; + + # Exit if the previous operation (CLEARCASE_POP_KIND) was not an rmname. The + # user could just be doing an lnname. We want to capture only moves which, by + # definition need to be an rmname followed by an lnname. (What is an lnname + # followed by an rmname?!? The mktrtype man page is confusing on this...) + exit 0 + if $ENV{CLEARCASE_POP_KIND} ne 'rmname'; + + # Surprisingly Clearcase does not set CLEARCASE_ACTIVITY when a move is done + # in a UCM context! This may be because a move in a UCM context can only be + # done within the context of a view set to an activity. So let's get our + # current activity... + my $cmd = 'lsactivity -cact -fmt "%Xn"'; + my @output = `cleartool $cmd`; + my $status = $?; + + trigdie "Unable to execute $cmd (Status: $status)\n" + . join ("\n", @output), $status + if $status; + + my ($activity, $pvob) = split /\@/, $output[0]; + + # Remove 'activity:' from $activity + $activity = substr $activity, 9; + + # Fix $pvob + $pvob = vobname $pvob; + + # Fix $ENV{CLEARCASE_PN2} + my $oldName = $ENV{CLEARCASE_PN2}; + + # Switch "\"'s -> "/"'s + $oldName =~ s/\\/\//g; + + # Remove the viewtag + $oldName = removeViewTag $oldName; + + # Now update CCDB to reflect the move + my $update = Dumper { + element => $elementName, + }; + + # Squeeze out extra spaces + $update =~ s/ = /=/g; + $update =~ s/ => /=>/g; + + triglog "Updating $oldName -> $elementName"; + + $request = "UpdateChangeset $activity $oldName % $pvob $update"; +} elsif ($ENV{CLEARCASE_OP_KIND} eq 'rmelem') { + # If we are doing rmelem then remove all traces of this element + triglog "Processing rmelem"; + + $request = "DeleteElementAll $elementName"; +} # if + +triglog "Executing request: $request"; + +($err, $msg) = $CCDBService->execute ($request); + +trigdie "Unable to execute request: $request\n" + . join ("\n", @$msg), $err + if $err; + +$CCDBService->disconnectFromServer; + +triglog 'Ending trigger'; + +exit 0; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + CCDBSerivce + TriggerUtils + +=end man + +=begin html + +
+CCDBService
+TriggerUtils
+
+ +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2011, ClearSCM, Inc. All rights reserved. + +=cut \ No newline at end of file diff --git a/CCDB/triggers/Stream.pl b/CCDB/triggers/Stream.pl new file mode 100644 index 0000000..877a97f --- /dev/null +++ b/CCDB/triggers/Stream.pl @@ -0,0 +1,206 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: Stream.pl,v $ + +This trigger will update CCDB when streams are added or removed. + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.6 $ + +=item Created: + +Fri Mar 11 17:45:57 PST 2011 + +=item Modified: + +$Date: 2011/03/26 06:24:44 $ + +=back + +=head1 DESCRIPTION + +This trigger will update the CCDB when UCM streams are added or removed. It +is implemented as a post operation trigger on the mkstream and rmstream +Clearcase operations. It should be attached to all UCM vobs (i.e. pvobs) that +you wish CCDB to monitor. If using mktriggers.pl the trigger defintion is: + + Trigger: CCDB_STREAM + Description: Updates CCDB when a stream is made or removed + Type: -element -all + Opkinds: -postop mkstream,rmstream + ScriptEngine: Perl + Script: Stream.pl + Vobs: ucm + EndTrigger + +=cut + +use strict; +use warnings; + +use FindBin; +use Data::Dumper; + +$Data::Dumper::Indent = 0; + +use lib $FindBin::Bin, "$FindBin::Bin/../lib", "$FindBin::Bin/../../lib"; + +use TriggerUtils; +use CCDBService; + +triglog 'Starting trigger'; + +my $VERSION = '$Revision: 1.6 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +# UCM fires the mkstream trigger operation (CLEARCASE_OP_KIND=mkstream) twice, +# once with CLEARCASE_MTYPE set to stream and another time with it set to +# project. The reason for this is to update the project that we now have this +# new stream. Normally we would use this to update a table in CCDB regarding +# the relationship between UCM projects and streams, but we're not tracking that +# so we can simply exit. +exit 0 + if ($ENV{CLEARCASE_MTYPE} and $ENV{CLEARCASE_MTYPE} eq 'project'); + +my ($name, $pvob) = split /\@/, $ENV{CLEARCASE_STREAM}; + +trigdie 'Stream name not known', 1 + unless $name; + +trigdie 'Pvob name not known', 1 + unless $pvob; + +$pvob = vobname $pvob; + +my $CCDBService = CCDBService->new; + +trigdie 'Unable to connect to CCDBService', 1 + unless $CCDBService->connectToServer; + +my ($err, $msg, $request); + +triglog "CLEARCASE_OP_KIND: $ENV{CLEARCASE_OP_KIND}"; + +if ($ENV{CLEARCASE_OP_KIND} eq 'mkstream') { + my $stream = Dumper { + name => $name, + pvob => $pvob + }; + + # Squeeze out extra spaces + $stream =~ s/ = /=/g; + $stream =~ s/ => /=>/g; + + $request = "AddStream $stream"; +} elsif ($ENV{CLEARCASE_OP_KIND} eq 'rmstream') { + $request = "DeleteStream $name $pvob"; +} elsif ($ENV{CLEARCASE_OP_KIND} eq 'deliver_complete' or + $ENV{CLEARCASE_OP_KIND} eq 'rebase_complete') { + # Add $ENV{CLEARCASE_DLV_ACTS} to $ENV{CLEARCASE_BASELINES}. + $ENV{CLEARCASE_DLVR_ACTS} ||= ''; + + foreach (split / /, $ENV{CLEARCASE_DLVR_ACTS}) { + my ($activity) = split /\@/; + + foreach (split / /, $ENV{CLEARCASE_BASELINES}) { + my ($baseline) = split /\@/; + + my $baselineActivityXref = Dumper { + baseline => $baseline, + activity => $activity, + pvob => $pvob, + }; + + # Squeeze out extra spaces + $baselineActivityXref =~ s/ = /=/g; + $baselineActivityXref =~ s/ => /=>/g; + + $request = "AddBaselineActivityXref $baselineActivityXref"; + + triglog "Executing request: $request"; + + ($err, $msg) = $CCDBService->execute ($request); + + # Just ignore dups + trigdie "Unable to execute request: $request\n" + . join ("\n", @$msg), $err + unless $err == 0 or $err == 1062; + } # foreach + } # foreach + + exit 0; +} # if + +triglog "Executing request: $request"; + +($err, $msg) = $CCDBService->execute ($request); + +trigdie "Unable to execute request: $request\n" + . join ("\n", @$msg), $err + if $err; + +$CCDBService->disconnectFromServer; + +triglog 'Ending trigger'; + +exit 0; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + CCDBSerivce + TriggerUtils + +=end man + +=begin html + +
+CCDBService
+TriggerUtils
+
+ +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2011, ClearSCM, Inc. All rights reserved. + +=cut + diff --git a/CCDB/triggers/TriggerUtils.pm b/CCDB/triggers/TriggerUtils.pm new file mode 100644 index 0000000..03e7f86 --- /dev/null +++ b/CCDB/triggers/TriggerUtils.pm @@ -0,0 +1,198 @@ +=pod + +=head1 NAME $RCSfile: TriggerUtils.pm,v $ + +Trigger Utilities + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.3 $ + +=item Created + +Fri Mar 11 15:37:34 PST 2011 + +=item Modified + +$Date: 2011/03/26 06:24:30 $ + +=back + +=head1 SYNOPSIS + +Provides an some utilities for the CCDB Triggers. + +=cut + +package TriggerUtils; + +use strict; +use warnings; + +use Carp; +use FindBin; + +use lib "$FindBin::Bin/../../lib"; + +use DateUtils; + +use base 'Exporter'; + +our $VIEW_DRIVE = 'M'; +our $VIEWTAG_PREFIX = ($^O =~ /mswin/i or $^O =~ /cygwin/) + ? "$VIEW_DRIVE:/" + : "/view/"; + +our @EXPORT = qw ( + trigmsg + triglog + triglogmsg + trigdie + vobname +); + +our $logfile; + +my $logfileName = "$FindBin::Bin/trigger.log"; + +sub trigmsg ($){ + # Display a message to the user using clearprompt + my ($msg) = @_; + + my $cmd = "clearprompt proceed -newline -type error -prompt \"$msg\" "; + $cmd .= "-mask abort -default abort"; + + `$cmd`; + + return; +} # trigmsg + +sub triglog ($) { + # Log a message to the log file + my ($msg) = @_; + + return unless $ENV{CCDB_TRIGGER_DEBUG}; + + unless ($logfile) { + open $logfile, '>>', $logfileName + or die "Unable to open logfile $logfile - $!\n"; + + $logfile->autoflush (1); + } # unless + + my $timestamp = timestamp; + + print $logfile "$FindBin::Script: $timestamp: $msg\n"; + + return; +} # triglog + +sub triglogmsg ($) { + my ($msg) = @_; + + # Log message to log file then display it to user + triglog $msg; + trigmsg $msg; + + return; +} # triglogmsg + +sub trigdie ($$) { + my ($msg, $err) = @_; + + $err ||= 0; + + triglog $msg; + die "$msg\n"; +} # trigdie + +sub vobname ($) { + my ($pvob) = @_; + + # CCDB stores pvob's in the database with the VOBTAG_PREFIX removed. This + # makes a vob name OS independent as on Windows it's \$pvob and Unix/Linux + # it's /vob/$pvob (or sometimes /vobs/$pvob! This is site specific). Now we + # have a handy method in Clearcase.pm for this but we want speed here. Doing a + # "use Clearcase;" will invoke a cleartool subproccess ($Clearcase::CC) and we + # don't want that overhead. So we are replicating that code here. We are + # hinging off of the first character of the vob name (either '\', or '/') to + # indicate if we are Windows or non-Windows. Additionally we are hardcoding + # '/vob/' as the vob tag prefix for the Unix/Linux case. + if (substr ($pvob, 0, 1) eq '\\') { + $pvob = substr $pvob, 1; + } elsif (substr ($pvob, 0, 1) eq '/') { + if ($pvob =~ /\/vob\/(.+)/) { + $pvob = $1; + } # if + } # if + + return $pvob; +} # vobname + +sub dumpenv () { + triglog 'Dumping CLEARCASE_* environment'; + + foreach (keys %ENV) { + next unless /CLEARCASE_/; + + triglog "$_: $ENV{$_}"; + } # foreach + + return; +} # dumpenv + +1; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + DateUtils + +=end man + +=begin html + +
+DateUtils
+
+ +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this module + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2011, ClearSCM, Inc. All rights reserved. + +=cut \ No newline at end of file diff --git a/CCDB/update.pl b/CCDB/update.pl new file mode 100644 index 0000000..cf70302 --- /dev/null +++ b/CCDB/update.pl @@ -0,0 +1,884 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: update.pl,v $ + +Updates the CCDB database + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.4 $ + +=item Created: + +Fri Mar 11 19:09:52 PST 2011 + +=item Modified: + +$Date: 2011/05/05 18:37:05 $ + +=back + +=head1 SYNOPSIS + + Usage update.pl: [-u|sage] [-ve|rbose] [-deb|ug] + + [-vo|b ] + + [[-p|vob ]| + [[-p|vob -a|ctivity ]| + [-p|vob -b|aseline ]| + [-p|vob -s|tream ]]] + + [-o|plog []] + + [-c|heckchangesets] + + Where: + -u|sage: Displays usage + + -ve|rbose: Be verbose + -deb|ug: Output debug messages + + -vo|b : Vob to process + + -p|vob : PVOB to operate on + -a|ctivity : Activity to process + -b|aseline : Baseline to process + -s|tream : Stream to process + + -o|plog []: Process oplog (Default: All vobs) + + -ch + +=head1 DESCRIPTION + +This script updates the CCDB database with Clearcase UCM meta data. It operates +in 2 modes. + +=head2 Update mode + +In this mode, indicated by specifying either no options or a -pvob and +optionally one of -activity, -baseline or -stream, update.pl will query +Clearcase and gather all metadata for the specified option. + +You can run update.pl with no paramters to process all pvobs in the current +registry region of you can specify a -pvob to process. This is generally how +the script is run. Note you can parallelize update.pl by running it multiple +times each with its own -pvob. In this case the script will log activity to +update..log. + +Or you can run "fix ups" to add individual activities, baselines or streams by +specifying -activity (or -baseline/-stream) and its -pvob. Note however that +the object is not validated (In such cases we don't check that say activity and +pvob are valid - we just add them to the database). + +Additionally you can use -vob to add a vob to CCDB. This should be a relatively +infrequent operation and it is necessary to add vobs that -oplog will process. + +=head2 Check Change Sets mode + +Even with this script initially popullating CCDB and with the appropriate +triggers set to fire to keep CCDB up to date, and even with -oplog mode to apply +changes from other sites the CCDB may still become out of sync with Clearcase. +This is due to the fact that orphaned files can effect change set membership in +UCM and Clearcase does not call any triggers or otherwise notify you of the +problem. To illustrate, if the user is running under UCM and checks out a +directory, makes an element and checks it in, but then cancels the checkout of +the directory, Clearcase is forced to orphan the file by placing it in +lost+found. A warning is issued to the user, however no triggers are called. + +Investigating the change set we see that the elements that were orphaned are +indicated in the change set but their paths have been altered to indicate that +the elements are in lost+found! One would think that Clearcase would fire the +chactivity trigger but it seems that trigger is only fired when elements change +from one activity to another. In this case the elements are changing, but the +activity is the same activity. To me this is a bug and Clearcase should fire the +chactivity trigger with CLEARCASE_ACTIVITY == CLEARCASE_TO_ACTIVITY. If this +were the case we could handle this situation with triggers. + +Check change set mode instead goes through all of the changesets in CCDB and +verifies that the changeset in CCDB matches the changeset as listed by +lsactivity -long. If not it updates it. This is an intense activity that will +be time consuming but I can see no other way to fix up this problem. + +=cut + +use strict; +use warnings; + +use FindBin; +use Getopt::Long; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use CCDB; +use Clearcase; +use Clearcase::Vob; +use Clearcase::UCM; +use Clearcase::UCM::Activity; +use Clearcase::Element; +use Display; +use Logger; +use TimeUtils; +use Utils; + +my $VERSION = '$Revision: 1.4 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +my (%opts, %totals, $log); + +my $ccdb = CCDB->new; + +# Forwards +sub ProcessFolder ($$); + +sub changeset ($$) { + my ($activity, $pvob) = @_; + + $pvob = Clearcase::vobtag $pvob; + + my $cmd = "lsact -fmt \"%[versions]CQp\" $activity\@$pvob"; + + my ($status, @output) = $Clearcase::CC->execute ($cmd); + + $log->err ("Unable to execute $cmd\n" . join ("\n", @output), $status) + if $status; + + # Need to split up change set. It's presented to us as quoted and space + # separated however the change set elements themselves can have spaces in + # them! e.g.: + # + # "/vob/foo/file name with spaces@@/main/1", "/vob/foo/file name2@@/main/2" + # + # So we'll split on '", ""'! Note that this will leave us with the first + # element with a leading '"' and the last element with a trailing '"' which + # we will have to handle. + # + # Additionally we will call collapseOverExtendedViewPathname to normalize + # the over extended pathnames to element hashes. + my (@changeset); + + @output = split /\", \"/, $output[0] + if $output[0]; + + foreach (@output) { + # Skip any cleartool warnings. We are getting warnings of the form: + # "A version in the change set of activity "63332.4" is currently + # unavailable". Probably some sort of subtle corruption that we can ignore. + # (It should be fixed but we aren't going to be doing that here!) + next if /cleartool: Warning/; + + # Strip any remaining '"'s + s/^\"//; s/\"$//; + + # Remove vob prefix but keep the leading "/" + $_ = '/' . Clearcase::vobname $_; + + my %element = Clearcase::Element::collapseOverExtendedVersionPathname $_; + + push @changeset, \%element; + } # foreach + + return @changeset; +} # changeset + +sub baselineActivities (%) { + my (%baseline) = @_; + + my $pvobTag = Clearcase::vobtag $baseline{pvob}; + + my $cmd = "lsbl -fmt \"%[activities]p\" $baseline{name}\@$pvobTag"; + + my ($status, @output) = $Clearcase::CC->execute ($cmd); + + $log->err ("Unable to execute $cmd\n" . join ("\n", @output), $status) + if $status; + + $output[0] ||= ''; + + return split / /, $output[0]; +} # baselineActivities + +sub UpdatePvob ($) { + my ($pvob) = @_; + + my %pvob = $ccdb->GetVob ($pvob); + + return if %pvob; + + my ($err, $msg) = $ccdb->AddVob ({ + name => $pvob, + type => 'ucm', + }); + + if ($err) { + $log->err ("Unable to add pvob:$pvob\n$msg"); + } else { + $totals{'Pvobs added'}++; + + $log->msg ("Added pvob:$pvob"); + } # if + + return; +} # UpdatePvob + +sub UpdateFolder ($$) { + my ($folder, $pvob) = @_; + + my %folder = $ccdb->GetFolder ($folder, $pvob); + + return if %folder; + + my ($err, $msg) = $ccdb->AddFolder ({ + name => $folder, + pvob => $pvob, + }); + + if ($err) { + $log->err ("Unable to add folder:$folder\n$msg"); + } else { + $totals{'Folders added'}++; + + $log->msg ("Added folder:$folder"); + } # if + + return; +} # UpdateFolder + +sub UpdateSubfolder ($$$) { + my ($parent, $subfolder, $pvob) = @_; + + my %subfolder = $ccdb->GetSubfolder ($parent, $subfolder, $pvob); + + return if %subfolder; + + my ($err, $msg) = $ccdb->AddSubfolder ({ + parent => $parent, + subfolder => $subfolder, + pvob => $pvob, + }); + + if ($err) { + $log->err ("Unable to add subfolder:$parent/$subfolder\n$msg"); + } else { + $totals{'Subfolders added'}++; + + $log->msg ("Added subfolder:$parent/$subfolder"); + } # if + + return; +} # UpdateSubfolder + +sub UpdateProject ($$$) { + my ($project, $folder, $pvob) = @_; + + my %project = $ccdb->GetProject ($project, $folder, $pvob); + + return if %project; + + my ($err, $msg) = $ccdb->AddProject ({ + name => $project, + folder => $folder, + pvob => $pvob, + }); + + if ($err) { + $log->err ("Unable to add project:$project folder:$folder pvob:$pvob\n$msg"); + } else { + $totals{'Projects added'}++; + + $log->msg ("Added Project:$project"); + } # if + + return; +} # UpdateProject + +sub UpdateStream ($$) { + my ($name, $pvob) = @_; + + my %stream = $ccdb->GetStream ($name, $pvob); + + return if %stream; + + # Determine the integration stream for this stream's project. First get + # project for the stream. + my $pvobTag = Clearcase::vobtag ($pvob); + + my $cmd = "lsstream -fmt \"%[project]p\" $name\@$pvobTag"; + + my ($status, @output) = $Clearcase::CC->execute ($cmd); + + if ($status) { + $log->err ("Unable to execute $cmd\n" . join ("\n", @output)); + + return; + } # if + + # Now get the intergration stream for this project + $cmd = "lsproject -fmt \"%[istream]p\" $output[0]\@$pvobTag"; + + ($status, @output) = $Clearcase::CC->execute ($cmd); + + if ($status) { + $log->err ("Unable to execute $cmd\n" . join ("\n", @output)); + + return; + } # if + + my $type = 'integration' + if $name eq $output[0]; + + my ($err, $msg) = $ccdb->AddStream ({ + name => $name, + pvob => $pvob, + type => $type, + }); + + if ($err) { + $log->err ( "Unable to add stream:$name\n$msg"); + } else { + $log->msg ("Added stream:$name"); + $totals{'Streams added'}++; + } # if +} # UpdateStream + +sub UpdateChangeset ($$$) { + my ($activity, $pvob, $element) = @_; + + my %element = ( + name => '/' . Clearcase::vobname $element->pname, + version => $element->version, + ); + + my %changeset = $ccdb->GetChangeset ( + $activity, + '/' . Clearcase::vobname $element->pname, + $element->version, + $pvob, + ); + + return if %changeset; + + my ($err, $msg) = $ccdb->AddChangeset ({ + activity => $activity, + element => $element{name}, + version => $element{version}, + pvob => $pvob, + + }); + + if ($err) { + $log->err ("Unable to add changeset activity:$activity " + . "element:$element{name}$Clearcase::SFX$element{version}\n$msg"); + } else { + $totals{'Changesets added'}++; + + $log->msg ("Linked activity:$activity -> element:$element{name}"); + } # if + + return; +} # UpdateChangeset + +sub UpdateActivity ($$) { + my ($name, $pvob) = @_; + + my %activity = $ccdb->GetActivity ($name, $pvob); + + return if %activity; + + my ($err, $msg) = $ccdb->AddActivity ({ + name => $name, + pvob => $pvob, + }); + + if ($err) { + $log->err ("Unable to add activity:$name\n$msg"); + } else { + $totals{'Activities added'}++; + + $log->msg ("Added activity $name"); + } # if + + return; +} # UpdateActivity + +sub UpdateBaselineActivityXref (%) { + my (%baseline) = @_; + + $log->msg ("Processing Baseline Activities for $baseline{name}"); + + my %baselineActivityXref = ( + baseline => $baseline{name}, + pvob => $baseline{pvob}, + ); + + foreach (baselineActivities %baseline) { + my ($err, $msg); + + # Often activities in a baseline have not yet been added so add them here. + # (Not sure why this is the case...) + + my %existingRec = $ccdb->GetActivity ($_, $baseline{pvob}); + + UpdateActivity $_, $baseline{pvob} + unless %existingRec; + + $baselineActivityXref{activity} = $_; + + %existingRec = $ccdb->GetBaselineActivityXref ( + $baselineActivityXref{baseline}, + $baselineActivityXref{activity}, + $baselineActivityXref{pvob} + ); + + unless (%existingRec) { + ($err, $msg) = $ccdb->AddBaselineActivityXref (\%baselineActivityXref); + + if ($err) { + $log->err ("Unable to add baseline:$baselineActivityXref{name}" + . " activity: $baselineActivityXref{activity}\n" + . $msg + ); + } else { + $totals{'Baseline Activity Xrefs added'}++; + } # if + } # unless + } # foreach + + $log->msg ("Processed Baseline Activities for $baseline{name}"); + + return; +} # UpdateBaselineActivityXref + +sub UpdateBaseline ($$) { + my ($name, $pvob) = @_; + + my %baseline = $ccdb->GetBaseline ($name, $pvob); + + return if %baseline; + + my ($err, $msg) = $ccdb->AddBaseline ({ + name => $name, + pvob => $pvob, + }); + + if ($err) { + $log->err ("Unable to add baseline:$name\n$msg"); + } else { + $totals{'Baselines added'}++; + + $log->msg ("Added baseline:$name"); + + my %baseline = $ccdb->GetBaseline ($name, $pvob); + + UpdateBaselineActivityXref (%baseline); + } # if + + return; +} # Updatebaseline + +sub UpdateStreamActivityXref ($$$) { + my ($stream, $activity, $pvob) = @_; + + my %streamActivityXref = $ccdb->GetStreamActivityXref ( + $stream, + $activity, + $pvob, + ); + + return if %streamActivityXref; + + my ($err, $msg) = $ccdb->AddStreamActivityXref ({ + stream => $stream, + activity => $activity, + pvob => $pvob, + }); + + if ($err) { + $log->err ("Unable to add stream_activity_xref stream:$stream " + . "activity:$activity\n$msg"); + return; + } else { + $totals{'Stream Activity Xrefs added'}++; + + $log->msg ("Linked stream:$stream -> activity:$activity"); + } # if + + return; +} # UpdateStreamActivityXref + +sub ProcessElements ($$) { + my ($name, $pvob) = @_; + + $log->msg ("Finding changeset for activity:$name"); + + my $activity = Clearcase::UCM::Activity->new ($name, $pvob); + + foreach ($activity->changeset) { + my ($element) = $_; + + # Remove vob prefix but keep the leading "/" + my $elementName = '/' . Clearcase::vobname $element->pname; + + $log->msg ( + "Processing element:$elementName" + . $Clearcase::SFX + . $element->version + ); + + UpdateChangeset $name, $pvob, $element; + } # foreach; + + $log->msg ("Processed changeset for activity:$name"); + + return; +} # ProcessElements + +sub ProcessActivities ($$) { + my ($stream, $pvob) = @_; + + $log->msg ("Finding activities in stream:$stream"); + + my $pvobTag = Clearcase::vobtag ($pvob); + + my $cmd = "lsstream -fmt \"%[activities]p\" $stream\@$pvobTag"; + + my ($status, @output) = $Clearcase::CC->execute ($cmd); + + if ($status) { + $log->err ("Unable to execute $cmd\n" . join ("\n", @output), $status); + + return; + } # if + + $output[0] ||= ''; + + foreach (sort split / /, $output[0]) { + next if /^DEFAULT.*NO_CHECKIN/; + + UpdateActivity ($_, $pvob); + + $totals{'Activities processed'}++; + + UpdateStreamActivityXref $stream, $_, $pvob; + + ProcessElements $_, $pvob; + } # foreach + + $log->msg ("Processed activities in stream:$stream"); + + return; +} # ProcessActivities + +sub ProcessBaselines ($$) { + my ($stream, $pvob) = @_; + + $log->msg ("Finding baselines in stream:$stream"); + + my $pvobTag = Clearcase::vobtag ($pvob); + + my $cmd = "lsbl -stream $stream\@$pvobTag -short"; + + my ($status, @baselines) = $Clearcase::CC->execute ($cmd); + + if ($status) { + $log->err ("Unable to execute $cmd\n" . join ("\n", @baselines)); + + return; + } # if + + foreach (sort @baselines) { + UpdateBaseline ($_, $pvob); + + $totals{'Baselines processed'}++; + } # foreach + + $log->msg ("Processed baselines in stream:$stream"); + + return; +} # ProcessBaselines + +sub ProcessStream ($$) { + my ($name, $pvob) = @_; + + $totals{'Streams processed'}++; + + UpdateStream $name, $pvob; + + ProcessActivities $name, $pvob; + ProcessBaselines $name, $pvob; + + return; +} # ProcessStream + +sub ProcessProject ($$$) { + my ($project, $folder, $pvob) = @_; + + my $pvobTag = Clearcase::vobtag $pvob; + + $log->msg ("Processing project:$project\@$pvobTag"); + + UpdateProject ($project, $folder, $pvob); + + my $cmd = "lsstream -short -in $project\@$pvobTag"; + + my ($status, @output) = $Clearcase::CC->execute ($cmd); + + if ($status) { + $log->err ("Unable to execute $cmd\n" . join ("\n", @output)); + + return; + } # if + + foreach (@output) { + ProcessStream $_, $pvob; + } # foreach + + return; +} # ProcessProject + +sub ProcessFolder ($$) { + my ($folder, $pvob) = @_; + + my $pvobTag = Clearcase::vobtag $pvob; + + $log->msg ("Processing folder:$folder\@$pvobTag"); + + UpdateFolder ($folder, $pvob); + + my $cmd = "lsfolder -fmt \"%[contains_folders]p\" $folder\@$pvobTag"; + + my ($status, @output) = $Clearcase::CC->execute ($cmd); + + if ($status) { + $log->err ("Unable to execute command $cmd (Status: $status)\n" + . join ("\n", @output), 1); + + return; + } # if + + $output[0] ||= ''; + + foreach (split / /, $output[0]) { + ProcessFolder $_, $pvob; + + UpdateSubfolder ($folder, $_, $pvob); + } # foreach + + $cmd = "lsfolder -fmt \"%[contains_projects]p\" $folder\@$pvobTag"; + + ($status, @output) = $Clearcase::CC->execute ($cmd); + + if ($status) { + $log->err ("Unable to execute command $cmd (Status: $status)\n" + . join ("\n", @output), 1); + + return; + } # if + + $output[0] ||= ''; + + foreach (split / /, $output[0]) { + ProcessProject $_, $folder, $pvob; + } # foreach + + return; +} # ProcessFolder + +sub ProcessPvob ($) { + my ($pvobName) = @_; + + $log->msg ("Processing pvob:$pvobName"); + + UpdatePvob $pvobName; + + ProcessFolder ('RootFolder', $pvobName); + + return; + + $log->msg ("Finding streams in pvob:$pvobName"); + + my $pvob = Clearcase::vobtag ($pvobName); + + my $cmd = "lsstream -invob $pvob -short"; + my ($status, @streams) = $Clearcase::CC->execute ($cmd); + + $log->err ("Unable to execute $cmd\n" . join ("\n", @streams), $status) + if $status; + + my %stream = ( + pvob => $pvobName, + ); + + foreach (sort @streams) { + $stream{name} = $_; + + $totals{'Streams processed'}++; + + ProcessStream $stream{name}, $stream{pvob}; + } # foreach + + $totals{'Pvobs processed'}++; + + $log->msg ("Finished processing pvob:$pvobName"); + + return; +} # ProcessPvob + +sub ProcessVob ($) { + my ($name) = @_; + + my ($err, $msg); + + my %existingRec = $ccdb->GetVob ($name); + + unless (%existingRec) { + my $vob = Clearcase::Vob->new (Clearcase::vobtag $name); + + # If vob doesn't exist then $vob is just an empty shell. Check to see if + # another field is present to make sure the vob really exists. A vob should + # always have a region, for example. + return + unless $vob->region; + + my $vobRegistryAttributes = $vob->vob_registry_attributes; + + my $type = ($vobRegistryAttributes and + $vobRegistryAttributes =~ /ucmvob/) ? 'ucm' : 'base'; + + ($err, $msg) = $ccdb->AddVob ({ + name => $name, + type => $type, + }); + + if ($err) { + $log->err ("Unable to add vob $name (Error: $err)\n$msg"); + } else { + $totals{'Vobs added'}++; + } # if + } # unless + + return; +} # ProcessVob + +# Main +local $| = 1; + +my $startTime = time; + +GetOptions ( + \%opts, + 'verbose' => sub { set_verbose }, + 'usage' => sub { Usage }, + 'activity=s', + 'baseline=s', + 'checkchangeset', + 'pvob=s', + 'stream=s', + 'vob=s', +) or Usage "Unknown option"; + +my $nbrOpts = 0; + +$nbrOpts++ if $opts{pvob}; +$nbrOpts++ if $opts{activity}; +$nbrOpts++ if $opts{baseline}; +$nbrOpts++ if $opts{stream}; +$nbrOpts++ if $opts{vob}; + +Usage "Cannot specify -checkchangeset and any other options" + if $opts{checkchangeset} and $nbrOpts != 0; + +Usage "Cannot specify -vob and any other options" + if $opts{vob} and ($nbrOpts != 1 or $opts{checkchangeset}); + +my $me = $FindBin::Script; + $me =~ s/\.pl$//; + +if ($opts{activity} and $opts{pvob} and + ($opts{baseline} or $opts{stream})) { + Usage "If -activity is specified then -pvob should be the only other " + . "option"; + exit 1; +} elsif ($opts{baseline} and $opts{pvob} and + ($opts{activity} or $opts{stream})) { + Usage "If -baseline is specified then -pvob should be the only other " + . "option"; + exit 1; +} elsif ($opts{stream} and $opts{pvob} and + ($opts{activity} or $opts{baseline})) { + Usage "If -stream is specified then -pvob should be the only other option"; + exit 1; +} elsif ($opts{pvob}) { + $nbrOpts = 0; + + $nbrOpts++ if $opts{activity}; + $nbrOpts++ if $opts{baseline}; + $nbrOpts++ if $opts{stream}; + + if ($nbrOpts != 0 and $nbrOpts > 1) { + Usage "If -pvob is specified then it must be used alone or in " + . "conjunction\nwith only one of -activity, -baseline or -stream " + . "must be specified\n"; + exit 1; + } # fi +} # if + +if ($opts{activity} and $opts{pvob}) { + $log = Logger->new; + + $log->msg ("$FindBin::Script V$VERSION"); + + UpdateActivity ($opts{activity}, $opts{pvob}); +} elsif ($opts{baseline} and $opts{pvob}) { + $log = Logger->new; + + $log->msg ("$FindBin::Script V$VERSION"); + + UpdateBaseline ($opts{baseline}, $opts{pvob}); +} elsif ($opts{stream} and $opts{pvob}) { + $log = Logger->new; + + $log->msg ("$FindBin::Script V$VERSION"); + + UpdateStream ($opts{stream}, $opts{pvob}); +} elsif ($opts{pvob}) { + $log = Logger->new (name => "$me.$opts{pvob}"); + + $log->msg ("$FindBin::Script V$VERSION"); + + ProcessPvob $opts{pvob}; +} elsif ($opts{checkchangeset}) { + error "The -checkchangeset option is not implemented yet", 1; +} elsif ($opts{vob}) { + $log = Logger->new; + + $log->msg ("$FindBin::Script V$VERSION"); + + ProcessVob $opts{vob}; +} else { + $log = Logger->new; + + my $UCM = Clearcase::UCM->new; + + $log->msg ("$FindBin::Script V$VERSION"); + + ProcessPvob $_ + foreach ($UCM->pvobs); +} # if + +display_duration $startTime, $log; + +$totals{Errors} = $log->errors; + +Stats \%totals, $log; diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ae6677b --- /dev/null +++ b/Makefile @@ -0,0 +1,78 @@ +################################################################################ +# +# File: $RCSfile: Makefile,v $ +# Revision: $Revision: 1.8 $ +# Description: Makefile for Clearscm +# Author: Andrew@Clearscm.com +# Created: Mon Nov 13 16:14:30 1995 +# Modified: $Date: 2012/09/20 06:52:37 $ +# Language: Makefile +# +# (c) Copyright 2010, ClearSCM, Inc., all rights reserved. +# +################################################################################ +CLEARLIB = etc/mail.conf\ + lib/CmdLine.pm\ + lib/BinMerge.pm\ + lib/DateUtils.pm\ + lib/Display.pm\ + lib/GetConfig.pm\ + lib/Logger.pm\ + lib/Machines.pm\ + lib/Mail.pm\ + lib/OSDep.pm\ + lib/Rexec.pm\ + lib/TimeUtils.pm\ + lib/Utils.pm +CLEARCC = lib/Clearcase.pm\ + lib/Clearcase +CLEARCQ = etc/cq.conf\ + lib/Clearquest.pm\ + lib/Clearquest +CLEARADM = clearadm +CLEARENV = rc +CLEARAGENT = lib/Display.pm\ + lib/OSDep.pm\ + lib/DateUtils.pm\ + lib/GetConfig.pm\ + lib/Utils.pm\ + clearadm/lib/Clearexec.pm\ + clearadm/clearagent.pl\ + clearadm/clearexec.pl\ + clearadm/etc/clearexec.conf\ + clearadm/etc/conf.d/clearadm\ + clearadm/etc/init.d/clearagent\ + clearadm/etc/init.d/cleartasks\ + clearadm/load.vbs\ + clearadm/log\ + clearadm/setup.pl\ + clearadm/var +TARGETS = clearlib.tar.gz\ + clearcc.tar.gz\ + clearcq.tar.gz\ + clearadm.tar.gz\ + clearenv.tar.gz\ + clearagent.tar.gz + +all: $(TARGETS) + +clean: + @rm -f $(TARGETS) + +clearlib.tar.gz: $(CLEARLIB) + @tar --exclude CVS -zcf $@ $(CLEARLIB) + +clearcc.tar.gz: $(CLEARCC) + @tar --exclude CVS -zcf $@ $(CLEARCC) + +clearcq.tar.gz: $(CLEARCQ) + @tar --exclude CVS -zcf $@ $(CLEARCQ) + +clearadm.tar.gz: $(CLEARADM) + @tar --exclude CVS -zcf $@ $(CLEARADM) + +clearenv.tar.gz: $(CLEARENV) + @tar --exclude CVS -zcf $@ $(CLEARENV) + +clearagent.tar.gz: $(CLEARAGENT) + @tar --exclude CVS -zcf $@ $(CLEARAGENT) diff --git a/bin/.cvsignore b/bin/.cvsignore new file mode 100644 index 0000000..0c41123 --- /dev/null +++ b/bin/.cvsignore @@ -0,0 +1,2 @@ +.perldb.hist +.cvsignore diff --git a/bin/.perldb.hist b/bin/.perldb.hist new file mode 100644 index 0000000..ac581e0 --- /dev/null +++ b/bin/.perldb.hist @@ -0,0 +1,100 @@ +\@main::INC +EOT +my $subref = \&dumpvar_epic::dump_array_expr; +my $savout = CORE::select($DB::OUT); +my $savbuf = $|; +$| = 0; +$subref->($offset, $varexpr); +$| = $savbuf; +print ""; +CORE::select($savout); +}; + +;{ +do 'dumpvar_epic.pm' unless defined &dumpvar_epic::dump_lexical_vars; + +my $offset = 0; +my $varexpr = <<'EOT'; ++{%main::INC} +EOT +my $subref = \&dumpvar_epic::dump_hash_expr; +my $savout = CORE::select($DB::OUT); +my $savbuf = $|; +$| = 0; +$subref->($offset, $varexpr); +$| = $savbuf; +print ""; +CORE::select($savout); +}; + +;{ +do 'dumpvar_epic.pm' unless defined &dumpvar_epic::dump_lexical_vars; + +my $offset = 0; +my $varexpr = <<'EOT'; ++{%main::SIG} +EOT +my $subref = \&dumpvar_epic::dump_hash_expr; +my $savout = CORE::select($DB::OUT); +my $savbuf = $|; +$| = 0; +$subref->($offset, $varexpr); +$| = $savbuf; +print ""; +CORE::select($savout); +}; + +;{ +do 'dumpvar_epic.pm' unless defined &dumpvar_epic::dump_lexical_vars; + +my $offset = 0; +my $varexpr = <<'EOT'; +$h->{'%violations'} +EOT +my $subref = \&dumpvar_epic::dump_hash_expr; +my $savout = CORE::select($DB::OUT); +my $savbuf = $|; +$| = 0; +$subref->($offset, $varexpr); +$| = $savbuf; +print ""; +CORE::select($savout); +}; + +;{ +do 'dumpvar_epic.pm' unless defined &dumpvar_epic::dump_lexical_vars; + +my $offset = 0; +my $varexpr = <<'EOT'; +$h->{'@lines'} +EOT +my $subref = \&dumpvar_epic::dump_array_expr; +my $savout = CORE::select($DB::OUT); +my $savbuf = $|; +$| = 0; +$subref->($offset, $varexpr); +$| = $savbuf; +print ""; +CORE::select($savout); +}; + +c 188 +x \%violations +x $nbrViolations +x $ip +x $violations{$ip} +x @emails +x $ip +x $message +c 285 +x $email +x $email +$email=1 +x $message +$to="andrew\@defaria.com" +$to="andrew\@defaria.com" +c 228 +x \%violations +c 235 +x $attempts +x $violations{$ip} diff --git a/bin/backup b/bin/backup new file mode 100755 index 0000000..ef98e9e --- /dev/null +++ b/bin/backup @@ -0,0 +1,61 @@ +#!/bin/bash +################################################################################ +# +# File: $RCSfile: backup,v $ +# Revision: $Revision: 1.7 $ +# Description: This script backs up the system in a consistent way +# Author: Andrew@DeFaria.com +# Created: Tue Jul 27 15:00:11 PDT 2004 +# Modified: $Date: 2011/05/26 06:17:20 $ +# Language: Bash +# +# (c) Copyright 2000-2005, ClearSCM, Inc., all rights reserved. +# +################################################################################ +# Full Backup +backup=/sbin/dump +dumppath=/backup +files2backup=/ + +if [ -f /etc/dump.excludes ]; then + excludes="-E /etc/dump.excludes" +else + excludes="" +fi + +if [ $(id -u) -ne 0 ]; then + echo "You must be root to backup" + exit 1 +fi + +function usage { + type="$1" + + echo "Usage: backup " + exit 1 +} # usage + +type="$1" +host=$(hostname) + +host=$(hostname) + +if [ "$type" = "full" ]; then + rm -f $dumppath/$host.$type.backup + rm -f $dumppath/$host.$type.backup.log + rm -f $dumppath/$host.$type.list + level=0 +elif [ "$type" = "incremental" ]; then + level=1 +else + usage $type +fi + +log=$dumppath/$host.$type.backup.log + +$backup -$level\ + -A $dumppath/$host.$type.list\ + -f $dumppath/$host.$type.backup\ + -z\ + $excludes\ + -u $files2backup > $log 2>&1 diff --git a/bin/bice.pl b/bin/bice.pl new file mode 100755 index 0000000..f1a6358 --- /dev/null +++ b/bin/bice.pl @@ -0,0 +1,398 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: bice.pl,v $ + +Report breakin attempts to this domain + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.3 $ + +=item Created: + +Fri Mar 18 01:14:38 PST 2005 + +=item Modified: + +$Date: 2013/05/30 15:35:27 $ + +=back + +=head1 SYNOPSIS + + Usage: bice [-u|sage] [-v|erbose] [-d|ebug] [-nou|pdate] [-nom|ail] + [-f|ilename ] + + Where: + -u|sage Print this usage + -v|erbose: Verbose mode (Default: -verbose) + -nou|pdate: Don't update security logfile file (Default: -update) + -nom|ail: Don't send emails (Default: -mail) + -f|ilename: Open alternate messages file (Default: /var/log/auth.log) + +=head1 DESCRIPTION + +This script will look at the security logfile for attempted breakins and then +use whois to report them to the upstream provider. + +=cut + +use strict; +use warnings; + +use FindBin; +use Getopt::Long; + +use lib "$FindBin::Bin/../lib"; + +use Display; +use Mail; +use Utils; + +use Fcntl ':flock'; # import LOCK_* constants + +my $security_logfile = '/var/log/auth.log'; + +# Customize these variables +my $domain = 'DeFaria.com'; +my $contact = 'Andrew@DeFaria.com'; +my $location = 'San Diego, California, USA'; +my $UTC = 'UTC-8'; +my $mailhost = $domain; +# End customize these variables + +my $verbose; +my $update = 1; +my $email = 1; +my $hostname = `hostname`; +chomp $hostname; + +if ($hostname =~ /(\w*)\./) { + $hostname = $1; +} # if + +sub AddToIPTables (@) { + my (@ips) = @_; + + # We shouldn't need to weed out duplicate but ya never know + my $ipfilename = '/etc/ipblock'; + + my $result = open my $ipfile, '<', $ipfilename; + + my (%ips, @oldips); + + if ($result) { + @oldips = <$ipfile>; + + close $ipfile if $ipfile; + + chomp @oldips; + } # if + + map { $ips{$_} = 1 } @oldips; + map { $ips{$_} = 1 } <@ips>; + + open $ipfile, '>', "$ipfilename" + or error "Unable to open $ipfilename - $!", 1; + + foreach (sort keys %ips) { + print $ipfile "$_\n"; + } # foreach + + close $ipfile; + + # Recreate the BICE chain + `/sbin/iptables -F BICE`; + `/sbin/iptables -X BICE`; + `/sbin/iptables -N BICE`; + + # Add all new @ips to iptables + `/sbin/iptables -A BICE -s $_ -p tcp --destination-port 22 -j DROP` foreach (sort keys %ips); + + return; +} # AddToIPTables + +# Use whois(1) to get the email addresses of the responsible parties for an IP +# address. Note that a hash is used to eliminate duplicates. +sub GetEmailAddresses ($) { + my ($ip) = @_; + + # List of whois servers to try + my @whois_list = ( + '', + 'whois.arin.net', + 'whois.nsiregistry.net', + 'whois.opensrs.net', + 'whois.networksolutions.com', + ); + + my %email_addresses; + + foreach (@whois_list) { + my @lines; + + if ($_ eq "") { + @lines = grep { /.*\@.*/ } `whois $ip`; + } else { + @lines = grep {/.*\@.*/ } `whois -h $_ $ip`; + } # if + + foreach (@lines) { + my @fields = split /:/, $_; + + $_ = $fields [@fields - 1]; + + if (/(\S+\@\S[\.\S]+)/) { + $email_addresses{$1} = ""; + } # if + } # foreach + + # Break out of loop if we found email addresses + last unless keys %email_addresses; + } # foreach + + return keys %email_addresses; +} # GetEmailAddresses + +# Send email to the responsible parties. +sub SendEmail ($$$$$) { + my ($to, $subject, $message, $ip, $violations) = @_; + + if ($email) { + verbose "Reporting $ip ($violations violations) to $to"; + } else { + verbose "Would have reported $ip ($violations violations) to $to"; + return; + } # if + + mail ( + from => "BICE\@$domain", + to => $to, + cc => $contact, + subject => $subject, + mode => 'html', + data => $message, + ); +} # SendEmail + +sub processLogfile () { + my %violations; + + # Note: Normally you must be root to open up $security_logfile + open my $readlog, '<', $security_logfile + or error "Unable to open $security_logfile - $!", 1; + + flock $readlog, LOCK_EX + or error "Unable to flock $security_logfile", 1; + + my @lines; + + while (<$readlog>) { + my $newline = $_; + + if (/^(\S+\s+\S+\s+\S+)\s+.*Invalid user (\w+) from (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/) { + my %violation = $violations{$3} ? %{$violations{$3}} : %_; + + push @{$violation{$2}}, $1; + + $violations{$3} = \%violation; + + $newline =~ s/Invalid user/INVALID USER/; + } elsif (/^(\S+\s+\S+\s+\S+)\s+.*authentication failure.*ruser=(\S+).*rhost=(\S+)/) { + my %violation = $violations{$3} ? %{$violations{$3}} : %_; + + push @{$violation{$2}}, $1; + + $violations{$3} = \%violation; + + $newline =~ s/authentication failure/AUTHENTICATION FAILURE/; + } elsif (/^(\S+\s+\S+\s+\S+)\s+.*Failed password for (\w+) from (\d{1,3}\.\d{1,3}\.d{1,3}\.d{1,3})/) { + my %violation = $violations{$3} ? %{$violations{$3}} : %_; + + push @{$violation{$2}}, $1; + + $violations{$3} = \%violation; + + $newline =~ s/Failed password/FAILED PASSWORD/; + } # if + + push @lines, $newline; + } # while + + return %violations unless $update; + + flock $readlog, LOCK_UN + or error "Unable to unlock $security_logfile", 1; + + close $readlog; + + open my $writelog, '>', $security_logfile + or error "Unable to open $security_logfile for writing - $!", 1; + + flock $writelog, LOCK_EX + or error "Unable to flock $security_logfile", 1; + + print $writelog $_ foreach @lines; + + flock $writelog, LOCK_UN + or error "Unable to unlock $security_logfile", 1; + + close $writelog; + + return %violations; +} # processLogfile + +# Report breakins to the authorities. +sub ReportBreakins () { + my %violations = processLogfile; + + my $nbrViolations = keys %violations; + + if ($nbrViolations == 0) { + verbose 'No violations found'; + } elsif ($nbrViolations == 1) { + verbose '1 site attempting to violate our perimeter'; + } else { + verbose "$nbrViolations sites attempting to violate our perimeter"; + } # if + + foreach (sort keys %violations) { + my $ip = $_; + + my $attempts; + + $attempts += @{$violations{$ip}{$_}} foreach (keys %{$violations{$ip}}); + + my @emails = GetEmailAddresses $ip; + + unless (@emails) { + verbose 'Unable to find any responsible parties for detected breakin ' + . "attempts from IP $ip ($attempts breakin attempts)"; + next; + } # unless + + my $to = join ',', @emails; + my $subject = "Illegal attempts to break into $domain from your domain"; + my $message = <<"END"; +

Somebody from your domain with an IP Address of $ip has been +attempting to break into my domain, $domain. Breaking into somebody +else's computer is illegal and criminal prosecution can result! As a +responsible ISP it is in your best interests to investigate such activity and to +shutdown any such illegal activity as it is a violation of law and most likely a +violation of your user level agreement. It is expected that you will investigate +this and send the result and/or disposition of your investigation back to +$contact. If you fail to do so then criminal prosecution may +result!

+ +

Please be aware that none of these attempts to breakin have been +successful - this system is configured such that only trusted users are allowed +to log in as they must provide authenticated keys in advance. So your attempts +have been wholly unsuccessful. Still, this does not diminish the illegality nor +the ability of us to pursue this matter in a court of law.

+ +

There were a total of $attempts attempts to break into $domain. The following +is a report of the breakin attempts from IP Address $ip along with the usernames +attempted and the time of the attempt:

+ +

Note: $domain is located in $location. All times are $UTC:

+ +
    +END + # Report users + foreach my $user (sort keys %{$violations{$ip}}) { + if (@{$violations{$ip}{$user}} == 1) { + $message .= "
  1. The user $user attempted access on $violations{$ip}{$user}[0]
  2. "; + } else { + $message .= "
  3. The user $user attemped access on the following date/times:
  4. "; + $message .= "
      "; + $message .= "
    1. $_
    2. " foreach (@{$violations{$ip}{$user}}); + $message .= "
    "; + } # if + } # foreach + + $message .= '

Your prompt attention to this matter is expected ' + . 'and will be appreciated.

'; + SendEmail $to, $subject, $message, $ip, $attempts; + } # foreach + + AddToIPTables keys %violations; +} # ReportBreakins + +## Main + +# Get options +GetOptions ( + 'verbose', sub { set_verbose }, + 'debug', sub { set_debug }, + 'usage', sub { Usage }, + 'update!', \$update, + 'mail!', \$email, + 'file=s', \$security_logfile, +) || Usage; + +Usage 'Must specify filename' + unless $security_logfile; + +ReportBreakins; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Display + Mail + Utils + +=end man + +=begin html + +
+<Display
+Mail
+a href="http://clearscm.com/php/cvs_man.php?file=lib/Utils.pm">Utils
+
+ +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut diff --git a/bin/bigfiles.pl b/bin/bigfiles.pl new file mode 100755 index 0000000..ec9f2a2 --- /dev/null +++ b/bin/bigfiles.pl @@ -0,0 +1,100 @@ +#!/usr/bin/perl +################################################################################ +# +# File: $RCSfile: bigfiles.pl,v $ +# Revision: $Revision: 1.3 $ +# Description: Reports large files +# Author: Andrew@DeFaria.com +# Created: Mon May 24 09:09:24 PDT 1999 +# Modified: $Date: 2011/04/18 05:15:29 $ +# Language: Perl +# +# (c) Copyright 2001, ClearSCM, Inc., all rights reserved +# +################################################################################ +use strict; +use warnings; + +use FindBin; +use lib "$FindBin::Bin/../lib"; + +use Getopt::Long; + +use OSDep; +use Display; + +sub Usage { + display "Usage: bigfiles: [ -verbose | -v ] [ -size | -s n ] [ ]"; + display "\t\t[ -top n | -t n ] [ -notop | -not ]\n"; + display "Where:"; + display " -size | -s n\tShow only files bigger then n Meg (default 1 Meg)"; + display " -verbose | -v\tTurn on verbose mode (default verbose off)"; + display " -top | -t n\tPrint out only the top n largest files (default LINES - 1)"; + display " -notop|not\tPrint out all files (default top LINES - 1)"; + display " \tDirectory paths to check"; + exit 1; +} # usage + +sub Bigfiles { + my $size = shift; + my @dirs = @_; + + my @files; + + foreach (@dirs) { + next if !-d "$_"; + my $cmd = "find \"$_\" -xdev -type f -size +$size -exec ls -lLGQ {} \\;"; + my @lines = `$cmd`; + + foreach (@lines) { + chomp; + + my %info; + + if (/\S+\s+\d+\s+(\S+)\s+(\d+).*\"\.\/(.*)\"/) { + $info {user} = $1; + $info {filesize} = $2; + $info {filename} = $3; + push @files, \%info; + } # if + } # foreach + } # foreach + + return @files; +} # Bigfiles + +my $lines = defined $ENV {LINES} ? $ENV {LINES} :-24; +my $top = $lines - 2; +my $bytes_in_meg = 1048576; +my $block_size = 512; +my $size_in_meg = 1; +my %opts; + +my $result = GetOptions ( + \%opts, + usage => sub { Usage }, + verbose => sub { set_verbose }, + debug => sub { set_debug }, + 'top=i', + 'size=i', +); + +my @dirs = @ARGV ? @ARGV : "."; + +my $size = $opts {size} ? $opts {size} * $bytes_in_meg / $block_size : 4096; + +# Now do the find +verbose "Directory:\t$_" + foreach (@dirs); +verbose "Size:\t\t$size_in_meg Meg ($size blocks)"; +verbose "Top:\t\t$top"; + +my $head = $top ? "cat" : "head -$top"; + +my @files = Bigfiles $size, @dirs; + +foreach (@files) { + my %info = %{$_}; + + print "${info {filesize}}\t${info {user}}\t${info {filename}}\n"; +} # foreach diff --git a/bin/checkdns b/bin/checkdns new file mode 100755 index 0000000..08e57c0 --- /dev/null +++ b/bin/checkdns @@ -0,0 +1,205 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: checkdns,v $ + +Check DNS by attempting to call gethostbyname of a well known host. + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.6 $ + +=item Created: + +Wed Aug 30 21:03:14 CDT 2006 + +=item Modified: + +$Date: 2011/04/15 15:05:16 $ + +=back + +=head1 SYNOPSIS + + Usage: checkdns [-u|sage] [-v|erbose] [-d|ebug] + [-s|leep ] [-l|ogpath ] + + Where: + -u|sage Print this usage + -v|erbose: Verbose mode + -d|ebug: Emit debug information + + -s|leep : Set sleep period to minutes (Default: 15 minutes) + -l|ogpath : Put the log file in (Default: /var/log) + +=head1 DESCRIPTION + +This script will look at the security logfile for attempted breakins and then +use whois to report them to the upstream provider. + +=cut + +use strict; +use warnings; + +use Getopt::Long; + +use FindBin; + +use lib "$FindBin::Bin/../lib"; + +use Logger; +use Utils; +use Display; + +my $VERSION = '$Revision: 1.6 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +$0 = $FindBin::Script; + +my $host = 'google.com'; +my $sleep = 15; +my $initial_sleep = $sleep; +my $logpath = '/var/log'; +my $log; + +sub CheckDNS { + my ($host) = @_; + + $? = 0; + + my @ipaddrs = gethostbyname $host; + + if (!@ipaddrs) { + debug "Host: $host (ipaddrs empty)"; + + # Cut down sleep time to monitor this outage more closely but do not go + # below once a minute. + if ($sleep > 1) { + $sleep -= $sleep / 2; + } else { + $sleep = 1; + } # if + + return 1; + } # if + + # Successful lookup - set $sleep to $initial_sleep + $sleep = $initial_sleep; + + return; +} # CheckDNS + +sub Shutdown { + my $msg; + + my $errors = $log->errors; + + $log->msg ("$errors errors encountered since starting") + if $errors; + + $log->msg ('Caught interrupt - shutting down'); + + exit $errors; +} # Interrupt + +# Main +GetOptions ( + usage => sub { Usage }, + verbose => sub { set_verbose }, + debug => sub { set_debug }, + 'sleep=i' => \$sleep, + 'logpath=s' => \$logpath, +) or Usage 'Invalid parameter'; + +$SIG {INT} = +$SIG {TERM} = \&Shutdown; + +# Call sethostent so that gethostbyname is fresh everytime +sethostent (0); + +$log = Logger->new ( + path => $logpath, + timestamped => 'yes', + append => 'yes', +); + +$log->msg ( + "Started $FindBin::Script $VERSION logging to $logpath/$FindBin::Script.log" +); + +$log->msg ("Polling DNS on host $host every $sleep minutes"); + +EnterDaemonMode + unless get_debug; + +while () { + my $status = CheckDNS $host; + + if ($status) { + $log->err ("Unable to resolve IP address for $host"); + } else { + $log->msg ("Successfully resolved $host"); + } # if + + sleep ($sleep * 60); +} # while + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Display + Logger + Utils + +=end man + +=begin html + +
+Display
+Logger
+Utils
+
+ +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2004, ClearSCM, Inc. All rights reserved. + +=cut diff --git a/bin/diskspace b/bin/diskspace new file mode 100755 index 0000000..7dfe796 --- /dev/null +++ b/bin/diskspace @@ -0,0 +1,86 @@ +#!/usr/bin/perl +################################################################################ +# +# File: $RCSfile: diskspace,v $ +# Revision: $Revision: 1.2 $ +# Description: Check filesystems to see if they are becoming too full +# Author: Andrew@DeFaria.com +# Created: Fri Mar 12 10:17:44 PST 2004 +# Modified: $Date: 2010/06/08 15:03:27 $ +# Language: Perl +# +# (c) Copyright 2005, ClearSCM, Inc., all rights reserved +# +################################################################################ +use strict; +use warnings; +use File::Spec; + +use FindBin; +use lib "$FindBin::Bin/../lib"; + +use Display; + +my $threshold = 90; + +sub Usage { + my $msg = shift; + + display "ERROR: $msg\n" if defined $msg; + + display "diskspace\t[-v] [-d] [-u] [ -t ]"; + display "\t-v\tTurn on verbose mode"; + display "\t-d\tTurn on debug mode"; + display "\t-u\tThis usage message"; + display "\t-t\tThreshold (0-100)"; + + exit 1; +} # Usage + +sub CheckLocalFilesystems { + my @local_filesystems = `df -lP`; + + @local_filesystems = grep {/^\/dev/} @local_filesystems; + + foreach (@local_filesystems) { + my ($fs, $blocks, $used, $available, $used_percent, $mounted_on) = split; + + if ($used_percent =~ /(\d+)%/) { + $used_percent = $1; + } # if + + $available = sprintf ("%.3f", $available / 1024); + + # Check if over threshold and report + if ($used_percent <= $threshold ) { + verbose "$mounted_on is $used_percent% full - $available Megs left"; + } else { + warning "$mounted_on is $used_percent% full - $available Megs left"; + } # if + } # foreach +} # CheckLocalFilesystems + +# Get parameters +while ($ARGV [0]) { + if ($ARGV [0] eq "-v") { + set_verbose; + } elsif ($ARGV [0] eq "-d") { + set_debug; + } elsif ($ARGV [0] eq "-t") { + shift (@ARGV); + if (!$ARGV [0]) { + Usage "Must specify threshold after -t"; + } else { + $threshold = $ARGV [0]; + } # if + } elsif ($ARGV [0] eq "-u") { + Usage; + } else { + Usage "Unknown argument found: " . $ARGV [0]; + } # if + + shift (@ARGV); +} # while + +verbose "Theshold: $threshold\%"; +CheckLocalFilesystems; diff --git a/bin/httpdwho b/bin/httpdwho new file mode 100755 index 0000000..9ebcfc9 --- /dev/null +++ b/bin/httpdwho @@ -0,0 +1,131 @@ +#!/usr/bin/perl +################################################################################ +# +# File: $RCSfile: httpdwho,v $ +# Revision: $Revision: 1.2 $ +# Description: Parse Apache access.log and produce a report on the locations +# of the visitors to the site +# Author: Andrew@DeFaria.com +# Created: Thu Dec 21 21:49:54 CST 2006 +# Modified: $Date: 2010/06/08 15:03:27 $ +# Dependencies: GEOLite +# Language: Perl +# +# This product includes GeoLite data created by MaxMind, available from +# http://www.maxmind.com +# +# (C) Copyright 2006, ClearSCM, Inc., all rights reserved. +# +################################################################################ +use strict; +use warnings; + +use FindBin; +use Getopt::Long; + +use lib "$FindBin::Bin/../lib"; + +use Display; +use Utils; + +use Geo::IP::PurePerl; + +sub Usage (;$) { + my $msg = shift; + + dipslay $msg if $msg; + display "Usage: $FindBin::Script: [ -verbose | -v ] "; + display "\nWhere:"; + display " -verbose | -v\tTurn on verbose mode (Default: verbose off)"; + display " \tIs the Apache formated access logfile"; + exit 1; +} # usage + +sub GetIPs ($) { + my $filename = shift; + + my %ipaddrs; + + verbose_nolf "Processing $filename"; + + foreach (ReadFile ($filename)) { + verbose_nolf "."; + + my @fields = split; + my @ipaddrs = gethostbyname $fields [0]; + + next if !@ipaddrs; # Skip errors + + my ($a, $b, $c, $d) = unpack "C4", $ipaddrs [4]; + my $ipaddr = "$a.$b.$c.$d"; + + debug "Host: ${fields [0]} IP: $ipaddr"; + + if ($ipaddrs {$ipaddr}) { + $ipaddrs {$ipaddr}[1]++; + } else { + my @domain_info; + $domain_info [0] = $ipaddrs [0]; + $domain_info [1] = 1; + $ipaddrs {$ipaddr} = \@domain_info; + } # if + } # foreach + + verbose "\nFinished processing $filename"; + return %ipaddrs; +} # GetIPs + +my $logfile = "/var/log/httpd/access_log"; + +my $result = GetOptions ( + "file=s" => \$logfile, + "usage" => sub { Usage }, + "verbose" => sub { set_verbose }, + "debug" => sub { set_debug }, +) or Usage "Invalid option specified"; + +# Instantiate a new Geo::IP object +my $gi = Geo::IP::PurePerl->new ( + "/usr/local/share/GeoIP/GeoIPCity.dat", + GEOIP_STANDARD +); + +# Turn off buffering +$| = 1; + +error "Unable to open $logfile", 1 if !-f $logfile; + +my %ip_records = GetIPs $logfile; + +foreach (sort keys %ip_records) { + my ( + $country_code, + $country_code3, + $country_name, + $region, + $city, + $postal_code, + $latitude, + $longitude, + $dma_code, + $area_code) + = $gi->get_city_record ($_); + + my @domain_info = @{$ip_records {$_}}; + + display_nolf "$_\t"; + display_nolf $city ? "$city\t" : "*Unknown*\t"; + display_nolf $postal_code ? "$postal_code\t" : "*Unknown*\t"; + display_nolf $country_name ? "$country_name\t" : "*Unknown*\t"; + display $domain_info [0] . " (" . $domain_info [1] . ")"; +# print $country_code . "\n"; +# print $country_code3 . "\n"; +# print $country_name . "\n"; +# print $region . "\n"; +# print $city . "\n"; +# print $postal_code . "\n"; +# print $latitude . "\n"; +# print $longitude . "\n"; +# print $dma_code . "\n"; +# print $area_code . "\n"; +} # foreach diff --git a/bin/mkplaylist b/bin/mkplaylist new file mode 100755 index 0000000..7c3611b --- /dev/null +++ b/bin/mkplaylist @@ -0,0 +1,214 @@ +#!/usr/bin/perl +################################################################################ +# +# File: $RCSfile: mkplaylist,v $ +# Revision: $Revision: 1.5 $ +# Description: Script to generate a random playlist of x nbr files +# Author: Andrew@DeFaria.com +# Created: Wed Sep 13 09:56:55 CDT 2006 +# Modified: $Date: 2011/01/09 00:54:42 $ +# Language: Perl +# +# (c) Copyright 2006, ClearSCM, Inc., all rights reserved. +# +################################################################################ +use strict; +use warnings; + +use MP3::Info; + +use FindBin; +use lib "$FindBin::Bin/../lib"; + +use Getopt::Long; +use Display; +use OSDep; +use Utils; + +my $version = "1.0"; +my $default_music_root = "/web/Music"; + +my %opts; +my @mp3files; + +sub Usage { + my $msg = shift; + + if (defined $msg) { + dipslay $msg; + } # if + + display "Usage: $FindBin::Script: [ -verbose | -v ] [ -n ]"; + display "\t\t [ -f ] [ -m ]"; + display "\nWhere:\n"; + display " -n \t\tLimit playlist to entries (Default: 100 entires)"; + display " -verbose\t\tTurn on verbose mode (Default: verbose off)"; + display " -f \t\tWrite playlist to (Default: playlist.wpl)"; + display " -m \tStart searching at (Default: $default_music_root)"; + exit 1; +} # usage + +sub GetMusic { + my $music_dir = shift; + + opendir MUSIC, "$music_dir" + or error "Unable to open music directory $music_dir", 1; + + my @entries = grep {!/^\./} readdir MUSIC; + + my $mp3info; + + closedir MUSIC; + + foreach (@entries) { + my $entity = "$music_dir/$_"; + if (-d "$entity") { + debug "Subdirectory found - recursing to $entity..."; + GetMusic ($entity); + } else { + if (/\.mp3$/) { + debug "\t$_"; + $mp3info = MP3::Info->new ($entity); + verbose_nolf "."; + # WPL files don't like &. + if (!defined $mp3info->{FILE}) { + $mp3info->{FILE} = "Unknown"; + } else { + $mp3info->{FILE} =~ s/&/&/g; + # When we run on Linux is /web but from XP it's //Jupiter + $mp3info->{FILE} =~ s/\/web/\/\/Jupiter/; + } # if + + push @mp3files, $mp3info; + } else { + debug "-\t$_ skipped"; + } # if + } # if + } # foreach +} # GetMusic + +sub RandomizePlaylist { + my @mp3files = @_; + + my @return_titles; + + my @genres_to_skip = ( + "Audio Book", + "Educational", + "Podcast", + "Talk Radio", + ); + + verbose_nolf "Randomizing playlist (${opts {n}})..."; + + # if we are asking for more than we have then just return everything + if ($opts {n} > $#mp3files) { + $opts {n} = $#mp3files; + return @mp3files; + } # if + + # Fill @return_titles with randomly selected songs. + for (my $i = 0; $i < ${opts{n}};) { + my $random = int (rand ($#mp3files)); + + # These are random songs - not random speach. Certain genres are + # always skipped. + next unless defined $mp3files[$random]->{GENRE}; + next if InArray ($mp3files [$random]->{GENRE}, @genres_to_skip); + + # Crude beginnings to a more sophisticated selection mechanism. If + # the t option was given then only consider songs that are in the + # Genre specified by t. Note this currently loops forever if more + # songs are requested than we have. + if (defined $opts {t}) { + if ($opts {t} eq $mp3files [$random]->{GENRE}) { + # Eliminate dups. No sense in giving back the same song more + # than once. + if (!InArray $mp3files [$random], @return_titles) { + push @return_titles, $mp3files [$random]; + $i++; + } else { + debug "Eliminating dup"; + } # if + } # if + } else { + # Eliminate dups. No sense in giving back the same song more + # than once. + if (!InArray $mp3files [$random], @return_titles) { + push @return_titles, $mp3files [$random]; + $i++; + } else { + debug "Eliminating dup"; + } # if + } # if + } # for + + verbose " done"; + + return @return_titles; +} # RandomizePlaylist + +sub WritePlaylistXML { + my @playlist = @_; + + verbose "Writing playlist ${opts {f}}"; + open PLAYLIST, ">${opts {f}}" + or error "Unable to open playlist file ${opts {f}}", 1; + + # Write heading + print PLAYLIST < + + + + Andrew\@DeFaria.com Copyright (c) 2006 ($FindBin::Script V$version) + Random Playlist of ${opts {n}} songs + + + +END + + my $total_size = 0; + + # Write the songs... + foreach (@playlist) { + print PLAYLIST " {FILE}\"/>\n"; + $total_size += $_->{SIZE} + } # foreach + + # Write the footing + print PLAYLIST < + + +END + + close PLAYLIST; + verbose "${opts {n}} entries writen to ${opts {f}} totaling " . + int ($total_size / (1024 * 1024)) . " Meg"; +} # WritePlaylistXML + +# Turn off buffering +$| = 1; + +# Set the defaults +$opts {n} = 100; +$opts {f} = "random_playlist.wpl"; +$opts {m} = $default_music_root; + +my $result = GetOptions ( + \%opts, + "usage" => sub { Usage }, + "verbose" => sub { set_verbose }, + "debug" => sub { set_debug }, + "n=i", + "f=s", + "t=s", + "m=s", +) || Usage; + +verbose "Gathering information about music in ${opts {m}}..."; +GetMusic ($opts {m}); +verbose "\n" . $#mp3files . " files found"; + +WritePlaylistXML (RandomizePlaylist (@mp3files)); diff --git a/bin/nag.pl b/bin/nag.pl new file mode 100755 index 0000000..093dd0f --- /dev/null +++ b/bin/nag.pl @@ -0,0 +1,278 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: nag.pl,v $ + +Nag: A progressively more agressive reminder program. + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision: + +$Revision: 1.6 $ + +=item Created: + +Tue Jul 27 15:00:11 PDT 2004 + +=item Modified: + +$Date: 2013/06/13 14:36:03 $ + +=back + +=head1 SYNOPSIS + + Usage: nag.pl [-u|sage] [-ve|rbose] [-d|ebug] [-nos|ign] [-noe|xec] + [-not|ag] + + Where: + + -u|sage: Displays this usage + -ve|rbose: Be verbose + -d|ebug: Output debug messages + + -noe|xec: No execute mode - just echo out what would have + been done (Default: exec) + -not|ag: Tag message with a signature detailing how many + times we've sent this email and when was the last time we + sent it (Default: Don't tag) + -nos|ign: Include random signature from ~/.signatures (Default: Don't + sign) + -f|ile Use as naglist (Default: ~/.nag/list) + +=head1 DESCRIPTION + +This script read a file indicating who to remind. The format for this file is: + + ||||| + +nag.pl will change a message that was set to send on a particular day of the +week to daily after 3 messages were sent. So if you set the message to be send +on say Mon it will be sent to 3 weeks and then flip to be sent daily. + +=head1 The following things should be done to improve this system: + +=over + +=item * + +Move naglist and message files to a database + +=item * + +Change MAPS to recognize when a message is returned from a nag message. Perhaps +tag it with X-Nag: (will this come back when the user replies?). MAPS +would then white list the sender and deliver the email as well as put the nag in +a pending state. + +=cut + +use strict; +use warnings; + +use FindBin; +use Getopt::Long; + +use lib "$FindBin::Bin/../lib"; + +use DateUtils; +use Display; +use Mail; +use Utils; + +my $VERSION = '1.0'; + +my $exec = 1; +my ($tag, $sign); + +my $nagfile = "$ENV{HOME}/.nag/list"; + +sub dow () { + my @days = ('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'); + + return $days[(localtime (time)) [6]]; +} # dow + +sub sign () { + my $sigfile = "$ENV{HOME}/.signatures"; + + return unless -r $sigfile; + + my $signature = "--
"; + + open my $sigs, '<', $sigfile + or error "Unable to open signature file $sigfile - $!", 1; + + my @sigs = <$sigs>; + chomp @sigs; + + close $sigs; + + $signature .= ''; + $signature .= splice (@sigs, int (rand (@sigs)), 1); + $signature .= ''; + + return $signature; +} # sign + +sub tag ($$) { + my ($sent, $date) = @_; + + return '' + unless $sent; + + my $tagStr = '

'; + $tagStr .= "This message has been sent to you $sent time"; + + $tagStr .= 's' + if $sent > 1; + + $tagStr .= " before
"; + $tagStr .= "The last time this message was sent to you was $date
"; + $tagStr .= "$FindBin::Script $VERSION

"; + + return $tagStr; +} # tag + +## Main +GetOptions ( + usage => sub { Usage }, + verbose => sub { set_verbose }, + debug => sub { set_debug }, + 'exec!' => \$exec, + 'tag!', => \$tag, + 'sign!', => \$sign, + 'file', => \$nagfile, +) or Usage 'Invalid parameter'; + +my $nagfilenew = "$nagfile.$$"; + +open my $nagsIn, '<', $nagfile + or error "Unable to open $nagfile for read access - $!", 1; + +open my $nagsOut, '>', $nagfilenew + or error "Unable to open new nagfile $nagfilenew for write access - $!", 1; + +while (<$nagsIn>) { + if (/^#/ or /^$/) { + print $nagsOut $_; + next; + } # if + + chomp; + + my ($email, $subject, $when, $msgfile, $sent, $date) = split /\|/; + + $sent ||= 0; + + my $dow = dow; + + if ($when =~ /$dow/i or $when =~ /daily/i) { + verbose "Nagging $email with $msgfile..."; + + my $footing = ''; + + $footing = tag $sent, $date + if $tag; + + $footing .= sign + if $sign; + + my $msg; + + my $msgfilename = $msgfile; + $msgfilename =~ s/~/$ENV{HOME}/; + + open $msg, '<', $msgfilename + or error "Unable to open message file $msgfile - $!", 1; + + mail ( + to => $email, + subject => $subject, + mode => 'html', + data => $msg, + footing => $footing, + ); + + close $msg + or error "Unable to close message file $msg - $!", 1; + + $sent++; + $date = YMDHM; + $when = "Daily" + if $sent > 3; + + print $nagsOut "$email|$subject|$when|$msgfile|$sent|$date\n"; + } else { + print $nagsOut "$_\n"; + } # if +} # while + +close $nagsIn + or error "Unable to close $nagfile - $!", 1; + +close $nagsOut + or error "Unable to close $nagfilenew - $!", 1; + +rename $nagfilenew, $nagfile + or error "Unable to rename $nagfilenew to $nagfile", 1; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + DateUtils + Display + Mail + Utils + +=end man + +=begin html + +
+DateUtils
+Display
+Mail
+Utils
+
+ +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2004, ClearSCM, Inc. All rights reserved. + +=cut \ No newline at end of file diff --git a/bin/raid b/bin/raid new file mode 100755 index 0000000..3e630c6 --- /dev/null +++ b/bin/raid @@ -0,0 +1,1361 @@ +#!/usr/local/bin/perl +use strict; +use warnings; + +=pod + +=head1 NAME $RCSfile: raid,v $ + +RAiD: Real Aid in Debugging + +This script will dynamically load C functions described in a .h file +and provide a command line interface to calling those functions. + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.1 $ + +=item Created: + +Fri Apr 29 11:58:36 PDT 2011 + +=item Modified: + +$Date: 2012/04/13 18:14:02 $ + +=back + +=head1 SYNOPSIS + + Usage raid: [-u|sage] [-verb|ose] [-deb|ug] [-vers|ion] [-rc ] + [-lo|ad <.h file>] [-li|b <.a file>] [-h|istfile ] + [-t|imeout ] + + Where: + -u|sage: Displays usage + + -verb|ose: Be verbose + -deb|ug: Output debug messages + -vers|ion: Display raid's version and exit + + -rc : Directory to find "run commands" + -lo|ad : A module to load + -li|b : A library to load + -h|istfile : Use as history file. + -t|imeout : Set the timeout to n seconds (Default: 5 seconds) + +=head1 DESCRIPTION + +This script loads functions defined in a C module so that Perl can +call them dirctly. A C module is defined to be a set of files, a .h +file and a .a (or .so) file. The .h file must have specific comments +in it to identify things properly for raid. These are: + +=over + +=item prototype + +A prototype line that describes the C function to call + +=item user input + +A user input string which, when matched, tells raid to call the +corresponding C function. + +=item help (optional) + +A short help string that describes the function. + +=item description (optional) + +A longer description string that can span multiple lines. + +=item category: + +A category - either 0 or 1 - defining the category of call. Normally +this is 1 for type 1 calls. Type 1 calls communicate with the backend +through debugsh using TIPC and have their output paged. Type 0 calls +do not use debugsh and are pure C functions. Any output from type 0 +calls are written directly to STDOUT and are not paged. + +=back + +Other comments can appear that we will just skip. + +The format of comments must be close to: + + int add (int a, int b); + /********************************************************** + prototype: int add (int a, int b) + user input: myadd + category: 0 + help: Add two numbers together + description: Because Perl's add is not good enough + **********************************************************/ + ... + int subtract (int a, int b) + /********************************************************** + prototype: int subtract (int a, int b) + user input: mysub + category: 0 + help: Subtract b from a + description: Because Perl's subtract is not good enough + **********************************************************/ + ... + void printit (char *s, int i, double f) + /********************************************************** + prototype: void printit (char *s, int i, double f) + user input: printer + category: 0 + help: Print some different datatypes + description: A simple routine to print out some different + datatypes. Note the void return. + + Turns out void returns are OK but void parms... not so good + **********************************************************/ + ... + void backendCall (char *s, int i, double f) + /********************************************************** + prototype: void backendCall (int i) + user input: call back end + category: 1 + help: This calls the back end passing it an int + **********************************************************/ + +=head1 Autoloading + +Raid preloads cmds by parsing all .h files in the rc directory. From +there it learns of all potential commands that can be loaded. A .h +filename is called the "module name". If a call is made to a function +raid checks to see if the module has been loaded. If not it loads the +module using rc/.h and lib/lib.[a|so]. A module is only +loaded once. See modules command to see what modules have been loaded. + +=head1 TYPEMAPS + +Inline uses the default Perl typemap file for its default types. This +file is called /usr/local/lib/perl5/5.6.1/ExtUtils/typemap, or +something similar, depending on your Perl installation. It has +definitions for over 40 types, which are automatically used by +Inline. (You should probably browse this file at least once, just to +get an idea of the possibilities.) + +Inline parses your code for these types and generates the XS code to +map them. The most commonly used types are: + +=over + +=item int + +=item long + +=item double + +=item char* + +=item void + +=item SV* + +=back + +If you need to deal with a type that is not in the defaults, just use +the generic SV* type in the function definition. Then inside your +code, do the mapping yourself. Alternatively, you can create your own +typemap files and specify them using the TYPEMAPS configuration +option. + +Note that the presence of a file named typemap along side your .h and +.a file should work. + +TYPEMAPS specifies a typemap file that defines non-standard C types +and how they relate to Perl types. + +=head1 COMMAND LINE + +Raid implements a command line with full ReadLine support. It +maintains a history stack of your commands for convenient recall as +well as audit purposes. Try using the arrow keys or C-p, C-n, C-r +Emacs bindings. History is saved between sessions in ~/.raid_hist. + +There is a small help facility. Type help to get a listing of raid +commands as well as the currently loaded C functions. Also, "help " will display the detailed help provided in the .h file +(if any). + +=head1 One liners + +You can also call raid and give is a parameter on the command line +which would be a command to execute. This command may need to be +quoted if any spaces or other special characters occur in the command. + +=head1 Exit status + +Raid sets $? equal to the return of the last function called. If the +last function called returns a string then raid will set $? equal to 1 +if the string has anything in it or 0 if it is empty or undefined. + +=head1 Colors + +For those of your who are color averse, simply export +ANSI_COLORS_DISABLED to some value and all coloring will be turned +off. Or use the color off|on command. + +=head1 More information + +For more information see the internal wiki page: + +=over + +=item . + +L + +=item . + +L + +=back + +=cut + +use Config; +use Getopt::Long; +use FindBin; +use File::Spec; +use File::Basename; +use IO::Handle; + +use Term::ANSIColor qw (color); + +# Add our lib directory as well as the appropraite lib areas below "lib" that +# contain things like our local copy of Term::ReadLine::Gnu and Inline::C. +use lib "$FindBin::Bin/lib", + "$FindBin::Bin/lib/perl5/site_perl", + "$FindBin::Bin/lib/lib64/", + "$FindBin::Bin/lib/lib64/perl5/site_perl"; + +use CmdLine; +use GetConfig; +use Display; +use Utils; + +use constant DBGSH_APPID => 300; + +my $VERSION = '$Revision: 1.1 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +my (%opts, %funcs, %allcmds, %modules, $debugshVer); + +%opts = GetConfig "$FindBin::Bin/etc/$FindBin::Script.conf"; + +my $debugshPid; +my $name = 'RAiD'; + +error "$name is not supported on 64 bit versions of Perl", 1 + if $Config{archname} =~ /64/; + +my %raidCmds = ( + appiddisplay => { + help => 'appiddisplay', + description => 'Displays App ID information', + }, + + appidclear => { + help => 'appidclear ', + description => 'Clears the specified App ID index', + }, + + cmds => { + help => 'cmds []', + description => 'Lists currently loaded commands (matching ).', + }, + + debug => { + help => 'debug []', + description => 'Turn on|off debuging of raid and debugsh. With no options displays +status of debug.', + }, + + exit => { + help => 'exit', + description => "Exits $name.", + }, + + modules => { + help => 'modules', + description => 'Displays all available modules', + }, + + perl => { + help => 'perl ', + description => 'Evaluate a Perl expression. Must be on one line.', + }, + + quit => { + help => 'quit', + description => "Quits $name.", + }, + + restart => { + help => 'restart', + description => "Reinitializes $name", + }, + + timeout => { + help => 'timeout []', + description => 'Set timeout to seconds. If n = 0 then timeout is disabled. Without just show current timeout value.', + }, + + version => { + help => 'version', + description => 'Displays version information.', + }, +); + +use Inline; + +my $PROMPT; + +# Seed PATH and LD_LIBRARY_PATH (Hack) +$ENV{PATH} = "/usr/wichorus/sysroot/usr/bin:/usr/wichorus/sysroot/usr/libexec/gcc/i386-redhat-linux/4.1.2:$ENV{PATH}"; +$ENV{LD_LIBRARY_PATH} = "/usr/wichorus/sysroot/usr/lib"; + +my ($cmdline, $attribs, $line, $result, $dsh); + +sub terminateDebugSh () { + if ($debugshPid) { + kill HUP => $debugshPid; + + waitpid $debugshPid, 0; + + my $result = DbgShRaidUnRegister (); + + warning "DbgShRaidRegister returned $result" + if $result; + + # Close old debugsh if we are reinitializing + close $dsh if $dsh; + + undef $dsh; + } # if + + return; +} # terminateDebugSh + +sub set_prompt (;$$) { + my ($cmd, $nbr) = @_; + + my $ignstart = $CmdLine::cmdline->{ignstart}; + my $ignstop = $CmdLine::cmdline->{ignstop}; + + my $prompt; + + if ($opts{color}) { + return $ignstart . color ('cyan') . $ignstop . $name + . $ignstart . color ('reset') . $ignstop . ' <' + . $ignstart . color ('yellow') . $ignstop . '\#' + . $ignstart . color ('reset') . $ignstop . '> '; + } else { + return "$name <#>"; + } # if +} # set_prompt + +sub moduleName ($) { + my ($file) = @_; + + my ($module, $path, $suffix) = fileparse ($file, ('\.a$', '\.so$', '\.h$')); + + $module =~ s/lib//; + + return $module; +} # moduleName + +sub parseh ($) { + my ($h) = @_; + + my %funcs; + + unless (-f $h) { + error "Unable to open file $h - $!"; + return; + } # unless + + open my $file, '<', $h + or error "Unable to open $h", 1; + + my ( + $indefinition, + $userinput, + $funcname, + $help, + $description, + $module, + $prototype, + $parms, + $returntype, + $type + ); + + while (<$file>) { + chomp; chop if /\r$/; + + if (/^\/\*{5,}/) { + $indefinition = 1; + $type = 0; + } elsif (/^\*{5,}/) { + error 'Missing user input keyword', 1 + unless $userinput; + + # We need to loop through and make sure that this new user input string + # does not previously appear, even if abbreviated. So we can't have say + # a new command - "my command" - when we already had a command such as + # "my command is nice". + foreach (keys %funcs) { + error "Ambiguous command \"$userinput\" & \"$_\" found in $h", 1 + if /^$userinput /; + } # foreach + + # Now test for the other way where we already have "my command" in %funcs + # and we are trying to add "my command is nice". + my $str; + + foreach my $word (split /\s+/, $userinput) { + if ($str) { + $str .= " $word"; + } else { + $str .= $word; + } # if + + # See if this exactly matches any existing key + error "Ambiguous command \"$userinput\" & \"$_\" found in $h", 1 + if $funcs{$str}; + } # foreach + + $funcs{$userinput}{funcname} = $funcname; undef $funcname; + $funcs{$userinput}{help} = $help; undef $help; + $funcs{$userinput}{description} = $description; undef $description; + $funcs{$userinput}{module} = $module; undef $module; + $funcs{$userinput}{prototype} = $prototype; undef $prototype; + $funcs{$userinput}{parms} = $parms; undef $parms; + $funcs{$userinput}{returntype} = $returntype; undef $returntype; + $funcs{$userinput}{type} = $type; undef $type; + + undef $userinput; + } elsif ($indefinition and $_ =~ /^\s*user input:\s*(.+)/i) { + $userinput = $1; $userinput =~ s/\s*$//; + } elsif ($indefinition and $_ =~ /^\s*prototype:\s*(.+);*/i) { + $prototype = $1; $prototype =~ s/\s*$//; + + while ($prototype !~ /\);*\s*$/) { + my $line = <$file>; + + if ($line) { + chomp; chop if /\r$/; + + # Trim + $line =~ s/^\s+//; + $line =~ s/\s+$//; + + $prototype .= $line; + } else { + error "Unterminated function prototype found in $h", 1; + } # if + } # while + + my $str = $prototype; + + # Remove annoying spaces around delimiters only + $str =~ s/\s*(\*|\(|\)|\,)\s*/$1/g; + + my @parts = split /(\s+|\(|\)|\*)/, $str; + + # Handle the case where prototype lacks a return type (technically + # invalid but we're such nice guys...). Note we simply assume they meant + # "void" for a return type. + if ($parts[1] eq '(') { + $funcname = $parts[0]; + $returntype = 'void'; + $parms = join '', @parts[1..$#parts]; + } elsif ($parts[1] eq '*') { + $funcname = $parts[2]; + $returntype = "$parts[0]*"; + $parms = join '', @parts[3..$#parts]; + } else { + $funcname = $parts[2]; + $returntype = $parts[0]; + $parms = join '', @parts[3..$#parts]; + } # if + + $module = moduleName $h; + } elsif ($indefinition and $_ =~ /^\s*help:\s*(.*)/i) { + $help = $1; $help =~ s/\s*$//; + } elsif ($indefinition and $_ =~ /^\s*description:\s*(.*)/i) { + my $desc = $1; $desc =~ s/\s*$//; + + $desc =~ s/^\s+//; + + $description = $desc unless $desc eq ''; + $indefinition = 2; + } elsif ($indefinition and $_ =~ /^\s*category:\s*(\d+)/i) { + $type = $1; + } elsif ($indefinition and $indefinition == 2) { + if (/\*{5,}/) { + $indefinition = 0; + next; + } else { + s/^\s+//; + + if ($description) { + $description .= "\n$_"; + } else { + $description = $_; + } # if + } # if + } # if + } # while + + close $file; + + return %funcs; +} # parseh + +sub loadModules ($) { + my ($rcdir) = @_; + + # Load all known commands by combing through $FindBin::Bin/rc/*.h + opendir my $rc, $rcdir + or error "Unable to opendir $rcdir", 1; + + my %moduleFuncs; + my @modules = grep { !/^\./ } readdir $rc; + @modules = grep { /.+\.h$/ } @modules; + + closedir $rc; + + foreach (@modules) { + my $moduleFile = "$rcdir/$_"; + my $module = moduleName $moduleFile; + my %funcs = parseh $moduleFile; + + foreach (keys %funcs) { + error "Duplicate definition $_ found in $moduleFile", 1 + if defined $moduleFuncs{$_}; + + $moduleFuncs{$_} = $funcs{$_}; + } # foreach + + $modules{$module} = { + moduleFile => $moduleFile, + loaded => 0, + }; + } # foreach + + return %moduleFuncs; +} # loadModules + +sub modules () { + my ($moduleName, $moduleStatus, $moduleFile); + + format STDOUT = +@<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<< +$moduleName,$moduleStatus +. + foreach $moduleName (sort keys %modules) { + next if $moduleName eq 'DbgSh'; + + $moduleStatus = ($modules{$moduleName}{loaded}) ? 'loaded' : 'not loaded'; + + write; + } # foreach + + return; +} # modules + +sub load ($;$) { + my ($file, $lib) = @_; + + my ($module, $path, $suffix) = fileparse ($file, ('\.a$', '\.so$', '\.h$')); + + $module =~ s/lib//; + $path =~ s/^inc\///; + + display_nolf color ('dark') . "Loading $module..." . color ('reset'); + + my $hfile; + + if (-f "$path$module.h") { + $hfile = "$path$module.h"; + } elsif (-f "${path}inc/$module.h") { + $hfile = "${path}inc/$module.h"; + } # if + + unless ($hfile) { + display ''; + error "Unable to load $module - .h file missing"; + return; + } # unless + + my $libfile; + + if ($lib and -f $lib) { + $libfile = $lib; + } elsif (-f "${path}lib$module.a") { + $libfile = "${path}lib$module.a"; + } elsif (-f "${path}lib$module.so") { + $libfile = "${path}lib$module.so"; + } elsif (-f "${path}lib/lib$module.a") { + $libfile = "${path}lib/lib$module.a"; + } elsif (-f "${path}lib/lib$module.so") { + $libfile = "${path}lib/lib$module.so"; + } elsif (-f "${path}../lib/lib$module.a") { + $libfile = "${path}../lib/lib$module.a"; + } elsif (-f "${path}../lib/lib$module.so") { + $libfile = "${path}../lib/lib$module.so"; + } # if + + unless ($libfile) { + display ''; + error "Unable to load $module - .a or .so file missing"; + return; + } # unable + + # Need absolute pathname for -L + my $libpath; + + (undef, $libpath, $libfile) = + File::Spec->splitpath (File::Spec->rel2abs ($libfile)); + + # Strip trailing "/", if any + $libpath =~ s/\/$//; + + # Compose $libs + my $devWinfraLibPath = "$FindBin::Bin/../../../../9200_packetcore/" + . "packetcore/infra/lib/src"; + my $prodWinfraLibPath = '/usr/wichorus/lib'; + my $devDbgShLibPath = "$FindBin::Bin/lib"; + my $libs = "-L$libpath -L$libpath/lib -L$devWinfraLibPath -L$devDbgShLibPath " + . "-L$prodWinfraLibPath -l$module -lDbgSh -lwinfra -lrt"; + $libs .= " $opts{additionallibs}" if $opts{additionallibs}; + + verbose "Binding C functions defined in $hfile"; + debug "Loading module $module"; + debug "libs = $libs"; + + my ($status, @output) = Execute 'uname -r'; + + if ($output[0] =~ /WR3.0.2ax_cgl/) { + my $sysroot = '/usr/wichorus/sysroot'; + + Inline->bind ( + C => $hfile, + CC => "$sysroot/usr/bin/gcc", + LD => "$sysroot/usr/bin/ld", + CCFLAGS => "-I$sysroot/usr/include -I$sysroot/usr/lib/gcc/i386-redhat-linux/4.1.2/include", + LDDLFLAGS => "-fPIC -shared -O2 -L$sysroot/usr/lib -L/usr/local/lib", + LIBS => $libs, + ENABLE => 'AUTOWRAP', + FORCE_BUILD => $opts{build}, + BUILD_NOISY => $opts{noisy}, + CLEAN_AFTER_BUILD => $opts{clean}, + PRINT_INFO => $opts{info}, + ); + } else { + Inline->bind ( + C => $hfile, + LIBS => $libs, + ENABLE => 'AUTOWRAP', + FORCE_BUILD => $opts{build}, + BUILD_NOISY => $opts{noisy}, + CLEAN_AFTER_BUILD => $opts{clean}, + PRINT_INFO => $opts{info}, + ); + } # if + + # Now the module's loaded + $modules{$module}{loaded} = 1; + $modules{$module}{moduleFile} = $hfile; + + $CmdLine::cmdline->set_prompt (set_prompt); + + # Rebuild %allcmds + %allcmds = %raidCmds; + + $allcmds{$_} = $funcs{$_} foreach (keys %funcs); + + # Set cmds + $CmdLine::cmdline->set_cmds (%allcmds); + + display color ('dark') . 'done' . color ('reset'); + + return 1; +} # load + +sub getOutput () { + my ($status, @output) = (0, ()); + + debug "ENTER: getOutput"; + + eval { + while (<$dsh>) { + debug "read: $_"; + if (/\s*DBGSH\s*\[$debugshPid\]:\s*(\d+)$/) { + debug "Found DBGSH line - status = $1"; + $status = $1; + last; + } # if + + # Trim output of both \n and \r; + chomp; chop if /\r$/; + + debug "Pushing '$_' on output"; + push @output, $_ + } # while + }; + + if ($@ =~ /Operation aborted/) { + debug "Operation aborted - cleaning pipe"; + + # Need to remove debris from the pipe + while (<$dsh>) { + debug "Found debris: $_"; + + if (/\s*DBGSH\s*\[$debugshPid\]:\s*(\d+)$/) { + debug "Found DBSH line - status = $1"; + $status = $1; + last; + } # if + } # while + + debug "Returning error $@"; + return (undef, ($@)); + } else { + debug "Returning output (Status: $status)"; + return ($status, @output); + } # if +} # getOutput + +sub debugshInit () { + my @debugsh = ($opts{debugsh}); + + push @debugsh, '2>&1'; + + local $SIG{INT} = 'IGNORE'; + + $debugshPid = open $dsh, '-|', @debugsh + or error "Unable to start pipe for $opts{debugsh}", 1; + + # Turn off buffering on $dsg + $dsh->autoflush (1); + + # Temporarily turn off eval + my $oldEval = $CmdLine::cmdline->set_eval; + + # Set DEBUGSHPID + $CmdLine::cmdline->_set ('DEBUGSHPID', $debugshPid); + + # Turn eval back on + $CmdLine::cmdline->set_eval ($oldEval); + + # Load our interface to DbgSh lib + load "$FindBin::Bin/DbgShRaidAPI", "$FindBin::Bin/lib/libDbgSh.a"; + + $debugshVer = GetDbgShVer (); + + # Check verion of debugsh + my $minimumVer = '0.3.0'; + + error "Debugsh Version $debugshVer must be >= $minimumVer", 1 + if compareVersions ($debugshVer, $minimumVer) == -1; + + DbgShRaidRegister ($debugshPid); + + if (get_debug) { + DbgShProcessUserInput (DBGSH_APPID, $debugshPid, 'SetDebug'); + + my ($result, @output) = getOutput; + + $CmdLine::cmdline->_set ('result', $result); + + $CmdLine::cmdline->handleOutput ('', @output); + + error "$line was not successful (Result: $result)" + if $result; + } # if + + return; +} # debugshInit + +END { + terminateDebugSh; +} # END + +sub interrupt () { + display_nolf + color ('yellow') + . '' + . color ('reset') + . '... ' + . color ('red') + . "Abort current operation (y/N)?" + . color ('reset'); + + my $response = ; + chomp; + + if ($response =~ /(^y$|^yes$)/i) { + DbgShProcessUserInput (DBGSH_APPID, $debugshPid, 'Interrupted'); + die "Operation aborted\n"; + } # if + + display color ('cyan') . 'Continuing...' . color ('reset'); +} # interrupt + +sub init () { + # Stop debugsh if it was running + terminateDebugSh; + + # Intialize functions (Type 1 commands) + if (-d $opts{rc}) { + # Load %funcs with all type 1 commands. Nothing is loaded by this. Loading + # (actually binding) of C libraries is done automatically when the command + # is called. + %funcs = loadModules $opts{rc}; + } else { + %funcs = (); + + warning "Unable to find RC commands in $opts{rc}"; + } # if + + # Load commands from config file (Type 2 commands) + foreach (keys %opts) { + my $cmd; + + if (/^type2_(\S+)/) { + $cmd = $1; + #$cmd =~ s/_/ /g; + } else { + next; + } # if + + $funcs{$cmd} = { + appID => $opts{$_}, + type => 2, + prototype => "$cmd ", + help => "Send (AppID $opts{$_}) to debugsh", + }; + } # foreach + + # Now combine %funcs, which contain all type 1 and type 2 commands, and + # %raidCmds, which contain raid commands like load, unload, perl, restart, + # etc. + %allcmds = %raidCmds; + + foreach (keys %funcs) { + $allcmds{$_} = $funcs{$_}; + } # foreach + + # Initialize debugsh + my $result = debugshInit; + + error "Unable to initialize debugsh", $result + if $result; +} # init + +sub compareVersions ($$) { + my ($version1, $version2) = @_; + + $version1 =~ s/\.//g; + $version2 =~ s/\.//g; + + return $version1 <=> $version2; +} # compareVersions + +sub setVersionStr () { + my $raidVersionStr = color ('cyan') + . $name + . color ('reset') + . color ('dark') + . ' (Real Aid in Debugging) ' + . color ('reset') + . color ('green') + . 'Version ' + . color ('reset') + . color ('yellow') + . $VERSION + . color ('reset'); + + my $debugshVerStr = color ('cyan') + . 'Debug Shell Core ' + . color ('green') + . 'Version ' + . color ('yellow') + . $debugshVer + . color ('reset'); + + return $raidVersionStr . "\n" . $debugshVerStr; +} # setVersionStr + +sub cmds ($%) { + my ($cmd, %funcs) = @_; + + if (keys %funcs == 0) { + warning "Nothing loaded"; + return; + } else { + my @output; + my @colors = (color ('dark'), color ('magenta'), color ('green')); + + my $searchStr; + + if ($cmd and $cmd =~ /^\s*(\w+)/) { + $searchStr = $1; + } # if + + foreach (sort { + $funcs{$a}{type} <=> $funcs{$b}{type} || + $a cmp $b + } keys %funcs) { + if ($searchStr) { + next + unless /$searchStr/i; + } # if + + my $color = ''; + + $color = $colors[$funcs{$_}{type}] + if $colors[$funcs{$_}{type}]; + + my $cmdName = $_; + + my $boldOn = ''; + my $boldOff = ''; + + if ($funcs{$_}{type} == 1) { + $boldOn = color ('white on_magenta'); + $boldOff = color ('reset') . $color; + } elsif ($funcs{$_}{type} == 2) { + $boldOn = color ('white on_green'); + $boldOff = color ('reset') . $color; + } # if + + if ($searchStr) { + $cmdName =~ s/($searchStr)/$boldOn$1$boldOff/; + } # if + + my $line = $color . $cmdName; + $line .= " $funcs{$_}{parms}" if $funcs{$_}{parms}; + $line .= color ('reset'); + $line .= " - $funcs{$_}{help}" if $funcs{$_}{help}; + + push @output, $line; + } # foreach + + $CmdLine::cmdline->handleOutput ('', @output); + } # if + + return; +} # cmds + +sub timeout (;$) { + my ($timeout) = @_; + + my ($result, @output); + + if ($timeout) { + if ($timeout < 0 or $timeout > 100) { + error "Timeout must be between 0 and 100"; + + $CmdLine::cmdline->_set ('result', 1); + + return; + } # if + + DbgShProcessUserInput (DBGSH_APPID, $debugshPid, "SetTimeout $timeout"); + + ($result, @output) = getOutput; + + $CmdLine::cmdline->_set ('result', $result); + + $CmdLine::cmdline->handleOutput ('', @output); + + error "Unable to set timeout (Result: $result)" + if $result; + } else { + DbgShProcessUserInput (DBGSH_APPID, $debugshPid, 'GetTimeout'); + + ($result, @output) = getOutput; + + $CmdLine::cmdline->_set ('result', $result); + + $CmdLine::cmdline->handleOutput ('', @output); + + error "Unable to get timeout (Result: $result)" + if $result; + } # if +} # timeout + +sub callc ($@) { + my ($cmd, @parms) = @_; + + # Check to see if we know about this $cmd + my $found; + + foreach (keys %funcs) { + next unless /^$cmd$/i; + + if ($cmd eq $_) { + $found = 1; + last; + } # if + } # foreach + + unless ($found) { + error "Unknown command: $cmd"; + + return; + } # unless + + # Check to see if the module's been loaded + unless ($modules{$funcs{$cmd}{module}}{loaded}) { + if ($funcs{$cmd}{module}) { + unless (load $modules{$funcs{$cmd}{module}}{moduleFile}) { + error "Unable to load module for $cmd"; + return; + } # unless + } else { + error "Undefined module for $cmd"; + return; + } # if + } # unless + + my ($result, @output); + + no strict; + + eval { + $result = &{$funcs{$cmd}{funcname}} (@parms); + }; + + use strict; + + if ($@) { + display_nolf $@; + + return -1; + } else { + return $result + unless $funcs{$cmd}{type} == 1; + + ($result, @output) = getOutput; + + $CmdLine::cmdline->handleOutput ($cmd, @output); + + return $result; + } # if +} # callc + +sub evaluate ($) { + my ($line) = @_; + + my $result = $CmdLine::cmdline->_get('result'); + my @parms; + + if ($line =~ /^\s*(exit|quit)\s*$/i) { + unless ($result) { + exit 0; + } elsif ($result =~ /^\s*(\d+)\s*$/) { + exit $1; + } else { + exit 1; + } # if + } elsif ($line =~ /^\s*version/i) { + display setVersionStr; + return; + } elsif ($line =~ /^\s*cmds\s+(.*)/i) { + cmds $1, %funcs; + return; + } elsif ($line =~ /^\s*cmds\s*$/i) { + cmds undef, %funcs; + return; + } elsif ($line =~ /^\s*restart\s*$/i) { + init; + return; + } elsif ($line =~ /^\s*debug\s+(\S+)/i) { + my @output; + + if ($1 =~ /(1|on)/i) { + set_debug 1; + + DbgShProcessUserInput (DBGSH_APPID, $debugshPid, 'SetDebug'); + + ($result, @output) = getOutput; + + $CmdLine::cmdline->_set ('result', $result); + + $CmdLine::cmdline->handleOutput ($line, @output); + + error "$line was not successful (Result: $result)" + if $result; + + return; + } elsif ($1 =~ /(0|off)/i) { + set_debug 0; + + DbgShProcessUserInput (DBGSH_APPID, $debugshPid, 'ClearDebug'); + + ($result, @output) = getOutput; + + $CmdLine::cmdline->_set ('result', $result); + + $CmdLine::cmdline->handleOutput ($line, @output); + + error "$line was not successful (Result: $result)" + if $result; + + return; + } else { + error "Unknown command: $line"; + return; + } # if + } elsif ($line =~ /^\s*timeout\s+([-+]*\d+)/i) { + timeout $1; + + return; + } elsif ($line =~ /^\s*timeout\s*$/i) { + timeout; + + return; + } elsif ($line =~ /^\s*debug\s*$/) { + if (get_debug) { + display 'Debug is currently on'; + } else { + display 'Debug is currently off'; + } # if + + return; + } elsif ($line =~ /^\s*appiddisplay\s*$/i) { + DbgShAppIdInfo (); + return; + } elsif ($line =~ /^\s*appidclear\s+(\d+)\s*$/i) { + DbgShAppIdClearIdx ($1); + return; + } elsif ($line =~ /^\s*perl\s*(.*)/) { + # Need to turn off scrict for eval + eval "no strict; $1; use strict"; + + $result = $@ ne ''; + } elsif ($line =~ /^\s*modules\s*$/i) { + modules; + return; + } elsif ($line =~ /^\s*(.+)\s*$/) { + my @userinput = split /[,\s\t]+/, $1; + my $userinput = join ' ', @userinput; + my $funcname = $userinput[0]; + + # We have a slight problem here. It is possible for a type 1 command and a + # type 2 command to clash. For example, if a type 1 command is defined as + # "ckt show id" then that will conflict with the type 2 command "ckt". In + # such cases which do we call? + # + # Here's what we do. We favor type 1 calls (as they are the future). If we + # do not find a type 1 call we'll check for a type 2. If we find neither + # then we have an unknown command situation. + # + # If we find a type 1 command but no type 2 then we simply execute the type + # 1 command. + # + # If we do not find a type 1 command but find a type 2 command then we + # simply execute the type 2 command. + # + # However if we find a type 1 command *and* we find a type 2 command we have + # and error situation so we give an error. + + # Search for type 1 command + while ($userinput ne '') { + last if $funcs{$userinput} and $funcs{$userinput}{type} != 2; + + unshift @parms, pop @userinput; + + $userinput = join ' ', @userinput; + } # while + + if ($userinput eq '') { + # No type 1 command - check type 2 + if ($funcs{$funcname} and $funcs{$funcname}{type} == 2) { + my @output; + + # Strip off any thing that begins with "\S+_" + $line =~ s/^\s*\S+_(.+)/$1/; + + DbgShProcessUserInput ($funcs{$funcname}{appID}, $debugshPid, $line); + + ($result, @output) = getOutput; + + $CmdLine::cmdline->_set ('result', $result); + + $CmdLine::cmdline->handleOutput ($line, @output); + + error "$line was not successful (Result: $result)" + if $result; + + return; + } else { + error "Unknown command: $line"; + + return; + } # if + } else { + # We've found a type 1 command but is there a clashing type 2 command? + if ($funcs{$funcname} and $funcs{funcname}{type} == 2) { + error "Clash between type 1 and type 2 commands for $funcname"; + + return; + } # if + } # if + + # Process parms + foreach my $parm (@parms) { + # Strip () if they are there + $parm =~ s/^\s*\(//; + $parm =~ s/\)\s*$//; + + # Trim + $parm =~ s/^\s+//; + $parm =~ s/\s+$//; + + $parm = oct ($parm) if $parm =~ /^0/; + } # foreach + + $result = callc $userinput, @parms; + } else { + error "Unknown command: $line"; + + return; + } # if + + $CmdLine::cmdline->_set ('result', $result) + if $result; + + return $result +} # evalulate + +# Main +$| = 1; + +$CmdLine::cmdline->_set ('result', 1); + +set_me $name; + +$opts{histfile} = $ENV{RAID_HISTFILE} + ? $ENV{RAID_HISTFILE} + : '.raid_hist'; +$opts{debugsh} = $ENV{RAID_DEBUGSH} + ? $ENV{RAID_DEBUGSH} + : "$FindBin::Bin/debugsh"; +$opts{load} = $ENV{RAID_LOAD} + ? $ENV{RAID_LOAD} + : undef; +$opts{lib} = $ENV{RAID_LIB} + ? $ENV{RAID_LIB} + : undef; +$opts{additionalLibs} = $ENV{RAID_ADDITIONALLIBS} + ? $ENV{RAID_ADDITIONALLIBS} + : ''; +$opts{rc} = $ENV{RAID_RC} + ? $ENV{RAID_RC} + : "$FindBin::Bin/rc"; +$opts{build} = 1; +$opts{clean} = 1; +$opts{color} = 1; + +GetOptions ( + \%opts, + 'verbose' => sub { set_verbose }, + 'debug' => sub { set_debug }, + 'usage' => sub { Usage }, + 'rc=s', + 'load=s', + 'lib=s', + 'histfile=s', + 'debugsh=s', + 'timeout=i', + 'additionallibs=s', + 'noisy!', + 'build!', + 'clean!', + 'info!', + 'version', +) || Usage; + +if ($opts{version}) { + display "$name Version $VERSION"; + exit; +} # if + +$SIG{INT} = \&interrupt; + +init; + +timeout $opts{timeout} if $opts{timeout}; + +load $opts{load}, $opts{lib} + if $opts{load}; + +# Single execution from command line +if ($ARGV[0]) { + my $result = evaluate join ' ', @ARGV; + + $result ||= 1; + + exit $result; +} # if + +my ($cmd, @parms); + +$CmdLine::cmdline->set_histfile ($opts{histfile}) + if $opts{histfile}; + +$CmdLine::cmdline->set_prompt (set_prompt); +$CmdLine::cmdline->set_cmds (%allcmds); +$CmdLine::cmdline->set_eval (\&evaluate); + +while ((($line, $result) = $CmdLine::cmdline->get)) { + last unless defined $line; + next if $line =~ /^\s*($|\#)/; + + $result = evaluate $line; + + if (defined $result) { + if (ref \$result eq 'SCALAR') { + if ($line =~ /^\s*(\S+)/) { + $cmd = $1; + } # if + + # We used to output only for raidcmds... + $CmdLine::cmdline->handleOutput ($line, split /\n/, $result); + } else { + display "Sorry but I cannot display structured results"; + } # if + } # if + + $CmdLine::cmdline->set_prompt (set_prompt $cmd); +} # while + +$result = $CmdLine::cmdline->_get ('result'); + +unless ($result) { + exit 0; +} elsif ($result =~ /^\s*(\d+)\s*$/) { + exit $1; +} else { + exit 1; +} # if \ No newline at end of file diff --git a/bin/rexec b/bin/rexec new file mode 100755 index 0000000..39359d4 --- /dev/null +++ b/bin/rexec @@ -0,0 +1,326 @@ +#!/usr/local/bin/perl +################################################################################ +# +# File: $RCSfile: rexec,v $ +# Revision: $Revision: 1.1 $ +# Description: Remotely run processes on other machines +# Author: Andrew@ClearSCM.com +# Created: Tue Jan 8 15:57:27 MST 2008 +# Modified: $Date: 2008/02/29 15:09:15 $ +# Language: perl +# +# (c) Copyright 2008, ClearSCM, Inc., all rights reserved +# +################################################################################ +use strict; +use warnings; + +use FindBin; +use Getopt::Long; +use Term::ANSIColor qw(:constants); +use POSIX ":sys_wait_h"; + +my $libs; + +BEGIN { + $libs = $ENV{SITE_PERLLIB} ? $ENV{SITE_PERLLIB} : "$FindBin::Bin/lib"; + + die "Unable to find libraries\n" if !$libs and !-d $libs; +} + +use lib $libs; + +use Display; +use Logger; +use Machines; +use Rexec; +use Utils; + +our $_host; +our $_skip = 0; +our $_currentHost; + +my $_log = 0; +my $_quiet = 0; +my $_alternateFile; +my $_parallel = 0; + +my $_totalMachines = 0; +my $_totalExecutions = 0; +my $_totalFailures = 0; +my $_totalConnectFailures = 0; +my $_totalSkips = 0; + +my (%_workerStatuses, %_workerNames); + +sub Usage { + my $msg = shift; + + display "ERROR: $msg\n" if defined $msg; + + display "rexec\t[-v] [-d] [-u] "; + display "\t-v\tTurn on verbose mode"; + display "\t-d\tTurn on debug mode"; + display "\t-u\tThis usage message"; + display "\tCommand to execute remotely"; + + exit 1; +} # Usage + +sub printStats { + display YELLOW . "Machines: " . RESET . "$_totalMachines " . + MAGENTA . "Executions/Failures: " . RESET . "($_totalExecutions/$_totalFailures) " . + BLUE . "Connect Failures/Skips: " . RESET . "($_totalConnectFailures/$_totalSkips)"; +} # printStats + +sub Interrupted { + use Term::ReadKey; + + display BLUE . "\nInterrupted execution on $_host" . RESET; + + printStats; + + display_nolf "Executing on " . YELLOW . $_host . RESET . " - " + . GREEN . BOLD . "S" . RESET . GREEN . "kip" . RESET . ", " + . CYAN . BOLD . "C" . RESET . CYAN . "ontinue" . RESET . " or " + . MAGENTA . BOLD . "A" . RESET . MAGENTA . "bort run" . RESET . " (" + . GREEN . BOLD . "s" . RESET . "/" + . CYAN . BOLD . "C" . RESET . "/" + . MAGENTA . BOLD . "a" . RESET . ")?"; + + ReadMode ("cbreak"); + my $answer = ReadKey (0); + ReadMode ("normal"); + + if ($answer eq "\n") { + display "c"; + } else { + display $answer; + } # if + + $answer = lc $answer; + + if ($answer eq "s") { + *STDOUT->flush; + display "Skipping $_host"; + $_skip = 1; + $_totalSkips++; + } elsif ($answer eq "a") { + display RED . "Aborting run". RESET; + printStats; + exit; + } else { + display "Continuing..."; + $_skip = 0; + } # if +} # Interrupted + +sub workerDeath { + while ((my $worker = waitpid (-1, WNOHANG)) > 0) { + my $status = $?; + + # Ignore all child deaths except for processes we started + next if !exists $_workerStatuses{$worker}; + + $_workerStatuses{$worker} = $status; + } # while + + $SIG{CHLD} = \&workerDeath; +} # workerDeath + +sub execute ($$$) { + my ($cmd, $host, $prompt) = @_; + + my @lines; + + verbose_nolf "Connecting to machine $host..."; + + eval { + $_currentHost = new Rexec ( + host => $host, + prompt => $prompt, + ); + }; + + # Problem with creating Rexec object. Log error if logging and return. + if ($@ or !$_currentHost) { + if ($_log) { + my $log = new Logger (name => $_host); + + $log->err ("Unable to connect to $host to execute command\n$cmd"); + } # if + + $_totalConnectFailures++; + + return (1, ()); + } # if + + verbose " connected"; + + display YELLOW . "$host:" . RESET . UNDERLINE . "$cmd" . RESET unless $_quiet; + + @lines = $_currentHost->exec ($cmd); + + if ($_skip) { + # Kick current connection + kill INT => $_currentHost->{handle}->pid; + } # if + + if ($_parallel != 0) { + if ($_log) { + my $log = new Logger (name => $_host); + + $log->err ("Unable to connect to $host to execute command\n$cmd"); + } # if + + $_totalConnectFailures++; + } # if + + verbose "Disconnected from $host"; + + my $status = $_currentHost->status; + + undef $_currentHost; + + return ($status, @lines); +} # execute + +sub parallelize ($%) { + my ($cmd, %machines) = @_; + + my $thread_count = 1; + + foreach $_host (sort keys %machines) { + if ($thread_count <= $_parallel) { + debug "Processing $_host ($thread_count)"; + $thread_count++; + + if (my $pid = fork) { + # In parent process - record this host and its status + $_workerNames{$pid} = $_host; + } else { + # In spawned child... + $pid = $$; + + debug "Starting process for $_host [$pid]"; + + $_workerNames{$pid} = $_host; + + my ($status, @lines) = execute $cmd, $_host, $machines{$_host}; + + my $log = new Logger (name => $_host); + + $log->log ($_) foreach (@lines); + + exit $status; + } # if + } else { + # Wait for somebody to finish; + debug "Waiting for somebody to exit..."; + my $reaped = wait; + + debug "Reaped $_workerNames{$reaped} [$reaped] (Status: $?)"; + $_workerStatuses{$reaped} = $? >> 8 if $reaped != -1; + + $thread_count--; + } # if + } # foreach + + # Wait for all kids + my %threads = %_workerNames; + + foreach (keys %threads) { + if (waitpid ($_, 0) == -1) { + delete $threads{$_}; + } else { + $_workerStatuses{$_} = $? >> 8; + debug "$threads{$_} [$_] exited with a status of $_workerStatuses{$_}"; + } # if + } # foreach + + debug "All processed completed - Status:"; + + if (get_debug) { + foreach (sort keys %_workerStatuses) { + debug "$_workerNames{$_}\t[$_]:\tStatus: $_workerStatuses{$_}"; + } # foreach + } # if + + # Gather output... + display "Output of all executions"; + foreach $_host (sort keys %machines) { + if (-f "$_host.log") { + display "$_host:$_" foreach (ReadFile ("$_host.log")); + + #unlink "$_host.log"; + } else { + warning "Unable to find output for $_host ($_host.log missing)"; + } # if + } # foreach +} # parallelize + +# Print the totals if interrupted +$SIG{INT} = \&Interrupted; + +# Get our options +GetOptions ( + "usage" => sub { Usage "" }, + "verbose" => sub { set_verbose }, + "debug" => sub { set_debug }, + "log" => \$_log, + "quiet" => \$_quiet, + "file=s" => \$_alternateFile, + "parallel:i" => \$_parallel, +) || Usage "Unknown parameter"; + +my $cmd = join " ", @ARGV; + +error "No command specified", 1 if !$cmd; + +my $machines = Machines->new (file => $_alternateFile); +my %machines = $machines->all (); + +if ($_parallel > 0) { + parallelize ($cmd, %machines); + printStats; + exit; +} # if + +display "NOTE: Logging output to .log" if $_log; + +foreach $_host (sort keys (%machines)) { + $_totalMachines++; + + my ($status, @lines) = execute $cmd, $_host, $machines{$_host}; + + if ($_skip) { + $_skip = 0; + next; + } # if + + if (defined $status) { + if ($status == 0) { + $_totalExecutions++; + } else { + if ($_log) { + my $log = new Logger (name => $_host); + + $log->err ("Unable to execute command on $_host\n$cmd"); + } # if + + $_totalFailures++; + + next; + } # if + } # if + + if ($_log) { + my $log = new Logger (name => $_host); + + $log->log ($_) foreach (@lines); + } else { + display $_ foreach (@lines); + } # if +} # foreach + +printStats; \ No newline at end of file diff --git a/bin/root b/bin/root new file mode 100755 index 0000000..acd936a --- /dev/null +++ b/bin/root @@ -0,0 +1,42 @@ +#!/bin/bash +################################################################################ +# +# File: $RCSfile: root,v $ +# Revision: $Revision: 1.4 $ +# Description: Run a command/shell as root +# Author: Andrew@DeFaria.com +# Created: Mon Nov 13 16:14:30 1995 +# Modified: $Date: 2010/06/08 15:03:27 $ +# Language: Bash +# +# (c) Copyright 2000-2005, ClearSCM, Inc., all rights reserved. +# +################################################################################ +if [ $# -gt 0 ]; then + # Execute the commands + sudo "$@" +else + # Become a "wizard"! + # Source in profile + if [ -f ~/.rc/profile ]; then + . ~/.rc/profile + fi + + # Source in functions (needed for set_title and set_prompt) + if [ -f ~/.rc/functions ]; then + . ~/.rc/functions + fi + + sudo -s + + # Reset title and prompt + # Note: I don't like doing this but an alias doesn't work... + if [ $ARCH = "sun" ]; then + id=/usr/xpg4/bin/id + else + id=id + fi + + set_title + set_prompt +fi diff --git a/bin/setbg b/bin/setbg new file mode 100755 index 0000000..777f632 --- /dev/null +++ b/bin/setbg @@ -0,0 +1,130 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: setbg,v $ + +Set background + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision: + +$Revision: 1.10 $ + +=item Created: + +Fri Mar 18 01:14:38 PST 2005 + +=item Modified: + +$Date: 2012/11/09 15:31:30 $ + +=back + +=head1 SYNOPSIS + + Usage: setbg [-u|sage] [-ve|rbose] [-d|ebug] [-s|leep ] [-bgdir ] + + Where: + + -u|sage: Displays this usage + -ve|rbose: Be verbose + -d|ebug: Output debug messages + + -s|leep: Number of minutes to sleep between setting the background + (Default: 1 hour) + -b|gdir: Directory to scan for images (Default: /web/Pictures) + +=head1 DESCRIPTION + +This script sets the background image randomly based on images $imgDir. Note +if this script is run again it senses that it was previously run and sends the +previous script a SIGUSR2 which the script intrprets as "Change the background +now", then exits. + +=cut + +use strict; +use warnings; + +use FindBin; +use Getopt::Long; +use Proc::ProcessTable; + +use lib "$FindBin::Bin/../lib"; + +use Display; +use Utils; + +my $VERSION = '$Revision: 1.10 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +my $processes = new Proc::ProcessTable; + +foreach my $process (@{$processes->table}) { + if ($process->cmndline =~ /setbg/ and + $process->pid != $$) { + kill 12, $process->pid; + + exit 0; + } # if +} # foreach + +$0 = "$FindBin::Script " . join ' ', @ARGV; + +verbose "$FindBin::Script v$VERSION"; + +my $sleep = 60 * 60; +my $imgDir = $ENV{SETBG_DIR} ? $ENV{SETBG_DIR} : '/web/Pictures'; + +GetOptions ( + 'usage' => sub { Usage }, + 'verbose' => sub { set_verbose }, + 'debug' => sub { set_debug }, + 'sleep=i' => \$sleep, + 'bgdir=s' => \$imgDir, +) || Usage; + +error "$imgDir is not a directory", 1 unless -d $imgDir; + +# Using gsettings +my $setbg = "gsettings"; +my $setbgOpts = "set org.gnome.desktop.background picture-uri \"file://"; + +chomp (my @images = `find $imgDir -type f -name "*.jpg"`); + +sub SwitchWallPaper { + # We don't need to do anything here, just handle the interrupt and + # let the while loop continue. + debug 'SwitchWallPaper: Interrupt received'; +} # SwitchWallPaper + +$SIG{USR2} = \&SwitchWallPaper; + +my $debugger = $DB::OUT; + +EnterDaemonMode unless defined $DB::OUT; + +while () { + my $image = $images[int (rand $#images)]; + + open my $log, '>', "$ENV{HOME}/.$FindBin::Script" + or error "Unable to open $ENV{HOME}/.setbg for writing - $!", 1; + + display "Current background: $image", $log; + + my $cmd = "$setbg $setbgOpts$image\" 2> /dev/null"; + + `$cmd`; + + close $log; + + sleep $sleep; +} # while diff --git a/bin/setup_cron b/bin/setup_cron new file mode 100755 index 0000000..95787fa --- /dev/null +++ b/bin/setup_cron @@ -0,0 +1,29 @@ +#!/bin/bash +################################################################################ +# +# File: $RCSfile: setup_cron,v $ +# Revision: $Revision: 1.2 $ +# Description: This script sets up Cygwin's cron on the local machine +# Author: Andrew@DeFaria.com +# Created: Somewhere in 2003 or so... +# Modified: $Date: 2010/06/08 15:03:27 $ +# Language: Bash +# +# (c) Copyright 2002, ClearSCM, Inc., all rights reserved +# +################################################################################ +me=$(basename $0) +# Make sure that certain directories and files do not exist! This is to let +# cron create them, which appears to be the only way to get these created +# correctly! +if [ ! -d /var/cron ]; then + rm -rf /var/cron + rm -rf /var/run/cron.pid + rm -rf /var/log/cron.log + + # Install cron service: + cygrunsrv -I cron -p /usr/sbin/cron -a -D -d "Cygwin cron" -e "MAILTO=$USER@Salira.com" -e "CYGWIN=ntsec" +fi + +# Start cron service +cygrunsrv -S cron diff --git a/bin/setup_ssmtp b/bin/setup_ssmtp new file mode 100755 index 0000000..26772ee --- /dev/null +++ b/bin/setup_ssmtp @@ -0,0 +1,80 @@ +#!/bin/bash +################################################################################ +# +# File: $RCSfile; $ +# Revision: $Revision: 1.2 $ +# Description: This script sets up ssmtp mail configuration +# Author: Andrew@DeFaria.com +# Created: Wed Jan 9 12:57:13 2002 +# Modified: $Date: 2010/06/08 15:03:27 $ +# Language: Bash +# +# (c) Copyright 2002, ClearSCM, Inc., all rights reserved +# +################################################################################ +# Setup /etc/ssmtp config directory +ssmtp_dir=/etc/ssmtp +domain=$1 +mail_server=$2 +me=$(basename $0) + +function usage { + msg="$1" + + echo "$me: " + + if [ ! -z "$msg" ]; then + echo $msg + fi + + exit 1 +} # usage + +if [ -z "$mail_server" ]; then + usage "Mail_server not specified" +fi + +if [ -z "$domain" ]; then + usage "Domain not specified" +fi + +mkdir -p $ssmtp_dir +chmod 700 $ssmtp_dir + +# Make some simple aliases. Alias $USER to the proper email address and then +# alias root, Administrator and postmaster to the user's address thus making +# the user "god" of smtp on this machine only. +cat > $ssmtp_dir/revaliases < $ssmtp_dir/ssmtp.conf < + +=head1 COPYRIGHT + +Copyright (c) 2010 Andrew DeFaria , ClearSCM, Inc. +All rights reserved. + +=cut + +package DiffBLUI; + +use strict; +use warnings; + +use Cwd; +use POSIX; +use Tk; +use Tk::BrowseEntry; +use Tk::DialogBox; +use Tk::ROText; + +use lib '../lib'; + +use Clearcase; +use Display; +use OSDep; + +use CCDBService; + +use base 'Exporter'; + +my $VERSION = '1.0'; + +our ( + %SELECTED, + %LINES, + $MODE, + $INTEGRATIONACTIVITIES, + $integrationActivitiesCheck +); + +my ($msgWidget, $searchPattern); + +our @EXPORT = qw ( + createUI + displayLines + Tkerror + Tkmsg +); + +# Globals +my $TITLE = 'Compare Baselines: Use fields to select baselines then '; + $TITLE .= 'select Compare'; + +my $CCDBService = CCDBService->new; + +# Widgets +my ( + $main, + $versionsMenu, + $activitiesMenu, + $pvobDropdown, + $streamDropdown, + $fromBaselineDropdown, + $toBaselineDropdown, + $compareButton, + $output, +); + +# Data +my (@pvobs, @streams, @fromBaselines, @toBaselines); + +sub createButton ($$$) { + my ($parent, $label, $action) = @_; + + $parent->Button ( + -text => $label, + -width => length $label, + -command => \$action + )->pack ( + -side => "left", + -padx => 5, + -pady => 5, + ); + + return; +} # createButton + +sub createDropdown ($$$;$$) { + my ($parent, $label, $variable, $action, $list) = @_; + + my $widget = $parent->BrowseEntry ( + -label => "$label:", + -font => 'Arial 8 bold', + -variable => $variable, + -width => 175, + -takefocus => 1, + )->pack ( + -padx => 5, + -pady => 2, + ); + + if ($action) { + # Any of these cause the action to be invoked + $widget->configure (-browsecmd => \$action); + $widget->bind ('' => \$action); + $widget->bind ('' => \$action); + } # if + + $widget->configure (-listcmd => \$list) + if $list; + + my $listBox = $widget->Subwidget ('slistbox'); + my $entry = $widget->Subwidget ('entry'); + my $arrow = $widget->Subwidget ('arrow'); + my $choices = $widget->Subwidget ('choices'); + + # Turn off bolding on the entry + $entry->configure (-font => 'Arial 8'); + + # Allow both widgets to have highlighted parts + $listBox->configure (-exportselection => 0); + $entry->configure (-exportselection => 0); + + # Take the arrow out of the focus business - Works on Unix! + # Bug on Windows! :-( + $arrow->configure (-takefocus => 0); + + # This gets the mouse wheel working - Works on Unix! + # Bug on Windows! :-( + $choices->bind ('', sub {$choices->yviewScroll (1,'units')}); + $choices->bind ('', sub {$choices->yviewScroll (-1,'units')}); + $choices->bind ('', sub {$choices->yview (1,'units')}); + $choices->bind ('', sub {$choices->yview (-1,'units')}); + + foreach ( + '', + '', + '', + '', + '' + ) { + $entry->bind ($_, [\&handleKeypress, $listBox]); + } # foreach + + return $widget; +} # createDropdown + +sub createList ($) { + my ($parent) = @_; + + my $widget = $parent->Scrolled ('Listbox', + -height => 10, + -width => 100, + -scrollbars => 'e', + )->pack ( + -padx => 5, + -pady => 5, + -fill => 'both', + -expand => 'yes', + -anchor => 'w', + ); + + # Make this list resizeable + $parent->pack ( + -fill => 'both', + -expand => 'yes', + ); + + # Bind actions + $widget->bind ('', [ \&popupCCActions, Ev('@') ]); + $widget->bind ('', \&properties); + + # This gets the mouse wheel working + $widget->bind ('', sub {$widget->yviewScroll (1,'units')}); + $widget->bind ('', sub {$widget->yviewScroll (-1,'units')}); + + return $widget; +} # createList + +sub setList ($@) { + my ($list, @value) = @_; + + $list->insert ('end', $_) + foreach @value; + + return; +} # setList + +sub clearList($) { + my ($list) = @_; + + return + unless $list; + + $list->delete ('0.0', 'end'); + + return; +} # clearList + +sub search (@) { + my (@values) = @_; + + return (undef, ()) + unless length $searchPattern; + + my ($index, @matches); + + # First filter @values including only matching entries + foreach (@values) { + push @matches, $_ + if /$searchPattern/i; + } # foreach + + @values = (); + + # Now determine the first qualifying entry. Note if index is already set then + # we do not need to recompute it. It was computed above via a Up or Down key. + foreach (0 .. $#matches) { + if ($matches[$_] =~ m/$searchPattern/i) { + push @values, $matches[$_]; + $index = $_ unless defined $index; + } # if + } # foreach + + return ($index, @values); +} # search + +sub setDropdown ($$$) { + my ($listBox, $entry, $index) = @_; + + # Set listBox widget. This is actually the dropdown list. + $listBox->see ($index); + $listBox->activate ($index); + + # This should be the active entry to set into the entry widget + my $currentEntry = $listBox->get ($index); + + # Set the entry widget. This is the line that the user is typing in + $entry->delete (0, 'end'); + $entry->insert (0, $currentEntry); + + # Set the selection highlight (if the searchPattern is found) + unless (length $searchPattern == 0) { + if ($currentEntry =~ /$searchPattern/i) { + $entry->selectionClear; + $entry->selectionRange ($-[0], $+[0]); + $entry->icursor ($+[0]); + } # if + } # unless + + return; +} # setDropdown + +sub handleKeypress { + my ($entry, $listBox) = @_; + + my (@matches, $match, $index); + + # This is ugly but works + my $browseEntry = $listBox->parent->parent; + + my $key = $entry->XEvent->A; + my $keysym = $entry->XEvent->K; + + debug "Entry: " . $entry->get; + debug "Key: '$key' ($keysym)"; + + # Map Cntl-n and Cntl-p to Down and Up + $keysym = 'Down' if ord ($key) == 14; + $keysym = 'Up' if ord ($key) == 16; + + my $first = 0; + my $Last = $listBox->index ('end') - 1; # Make 0 relative + my $active = $listBox->index ('active'); + + $index = $active; + + if ($keysym eq 'BackSpace') { + $searchPattern = substr $searchPattern, 0, -1 + if length $searchPattern > 0; + + if (length $searchPattern == 0) { + $index = 0; + $entry->delete (0, 'end'); + } # if + } elsif ($keysym eq 'Down') { + if ($active < $Last) { + setDropdown ($listBox, $entry, ++$active); + } else { + debug "Beep - no more down"; + $main->bell; + } # if + + return; + } elsif ($keysym eq 'Up') { + if ($active > 0) { + setDropdown ($listBox, $entry, --$active); + } else { + debug "Beep - no more up"; + $main->bell; + } # unless + + return; + } elsif ($keysym eq 'Tab') { + $entry->selectionClear; + + return; + } else { + return if (!isprint ($key) || !ord ($key)); + + $searchPattern .= $key; + } # if + + debug "searchPattern: $searchPattern"; + + # Get values based on the $browseEntry widget + my @values; + + unless ($index) { + do { + if ($browseEntry == $pvobDropdown) { + ($index, @values) = search sort @pvobs; + } elsif ($browseEntry == $streamDropdown) { + ($index, @values) = search sort @streams; + } elsif ($browseEntry == $fromBaselineDropdown) { + ($index, @values) = search sort @fromBaselines; + } elsif ($browseEntry == $toBaselineDropdown) { + ($index, @values) = search sort @toBaselines; + } # if + + if (defined $index) { + debug "Index: $index"; + $match = $values[$index]; + } else { + debug "Index: "; + debug "Length of searchPatern " . length $searchPattern; + if (length $searchPattern == 0) { + debug "Setting match to blank"; + $match = ''; + $index = 0; + } else { + debug "making searchPattern shorter"; + $searchPattern = substr $searchPattern, 0, -1; + debug "Length of searchPatern now " . length $searchPattern; + } # if + } # if + } until $match or length $searchPattern == 0; + } # unless + + # Setting the listBox clears the active indicator so save it and reset it. + $active = $listBox->index ('active'); + + clearList $listBox; + setList $listBox, sort @values; + + $listBox->activate ($active); + + if ($searchPattern) { + if ($match and $match =~ /$searchPattern/i) { + $entry->delete (0, 'end'); + $entry->selectionClear; + $entry->insert (0, $match); + $entry->icursor ($+[0]); + $entry->selectionRange ($-[0], $+[0]); + } else { + debug "Beep - no matches"; + $main->bell; + return; + } # if + } # if + + # Now update the assocated listBox. + $listBox->selectionClear (0, 'end'); + $listBox->selectionSet ($index, $index); + + # Makes it so that the entry selected above is centered in the drop down list. + # So if you had say entries like 1, 2, 3, 4,... 10 and you hit '5', you'll see + # '5' in the listBox entry but you really want to also shift it so that if you + # hit the drop down arrow, 5, is the entry at the top of the drop down list. + $listBox->see ($index); + + debug 'Entry: ' . $entry->get; + + return; +} # handleKeypress + +sub Tkerror ($) { + my ($msg) = @_; + + my $error = $main->DialogBox ( + -title => 'Error', + -buttons => [ 'OK' ], + ); + + my $text = $error->add ( + 'ROText', + -width => 65, + -height => 8, + -font => "Arial 8", + -wrap => 'word', + )->pack ( + -fill => 'both', + -expand => 1, + ); + + $text->insert ('end', $msg); + + $error->Show; + + return; +} # Tkerror + +sub Tkmsg ($;$) { + my ($msg, $sleep) = @_; + + if ($msgWidget) { + $msgWidget->configure (-text => $msg); + $msgWidget->update; + + if ($sleep) { + return + if $sleep < 0; + + sleep $sleep; + } # if + + $msgWidget->configure (-text => ''); + $msgWidget->update; + } # if + + return; +} # Tkmsg + +sub about () { + my $msg = "Utility to select baselines and provide a simple list of " + . "activities or file/directory versions that differ between " + . "two baselines.\n\n" + . "Note you can save this list using the Save button or you can " + . "right click on a line and select Clearcase operations.\n\n" + . "Written by Andrew DeFaria "; + + my $about = $main->DialogBox ( + -title => "About $FindBin::Script V$VERSION", + -buttons => [ 'OK' ], + ); + + my $text = $about->add ( + 'ROText', + -width => 65, + -height => 8, + -font => "Arial 8", + -wrap => 'word', + )->pack; + + # Stop about dialog from resizing + $about->bind ( + '' => sub { + my $e = $about->XEvent; + + $about->maxsize ($e->w, $e->h); + $about->minsize ($e->w, $e->h); + }, + ); + + $text->insert ('end', $msg); + + $about->Show; + + return; +} # about + +sub popupCCActions ($) { + my ($widget, $xy) = @_; + + $widget->selectionClear (0, 'end'); + + my $index = $widget->index ($xy); + my $event = $widget->XEvent; + + if (defined $index) { + $widget->selectionSet ($index); + + if ($MODE eq 'versions') { + $versionsMenu->post ($widget->rootx + $event->x, $widget->rooty + $event->y); + } else { + $activitiesMenu->post ($widget->rootx + $event->x, $widget->rooty + $event->y); + } + } # if + + return; +} # popupCCActions + +sub busy () { + $main->Busy (-recurse => 1); + $main->update; + + return; +} # busy + +sub unbusy () { + $main->Unbusy; + $main->update; + + return; +} # unbusy + +sub getPvobs { + $main->Busy (-recurse => 1); + + my ($status, @output) = $Clearcase::CC->execute ('lsvob'); + + @pvobs = (); + + foreach (grep { /\(ucmvob/ } @output) { + my @tokens = split; + + my $pvob = $tokens[0] eq '*' + ? Clearcase::vobname ($tokens[1]) + : Clearcase::vobname ($tokens[0]); + + push @pvobs, $pvob; + } # foreach + + clearList $streamDropdown; $SELECTED{stream} = ''; + $streamDropdown->update; + clearList $fromBaselineDropdown; $SELECTED{fromBaseline} = ''; + $fromBaselineDropdown->update; + clearList $toBaselineDropdown; $SELECTED{toBaseline} = ''; + $toBaselineDropdown->update; + clearList $output; + + clearList $pvobDropdown; + setList $pvobDropdown, sort @pvobs; + + $main->Unbusy; + + return; +} # getPvobs + +sub getStreams () { + $main->Busy (-recurse => 1); + + $searchPattern = ''; + + clearList $streamDropdown; + + $SELECTED{stream} = 'Getting streams...'; + + $streamDropdown->update; + + my $pvob = Clearcase::vobname ($SELECTED{pvob}); + + $CCDBService->connectToServer + or error "Unable to connect to CCDBService", 1; + + my ($status, $streams) = $CCDBService->execute ("FindStream * $pvob"); + + $CCDBService->disconnectFromServer; + + if ($status) { + Tkerror "Unable to get streams (Status: $status)\n" . join ("\n", @$output); + return; + } # if + + # First empty @streams of the old contents + @streams = (); + + push @streams, $$_{name} + foreach (@$streams); + + clearList $fromBaselineDropdown; + clearList $toBaselineDropdown; + + $SELECTED{fromBaseline} = ''; + $SELECTED{toBaseline} = ''; + + $fromBaselineDropdown->update; + $toBaselineDropdown->update; + + clearList $output; + + $SELECTED{stream} = ''; + + setList $streamDropdown, sort @streams; + + $streamDropdown->focus; + + $main->Unbusy; + + return; +} # getStreams + +sub getBaselines () { + $main->Busy (-recurse => 1); + + $searchPattern = ''; + + my $status; + + clearList $fromBaselineDropdown; + clearList $toBaselineDropdown; + + $SELECTED{fromBaseline} = 'Getting baselines...'; + $SELECTED{toBaseline} = 'Getting baselines...'; + + $fromBaselineDropdown->update; + $toBaselineDropdown->update; + + ($status, @fromBaselines) = $Clearcase::CC->execute + ("lsbl -short -stream $SELECTED{stream}\@$Clearcase::VOBTAG_PREFIX$SELECTED{pvob}"); + + @toBaselines = @fromBaselines; + + clearList $fromBaselineDropdown; + clearList $toBaselineDropdown; + + $SELECTED{fromBaseline} = ''; + $SELECTED{toBaseline} = ''; + + $fromBaselineDropdown->update; + $toBaselineDropdown->update; + + clearList $output; + + setList $fromBaselineDropdown, sort @fromBaselines; + setList $toBaselineDropdown, sort @toBaselines; + + $main->Unbusy; + + return; +} # getBaselines + +sub saveList () { + my @types = ( + ['Text Files', '.txt', 'TEXT'], + ['All Files', '*'] + ); + + my $filename = $main->getSaveFile ( + -filetype => \@types, + -initialfile => "$SELECTED{fromBaseline}.$SELECTED{toBaseline}.diffs", + -defaultextension => '.txt', + ); + + return unless $filename; + + open my $file, '>', $filename + or Tkmsg "Unable to open $filename for writing - $!", -1 + and return; + + foreach ($output->get (0, 'end')) { + print $file "$_\n"; + } # foreach + + close $file; + + return; +} # saveList + +sub childDeath () { + my $pid = wait; + + display "$pid died"; + + CORE::exit; + + return; +} # childDeath + +local $SIG{CLD} = \&childDeath; +local $SIG{CHLD} = \&childDeath; + +sub ccexec ($;$$) { + my ($cmd, $parm1, $parm2) = @_; + + unless ($parm1) { + my $selected = $output->curselection; + + return + unless $selected; + + my $line = $output->get ($selected); + + if ($MODE eq 'versions') { + # Need to add on the view tag prefix + $cmd .= " $Clearcase::VIEWTAG_PREFIX/" . $::view->tag . $line; + } else { + $cmd .= " activity:$line\@"; + $cmd .= Clearcase::vobtag $SELECTED{pvob}; + } # if + } else { + $cmd .= " $parm1 $parm2"; + } # unless + + $main->Busy (-recurse => 1); + + if ($ARCH eq 'windows' or $ARCH eq 'cygwin') { + $Clearcase::CC->execute ($cmd); + } else { + # Use fork/exec to allow CC processes to not cause us to block + unless (fork) { + $Clearcase::CC->execute ($cmd); + CORE::exit; + } # unless + } # if + + $main->Unbusy; + + return +} # ccexec + +sub findVersion ($$) { + my ($element, $baseline) = @_; + + my $cmd = 'find ' . substr ($element, 1) . ' -directory '; + $cmd .= "-version 'lbtype($baseline)' -print"; + + my ($status, @output) = $Clearcase::CC->execute ($cmd); + + if ($status) { + my $msg = "Unable to determine the version for $element ($baseline)"; + + Tkerror join ("\n", "$msg (Status: $status)", "\n", @output); + + exit $status; + } # if + + # Change these silly '\'s -> '/'s + $output[0] =~ s/\\/\//g; + + my $version; + + if ($output[0] =~ /.*$Clearcase::SFX(.*)/) { + $version = $1; + } # if + + return $version +} # findVersion + +sub compareToPrev () { + my $selected = $output->curselection; + + return + unless $selected; + + my $element = $output->get ($selected); + + # Need to add on the view tag prefix + my $element1 = "$Clearcase::VIEWTAG_PREFIX/" . $::view->tag . $element; + my $element2 = "$Clearcase::VIEWTAG_PREFIX/" . $::view->tag . $element; + + # Get into the view context + my $view_context = $Clearcase::VIEWTAG_PREFIX . '/' . $::view->tag; + my $cwd = getcwd; + + # For my Cygwin environment - translate that path back into a Windows path + if ($ARCH eq 'cygwin') { + my @cwd = `cygpath -w $cwd`; + chomp @cwd; + + $cwd = $cwd[0]; + } # if + + my ($status, @output) = $Clearcase::CC->execute ("cd \"$view_context\""); + + Tkerror "Unable to set view context to $view_context (Status: $status)" . + join ("\n", @output) + if $status; + + my $version; + + # Determine from baseline version + $version = findVersion $element, $SELECTED{fromBaseline}; + $element1 .= "$Clearcase::SFX$version"; + + # Determine to baseline version + $version = findVersion $element, $SELECTED{toBaseline}; + $element2 .= "$Clearcase::SFX$version"; + + ccexec 'diff -g', $element1, $element2; + + return; +} # compareToPrev + +sub history () { + ccexec 'lshist -g'; + + return; +} # history + +sub versionTree () { + ccexec 'lsvtree -g'; + + return; +} # versionTree + +sub properties () { + ccexec 'describe -g'; + + return; +} # properties + +sub displayLines () { + clearList $output; + + my @lines = keys %LINES; + + if ($MODE eq 'activities') { + @lines = grep {!/(deliver|rebase|tlmerge|integrate)/} @lines + unless $INTEGRATIONACTIVITIES; + } # if + + setList $output, sort @lines; + + my $msg = @lines > 0 ? @lines : 'No'; + $msg .= $MODE eq 'versions' ? ' Element' : ' Activit'; + + if ($MODE eq 'versions') { + $msg .= 's' + if @lines != 1; + } else { + if (@lines != 1) { + $msg .= 'ies'; + } else { + $msg .= 'y'; + } # if + } # if + + Tkmsg $msg, 3; + + return; +} # displayLines + +sub setFocus () { + my ($entry) = @_; + + $searchPattern = ''; + $entry->icursor (0); + + return; +} # setFocus + +sub createUI () { + $main = MainWindow->new; + + # Set an icon image + $main->iconimage ($main->Photo (-file => "$FindBin::Bin/diffbl.gif")) + if -f "$FindBin::Bin/diffbl.gif"; + + my $WIDTH = (length ($TITLE) + 1) * 10; + + $main->geometry ("${WIDTH}x600"); + $main->title ("$FindBin::Script V$VERSION"); + + my @frame; + + for (my $i = 0; $i < 9; $i++) { + $frame[$i] = $main->Frame->pack; + } # for + + # Create versions popup menu + $versionsMenu = $main->Menu ( + -tearoff => 0, + ); + + $versionsMenu->add ( + 'command', + -label => 'Compare to Prev', + -command => \&compareToPrev, + ); + $versionsMenu->add ( + 'command', + -label => 'History', + -command => \&history, + ); + $versionsMenu->add ( + 'command', + -label => 'Version Tree', + -command => \&versionTree, + ); + $versionsMenu->add ( + 'command', + -label => 'Properties', + -font => 'Arial 8 bold', + -command => \&properties, + ); + + # Create activities popup menu + $activitiesMenu = $main->Menu ( + -tearoff => 0, + ); + + $activitiesMenu->add ( + 'command', + -label => 'Show Contributing Activities', + -state => 'disable', + -command => [ \&Tkerror, "Unimplemented" ], + ); + $activitiesMenu->add ( + 'command', + -label => 'Checkin All', + -state => 'disable', + -command => [ \&Tkerror, "Unimplemented" ], + ); + $activitiesMenu->add ( + 'command', + -label => 'Finish Activity', + -state => 'disable', + -command => [ \&Tkerror, "Unimplemented" ], + ); + $activitiesMenu->add ( + 'command', + -label => 'Properties', + -font => 'Arial 8 bold', + -command => \&properties, + ); + + $frame[0]->Label ( + -font => 'Arial 10 bold', + -text => $TITLE, + -anchor => 'center', + )->pack; + + $pvobDropdown = createDropdown ( + $frame[1], + 'Project Vob', + \$SELECTED{pvob}, + \&getStreams, + \&getPvobs, + ); + + # Remove the Leave binding from $pvobDropDown + $pvobDropdown->bind ('', undef); + + $streamDropdown = createDropdown ( + $frame[2], + 'Stream', + \$SELECTED{stream}, + \&getBaselines, + ); + + $streamDropdown->bind ('', \&setFocus); + + $fromBaselineDropdown = createDropdown ( + $frame[3], + 'From baseline', + \$SELECTED{fromBaseline}, + ); + + $fromBaselineDropdown->bind ('', \&setFocus); + + $toBaselineDropdown = createDropdown ( + $frame[4], + 'To baseline', + \$SELECTED{toBaseline}, + ); + + $toBaselineDropdown->bind ('', \&setFocus); + + $frame[5]->Label ( + -text => 'Show:', + -font => 'Arial 8 bold', + )->pack ( + -side => "left", + -padx => 5, + -pady => 5, + ); + my $versionsToggle = $frame[5]->Radiobutton ( + -text => 'Versions', + -value => 'versions', + -variable => \$MODE, + -command => \&::compareBaselines, + )->pack ( + -side => "left", + -padx => 5, + -pady => 5, + ); + my $activitiesToggle = $frame[5]->Radiobutton ( + -text => 'Activities', + -value => 'activities', + -variable => \$MODE, + -command => \&::compareBaselines, + )->pack ( + -side => "left", + -padx => 5, + -pady => 5, + ); + + # Toggle on activities + $activitiesToggle->select; + + $integrationActivitiesCheck = $frame[5]->Checkbutton ( + -text => 'Integration activities', + -variable => \$INTEGRATIONACTIVITIES, + -command => \&displayLines, + )->pack ( + -side => "left", + -padx => 5, + -pady => 5, + ); + + $output = createList $frame[6]; + + $msgWidget = $frame[7]->Label ( + -font => 'Arial 8 bold', + )->pack; + + createButton $frame[8], 'About', \&about; + $compareButton = createButton $frame[8], 'Compare', \&::compareBaselines; + createButton $frame[8], 'Save', \&saveList; + createButton $frame[8], 'Exit', \&exit; + + # Now populate the streams + getStreams; + + MainLoop; + + return; +} # createUI + +1; diff --git a/cc/bin_merge b/cc/bin_merge new file mode 100644 index 0000000..fbe7066 --- /dev/null +++ b/cc/bin_merge @@ -0,0 +1,112 @@ +#!ccperl +################################################################################ +# +# File: bin_merge +# Description: This script will perform a merge checking for any merge +# conflicts and grouping them at the end. This allows the +# majority of a large merge to happen and the user can resolve +# the conflicts at a later time. +# +# This script also assists in performing binary merges for the +# common case. With a binary merge one cannot easily merge the +# binary code. Most often it's a sitatution where the user will +# either accept the source or the destination binary file as +# a whole. In cases where there is only a 2 way merge, this +# script offers the user the choice to accept 1 binary file +# or the other or to abort this binary merge. Binary merges +# conflicts greater than 2 way are not handled. +# +# Author: Andrew@DeFaria.com +# Created: Thu Nov 3 10:55:51 PST 2005 +# Language: Perl +# +# (c) Copyright 2005, Andrew@DeFaria.com, all rights reserved +# +################################################################################ +use strict; +use warnings; +use Getopt::Long; +use File::Spec; + +my $me; + +BEGIN { + # Set $lib_path + my $lib_path = $^O =~ /MSWin/ ? "\\\\brcm-irv\\dfs\\projects\\ccase\\SCM\\lib" + : "/projects/ccase/SCM/lib"; + + # Extract relative path and basename from script name. + $0 =~ /(.*)[\/\\](.*)/; + + my $abs_path = (!defined $1) ? "." : File::Spec->rel2abs ($1); + $me = (!defined $2) ? $0 : $2; + $me =~ s/\.pl$//; + + # Remove .pl for Perl scripts that have that extension + $me =~ s/\.pl$//; + + # Add the appropriate path to our modules to @INC array. + unshift @INC, "$abs_path"; + unshift @INC, $ENV {SITE_PERL_LIBPATH} if defined $ENV {SITE_PERL_LIBPATH}; + unshift @INC, "$lib_path"; +} # BEGIN + +use BinMerge; +use Display; +use Logger; + +sub Usage { + my $msg = shift; + + display "ERROR: $msg\n" if defined $msg; + + display "Usage: $me [-u] [-v] [-d] -branch -path + +Where: + + -u: Display usage + -v: Turn on verbose mode + -d: Turn on debug mode + -branch Branch to merge from + -path: Path to consider (Default .) +"; + exit 1; +} # Usage + + +my $branch; +my $path = "."; +my $verbose = 0; +my $debug = 0; + +while ($ARGV [0]) { + if ($ARGV [0] eq "-v") { + $verbose = 1; + } elsif ($ARGV [0] eq "-d") { + $debug = 1; + } elsif ($ARGV [0] eq "-branch") { + shift; + if (!$ARGV [0]) { + Usage "Must specify after -branch"; + } else { + $branch = $ARGV [0]; + } # if + } elsif ($ARGV [0] eq "-path") { + shift; + if (!$ARGV [0]) { + Usage "Must specify after -path"; + } else { + $path = join (" ", @ARGV); + } # if + } elsif ($ARGV [0] eq "-u") { + Usage; + } else { + Usage "Unknown argument found: " . $ARGV [0]; + } # if + + shift (@ARGV); +} # while + +Usage "Must specify a branch" if !defined $branch; + +Merge $branch, $path, $verbose, $debug; diff --git a/cc/bin_rebase b/cc/bin_rebase new file mode 100644 index 0000000..7107b95 --- /dev/null +++ b/cc/bin_rebase @@ -0,0 +1,108 @@ +#!ccperl +################################################################################ +# +# File: bin_rebase +# Description: This script will perform a rebase checking for any merge +# conflicts and grouping them at the end. This allows the +# majority of a large merge to happen and the user can resolve +# the conflicts at a later time. +# +# This script also assists in performing binary merges for the +# common case. With a binary merge one cannot easily merge the +# binary code. Most often it's a sitatution where the user will +# either accept the source or the destination binary file as +# a whole. In cases where there is only a 2 way merge, this +# script offers the user the choice to accept 1 binary file +# or the other or to abort this binary merge. Binary merges +# conflicts greater than 2 way are not handled. +# +# Author: Andrew@DeFaria.com +# Created: Thu Nov 3 10:55:51 PST 2005 +# Language: Perl +# +# (c) Copyright 2005, Andrew@DeFaria.com, all rights reserved +# +################################################################################ +use strict; +use warnings; +use Getopt::Long; +use File::Spec; + +my ($me, $SEPARATOR, $NULL); + +BEGIN { + my ($abs_path, $lib_path); + + # Extract relative path and basename from script name. + $0 =~ /(.*)[\/\\](.*)/; + + $abs_path = (!defined $1) ? "." : File::Spec->rel2abs ($1); + $me = (!defined $2) ? $0 : $2; + $me =~ s/\.pl$//; + + # Remove .pl for Perl scripts that have that extension + $me =~ s/\.pl$//; + + # Define the path SEPARATOR + $SEPARATOR = ($^O =~ /MSWin/) ? "\\" : "/"; + $NULL = ($^O =~ /MSWin/) ? "NUL" : "/dev/null"; + + # Setup paths + $lib_path = "$abs_path" . $SEPARATOR . ".." . $SEPARATOR . "lib"; + + # Add the appropriate path to our modules to @INC array. + unshift (@INC, "$abs_path"); + unshift (@INC, "$lib_path"); +} # BEGIN + +use BinMerge; +use Display; +use Logger; + +sub Usage { + my $msg = shift; + + display "ERROR: $msg\n" if defined $msg; + + display "Usage: $me [-u] [-v] [-d] -view + { -recommended | -baseline } + +Where: + + -u: Display usage + -v: Turn on verbose mode + -d: Turn on debug mode + -recommended: Rebase from recommended baselines + -baseline: Rebase from this baseline +"; + exit 1; +} # Usage + +my $baseline; +my $verbose = 0; +my $debug = 0; + +while ($ARGV [0]) { + if ($ARGV [0] eq "-v") { + $verbose = 1; + } elsif ($ARGV [0] eq "-d") { + $debug = 1; + } elsif ($ARGV [0] eq "-recommended") { + # Nothing to do, leave $baseline undefined + } elsif ($ARGV [0] eq "-baseline") { + shift; + if (!$ARGV [0]) { + Usage "Must specify after -baseline"; + } else { + $baseline = $ARGV [0]; + } # if + } elsif ($ARGV [0] eq "-u") { + Usage; + } else { + Usage "Unknown argument found: " . $ARGV [0]; + } # if + + shift (@ARGV); +} # while + +Rebase $baseline, $verbose, $debug; diff --git a/cc/diffbl.gif b/cc/diffbl.gif new file mode 100644 index 0000000000000000000000000000000000000000..96adf5c2f7dfff70b7e8ebea86eca7669c7149d7 GIT binary patch literal 1240 zcmb_b`)^cb5FOLfZrK#K_yAgQyAlHm*5X$IB?dL}=ryU7Zr2hBQft3#G3zUKeKf>% z3yntjL7FNy-6HPT4=pOpVdAxmit3=9Z<7ZU%0C%z%@+y57a$PzJ;(@n^XA!Erf z8AnF6Xbo9QmdQG@qP=X0Ein^2V$o4&$XRks&XH55B!YETbS*M#jb&qb|6xfHx>wqi4JZQqI$Y1$45QS)ozSa7lpaAw2 z-L~#%=2Uw@Fwy&Bd*pC>d{%7b+$-V3l^usprJLhJkL@TAmd-r(?t+8&+$gnW{TsFp zy%DaT-Bn3;a-nHWN(|w&E*1j2TIltM9W>&TRdwHVMyZ^V5wi%yH zRNlNee&*5T`8A(jJ975E&*DP|wik9zu0B$>Hg@pprEs)o`tJ+h1&`l;qJ4fMaQ*G< ztdsHS%kyi#9^LX#b6(TTX#HU3-bKKs4f_|sGArNLYFO}(__#P)3VgO2XfToV`E zwW<5kkGYnykGsC?oO&WV^w!kx)nCo=`mdZDEWEh5=TODEZ9Aj69TRO;x%cmGKKAo- zq1c53(e6#hH!pfMm>eDNJKeDN>g4$TT@QM<_9uehBsyw7U)Yn2tpB62_KTg7j_Ibs z^+!klEJ;=^-Bb5u@4(iO7piDJ`^c)Umot&oMSc5=0|l*_bbkFuBkg6?4WTuQlC6!g z1x2lssfyyN+1?$=Xi0K;aXjn=64|D?zeJuHEUp}-yvEFMVQ`OEx?%X?y6ZO$d-bKQ tTVi?Vez^0Mk=Hi + +=item Revision + +$Revision: 1.7 $ + +=item Created: + +Mon Oct 25 11:10:47 PDT 2008 + +=item Modified: + +$Date: 2011/08/31 21:57:06 $ + +=back + +=head1 SYNOPSIS + + Usage cqperl diffbl.pl: [-u|sage] [-v|erbose] [-d|ebug] + [-[baseline1|bl1] ] + [-[baseline2|bl2] ] + [-p|vob ] + + Where: + -u|sage: Displays usage + -ve|rbose: Be verbose + -d|ebug: Output debug messages + -bl1 Full baseline 1 to use in the comparison + -bl2 Full baseline 2 to use in the comparison + -p|vob Pvob to use + +=head2 DESCRIPTION + +This script provides a Perl/Tk GUI application to compare baselines. It provides +several benefits over IBM/Rational's graphical diffbl (cleartool diffbl -g ...). +First, it assists you in finding baselines to compare whereas diffbl -g requires +that you find the baselines yourself. When diffbl.pl is run you are presented +with a GUI that you can use to find baselines by either using the dropdown to +select pvobs, streams and ultimately baselines. You can also simply type part +the name and diffbl.pl will narrow down the list of pvobs, streams or baselines +in the drop down. This allows you to easily find the baselines you wish to +compare. + +Additionally, IBM/Rational's diffbl -g -version often shows extremely long, but +technically accurate version extended pathnames where the user thinks more along +the lines of "path to element in a vob" only. diffbl.pl shows shorter, more +easily comprehenable pathnames. diffbl.pl also shows only the latest version of +the element. Thus if foo.c changed with version 3, 4 and 5 then you see just +foo.c and it represents foo.c version 5. + +Finally, diffbl.pl provides a way to save the list of elements that have changed +in a file. + +diffbl.pl also provides right click menu options to easily show the elements +properties, compare to previous, show the version tree and history of the +element. + +=cut + +use strict; +use warnings; + +use FindBin; +use Cwd; +use Getopt::Long; + +use lib "$FindBin::Bin/../CCDB/lib", "$FindBin::Bin/../lib"; + +use DiffBLUI; +use Display; +use Utils; +use OSDep; + +use Clearcase; +use Clearcase::View; + +$DiffBLUI::SELECTED{pvob} = $ENV{pvob} + ? Clearcase::vobname ($ENV{pvob}) + : '8800_projects'; + +our $view; +my $currentStream; + +my ($bl1, $bl2); + +END { + $view->remove + if $view; +} # END + +# Should this be moved to Clearcase.pm? +sub ccerror ($$@) { + my ($msg, $status, @output) = @_; + + Tkerror join ("\n", "$msg (Status: $status)", "\n", @output); + + exit $status; +} # ccerror + +sub mkview () { + my $user = $ARCH eq 'windows' ? $ENV{USERNAME} : $ENV{USER}; + + my $viewname = "${user}_$FindBin::Script"; + + Tkmsg "Creating a view to work in", -1; + + my $newView = Clearcase::View->new ($viewname); + + # If the streams have changed then we need to recreate the view. + $currentStream ||= $DiffBLUI::SELECTED{stream}; + + if ($currentStream ne $DiffBLUI::SELECTED{stream}) { + $newView->remove; + $currentStream = $DiffBLUI::SELECTED{stream}; + } # if + + # The create method needs to support additional parameters such as -stream so + # we have to do this by hand right now... + my ($status, @output) = + $Clearcase::CC->execute ( + "mkview -tag $viewname -stream $DiffBLUI::SELECTED{stream}\@" . + "$Clearcase::VOBTAG_PREFIX$DiffBLUI::SELECTED{pvob} -stgloc -auto" + ); + + unless (grep {/already exists/} @output) { + if ($status) { + ccerror ("Unable to create view $viewname", $status, @output); + } # if + } # unless + + $newView->updateViewInfo; + + # Start the view + ($status, @output) = $newView->start; + + ccerror ('Unable startview ' . $newView->tag, $status, @output) + if $status; + + Tkmsg 'Done'; + + return $newView; +} # mkview + +sub compareBaselines () { + unless ($DiffBLUI::SELECTED{pvob}) { + Tkerror 'Project Vob must be selected'; + return; + } # unless + + unless ($DiffBLUI::SELECTED{fromBaseline}) { + Tkerror 'From baseline must be selected'; + return; + } # unless + + unless ($DiffBLUI::SELECTED{toBaseline}) { + Tkerror 'To baseline must be selected'; + return; + } # unless + + DiffBLUI::busy; + + if ($DiffBLUI::MODE eq 'versions') { + $DiffBLUI::integrationActivitiesCheck->configure (-state => 'disable'); + } else { + $DiffBLUI::integrationActivitiesCheck->configure (-state => 'normal'); + } + + # Create a view to work in. + $view = mkview; + + # Get into the view context + my $view_context = $Clearcase::VIEWTAG_PREFIX . '/' . $view->tag; + my $cwd = getcwd; + + # For my Cygwin environment - translate that path back into a Windows path + if ($ARCH eq 'cygwin') { + my @cwd = `cygpath -w $cwd`; + chomp @cwd; + + $cwd = $cwd[0]; + } # if + + my ($status, @output) = $Clearcase::CC->execute ("cd \"$view_context\""); + + ccerror "Unable to set view context to $view_context", $status, @output + if $status; + + Tkmsg 'Comparing baselines (This may take a while)', -1; + + %DiffBLUI::LINES= (); + + my $cmd = "diffbl -$DiffBLUI::MODE "; + $cmd .= $DiffBLUI::SELECTED{fromBaseline}; + $cmd .= "\@$Clearcase::VOBTAG_PREFIX$DiffBLUI::SELECTED{pvob} "; + $cmd .= $DiffBLUI::SELECTED{toBaseline}; + $cmd .= "\@$Clearcase::VOBTAG_PREFIX$DiffBLUI::SELECTED{pvob}"; + + ($status, @output) = $Clearcase::CC->execute ($cmd); + + ccerror "Unable to perform command $cmd", $status, @output + if $status; + + Tkmsg 'Done'; + + my $viewtag = $Clearcase::VIEWTAG_PREFIX . '/' . $view->tag; + + foreach (@output) { + # Skip lines that don't have either <<, >>, <- or -> at the beginning + next unless /^(\<\-|\>\>|\<\<|\-\>)\s/; + + if ($DiffBLUI::MODE eq 'activities') { + if (/\W+\s+(.*)\@/) { + $DiffBLUI::LINES{$1} = $1; + } # if + } else { + # Change those silly '\'s -> '/'s + s/\\/\//g; + + # Extract the pathname and strip off the version. Note we use a hash here + # to get uniqueness based on the element name and only store the last + # version checked in. It is very possible, for example, that there is foo + # versions 1, 2 and 3 but we want to more simply just report that foo + # changed - not that foo changed 3 times. + if (/\W+\s+(.*)$Clearcase::SFX/) { + my $elementName = $1; + + # Remove view path and tagname from $elementName + $elementName =~ s/$viewtag//; + + $DiffBLUI::LINES{$elementName} = $elementName; + } # if + } # if + } # foreach + + ($status, @output) = $Clearcase::CC->execute ("cd \"$cwd\""); + + ccerror "Unable to set view context to $cwd", $status, @output + if $status; + + displayLines; + + DiffBLUI::unbusy; + + Tkmsg 'Done', 1; + + return; +} # compareBaselines + +# Main +GetOptions ( + 'usage' => sub { Usage }, + 'verbose' => sub { set_verbose }, + 'debug' => sub { set_debug }, + 'baseline1|bl1=s' => \$bl1, + 'baseline2|bl2=s' => \$bl2, + 'pvob=s' => \$DiffBLUI::SELECTED{pvob}, +) or Usage "Invalid parameter"; + +createUI; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Clearcase + Clearcase::View + DiffBLUI + Display + OSDep + Utils + +=end man + +=begin html + +
+Clearcase
+Clearcase::View
+DiffBLUI
+Display
+OSDep
+Utils
+
+ +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut diff --git a/cc/dominos b/cc/dominos new file mode 100644 index 0000000..2f1504c --- /dev/null +++ b/cc/dominos @@ -0,0 +1,205 @@ +#!/usr/bin/perl +################################################################################ +# +# File: dominos,v +# Revision: 1.1.1.1 +# Description: Quick script to deliver a stream to an integration view +# (Hot and fresh in 30 minutes or less! :-) +# Author: Andrew@DeFaria.com +# Created: Mon Feb 13 10:35:34 PST 2006 +# Modified: 2007/05/17 07:45:48 +# Language: Perl +# +# (c) Copyright 2006, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +use strict; +use warnings; + +use FindBin; +use lib "$FindBin::Bin/../lib"; + +use Getopt::Long; + +use Clearcase; +use Clearcase::View; +use DateUtils; +use Display; +use OSDep; +use Logger; + +my $me = $FindBin::Script; +my $pvob = $Clearcase::vobtag_prefix . "ilm_pvob"; +my $logdir = $ENV {TMP} ? $ENV {TMP} : "."; +my $log = Logger->new ( + path => $logdir, + name => $me, +); + +my $from_address = "build\@persistcorp.com"; +my $to_addresses = "philippe.rollet\@hp.com,andrew.defaria\@hp.com"; + +sub Usage { + my $msg = shift; + + display "ERROR: $msg\n" if defined $msg; + + display "Usage: $me\t[-u] [-v] [-d] [-stream ] [-view_tag ] + +Where: + + -usage: Display usage + -vebose: Turn on verbose mode + -debug: Turn on debug mode + -stream: Name of stream to deliver from + -view_tag: View tag to deliver to +"; + exit 1; +} # Usage + +sub CheckForFailures { + my $log = shift; + + my @lines = $log->loglines; + + my @failures; + my $element; + my $from; + my $branch; + + foreach (@lines) { + if (/Needs Merge "(.*)".*from (.*) base/) { + $element = $1; + $from = $2; + + if ($arch eq "windows" or $arch eq "cygwin") { + if ($from =~ /.*\\(\w*)\\\d*/) { + $branch = $1; + } # if + } else { + if ($from =~ /.*\/(\w*)\/\d*/) { + $branch = $1; + } # if + } # if + } elsif (/merge: Error: \*\*\* Aborting\.\.\./ or + /\*\*\* No Automatic Decision Possible/) { + # Argh! On Windows silly \'s are used and it always interferes + # with things. Even though $element has the requesite doubling + # of the \'s, one gets eaten up by calling system, the + # eventually cleartool call here. So we change them from \ -> /! + $element =~ tr /\\/\// if ($arch eq "windows" or $arch eq "cygwin"); + my ($status, @output) = Clearcase::cleartool ( + "lshistory -last -directory -branch $branch -fmt \"%Fu %u\" " . $element + ); + + # Argh, sometimes %Fu above gives only a one name fullname + # (e.g. Bounour). Not only do we need to account for this but we + # have to abandon the hope of composing an email address! + $_ = $output [0]; + my @line = split; + my ($name, $email, $username); + + if (scalar @line eq 3) { + $name = $line [0] . " " . $line [1]; + $email = $line [0] . "." . $line [1] . "\@hp.com"; + $username = $line [2]; + $element .= " \"$name\" <$email> ($username)"; + } elsif (scalar @line eq 2) { + $name = $line [0]; + $username = $line [1]; + $element .= " \"$name\" ($username)"; + } # if + + push @failures, $element; + } # if + } # foreach + + return @failures; +} # CheckForFailures + +sub Deliver { + my $stream = shift; + my $view_tag = shift; + my $log = shift; + + $log->msg ("Delivering $stream -> $view_tag"); + + # Here we do the actual delivery. Note we use all of -force, -abort + # and -complete. The force option says "Don't prompt me - just do + # it!". The abort says abort this delivery if we cannot do it in an + # automated fashion. The complete options says "If you can + # successfully merge then complete the delivery". + my ($status, @output) = Clearcase::cleartool ( + "deliver -force -abort -complete -stream $stream\@\\$pvob -to $view_tag 2>&1", + $true + ); + + foreach (@output) { + $log->msg ($_); + } # foreach + + if ($status ne 0) { + $log->msg ("Unable to deliver from $stream -> $view_tag"); + return $false; + } else { + $log->msg ("Delivery from $stream stream to $view_tag view successful"); + return $true; + } # if +} # Delivery + +# Get options +my $stream; +my $view_tag; +my $result = GetOptions ("debug" => sub { set_debug }, + "usage" => sub { Usage }, + "verbose" => sub { set_verbose }, + "stream=s" => \$stream, + "view_tag=s" => \$view_tag, + ); + +Usage "Stream must be specified" if !defined $stream; +Usage "View tag must be specified" if !defined $view_tag; + +my $view = new Clearcase::View (tag => $view_tag); + +Usage "View tag $view_tag is not a valid view" if !defined $view; + +# Should put in code to validate that the stream is a valid Clearcase object. + +my $status = Deliver $stream, $view_tag, $log; +my $subject = "Delivery from $stream -> $view_tag "; +$subject .= $status eq $true ? "succeeded" + : "failed"; +my $heading = "

Delivery from $stream -> $view_tag "; +$heading .= $status eq $true ? "succeeded" + : "failed"; +$heading .= "

"; + +my %additional_emails; + +if ($status ne $true) { + my @failures = CheckForFailures ($log); + + if (scalar @failures gt 0) { + $heading .= "\n

The following elements could not be automatically merged:

"; + $heading .= "\n
    \n"; + + foreach (@failures) { + if (/<(.*)>.*\((\w*)\)/) { + $additional_emails {$2} = $1; + } # if + $heading .= "
  1. $_
  2. \n"; + } # foreach + $heading .= "
\n"; + } # if +} # if + +$log->maillog ( + from => $from_address, + to => $to_addresses, + cc => (join ",", values (%additional_emails)), + mode => "html", + subject => YMD . ": " . $subject, + heading => $heading, + footing => "-- \n
Regards,
\nRelease Engineering", +); diff --git a/cc/etf.pl b/cc/etf.pl new file mode 100755 index 0000000..8c948c5 --- /dev/null +++ b/cc/etf.pl @@ -0,0 +1,471 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: etf.pl,v $ + +Evil Twin Finder + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision: + +$Revision: 1.5 $ + +=item Created: + +Fri Apr 23 09:40:31 PDT 2010 + +=item Modified: + +$Date: 2011/01/09 01:00:28 $ + +=back + +=head1 SYNOPSIS + + Usage: eft.pl [-u|sage] [-ve|rbose] [-d|ebug] [-di|rectory ] + + Where: + + -u|sage: Displays usage + -ve|rbose: Be verbose + -d|ebug: Output debug messages + + -dir Directory to process + +=head1 DESCRIPTION + +This script will search for existing evil twins in a Clearcase vob. It is +intended to be used in the context of a base Clearcase view with a default +config spec. + +An evil twin is defined as two or more Clearcase elements that share the same +path, and same name, but have different OIDs thus having different version +histories. This can occur when a user creates an element in a directory that +used to exist in this same directory on another branch or on a previous version +of the same branch. By default Clearcase will create an element with a new OID. +This new, evil twin will then develop it's own version history. This then +becomes a problem when you attempt to merge branches - which twin (OID) should +Clearcase keep track of? + +Most Clearcase users implement an evil twin trigger to prevent the creation of +evil twins but sometimes evil twins have already been created. This script helps +identify these already existing evil twins. + +Note: Evil twins can also happen if you only apply your evil twin trigger to the +mkelem Clearcase action. It should be applied to the lnname action as elements +come into creation by things like the cleartool ln, mv and mkdir commands. These +all eventually do an lnname so that's where you should put your evil twin +trigger. + +=head1 ALGORITHM + + TODO: Is cleartool find really needed? I mean since we are going through + the extended version namespace don't we by default find all + subdirectories? + +This script will use cleartool find to process all directory elements from +$startingDir (Default '.'). For each version of the directory a hash will be +built up containing all of the element names in that directory version. +Elements are always added and never deleted in this hash as we are looking for +all elements that have ever existed in the directory at any point in time. + +This script then dives into the view extended namespace for directory elements +examining the internal Clearcase structures. If we find a branch we recurse or +numbered directory version we recurse looking for file elements (TODO: What +about directory evil twins?). Note that we skip version 0 as version 0 is never +interesting - it is always a duplicate of what it branched from and empty. + +Directory versions that are not numbered are labels or baselines that point to +numbered directory versions so we don't need to look at them again. + +For each file element we find we use the cleartool dump command to get the OID +of this particiular versioned element and build up an array of hashes of all the +elements in the directory. For each element version we maintain a hash keyed by +the OID. The structure also contains a count of the number of times the OID was +found. An evil twin therefore will have multiple OIDs for the same element +version name. + +After the directory is processed we look though the array of hashes for elements +that have multiple OIDs and report them. Then we proceed to the next directory. + +=cut + +use strict; +use warnings; + +use Getopt::Long; +use File::Basename; +use Cwd; + +use FindBin; +use lib "$FindBin::Bin/../lib"; + +use Clearcase; +use Clearcase::Element; +use Display; +use Logger; +use TimeUtils; +use Utils; + +my $VERSION = '1.0'; + +my (%total, %dirInfo, $log, $startTime); + +=pod + +=head2 reportDir (%directoryInfo) + +Report any evil twins found in %directoryInfo + +Parameters: + +=for html
+ +=over + +=item %directoryInfo + +Structure representing the OIDs of element in a direcotry + +=back + +=for html
+ +Returns: + +=for html
+ +=over + +=item nothing + +=back + +=for html
+ +=cut + +sub reportDir (%) { + my (%directoryInfo) = @_; + + my $ets = 0; + + foreach my $filename (sort keys %directoryInfo) { + my @oids = @{$directoryInfo{$filename}}; + + if (scalar @oids > 1) { + $ets++; + + $log->msg ("File: $filename"); + + foreach (@oids) { + $log->msg ("\tOID: $$_{OID} ($$_{count})"); + $log->msg ("\tFirst detected \@: $$_{version}"); + } # foreach + } # if + } # foreach + + return $ets; +} # reportDir + +=pod + +=head2 proceedDir $dirName + +Build up a data structure for $dirName looking for evil twins + +Parameters: + +=for html
+ +=over + +=item $dirName + +Directory to examine + +=back + +=for html
+ +Returns: + +=for html
+ +=over + +=item %dirInfo + +Directory info hash keyed by element name whose value is an array of oidInfo +hashes containing a unique OID and a count of how many occurences of that OID +exist for that element. + +=back + +=for html
+ +=cut + +sub processDir ($); +sub processDir ($) { + my ($dirName) = @_; + + opendir my $dir, $dirName + or $log->err ("Unable to open directory $dirName - $!", 1); + + my @dirVersions = grep {!/^\./} readdir $dir; + + closedir $dir; + + my ($directory, $version) = split /$Clearcase::SFX/, $dirName; + + $directory = basename (cwd) + if $directory eq '.'; + + my $displayName = "$directory$Clearcase::SFX$version"; + + # We only want to deal with branches and numbered versions. Non-numbered + # versions which are not branches represent labels and baselines which are + # just aliases for directory and file elements. Branches represent recursion + # points and numbered versions represent unique directory versions. + my @elements; + + foreach (@dirVersions) { + my ($status, @output) = $Clearcase::CC->execute ( + "describe -fmt %m $dirName/$_" + ); + my $objkind = $output[0]; + + if ($objkind =~ / element/) { + push @elements, $_; + } elsif (/^\d/ or $objkind eq 'branch') { + # Skip 0 element - it's never interesting. + next if $_ eq '0'; + + # Recurse for branches and numbered directory versions + if ($objkind eq 'branch') { + $total{branches}++; + } else { + $total{'directory versions'}++; + } # if + + verbose_nolf '.'; + + #$log->log ("Recurse:\t$displayName/$_"); + + %dirInfo = processDir "$dirName/$_"; + + next; + } # if + } # foreach + + foreach (@elements) { + $total{'element versions'}++; + + #$log->log ("Element:\t$displayName/$_"); + + # Get oid using the helper function + my $oid = Clearcase::Element::oid "$dirName/$_"; + + if ($dirInfo{$_}) { + my $found = 0; + + # Search our %dirInfo for a version matching $version + foreach (@{$dirInfo{$_}}) { + # Increment count if we find a matching oid + if ($$_{OID} eq $oid) { + $$_{count}++; + $found = 1; + last; + } # if + } # foreach + + unless ($found) { + # If we didn't find a match then make a new %objInfo starting with a + # count of 1. Also save this current $version, which is the first + # instance of this new oid. + push @{$dirInfo{$_}}, { + OID => $oid, + count => 1, + version => "$dirName/$_", + }; + } # unless + } else { + $dirInfo{$_} = [{ + OID => $oid, + count => 1, + version => "$dirName/$_", + }]; + } # if + } # foreach + + return %dirInfo; +} # processDir + +=pod + +=head2 proceedDirs $startingDir + +Process all directories under $startingDir + + +Parameters: + +=for html
+ +=over + +=item $startingDir + +Directory to start processing + +=back + +=for html
+ +Returns: + +=for html
+ +=over + +=item $total{etf} + +Total number of evil twins found + +=back + +=for html
+ +=cut + +sub processDirs ($) { + my ($startingDir) = @_; + + my $cmd = "cleartool find \"$startingDir\" -type d -print"; + + open my $dirs, '-|', $cmd + or $log->err ("Unable to execute $cmd - $!", 1); + + while (<$dirs>) { + chomp; chop if /\r$/; + + my $displayName = $_; + + $displayName =~ s/\@\@$//; + + if ($displayName eq '.') { + $displayName = basename (cwd); + } # if + + $log->msg ("Processing $displayName"); + + my $startingTime = time; + my %directoryInfo = processDir $_; + + verbose ''; + + display_duration $startingTime, $log; + + $total{'evil twins'} += reportDir %dirInfo; + } # while + + close $dirs + or $log->err ("Unable to close $cmd - $!"); + + return $total{'evil twins'}; +} # processDirs + +# Main +local $| = 1; + +my $startingDir = '.'; + +GetOptions ( + usage => sub { Usage }, + verbose => sub { set_verbose }, + debug => sub { set_debug }, + 'directory=s' => \$startingDir, +) or Usage 'Invalid parameter'; + +$startTime = time; + +$log = Logger->new; + +$log->msg ("Evil Twin Finder $FindBin::Script v$VERSION"); + +processDirs $startingDir; + +Stats \%total, $log; + +$log->msg ("$FindBin::Script finished @ " . localtime); + +display_duration $startTime, $log; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Clearcase + Clearcase::Element + Display + Logger + TimeUtils + Utils + +=end man + +=begin html + +
+Clearcase
+Element
+Display
+Logger
+TimeUtils
+Utils
+
+ +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut diff --git a/cc/findview b/cc/findview new file mode 100644 index 0000000..083c137 --- /dev/null +++ b/cc/findview @@ -0,0 +1,90 @@ +#!/usr/bin/perl +################################################################################ +# +# File: findview,v +# Revision: 1.1.1.1 +# Description: This script will locate a view by searching through the various +# regions. +# Author: Andrew@DeFaria.com +# Created: Mon May 3 09:06:55 PDT 2004 +# Modified: 2007/05/17 07:45:48 +# Language: Perl +# +# (c) Copyright 2004, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +use strict; +use warnings; + +use FindBin; +use lib "$FindBin::Bin/../lib"; + +use Display; + +sub Usage { + display "Usage $FindBin::Script: [ ... | -u ]"; + display "\nWhere:"; + display "\t\tName of the view to find (can be partial name)"; + + exit 1; +} # Usage + +sub SearchRegions { + my $view = shift; + + my $nbr_views = 0; + + # Get a list of regions + my @regions = `cleartool lsregion`; + my $region; + + # Process each region + foreach $region (@regions) { + chomp $region; + chop $region if $region =~ /\r/; # Remove carriage returns + + verbose "Searching $region region...\n"; + + # Get a list of views in the region + my @lines = `cleartool lsview -region $region`; + + # Parse the lines extracting view tag and storage area + foreach (@lines) { + verbose "Searching view $_"; + if (/[\* ]\s*(\S*)\s*\S*/) { + my $name = $1; + + if ($name =~ /$view/i) { + display "\t$name ($region)"; + $nbr_views++; + next; + } # if + } # if + } # foreach @lines + } # foreach @regions + + return $nbr_views; +} # SearchRegions + +# Get parms +if (defined $ARGV [0] and $ARGV [0] =~ /^-u/) { + Usage; +} # if + +foreach (@ARGV) { + verbose "Searching for views containing \"$_\""; + my $nbr_views = SearchRegions $_; + + if ($nbr_views eq 0) { + display "No views found" + } elsif ($nbr_views eq 1) { + display "1 view found"; + } else { + display "$nbr_views views found"; + } # if + + verbose " matching \"$_\""; +} # foreach + +# All done... +exit 0; diff --git a/cc/findvob b/cc/findvob new file mode 100644 index 0000000..02fa81b --- /dev/null +++ b/cc/findvob @@ -0,0 +1,88 @@ +#!/usr/bin/perl +################################################################################ +# +# File: findvob,v +# Revision: 1.1.1.1 +# Description: This script will locate a vob by searching through the various +# regions. +# Author: Andrew@DeFaria.com +# Created: Mon May 3 09:06:55 PDT 2004 +# Modified: 2007/05/17 07:45:48 +# Language: Perl +# +# (c) Copyright 2004, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +use strict; +use warnings; + +use FindBin; +use lib "$FindBin::Bin/../lib"; + +use Display; + +sub Usage { + display "Usage $FindBin::Script: [ ... | -u ]"; + display "\nWhere:"; + display "\t\tName of the vob to find (can be partial name)"; + + exit 1; +} # Usage + +sub SearchRegions { + my $vob = shift; + + my $nbr_vobs = 0; + + # Get a list of regions + my @regions = `cleartool lsregion`; + my $region; + + # Process each region + foreach $region (@regions) { + chomp $region; + chop $region if $region =~ /\r/; # Remove carriage returns + + # Get a list of vovs in the region + my @lines = `cleartool lsvob -region $region`; + + # Parse the lines extracting vob tag and storage area + foreach (@lines) { + if (/[\* ]\s*(\S*)\s*\S*/) { + my $name = $1; + + if ($name =~ /$vob/i) { + display "\t$name ($region)"; + $nbr_vobs++; + next; + } # if + } # if + } # foreach @lines + } # foreach @regions + + return $nbr_vobs; +} # SearchRegions + +# Get parms +if (defined $ARGV [0] and $ARGV [0] =~ /^-u/) { + Usage; +} # if + +foreach (@ARGV) { + verbose "Searching for vobs containing \"$_\"\n"; + my $nbr_vobs = SearchRegions $_; + + if ($nbr_vobs eq 0) { + display "No vobs found" + } elsif ($nbr_vobs eq 1) { + display "1 vob found"; + } else { + display "$nbr_vobs vobs found"; + } # if + + verbose " matching \"$_\"\n"; +} # foreach + +# All done... +exit 0; + diff --git a/cc/lockvobs b/cc/lockvobs new file mode 100644 index 0000000..e360d58 --- /dev/null +++ b/cc/lockvobs @@ -0,0 +1,283 @@ +#!/usr/bin/perl +################################################################################ +# +# File: lockvobs,v +# Revision: 1.1.1.1 +# Description: [Un]locks all vobs in the current region, reports results +# Author: Andrew@DeFaria.com +# Created: Mon Mar 15 08:48:24 PST 2004 +# Modified: 2007/05/17 07:45:48 +# Language: Perl +# +# (c) Copyright 2004, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +use strict; +use warnings; + +use Net::SMTP; +use File::Spec; + +# This will be set in the BEGIN block but by putting them here the become +# available for the whole script. +my ( + $abs_path, + $me, + $bin_path, + $triggers_path, + $lib_path, + $log_path, + $etc_path, + $windows +); + +BEGIN { + # Extract relative path and basename from script name. + $0 =~ /(.*)[\/\\](.*)/; + + $abs_path = (!defined $1) ? "." : File::Spec->rel2abs ($1); + $me = (!defined $2) ? $0 : $2; + + # Check to see if we are running on Windows + $windows = ($^O =~ /MSWin/) ? "yes" : "no"; + + # Setup paths + $bin_path = "$abs_path"; + $triggers_path = "$abs_path/../triggers"; + $lib_path = "$abs_path/../lib"; + $log_path = "$abs_path/../log"; + $etc_path = "$abs_path/../etc"; + + # Add the appropriate path to our modules to @INC array. + unshift (@INC, "$lib_path"); +} # BEGIN + +use Display; + +# Store logfile in CM_TOOLS/logs +my $logfile = "$log_path/lockvobs.log"; + +# Production vob server +my $vob_server = defined $ENV {VOBSERVER} ? $ENV {VOBSERVER} : undef; + +# Gotta be from somebody! +my $from = defined $ENV {FROM} ? $ENV {FROM} : undef; + +# This should be changed to an email alias +my @to = (); + +# Who gets notified when there are errors +my @errors_to = (); + +my $unlock = "no"; +my $execute = "yes"; +my $smtphost = "appsmtp"; + +# Exceptions file +my $exceptions_file = "$etc_path/vob_exceptions"; + +# Any errors? +my $errors = 0; + +sub Usage { + my $me = $0; + + $me =~ s/\.\///; + + print "Usage $me:\t[-u] [-n] [-smtphost ] [-to ]\n"; + print "\nWhere:\n"; + print "\t-u\t\tUnlock vobs (default lock vobs)\n"; + print "\t-smtphost\tSpecifies what SMTP host to use for mail (default\n"; + print "\t\t\tnotesmail01)\n"; + print "\t-to\t\tComma separated list (no spaces) of email addresses to\n"; + print "\t\t\tsend output to (default: bsomisetty\@ameriquest.net,\n"; + print "\t\t\tsgopavarapu\@ameriquest.net)\n"; + print "\t-errors-to\tComma separated list (no spaces) of email addresses\n"; + print "\t\t\tto send (only errors) to (default:\n"; + print "\t\t\tadefaria\@ameriquest.net)\n"; + exit 1; +} # Usage + +sub logmsg { + my $msg = shift; + + open LOGFILE, ">>$logfile" + or die "Unable to open logfile: $logfile - $!"; + + print LOGFILE $msg . "\n"; + + close LOGFILE; +} # logmsg + +sub notify { + my $smtphost = shift; + my $from = shift; + my $errors = shift; + my $unlock = shift; + + my $subject = $unlock eq "yes" ? "Unlock VOBs" : "Lock VOBs"; + + # Connect to mail server + my $smtp = Net::SMTP->new ($smtphost); + + die "Unable to open connection to mail host: $smtphost\n" if !defined $smtp; + + # Compose message + $smtp->mail ($from); + + if ($errors ne 0) { + # Add @errors_to + foreach (@errors_to) { + push @to, $_; + } # foreach + } # if + + # Add @to + foreach (@to) { + $smtp->to ($_); + } # foreach + + # Start email data + $smtp->data (); + + # Add From line + $smtp->datasend ("From: $from\n"); + + # Add @to and @errors_to + $smtp->datasend ("To: " . join (",", @to) . "\n"); + + # Add subject + $smtp->datasend ("Subject: $subject\n\n"); + + # Open logfile + open LOGFILE, $logfile + or die "Unable to open logfile $logfile - $!\n"; + + while () { + $smtp->datasend ($_); + } # while + + $smtp->dataend (); + $smtp->quit; + + return 0; +} # notify + +sub Error { + my $msg = shift; + + logmsg $msg; + + $errors++; + + notify $smtphost, $from, $errors, $unlock; + + exit $errors; +} # Error + +sub IsAMember { + my $item = shift; + my @list = @_; + + $item =~ s/\\//g; + + foreach (@list) { + chomp; + s/\\//g; + return 1 if $item eq $_; + } # foreach + + return 0; +} # IsAMember + +# Get parms +while ($#ARGV >= 0) { + if ($ARGV [0] eq "-u") { + $unlock = "yes"; + shift; + next; + } # if + + if ($ARGV [0] eq "-n") { + $execute = "no"; + shift; + next; + } # if + + if ($ARGV [0] eq "-smtphost") { + shift; + $smtphost = $ARGV [0]; + shift; + next; + } # if + + if ($ARGV [0] eq "-to") { + shift; + @to = split /,/,$ARGV [0]; + shift; + next; + } # if + + if ($ARGV [0] eq "-errors-to") { + shift; + @errors_to = split /,/,$ARGV [0]; + shift; + next; + } # if + + Usage; +} # while + +Usage "Vob server hasn't been defined" if !defined $vob_server; +Usage "From has not been specified" if !defined $from; +Usage "To has not been specified" if @to; +Usage "Errors to has not been specififed" if @errors_to; + +open EXCEPTIONS, $exceptions_file + or error "Unable to open exceptions file ($exceptions_file)", 1; + +my @exceptions = ; + +# Remove logfile if present +unlink ($logfile) if (-e $logfile); + +# Get list of vobs +open (VOBS, "cleartool lsvob -short -host $vob_server|") + or Error "Can't list vobs: $!"; + +# Process them +while () { + chomp; + chop if /\r/; # any carriage return + + next if $#exceptions ne 0 and IsAMember ($_, @exceptions); + + $_ = "\\" . $_ if $windows ne "yes"; + + # [Un]lock the vob + if ($unlock eq "yes") { + if ($execute eq "no") { + print "[noexecute] cleartool unlock vob:$_\n"; + } else { + system ("cleartool unlock vob:$_ >> $logfile 2>&1"); + } # if + } else { + if ($execute eq "no") { + print "[noexecute] cleartool lock vob:$_\n"; + } else { + system ("cleartool lock vob:$_ >> $logfile 2>&1"); + } # if + } # if + + # Convert the status + my $status = $? >> 8; + + if ($status ne 0) { + $errors++; + } # if +} # while + +my $status = $execute eq "yes" ? notify $smtphost, $from, $errors, $unlock : 0; + +exit $status; diff --git a/cc/log_activity b/cc/log_activity new file mode 100644 index 0000000..0ca34d0 --- /dev/null +++ b/cc/log_activity @@ -0,0 +1,168 @@ +#!/usr/bin/perl +################################################################################ +# +# File: log_activity,v +# Revision: 1.1.1.1 +# Description: Logs Clearcase activity +# Author: Andrew@DeFaria.com +# Created: Tue Dec 27 16:33:30 PST 2005 +# Modified: 2007/05/17 07:45:48 +# Language: perl +# +# (c) Copyright 2000-2005, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +use strict; +use warnings; +use File::Spec; + +my $me; + +BEGIN { + # Set $lib_path + my $lib_path = $^O =~ /MSWin/ ? "\\\\brcm-irv\\dfs\\projects\\ccase\\SCM\\lib" + : "/projects/ccase/SCM/lib"; + + # Extract relative path and basename from script name. + $0 =~ /(.*)[\/\\](.*)/; + + my $abs_path = (!defined $1) ? "." : File::Spec->rel2abs ($1); + $me = (!defined $2) ? $0 : $2; + $me =~ s/\.pl$//; + + # Add the appropriate path to our modules to @INC array. + unshift @INC, "$lib_path"; + unshift @INC, $ENV {SITE_PERL_LIBPATH} if defined $ENV {SITE_PERL_LIBPATH}; + unshift @INC, "$abs_path"; +} # BEGIN + +use OSDep; +use Display; +use DateUtils; +use Logger; +use Clearcase; +use Clearcase::Vobs; +use Clearcase::View; + +# The lshistory command needs a view context. We'll create this view +# if necessary. +my $tag = $ENV {DEFAULT_VIEW} ? $ENV {DEFAULT_VIEW} : "default"; + +# Path to logs directory +my $logdir = "$scm_base$/logs"; + +error "Logdir $logdir does not exist - $!", 1 if !-d $logdir; + +sub Usage { + my $msg = shift; + + display "ERROR: $msg\n" if defined $msg; + + display "Usage: $me\t[-u] [-v] [-d] [-n <# of days>] + +Where: + + -u: Display usage + -v: Turn on verbose mode + -d: Turn on debug mode + -n: Number of days to report (Default: 1) + +Note: Number of days is relative to midnight. Output is to a logfile named +activity..log. Since we want to be accurate this script attempts +to have each log file have only that days activity. Therefore -n 1 will report +everything in the last full 24 hour day, -n2 will be the last two full 24 hour +days, etc. +"; + exit 1; +} # Usage + +my $today = time; + +sub ReportActivity { + my $view = shift; + my $since = shift; + + my $cc = Clearcase->new; + # This is Unix only! + my $cmd = "$Clearcase::cleartool setview -exec \"$Clearcase::cleartool lshistory -since $since -avobs -fmt '%Nd;%Fu;%u@%h;%e;%n\\n'\" $view"; + + open OUTPUT, "$cmd|" + or error "Unable to open pipe for $cmd", 1; + + my $today_ymd = YMD; + my $date = YMD; + my $logfile; + + while () { + # Split the line into fields. The first field is date and time + my @fields = split /;/; + + # Now split the first field by "." which separates the date and time + @fields = split /\./, $fields [0]; + + # Never report today's activity because today's never over! + # Reporting on today may give a partial result + next if $fields [0] eq $today_ymd; + + # Ugh - might have stuff that's future dated! + next if $fields [0] gt $today_ymd; + + # Skip noise. In this case noise is activity to the perftest + # vob. No normal activity happens to perftest - just performance + # testing. + next if /${Clearcase::vobtag_prefix}perftest/; + + if ($fields [0] lt $date) { + $date = $fields [0]; + my $log_filename = $cc->sitename . ".activity.$date"; + verbose "Starting logfile $log_filename"; + $logfile = undef; + $logfile = Logger->new (name => $log_filename, path => $logdir); + } # if + + chomp; + $logfile->log ($_); + } # while + + close OUTPUT; +} # ReportActivity + +my $nbr_days = 1; + +while ($ARGV [0]) { + if ($ARGV [0] eq "-v") { + Display::set_verbose; + } elsif ($ARGV [0] eq "-d") { + set_debug; + } elsif ($ARGV [0] eq "-n") { + shift @ARGV; + if ($ARGV [0]) { + $nbr_days = $ARGV [0]; + } else { + Usage "Need to specify nbr_days after -n"; + } # if + } elsif ($ARGV [0] eq "-u") { + Usage; + } else { + Usage "Invalid argument: $ARGV [0]"; + } # if + + shift (@ARGV); +} # while + +my $date = YMD; + +verbose "Creating view $tag"; +my $view = Clearcase::View->new (tag => $tag); +$view->create; + +verbose "Mounting all vobs"; +my $vobs = Clearcase::Vobs->new; +$vobs->mount; + +# Compute $since +my $seconds_in_day = 60 * 60 * 24; +my $since = YMD ($today - ($nbr_days * $seconds_in_day)); + +verbose "Producing report"; +ReportActivity $tag, $since; diff --git a/cc/lscset b/cc/lscset new file mode 100644 index 0000000..112a4ce --- /dev/null +++ b/cc/lscset @@ -0,0 +1,118 @@ +#!/usr/bin/perl +################################################################################ +# +# File: lscset,v +# Revision: 1.1.1.1 +# Description: This script will list change sets for activities +# Author: Andrew@DeFaria.com +# Created: Thu Apr 27 18:10:37 PDT 2006 +# Modified: 2007/05/17 07:45:48 +# Language: Perl +# +# (c) Copyright 2006, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +use strict; +use warnings; + +use FindBin; +use lib "$FindBin::Bin/../lib"; + +use OSDep; +use Display; +use Clearcase; +use Utils; + +my $me = $FindBin::Script; + +# This is site specific - and problematic! +my $pvob_name = "ilm_pvob"; +my $pvob = ($arch eq "windows" or $arch eq "cygwin") ? + "\\" . "$Clearcase::vobtag_prefix$pvob_name" : + "$Clearcase::vobtag_prefix$pvob_name"; + +sub Usage { + my $msg = shift; + + display "Usage: $me: [ ]"; + + if (defined $msg) { + error "$msg", 1; + } # if + + exit 0; +} # Usage + +sub GetChangeSet { + my $activity = shift; + my $current_view = shift; + + my @changes; + my $cmd = "cleartool lsactivity -l $activity\@$pvob 2>&1"; + my @output = `$cmd`; + my $status = $?; + + if ($status ne 0) { + warning "$activity Activity does not exist"; + return; + } else { + my $found_changeset = $false; + + foreach (@output) { + if (!$found_changeset) { + if (/ change set versions/) { + $found_changeset = $true; + next; + } else { + next; + } # if + } else { + if (/\s*(.*)/) { + my $element = $1; + # Trim off view stuff + if ($element =~ /$current_view(.*)/) { + $element = $1; + } # if + push @changes, $element; + } # if + } # if + } # foreach + + return @changes; + } # if +} # GetChangeSet + +sub GetPWV { + my $cmd = "cleartool pwv -s"; + my @output = `$cmd`; + chomp @output; + my $status = $?; + + my $view = $output [0]; + chop $view if $view =~ /\r/; + + if ($status ne 0 or $view =~ /\*\* NONE \*\*/) { + return undef; + } else { + return $view; + } # if +} # GetPWV + +sub DisplayChangeSet { + my $activity = shift; + my @changes = @_; + + display "$_" foreach (@changes); +} # DisplayChangeSet + +Usage "Must specify an activity" if !defined $ARGV [0]; + +# Should probably make a constructor for Clearcase::View to return the +# current view, if any. +my $current_view = GetPWV; + +Usage "Must be in a view" if !$current_view; + +my @activity = @ARGV; + +DisplayChangeSet $_, GetChangeSet $_, $current_view foreach (@activity); diff --git a/cc/lsnusers b/cc/lsnusers new file mode 100644 index 0000000..eebfe2d --- /dev/null +++ b/cc/lsnusers @@ -0,0 +1,111 @@ +#!/usr/bin/perl +################################################################################ +# +# File: $RCSfile: lsnusers,v $ +# Revision: $Revision: 1.3 $ +# Description: This script will perform builds for ILM/HP. +# Author: Andrew@DeFaria.com +# Created: Mon Feb 13 10:35:34 PST 2006 +# Modified: $Date: 2011/08/31 21:57:06 $ +# Language: Perl +# +# (c) Copyright 2006, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +use strict; +use warnings; + +use FindBin; +use lib "$FindBin::Bin/../lib"; + +use Getopt::Long; + +use OSDep; +use Display; +use Utils; + +my $me = $FindBin::Script; + +# Pick up from the environment if the user specifies pvob +my $pvob = $ENV{pvob}; + +my @pvob_related_objects = ( + "activity", + "stream", +); + +sub Usage { + my $msg = shift; + + display "Usage: $me: "; + + if (defined $msg) { + error "$msg", 1; + } # if + + exit 0; +} # Usage + +Usage "Must specify an object selector" if !defined $ARGV [0]; + +my $object = $ARGV [0]; +my $object_type = $object; +my $full_object; + +$object_type =~ s/:.*//; + +if ($object =~ m/(.*)\@(.*)/) { + $object = $1; + $pvob = $2; +} # if + +Usage "Must specify pvob or set pvob in your environment" if !$pvob; + +if (InArray $object_type, @pvob_related_objects) { + # Need to add additional "\\" because Windows will eat them up when executing a ``; + if ($arch eq "windows" or $arch eq "cygwin") { + $full_object = "$object\@\\$pvob"; + } else { + $full_object = "$object\@$pvob"; + } # if +} else { + $full_object = $object; + + # Handle oddity with windows using \ for vob tags + if ($full_object =~ /vob:\\(.*)/) { + $full_object = "vob:\\\\" . $1; + } # if +} # if + +my $cmd = "cleartool lslock $full_object 2>&1"; +my @output = `$cmd`; +my $status = $?; + +if ($status eq 0) { + if (scalar @output eq 0) { + display "$object is not locked"; + exit 0; + } # if +} else { + display "$object does not exist"; + exit 1; +} # if + +my @users; + +foreach (@output) { + if (/\"Locked except for users: (.*)\"/) { + @users = split " ", $1; + last; + } # if +} # foreach + +if ((scalar @users) gt 0) { + display "Users excluded from lock for this $object_type include:"; + + foreach (sort @users) { + display "\t$_"; + } # foreach +} else { + display "This $object_type is locked from all users"; +} # if diff --git a/cc/mknusers b/cc/mknusers new file mode 100644 index 0000000..71c67af --- /dev/null +++ b/cc/mknusers @@ -0,0 +1,129 @@ +#!/usr/bin/perl +################################################################################ +# +# File: $RCSfile: mknusers,v $ +# Revision: $Revision: 1.3 $ +# Description: This script will add a user to the nusers list for a lock +# Author: Andrew@DeFaria.com +# Created: Mon Feb 13 10:35:34 PST 2006 +# Modified: $Date: 2011/08/31 21:57:06 $ +# Language: Perl +# +# (c) Copyright 2006, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +use strict; +use warnings; + +use FindBin; +use lib "$FindBin::Bin/../lib"; + +use Getopt::Long; + +use OSDep; +use Display; +use Utils; + +my $me = $FindBin::Script; + +# Pick up from the environment if the user specifies pvob +my $pvob = $ENV{pvob}; + +my @pvob_related_objects = ( + "activity", + "stream", +); + +sub Usage { + my $msg = shift; + + display "Usage: $me: []"; + + if (defined $msg) { + error "$msg", 1; + } # if + + exit 0; +} # Usage + +sub AddNuser { + my $object = shift; + my $username = shift; + my @users = @_; + + my $cmd = "cleartool lock -replace -nusers "; + + foreach (@users) { + $cmd .= "$_,"; + } # foreach + + $cmd .= "$username $object"; + + my @output = `$cmd`; + + return $?; +} # AddNuser + +my $object = shift; +my @users = @ARGV; + +Usage "Must specify an object selector" if !defined $object; +Usage "Must specify an username" if scalar @users eq 0; + +my $object_type = $object; +my $full_object; + +$object_type =~ s/:.*//; + +if ($object =~ m/(.*)\@(.*)/) { + $object = $1; + $pvob = $2; +} # if + +Usage "Must specify pvob or set pvob in your environment" if !$pvob; + +if (InArray $object_type, @pvob_related_objects) { + # Need to add additional "\\" because Windows will eat them up when executing a ``; + if ($arch eq "windows" or $arch eq "cygwin") { + $full_object = "$object\@\\$pvob"; + } else { + $full_object = "$object\@$pvob"; + } # if +} else { + $full_object = $object; + + # Handle oddity with windows using \ for vob tags + if ($full_object =~ /vob:\\(.*)/) { + $full_object = "vob:\\\\" . $1; + } # if +} # if + +foreach (@users) { + my $cmd = "cleartool lslock $full_object 2>&1"; + my @output = `$cmd`; + my $status = $?; + + if ($status ne 0) { + display "$object does not exist"; + exit 1; + } # if + + my @current_users; + + foreach (@output) { + if (/\"Locked except for users: (.*)\"/) { + @current_users = split " ", $1; + last; + } # if + } # foreach + + if (InArray $_, @current_users) { + error "User $_ is already on the nusers list for $object", 1; + } else { + if (AddNuser $full_object, $_, @current_users) { + error "Unable to add $_ to nusers list for $object", 2; + } else { + display "User $_ added to the list of nusers for $object"; + } # if + } # if +} # foreach diff --git a/cc/mktriggers.pl b/cc/mktriggers.pl new file mode 100755 index 0000000..2ee2570 --- /dev/null +++ b/cc/mktriggers.pl @@ -0,0 +1,472 @@ +#!/usr/bin/perl +use strict; +use warnings; + +=head2 NAME $RCSfile: mktriggers.pl,v $ + +Enforce the application of triggers to vobs + +=head2 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision: + +$Revision: 1.6 $ + +=item Created: + +Sat Apr 3 09:06:11 PDT 2003 + +=item Modified: + +$Date: 2011/03/24 22:22:00 $ + +=head2 SYNOPSIS + + Usage: mktriggers.pl [-u|sage] [-[no]e|xec] [-[no]a|dd] [-[no]r|eplace] + [-[no]p|rivate] [ -vobs ] [-ve|rbose] [-d|ebug] + + Where: + + -u|sage: Displays usage + -[no]e|exec: Execute mode (Default: Do not execute) + -[no]a|dd: Add any missing triggers (Default: Don't add) + -[no]r|eplace: Replace triggers even if already present (Default: + Don't replace) + + Note: If neither -add nor -replace is specified then + both -add and -replace are performed. + + -triggers: Name of triggers.dat file (Default: + $FindBin::Bin/../etc/triggers.dat) + -[no]p|rivate: Process private vobs (Default: Don't process private + vobs) + + -ve|rbose: Be verbose + -d|ebug: Output debug messages + + -vob List of vob tags to apply triggers to (default all vobs) + +Note: You can specify -vob /vobs/vob1,/vobs/vob2 -vob /vobs/vob3 which will +result in processing all of /vobs/vobs1, /vobs/vob2 and /vobs/vob3. + +=head2 DESCRIPTION + +This script parses triggers.dat and created trigger types in vobs. It is +designed to be run periodically (cron(1)) and will add/replace triggers on +all vobs by default. It can also operate on individual vobs if required. The +script is driven by a data file, triggers.dat, which describes which triggers +are to be enforced one which vobs. + +=head3 triggers.dat + +File format: Lines beginning with "#" are treated as comments Blank lines are +skipped. Spaces and tabs can be used for whitespace. + + # Globals + WinTriggerPath: \\\clearscm\triggers + LinuxTriggerPath: /net//clearscm/triggers + + # All vobs get the evil twin trigger + Trigger: EVILTWIN + Description: Evil Twin Prevention Trigger + Type: -all -element + Opkinds: -preop lnname + ScriptEngine: Perl + Script: eviltwin.pl + EndTrigger + + # Only these vobs get this trigger to enforce a naming policy + # Note the trigger script gets a parameter + Trigger: STDNAMES + Description: Enforce standard naming policies + Type: -all -element + Opkinds: -preop lnname + ScriptEngine: Perl + Script: stdnames.pl -lowercase + Vobs: \dbengine, \backend + EndTrigger + + # All vobs get rmelen trigger except ours! + Trigger: RMELEM + Description: Disable RMELEM + Type: -all -element + Opkinds: -preop lnname + ScriptEngine: Perl + Script: rmelem.pl + Novobs: \scm + EndTrigger + +=head2 ENVIRONMENT + +If the environment variable VEBOSE or DEBUG are set then it's as if -verbose +or -debug was specified. + +=head2 COPYRIGHT + +Copyright (c) 2004 Andrew DeFaria , ClearSCM, Inc. +All rights reserved. + +=cut + +use FindBin; + +use Getopt::Long; + +use lib "$FindBin::Bin/../lib"; + +use Display; +use OSDep; + +# Where is the trigger source code kept? +my ($windows_trig_path, $linux_trig_path); + +# Where is the trigger definition file? +my $etc_path = "$FindBin::Bin/../etc"; +my $triggerData = "$etc_path/triggers.dat"; + +sub Usage (;$) { + my ($msg) = @_; + + display $msg + if $msg; + + system "perldoc $FindBin::Script"; + + exit 1; +} # Usage + +sub ParseTriggerData { + open my $triggerData, '<', $triggerData + or error "Unable to open $triggerData - $!", 1; + + my @triggers; + my ($name, $desc, $type, $opkinds, $engine, $script, $vobs, $novobs); + + while (<$triggerData>) { + chomp; chop if /\r$/; + + next if /^$/; # Skip blank lines + next if /^\#/; # and comments + + s/^\s+//; # ltrim + s/\s+$//; # rtrim + + if (/^\s*WinTriggerPath:\s*(.*)/i) { + $windows_trig_path = $1; + next; + } # if + + if (/^\s*LinuxTriggerPath:\s*(.)/i) { + $linux_trig_path = $1; + next; + } # if + + if (/^\s*Trigger:\s*(.*)/i) { + $name = $1; + next; + } # if + + if (/^\s*Description:\s*(.*)/i) { + $desc = $1; + next; + } # if + + if (/^\s*Type:\s*(.*)/i) { + $type = $1; + next; + } # if + + if (/^\s*Opkinds:\s*(.*)/i) { + $opkinds = $1; + next; + } # if + + if (/^\s*ScriptEngine:\s*(.*)/i) { + $engine = $1; + next; + } # if + + if (/^\s*Script:\s*(.*)/i) { + $script = $1; + next; + } # if + + if (/^\s*Vobs:\s*(.*)/i) { + $vobs = $1; + next; + } # if + + if (/^\s*Novobs:\s*(.*)/i) { + $novobs = $1; + next; + } # if + + if (/EndTrigger/) { + my %trigger; + + $trigger{name} = $name; + $trigger{desc} = $desc; + $trigger{type} = $type; + $trigger{opkinds} = $opkinds; + $trigger{engine} = $engine; + $trigger{script} = $script; + $trigger{vobs} = !$vobs ? 'all' : $vobs; + $trigger{novobs} = $novobs ? $novobs : ''; + + push (@triggers, \%trigger); + + $name = $desc = $type = $opkinds = $engine = $script = $vobs = $novobs = ""; + } # if + } # while + + close $triggerData; + + error 'You must define WindowsTriggerPath, LinuxTriggerPath or both', 1 + unless ($windows_trig_path or $linux_trig_path); + + return @triggers; +} # ParseTriggerData + +sub RemoveVobPrefix ($) { + my ($vob) = @_; + + if ($ARCH =~ /windows/ or $ARCH =~ /cygwin/) { + $vob =~ s/^\\//; + } else { + $vob =~ s/^\/vobs\///; + } # if + + return $vob; +} # RemoveVobPrefix + +sub MkTriggerType ($$$$%) { + my ($vob, $exec, $add, $replace, %trigger) = @_; + + my $replaceOpt = ''; + + # Need an extra set of "\\" for non Windows systems such as Cygwin + # since apparently the shell if envoked, collapsing a set of "\\". + my $vobtag = $ARCH =~ /cygwin/i ? "\\$vob" : $vob; + my $status = system ("cleartool lstype trtype:$trigger{name}\@$vobtag > $NULL 2>&1"); + + if ($status == 0) { + debug "Found pre-existing trigger $trigger{name}"; + + # If we are not replacing then skip by returning + return + unless $replace; + + $replaceOpt = '-replace'; + } else { + debug "No pre-existing trigger $trigger{name}"; + + # We need to add the trigger. However, if we are not adding then skip by + # returning + return + unless $add; + } # if + + error "Sorry I only support ScriptEngines of Perl!" if $trigger{engine} ne "Perl"; + + my $win_engine = 'ccperl'; + my $linux_engine = 'Perl'; + + my ($script, $parm) = split / /, $trigger{script}; + + $parm ||= ''; + + my ($win_script, $linux_script, $execwin, $execlinux); + + $execwin = $execlinux = ''; + + if ($windows_trig_path) { + $win_script = $ARCH =~ /cygwin/i ? "\\\\$windows_trig_path\\$script" + : "$windows_trig_path\\$script"; + + warning "Unable to find trigger script $win_script ($!)" + if ($ARCH =~ /windows/i and $ARCH =~ /cygwin/) and not -e $win_script; + + $execwin = "-execwin \"$win_engine $win_script $parm\" "; + } elsif ($linux_trig_path) { + $linux_script = "$linux_trig_path/$script"; + + warning "Unable to find trigger script $linux_script ($!)" + if ($ARCH !~ /windows/i and $ARCH !~ /cygwin/) and not -e $linux_script; + + $execlinux = "-execwin \"$win_engine $win_script $parm\" "; + } # if + + my $command = + 'cleartool mktrtype ' . + "$replaceOpt " . + "$trigger{type} " . + "$trigger{opkinds} " . + "-comment \"$trigger{desc}\" " . + $execwin . + $execlinux . + "$trigger{name}\@$vobtag " . + "> $NULL 2>&1"; + + debug "Command: $command"; + + $vob =~ s/\\\\/\\/; + + $status = 0; + $status = system $command + if $exec; + + if ($status) { + error "Unable to add trigger! Status = $status\nCommand: $command"; + return 1; + } # if + + if ($replaceOpt) { + if ($replace) { + if ($exec) { + display "Replaced trigger $trigger{name} in $vob"; + } else { + display "[noexecute] Would have replaced trigger $trigger{name} in $vob"; + } # if + } # if + } else { + if ($add) { + if ($exec) { + display "Added trigger $trigger{name} to $vob"; + } else { + display "[noexecute] Would have added trigger $trigger{name} to $vob"; + } # if + } # if + } # if + + return; +} # MkTriggerType + +sub VobType ($) { + my ($vob) = @_; + + # Need an extra set of "\\" for non Windows systems such as Cygwin + # since apparently the shell if envoked, collapsing a set of "\\". + $vob = "\\" . $vob if $ARCH =~ /cygwin/; + + my @lines = `cleartool describe vob:$vob`; + + chomp @lines; chop @lines if $lines[0] =~ /\r$/; + + foreach (@lines) { + return 'ucm' + if /AdminVOB \<-/; + } # foreach + + return 'base'; +} # VobType + +sub MkTriggers ($$$$@) { + my ($vob, $exec, $add, $replace, @triggers) = @_; + + TRIGGER: foreach (@triggers) { + my %trigger = %{$_}; + + my $vobname = RemoveVobPrefix $vob; + + # Skip vobs on the novobs list + foreach (split /[\s+|,]/, $trigger{novobs}) { + my $vobtag = RemoveVobPrefix $_; + + if ($vobname eq RemoveVobPrefix $_) { + debug "Skipping $vob (on novobs list)"; + next TRIGGER; + } # if + } # foreach + + # For triggers whose vob type is "all" or unspecified make the trigger + if ($trigger{vobs} eq 'all' || $trigger{vobs} eq '') { + MkTriggerType $vob, $exec, $add, $replace, %trigger; + } elsif ($trigger{vobs} eq 'base' || $trigger{vobs} eq 'ucm') { + # If vob type is "base" or "ucm" make sure the vob is of correct type + my $vob_type = VobType ($vob); + + if ($vob_type eq $trigger{vobs}) { + MkTriggerType $vob, $exec, $add, $replace, %trigger; + } else { + verbose "Trigger $trigger{name} is for $trigger{vobs} vobs but $vob is a $vob_type vob - Skipping..."; + } # if + } else { + my @Vobs = split /[\s+|,]/, $trigger{vobs}; + + # Otherwise we expect the strings in $triggers{vobs} to be space or comma + # separated vob tags so we make sure it matches this $vob. + foreach (@Vobs) { + if ($vobname eq RemoveVobPrefix $_) { + MkTriggerType $vob, $exec, $add, $replace, %trigger; + last; + } # if + } # foreach + } # if + } # foreach + + return; +} # MkTriggers + +my ($exec, $add, $replace, $private, @vobs) = (0, 0, 0, 0); + +GetOptions ( + usage => sub { Usage }, + verbose => sub { set_verbose }, + debug => sub { set_debug }, + 'triggers=s', \$triggerData, + 'exec!', \$exec, + 'add!', \$add, + 'replace!', \$replace, + 'private!', \$private, + 'vobs=s', \@vobs, +) or Usage "Invalid parameter"; + +# This allows comma separated parms like -vob vob1,vob2,etc. +@vobs = split /,/, join (',', @vobs); + +# If the user didn't specify -add or -replace then toggle both on +$add = $replace = 1 + unless $add or $replace; + +# If the user didn't specify any -vobs then that means all vobs +@vobs = `cleartool lsvob -short` + unless @vobs; + +chomp @vobs; chop @vobs if $vobs[0] =~ /\r/; + +# Parse the triggers.dat file +debug "Parsing trigger data ($triggerData)"; + +my @triggers = ParseTriggerData; + +# Iterrate through the list of vobs +debug 'Processing ' . scalar @vobs . ' vobs'; + +foreach (sort @vobs) { + # Need an extra set of "\\" for non Windows systems such as Cygwin + # since apparently the shell if envoked, collapsing a set of "\\". + my $vob = $ARCH =~ /cygwin/i ? "\\$_" : $_; + my $line = `cleartool lsvob $vob`; + + # Skip private vobs + unless ($private) { + if ($line =~ / private/) { + verbose "Skipping private vob $vob..."; + next; + } # if + } # unless + + $vob =~ s/\\\\/\\/; + + debug "Applying triggers to $vob..."; + + MkTriggers $_, $exec, $add, $replace, @triggers; +} # foreach + +debug 'All triggers applied'; diff --git a/cc/msl/delete.gif b/cc/msl/delete.gif new file mode 100644 index 0000000000000000000000000000000000000000..5230a99b6d67587158eb6a2161e0fdb501535caf GIT binary patch literal 331 zcmV-R0kr-{Nk%w1VG{ro0K@x-`HQOAsvrP>l(v;P8;G{; zxWnFkqUySVbKShPA^8LV00000EC2ui022Tc000HZVD^k;X^vcztZeJ9gIdgVZ6}Oe zKL|bc1)K^&EG!O<;*moXCIG}Ep)x^y4g^iLkSjnD6^y~)c^nKB$F>V-TQr368RNK2 z0Nh65-o9wY1_TZndfCm?pcR2_Upb%FE7;y#_8=wH5 z5)cui6byf36|Jic0Id-fferxye{=y67C8VN5)lDu0?5k_&8^1B9T5Tp9mv+$$PmWK d5#bTw;^4&%0Rrml?Ck;y83Xe3^!4*006T!!h5P^j literal 0 HcmV?d00001 diff --git a/cc/msl/index.php b/cc/msl/index.php new file mode 100644 index 0000000..da8431a --- /dev/null +++ b/cc/msl/index.php @@ -0,0 +1,67 @@ + + + + + + + + + + <?php echo $heading?> + + +

+ +

Popular Streams:

+ + + +
+
+ +

All streams:  + + 

+
+ +

Back to main build page

+
+ + + diff --git a/cc/msl/lsnusers.php b/cc/msl/lsnusers.php new file mode 100644 index 0000000..047e767 --- /dev/null +++ b/cc/msl/lsnusers.php @@ -0,0 +1,75 @@ + + + + + + + + + + <?php echo $heading?> + + +

+ +Users excluded from lock for this stream include:

\n"; + print "
\n"; + sort ($nlocked_users); + foreach ($nlocked_users as $user) { + //print "
  • $user delete
  • \n"; + print "\"delete\"  "; + if (array_key_exists ($user, $nlocked_usernames)) { + print $nlocked_usernames{$user}; + } else { + print $user; + } // if + print "
    \n"; + } // foreach +} else { + print "Stream $stream is not locked."; +} // if + +print "
    \n"; +print "

    Add new user

    \n"; +?> + +
    +

    Manage Stream Locks Home

    + +
    + + diff --git a/cc/msl/private/addnuser.php b/cc/msl/private/addnuser.php new file mode 100644 index 0000000..a62271e --- /dev/null +++ b/cc/msl/private/addnuser.php @@ -0,0 +1,66 @@ + + + + + + + + + + <?php echo $heading?> + + +

    + +
    \n"; +print "\n"; +print " + + 

    +
    + +
    +

    Manage Stream Locks Home

    + +
    + + diff --git a/cc/msl/private/mknusers.php b/cc/msl/private/mknusers.php new file mode 100644 index 0000000..f4739bf --- /dev/null +++ b/cc/msl/private/mknusers.php @@ -0,0 +1,98 @@ + + + + + + + + + + <?php echo $heading?> + + +

    + + $value) { + if ($user == $value) { + $user = $key; + break; + } // if +} // foreach + +$nusers = get_nusers ($stream); + +if (count ($nusers) == 0) { + $nusers [0] = $user; + $status = chnusers ($stream, $nusers); + + if ($status == 0) { + print "$user is now allowed to access $stream"; + } else { + print "ERROR: Unable to add $user to nuser list of $stream"; + } // if +} elseif (is_member ($user, $nusers)) { + print "ERROR: $user is already allowed access to $stream
    "; +} else { + array_push ($nusers, $user); + $status = chnusers ($stream, $nusers); + + if ($status == 0) { + print "$user is now allowed to access $stream"; + } else { + print "ERROR: Unable to add $user to nuser list of $stream"; + } // if +} // if +?> + +
    +

    Manage Stream Locks for

    + +
    + + diff --git a/cc/msl/private/rmnusers.php b/cc/msl/private/rmnusers.php new file mode 100644 index 0000000..b8f0327 --- /dev/null +++ b/cc/msl/private/rmnusers.php @@ -0,0 +1,80 @@ + + + + + + + + + + <?php echo $heading?> + + +

    + +ERROR: $user is already not allowed access to $stream
    "; +} else { + $nusers = remove_from_array ($user, $nusers); + $status = chnusers ($stream, $nusers); + + if ($status == 0) { + print "$user is no longer allowed to access $stream"; + } else { + print "ERROR: Unable to remove $user from $stream"; + } // if +} // if +?> + +
    +

    Manage Stream Locks for

    + +
    + + diff --git a/cc/msl/streams.php b/cc/msl/streams.php new file mode 100644 index 0000000..51c6140 --- /dev/null +++ b/cc/msl/streams.php @@ -0,0 +1,199 @@ +DEBUG: $msg
    \n"; +} // debug + +function error ($msg) { + print "ERROR: $msg
    \n"; + exit (1); +} // error + +function get_streams () { + global $pvob; + global $cleartool; + + $cmd = "$cleartool lsstream -s -invob $pvob"; + + exec ($cmd, $output, $status); + + if ($status != 0) { + print "Unable to execute command \"$cmd\" (Status: $status)
    "; + exit (1); + } // if + + return $output; +} // get_streams + +function get_usernames () { + $cmd = "ypcat passwd"; + + exec ($cmd, $lines, $status); + + if ($status != 0) { + print "Unable to execute command \"$cmd\" (Status: $status)
    "; + exit (1); + } // if + + $users = array (); + + foreach ($lines as $line) { + $fields = explode (":", $line); + $users {$fields [0]} = $fields [4]; + } // foreach + + return $users; +} // get_usernames + +function get_users () { + $cmd = "ypcat passwd"; + + exec ($cmd, $lines, $status); + + if ($status != 0) { + print "Unable to execute command \"$cmd\" (Status: $status)
    "; + exit (1); + } // if + + $users = array (); + + foreach ($lines as $line) { + $fields = explode (":", $line); + array_push ($users, $fields [0]); + } // foreach + + return $users; +} // get_users + +function get_nusers ($stream) { + global $cleartool; + global $pvob; + + $cmd = "$cleartool lslock stream:$stream@$pvob"; + + exec ($cmd, $output, $status); + + if ($status != 0) { + print "Stream: $stream not found"; + exit (1); + } else { + if (count ($output) == 0) { + return; + } // if + } // if + + $nusers = array (); + + foreach ($output as $line) { + if (preg_match ("/\"Locked except for users: (.*)\"/", $line, $matches)) { + $nusers = split (" ", $matches [1]); + } // if + } // foreach + + return $nusers; +} // get_nusers + +function is_member ($new_item, $array) { + if (empty ($new_item) || empty ($array)) { + return 0; + } // if + + foreach ($array as $item) { + if ($new_item == $item) { + return 1; + } // if + } // foreach + + return 0; +} // is_member + +function remove_from_array ($removed_item, $array) { + $new_array = array (); + + foreach ($array as $item) { + if ($removed_item != $item) { + array_push ($new_array, $item); + } // if + } // foreach + + return $new_array; +} // remove_from_array + +function chnusers ($stream, $users) { + $nusers = ""; + + foreach ($users as $user) { + if (empty ($nusers)) { + $nusers .= $user; + } else { + $nusers .= ",$user"; + } // if + } // foreach + + $current_nusers = get_nusers ($stream); + + if (count ($current_nusers) == 0 || count ($users) == 0) { + $cmd = "./chnusers $stream $nusers"; + } else { + $cmd = "./chnusers $stream $nusers replace"; + } // if + + exec ($cmd, $output, $status); + + return $status; +} // chnusers + +function copyright ($start_year = "", $version = "") { + $today = getdate (); + $current_year = $today ["year"]; + + $this_file = $_SERVER['PHP_SELF']; + + // Handle user home web pages + if (preg_match ("/\/\~/", $this_file)) { + $this_file= preg_replace ("/\/\~(\w+)\/(\s*)/", "/home/$1/web$2/", $this_file); + } else { + $this_file = "/var/devenv/tiburon/" . $this_file; + } // if + + $mod_time = date ("F d Y @ g:i a", filemtime ($this_file)); + + print << +Last modified: $mod_time
    +Copyright © +END; + + if ($start_year != "") { + print "$start_year-"; + } // if + +print <<HP/Information Lifecycle Management Solutions
    +All rights reserved ( +END; + +print basename ($_SERVER ["PHP_SELF"], ".php"); + +if ($version != "") { + print " V$version"; +} // if + +print ")\n\n"; +} // copyright diff --git a/cc/perf/pulse b/cc/perf/pulse new file mode 100644 index 0000000..4f9818c --- /dev/null +++ b/cc/perf/pulse @@ -0,0 +1,265 @@ +#!/usr/bin/perl +################################################################################ +# +# File: pulse,v +# Revision: 1.1.1.1 +# Description: Checks Clearcase's "pulse" by attempting to some rudimentary +# Clearcase operations and timing them. Timing data is logged +# for historical purposes. +# Author: Andrew@DeFaria.com +# Created: Thu Dec 29 12:07:59 PST 2005 +# Modified: 2007/05/17 07:45:48 +# Language: perl +# +# (c) Copyright 2005, Andrew@DeFaria.com, all rights reserved +# +################################################################################ +use strict; +use warnings; +use File::Spec; + +my $me; + +BEGIN { + # Set $lib_path + my $lib_path = $^O =~ /MSWin/ ? "\\\\brcm-irv\\dfs\\projects\\ccase\\SCM\\lib" + : "/projects/ccase/SCM/lib"; + + # Extract relative path and basename from script name. + $0 =~ /(.*)[\/\\](.*)/; + + my $abs_path = (!defined $1) ? "." : File::Spec->rel2abs ($1); + $me = (!defined $2) ? $0 : $2; + $me =~ s/\.pl$//; + + # Add the appropriate path to our modules to @INC array. + unshift @INC, "$lib_path"; + unshift @INC, $ENV {SITE_PERL_LIBPATH} if defined $ENV {SITE_PERL_LIBPATH}; + unshift @INC, "$abs_path"; +} # BEGIN + +use OSDep; +use Display; +use Logger; +use Clearcase; +use Clearcase::Vob; +use Clearcase::View; +use Clearcase::Element; +use TimeUtils; + +my $version = "1.0"; + +# We need a view context. We'll create this view if necessary. +my $tag = "default"; + +# Which vob are we going to use +my $vobtag = "perftest"; + +# We need an element to check out and in +my $element_name = "testelement"; + +# How long is too long? +my $too_long = 60; # seconds; + +# How many times will we perform the checkout/in? +my $iterations = 10; + +# Some options that we use +my %identical = ( + "-identical", "", + "-nc", "", +); +my %nc = ( + "-nc", "", +); +my %rm = ( + "-rm", "", +); +my %force = ( + "-force", "", +); + +my $log; +my $view; +my $step_start_time; + +# Path to logs directory +my $logdir = "$scm_base$/logs"; + +error "Logdir $logdir does not exist - $!", 1 if !-d $logdir; + +my $cc = Clearcase->new; + +sub Usage { + my $msg = shift; + + display "ERROR: $msg\n" if defined $msg; + + display "Usage:\t$me (v$version) [-u] [-v] [-d] [-view ] [-vob ] +\t[-element ] [-t ] [-i ] + +Where: + + -u: Display usage + -v: Turn on verbose mode + -d: Turn on debug mode + -view: View tag to create/use (Default: $tag) + -vob: Vob tag to use (Default $vobtag) + -element: Vob relative path to element to checkout/in (Default: $element_name) + -t : Threshold of what is \"too long\" (Default $too_long seconds) + -i : Number of iterations (default $iterations) +"; + exit 1; +} # Usage + +sub Setup { + $log = Logger->new ( + name => $cc->sitename . "." . $me, + path => $logdir, + timestamped => "true", + append => "true" + ); + + verbose "Startup"; + + # Set up view + verbose "Setting u p view $tag"; + $view = Clearcase::View->new (tag => $tag); + $view->create; + + $view->set; + + # Set up vob + verbose "Setting up vob $vobtag"; + my $vob = Clearcase::Vob->new (tag => $vobtag); + + $log->err ("Vob $Clearcase::VOBTAG_PREFIX$vobtag doesn't exist", 1) if !$vob; + + $vob->mount; + + chdir "$Clearcase::VIEWTAG_PREFIX/$tag$Clearcase::VOBTAG_PREFIX$vobtag" + or $log->err ("Unable to chdir to vob root", 1); + + # Create an element + verbose "Creating element $element_name"; + my $size = 5; + my $meg = 1024 * 1024; + my $buf = 1024; + my $bytes_to_write = $size * $meg; + my $bytes_written = 0; + + # Can we make a file in tmp? + open ELEMENT, ">$element_name" + or error "Unable to create element $element_name - $!", 1; + + while ($bytes_written < $bytes_to_write) { + my $data = "." x $buf; + + print ELEMENT $data; + $bytes_written += $buf; + } # while + + close ELEMENT; + + verbose "Setup complete"; + + return Clearcase::Element->create ($element_name); +} # Setup + +sub Shutdown { + my $element = shift; + + verbose "Shutdown"; + verbose "Unchecking out $element->{name}"; + $element->uncheckout (%rm); + verbose "Removing $element->{name}"; + $element->remove (%force); + + my $parent = Clearcase::Element->new (name => "."); + + verbose "Canceling checkout of parent directory"; + $parent->uncheckout; +} # Shutdown + +sub Checkout_in { + my $element = shift; + + verbose "Checking in $element->{name}"; + $element->checkin (%identical); + + verbose "Checking out $element->{name}"; + $element->checkout (%nc); +} # Checkout_in + +while ($ARGV [0]) { + if ($ARGV [0] eq "-v") { + Display::set_verbose; + } elsif ($ARGV [0] eq "-d") { + set_debug; + } elsif ($ARGV [0] eq "-u") { + Usage; + } elsif ($ARGV [0] eq "-view") { + shift @ARGV; + if ($ARGV [0]) { + $view = $ARGV [0]; + } else { + Usage "Need to specify view after -view"; + } # if + } elsif ($ARGV [0] eq "-vob") { + shift @ARGV; + if ($ARGV [0]) { + $too_long = $ARGV [0]; + } else { + Usage "Need to specify vob after -vob"; + } # if + } elsif ($ARGV [0] eq "-element") { + shift @ARGV; + if ($ARGV [0]) { + $too_long = $ARGV [0]; + } else { + Usage "Need to specify vob relative path to element after -element"; + } # if + } elsif ($ARGV [0] eq "-t") { + shift @ARGV; + if ($ARGV [0]) { + $too_long = $ARGV [0]; + } else { + Usage "Need to specify number of seconds after -t"; + } # if + } elsif ($ARGV [0] eq "-i") { + shift @ARGV; + if ($ARGV [0]) { + $too_long = $ARGV [0]; + } else { + Usage "Need to specify number of iterations after -i"; + } # if + } else { + Usage "Invalid argument: $ARGV [0]"; + } # if + + shift (@ARGV); +} # while + +my $element = Setup; + +$log->err ("Unable to setup environment", 1) if !$element; + +$step_start_time = time; + +$log->msg ("Performing $iterations checkout/ins in view $tag vob $vobtag of element " . $element->name); +for (my $i = 0; $i < $iterations; $i++) { + verbose "Iteration #" . ($i + 1); + Checkout_in $element; +} # for + +my $end_time = time; + +display_duration $step_start_time, $log; + +if (($end_time - $step_start_time) > $too_long) { + my $msg = "Taking too long to perform $iterations checkout/ins\nShould take less than $too_long seconds"; + $log->err ($msg); + error $msg; +} # if + +Shutdown $element; diff --git a/cc/rmnusers b/cc/rmnusers new file mode 100644 index 0000000..793ae05 --- /dev/null +++ b/cc/rmnusers @@ -0,0 +1,148 @@ +#!/usr/bin/perl +################################################################################ +# +# File: $RCSfile: rmnusers,v $ +# Revision: $Revision: 1.3 $ +# Description: This script will remove a user to the nusers list for a lock +# Author: Andrew@DeFaria.com +# Created: Mon Feb 13 10:35:34 PST 2006 +# Modified: $Date: 2011/08/31 21:57:06 $ +# Language: Perl +# +# (c) Copyright 2006, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +use strict; +use warnings; + +use FindBin; +use lib "$FindBin::Bin/../lib"; + +use Getopt::Long; + +use OSDep; +use Display; +use Utils; + +my $me = $FindBin::Script; + +# Pick up from the environment if the user specifies pvob +my $pvob = $ENV{pvob}; + +my @pvob_related_objects = ( + "activity", + "stream", +); + +sub Usage { + my $msg = shift; + + display "Usage: $me: []"; + + if (defined $msg) { + error "$msg", 1; + } # if + + exit 0; +} # Usage + +sub RemoveNuser { + my $object = shift; + my $username = shift; + my @users = @_; + + my $cmd = "cleartool lock "; + + $cmd .= "-replace "; + + if (scalar @users gt 1) { + $cmd .= "-nusers "; + + my $first = $true; + + foreach (@users) { + next if $_ eq $username; + if ($first) { + $first = $false; + $cmd .= $_; + } else { + $cmd .= ",$_"; + } # if + } # foreach + } # if + + $cmd .= " $object"; + + my @output = `$cmd`; + + return $?; +} # RemoveNuser + +my $object = shift; +my @users = @ARGV; + +Usage "Must specify an object selector" if !defined $object; +Usage "Must specify an username" if scalar @users eq 0; + +my $object_type = $object; +my $full_object; + +$object_type =~ s/:.*//; + +if ($object =~ m/(.*)\@(.*)/) { + $object = $1; + $pvob = $2; +} # if + +Usage "Must specify pvob or set pvob in your environment" if !$pvob; + +if (InArray $object_type, @pvob_related_objects) { + # Need to add additional "\\" because Windows will eat them up when executing a ``; + if ($arch eq "windows" or $arch eq "cygwin") { + $full_object = "$object\@\\$pvob"; + } else { + $full_object = "$object\@$pvob"; + } # if +} else { + $full_object = $object; + + # Handle oddity with windows using \ for vob tags + if ($full_object =~ /vob:\\(.*)/) { + $full_object = "vob:\\\\" . $1; + } # if +} # if + +foreach (@users) { + my $cmd = "cleartool lslock $full_object 2>&1"; + my @output = `$cmd`; + my $status = $?; + + if ($status eq 0) { + if (scalar @output eq 0) { + display "$object is not locked"; + exit 0; + } # if + } else { + display "$object does not exist"; + exit 1; + } # if + + my @current_users; + + foreach (@output) { + if (/\"Locked except for users: (.*)\"/) { + @current_users = split " ", $1; + last; + } # if + } # foreach + + if (InArray $_, @current_users) { + if (RemoveNuser $full_object, $_, @current_users) { + error "Unable to remove $_ from nusers for $object"; + } else { + display "User $_ removed from the list of nusers for $object"; + } # if + } else { + error "User $_ is not on the nusers list for $object"; + } # if +} # foreach diff --git a/cc/stats b/cc/stats new file mode 100644 index 0000000..07ad322 --- /dev/null +++ b/cc/stats @@ -0,0 +1,142 @@ +#!/usr/bin/perl +################################################################################ +# +# File: $RCSfile: stats,v $ +# Revision: $Revision: 1.2 $ +# Description: Produce statistical reports about vobs and views at this site +# For each vob create a log file that contains the following data: +# +# date_time;site;VOB_name;size of database;size of source pool;size of devired object;size of cleartext;size of admin data;#elements;#branches;#versions +# +# Author: Andrew@DeFaria.com +# Created: Mon Jan 2 17:23:08 PST 2006 +# Modified: $Date: 2007/05/17 07:45:48 & +# Language: Perl +# +# (c) Copyright 2006-2010, Andrew@ClearSCM.com, all rights reserved. +# +################################################################################# +use strict; +use warnings; + +use File::Spec; +use FindBin; +use Getopt::Long; + +use lib "$FindBin::Bin/../lib"; + +use OSDep; +use Logger; +use Display; +use DateUtils; +use Clearcase; +use Clearcase::Vobs; +use Clearcase::Vob; +use Clearcase::Views; + +my $cc = Clearcase->new; +my $site = $cc->sitename; +my $logdir = '.'; +my $sitelog = "$site.site"; +my $voblog = "$site.vob"; + +sub Usage { + my $msg = shift; + + display "ERROR: $msg\n" if defined $msg; + + display "Usage: $FindBin::Script\t[-u] [-v] [-d] [-vobs] [-site] + +Where: + + -u|sage: Display usage + -ve|rbose: Turn on verbose mode + -d|ebug: Turn on debug mode + -vo|bs: Produce vob stats + -s|ite: Produce site stats + -l|ogpath: Directory to put logs (Default '.') + +Default is to report both the vobs and site statistics. +"; + exit 1; +} # Usage + +my $do_vobs = 0; +my $do_site = 0; + +GetOptions ( + 'usage' => sub { Usage }, + 'verbose' => sub { set_verbose }, + 'debug' => sub { set_debug }, + 'vobs', \$do_vobs, + 'site', \$do_site, + 'logdir=s', \$logdir, +) or Usage 'Invalid parameter'; + +unless ($do_vobs or $do_site) { + $do_vobs = $do_site = 1; +} # if + +my $datetime = YMDHM; +my $vobs = Clearcase::Vobs->new; +my $total_vobsize = 0; + +if ($do_vobs) { + verbose 'Processing vobs...'; + + my $log = Logger->new ( + path => $logdir, + name => $voblog, + append => 1, + ); + + foreach ($vobs->vobs) { + verbose "Processing vob: $Clearcase::vobtag_prefix$_"; + my $vob = Clearcase::Vob->new (tag => "$Clearcase::vobtag_prefix$_"); + + my $elements = $vob->elements; + my $branches = $vob->branches; + my $versions = $vob->versions; + + $log->msg ( + "$datetime;$site" . ';' . + $_ . ';' . + $vob->dbsize . ';' . + $vob->srcsize . ';' . + $vob->dosize . ';' . + $vob->ctsize . ';' . + $vob->admsize . ';' . + $vob->size . ';' . + $vob->elements . ';' . + $vob->branches . ';' . + $vob->versions + ); + + $total_vobsize += $vob->size; + } # foreach +} # if + +if ($do_site) { + verbose 'Processing site stats...'; + + my $log = Logger->new ( + path => $logdir, + name => $sitelog, + append => 1, + ); + + my $views = Clearcase::Views->new; + my $nbr_views = $views->views; + + $datetime = YMDHM; + + $log->msg ( + "$datetime;$site" . ';' . + $vobs->vobs . ';' . + $total_vobsize . ';' . + $views->dynamic . ';' . + $views->snapshot . ';' . + $views->ucm . ';' . + $views->web + ); +} # if diff --git a/cc/testcc.conf b/cc/testcc.conf new file mode 100644 index 0000000..a2d4c2b --- /dev/null +++ b/cc/testcc.conf @@ -0,0 +1,21 @@ +################################################################################ +# +# File: testcc.conf +# Revision: 2.0 +# Description: Parameters for testcc +# +# Author: Andrew@DeFaria.com +# Created: Thu Sep 6 14:05:55 MST 2007 +# Modified: +# Language: Conf +# +# (c) Copyright 2007, Andrew@DeFaria.com, all rights reserved. +# +################################################################################# +vobhost: gdvob1 +vobpath: /net/$vobhost +vobstore: $vobpath/local/gdvob1a + +viewhost: view1 +viewpath: /net/$viewhost +viewstore: $viewpath/local/view1a \ No newline at end of file diff --git a/cc/testcc.pl b/cc/testcc.pl new file mode 100644 index 0000000..87e6e3c --- /dev/null +++ b/cc/testcc.pl @@ -0,0 +1,562 @@ +#!/bin/bin/perl + +=pod + +=head1 NAME $RCSfile: testcc.pl,v $ + +Test Clearcase + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.6 $ + +=item Created: + +Tue Apr 10 13:14:15 CDT 2007 + +=item Modified: + +$Date: 2011/01/09 01:01:32 $ + +=back + +=head1 SYNOPSIS + + Usage testcc.pl: [-u|sage] [-ve|rbose] [-d|ebug] + [-c|onfig ] [-vi|ewstore ] + [-vo|bstore ] + + Where: + -u|sage: Displays usage + + -ve|rbose: Be verbose + -d|ebug: Output debug messages + + -c|onfig : Config file (Default: testcc.conf) + -vi|ewstore: Path to view storage area + -vo|bstore: Path to vob storage area + +=head1 DESCRIPTION + +Clearcase smoke tests. Perform simple Clearcase operations to validate that +Clearcase minimally works + +=cut + +use strict; +use warnings; + +use FindBin; +use Getopt::Long; +use Cwd; +use Term::ANSIColor qw(:constants); + +my $libs; + +BEGIN { + $libs = $ENV{SITE_PERLLIB} ? $ENV{SITE_PERLLIB} : "$FindBin::Bin/../lib"; + + die "Unable to find libraries\n" + unless -d $libs; +} # BEGIN + +use lib $libs; + +use Clearcase; +use Clearcase::Element; +use Clearcase::View; +use Clearcase::Views; +use Clearcase::Vob; +use Clearcase::Vobs; +use DateUtils; +use Display; +use GetConfig; +use Logger; +use OSDep; +use Utils; + +# Globals +my $VERSION = '2.0'; + +my ($vbs, $vws, %default_opts, %opts); + +my $log = Logger->new; +my $view = $Clearcase::VIEWTAG_PREFIX; +my $view_tag = $FindBin::Script; +my $vob = $ENV{TMP} ? $ENV{TMP} : "/tmp"; # Private vob - mount to /tmp! +my $vob_tag = $view_tag; + +my ($test_view, $test_vob); + +# LogOpts: Log the %opts has to the log file so we can tell the options used for +# this run. +sub LogOpts () { + $log->msg ( + "$FindBin::Script v$VERSION run at " + . YMDHM + . " with the following options:" + ); + + foreach (sort keys %opts) { + if (ref $opts{$_} eq "ARRAY") { + my $name = $_; + $log->msg ("$name:\t$_") foreach (@{$opts{$_}}); + } else { + $log->msg ("$_:\t$opts{$_}"); + } # if + } # foreach + + return; +} # LogOpts + +sub CreateVob () { + $log->msg ("Creating vob $vob/$vob_tag"); + + $test_vob = Clearcase::Vob->new ("$vob/$vob_tag"); + + my ($status, @output) = $test_vob->create ($opts{vobhost}, $vbs); + + $log->log ($_) foreach (@output); + + if ($status != 0) { + if ($output[0] =~ /already exists/) { + $log->warn ("Vob " . $test_vob->tag . " already exists"); + return 0; + } # if + } # if + + return $status; +} # CreateVob + +sub MountVob () { + $log->msg ("Mounting vob " . $test_vob->tag); + + # Create mount directory + my ($status, @output) = Execute "mkdir -p " . $test_vob->tag . " 2>&1"; + + $log->log ($_) foreach (@output); + + ($status, @output) = $test_vob->mount; + + $log->log ($_) foreach (@output); + + return $status; +} # MountVob + +sub DestroyVob () { + my ($status, @output); + + ($status, @output) = $Clearcase::CC->execute ("cd"); + + $log->msg ("Unmounting vob " . $test_vob->tag); + + ($status, @output) = $test_vob->umount; + + $log->msg ("Removing vob " . $test_vob->tag); + + ($status, @output) = $test_vob->remove; + + $log->log ($_) foreach (@output); + + ($status, @output) = Execute "rmdir " . $test_vob->tag; + + $log->log ($_) + foreach (@output); + + return $status; +} # DestroyVob + +sub CreateView () { + $log->msg ("Creating view $view_tag"); + + $test_view = Clearcase::View->new ($view_tag); + + my ($status, @output) = $test_view->create ($opts{viewhost}, $vws); + + $log->log ($_) foreach (@output); + + if ($status != 0) { + if ($output[0] =~ /already exists/) { + $log->warn ("View " . $test_view->tag . " already exists"); + return 0; + } # if + } # if + + return $status; +} # CreateView + +sub SetView () { + $log->msg ("Setting view $test_view->tag"); + + my ($status, @output) = $test_view->set; + + $log->log ($_) foreach (@output); + + return $status; +} # SetView + +sub DestroyView () { + $log->msg ("Removing view " . $test_view->tag); + + my ($status, @output) = $Clearcase::CC->execute ("cd"); + + $log->log ($_) foreach (@output); + + chdir $ENV{HOME} + or $log->err ("Unable to chdir $ENV{HOME}", 1); + + ($status, @output) = $test_view->remove; + + $log->log ($_) foreach (@output); + + return $status; +} # DestroyView + +sub CreateViewPrivateFiles (@) { + my (@elements) = @_; + + $log->msg ("Creating test files"); + + foreach (@elements) { + my $file; + + $log->msg ("Creating $_"); + + open $file, ">>", $_ + or $log->err ("Unable to open $_ for writing - $!", 1); + + print $file "This is file $_\n"; + + close $file; + } # foreach + + return; +} # CreateViewPrivateFiles + +sub CheckOut ($) { + my ($element) = @_; + + my ($status, @output); + + if (ref $element eq "ARRAY") { + foreach (@{$element}) { + $log->msg ("Checking out $_"); + + my $newElement = Clearcase::Element->new ($_); + + ($status, @output) = $newElement->checkout; + + $log->log ($_) foreach (@output); + $log->err ("Unable to check out $_", $status) if $status; + } # foreach + } else { + $log->msg ("Checking out $element"); + + my $newElement = Clearcase::Element->new ($element); + + ($status, @output) = $newElement->checkout; + + $log->log ($_) foreach (@output); + $log->err ("Unable to check out $element", $status) if $status; + } # if + + return; +} # CheckOut + +sub CheckIn ($) { + my ($element) = @_; + + my ($status, @output); + + if (ref $element eq "ARRAY") { + foreach (@{$element}) { + $log->msg ("Checking in $_"); + + my $newElement = Clearcase::Element->new ($_); + + ($status, @output) = $newElement->checkin; + + $log->log ($_) foreach (@output); + $log->err ("Unable to check in $_", $status) if $status; + } # foreach + } else { + $log->msg ("Checking in $element"); + + my $newElement = Clearcase::Element->new ($element); + + ($status, @output) = $newElement->checkin; + + $log->log ($_) foreach (@output); + $log->err ("Unable to check in $element", $status) if $status; + } # if + + return; +} # CheckIn + +sub ComparingFiles (@) { + my (@elements) = @_; + + foreach (@elements) { + my @lines = ReadFile $_; + + $log->err ("Element $_ should contain only two lines", 2) if scalar @lines != 2; + } # foreach + + return; +} # ComparingFiles + +sub MakeElements (@) { + my (@elements) = @_; + + foreach (@elements) { + $log->msg ("Mkelem $_"); + + my $newElement = Clearcase::Element->new ($_); + + my ($status, @output) = $newElement->mkelem; + + $log->log ($_) foreach (@output); + $log->err ("Unable to make $_ an element", $status) if $status; + } # foreach + + return; +} # MakeElements + +sub RunTests () { + # Simple tests: + # + # . Create a few elements + # . Check them in + # . Check them out + # . Modify them + # . Check them in + # + # Assumptions: + # + # . $vob_tag is already created + # . $view_tag is already created + # . View is set and we are in the vob + # . There are no vob elements for @elements + my @elements = ( + "cctest.h", + "ccsetup.c", + "cctest.c", + "Makefile", + ); + + $log->msg ("Removing test files"); + + unlink $_ foreach (@elements); + + $log->msg ("Creating view private files"); + + CreateViewPrivateFiles $log, @elements; + + $log->msg ("Making elements"); + + CheckOut '.'; + MakeElements @elements; + CheckIn \@elements; + CheckIn '.'; + + $log->msg ("Checking out files"); + + CheckOut \@elements; + + $log->msg ("Modifying files"); + + CreateViewPrivateFiles @elements; + + $log->msg ("Checking in files"); + + CheckIn \@elements; + + $log->msg ("Comparing files"); + + ComparingFiles @elements; + + $log->msg ("$FindBin::Script: End Tests"); + + return 0; +} # RunTests + +sub Cleanup () { + my $status = 0; + + $log->msg ("Cleaning up"); + + if ($test_view && $test_view->exists) { + $status += DestroyView; + } # if + + if ($test_vob && $test_vob->exists) { + $status += DestroyVob; + } # if + + return $status; +} # Cleanup + +sub SetupTest () { + $log->msg ("Setup test environment"); + + my $status += CreateVob; + + return $status if $status != 0; + + $status += MountVob; + + return $status if $status != 0; + + $status += CreateView; + + return $status if $status != 0; + + $status += $test_view->start; + + my $dir = $Clearcase::VIEWTAG_PREFIX . $test_view->tag . $test_vob->tag; + + chdir $dir + or $log->err ("Unable to chdir to $dir", $status++); + + my @output; + + ($status, @output) = $Clearcase::CC->execute ("cd $dir"); + + if ($status != 0) { + $log->log ($_) foreach (@output); + $log->err ("Unable to chdir to $dir", $status); + } # if + + return $status; +} # SetupTest + +my $conf_file = "$FindBin::Script.conf"; + +GetOptions ( + \%opts, + "v|verbose" => sub { set_verbose }, + "u|usage" => sub { Usage }, + "c|onfig=s", + "n|etpath=s", + "viewstore=s", + "vobstore=s", +) or Usage; + +# Read the config file +if (-f $conf_file) { + %default_opts = GetConfig $conf_file; +} else { + $log->err ("Unable to find config file $conf_file", 1); +} # if + +# Overlay default opts if not specified +foreach (keys %default_opts) { + $opts{$_} = $default_opts{$_} if !$opts{$_}; +} # foreach + +$vws = "$opts{viewstore}/$view_tag.vws"; +$vbs = "$opts{vobstore}/$vob_tag.vbs"; + +$log->msg ("START: $FindBin::Script (v$VERSION)"); + +LogOpts; + +my $status = SetupTest; + +if ($status == 0) { + $status += RunTests; +} else { + $log->err ("Tests not run. Failure occured in SetupTest - check logfile"); +} # if + +$status += Cleanup; + +if ($status != 0) { + $log->err ("$FindBin::Script failed"); +} else { + $log->msg ("$FindBin::Script passed"); +} # if + +$log->msg ("END: $FindBin::Script (v$VERSION)"); + +exit $status; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Clearcase + Clearcase::Element + Clearcase::View + Clearcase::Views + Clearcase::Vob + Clearcase::Vobs + DateUtils + Display + GetConfig + Logger + OSDep + Utils + +=end man + +=begin html + +
    +Clearcase
    +Element
    +View
    +Views
    +Vob
    +Vobs
    +DateUtils
    +Display
    +GetConfig
    +Logger
    +OSDep
    +Utils
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut diff --git a/cc/triggers/AddExecute.pl b/cc/triggers/AddExecute.pl new file mode 100644 index 0000000..5037675 --- /dev/null +++ b/cc/triggers/AddExecute.pl @@ -0,0 +1,24 @@ +#!/usr/bin/perl +################################################################################ +# +# File: AddExecute.pl +# Description: This trigger script simply adds execute permission to an element +# when it is created in Clearcase +# Trigger Type: All element +# Operation: Postop mkelem +# Author: Andrew@DeFaria.com +# Created: Fri Mar 12 10:17:44 PST 2004 +# Language: Perl +# Modifications: +# +# (c) Copyright 2004, Andrew@DeFaria.com, all rights reserved +# +################################################################################ +use strict; +use warnings; + +my $element = $ENV{CLEARCASE_PN}; + +system "cleartool protect -chmod +x \"$element\""; + +exit 0; diff --git a/cc/triggers/CheckComment.pl b/cc/triggers/CheckComment.pl new file mode 100644 index 0000000..a2999db --- /dev/null +++ b/cc/triggers/CheckComment.pl @@ -0,0 +1,29 @@ +#!/usr/bin/perl +################################################################################# +# File: CheckComment.pl,v +# Revision: 1.1.1.1 +# Description: This trigger checks to insure that the user enters a comment +# during checkin time. +# Trigger Type: All element +# Operation: Preop checkin +# Author: Andrew@DeFaria.com +# Created: May 24, 2004 +# Modified: 2007/05/17 07:45:48 +# Language: Perl +# +# (c) Copyright 2006, Andrew@DeFaria.com, all rights reserved +# +################################################################################ +use strict; +use warnings; + +# Get comment +my $comment = $ENV {"CLEARCASE_COMMENT"}; + +# Check if it's empty +if ($comment eq "") { + # Alert user + `clearprompt proceed -type error -prompt "You must specify a comment" -mask proceed`; + # Exit with non-zero status so checkin aborts + exit 1 +} # if diff --git a/cc/triggers/EvilTwin.pl b/cc/triggers/EvilTwin.pl new file mode 100644 index 0000000..e55dcfd --- /dev/null +++ b/cc/triggers/EvilTwin.pl @@ -0,0 +1,151 @@ +#!/usr/bin/perl -w +################################################################################ +# +# File: EvilTwin.pl,v +# Revision: 1.1.1.1 +# Description: This trigger checks for evil twins. And evil twin can occur when +# a user checks in an element which matches an element name on +# some other branch of the directory that is invisible in the +# current view. +# Trigger Type: All element +# Operation: Preop lnname +# Author: Andrew@DeFaria.com +# Created: May 24, 2004 +# Modified: 2007/05/17 07:45:48 +# Language: Perl +# +# (c) Copyright 2004, Andrew@DeFaria.com, all rights reserved +# +################################################################################ +use strict; +use File::Basename; + +# Ensure that the view-private file will get named back on rejection. +BEGIN { + END { + rename "$ENV{CLEARCASE_PN}.mkelem", "$ENV{CLEARCASE_PN}" + if $? && ! -e "ENV{CLEARCASE_PN}" && -e "$ENV{CLEARCASE_PN}.mkelem"; + } # END +} # BEGIN + +# Check to see if we are running on Windows +my $windows = ($^O =~ /MSWin/) ? "yes" : "no"; + +# Delimeters and null are different on the different OSes +my $dir_delim = $windows eq "yes" ? "\\" : "/"; +my $dir_delim_e = $windows eq "yes" ? "\\\\" : "\/"; +my $null = $windows eq "yes" ? "NUL" : "/dev/null"; + +# This is called only if an evil twin is detected. It simply writes +# out information about the evil twin to a log file. Eventually we +# will turn this off. +sub Log { + my $msg = shift; + + my $time = localtime; + my $user = $ENV {CLEARCASE_USER}; + my $logpath = $windows eq "yes" ? "\\\\p01ccvob.usa.hp.com\\vobstore\\triggers\\" : + "/net/p01ccvob.usa.hp.com/vobstore/triggers/"; + my $logfile = $logpath . "EvilTwin.log"; + open LOG, ">>$logfile" or die "Unable to open $logfile"; + + print LOG "$time: $user: $msg\n"; + + close LOG; +} # Log + +# Get Clearcase Environment variables needed +my $pname = $ENV {CLEARCASE_PN}; + +#Log "pname = $pname"; + +# Get element and parent directory name +my ($element_name, $parent) = fileparse ($pname); +#Log "element_name = $element_name"; +#Log "parent = $parent"; + +# At this point parent will either end with "\.\" on Windows ("/./" on +# Unix) or a single "\" Windows ("/" on Unix). Windows has a strange +# situation when the trailing part of parent is = "\". It ends up +# quoting the double quote and causes the execution of the lsvtree to +# fail. We must detect this and add on an additional "\". +if ($parent =~ m/$dir_delim_e\.$dir_delim_e$/) { + $parent =~ s/$dir_delim_e\.$dir_delim_e$/$dir_delim_e/; +} elsif ($parent =~ m/\\$/) { + $parent .= $dir_delim; +} # if + +#Log "parent = $parent"; + +# Look for evil twins +my $status; +my $possible_dup; + +# Get list of all branches for the parent directory. We will search +# these for possible evil twins. +my @parent_dir_branches = `cleartool lsvtree -all -s "$parent"`; + +# Fixup parent by removing trailing delimiters +$parent =~ s/\\\\$/\\/; + +foreach (@parent_dir_branches) { + chomp; + chop if /\r/; +# Log $_; +} # foreach + +my $evil_twin = 1; + +#Log "Checking parent directories"; +foreach (@parent_dir_branches) { + chomp; + + $possible_dup = $_ . $dir_delim . $element_name; +# Log "possible_dup = $possible_dup"; + + # View extended pathnames don't work from snapshot views. While + # using cleartool ls is slower it also has the benefit of respecting + # the case sensitivity of MVFS. +# Log "Doing ct ls"; + $status = (system "cleartool ls -s $possible_dup > $null 2>&1") >> 8; + + if ($status eq 0) { + # We found something related to $element_name. Now check to see if + # this something is a branch name +# Log "Found something"; + my $type = `cleartool desc -fmt %m $possible_dup 2>&1`; + chomp ($type); + + if ("$type" ne "branch") { + # If it's not a branch then we've found an evil twin - set $status + # to 1 indicating this and break out. +# Log "Evil twin found!"; + $evil_twin = 0; + last; + } # if +# } else { +# Log "status = $status"; + } # if +} # foreach + +# Exit 0 if the evil twin is not found +exit 0 if $evil_twin; + +# Possible duplicate element is found on invisible branch(es). +my $prompt; +my $nl = $windows eq "yes" ? "\\n" : "\n"; +$parent = "." if $parent eq ""; +$prompt = "The element $element_name already exists for the directory \'$parent\'$nl"; +$prompt .= "in another branch as ($possible_dup).$nl$nl"; +$prompt .= "You could either merge the parent directories or create a Clearcase hardline to$nl"; +$prompt .= "that element.$nl$nl"; +$prompt .= "For more information about this condition see:$nl$nl"; +$prompt .= "http://ilmwiki.usa.hp.com/wiki/ClearCase_Evil_Twins$nl$nl"; +$prompt .= "If you feel you really need to perform this action please submit a request$nl"; +$prompt .= "through SourceForge at:$nl$nl"; +$prompt .= "http://plesf01srv.usa.hp.com/sf/tracker/do/listArtifacts/projects.eng_tools_support/tracker.clearcase"; + +Log "Evil twin detected in $parent. Twin: $possible_dup"; +system ("clearprompt yes_no -mask abort -default abort -newline -prompt \"$prompt\""); + +exit 1; diff --git a/cc/triggers/Notify.pl b/cc/triggers/Notify.pl new file mode 100644 index 0000000..b2c9206 --- /dev/null +++ b/cc/triggers/Notify.pl @@ -0,0 +1,155 @@ +#!/usr/bin/perl +################################################################################ +# +# File: Notify.pl +# Description: This script is a generalized notify trigger. It takes one +# parameter, a message file. The format of this file is similar +# to an email message. Environment variables will be substituted. +# +# This trigger is typically added, perhaps multiple times with +# different message files, then attached to elements in the vob +# as needed. Make the trigger with: +# +# cleartool mktrtype -element -postop checkin \ +# -c "" \ +# -exec " /Notify.pl \ +# " +# +# Assumptions: Clearprompt is in the users PATH +# Author: Andrew@DeFaria.com +# Created: Tue Mar 12 15:42:55 2002 +# Language: Perl +# Modifications: +# +# (c) Copyright 2004, Andrew@DeFaria.com, all rights reserved +# +################################################################################ +use strict; + +use File::Spec; +use Net::SMTP; + +my $mailhost = "smtphost"; + +# This will be set in the BEGIN block but by putting them here the become +# available for the whole script. +my ( + $abs_path, + $lib_path, + $me, + $msgfiles_path, + $triggers_path +); + +BEGIN { + # Extract relative path and basename from script name. + $0 =~ /(.*)[\/\\](.*)/; + + $abs_path = (!defined $1) ? "." : File::Spec->rel2abs ($1); + $me = (!defined $2) ? $0 : $2; + + # Setup paths + $lib_path = "$abs_path/../lib"; + $triggers_path = "$abs_path/../triggers"; + $msgfiles_path = "$abs_path/../msgs"; + + # Add the appropriate path to our modules to @INC array. + unshift (@INC, "$lib_path"); +} # BEGIN + +use TriggerUtils; + +# This routine will replace references to environment variables. If an +# environment variable is not defined then the string is +# substituted. +sub ReplaceText { + my $line = shift; + + while ($line =~ /\$(\w+)/) { + my $var = $1; + if ($ENV{$var} eq "") { + $line =~ s/\$$var/\/; + } else { + my $value = $ENV{$var}; + $value =~ s/\\/\//g; + $line =~ s/\$$var/$value/; + } # if + } # while + + return $line; +} # ReplaceText + +sub error { + my $message = shift; + + clearlogmsg $message; + + exit 1; +} # error + +# First open the message file. If we can't then there's a problem, die! +my $msgfile = "$msgfiles_path/$ARGV[0]"; +open MSG, $msgfile + or error "Unable to open message file:\n\n$msgfile\n\n($!)"; + +# Suck in file +my @lines = ; + +# Connect to mail server +my $smtp = Net::SMTP->new ($mailhost); + +error "Unable to open connection to mail host: $mailhost" if $smtp == undef; + +# Compose message +my $data_sent = "F"; +my $from_seen = "F"; +my $to_seen = "F"; +my ($line, $from, $to, @addresses); + +foreach $line (@lines) { + next if $line =~ /^\#/; + next if $line =~ /--/; + + $line = ReplaceText $line; + + if ($line =~ /^From:\s+/) { + $_ = $line; + $from = $line; + s/^From:\s+//; + $smtp->mail ($_); + $from_seen = "T"; + next; + } # if + + if ($line =~ /^To:\s+/) { + $_ = $line; + $to = $line; + s/^To:\s+//; + @addresses = split (/,|;| /); + $to_seen = "T"; + foreach (@addresses) { + next if ($_ eq ""); + $smtp->to ($_); + } # foreach + next; + } # if + + if ($data_sent eq "F") { + $smtp->data (); + $smtp->datasend ($from); + $smtp->datasend ($to); + $data_sent = "T"; + } # if + + if ($from_seen eq "T" && $to_seen eq "T" && $data_sent eq "T") { + $smtp->datasend ($line); + } else { + clearlogmsg "Message file ($ARGV[0]) missing From and/or To!"; + exit 1; + } # if +} # foreach + +$smtp->dataend (); +$smtp->quit; + +exit 0; diff --git a/cc/triggers/NotifyCheckin.msg b/cc/triggers/NotifyCheckin.msg new file mode 100644 index 0000000..1c3caf9 --- /dev/null +++ b/cc/triggers/NotifyCheckin.msg @@ -0,0 +1,14 @@ +From: Vobadm +To: <***ADD EMAIL ADDRESSES HERE***> +Subject: $CLEARCASE_OP_KIND notification: $CLEARCASE_PN +-- +This is a notification that a $CLEARCASE_OP_KIND has occurred: + +Element: $CLEARCASE_PN +Branch: $CLEARCASE_BRTYPE +Operation: $CLEARCASE_OP_KIND +User: $CLEARCASE_USER +View: $CLEARCASE_VIEW_TAG +Comments: + +$CLEARCASE_COMMENT diff --git a/cc/triggers/NotifyTrigger.pl b/cc/triggers/NotifyTrigger.pl new file mode 100644 index 0000000..01f2a1e --- /dev/null +++ b/cc/triggers/NotifyTrigger.pl @@ -0,0 +1,159 @@ +#!/usr/bin/perl +################################################################################ +# +# File: NotifyTrigger.pl,v +# Revision: 1.1.1.1 +# Description: This script is a generalized notify trigger. It takes one +# parameter, a message file. The format of this file is similar +# to an email message. Environment variables will be substituted. +# +# This trigger is typically added, perhaps multiple times with +# different message files, then attached to elements in the vob +# as needed. Make the trigger with: +# +# cleartool mktrtype -element -postop checkin \ +# -c "" \ +# -exec " /NotifyTrigger.pl \ +# " +# +# Assumptions: Clearprompt is in the users PATH +# Author: Andrew@DeFaria.com +# Created: Tue Mar 12 15:42:55 2002 +# Modified: 2007/05/17 07:45:48 +# Language: Perl +# +# (c) Copyright 2004, Andrew@DeFaria.com, all rights reserved +# +################################################################################ +use strict; +use warnings; + +use File::Spec; +use Net::SMTP; + +# Define mailhost here (or set in the environment) +my $mailhost = defined $ENV {MAILHOST} ? $ENV {MAILHOST} : undef; + +# This will be set in the BEGIN block but by putting them here the become +# available for the whole script. +my ( + $abs_path, + $lib_path, + $log_path, + $me, + $triggers_path +); + +BEGIN { + # Extract relative path and basename from script name. + $0 =~ /(.*)[\/\\](.*)/; + + $abs_path = (!defined $1) ? "." : File::Spec->rel2abs ($1); + $me = (!defined $2) ? $0 : $2; + + # Setup paths + $lib_path = "$abs_path/../lib"; + $log_path = "$abs_path/../log"; + $triggers_path = "$abs_path/../triggers"; + + # Add the appropriate path to our modules to @INC array. + unshift (@INC, "$lib_path"); +} # BEGIN + +use TriggerUtils; + +# This routine will replace references to environment variables. If an +# environment variable is not defined then the string is +# substituted. +sub ReplaceText { + my $line = shift; + + while ($line =~ /\$(\w+)/) { + my $var = $1; + if ($ENV{$var} eq "") { + $line =~ s/\$$var/\/; + } else { + my $value = $ENV{$var}; + $value =~ s/\\/\//g; + $line =~ s/\$$var/$value/; + } # if + } # while + + return $line; +} # ReplaceText + +sub error { + my $message = shift; + + clearlogmsg $message; + + exit 1; +} # error + +# First open the message file. If we can't then there's a problem, die! +my $msgfile = "$triggers_path/$ARGV[0]"; +open MSG, $msgfile + or error "Unable to open message file:\n\n$msgfile\n\n($!)"; + +# Suck in file +my @lines = ; + +# Connect to mail server +error "Mailhost is not defined!" if !defined $mailhost; +my $smtp = Net::SMTP->new ($mailhost); + +error "Unable to open connection to mail host: $mailhost" if $smtp == undef; + +# Compose message +my $data_sent = "F"; +my $from_seen = "F"; +my $to_seen = "F"; +my ($line, $from, $to, @addresses); + +foreach $line (@lines) { + next if $line =~ /^\#/; + next if $line =~ /--/; + + $line = ReplaceText $line; + + if ($line =~ /^From:\s+/) { + $_ = $line; + $from = $line; + s/^From:\s+//; + $smtp->mail ($_); + $from_seen = "T"; + next; + } # if + + if ($line =~ /^To:\s+/) { + $_ = $line; + $to = $line; + s/^To:\s+//; + @addresses = split (/,|;| /); + $to_seen = "T"; + foreach (@addresses) { + next if ($_ eq ""); + $smtp->to ($_); + } # foreach + next; + } # if + + if ($data_sent eq "F") { + $smtp->data (); + $smtp->datasend ($from); + $smtp->datasend ($to); + $data_sent = "T"; + } # if + + if ($from_seen eq "T" && $to_seen eq "T" && $data_sent eq "T") { + $smtp->datasend ($line); + } else { + clearlogmsg "Message file ($ARGV[0]) missing From and/or To!"; + exit 1; + } # if +} # foreach + +$smtp->dataend (); +$smtp->quit; + +exit 0; diff --git a/cc/triggers/Protect.pl b/cc/triggers/Protect.pl new file mode 100644 index 0000000..94db93f --- /dev/null +++ b/cc/triggers/Protect.pl @@ -0,0 +1,71 @@ +################################################################################ +# +# File: Protect.pl,v +# Revision: 1.1.1.1 +# Description: When new elements are created in the VOB change the elements +# ownership to the owner of the VOB and change element permissions +# to appropiate for element_type. +# +# NOTE: If a particular file_type is not implemented in +# your VOB then comment it out. Unspecified file_types +# will have origional permissions, but will have +# ownership changed. +# Assumptions: Clearprompt is in the users PATH +# Author: Andrew@DeFaria.com +# Created: April 20, 2003 +# Modified: 2007/05/17 07:45:48 +# Language: Perl +# +################################################################################ +use strict; +use warnings; + +# What do we set the owner and group to? +my $owner = "vobadm"; +my $group = "ccadmin"; + +# Get CLEARCASE_PN +my $pname = $ENV {CLEARCASE_PN}; + +# Let's get the real owner from the real output of describe +my @output = `cleartool describe vob:$pname`; + +foreach (@output) { + if (/owner\s*\w*\\(.*)/) { + $owner = $1; + chop $owner if $owner =~ /\r/; # any carriage return + last; + } # if +} # foreach + +# Let's get the real group from the real output of describe +foreach (@output) { + if (/group\s*\w*\\(.*)/) { + $group = $1; + chop $group if $group =~ /\r/; # any carriage return + last; + } # if +} # foreach + +# Get what element type we are dealing with +my $eltype = $ENV {CLEARCASE_ELTYPE}; + +if (($eltype eq "directory") || + ($eltype =~ /.*script/) || + ($eltype =~ /.*program/)) { + # All element types that are known to be 775 should be placed here. + `cleartool protect -chmod 775 -chown $owner -chgrp $group $pname`; +} elsif (($eltype eq "makefile") || + ($eltype =~ /.*include/) || + ($eltype =~ /.*source/)) { + # All element types that are known to be 664 should be placed here. + `cleartool protect -chmod 664 -chown $owner -chgrp $group $pname`; +} elsif ($eltype eq "report") { + # All element types that are known to be 644 should be placed here. + `cleartool protect -chmod 644 -chown $owner -chgrp $group $pname`; +} else { + # All other element types should just have the ownership changed. + `cleartool protect -chown $owner -chgrp $group $pname`; +} # if + +exit 0 diff --git a/cc/triggers/RemoveEmptyBranch.pl b/cc/triggers/RemoveEmptyBranch.pl new file mode 100644 index 0000000..3515abe --- /dev/null +++ b/cc/triggers/RemoveEmptyBranch.pl @@ -0,0 +1,116 @@ +#!/usr/bin/perl +################################################################################ +# +# File: RemoveEmptyBranch.pl,v +# Revision: 1.1.1.1 +# Description: This trigger script is remove empty branches. If a branch has +# no elements (except the 0 element of course) after an uncheckout +# remove it and the branch. +# Trigger Type: All element +# Operation: Postop rmbranch, uncheckout +# Author: Andrew@DeFaria.com +# Created: Fri Mar 12 10:17:44 PST 2004 +# Modified: 2007/05/17 07:45:48 +# Language: Perl +# +# (c) Copyright 2004, ClearSCM, Inc., all rights reserved +# +################################################################################ +use strict; +use warnings; + +use Carp; + +my $debug = $ENV{TRIGGER_DEBUG}; +my $windows = ($^O || $ENV{OS}) =~ /MSWin32|Windows_NT/i ? "yes" : "no"; +my $SEPARATOR = $windows eq "yes" ? "\\" : "/"; +my $null = $windows eq "yes" ? "NUL" : "/dev/null"; +my $trigger_file; + +sub InitDebug { + my $tmpdir = $ENV{TMP}; + my $trigger_debug_log = "$tmpdir/trigger_debug.log"; + + open my $debugLog, '>>', $trigger_debug_log + or croak "Unable to open $trigger_debug_log"; + + return $debugLog +} # InitDebug + +sub debug { + my ($msg) = @_; + + return if !defined $debug; + + $trigger_file = InitDebug if !defined $trigger_file; + + print $trigger_file "$msg\n"; + + return; +} # debug + +# The following environment variables are set by Clearcase when this +# trigger is called +my $xname = $ENV{CLEARCASE_XPN}; +my $xn_sfx = $ENV{CLEARCASE_XN_SFX}; +my $opkind = $ENV{CLEARCASE_OP_KIND}; +my $brtype = $ENV{CLEARCASE_BRTYPE}; +my $view_type = $ENV{CLEARCASE_VIEW_KIND}; + +debug "RM_EMPTY_BRANCH Trigger:"; +debug "CLEARCASE_XPN = $xname"; +debug "CLEARCASE_XN_SFX = $xn_sfx"; +debug "CLEARCASE_OP_KIND = $opkind"; +debug "CLEARCASE_BRTYPE = $brtype"; +debug "CLEARCASE_VIEW_KIND = $view_type"; + +$xname =~ s/\\/\//g if $windows eq "yes"; + +# For uncheckout, if the remaining version is not 0 then we are done - +# the most common case... +exit 0 if ($opkind eq "uncheckout" && $xname !~ m/\/0$/); + +my $branch = $xname; + +if ($opkind eq "uncheckout") { + # Remove the last component + $branch =~ s/\/[^\/]*$//; +} # if + +# Don't try to remove the /main branch +exit 0 if $branch =~ m/$xn_sfx\/main$/; + +# Check if there are other versions, branches, labels or checked out versions +# on this branch. If so don't do anything. +if ($view_type eq "dynamic") { + opendir (DIR, $branch); + my @entries = readdir (DIR); + closedir (DIR); + + # In an empty branch there are four things: ".", "..", "0" an d"LATEST". + # If there are more then it isn't an empty branch + exit 0 if (scalar (@entries) != 4); +} else { + # Snapshot views. + my ($pname, $brpath) = split ($xn_sfx, $branch); + + # The rmbranch will not reload the element. This shows as "special + # selection, deleted version" in snapshot views This cleans that up. + if ($opkind eq "rmbranch") { + system "cleartool update -log $null \"$pname\"" if ($opkind eq "rmbranch"); + exit 0; # Nothing else to do here... + } # if + + my @vtree = `cleartool lsvtree -branch $brpath \"$pname\"`; + my $latest; + chomp ($latest = pop (@vtree)); + $latest =~ tr/\\/\// if $windows eq "yes"; + + exit 0 unless $latest =~ m/$brpath\/0$/; +} # if + +# Remove the branch! +debug "Removing empty branch $branch"; +system "cleartool rmbranch -force -nc \"$branch\""; + +exit 0; diff --git a/clearadm/.-_hist b/clearadm/.-_hist new file mode 100644 index 0000000..e69de29 diff --git a/clearadm/.clearexec_hist b/clearadm/.clearexec_hist new file mode 100644 index 0000000..370b1d2 --- /dev/null +++ b/clearadm/.clearexec_hist @@ -0,0 +1,7 @@ +ls +quit +ls +exit +lsa +ls +quit diff --git a/clearadm/.cvsignore b/clearadm/.cvsignore new file mode 100644 index 0000000..7add31b --- /dev/null +++ b/clearadm/.cvsignore @@ -0,0 +1,4 @@ +.project +.cvsignore +.clearexec_hist +.perldb.hist diff --git a/clearadm/.perldb.hist b/clearadm/.perldb.hist new file mode 100644 index 0000000..cd00922 --- /dev/null +++ b/clearadm/.perldb.hist @@ -0,0 +1,100 @@ + +my $offset = 0; +my $savout = select($DB::OUT); +dumpvar_epic::dump_lexical_vars($offset); +select($savout); +}; + +;{ +do 'dumpvar_epic.pm' unless defined &dumpvar_epic::dump_lexical_vars; + +my $offset = 0; +my $varexpr = <<'EOT'; +$h->{'%parms'} +EOT +my $subref = \&dumpvar_epic::dump_hash_expr; +my $savout = select($DB::OUT); +my $savbuf = $|; +$| = 0; +$subref->($offset, $varexpr); +$| = $savbuf; +print ""; +select($savout); +}; + +;{ +do 'dumpvar_epic.pm' unless defined &dumpvar_epic::dump_lexical_vars; + +my $offset = 0; +my $varexpr = <<'EOT'; +$h->{'@months'} +EOT +my $subref = \&dumpvar_epic::dump_array_expr; +my $savout = select($DB::OUT); +my $savbuf = $|; +$| = 0; +$subref->($offset, $varexpr); +$| = $savbuf; +print ""; +select($savout); +}; + +;{ +do 'dumpvar_epic.pm' unless defined &dumpvar_epic::dump_lexical_vars; + +my $offset = 0; +my $varexpr = <<'EOT'; +$h->{'@validKeys'} +EOT +my $subref = \&dumpvar_epic::dump_array_expr; +my $savout = select($DB::OUT); +my $savbuf = $|; +$| = 0; +$subref->($offset, $varexpr); +$| = $savbuf; +print ""; +select($savout); +}; + +b 166 +x $system +x $status +x @output +f Clearexec +b 345 +x $? +x $! +c snapshotsystem +/snapshot +c 82 +x $status +x @output +x $output[0] +x $1 +x \%load +x @output +v 94 +c 94 +x $cmd +x $restart +x $status +x @output +$cmd .= "| grep -v 'grep -i \'$name\'" +x $cmd +($status, @output) = Execute $cmd +x $cmd +$cmd = 'ps -ef | grep -i \'mediamallserver\' | grep -v \'grep -i \"mediamallserver\"\'' +x $cmd +$cmd = 'ps -ef | grep -i \'mediamallserver\' | grep -v \'grep -i \"mediamallserver\"\'' +($status, @output) = Execute $cmd +x $status +x @output +$cmd = 'ps -eWf | grep -i \'mediamallserver\' | grep -v \'grep -i \"mediamallserver\"\'' +($status, @output) = Execute $cmd +x $cmd +ls +$status = 1 +x $status +x $restart +x $status +x @output diff --git a/clearadm/.project b/clearadm/.project new file mode 100644 index 0000000..4b42c19 --- /dev/null +++ b/clearadm/.project @@ -0,0 +1,17 @@ + + + clearadm + + + + + + org.epic.perleditor.perlbuilder + + + + + + org.epic.perleditor.perlnature + + diff --git a/clearadm/README b/clearadm/README new file mode 100644 index 0000000..038bc07 --- /dev/null +++ b/clearadm/README @@ -0,0 +1,108 @@ +CLEARADM: + +Clearadm is a set of scripts and a web app designed to discover and monitor +systems in your infrastructure with an eye towards servers as well as Clearcase. + +DEPENDENCIES: + +In order for graphics to work you need to install GD for Perl (libgd-graph-perl +and I also installed libgd-graph3d-perl) as well as GD::Graph from cpan. + +While Clearadm is designed to monitor Unix, Linux and Windows machines, it +requires the installation of Cygwin and Cygwin's Perl package to run on Windows. +Clearagent also requires Cygwin and Cygwin's Perl. + +INSTALLATION + +SERVER Selection + +Clearadm is a distributed system. Various components can be set up to run on +different servers. For example, the database server machine need not be the same +machine that the web component of Clearadm runs. Additionally all systems report +status through clearagent.pl by running a small agent daemon. Finally +cleartasks.pl performs scheduled tasks and it can run on a separate server. +Generally you only use one or two servers but you have the option to distribute +the load. + +CLEARADM Database + +Clearadm uses a MySQL database to store information about your infrastructure. +Unpack the clearadm.tar.gz file (normally rooted in /opt/clearscm) and set up +the MySQL database by executing mysql then sourcing: + + clearadm.sql -- Creates the database + users.sql -- Sets up the database users + load.sql -- Loads up some predefined tasks and schedules + +CLEARAGENT Setup + +Clearagent: This is a little agent program that receives requests from other +hosts and executes them on the host running clearagent. As such you want to have +clearagent.pl running all the time. Normally it backgrounds itself and it is +multithreaded so that it can handle multiple requests efficiently. + +Clearagent components have been separated into the clearagent.tar.gz file. This +allows you to install only the clearagent portion on your servers. + +Under Unix/Linux hosts there is a Sys/V init.d script under etc/init.d. +Additionally for security concerns clearagent is run under a plain user named +clearagent. In order to set this up on a Unix/Linux host do the following as +root: + + $ export CLEARADM=/opt/clearscm/clearadm + $ useradd -Mr clearagent + $ chmod 777 $CLEARADM/var $CLEARADM/var/run $CLEARADM/log + $ ln -s $CLEARADM/etc/init.d/clearagent /etc/init.d/clearagent + $ /etc/init.d/clearagent start + +You can test to see if clearagent is running properly by executing: + + $ $CLEARADM/clearexec.pl -host localhost + clearexec:hostname + + clearexec:exit + $ + + For Windows machines, assuming you have Cygwin installed, create a service for + clearagent: + + $ cygrunsrv -I clearagent -p C:/Cygwin/bin/perl \ + > -a '/opt/clearscm/clearadm/clearagent.pl -nodaemon' + $ net start clearagent + + Note that -nodaemon is used but the Windows service will handle the + backgrounding of clearagent. Testing clearagent is the same as for Unix/Linux. + +CLEARADM Discovery + +You can use the discover.pl script to discover machines on the network and have +them added to the clearadm database. However, until you set up clearagent on +those machines, monitoring of these systems will be limited. + +CLEARADM Web + +Clearadm Web component should be running on one server in your subnet. It is +designed to work with Apache 2 Web servers. Symlink +$CLEARADM/etc/conf.d/clearadm -> /etc/apache2/conf.d and restart Apache + +CLEARADM Tasks + +In additional to setting up the database, web server and installing agents on +the various machines you wish to monitor, you should run cleartasks.pl to +perform the scheduled tasks on a periodic bases. Clearadm comes with some +predefined tasks. You can add/develop your own. Simply run cleartasks.pl and it +will background itself, performing tasks when necessary (Should we make an +init.d script for this? My worry is that if that gets configured on more than +one machine...) + +CLEARADM Clearcase Reporting (Need to fill this out better) + +Clearadm can show you where your views and vobs reside as well as provide useful +functionality like a view aging report, etc. In order to collect such +information Clearadm needs to periodically collect information about those +Clearcase objects. Predefined scheduled tasks and alerts are set up to do this +but you must do some configuration yourself to tell Clearadm where your +Clearcase objects reside. For example, you need to tell it where your registry +server(s) are, what regions you wish to report on as well as set other +configuration settings as to how long to age views and vobs, etc. + diff --git a/clearadm/add.png b/clearadm/add.png new file mode 100644 index 0000000000000000000000000000000000000000..a442019d53717d29881822621fd001c5cc32385c GIT binary patch literal 566 zcmV-60?GY}P)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L0Cu7P0Cu7Q{X~h`00007bV*G`2ipM? z4ks(O2JxZ*00Fj1L_t(I%axPAYLihIhkqx@ms$mz79%PSiko-?6~)E7=oPr=(00(l z!LgfvRu?ZLAO&y0#i7JOa25P&lJA`N|8WR7NFwIIyFKqSygVmi&Aa*G;_2;McOIph z5*6yszD%Fbj_~$xZm>?UGal_7KYWn}Xz+dUt^Z_tJX*!A6;zE%P5=mPZfj3beG@?# zP^>_MvCsvQt>*qA2+a5#5`xSNB;QcrVm9YFBp97^a6(xrxbc4bbTr=G3-wyPB0S(4 zcHz?Y-G$5{F33{0u3Q}+Hm{q5>gYvOR11Vvqz99G@$B(YYV%JN&B&Hfw1B)oO+ZG- zl561%Hcp}p7J-<6m@#noRIFPr{+R;Of}#YnQbjnCF<1y}3^9R)OBY8#m}#)x|w7{{R3007*qoM6N<$ Ef4u*Y00001b5ch_0Itp) z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2ipM? z4j&O_m6YoM00MMLL_t(I%bn0mNK|0}#qs|)_s-~C=hfsjoosx}I64iaLO~LNwFpyt zAVg4`+T==*qNr%mqJwx$#=&@lO{ycC->#;FCxw~5p z!IpnU9>VJJ&295)^r_a59_hm6OZtAL{jwWMmfCy%1oA~JvR1CR5R=oTb5Cxw zkWQnNA{vbX5DtfNwIH@w*i$@&)4Y~zAbq$rBe0;;~rY+>PCZErfN~K7r z)7Z96p-^BknWWgYll=Tw(ksG84?rV;0dNz_Rwd#OYQ#*~X==tfcm%^V5kjDp!f_lj znG67q<3MAEuXlzCxJ5H3X&906h5@j*wB*dXRCy;~rdc?B5gCjigg^+v=;$bYeSNgI zw`1Ejj^m(A6Hyl6Q#MPvWhH}7;gz@!Ya3fqFX9VR-eOVWG`As3{74O27$`i<=Dy*# z0u)?H!IiiQ7TZWJ9j;VJsc?T(NzI&6NLS%1 mg_IgsDtuNMuB)gNqWU{weC{c*78N}J0000 + +=item Revision + +$Revision: 1.9 $ + +=item Created: + +Mon Oct 25 11:10:47 PDT 2008 + +=item Modified: + +$Date: 2011/01/29 23:33:04 $ + +=back + +=head1 SYNOPSIS + + Usage alertlog.cgi: [-u|sage] [-ve|rbose] [-d|ebug] + + Where: + -u|sage: Displays usage + -ve|rbose: Be verbose + -d|ebug: Output debug messages + +=head2 DESCRIPTION + +This script displays the alert log + +=cut + +use strict; +use warnings; + +use FindBin; +use Getopt::Long; +use CGI qw (:standard :cgi-lib *table start_Tr end_Tr); +use CGI::Carp 'fatalsToBrowser'; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use Clearadm; +use ClearadmWeb; +use Display; +use Utils; + +my $VERSION = '$Revision: 1.9 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +my $clearadm; + +my %opts = Vars; + +$opts{start} ||= 0; +$opts{page} ||= 10; + +# Main +GetOptions ( + usage => sub { Usage }, + verbose => sub { set_verbose }, + debug => sub { set_debug }, +) or Usage 'Invalid parameter'; + +undef $opts{alert} + if $opts{alert} and $opts{alert} eq 'All'; +undef $opts{system} + if $opts{system} and $opts{system} eq 'All'; +undef $opts{notification} + if $opts{notification} and $opts{notification} eq 'All'; + +# Announce ourselves +verbose "$FindBin::Script v$VERSION"; + +$clearadm = Clearadm->new; + +my $title = 'Alert Log'; + +heading $title; + +display h1 {class => 'center'}, $title; + +displayAlertlog (%opts); + +footing; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Clearadm + ClearadmWeb + Display + Utils + +=end man + +=begin html + +
    +Clearadm
    +ClearadmWeb
    +Display
    +Utils
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut \ No newline at end of file diff --git a/clearadm/alerts.cgi b/clearadm/alerts.cgi new file mode 100755 index 0000000..ea5e842 --- /dev/null +++ b/clearadm/alerts.cgi @@ -0,0 +1,146 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: alerts.cgi,v $ + +Display alerts + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.3 $ + +=item Created: + +Mon Oct 25 11:10:47 PDT 2008 + +=item Modified: + +$Date: 2011/01/20 01:19:24 $ + +=back + +=head1 SYNOPSIS + + Usage alerts.cgi: [-u|sage] [-ve|rbose] [-d|ebug] + + Where: + -u|sage: Displays usage + -ve|rbose: Be verbose + -d|ebug: Output debug messages + +=head2 DESCRIPTION + +This script displays alerts + +=cut + +use strict; +use warnings; + +use FindBin; +use Getopt::Long; +use CGI qw (:standard :cgi-lib *table start_Tr end_Tr); +use CGI::Carp 'fatalsToBrowser'; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use Clearadm; +use ClearadmWeb; +use Display; +use Utils; + +my $VERSION = '$Revision: 1.3 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +my $clearadm; + +my %opts = Vars; + +# Main +GetOptions ( + usage => sub { Usage }, + verbose => sub { set_verbose }, + debug => sub { set_debug }, +) or Usage 'Invalid parameter'; + +# Announce ourselves +verbose "$FindBin::Script v$VERSION"; + +$clearadm = Clearadm->new; + +my $title = $opts{alert} + ? "Alerts matching $opts{alert}" + : 'Alerts'; + +heading $title; + +display h1 {class => 'center'}, $title; + +displayAlert $opts{alert}; + +footing; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Clearadm + ClearadmWeb + Display + Utils + +=end man + +=begin html + +
    +Clearadm
    +ClearadmWeb
    +Display
    +Utils
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut \ No newline at end of file diff --git a/clearadm/banner.jpg b/clearadm/banner.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b46366262691aeae76bf9b9c4ef09c3bd430ac03 GIT binary patch literal 34080 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 UU+a&L{Ie!`H@l{GrjkGZ*#;Xo6951J literal 0 HcmV?d00001 diff --git a/clearadm/clearadm.css b/clearadm/clearadm.css new file mode 100644 index 0000000..1fffdfe --- /dev/null +++ b/clearadm/clearadm.css @@ -0,0 +1,303 @@ +/******************************************************************************* +* +* File: $RCSfile: clearadm.css,v $ +* Revision: $Revision: 1.20 $ +* Description: Cascading Style Sheet definitions for Clearadm +* Author: Andrew@ClearSCM.com +* Created: Sun Jan 16 11:13:19 EST 2011 +* Modified: $Date: 2011/01/28 21:28:40 $ +* Language: Cascading Style Sheet +* +* (c) Copyright 2010, Andrew@DeFaria.com, all rights reserved. +* +*******************************************************************************/ +body { + background-color:white; + font-family:veranda, times; + font-size:14px; + margin:0px; + background-image:url('/clearadm/banner.jpg'); + background-repeat:repeat-x; + margin-bottom:100px; +} + +/* Element classes */ +h1.title { + color:white; + text-align:center; + height:70px; +} + +font.unknown { + color:#ddd; +} + +font.dim { + color:#999; +} + +p, h2 { + margin:10px; +} + +/* Stylings for page div */ +.page a:link { + color:blue; + text-decoration:none; +} + +.page a:visited { + color:blue; +} + +.page a:hover { + color:red; +} + +.dataCenteredAlert a:link { + color:red; + font-size:18px; +} +.dataCenteredAlert a:visited { + color:red; + font-size:18px; +} + +/* Classes for control boxes */ +.filesystemControls { + background-color:#fef5d8; + text-align:right; + padding:0px; + border-right:1px solid black; + border-left:1px solid black; + border-bottom:1px solid black; +} + +.controls { + background-color:#fef5d8; + text-align:right; + padding:0px; + border:1px solid black; +} + +.filter { + background-color:#fef5d8; +} + +/* Set the border to have a 3D effect for the chart image */ +img.chart { + border-top:2px solid #eee; + border-left:2px solid #eee; + border-right:2px solid black; + border-bottom:2px solid black; +} + +/* Stylings for table elements */ +table { + background-color:black; + padding:0px; + margin-left:auto; + margin-right:auto; +} + +tr.oldview { + background-color:#eeeeee; +} + +tr.view { + background-color:white; +} + +td.center { + text-align:center; +} + +td.right { + text-align:right; +} + +th, td { + padding:4px; +} + +th.label { + text-align:right; + background-color:#fef5d8; +} + +th.labelCentered { + text-align:center; + background-color:#fef5d8; +} + +td.data { + background-color:white; +} + +td.dataTop { + background-color:white; + vertical-align:top; +} + +td.dataAlert { + background-color:white; + color:red; + font-weight:bold; +} + +td.dataAlertTop { + background-color:white; + color:red; + font-weight:bold; + vertical-align:top; +} + +td.dataRightAlert { + background-color:white; + color:red; + font-weight:bold; + text-align:right; +} + +td.dataCenteredAlert { + background-color:white; + color:red; + font-weight:bold; + text-align:center; +} + +td.dataRightAlertTop { + background-color:white; + color:red; + font-weight:bold; + text-align:right; + vertical-align:top; +} + +td.dataCentered { + text-align:center; + background-color:white; +} + +td.dataCenteredTop { + text-align:center; + background-color:white; + vertical-align:top; +} + +td.dataAlertCentered { + text-align:center; + background-color:white; + color:red; + font-weight:bold; +} + +td.dataAlertCenteredTop { + text-align:center; + background-color:white; + color:red; + font-weight:bold; + vertical-align:top; +} + +td.dataRight { + text-align:right; + background-color:white; +} + +td.dataRightTop { + text-align:right; + background-color:white; + vertical-align:top; +} + +td.dataAlertRight { + text-align:right; + background-color:white; + color:red; + font-weight:bold; +} + +td.dataAlertRightTop { + text-align:right; + background-color:white; + color:red; + font-weight:bold; + vertical-align:top; +} + +/* Classes for viewager */ +.dynamic { + color:red; +} + +.snapshot { + color:green; +} + +.web { + color:orange; +} + +.caption { + background-color:white; +} + +/* General classes */ +.label { + font-weight:bold; +} + +.heading { + background-color:#fef5d8; +} + +.center { + text-align:center; +} + +.error { + color:red; + font-weight:bold; +} + +.inputfield { + background:#ece9d8; + font-family:arial, veranda, times; + font-size:14px; +} + +.inputfieldRight { + background:#ece9d8; + font-family:arial, veranda, times; + font-size:14px; + text-align:right; +} + +.dropdown { + background-color:#ece9d8; + font-family:arial, veranda, times; + font-size:14px; +} + +/* Copyright block */ +.copyright { + border-bottom:1px dotted #ccc; + border-top:1px dotted #ccc; + color:#999; + font-family:verdana, arial, sans-serif; + font-size:10px; + margin-top:5px; + text-align:center; + width:auto; +} + +.copyright a:link, a:visited { + color:#666; + font-weight:bold; + text-decoration:none; +} + +.copyright a:hover { + color:#333; +} \ No newline at end of file diff --git a/clearadm/clearadm.js b/clearadm/clearadm.js new file mode 100644 index 0000000..6561dd0 --- /dev/null +++ b/clearadm/clearadm.js @@ -0,0 +1,231 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// File: $RCSfile: clearadm.js,v $ +// Revision: $Revision: 1.8 $ +// Description: Javascript routines for Clearadm +// Author: Andrew@ClearSCM.com +// Created: Wed Dec 29 12:36:47 EST 2010 +// Modified: $Date: 2011/01/21 01:00:09 $ +// Language: JavaScript +// +// (c) Copyright 2010, ClearSCM, Inc., all rights reserved. +// +//////////////////////////////////////////////////////////////////////////////// +function getXMLHTTP () { + try { + return new XMLHttpRequest (); + } catch (e) { + try { + return new ActiveXObject ('Microsoft.XMLHTTP'); + } catch (e) { + try { + return new ActiveXObject ('Msxml2.XMLHTTP'); + } catch (e) { + return false; + } // try + } // try + } // try +} // getXMLHTTP + +function updateTimestamp (system, elementID, filesystem) { + var request = getXMLHTTP (); + var script = 'getTimestamp.cgi?system=' + system + '&elementID=' + elementID; + + var scaling = document.getElementById ('scalingFactor').value; + + if (scaling) { + script += '&scaling=' + scaling; + } // if + + if (filesystem) { + script += '&filesystem=' + filesystem; + } // if + + if (request) { + request.onreadystatechange = function () { + if (request.readyState == 4) { + if (request.status == 200) { + document.getElementById (elementID).innerHTML + = request.responseText; + } // if + } // if + } // function + + request.open ('get', script, true); + request.send (null); + } else { + alert ('Unable to create XMLHTTP Request object'); + } // if +} // updateTimestamp + +function updateSystem (system) { + updateTimestamp (system, 'startTimestamp'); + updateTimestamp (system, 'endTimestamp'); +} // updateSystem + +function updateSystemLink (system) { + document.getElementById ('systemLink').innerHTML + = 'System'; + + updateTimestamp (system, 'startTimestamp'); + updateTimestamp (system, 'endTimestamp'); +} // updateSystemLink + +function updateFilesystems (system) { + var request = getXMLHTTP (); + + if (request) { + request.onreadystatechange = function () { + if (request.readyState == 4) { + if (request.status == 200) { + document.getElementById ('filesystems').innerHTML + = request.responseText; + } // if + } // if + } // function + + request.open ('GET', 'getFilesystems.cgi?system=' + system, true); + request.send (null); + } else { + alert ('Unable to create XMLHTTP Request object'); + } // if +} // updateFilesystems + +function updateFilesystem (system, filesystem) { + updateTimestamp (system, 'startTimestamp', filesystem); + updateTimestamp (system, 'endTimestamp', filesystem); +} // updateFilesystem + +function trimSpaces (str) { + return str.replace (/^\s+|\s+$/g, ''); +} // trimSpaces + +function validEmailAddress (email) { + var emailPattern = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/; + + return emailPattern.test (email); +} // validEmailAddress + +function validateAlert (alertrec) { + with (alertrec) { + if (name.value == '') { + alert ("You must specify the alert's name"); + name.focus (); + return false; + } // if + + if (who.value) { + if (!validEmailAddress (alertrec.who.value)) { + alert ('That email address is invalid!\n' + + 'Must be @\n' + + 'For example: Andrew@ClearSCM.com'); + return false; + } // if + } // if + } // with +} // validateAlert + +function validateNotification (notification) { + with (notification) { + if (name.value == '') { + alert ("You must specify the notification's name"); + name.focus (); + return false; + } // if + + if (cond.value == '') { + alert ('You must specify a condition'); + cond.focus (); + return false; + } // if + } // with +} // validateNotification + +function validateSchedule (schedule) { + with (schedule) { + if (name.value == '') { + alert ("You must specify the schedule's name"); + name.focus; + return false; + } // if + + if (isNaN (nbr.value)) { + alert ('Frequency is not a number'); + nbr.focus; + return false; + } else if (nbr.value < 1 || nbr.value > 999) { + alert ('Frequency must be a positive number between 1-999'); + nbr.focus; + return false; + } // if + } // with +} // validateSchedule + +function validateTask (task) { + with (task) { + if (name.value == '') { + alert ("You must specify the task's name"); + name.focus; + return false; + } // if + } // with +} // validateTask + +function validateSystem (system) { + with (system) { + name.value = trimSpaces (name.value); + + if (name.value == '') { + alert ("You must specify the system's name"); + name.focus (); + return false; + } // if + + admin.value = trimSpaces (admin.value); + + if (admin.value == '') { + alert ("You must specify the admin's name"); + admin.focus (); + return false; + } // if + + if (isNaN (port.value)) { + alert ('Port is not a number'); + port.focus; + return false; + } else if (port.value < 1 || port.value > 65535) { + alert ('Port must be a positive number between 1-65535'); + port.focus; + return false; + } // if + + if (isNaN (loadavgThreshold.value)) { + alert ('Loadavg Threshold is not a number'); + loadavgThreshold.focus; + return false; + } else if (loadavgThreshold.value < 0 || loadavgThreshold.value > 99.99) { + alert ('Loadavg Threshold must be a positive number between 0 - 99.99'); + loadavgThreshold.focus; + return false; + } // if + + email.value = trimSpaces (email.value); + + if (email.value == '') { + alert ("You must specify the admin's email"); + email.focus (); + return false; + } else { + if (!validEmailAddress (email.value)) { + alert ('That email address is invalid!\n' + + 'Must be @\n' + + 'For example: Andrew@ClearSCM.com'); + return false; + } // if + } // if + } // with +} // validateSystem + +function AreYouSure (message) { + return window.confirm (message); +} // AreYouSure \ No newline at end of file diff --git a/clearadm/clearadmscrub.pl b/clearadm/clearadmscrub.pl new file mode 100755 index 0000000..f9fb105 --- /dev/null +++ b/clearadm/clearadmscrub.pl @@ -0,0 +1,181 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: clearadmscrub.pl,v $ + +Scrub Clearadm records + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.9 $ + +=item Created: + +Sun Jan 2 19:40:28 EST 2011 + +=item Modified: + +$Date: 2012/11/09 06:45:36 $ + +=back + +=head1 SYNOPSIS + + Usage clearadmscrub.pl: [-u|sage] [-ve|rbose] [-deb|ug] + + Where: + -u|sage: Displays usage + + -ve|rbose: Be verbose + -deb|ug: Output debug messages + +=head1 DESCRIPTION + +This script will scrub all old records in the Clearadm database + +=cut + +use strict; +use warnings; + +use FindBin; +use Getopt::Long; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use Clearadm; +use DateUtils; +use Display; +use TimeUtils; +use Utils; + +my $VERSION = '$Revision: 1.9 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +my $clearadm = Clearadm->new; + +my ($host, $fs); + +# Main +GetOptions ( + 'usage' => sub { Usage }, + 'verbose' => sub { set_verbose }, + 'debug' => sub { set_debug }, +) or Usage "Invalid parameter"; + +Usage 'Extraneous options: ' . join ' ', @ARGV + if @ARGV; + +# Announce ourselves +verbose "$FindBin::Script V$VERSION"; + +my ($err, $msg); + +foreach my $system ($clearadm->FindSystem ($host)) { + ($err, $msg) = $clearadm->TrimLoadavg ($$system{name}); + + if ($msg eq 'Records deleted' or $msg eq '') { + verbose "Scrub loadavg $$system{name}: $err $msg:"; + } else { + error "#$err: $msg"; + } # if + + foreach my $filesystem ($clearadm->FindFilesystem ($$system{name}, $fs)) { + ($err, $msg) = $clearadm->TrimFS ($$system{name}, $$filesystem{filesystem}); + + if ($msg eq 'Records deleted' or $msg eq '') { + verbose "Scrub filesystem $$system{name}:$$filesystem{filesystem}: $err $msg"; + } else { + error "#$err: $msg"; + } # if + } # foreach +} # foreach + +# TODO: These should be configurable +my $sixMonthsAgo = SubtractDays (Today2SQLDatetime, 180); + +my %runlog = ( + task => 'Scrub', + started => Today2SQLDatetime, +); + +# Scrub old alertlogs +($runlog{status}, $runlog{message}) = + $clearadm->DeleteAlertlog ("timestamp<='$sixMonthsAgo'"); + +verbose "$runlog{task} alertlog: $runlog{status} $runlog{message}"; + +$clearadm->AddRunlog (%runlog); + +$runlog{started} = Today2SQLDatetime; + +# Scrub old runlogs +($runlog{status}, $runlog{message}) = + $clearadm->DeleteRunlog ("started<='$sixMonthsAgo'"); + +verbose "$runlog{task} runlog: $runlog{status} $runlog{message}"; + +$clearadm->AddRunlog (%runlog); + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Clearadm + DateUtils + Display + TimeUtils + Utils + +=end man + +=begin html + +
    +Clearadm
    +DateUtils
    +Display
    +TimeUtils
    +Utils
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut \ No newline at end of file diff --git a/clearadm/clearagent.pl b/clearadm/clearagent.pl new file mode 100755 index 0000000..a6ad922 --- /dev/null +++ b/clearadm/clearagent.pl @@ -0,0 +1,157 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: clearagent.pl,v $ + +Daemon process to run commands on current host in response to requests from +other hosts. + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.11 $ + +=item Created: + +Mon Dec 13 09:13:27 EST 2010 + +=item Modified: + +$Date: 2011/02/02 18:43:53 $ + +=back + +=head1 SYNOPSIS + + Usage clearagent.pl: [-u|sage] [-ve|rbose] [-deb|ug] + + Where: + -u|sage: Displays usage + + -ve|rbose: Be verbose + -de|bug: Output debug messages + + -da|emon: Run in daemon mode (Default) + -m|ultithreaded: Multithread requests (Default) + -p|idfile: File to be created with the pid written to it (Default: + clearagent.pid). Note: pidfile is only written if -daemon + is specified. + +=head1 DESCRIPTION + +This script normally runs as a daemon and accepts requests from other hosts to +execute commands locally and send back the results. + +=cut + +use strict; +use warnings; + +use Getopt::Long; +use FindBin; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use Clearadm; +use Clearexec; +use Display; +use Utils; + +my $VERSION = '$Revision: 1.11 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +my $pidfile = "$Clearexec::CLEAROPTS{CLEAREXEC_RUNDIR}/$FindBin::Script.pid"; + +# Augment PATH with $Clearadm::CLEAROPTS{CLEARADM_BASE} +$ENV{PATH} .= ":$Clearadm::CLEAROPTS{CLEARADM_BASE}"; + +my $clearexec; + +# Main +my $multithreaded = $Clearexec::CLEAROPTS{CLEAREXEC_MULTITHREADED}; +my $daemon = 1; + +GetOptions ( + 'usage' => sub { Usage }, + 'verbose' => sub { set_verbose }, + 'debug' => sub { set_debug }, + 'daemon!' => \$daemon, + 'multithreaded!' => \$multithreaded, + 'pidfile=s' => \$pidfile, +) or Usage "Invalid parameter"; + +Usage 'Extraneous options: ' . join ' ', @ARGV + if @ARGV; + +$clearexec = Clearexec->new; + +$clearexec->setMultithreaded ($multithreaded); + +my $logfile = "$Clearexec::CLEAROPTS{CLEAREXEC_LOGDIR}/$FindBin::Script.log"; + +EnterDaemonMode $logfile, $logfile, $pidfile + if $daemon; + +display "$FindBin::Script V$VERSION started at " . localtime; + +$clearexec->startServer; + +verbose "Server running"; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Clearexec + Display + Utils + +=end man + +=begin html + +
    +Clearexec
    +Display
    +Utils
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut diff --git a/clearadm/clearexec.pl b/clearadm/clearexec.pl new file mode 100755 index 0000000..0c709ce --- /dev/null +++ b/clearadm/clearexec.pl @@ -0,0 +1,190 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: clearexec.pl,v $ + +Execute commands on the remote system + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.11 $ + +=item Created: + +Mon Dec 13 09:13:27 EST 2010 + +=item Modified: + +$Date: 2012/04/27 14:47:22 $ + +=back + +=head1 SYNOPSIS + + Usage clearexec.pl: [-u|sage] [-ve|rbose] [-deb|ug] + [-h|ost ] [-p|ort ] [] + + Where: + -u|sage: Displays usage + + -ve|rbose: Be verbose + -deb|ug: Output debug messages + + -h|ost : Host to contact (Default: localhost) + -p|ort : Port to connect to (Default: 25327) + Command to perform + +=head1 DESCRIPTION + +This script exercises the clearserver.pl daemon by executing a command on the +remote host:port that the clearserver.pl daemon is running on + +=cut + +use strict; +use warnings; + +use Getopt::Long; +use FindBin; +use Term::ANSIColor qw (color); + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use Clearexec; +use CmdLine; +use Display; +use Utils; + +my $VERSION = '$Revision: 1.11 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +my $me = $FindBin::Script; + $me =~ s/\.pl$//; + +local $0 = $me; + +my $host = $Clearexec::CLEAROPTS{CLEAREXEC_HOST}; +my $port = $Clearexec::CLEAROPTS{CLEAREXEC_PORT}; + +my $clearexec; + +sub CmdLoop () { + my ($line, $result); + + my $prompt = color ('BOLD YELLOW') . "$me->$host:" . color ('RESET'); + + $CmdLine::cmdline->set_prompt ($prompt); + + while (($line, $result) = $CmdLine::cmdline->get ()) { + last unless defined $line; + last if $line =~ /exit|quit/i; + + my ($status, @output) = $clearexec->execute ($line); + + last if $line =~ /stopserver/i; + + if ($status) { + error "Non zero status returned from $line ($status)\n" . join "\n", @output; + } else { + display join "\n", @output; + display "Status: $status" + if $status; + } # if + } # while + + return; +} # CmdLoop + +# Main +GetOptions ( + 'usage' => sub { Usage }, + 'verbose' => sub { set_verbose }, + 'debug' => sub { set_debug }, + 'host=s' => \$host, + 'port=s' => \$port, +) or Usage "Invalid parameter"; + +my $cmd = join ' ', @ARGV; + +verbose "$FindBin::Script V$VERSION"; + +$clearexec =Clearexec->new; + +my ($status, @output); + +$status = $clearexec->connectToServer ($host, $port); + +error "Unable to connect to $host:$port", 1 + unless $status; + +if ($cmd ne '') { + ($status, @output) = $clearexec->execute ($cmd); + + if ($status) { + error "Unable to execute $cmd (Status: $status)\n" . join ("\n", @output), 1; + } else { + display join "\n", @output; + display "Status: $status"; + } # if +} else { + CmdLoop; +} # if + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Clearexec + Display + Utils + +=end man + +=begin html + +
    +Clearexec
    +Display
    +Utils
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut diff --git a/clearadm/clearmenu.css b/clearadm/clearmenu.css new file mode 100644 index 0000000..f71bc75 --- /dev/null +++ b/clearadm/clearmenu.css @@ -0,0 +1,305 @@ +/*============================================================================== + +GRC multi-level script-free pure-CSS menuing system stylesheet. This code is +hereby placed into the public domain by its author Steve Gibson. It maybe freely +used for any purpose whatsoever. + +Computed Geometries: with a default 12px font, 1.0em == 12px and +1px == 0.08333em. +Thus, our 98px wide Freeware & Research buttons are 8.166666em wide. + +PUBLIC DOMAIN CONTRIBUTION NOTICE + +This work has been explicitly placed into the Public Domain for the benefit of +anyone who may find it useful for any purpose whatsoever. +==============================================================================*/ + +/*================= STYLES FOR THE GRC MASTHEAD & CONTROLS ===================*/ +/* for all browsers (non-IE) that obey min-width */ +.menuminwidth0 { + position:relative; + border:0; + margin:0; + padding:0; + width:100%; + height:55px; /* 36px masthead height + 18px button height + 1px lower border*/ + min-width:560px; +} + +/* suppress our whole menu when not an interactive mode (when printing, etc.) */ +@media print, projection { .menuminwidth0 { display:none; } } + +* html .menuminwidth1 { /* this allows IE5/6 to simulate min-width capability */ + position:relative; /* we can simulate a minimum width by creating a large */ + float:left; /* border in this first div, then placing our content */ + height: 1px; /* into a second nested div (see 2nd nested div next */ + border-left:560px solid #fff; /* CSS box-model borders are a fixed size */ +} + +/* used to simulate min-width capability for IE5/6 */ +* html .menuminwidth2 { + position:relative; + margin-left:-560px; + height: 1px; +} + +#masthead { + position:relative; /* position our child objects relative to this div */ + float:left; + vertical-align:top; /* protect from super-large user text sizing */ + border:0; + margin:0; + padding:0; + width:100%; /* grey-fill the entire width */ + height:36px; /* set the overall height above the menu-bar */ + background:#f3fefe; /* a very light shade of grey */ +} + +#mastheadlogo { + /*float:left;*/ + vertical-align:top; + border:0; + padding:0; + margin:6px 0 0 7px; + height:56px; +} + +/* GRC's focus label */ +#focus { + position:absolute; + border:0; + margin:0; + padding:0; + top:15px; + left:301px; + width:121px; + height:13px; +} + +/* search button */ +#search { + position:absolute; + border:0; + margin:0; + padding:0; + top:7px; + right:6px; + width:60px; + height:19px; +} + +/* search text field */ +#text { + position:absolute; + border:1px solid #404040; + margin:0; + padding:0 0 0 2px; /* move the left starting point a bit right */ + top:7px; + right:165px; + width:12em; /* search field width */ +/* height:1.215em; we'll define this at the bottom of our style sheet */ + font-size:14px !important; + background:#fefefe; +} + +/*========================= TOP OF THE MENU CASCADE ==========================*/ +.menu { + position:relative; /* establish a menu-relative positioning context */ + float:left; /* play nicely with others */ + margin:0; + padding:0; + border:0; + height:18px; /* the menu's overall height */ + width:100%; /* we always want our menu to fill the available space */ + background:#ffdb17; + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size:12px; /* this (and also below) sets the menu's font size */ + font-weight:bold; + border-bottom:1px solid black; /* give us a black border underneath */ +} + +.menu img { + vertical-align: top; /* prevent images from being pushed down by text */ +} + +.menu ul { + margin:0; + list-style-type:none; /* we don't want to view the list as a list */ + line-height:1.5em; /* globally set the menu's item spacing. note */ +} /* this must be 1.0 or 1.5 or 2.0 for Mozilla */ + +.menu li { + float:left; /* this creates the side-by-side array of top-level buttons */ + position:relative; /* create local positioning contexts for each button */ + margin:0; + width:85px; +} + +.menu ul li table { + margin:-1px 0; /* IE5 needs -1px top and bottom table margins */ + margin:0; /* re-zero the table margins for everyone but IE5 */ + border-collapse:collapse; /* IE5 needs this for the sub-menus to work */ + font-size:12px; /* this sets the base font size for our entire menu */ +} + +.drop { + display:block; + padding:0px 0.33em; /* this sets the l/r margins for our menu item */ + margin:0; + text-align:right; /* this right alignment goes with the float:left below */ + cursor:pointer; /* IE tries to switch back to an I-beam, don't let it */ + cursor:hand; /* IE5 only knows about "hand", so set it both ways */ +} + +.drop span { /* this simultaneously left and right aligns the text and */ + float:left; /* the >> in the drop-down menus which link to sub-menus */ +} + +.rightmenu { + position:relative; /* establish a local positioning context for YAH label */ + float:right; /* and right-align it at the top of our page */ +} + +/*======================== TOP LEVEL MENU DEFINITIONS ========================*/ +.menu a { + text-decoration: none; +} + +.menu a:link { + color:black; +} + +.menu a:visited { + color: black; +} + +.menu a:hover { + color:blue; +} + +.menu ul li ul { + display:none; /* initially hide the entire list hierarchy */ + padding:1px; /* this is our box border width */ +} + +.menu ul li a, +.menu ul li a:visited { /* unselected top-level menu items */ + display:block; + float:left; + text-decoration:none; + height:18px; +} + +.menu ul li:hover a, +.menu ul li a:hover { /* selected top-level menu items */ + display:block; + border-top:1px solid #000; /* these 2 lines create the push-in illusion */ + height:16px; +} + +/*======================== 2ND LEVEL MENU DEFINITIONS ========================*/ + +/* 2nd level drop-down box */ +.menu ul li:hover ul, +.menu ul li a:hover ul { + display:block; + position:absolute; + margin:0; + top:18px; /* place us just up underneath the top-level images */ + left:-1px; /* left-align our drop-down to the previous button border */ + height:auto; /* the drop-down height will be determiend by line count */ + width:13.5em; + color:black; /* this sets the unselected-text color */ + background:black; /* this sets our menu's effective "border" color */ +} + +.menu ul li:hover ul.leftbutton, +.menu ul li a:hover ul.leftbutton {/* our first dropdown should not be skewed */ + left:0px; +} + +.menu ul li:hover ul.skinny, +.menu ul li a:hover ul.skinny { /* 2nd level skinny drop-down box */ + width:8.08333em; /* with a 12px default font, this is 97px width (97/12) */ +} + +.menu ul.rightmenu li:hover ul, +.menu ul.rightmenu li a:hover ul { /* 2nd level neighborhood drop-down box */ + left:auto; + right:0; /* nudge the right menu right to line up under the border */ + width:400px; /* with a 12px default font, this is 228px width (228/12) */ +} + +/* IE5/6 needs a tweak here */ +* html .menu ul.rightmenu li a:hover ul { + right:-1px; +} + +/* 2nd level unselected items */ +.menu ul li:hover ul li a, +.menu ul li a:hover ul li a { + border:0; + margin:0; + padding:0; + height:auto; + color:#000; /* this sets the unselected drop-down text color */ + background:#fef5d8; /* this sets the drop-down menu background color */ + width:13.5em; +} + +/* 2nd level selected item */ +.menu ul li:hover ul li:hover a, +.menu ul li a:hover ul li a:hover { + color:black; + background:white; +} + +/* 2nd level un+selected items */ +.menu ul li:hover ul.skinny li a, +.menu ul li a:hover ul.skinny li a, +.menu ul li:hover ul.skinny li a:hover, +.menu ul li a:hover ul.skinny li a:hover { + width:8.08333em; +} + +/*======================== 3RD LEVEL MENU DEFINITIONS ========================*/ + +/* hide inactive 3rd-level menus */ +.menu ul li:hover ul li ul, +.menu ul li a:hover ul li a ul { + visibility:hidden; +} + +/* 3rd level drop-down box */ +.menu ul li:hover ul li:hover ul, +.menu ul li a:hover ul li a:hover ul { + visibility:visible; + position:absolute; + margin-top:-1px; /* bring the top edge of the 3rd level menu up one */ + top:0; + left:8.08333em; + width:14em; +} + +/* 3rd level unselected items */ +.menu ul li:hover ul li:hover ul li a, +.menu ul li a:hover ul li a:hover ul li a { + width:14em; + background:#fef5d8; +} + +/* level3 selected items */ +.menu ul li:hover ul li:hover ul li a:hover, +.menu ul li a:hover ul li a:hover ul li a:hover { + width:14em; + background:white; +} + +/* +the Mac's standard Safari browser will not see this code but every other browser +will and should Safari barfs on the illegal pound sign (#) after the rule's +property val +*/ +#text { + height:1.215em;# +} \ No newline at end of file diff --git a/clearadm/cleartasks.pl b/clearadm/cleartasks.pl new file mode 100755 index 0000000..aa5c09c --- /dev/null +++ b/clearadm/cleartasks.pl @@ -0,0 +1,596 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: cleartasks.pl,v $ + +Scrub Clearadm records + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.25 $ + +=item Created: + +Sun Jan 2 19:40:28 EST 2011 + +=item Modified: + +$Date: 2013/06/02 18:47:26 $ + +=back + +=head1 SYNOPSIS + + Usage cleartasks.pl: [-u|sage] [-ve|rbose] [-deb|ug] + + Where: + -u|sage: Displays usage + + -v|erbose: Be verbose + -de|bug: Output debug messages + + -da|emon: Run in daemon mode (Default: yes) + -p|idfile: File to be created with the pid written to it (Default: + cleartasks.pid). Note: pidfile is only written if -daemon is + specified. + +=head1 DESCRIPTION + +Examine the Clearadm schedule and perform the tasks required. + +=cut + +use strict; +use warnings; + +use FindBin; +use Getopt::Long; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use Clearadm; +use Clearexec; +use DateUtils; +use Display; +use TimeUtils; +use Utils; + +my $VERSION = '$Revision: 1.25 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +my $logfile = "$Clearadm::CLEAROPTS{CLEARADM_LOGDIR}/$FindBin::Script.log"; +my $pidfile = "$Clearadm::CLEAROPTS{CLEARADM_RUNDIR}/$FindBin::Script.pid"; +my $daemon = 1; + +# Augment PATH with $Clearadm::CLEAROPTS{CLEARADM_BASE} +$ENV{PATH} .= ":$Clearadm::CLEAROPTS{CLEARADM_BASE}"; + +my ($clearadm, $clearexec); + +sub HandleSystemNotCheckingIn (%) { + my (%system) = @_; + + my $startTime = time; + + my $message = "Unable to connect to system $system{name}:$system{port}"; + + my %runlog = ( + task => 'System checkin', + started => Today2SQLDatetime, + status => 1, + message => $message, + system => $system{name}, + ); + + my ($err, $msg, $lastid) = $clearadm->AddRunlog (%runlog); + + $clearadm->Error ("Unable to add to runlog (Status: $err)\n$msg") if $err; + + # Check to see if we should notify anybody about this non-responding system + my %notification = $clearadm->GetNotification ('System checkin'); + + my $when = Today2SQLDatetime; + my $nomorethan = lc $notification{nomorethan}; + my $systemLink = $Clearadm::CLEAROPTS{CLEARADM_WEBBASE}; + $systemLink .= "/systemdetails.cgi?system=$system{name}"; + my $runlogLink = $Clearadm::CLEAROPTS{CLEARADM_WEBBASE}; + $runlogLink .= "/runlog.cgi?id=$lastid"; + my $subject = "System is not responding (Is clearagent running?)"; + $message = <<"END"; +
    +

    Alert System not responding!

    +
    + +

    On $when the system $system{name} was not responding to clearagent requests. This can happen if +clearagent is not setup and running on the system.

    +END + + $clearadm->Notify ( + $notification{name}, + $subject, + $message, + 'System Checkin', + $system{name}, + undef, + $lastid, + ); + + verbose "$system{name}: $subject"; + + return; +} # HandleSystemNotCheckingIn + +sub SystemsCheckin () { + foreach ($clearadm->FindSystem) { + my %system = %$_; + + next if $system{active} eq 'false'; + + verbose "Contacting system $system{name}:$system{port}"; + + my $startTime = time; + + my $status = $clearexec->connectToServer ( + $system{name}, + $system{port} + ); + + unless ($status) { + HandleSystemNotCheckingIn %system; + next; + } # unless + + $clearexec->disconnectFromServer; + + verbose 'Successfully checked in with system: ' + . "$system{name}:$system{port}"; + + display __FILE__ . " DEBUG: System undefined 1" unless $system{name}; + $clearadm->UpdateSystem ( + $system{name}, + (lastheardfrom => Today2SQLDatetime) + ); + + $clearadm->ClearNotifications ($system{name}) + if $system{notification} and $system{notification} eq 'Heartbeat'; + } # foreach + + return; +} # SystemsCheckin + +sub UpdateRunlog ($$$$) { + my ($status, $startTime, $task, $output) = @_; + + my %runlog = ( + task => $$task{name}, + system => $$task{system}, + started => Today2SQLDatetime, + ); + + $runlog{status} = $status; + + if ($status == 0) { + if (@$output) { + $runlog{message} = join "\n", @$output; + } else { + $runlog{message} = 'Successful execution of '; + $runlog{message} .= "$$task{name}: $$task{command}"; + } # if + } else { + if (@$output) { + $runlog{message} = join "\n", @$output; + } else { + $runlog{message} = 'Unable to execute '; + $runlog{message} .= "$$task{name}: $$task{command} "; + $runlog{message} .= join (' ', @$output); + } # if + } # if + + my ($err, $msg, $lastid) = $clearadm->AddRunlog (%runlog); + + $clearadm->Error ($msg, $err) if $err; + + return $lastid; +} # UpdateRunlog + +sub MakeSystemLink ($) { + my ($system) = @_; + + return "$Clearadm::CLEAROPTS{CLEARADM_WEBBASE}/systemdetails.cgi?system=" + . $system; +} # MakeSystemLink + +sub MakeLoadavgLink ($) { + my ($system) = @_; + + return "$Clearadm::CLEAROPTS{CLEARADM_WEBBASE}/plot.cgi?type=loadavg&system=" + . "$system&scaling=Hour&points=24"; +} # MakeLoadavgLink + +sub ProcessLoadavgErrors ($$$$@) { + # TODO: Also need to handle the case where the error was something other + # than "Load average over threshold". Perhaps by having different return + # status. Also, runlog entry #22169 never reported! + my ($notification, $task, $system, $lastid, @output) = @_; + + my $when = Today2SQLDatetime; + + foreach (@output) { + # We need to log this output. Write it to STDOUT + display $_; + + my ($subject, $message, $currLoadavg, $threshold, $systemLink, $loadavgLink); + + if (/System: (\w+) Loadavg (\d+\.\d+) Threshold (\d+\.\d+)/) { + $system = $1; + $currLoadavg = $2; + $threshold = $3; + $systemLink = MakeSystemLink $system; + $loadavgLink = MakeLoadavgLink $system; + $subject = "Load average of $currLoadavg exceeds threshold "; + $subject .= "($threshold)"; + $message = <<"END"; +
    +

    Alert Load Average is over the threshold!

    +
    + +

    On $when the system $system's load avg +($currLoadavg) had exceeded the threshold set for +this system ($threshold).

    +END + } elsif (/ERROR.*system\s+(\S+):/) { + $system = $1; + $systemLink = MakeSystemLink $system; + $subject = "Error trying to obtain Loadavg"; + $message = <<"END"; +
    +

    Alert Unable to obtain Loadavg!

    +
    + +

    On $when we were unable to obtain the Loadavg for +system $system.

    + +

    The following was the error message:

    +
    $_
    +END + } else { + $message = <<"END"; +

    On $when on the system $system, we were unable to parse the Loadavg output. This is what we saw:

    + +
    +END
    +      $message .= join "\n", @output;
    +      $message .= "
    "; + $clearadm->Error ($message, -1); + + last; + } # if + + $clearadm->Notify ( + $notification, + $subject, + $message, + $task, + $system, + undef, + $lastid, + ); + } # foreach + + return; +} # ProcessLoadAvgErrors + +sub ProcessFilesystemErrors ($$$$@) { + # TODO: Also need to handle the case where the error was something other + # than "Filesystem over threshold". Perhaps by having different return + # status. + my ($notification, $task, $system, $lastid, @output) = @_; + + my $when = Today2SQLDatetime; + + my %system; + + foreach (@output) { + # We need to log this output. Write it to STDOUT + display $_; + + if (/System:\s*(\S+)\s*Filesystem:\s*(\S+)\s*Used:\s*(\d+\.\d+)%\s*Threshold:\s*(\d+)/) { + my %fsinfo = ( + filesystem => $2, + usedPct => $3, + threshold => $4 + ); + + if ($system{$1}) { + $system{$1} = [$system{$1}, \%fsinfo]; + } else { + $system{$1} = \%fsinfo; + } # if + } # if + } # foreach + + foreach my $systemName (keys %system) { + my @fsinfo; + + if (ref $system{$systemName} eq 'HASH') { + push @fsinfo, $system{$systemName}; + } else { + push @fsinfo, @{$system{$systemName}}; + } # if + + my $systemLink = MakeSystemLink ($systemName); + my $subject = 'Filesystem has exceeded threshold'; + my $message = <<"END"; +
    +

    Alert Filesystem is over the threshold!

    +
    + +

    On $when the following filesystems on $systemName +were over their threshold.

    + +
      +END + foreach (@fsinfo) { + my %fsinfo = %{$_}; + my $filesystemLink = $Clearadm::CLEAROPTS{CLEARADM_WEBBASE}; + $filesystemLink .= "/plot.cgi?type=filesystem&system=$systemName"; + $filesystemLink .= "&filesystem=$fsinfo{filesystem}"; + $filesystemLink .= '&scaling=Day&points=7'; + $message .= "
    • Filesystem "; + $message .= "$fsinfo{filesystem} is $fsinfo{usedPct}% full. Threshold is "; + $message .= "$fsinfo{threshold}%
    • "; + } # foreach + + $message .= "
    "; + + $clearadm->Notify ( + $notification, + $subject, + $message, + $task, + $systemName, + undef, + $lastid, + ); + } # foreach + + return; +} # ProcessFilesystemErrors + +sub NonZeroReturn ($$$$$$) { + my ($system, $notification, $status, $lastid, $output, $task) = @_; + + my @output = @{$output}; + my %task = %{$task}; + + my $when = Today2SQLDatetime; + + my $subject = "Non zero return from $task{command} " + . "executing on $system"; + my $taskLink = $Clearadm::CLEAROPTS{CLEARADM_WEBBASE}; + $taskLink .= "/tasks.cgi?task=$task{name}"; + my $similarLink = $Clearadm::CLEAROPTS{CLEARADM_WEBBASE}; + $similarLink .= "/runlog.cgi?system=$task{system}" + . "&status=$status&" + . "&task=$task{name}"; + my $runlogLink = $Clearadm::CLEAROPTS{CLEARADM_WEBBASE}; + $runlogLink .= "/runlog.cgi?id=$lastid"; + my $message = <<"END"; +
    +

    Alert Non zero status from script execution!

    +
    + +

    On $when, while executing $task{name} on +$task{system}, a non zero status of $status was returned. Here is the resulting +output:

    +END
    +
    +  $message .= join "\n", @output;
    +  $message .= <<"END";
    +
    +

    You may wish to examine the individual runlog entry +that caused this alert or a list of similar +failures.

    +END + + $message .= ""; + + $clearadm->Notify ( + $notification, + $subject, + $message, + $task, + $system, + undef, + $lastid, + ); + + return; +} # NonZeroReturn + +sub ExecuteTask ($%) { + my ($sleep, %task) = @_; + + my ($status, @output, %system, $subject, $message); + + verbose_nolf "Performing task $task{name}"; + + my %notification = $clearadm->GetNotification ($task{notification}); + + my $startTime = time; + + if ($task{system} =~ /localhost/i) { + verbose " on localhost"; + ($status, @output) = Execute "$task{command} 2>&1"; + } else { + %system = $clearadm->GetSystem ($task{system}); + + verbose " on $system{name}"; + + $status = $clearexec->connectToServer ( + $system{name}, + $system{port} + ); + + unless ($status) { + $output[0] = "Unable to connect to system $system{name}:$system{port} to " + . "execute $task{command}"; + $status = -1; + } else { + ($status, @output) = $clearexec->execute ($task{command}); + + $output[0] = "Unable to exec $task{command} on $system{name}" + if $status == -1; + } # unless + + $clearexec->disconnectFromServer; + } # if + + my $lastid = UpdateRunlog ($status, $startTime, \%task, \@output); + + if ($status != 0) { + if ($notification{cond} + and $notification{cond} =~ /non zero return/i) { + NonZeroReturn ( + $system{name}, + $notification{name}, + $status, + $lastid, + \@output, + \%task + ); + } elsif ($notification{cond} =~ /loadavg over threshold/i) { + ProcessLoadavgErrors ($notification{name}, $task{name}, $system{name}, $lastid, @output); + } elsif ($notification{cond} =~ /filesystem over threshold/i) { + ProcessFilesystemErrors ($notification{name}, $task{name}, $system{name}, $lastid, @output); + } # if + } else { + $clearadm->ClearNotifications ($task{system}); + } # if + + my ($err, $msg) = $clearadm->UpdateSchedule ( + $task{schedulename}, + ( 'lastrunid' => $lastid ), + ); + + $clearadm->Error ($msg, $err) if $err; + + $sleep -= time - $startTime; + + return $sleep; +} # ExecuteTask + +# Main +GetOptions ( + 'usage' => sub { Usage }, + 'verbose' => sub { set_verbose }, + 'debug' => sub { set_debug }, + 'daemon!' => \$daemon, + 'pidfile=s' => \$pidfile, +) or Usage "Invalid parameter"; + +Usage 'Extraneous options: ' . join ' ', @ARGV + if @ARGV; + +EnterDaemonMode $logfile, $logfile, $pidfile + if $daemon; + +display "$FindBin::Script V$VERSION started at " . localtime; + +$clearadm = Clearadm->new; +$clearexec = Clearexec->new; + +$clearadm->SetNotify; + +while () { + # First check in with all systems + SystemsCheckin; + + my ($sleep, @workItems) = $clearadm->GetWork; + + foreach (@workItems) { + my %scheduledTask = %{$_}; + + $scheduledTask{system} ||= 'All systems'; + + if ($scheduledTask{system} =~ /all systems/i) { + foreach my $system ($clearadm->FindSystem) { + $scheduledTask{system} = $$system{name}; + $sleep = ExecuteTask $sleep, %scheduledTask; + } # foreach + } else { + $sleep = ExecuteTask $sleep, %scheduledTask; + } # if + } # foreach + + if ($sleep > 0) { + verbose "Sleeping for $sleep seconds"; + sleep $sleep; + } # if +} # foreach + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Clearadm + Clearexec + DateUtils + Display + TimeUtils + Utils + +=end man + +=begin html + +
    +Clearadm
    +Clearexec
    +DateUtils
    +Display
    +TimeUtils
    +Utils
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut diff --git a/clearadm/delete.png b/clearadm/delete.png new file mode 100644 index 0000000000000000000000000000000000000000..6fc4d3b2030ecf772fae856411c7b0968d99005b GIT binary patch literal 311 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLli5Jei0#Y0$L4LviA%Njt^WI31l&6bhNX4zB1b1cz#v=#L9XQ1BeB*}A z%q#-T*4)b4(o?t>F*0#zoIb=Q$oTmtkhx|Ks|wRYkOCL`0G3nf>>LU!8g&ecZvf@i zF{v>LIB*y*Py(9O5Wuep(t6;`5vIViTuKZ~9gTA(-x+M+aA06$eNbb-z@e}}OvNsM m(}BTo!EZ5z1_KrbCLV@_U2LV?JKcpq4)=8Rb6Mw<&;$Tia9maZ literal 0 HcmV?d00001 diff --git a/clearadm/deletealertlog.cgi b/clearadm/deletealertlog.cgi new file mode 100755 index 0000000..88ee8e2 --- /dev/null +++ b/clearadm/deletealertlog.cgi @@ -0,0 +1,159 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: deletealertlog.cgi,v $ + +Delete alertlog entries + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.2 $ + +=item Created: + +Mon Oct 25 11:10:47 PDT 2008 + +=item Modified: + +$Date: 2011/02/02 16:50:27 $ + +=back + +=head1 SYNOPSIS + + Usage deletealertlog.cgi: [-u|sage] [-ve|rbose] [-d|ebug] + alertlogid=[|all] + + Where: + -u|sage: Displays usage + -ve|rbose: Be verbose + -d|ebug: Output debug messages + + alertlogid: Alertlog ID to delete or 'all' to clear the alertlog. + +=head2 DESCRIPTION + +This script deletes alertlog entries. + +=cut + +use strict; +use warnings; + +use FindBin; +use Getopt::Long; +use CGI qw (:standard :cgi-lib *table start_Tr end_Tr); +use CGI::Carp 'fatalsToBrowser'; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use Clearadm; +use ClearadmWeb; +use Display; +use Utils; + +my $VERSION = '$Revision: 1.2 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +my $clearadm; + +# Main +GetOptions ( + usage => sub { Usage }, + verbose => sub { set_verbose }, + debug => sub { set_debug }, +) or Usage 'Invalid parameter'; + +# Announce ourselves +verbose "$FindBin::Script v$VERSION"; + +$clearadm = Clearadm->new; + +my %opts = Vars; + +my $title = 'Alert Log'; + +heading $title; + +my ($err, $msg) = $clearadm->DeleteAlertlog ($opts{alertlogid}); + +display h1 {class => 'center'}, $title; + +if ($msg eq 'Records deleted') { + if ($err > 1) { + display h3 {class => 'center'}, "Cleared all alertlog entries"; + } else { + display h3 {class => 'center'}, "Deleted alertlog record"; + } # if + + displayAlertlog (%opts); +} else { + displayError "Unable to delete alertlog entry (Status: $err)
    $msg"; +} # if + +footing; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Clearadm + ClearadmWeb + Display + Utils + +=end man + +=begin html + +
    +Clearadm
    +ClearadmWeb
    +Display
    +Utils
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut \ No newline at end of file diff --git a/clearadm/discovery.pl b/clearadm/discovery.pl new file mode 100755 index 0000000..ee4afa8 --- /dev/null +++ b/clearadm/discovery.pl @@ -0,0 +1,199 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: discovery.pl,v $ + +Update System + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.1 $ + +=item Created: + +Mon Dec 13 09:13:27 EST 2010 + +=item Modified: + +$Date: 2011/01/07 20:48:22 $ + +=back + +=head1 SYNOPSIS + + Usage updatesystem.pl: [-u|sage] [-ve|rbose] [-deb|ug] + [-b|roadcastTime ] + + Where: + -u|sage: Displays usage + + -ve|rbose: Be verbose + -deb|ug: Output debug messages + + -broadcastA|ddr : Broadcast IP (Default: Current subnet) + -broadcastT|ime : Number of sends to wait for responses to broadcast + (Default: 30 seconds) + +=head1 DESCRIPTION + +This script will discover systems on the local subnet and then add or update +them in the Clearadm database. + +=cut + +use strict; +use warnings; + +use Socket; + +use FindBin; +use Getopt::Long; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use Clearadm; +use Display; +use Utils; + +my $VERSION = '$Revision: 1.1 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +my $clearadm = Clearadm->new; + +my $broadcastTime = 10; + +sub discover ($) { + my ($broadcast) = @_; + + my $startTime = time; + + my %hosts; + + verbose "Performing discovery (for $broadcastTime seconds)..."; + + while (<$broadcast>) { + if (/from (.*):/) { + my $ip = $1; + my $hostname = gethostbyaddr (inet_aton ($ip), AF_INET); + + unless ($hosts{$ip}) { + verbose "Received response from ($ip): $hostname"; + $hosts{$ip} = $hostname; + } # unless + } # if + + last + if (time () - $startTime) > $broadcastTime; + } # while + + verbose "$broadcastTime seconds has elapsed - discovery complete"; + + return %hosts +} # discover + +# Main +my $broadcastAddress = inet_ntoa (INADDR_BROADCAST); + +GetOptions ( + usage => sub { Usage }, + verbose => sub { set_verbose }, + debug => sub { set_debug }, + 'broadcastTime=s' => \$broadcastTime, + 'broadcastAddr=s' => \$broadcastAddress, +) or Usage "Invalid parameter"; + +Usage 'Extraneous options: ' . join ' ', @ARGV + if @ARGV; + +# Announce ourselves +verbose "$FindBin::Script V$VERSION"; + +my $broadcastCmd = "ping -b $broadcastAddress 2>&1"; + +my $pid = open my $broadcast, '-|', $broadcastCmd + or error "Unable to do $broadcastCmd", 1; + +my %hosts = discover $broadcast; + +kill TERM => $pid; + +close $broadcast; + +my $nbrHosts = scalar keys %hosts; + +verbose_nolf "Found $nbrHosts host"; +verbose_nolf 's' if $nbrHosts != 1; +verbose " on subnet $broadcastAddress"; + +foreach (sort values %hosts) { + my $verbose = get_verbose () ? '-verbose' : ''; + + my ($status, @output) = Execute "updatesystem.pl -host $_ $verbose"; + + error "Unable to update host $_ (Status: $status)\n" + . join ("\n", @output), 1 + if $status; + + verbose join "\n", @output; +} # foreach + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Clearadm + Display + Utils + +=end man + +=begin html + +
    +Clearadm
    +Display
    +Utils
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut \ No newline at end of file diff --git a/clearadm/down.png b/clearadm/down.png new file mode 100644 index 0000000000000000000000000000000000000000..1b214625b873ed0f529a96851f225f077d6e26a8 GIT binary patch literal 823 zcmV-71IYY|P)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2ipM- z4G|W`M;+Y&00Os3L_t(2&sCFKh|F;m$3MT{fBtj7&+f7pTGm?ESi&aCighbeB*g=d zct9e_lcFRK!iy4-El(oJ#y)J?-3Pg?aoNl)wf;M6{b%g#Ty}OYKM%uNr%vb8`JQv? zobMMv{Byw+v*8KW0|6!rLQKV5CZP_OBAbRe%XU&X>nv5Z&E8;uTsBK?$oRqc=uN`i z=Wx0+WK!i(07wl=MOe{x&(YGlcgL0uYEgAaSfobCZowl3H#&QH-hMAe&yzLO&WsTK zC^SHz6g0P93vAnc@c5qX)Vb}#Ed|+6efTxW`o@{;TG~XZey*!08r#F0t{Xs3L(?Ht zyMWajZ?$dPOy6*Vk$95vg5uJlL&U~XG`F-c$LC;4b%hAya7X$hHcX>KQp`hL;}LB^ zGhIf4N(oIqCyOgwpzHifrsy0Pq9Zy?YNEh_)|HAG4f_E{`CxwkD&g}}ZPf@`H7cDt z^?n;Duv-MxE-M{TgJ1a~2_*#6YAS&$34o@%EEWx22y)3WlIBm`HXW2gN`cF6W#sDs zAIwoQ#Uh$+1@M%cGh>6+VqT?6B|g7<%Y|bnc-hwvLQq1|ey5%L=g;xw=O~$p0>wlE zz|;V+yDQucO`Ed*AdRzT<8#=#bLA@7te9qk{`Xq)S zoYj4Y=Ph4ujf@vWedAo*PA9|RK7=M&d-N2BH$+KFx*tCl^gKC9sP0wy&)#4TbNFp6 zo!>Wq@j{i-bP=zpLHPUx)^Ek|1c~SJ>U(dup!30PjOazi%tCo{E(__=2tSNoV{oV~ zSro1+zZdE8l9|Y>Ol(Muzj!9-dU%5cTFRo%a9m( z&9@JCfiM=mKp{U#?WsyB^So>l&;bko%k6J2egjl!0%brSh>ZXM002ovPDHLkV1m!; BbY=hm literal 0 HcmV?d00001 diff --git a/clearadm/edit.png b/clearadm/edit.png new file mode 100644 index 0000000000000000000000000000000000000000..05711a0948ed445c97dd21645f42a22a403735c9 GIT binary patch literal 451 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLl>+{$oq9z#bv`|(K8|7RH1EM%;!+W@rl%$ff) zXP){0|Noy*%+B9=lNdxR0lGTCPZRLGK012lzcvQJL|i0{bqbP0l+XkKJS4LG literal 0 HcmV?d00001 diff --git a/clearadm/etc/clearadm.conf b/clearadm/etc/clearadm.conf new file mode 100644 index 0000000..668da41 --- /dev/null +++ b/clearadm/etc/clearadm.conf @@ -0,0 +1,23 @@ +############################################################################### +# +# File: $RCSfile: clearadm.conf,v $ +# Revision: $Revision: 1.7 $ +# Description: Config file for Clearadm +# Author: Andrew@ClearSCM.com +# Created: Wed Dec 15 18:43:12 EST 2010 +# Modified: $Date: 2011/12/26 18:34:58 $ +# Language: conf +# +# (c) Copyright 2010, ClearSCM, Inc., all rights reserved +# +############################################################################### +CLEARADM_SERVER: earth +CLEARADM_PORT: 25327 +CLEARADM_LOADAVG_THRESHOLD: 5.00 +CLEARADM_USERNAME: clearwriter +CLEARADM_PASSWORD: clearwriter +CLEARADM_BASE: /opt/clearscm/clearadm +CLEARADM_LOGDIR: $CLEARADM_BASE/log +CLEARADM_RUNDIR: $CLEARADM_BASE/var/run +CLEARADM_WEBBASE: http://$CLEARADM_SERVER/clearadm +CLEARADM_NOTIFY: Andrew@DeFaria.com \ No newline at end of file diff --git a/clearadm/etc/clearexec.conf b/clearadm/etc/clearexec.conf new file mode 100644 index 0000000..c344d75 --- /dev/null +++ b/clearadm/etc/clearexec.conf @@ -0,0 +1,18 @@ +############################################################################### +# +# File: $RCSfile: clearexec.conf,v $ +# Revision: $Revision: 1.2 $ +# Description: Config file for Clearexec +# Author: Andrew@ClearSCM.com +# Created: Wed Dec 15 18:43:12 EST 2010 +# Modified: $Date: 2011/01/04 14:44:39 $ +# Language: conf +# +# (c) Copyright 2010, ClearSCM, Inc., all rights reserved +# +############################################################################### +CLEAREXEC_HOST: earth +CLEAREXEC_PORT: 25327 +CLEAREXEC_MULTITHREADED: 1 +CLEAREXEC_LOGDIR: /opt/clearscm/clearadm/log +CLEAREXEC_RUNDIR: /opt/clearscm/clearadm/var/run \ No newline at end of file diff --git a/clearadm/etc/clearuser.conf b/clearadm/etc/clearuser.conf new file mode 100644 index 0000000..768d9c6 --- /dev/null +++ b/clearadm/etc/clearuser.conf @@ -0,0 +1,18 @@ +############################################################################### +# +# File: $RCSfile: clearuser.conf,v $ +# Revision: $Revision: 1.1 $ +# Description: Config file for User.pm +# Author: Andrew@ClearSCM.com +# Created: Wed Dec 15 18:43:12 EST 2010 +# Modified: $Date: 2010/12/22 12:37:36 $ +# Language: conf +# +# (c) Copyright 2010, ClearSCM, Inc., all rights reserved +# +############################################################################### +CLEARUSER_LDAPHOST: +CLEARUSER_BIND: +CLEARUSER_BASEDN: +CLEARUSER_USERNAME: +CLEARUSER_PASSWORD: \ No newline at end of file diff --git a/clearadm/etc/conf.d/clearadm b/clearadm/etc/conf.d/clearadm new file mode 100644 index 0000000..eb251fb --- /dev/null +++ b/clearadm/etc/conf.d/clearadm @@ -0,0 +1,24 @@ +############################################################################### +# +# File: $RCSfile: clearadm,v $ +# Revision: $Revision: 1.3 $ +# Description: Apache config file for Clearadm +# Author: Andrew@ClearSCM.com +# Created: Wed Dec 15 18:43:12 EST 2010 +# Modified: $Date: 2011/05/26 05:48:43 $ +# Language: Apache conf +# +# (c) Copyright 2010, ClearSCM, Inc., all rights reserved +# +############################################################################### +Alias /clearadm /opt/clearscm/clearadm + + + Options Indexes FollowSymLinks ExecCGI + AllowOverride None + Order allow,deny + Allow from all + DirectoryIndex index.cgi index.html + + +AddHandler cgi-script .cgi diff --git a/clearadm/etc/init.d/clearagent b/clearadm/etc/init.d/clearagent new file mode 100755 index 0000000..99b5e55 --- /dev/null +++ b/clearadm/etc/init.d/clearagent @@ -0,0 +1,156 @@ +#!/bin/sh +### BEGIN INIT INFO +# Provides: clearagent +# Required-Start: $network +# Required-Stop: none +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Starts the clearagent daemon +# Description: Clearagent is part of the Clearadm package by ClearSCM, +# Inc. It is a daemon that runs in the background and +# responds to requests to run commands on the local system +# and return the results. +### END INIT INFO + +# Author: Andrew DeFaria +# +# Do NOT "set -e" + +# PATH should only include /usr/* if it runs after the mountnfs.sh script +PATH=/sbin:/usr/sbin:/bin:/usr/bin +DESC="Clearagent Daemon" +NAME=clearagent.pl +DAEMON=/opt/clearscm/clearadm/$NAME +PIDFILE=/opt/clearscm/clearadm/var/run/$NAME.pid +DAEMON_ARGS="" +SCRIPTNAME=/etc/init.d/$NAME +RUNASUSER="clearagent" + +# Exit if the package is not installed +[ -x "$DAEMON" ] || exit 0 + +# Read configuration variable file if it is present +[ -r /etc/default/$NAME ] && . /etc/default/$NAME + +# Load the VERBOSE setting and other rcs variables +. /lib/init/vars.sh + +# Define LSB log_* functions. +# Depend on lsb-base (>= 3.0-6) to ensure that this file is present. +. /lib/lsb/init-functions + +# +# Function that starts the daemon/service +# +do_start() +{ + # Return + # 0 if daemon has been started + # 1 if daemon was already running + # 2 if daemon could not be started + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ + || return 1 + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON \ + --chuid $RUNASUSER \ + -- $DAEMON_ARGS \ + || return 2 +} + +# +# Function that stops the daemon/service +# +do_stop() +{ + # Return + # 0 if daemon has been stopped + # 1 if daemon was already stopped + # 2 if daemon could not be stopped + # other if a failure occurred + start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME + RETVAL="$?" + [ "$RETVAL" = 2 ] && return 2 + # Wait for children to finish too if this is a daemon that forks + # and if the daemon is only ever run from this initscript. + # If the above conditions are not satisfied then add some other code + # that waits for the process to drop all resources that could be + # needed by services started subsequently. A last resort is to + # sleep for some time. + start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON + [ "$?" = 2 ] && return 2 + # Many daemons don't delete their pidfiles when they exit. + rm -f $PIDFILE + return "$RETVAL" +} + +# +# Function that sends a SIGHUP to the daemon/service +# +do_reload() { + # + # If the daemon can reload its configuration without + # restarting (for example, when it is sent a SIGHUP), + # then implement that here. + # + start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME + return 0 +} + +case "$1" in + start) + [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" + do_start + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + stop) + [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" + do_stop + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + status) + status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? + ;; + #reload|force-reload) + # + # If do_reload() is not implemented then leave this commented out + # and leave 'force-reload' as an alias for 'restart'. + # + #log_daemon_msg "Reloading $DESC" "$NAME" + #do_reload + #log_end_msg $? + #;; + restart|force-reload) + # + # If the "reload" option is implemented then remove the + # 'force-reload' alias + # + log_daemon_msg "Restarting $DESC" "$NAME" + do_stop + case "$?" in + 0|1) + do_start + case "$?" in + 0) log_end_msg 0 ;; + 1) log_end_msg 1 ;; # Old process is still running + *) log_end_msg 1 ;; # Failed to start + esac + ;; + *) + # Failed to stop + log_end_msg 1 + ;; + esac + ;; + *) + #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2 + echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 + exit 3 + ;; +esac + +: diff --git a/clearadm/etc/init.d/cleartasks b/clearadm/etc/init.d/cleartasks new file mode 100755 index 0000000..ddbb1c2 --- /dev/null +++ b/clearadm/etc/init.d/cleartasks @@ -0,0 +1,156 @@ +#!/bin/sh +### BEGIN INIT INFO +# Provides: cleartasks +# Required-Start: $network $mysql +# Required-Stop: none +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Starts the cleartasks daemon +# Description: Cleartasks are part of the Clearadm package by ClearSCM, +# Inc. It is a daemon that runs in the background and +# performs the various predefined and user defined tasks +# from the Clearadm database +### END INIT INFO + +# Author: Andrew DeFaria +# +# Do NOT "set -e" + +# PATH should only include /usr/* if it runs after the mountnfs.sh script +PATH=/sbin:/usr/sbin:/bin:/usr/bin +DESC="Cleartasks Daemon" +NAME=cleartasks.pl +DAEMON=/opt/clearscm/clearadm/$NAME +PIDFILE=/opt/clearscm/clearadm/var/run/$NAME.pid +DAEMON_ARGS="" +SCRIPTNAME=/etc/init.d/$NAME +RUNASUSER="clearagent" + +# Exit if the package is not installed +[ -x "$DAEMON" ] || exit 0 + +# Read configuration variable file if it is present +[ -r /etc/default/$NAME ] && . /etc/default/$NAME + +# Load the VERBOSE setting and other rcs variables +. /lib/init/vars.sh + +# Define LSB log_* functions. +# Depend on lsb-base (>= 3.0-6) to ensure that this file is present. +. /lib/lsb/init-functions + +# +# Function that starts the daemon/service +# +do_start() +{ + # Return + # 0 if daemon has been started + # 1 if daemon was already running + # 2 if daemon could not be started + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ + || return 1 + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON \ + --chuid $RUNASUSER \ + -- $DAEMON_ARGS \ + || return 2 +} + +# +# Function that stops the daemon/service +# +do_stop() +{ + # Return + # 0 if daemon has been stopped + # 1 if daemon was already stopped + # 2 if daemon could not be stopped + # other if a failure occurred + start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME + RETVAL="$?" + [ "$RETVAL" = 2 ] && return 2 + # Wait for children to finish too if this is a daemon that forks + # and if the daemon is only ever run from this initscript. + # If the above conditions are not satisfied then add some other code + # that waits for the process to drop all resources that could be + # needed by services started subsequently. A last resort is to + # sleep for some time. + start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON + [ "$?" = 2 ] && return 2 + # Many daemons don't delete their pidfiles when they exit. + rm -f $PIDFILE + return "$RETVAL" +} + +# +# Function that sends a SIGHUP to the daemon/service +# +do_reload() { + # + # If the daemon can reload its configuration without + # restarting (for example, when it is sent a SIGHUP), + # then implement that here. + # + start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME + return 0 +} + +case "$1" in + start) + [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" + do_start + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + stop) + [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" + do_stop + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + status) + status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? + ;; + #reload|force-reload) + # + # If do_reload() is not implemented then leave this commented out + # and leave 'force-reload' as an alias for 'restart'. + # + #log_daemon_msg "Reloading $DESC" "$NAME" + #do_reload + #log_end_msg $? + #;; + restart|force-reload) + # + # If the "reload" option is implemented then remove the + # 'force-reload' alias + # + log_daemon_msg "Restarting $DESC" "$NAME" + do_stop + case "$?" in + 0|1) + do_start + case "$?" in + 0) log_end_msg 0 ;; + 1) log_end_msg 1 ;; # Old process is still running + *) log_end_msg 1 ;; # Failed to start + esac + ;; + *) + # Failed to stop + log_end_msg 1 + ;; + esac + ;; + *) + #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2 + echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 + exit 3 + ;; +esac + +: diff --git a/clearadm/filesystems.cgi b/clearadm/filesystems.cgi new file mode 100755 index 0000000..309fe4f --- /dev/null +++ b/clearadm/filesystems.cgi @@ -0,0 +1,154 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: filesystems.cgi,v $ + +Filesystems + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.11 $ + +=item Created: + +Mon Oct 25 11:10:47 PDT 2008 + +=item Modified: + +$Date: 2011/02/14 14:50:37 $ + +=back + +=head1 SYNOPSIS + + Usage filesystems.cgi: [-u|sage] [-ve|rbose] [-d|ebug] + [-s|ystem ] + + Where: + -u|sage: Displays usage + -v|erbose: Be verbose + -d|ebug: Output debug messages + + -s|sytem: System to report on filesystems (Default: all) + +=head2 DESCRIPTION + +This script displays all known filesystems + +=cut + +use strict; +use warnings; + +use FindBin; +use Getopt::Long; +use CGI qw (:standard :cgi-lib *table start_Tr end_Tr start_td end_td); +use CGI::Carp 'fatalsToBrowser'; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use Clearadm; +use ClearadmWeb; +use Display; +use Utils; + +my $VERSION = '$Revision: 1.11 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +my $subtitle = 'Filesystems Status'; + +my $system = param 'system'; + +my $clearadm; + +# Main +GetOptions ( + usage => sub { Usage }, + verbose => sub { set_verbose }, + debug => sub { set_debug }, + 'system=s' => \$system +) or Usage 'Invalid parameter'; + +$system ||= ''; + +# Announce ourselves +verbose "$FindBin::Script v$VERSION"; + +$subtitle .= $system eq '' + ? ': All Systems' + : ': ' . ucfirst $system; + +$clearadm = Clearadm->new; + +heading $subtitle; + +display h1 {class => 'center'}, $subtitle; + +displayFilesystem ($system); + +footing; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Clearadm + ClearadmWeb + Display + Utils + +=end man + +=begin html + +
    +Clearadm
    +ClearadmWeb
    +Display
    +Utils
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut \ No newline at end of file diff --git a/clearadm/getFilesystems.cgi b/clearadm/getFilesystems.cgi new file mode 100755 index 0000000..ff50079 --- /dev/null +++ b/clearadm/getFilesystems.cgi @@ -0,0 +1,120 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: getFilesystems.cgi,v $ + +Get a list of filesystems for a system + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.5 $ + +=item Created: + +Mon Dec 13 09:13:27 EST 2010 + +=item Modified: + +$Date: 2011/01/14 16:29:37 $ + +=back + +=head1 SYNOPSIS + + Usage getFilesystems.cgi: system= + + Where: + system=: Name of the system defined in the Clearadm database to + retrieve the filesystems for +=head1 DESCRIPTION + +Retrieve a list of filesystems for a given system and put out a web page that +specifies the dropdown representing the timestamps. If +filesystem is specified then we retrieve information about filesystem snapshots +in clearadm.fs, otherwise we retrieve information about loadavg snapshots in +clearadm.loadavg for the given system. Data is scaled by scaling and elementID +is used to determine if we should make 'Earliest' or 'Latest' the default. This +script is intended to be called by AJAX to fill in a dropdown list on a web page +in response to JavaScript action on another dropdown (a system dropdown or an +interval dropdown). + +=cut + +use strict; +use warnings; + +use FindBin; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use Clearadm; +use ClearadmWeb; +use Display; + +use CGI qw (:standard :cgi-lib); + +my %opts = Vars; + +error "System not specified", 1 + unless $opts{system}; + +error "ElementID not specified", 1 + unless $opts{elementID}; + +error 'ElementID must be either "startTimestamp" or "endTimestamp"', 1 + unless $opts{elementID} eq 'startTimestamp' or $opts{elementID} eq 'endTimestamp'; + +my $default = $opts{elementID} eq 'startTimestamp' ? 'Earliest' : 'Latest'; + +my $clearadm = Clearadm->new; + +heading undef, 'short'; + +my $name = $opts{elementID} eq 'startTimestamp' + ? 'start' + : $opts{elementID} eq 'endTimestamp' + ? 'end' + : 'unknown'; + +if ($opts{filesystem}) { + display makeTimeDropdown + 'filesystem', + $opts{elementID}, + $opts{system}, + $opts{filesystem}, + $opts{label}, + $default, + $opts{scaling}, + $name; +} else { + display makeTimeDropdown + 'loadavg', + $opts{elementID}, + $opts{system}, + ucfirst $name, + $opts{label}, + $default, + $opts{scaling}, + $name; +} # if + +display end_html; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Clearadm + ClearadmWeb + Display + +=end man + +=begin html + +
    +Clearadm
    +ClearadmWeb
    +Display
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut \ No newline at end of file diff --git a/clearadm/index.cgi b/clearadm/index.cgi new file mode 100755 index 0000000..ad642a2 --- /dev/null +++ b/clearadm/index.cgi @@ -0,0 +1,206 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: index.cgi,v $ + +Clearadm: Portal to your Clearcase Infrastructure + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.22 $ + +=item Created: + +Mon Oct 25 11:10:47 PDT 2008 + +=item Modified: + +$Date: 2011/02/14 14:50:48 $ + +=back + +=head1 DESCRIPTION + +Clearadm is a web based portal into your Clearcase infrastucture. It seeks to +provide your CM staff with an easy to use, yet informative interface to locate, +report on and monitor various aspects of the Clearcase infrastructure. + +=cut + +use strict; +use warnings; + +use FindBin; +use Getopt::Long; + +use CGI qw (:standard *table start_Tr end_Tr); +use CGI::Carp 'fatalsToBrowser'; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use ClearadmWeb; +use Clearadm; +#use Clearcase; +#use Clearcase::Views; +use Display; +use Utils; + +my $clearadm = Clearadm->new; + +# Main +GetOptions ( + 'usage' => sub { Usage }, + 'verbose' => sub { set_verbose }, + 'debug' => sub { set_debug }, +) or Usage "Invalid parameter"; + +# Announce ourselves +verbose "$ClearadmWeb::APPNAME V$ClearadmWeb::VERSION"; + +heading; + +display p ' '; +display p <<"END"; +Clearadm is a web based portal into your infrastructure. It seeks to provide +your system administrative staff with an easy to use, yet informative interface +to locate, report on and monitor various aspects of your infrastructure. +END + display p <<"END"; +Additionally, Clearacdm is aware of Clearcase servers as well as Clearcase +objects such as views, vobs, etc. When systems are added to Clearadm that house +or server Clearcase objects, additional information is collected about those +objects. +END + +display h1 {class => 'center'}, 'Systems Snapshot'; + +display start_table {cellspacing => 1}; + +my $i = 0; +my $perRow = 5; + +display start_Tr; + +my @systems = $clearadm->FindSystem; + +$perRow = @systems if @systems < $perRow; + +foreach (@systems) { + my %system = %{$_}; + + if ($i++ % $perRow == 0) { + display end_Tr; + display start_Tr; + } # if + + my %load = $clearadm->GetLatestLoadavg ($system{name}); + + my $data; + + $data = '' + if $system{active} eq 'false'; + + $data .= a { + href => "systemdetails.cgi?system=$system{name}" + }, ucfirst $system{name}; + + if ($system{notification}) { + $data .= ' ' . a { + href => "alertlog.cgi?system=$system{name}"}, img { + src => 'alert.png', + border => 0, + alt => 'Alert!', + title => 'This system has alerts', + }; + } # if + + $data .= '
    ' . + a {href => + "plot.cgi?type=loadavg&system=$system{name}&scaling=Hour&points=24" + }, img { + src => "plotloadavg.cgi?system=$system{name}&tiny=1", + border => 0, + }; + + $data .= '
    ' + if $system{active} eq 'false'; + + display td {class => 'dataCentered'}, "$data ", + font {class => 'dim' }, "
    Up: $load{uptime}"; +} # foreach + +while ($i % $perRow != 0) { + $i++; + display td {class => 'data'}, ' '; +} # while + +display end_Tr; + +display end_table; + +footing; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Clearadm + ClearadmWeb + Display + Utils + +=end man + +=begin html + +
    +Clearadm
    +ClearadmWeb
    +Display
    +Utils
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut \ No newline at end of file diff --git a/clearadm/left.png b/clearadm/left.png new file mode 100644 index 0000000000000000000000000000000000000000..305726be286330a707029aaed595d5b0c07ca127 GIT binary patch literal 925 zcmV;O17iG%P)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2ipM~ z5ivD`U$wLV00SLKL_t(I%YBnyXq9ys$G^|-dEfWEy>sqt?#xhkCc2y;XCSMKLi%IN zE;0levM`Ai_7CYI1|>v#V+A>+o62a6Zc8st1ci}k$|gnyW@?aOJr~dS!voLv3;d;C^dbG~MeNTH0RR+=HA7vi{2{Z$f0`a^9b6m7ZR9Ks zRn#coK2Hg>@aV1sXmi%xQ|igRMw@jX8KuRaZym(R_V*BGtA7WWWx$rKZr_&G;+_n@ z)I9b;>5&^FoT}}!G1Qa@9N6_VkZ;1BG=L`pf7sv4pWu7%Yl!nD7Tq*zu~G9=(}9(b zcVoBtw}&pDUSYOuUzE0BbxUbD`=a#f3r?$cilRYfke%Xl{*S@CJI5aU_AULQF@iVg zIb`&+qd9uk>j3nyIuE7d>*yWtL-!QroU^ESr%>dRxDh+V?kqjj-s>|n^fK*}&!D!jkeRp~8UKHk^&+tGD*a8p*QUXH;olkWNXgf zJKb5Cz`{SfaL0Bv`wZW6;knjj3d8B_e^&kg;rDKIG5oRM00000NkvXXu0mjfflaXm literal 0 HcmV?d00001 diff --git a/clearadm/lib/Clearadm.pm b/clearadm/lib/Clearadm.pm new file mode 100644 index 0000000..4814952 --- /dev/null +++ b/clearadm/lib/Clearadm.pm @@ -0,0 +1,2100 @@ +=pod + +=head1 NAME $RCSfile: Clearadm.pm,v $ + +Object oriented interface to Clearadm. + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.54 $ + +=item Created + +Tue Dec 07 09:13:27 EST 2010 + +=item Modified + +$Date: 2012/11/09 06:43:26 $ + +=back + +=head1 SYNOPSIS + +Provides the Clearadm object which handles all interaction with the Clearadm +database. Similar add/change/delete/update methods for other record types. In +general you must orient your record hashs to have the appropriately named +keys that correspond to the database. Also see mothod documentation for +specifics about the method you are envoking. + + # Create new Clearadm object + my $clearadm = new Clearadm; + + # Add a new system + my %system = ( + name => 'jupiter', + alias => 'defaria.com', + admin => 'Andrew DeFaria', + os => 'Linux defaria.com 2.6.32-25-generic-pae #45-Ubuntu SMP Sat Oct 16 21:01:33 UTC 2010 i686 GNU/Linux', + type => 'Linux', + description => 'Home server', + ); + + my ($err, $msg) = $clearadm->AddSystem (%system); + + # Find systems matching 'jup' + my @systems = $clearadm->FindSystem ('jup'); + + # Get a system by name + my %system = $clearadm->GetSystem ('jupiter'); + + # Update system + my %update = ( + 'region' => 'East Coast', + ); + + my ($err, $msg) = $clearadm->UpdateSystem ('jupiter', %update); + + # Delete system (Warning: will delete all related records regarding this + # system). + my ($err, $msg) = $clearadm->DeleteSystem ('jupiter'); + +=head1 DESCRIPTION + +This package provides and object oriented interface to the Clearadm database. +Methods are provided to manipulate records by adding, updating and deleting +them. In general you need to specify a hash which contains keys and values +corresponding to the database field names and values. + +=head1 ROUTINES + +The following methods are available: + +=cut + +package Clearadm; + +use strict; +use warnings; + +use Carp; +use DBI; +use Net::Domain qw(hostdomain); + +use FindBin; + +use lib "$FindBin::Bin", "$FindBin::Bin/../../lib"; + +use DateUtils; +use Display; +use GetConfig; +use Mail; + +our %CLEAROPTS = GetConfig ("$FindBin::Bin/etc/clearadm.conf"); + +# Globals +our $VERSION = '$Revision: 1.54 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +$CLEAROPTS{CLEARADM_USERNAME} = $ENV{CLEARADM_USERNAME} + ? $ENV{CLEARADM_USERNAME} + : $CLEAROPTS{CLEARADM_USERNAME} + ? $CLEAROPTS{CLEARADM_USERNAME} + : 'clearwriter'; +$CLEAROPTS{CLEARADM_PASSWORD} = $ENV{CLEARADM_PASSWORD} + ? $ENV{CLEARADM_PASSWORD} + : $CLEAROPTS{CLEARADM_PASSWORD} + ? $CLEAROPTS{CLEARADM_PASSWORD} + : 'clearwriter'; +$CLEAROPTS{CLEARADM_SERVER} = $ENV{CLEARADM_SERVER} + ? $ENV{CLEARADM_SERVER} + : $CLEAROPTS{CLEARADM_SERVER} + ? $CLEAROPTS{CLEARADM_SERVER} + : 'localhost'; + +my $defaultFilesystemThreshold = 90; +my $defaultFilesystemHist = '6 months'; +my $defaultLoadavgHist = '6 months'; + +# Internal methods +sub _dberror ($$) { + my ($self, $msg, $statement) = @_; + + my $dberr = $self->{db}->err; + my $dberrmsg = $self->{db}->errstr; + + $dberr ||= 0; + $dberrmsg ||= 'Success'; + + my $message = ''; + + if ($dberr) { + my $function = (caller (1)) [3]; + + $message = "$function: $msg\nError #$dberr: $dberrmsg\n" + . "SQL Statement: $statement"; + } # if + + return $dberr, $message; +} # _dberror + +sub _formatValues (@) { + my ($self, @values) = @_; + + my @returnValues; + + # Quote data values + push @returnValues, $_ eq '' ? 'null' : $self->{db}->quote ($_) + foreach (@values); + + return @returnValues; +} # _formatValues + +sub _formatNameValues (%) { + my ($self, %rec) = @_; + + my @nameValueStrs; + + push @nameValueStrs, "$_=" . $self->{db}->quote ($rec{$_}) + foreach (keys %rec); + + return @nameValueStrs; +} # _formatNameValues + +sub _addRecord ($%) { + my ($self, $table, %rec) = @_; + + my $statement = "insert into $table ("; + $statement .= join ',', keys %rec; + $statement .= ') values ('; + $statement .= join ',', $self->_formatValues (values %rec); + $statement .= ')'; + + my ($err, $msg); + + $self->{db}->do ($statement); + + return $self->_dberror ("Unable to add record to $table", $statement); +} # _addRecord + +sub _deleteRecord ($;$) { + my ($self, $table, $condition) = @_; + + my $count; + + my $statement = "select count(*) from $table "; + $statement .= "where $condition" + if $condition; + + my $sth = $self->{db}->prepare ($statement) + or return $self->_dberror ('Unable to prepare statement', $statement); + + $sth->execute + or return $self->_dberror ('Unable to execute statement', $statement); + + my @row = $sth->fetchrow_array; + + $sth->finish; + + if ($row[0]) { + $count = $row[0]; + } else { + $count = 0; + } # if + + return ($count, 'Records deleted') + if $count == 0; + + $statement = "delete from $table "; + $statement .= "where $condition" + if $condition; + + $self->{db}->do ($statement); + + if ($self->{db}->err) { + return $self->_dberror ("Unable to delete record from $table", $statement); + } else { + return $count, 'Records deleted'; + } # if +} # _deleteRecord + +sub _updateRecord ($$%) { + my ($self, $table, $condition, %rec) = @_; + + my $statement = "update $table set "; + $statement .= join ',', $self->_formatNameValues (%rec); + $statement .= " where $condition" + if $condition; + + $self->{db}->do ($statement); + + return $self->_dberror ("Unable to update record in $table", $statement); +} # _updateRecord + +sub _checkRequiredFields ($$) { + my ($fields, $rec) = @_; + + foreach my $fieldname (@$fields) { + my $found = 0; + + foreach (keys %$rec) { + if ($fieldname eq $_) { + $found = 1; + last; + } # if + } # foreach + + return "$fieldname is required" + unless $found; + } # foreach + + return; +} # _checkRequiredFields + +sub _getRecords ($$) { + my ($self, $table, $condition) = @_; + + my ($err, $msg); + + my $statement = "select * from $table where $condition"; + + my $sth = $self->{db}->prepare ($statement); + + unless ($sth) { + ($err, $msg) = $self->_dberror ('Unable to prepare statement', $statement); + + croak $msg; + } # if + + my $attempts = 0; + my $maxAttempts = 3; + my $sleepTime = 30; + my $status; + + # We've been having the server going away. Supposedly it should reconnect so + # here we simply retry up to $maxAttempts times to re-execute the statement. + # (Are there other places where we need to do this?) + $err = 2006; + + while ($err == 2006 and $attempts++ < $maxAttempts) { + $status = $sth->execute; + + if ($status) { + $err = 0; + last; + } else { + ($err, $msg) = $self->_dberror ('Unable to execute statement', + $statement); + } # if + + last if $err == 0; + + croak $msg unless $err == 2006; + + my $timestamp = YMDHMS; + + $self->Error ("$timestamp: Unable to talk to DB server.\n\n$msg\n\n" + . "Will try again in $sleepTime seconds", -1); + + # Try to reconnect + $self->_connect ($self->{dbserver}); + + sleep $sleepTime; + } # while + + $self->Error ("After $maxAttempts attempts I could not connect to the database", $err) + if ($err == 2006 and $attempts > $maxAttempts); + + my @records; + + while (my $row = $sth->fetchrow_hashref) { + push @records, $row; + } # while + + return @records; +} # _getRecord + +sub _aliasSystem ($) { + my ($self, $system) = @_; + + my %system = $self->GetSystem ($system); + + if ($system{name}) { + return $system{name}; + } else { + return; + } # if +} # _aliasSystem + +sub _getLastID () { + my ($self) = @_; + + my $statement = 'select last_insert_id()'; + + my $sth = $self->{db}->prepare ($statement); + + my ($err, $msg); + + unless ($sth) { + ($err, $msg) = $self->_dberror ('Unable to prepare statement', $statement); + + croak $msg; + } # if + + my $status = $sth->execute; + + unless ($status) { + ($err, $msg) = $self->_dberror ('Unable to execute statement', $statement); + + croak $msg; + } # if + + my @records; + + my @row = $sth->fetchrow_array; + + return $row[0]; +} # _getLastID + +sub _connect (;$) { + my ($self, $dbserver) = @_; + + $dbserver ||= $CLEAROPTS{CLEARADM_SERVER}; + + my $dbname = 'clearadm'; + my $dbdriver = 'mysql'; + + $self->{db} = DBI->connect ( + "DBI:$dbdriver:$dbname:$dbserver", + $CLEAROPTS{CLEARADM_USERNAME}, + $CLEAROPTS{CLEARADM_PASSWORD}, + {PrintError => 0}, + ) or croak ( + "Couldn't connect to $dbname database " + . "as $CLEAROPTS{CLEARADM_USERNAME}\@$CLEAROPTS{CLEARADM_SERVER}" + ); + + $self->{dbserver} = $dbserver; + + return; +} # _connect + +sub new (;$) { + my ($class, $dbserver) = @_; + + my $self = bless {}, $class; + + $self->_connect ($dbserver); + + return $self; +} # new + +sub SetNotify () { + my ($self) = @_; + + $self->{NOTIFY} = $CLEAROPTS{CLEARADM_NOTIFY}; + + return; +} # SetNotify + +sub Error ($;$) { + my ($self, $msg, $errno) = @_; + + # If $errno is specified we need to stop. However we need to notify somebody + # that cleartasks is no longer running. + error $msg; + + if ($errno) { + if ($self->{NOTIFY}) { + mail ( + to => $self->{NOTIFY}, + subject => 'Internal error occurred in Clearadm', + data => "

    An unexpected, internal error occurred in Clearadm:

    $msg

    ", + mode => 'html', + ); + + exit $errno if $errno > 0; + } # if + } # if + + return; +} # Error + +sub AddSystem (%) { + my ($self, %system) = @_; + + my @requiredFields = ( + 'name', + ); + + my $result = _checkRequiredFields \@requiredFields, \%system; + + return -1, "AddSystem: $result" + if $result; + + $system{loadavgHist} ||= $defaultLoadavgHist; + + return $self->_addRecord ('system', %system); +} # AddSystem + +sub DeleteSystem ($) { + my ($self, $name) = @_; + + return $self->_deleteRecord ('system', "name='$name'"); +} # DeleteSystem + +sub UpdateSystem ($%) { + my ($self, $name, %update) = @_; + + return $self->_updateRecord ('system', "name='$name'", %update); +} # UpdateSystem + +sub GetSystem ($) { + my ($self, $system) = @_; + + return + unless $system; + + my @records = $self->_getRecords ( + 'system', + "name='$system' or alias like '%$system%'" + ); + + if ($records[0]) { + return %{$records[0]}; + } else { + return; + } # if +} # GetSystem + +sub FindSystem (;$) { + my ($self, $system) = @_; + + $system ||= ''; + + my $condition = "name like '%$system%' or alias like '%$system%'"; + + return $self->_getRecords ('system', $condition); +} # FindSystem + +sub AddPackage (%) { + my ($self, %package) = @_; + + my @requiredFields = ( + 'system', + 'name', + 'version' + ); + + my $result = _checkRequiredFields \@requiredFields, \%package; + + return -1, "AddPackage: $result" + if $result; + + return $self->_addRecord ('package', %package); +} # AddPackage + +sub DeletePackage ($$) { + my ($self, $system, $name) = @_; + + return $self->_deleteRecord ( + 'package', + "(system='$system' or alias='$system') and name='$name'"); +} # DeletePackage + +sub UpdatePackage ($$%) { + my ($self, $system, $name, %update) = @_; + + $system = $self->_aliasSystem ($system); + + return + unless $system; + + return $self->_updateRecord ('package', "system='$system'", %update); +} # UpdatePackage + +sub GetPackage($$) { + my ($self, $system, $name) = @_; + + $system = $self->_aliasSystem ($system); + + return + unless $system; + + return + unless $name; + + my @records = $self->_getRecords ( + 'package', + "system='$system' and name='$name'" + ); + + if ($records[0]) { + return %{$records[0]}; + } else { + return; + } # if +} # GetPackage + +sub FindPackage ($;$) { + my ($self, $system, $name) = @_; + + $name ||= ''; + + $system = $self->_aliasSystem ($system); + + return + unless $system; + + my $condition = "system='$system' and name like '%$name%'"; + + return $self->_getRecords ('package', $condition); +} # FindPackage + +sub AddFilesystem (%) { + my ($self, %filesystem) = @_; + + my @requiredFields = ( + 'system', + 'filesystem', + 'fstype' + ); + + my $result = _checkRequiredFields \@requiredFields, \%filesystem; + + return -1, "AddFilesystem: $result" + if $result; + + # Default filesystem threshold + $filesystem{threshold} ||= $defaultFilesystemThreshold; + + return $self->_addRecord ('filesystem', %filesystem); +} # AddFilesystem + +sub DeleteFilesystem ($$) { + my ($self, $system, $filesystem) = @_; + + $system = $self->_aliasSystem ($system); + + return + unless $system; + + return $self->_deleteRecord ( + 'filesystem', + "system='$system' and filesystem='$filesystem'" + ); +} # DeleteFilesystem + +sub UpdateFilesystem ($$%) { + my ($self, $system, $filesystem, %update) = @_; + + $system = $self->_aliasSystem ($system); + + return + unless $system; + + return $self->_updateRecord ( + 'filesystem', + "system='$system' and filesystem='$filesystem'", + %update + ); +} # UpdateFilesystem + +sub GetFilesystem ($$) { + my ($self, $system, $filesystem) = @_; + + $system = $self->_aliasSystem ($system); + + return + unless $system; + + return + unless $filesystem; + + my @records = $self->_getRecords ( + 'filesystem', + "system='$system' and filesystem='$filesystem'" + ); + + if ($records[0]) { + return %{$records[0]}; + } else { + return; + } # if +} # GetFilesystem + +sub FindFilesystem ($;$) { + my ($self, $system, $filesystem) = @_; + + $filesystem ||= ''; + + $system = $self->_aliasSystem ($system); + + return + unless $system; + + my $condition = "system='$system' and filesystem like '%$filesystem%'"; + + return $self->_getRecords ('filesystem', $condition); +} # FindFilesystem + +sub AddVob (%) { + my ($self, %vob) = @_; + + my @requiredFields = ( + 'system', + 'tag', + ); + + my $result = _checkRequiredFields \@requiredFields, \%vob; + + return -1, "AddVob: $result" + if $result; + + return $self->_addRecord ('vob', %vob); +} # AddVob + +sub DeleteVob ($) { + my ($self, $tag) = @_; + + return $self->_deleteRecord ('vob', "tag='$tag'"); +} # DeleteVob + +sub GetVob ($) { + my ($self, $tag) = @_; + + return + unless $tag; + + my @records = $self->_getRecords ('vob', "tag='$tag'"); + + if ($records[0]) { + return %{$records[0]}; + } else { + return; + } # if +} # GetVob + +sub FindVob ($) { + my ($self, $tag) = @_; + + return $self->_getRecords ('vob', "tag like '%$tag%'"); +} # FindVob + +sub AddView (%) { + my ($self, %view) = @_; + + my @requiredFields = ( + 'system', + 'tag', + ); + + my $result = _checkRequiredFields \@requiredFields, \%view; + + return -1, "AddView: $result" + if $result; + + return $self->_addRecord ('view', %view); +} # AddView + +sub DeleteView ($) { + my ($self, $tag) = @_; + + return $self->_deleteRecord ('vob', "tag='$tag'"); +} # DeleteView + +sub GetView ($) { + my ($self, $tag) = @_; + + return + unless $tag; + + my @records = $self->_getRecords ('view', "tag='$tag'"); + + if ($records[0]) { + return %{$records[0]}; + } else { + return; + } # if +} # GetView + +sub FindView (;$$$$) { + my ($self, $system, $region, $tag, $ownerName) = @_; + + $system ||= ''; + $region ||= ''; + $tag ||= ''; + $ownerName ||= ''; + + my $condition; + + $condition = "system like '%$system%'"; + $condition .= ' and '; + $condition = "region like '%$region%'"; + $condition .= ' and '; + $condition .= "tag like '%$tag'"; + $condition .= ' and '; + $condition .= "ownerName like '%$ownerName'"; + + return $self->_getRecords ('view', $condition); +} # FindView + +sub AddFS (%) { + my ($self, %fs) = @_; + + my @requiredFields = ( + 'system', + 'filesystem', + ); + + my $result = _checkRequiredFields \@requiredFields, \%fs; + + return -1, "AddFS: $result" + if $result; + + # Timestamp record + $fs{timestamp} = Today2SQLDatetime; + + return $self->_addRecord ('fs', %fs); +} # AddFS + +sub TrimFS ($$) { + my ($self, $system, $filesystem) = @_; + + my %filesystem = $self->GetFilesystem ($system, $filesystem); + + return + unless %filesystem; + + my %task = $self->GetTask ('scrub'); + + $self->Error ("Unable to find scrub task!", 1) unless %task; + + my $days; + my $today = Today2SQLDatetime; + + # TODO: SubtractDays uses just an approximation (i.e. subtracting 30 days when + # in February is not right. + if ($filesystem{filesystemHist} =~ /(\d+) month/i) { + $days = $1 * 30; + } elsif ($filesystem{filesystemHist} =~ /(\d+) year/i) { + $days = $1 * 365; + } # if + + my $oldage = SubtractDays $today, $days; + + my ($dberr, $dbmsg) = $self->_deleteRecord ( + 'fs', + "system='$system' and filesystem='$filesystem' and timestamp<='$oldage'" + ); + + if ($dbmsg eq 'Records deleted') { + return (0, $dbmsg) + if $dberr == 0; + + my %runlog; + + $runlog{task} = $task{name}; + $runlog{started} = $today; + $runlog{status} = 0; + $runlog{message} = + "Scrubbed $dberr fs records for filesystem $system:$filesystem"; + + my ($err, $msg) = $self->AddRunlog (%runlog); + + $self->Error ("Unable to add runlog - (Error: $err)\n$msg") if $err; + } # if + + return ($dberr, $dbmsg); +} # TrimFS + +sub TrimLoadavg ($) { + my ($self, $system) = @_; + + my %system = $self->GetSystem ($system); + + return + unless %system; + + my %task = $self->GetTask ('loadavg'); + + $self->Error ("Unable to find loadavg task!", 1) unless %task; + + my $days; + my $today = Today2SQLDatetime; + + # TODO: SubtractDays uses just an approximation (i.e. subtracting 30 days when + # in February is not right. + if ($system{loadavgHist} =~ /(\d+) month/i) { + $days = $1 * 30; + } elsif ($system{loadavgHist} =~ /(\d+) year/i) { + $days = $1 * 365; + } # if + + my $oldage = SubtractDays $today, $days; + + my ($dberr, $dbmsg) = $self->_deleteRecord ( + 'loadavg', + "system='$system' and timestamp<='$oldage'" + ); + + if ($dbmsg eq 'Records deleted') { + return (0, $dbmsg) + if $dberr == 0; + + my %runlog; + + $runlog{task} = $task{name}; + $runlog{started} = $today; + $runlog{status} = 0; + $runlog{message} = + "Scrubbed $dberr loadavg records for system $system"; + + my ($err, $msg) = $self->AddRunlog (%runlog); + + $self->Error ("Unable to add runload (Error: $err)\n$msg") if $err; + } # if + + return ($dberr, $dbmsg); +} # TrimLoadavg + +sub GetFS ($$;$$$$) { + my ($self, $system, $filesystem, $start, $end, $count, $interval) = @_; + + $system = $self->_aliasSystem ($system); + + return + unless $system; + + return + unless $filesystem; + + $interval ||= 'Minute'; + + my $size = $interval =~ /month/i + ? 7 + : $interval =~ /day/i + ? 10 + : $interval =~ /hour/i + ? 13 + : 16; + + undef $start if $start and $start =~ /earliest/i; + undef $end if $end and $end =~ /latest/i; + + my $condition = "system='$system' and filesystem='$filesystem'"; + $condition .= " and timestamp>='$start'" if $start; + $condition .= " and timestamp<='$end'" if $end; + + $condition .= " group by left(timestamp,$size)"; + + if ($count) { + # We can't simply do a "limit 0, $count" as that just gets the front end of + # the records return (i.e. if $count = say 10 and the timestamp range + # returns 40 rows we'll see only rows 1-10, not rows 31-40). We need limit + # $offset, $count where $offset = the number of qualifying records minus + # $count + my $nbrRecs = $self->Count ('fs', $condition); + my $offset = $nbrRecs - $count; + + # Offsets of < 0 are not allowed. + $offset = 0 + if $offset < 0; + + $condition .= " limit $offset, $count"; + } # if + + my $statement = <<"END"; +select + system, + filesystem, + mount, + left(timestamp,$size) as timestamp, + avg(size) as size, + avg(used) as used, + avg(free) as free, + reserve +from + fs + where $condition +END + + my ($err, $msg); + + my $sth = $self->{db}->prepare ($statement); + + unless ($sth) { + ($err, $msg) = $self->_dberror ('Unable to prepare statement', $statement); + + croak $msg; + } # if + + my $status = $sth->execute; + + unless ($status) { + ($err, $msg) = $self->_dberror ('Unable to execute statement', $statement); + + croak $msg; + } # if + + my @records; + + while (my $row = $sth->fetchrow_hashref) { + push @records, $row; + } # while + + return @records; +} # GetFS + +sub GetLatestFS ($$) { + my ($self, $system, $filesystem) = @_; + + $system = $self->_aliasSystem ($system); + + return + unless $system; + + return + unless $filesystem; + + my @records = $self->_getRecords ( + 'fs', + "system='$system' and filesystem='$filesystem'" + . " order by timestamp desc limit 0, 1", + ); + + if ($records[0]) { + return %{$records[0]}; + } else { + return; + } # if +} # GetLatestFS + +sub AddLoadavg () { + my ($self, %loadavg) = @_; + + my @requiredFields = ( + 'system', + ); + + my $result = _checkRequiredFields \@requiredFields, \%loadavg; + + return -1, "AddLoadavg: $result" + if $result; + + # Timestamp record + $loadavg{timestamp} = Today2SQLDatetime; + + return $self->_addRecord ('loadavg', %loadavg); +} # AddLoadavg + +sub GetLoadavg ($;$$$$) { + my ($self, $system, $start, $end, $count, $interval) = @_; + + $system = $self->_aliasSystem ($system); + + return + unless $system; + + $interval ||= 'Minute'; + + my $size = $interval =~ /month/i + ? 7 + : $interval =~ /day/i + ? 10 + : $interval =~ /hour/i + ? 13 + : 16; + + my $condition; + + undef $start if $start and $start =~ /earliest/i; + undef $end if $end and $end =~ /latest/i; + + $condition .= " system='$system'" if $system; + $condition .= " and timestamp>='$start'" if $start; + $condition .= " and timestamp<='$end'" if $end; + + $condition .= " group by left(timestamp,$size)"; + + if ($count) { + # We can't simply do a "limit 0, $count" as that just gets the front end of + # the records return (i.e. if $count = say 10 and the timestamp range + # returns 40 rows we'll see only rows 1-10, not rows 31-40). We need limit + # $offset, $count where $offset = the number of qualifying records minus + # $count + my $nbrRecs = $self->Count ('loadavg', $condition); + my $offset = $nbrRecs - $count; + + # Offsets of < 0 are not allowed. + $offset = 0 + if $offset < 0; + + $condition .= " limit $offset, $count"; + } # if + + my $statement = <<"END"; +select + system, + left(timestamp,$size) as timestamp, + uptime, + users, + avg(loadavg) as loadavg +from + loadavg + where $condition +END + + my ($err, $msg); + + my $sth = $self->{db}->prepare ($statement); + + unless ($sth) { + ($err, $msg) = $self->_dberror ('Unable to prepare statement', $statement); + + croak $msg; + } # if + + my $status = $sth->execute; + + unless ($status) { + ($err, $msg) = $self->_dberror ('Unable to execute statement', $statement); + + croak $msg; + } # if + + my @records; + + while (my $row = $sth->fetchrow_hashref) { + push @records, $row; + } # while + + return @records; +} # GetLoadvg + +sub GetLatestLoadavg ($) { + my ($self, $system) = @_; + + $system = $self->_aliasSystem ($system); + + return + unless $system; + + my @records = $self->_getRecords ( + 'loadavg', + "system='$system'" + . " order by timestamp desc limit 0, 1", + ); + + if ($records[0]) { + return %{$records[0]}; + } else { + return; + } # if +} # GetLatestLoadavg + +sub AddTask (%) { + my ($self, %task) = @_; + + my @requiredFields = ( + 'name', + 'command' + ); + + my $result = _checkRequiredFields \@requiredFields, \%task; + + return -1, "AddTask: $result" + if $result; + + return $self->_addRecord ('task', %task); +} # AddTask + +sub DeleteTask ($) { + my ($self, $name) = @_; + + return $self->_deleteRecord ('task', "name='$name'"); +} # DeleteTask + +sub FindTask ($) { + my ($self, $name) = @_; + + $name ||= ''; + + my $condition = "name like '%$name%'"; + + return $self->_getRecords ('task', $condition); +} # FindTask + +sub GetTask ($) { + my ($self, $name) = @_; + + return + unless $name; + + my @records = $self->_getRecords ('task', "name='$name'"); + + if ($records[0]) { + return %{$records[0]}; + } else { + return; + } # if +} # GetTask + +sub UpdateTask ($%) { + my ($self, $name, %update) = @_; + + return $self->_updateRecord ('task', "name='$name'", %update); +} # Update + +sub AddSchedule (%) { + my ($self, %schedule) = @_; + + my @requiredFields = ( + 'task', + ); + + my $result = _checkRequiredFields \@requiredFields, \%schedule; + + return -1, "AddSchedule: $result" + if $result; + + return $self->_addRecord ('schedule', %schedule); +} # AddSchedule + +sub DeleteSchedule ($) { + my ($self, $name) = @_; + + return $self->_deleteRecord ('schedule', "name='$name'"); +} # DeleteSchedule + +sub FindSchedule (;$$) { + my ($self, $name, $task) = @_; + + $name ||= ''; + $task||= ''; + + my $condition = "name like '%$name%'"; + $condition .= ' and '; + $condition .= "task like '%$task%'"; + + return $self->_getRecords ('schedule', $condition); +} # FindSchedule + +sub GetSchedule ($) { + my ($self, $name) = @_; + + my @records = $self->_getRecords ('schedule', "name='$name'"); + + if ($records[0]) { + return %{$records[0]}; + } else { + return; + } # if +} # GetSchedule + +sub UpdateSchedule ($%) { + my ($self, $name, %update) = @_; + + return $self->_updateRecord ('schedule', "name='$name'", %update); +} # UpdateSchedule + +sub AddRunlog (%) { + my ($self, %runlog) = @_; + + my @requiredFields = ( + 'task', + ); + + my $result = _checkRequiredFields \@requiredFields, \%runlog; + + return -1, "AddRunlog: $result" + if $result; + + $runlog{ended} = Today2SQLDatetime; + + my ($err, $msg) = $self->_addRecord ('runlog', %runlog); + + return ($err, $msg, $self->_getLastID); +} # AddRunlog + +sub DeleteRunlog ($) { + my ($self, $condition) = @_; + + return $self->_deleteRecord ('runlog', $condition); +} # DeleteRunlog + +sub FindRunlog (;$$$$$$) { + my ($self, $task, $system, $status, $id, $start, $page) = @_; + + $task ||= ''; + + # If ID is specified then that's all that really matters as it uniquely + # identifies a runlog entry; + my $condition; + + unless ($id) { + $condition = "task like '%$task%'"; + + if ($system) { + $condition .= " and system like '%$system%'" + unless $system eq 'All'; + } else { + $condition .= ' and system is null'; + } # unless + + if (defined $status) { + if ($status =~ /!(-*\d+)/) { + $condition .= " and status<>$1"; + } else { + $condition .= " and status=$status" + } # if + } # if + + $condition .= " order by started desc"; + + if (defined $start) { + $page ||= 10; + $condition .= " limit $start, $page"; + } # unless + } else { + $condition = "id=$id"; + } # unless + + return $self->_getRecords ('runlog', $condition); +} # FindRunlog + +sub GetRunlog ($) { + my ($self, $id) = @_; + + return + unless $id; + + my @records = $self->_getRecords ('runlog', "id=$id"); + + if ($records[0]) { + return %{$records[0]}; + } else { + return; + } # if +} # GetRunlog + +sub UpdateRunlog ($%) { + my ($self, $id, %update) = @_; + + return $self->_updateRecord ('runlog', "id=$id", %update); +} # UpdateRunlog + +sub Count ($;$) { + my ($self, $table, $condition) = @_; + + $condition = $condition ? 'where ' . $condition : ''; + + my ($err, $msg); + + my $statement = "select count(*) from $table $condition"; + + my $sth = $self->{db}->prepare ($statement); + + unless ($sth) { + ($err, $msg) = $self->_dberror ('Unable to prepare statement', $statement); + + croak $msg; + } # if + + my $status = $sth->execute; + + unless ($status) { + ($err, $msg) = $self->_dberror ('Unable to execute statement', $statement); + + croak $msg; + } # if + + # Hack! Statements such as the following: + # + # select count(*) from fs where system='jupiter' and filesystem='/dev/sdb5' + # > group by left(timestamp,10); + # +----------+ + # | count(*) | + # +----------+ + # | 49 | + # | 98 | + # | 140 | + # | 7 | + # | 74 | + # | 124 | + # | 190 | + # +----------+ + # 7 rows in set (0.00 sec) + # + # Here we want 7 but what we see in $records[0] is 49. So the hack is that if + # statement contains "group by" then we assume we have the above and return + # scalar @records, otherwise we return $records[0]; + if ($statement =~ /group by/i) { + my $allrows = $sth->fetchall_arrayref; + + return scalar @{$allrows}; + } else { + my @records = $sth->fetchrow_array; + + return $records[0]; + } # if +} # Count + +# GetWork returns two items, the number of seconds to wait before the next task +# and array of hash records of work to be done immediately. The caller should +# execute the work to be done, timing it, and subtracting it from the $sleep +# time returned. If the caller exhausts the $sleep time then they should call +# us again. +sub GetWork () { + my ($self) = @_; + + my ($err, $msg); + + my $statement = <<"END"; +select + schedule.name as schedulename, + task.name, + task.system as system, + task.command, + schedule.notification, + frequency, + runlog.started as lastrun +from + task, + schedule left join runlog on schedule.lastrunid=runlog.id +where + schedule.task=task.name + and schedule.active='true' +order by lastrun +END + + my $sth = $self->{db}->prepare ($statement); + + unless ($sth) { + ($err, $msg) = $self->_dberror ('Unable to prepare statement', $statement); + + croak $msg; + } # if + + my $status = $sth->execute; + + unless ($status) { + ($err, $msg) = $self->_dberror ('Unable to execute statement', $statement); + + croak $msg; + } # if + + my $sleep; + my @records; + + while (my $row = $sth->fetchrow_hashref) { + if ($$row{system} !~ /localhost/i) { + my %system = $self->GetSystem ($$row{system}); + + # Skip inactive systems + next if $system{active} eq 'false'; + } # if + + # If started is not defined then this task was never run so run it now. + unless ($$row{lastrun}) { + push @records, $row; + next; + } # unless + + # TODO: Handle frequencies better. + my $seconds; + + if ($$row{frequency} =~ /(\d+) seconds/i) { + $seconds = $1; + } elsif ($$row{frequency} =~ /(\d+) minute/i) { + $seconds = $1 * 60; + } elsif ($$row{frequency} =~ /(\d+) hour/i) { + $seconds = $1 * 60 * 60; + } elsif ($$row{frequency} =~ /(\d+) day/i) { + $seconds= $1 * 60 * 60 * 24; + } else { + warning "Don't know how to handle frequencies like $$row{frequency}"; + next; + } # if + + my $today = Today2SQLDatetime; + my $lastrun = Add ($$row{lastrun}, (seconds => $seconds)); + my $waitTime = DateToEpoch ($lastrun) - DateToEpoch ($today); + + if ($waitTime < 0) { + # We're late - push this onto records and move on + push @records, $row; + } # if + + $sleep ||= $waitTime; + + if ($sleep > $waitTime) { + $sleep = $waitTime; + } # if + } # while + + # Even if there is nothing to do the caller should sleep a bit and come back + # to us. So if it ends up there's nothing past due, and nothing upcoming, then + # sleep for a minute and return here. Somebody may have added a new task next + # time we're called. + if (@records == 0 and not $sleep) { + $sleep = 60; + } # if + + return ($sleep, @records); +} # GetWork + +sub GetUniqueList ($$) { + my ($self, $table, $field) = @_; + + my ($err, $msg); + + my $statement = "select $field from $table group by $field"; + + my $sth = $self->{db}->prepare ($statement); + + unless ($sth) { + ($err, $msg) = $self->_dberror ('Unable to prepare statement', $statement); + + croak $msg; + } # if + + my $status = $sth->execute; + + unless ($status) { + ($err, $msg) = $self->_dberror ('Unable to execute statement', $statement); + + croak $msg; + } # if + + my @values; + + while (my @row = $sth->fetchrow_array) { + if ($row[0]) { + push @values, $row[0]; + } else { + push @values, ''; + } # if + } # foreach + + return @values; +} # GetUniqueList + +sub AddAlert(%) { + my ($self, %alert) = @_; + + my @requiredFields = ( + 'name', + 'type', + ); + + my $result = _checkRequiredFields \@requiredFields, \%alert; + + return -1, "AddAlert: $result" + if $result; + + return $self->_addRecord ('alert', %alert); +} # AddAlert + +sub DeleteAlert ($) { + my ($self, $name) = @_; + + return $self->_deleteRecord ('alert', "name='$name'"); +} # DeleteAlert + +sub FindAlert (;$) { + my ($self, $alert) = @_; + + $alert ||= ''; + + my $condition = "name like '%$alert%'"; + + return $self->_getRecords ('alert', $condition); +} # FindAlert + +sub GetAlert ($) { + my ($self, $name) = @_; + + return + unless $name; + + my @records = $self->_getRecords ('alert', "name='$name'"); + + if ($records[0]) { + return %{$records[0]}; + } else { + return; + } # if +} # GetAlert + +sub SendAlert ($$$$$$$) { + my ( + $self, + $alert, + $system, + $notification, + $subject, + $message, + $to, + $runlogID, + ) = @_; + + my $footing = '

    '; + $footing .= ''; + my $year = (localtime)[5] + 1900; + $footing .= "Clearadm
    "; + $footing .= "Copyright © $year, ClearSCM, Inc. - All rights reserved"; + + my %alert = $self->GetAlert ($alert); + + if ($alert{type} eq 'email') { + my $from = 'Clearadm@' . hostdomain; + + mail ( + from => $from, + to => $to, + subject => "Clearadm Alert: $system: $subject", + mode => 'html', + data => $message, + footing => $footing, + ); + } else { + $self->Error ("Don't know how to send $alert{type} alerts\n" + . "Subject: $subject\n" + . "Message: $message", 1); + } # if + + # Log alert + my %alertlog = ( + alert => $alert, + system => $system, + notification => $notification, + runlog => $runlogID, + timestamp => Today2SQLDatetime, + message => $subject, + ); + + return $self->AddAlertlog (%alertlog); +} # SendAlert + +sub GetLastAlert ($$) { + my ($self, $notification, $system) = @_; + + my $statement = <<"END"; +select + runlog, + timestamp +from + alertlog +where + notification='$notification' + and system='$system' +order by + timestamp desc +limit + 0, 1 +END + + my $sth = $self->{db}->prepare ($statement) + or return $self->_dberror ('Unable to prepare statement', $statement); + + $sth->execute + or return $self->_dberror ('Unable to execute statement', $statement); + + my $alertlog= $sth->fetchrow_hashref; + + $sth->finish; + + if ($alertlog) { + return %$alertlog; + } else { + return; + } # if +} # GetLastAlert + +sub GetLastTaskFailure ($$) { + my ($self, $task, $system) = @_; + + my $statement = <<"END"; +select + id, + ended +from + runlog +where + status <> 0 + and task='$task' + and system='$system' + and alerted='true' +order by + ended desc +limit + 0, 1 +END + + my $sth = $self->{db}->prepare ($statement) + or return $self->_dberror ('Unable to prepare statement', $statement); + + $sth->execute + or return $self->_dberror ('Unable to execute statement', $statement); + + my $runlog= $sth->fetchrow_hashref; + + $sth->finish; + + if ($$runlog{ended}) { + return %$runlog; + } # if + + # If we didn't get any ended in the last call then there's nothing that + # qualified. Still let's return a record (%runlog) that has a valid id so + # that the caller can update that runlog with alerted = 'true'. + $statement = <<"END"; +select + id +from + runlog +where + status <> 0 + and task='$task' + and system='$system' +order by + ended desc +limit + 0, 1 +END + + $sth = $self->{db}->prepare ($statement) + or return $self->_dberror ('Unable to prepare statement', $statement); + + $sth->execute + or return $self->_dberror ('Unable to execute statement', $statement); + + $runlog = $sth->fetchrow_hashref; + + $sth->finish; + + if ($runlog) { + return %$runlog; + } else { + return + } # if +} # GetLastTaskFailure + +sub Notify ($$$$$$) { + my ( + $self, + $notification, + $subject, + $message, + $task, + $system, + $filesystem, + $runlogID, + ) = @_; + + $runlogID = $self->_getLastID + unless $runlogID; + + my ($err, $msg); + + # Update filesystem, if $filesystem was specified + if ($filesystem) { + ($err, $msg) = $self->UpdateFilesystem ( + $system, + $filesystem, ( + notification => $notification, + ), + ); + + $self->Error ("Unable to set notification for filesystem $system:$filesystem " + . "(Status: $err)\n$msg", $err) if $err; + } # if + + # Update system + ($err, $msg) = $self->UpdateSystem ( + $system, ( + notification => $notification, + ), + ); + + my %notification = $self->GetNotification ($notification); + + my %lastnotified = $self->GetLastAlert ($notification, $system); + + if (%lastnotified and $lastnotified{timestamp}) { + my $today = Today2SQLDatetime; + my $lastnotified = $lastnotified{timestamp}; + + if ($notification{nomorethan} =~ /hour/i) { + $lastnotified = Add ($lastnotified, (hours => 1)); + } elsif ($notification{nomorethan} =~ /day/i) { + $lastnotified = Add ($lastnotified, (days => 1)); + } elsif ($notification{nomorethan} =~ /week/i) { + $lastnotified = Add ($lastnotified, (days => 7)); + } elsif ($notification{nomorethan} =~ /month/i) { + $lastnotified = Add ($lastnotified, (month => 1)); + } # if + + # If you want to fake an alert in the debugger just change $diff accordingly + my $diff = Compare ($today, $lastnotified); + + return + if $diff <= 0; + } # if + + my $when = Today2SQLDatetime; + my $nomorethan = lc $notification{nomorethan}; + my %alert = $self->GetAlert ($notification{alert}); + my $to = $alert{who}; + + # If $to is null then this means to send the alert to the admin for the + # machine. + unless ($to) { + if ($system) { + my %system = $self->GetSystem ($system); + + $to = $system{email}; + } else { + # If we don't know what system this error occurred on we'll have to notify + # the "super user" defined as $self->{NOTIFY} (The receiver of last + # resort) + $to = $self->{NOTIFY}; + } # if + } # unless + + unless ($to) { + Error "To undefined"; + } # unless + + $message .= "

    You will receive this alert no more than $nomorethan.

    "; + + ($err, $msg) = $self->SendAlert ( + $notification{alert}, + $system, + $notification{name}, + $subject, + $message, + $to, + $runlogID, + ); + + $self->Error ("Unable to send alert (Status: $err)\n$msg", $err) if $err; + + verbose "Sent alert to $to"; + + # Update runlog to indicate we notified the user for this execution + ($err, $msg) = $self->UpdateRunlog ( + $runlogID, ( + alerted => 'true', + ), + ); + + $self->Error ("Unable to update runlog (Status: $err)\n$msg", $err) if $err; + + return; +} # Notify + +sub ClearNotifications ($$;$) { + my ($self, $system, $filesystem) = @_; + + my ($err, $msg); + + if ($filesystem) { + ($err, $msg) = $self->UpdateFilesystem ( + $system, + $filesystem, (notification => undef), + ); + + error "Unable to clear notification for filesystem $system:$filesystem " + . "(Status: $err)\n$msg", $err + if $err; + + # Check to see any of this system's filesystems have notifications. If none + # then it's save to say we've turned off the last notification for a + # filesystem involved with this system and if $system{notification} was + # 'Filesystem' then we can toggle off the notification on the system too + my $filesystemsAlerted = 0; + + foreach ($self->FindFilesystem ($system)) { + $filesystemsAlerted++ + if $$_{notification}; + } # foreach + + my %system = $self->GetSystem ($system); + + return + unless $system; + + if ($system{notification} and + $system{notification} eq 'Filesystem' and + $filesystemsAlerted == 0) { + ($err, $msg) = $self->UpdateSystem ($system, (notification => undef)); + + $self->Error ("Unable to clear notification for system $system " + . "(Status: $err)\n$msg", $err) if $err; + } # if + } else { + ($err, $msg) = $self->UpdateSystem ($system, (notification => undef)); + + $self->Error ("Unable to clear notification for system $system " + . "(Status: $err)\n$msg", $err) if $err; + } # if + + return; +} # ClearNotifications + +sub SystemAlive (%) { + my ($self, %system) = @_; + + # If we've never heard from this system then we will assume that the system + # has not been set up to run clearagent and has never checked in. In any event + # we cannot say the system died because we've never known it to be alive! + return 1 + unless $system{lastheardfrom}; + + # If a system is not active (may have been temporarily been deactivated) then + # we don't want to turn on the bells and whistles alerting people it's down. + return 1 + if $system{active} eq 'false'; + + my $today = Today2SQLDatetime; + my $lastheardfrom = $system{lastheardfrom}; + + my $tenMinutes = 10 * 60; + + $lastheardfrom = Add ($lastheardfrom, (seconds => $tenMinutes)); + + if (DateToEpoch ($lastheardfrom) < DateToEpoch ($today)) { + $self->UpdateSystem ( + $system{name}, ( + notification => 'Heartbeat' + ), + ); + + return; + } else { + if ($system{notification}) { + $self->UpdateSystem ( + $system{name}, ( + notification => undef + ), + ); + } + return 1; + } # if +} # SystemAlive + +sub UpdateAlert ($%) { + my ($self, $name, %update) = @_; + + return $self->_updateRecord ( + 'alert', + "name='$name'", + %update + ); +} # UpdateAlert + +sub AddAlertlog (%) { + my ($self, %alertlog) = @_; + + my @requiredFields = ( + 'alert', + 'notification', + ); + + my $result = _checkRequiredFields \@requiredFields, \%alertlog; + + return -1, "AddAlertlog: $result" + if $result; + + # Timestamp record + $alertlog{timestamp} = Today2SQLDatetime; + + return $self->_addRecord ('alertlog', %alertlog); +} # AddAlertlog + +sub DeleteAlertlog ($) { + my ($self, $condition) = @_; + + return + unless $condition; + + if ($condition =~ /all/i) { + return $self->_deleteRecord ('alertlog'); + } else { + return $self->_deleteRecord ('alertlog', $condition); + } # if +} # DeleteAlertlog + +sub FindAlertlog (;$$$$$) { + my ($self, $alert, $system, $notification, $start, $page) = @_; + + $alert ||= ''; + $system ||= ''; + $notification ||= ''; + + my $condition = "alert like '%$alert%'"; + $condition .= ' and '; + $condition .= "system like '%$system%'"; + $condition .= ' and '; + $condition .= "notification like '%$notification%'"; + $condition .= " order by timestamp desc"; + + if (defined $start) { + $page ||= 10; + $condition .= " limit $start, $page"; + } # unless + + return $self->_getRecords ('alertlog', $condition); +} # FindAlertLog + +sub GetAlertlog ($) { + my ($self, $alert) = @_; + + return + unless $alert; + + my @records = $self->_getRecords ('alertlog', "alert='$alert'"); + + if ($records[0]) { + return %{$records[0]}; + } else { + return; + } # if +} # GetAlertlog + +sub UpdateAlertlog ($%) { + my ($self, $alert, %update) = @_; + + return $self->_updateRecord ( + 'alertlog', + "alert='$alert'", + %update + ); +} # UpdateAlertlog + +sub AddNotification (%) { + my ($self, %notification) = @_; + + my @requiredFields = ( + 'name', + 'alert', + 'cond' + ); + + my $result = _checkRequiredFields \@requiredFields, \%notification; + + return -1, "AddNotification: $result" + if $result; + + return $self->_addRecord ('notification', %notification); +} # AddNotification + +sub DeleteNotification ($) { + my ($self, $name) = @_; + + return $self->_deleteRecord ('notification', "name='$name'"); +} # DeletePackage + +sub FindNotification (;$$) { + my ($self, $name, $cond, $ordering) = @_; + + $name ||= ''; + + my $condition = "name like '%$name%'"; + $condition .= " and $cond" + if $cond; + + return $self->_getRecords ('notification', $condition); +} # FindNotification + +sub GetNotification ($) { + my ($self, $name) = @_; + + return + unless $name; + + my @records = $self->_getRecords ('notification', "name='$name'"); + + if ($records[0]) { + return %{$records[0]}; + } else { + return; + } # if +} # GetNotification + +sub UpdateNotification ($%) { + my ($self, $name, %update) = @_; + + return $self->_updateRecord ( + 'notification', + "name='$name'", + %update + ); +} # UpdateNotification + +1; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + DateUtils + Display + GetConfig + Mail + +=end man + +=begin html + +
    +DateUtils
    +Display
    +GetConfig
    +Mail
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this module + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut diff --git a/clearadm/lib/ClearadmWeb.pm b/clearadm/lib/ClearadmWeb.pm new file mode 100644 index 0000000..6abf742 --- /dev/null +++ b/clearadm/lib/ClearadmWeb.pm @@ -0,0 +1,2758 @@ +=pod + +=head1 NAME $RCSfile: ClearadmWeb.pm,v $ + +Common routines for the web portion of Clearadm + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.46 $ + +=item Created + +Sat Dec 18 08:43:27 EST 2010 + +=item Modified + +$Date: 2011/12/26 19:00:58 $ + +=back + +=head1 SYNOPSIS + +This module holds common web routines for the web portion of Clearadm. + +=head1 DESCRIPTION + +To be filled out. + +=head1 ROUTINES + +The following routines are exported: + +=cut + +package ClearadmWeb; + +use warnings; +use strict; + +use base 'Exporter'; + +use CGI qw ( + :standard + start_a + end_a + start_div + end_div + start_li + end_li + start_table + end_table + start_td + end_td + start_Tr + end_Tr + start_ul + end_ul +); + +use Carp; +use File::Basename; + +use FindBin; + +use lib "$FindBin::Bin/../../lib"; + +use Clearadm; +use DateUtils; +use Display; +use Utils; + +my $clearadm = Clearadm->new; + +our $APPNAME= 'Clearadm'; +our $VERSION = '$Revision: 1.46 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +our @EXPORT = qw ( + autoScale + displayError + displayAlert + displayAlertlog + displayFilesystem + displayNotification + displayRunlog + displaySchedule + displaySystem + displayTask + dbug + dumpVars + editAlert + editFilesystem + editNotification + editSchedule + editSystem + editTask + footing + graphError + heading + makeAlertDropdown + makeFilesystemDropdown + makeIntervalDropdown + makeNotificationDropdown + makeSystemDropdown + makeTimeDropdown + makeTaskDropdown + setField + setFields +); + +our @PREDEFINED_ALERTS = ( + 'Email admin', +); + +our @PREDEFINED_NOTIFICATIONS = ( + 'Loadavg', + 'Filesystem', + 'Scrub', + 'Heartbeat', + 'System checkin', + 'Update systems', +); + +our @PREDEFINED_TASKS = ( + 'Loadavg', + 'Filesystem', + 'Scrub', + 'System checkin', + 'Update systems', +); + +our @PREDEFINED_SCHEDULES = ( + 'Loadavg', + 'Filesystem', + 'Scrub', + 'Update systems', +); + +our @PREDEFINED_NOTMORETHAN = ( + 'Once an hour', + 'Once a day', + 'Once a week', + 'Once a month', +); + +our @PREDEFINED_MULTIPLIERS = ( + 'Seconds', + 'Minutes', + 'Hours', + 'Days', +); + +sub dbug ($) { + my ($msg) = @_; + + display font ({-class => 'error'}, '
    DEBUG: '). $msg; + + return; +} # dbug + +sub displayError ($) { + my ($msg) = @_; + + display font ({-class => 'error'}, 'Error: ') . $msg; + + return +} # displayError; + +sub setField ($;$) { + my ($field, $label) = @_; + + $label ||= 'Unknown'; + + my $undef = font {-class => 'unknown'}, $label; + + return defined $field ? $field : $undef; +} # setField + +sub setFields ($%) { + my ($label, %rec) = @_; + + $rec{$_} = setField ($rec{$_}, $label) + foreach keys %rec; + + return %rec; +} # setFields; + +sub dumpVars (%) { + my (%vars) = @_; + + foreach (keys %vars) { + dbug "$_: $vars{$_}"; + } # foreach + + return; +} # dumpVars + +sub graphError ($) { + my ($msg) = @_; + + use GD; + + # Make the image fit the message. It seems that characters are ~ 7px wide. + my $imageLength = length ($msg) * 7; + + my $errorImage = GD::Image->new ($imageLength, 20); + + # Allocate some colors + my $white = $errorImage->colorAllocate (255, 255, 255); + my $red = $errorImage->colorAllocate (255, 0, 0); + + # Allow the text to shine through + $errorImage->transparent($white); + $errorImage->interlaced('true'); + + # Now put out the message + $errorImage->string (gdMediumBoldFont, 0, 0, $msg, $red); + + # And return it + print "Content-type: image/png\n\n"; + print $errorImage->png; + + # Since we've "returned" the error in the form of an image, there's nothing + # left for us to do so we can exit + exit; +} # graphError + +sub autoScale ($) { + my ($amount) = @_; + + my $kbyte = 1024; + my $meg = (1024 * $kbyte); + my $gig = (1024 * $meg); + + my $size = $amount > $gig + ? sprintf ('%.2f Gig', $amount / $gig) + : $amount > $meg + ? sprintf ('%.2f Meg', $amount / $meg) + : sprintf ('%.2f Kbyte', $amount / $kbyte); + + return $size; +} # autoScale + +sub _makeAlertlogSelection ($$) { + my ($name, $default) = @_; + + $default ||= 'All'; + + my %values; + + $values{All} = 'All'; + + $values{$$_{$name}} = $$_{$name} + foreach ($clearadm->FindAlertlog); + + my $dropdown = popup_menu { + name => $name, + class => 'dropdown', + values => [sort keys %values], + default => $default, + }; + + return $dropdown; +} # _makeAlertlogSelection + +sub _makeRunlogSelection ($$) { + my ($name, $default) = @_; + + $default ||= 'All'; + + my @values = sort $clearadm->GetUniqueList ('runlog', $name); + + unshift @values, 'All'; + + my %values; + + foreach (@values) { + unless ($_ eq '') { + $values{$_} = $_; + } else { + $values{NULL} = ''; + } #if + } # foreach + + my $dropdown = popup_menu { + name => $name, + class => 'dropdown', + values => \@values, + default => $default, + labels => \%values, + }; + + return $dropdown; +} # _makeRunlogSelection + +sub _makeRunlogSelectionNumeric ($$) { + my ($name, $default) = @_; + + $default ||= 'All'; + + my @values = sort {$a <=> $b} $clearadm->GetUniqueList ('runlog', $name); + + unshift @values, 'All'; + + my $dropdown = popup_menu { + name => $name, + class => 'dropdown', + values => [@values], + default => $default, + }; + + return $dropdown; +} # _makeRunlogSelection + +sub makeAlertDropdown (;$$) { + my ($label, $default) = @_; + + $label ||= ''; + + my @values; + + push @values, $$_{name} + foreach ($clearadm->FindAlert); + + my $dropdown = "$label "; + $dropdown .= popup_menu { + name => 'alert', + class => 'dropdown', + values => [sort @values], + default => $default, + }; + + return $dropdown; +} # makeAlertDropdown + +sub makeMultiplierDropdown (;$$) { + my ($label, $default) = @_; + + $label ||= ''; + + my $dropdown = "$label "; + $dropdown .= popup_menu { + name => 'multiplier', + class => 'dropdown', + values => [sort @PREDEFINED_MULTIPLIERS], + default => $default, + }; + + return $dropdown; +} # makeMultiplierDropdown + +sub makeNoMoreThanDropdown (;$$) { + my ($label, $default) = @_; + + $label ||= ''; + + my $dropdown = "$label "; + $dropdown .= popup_menu { + name => 'nomorethan', + class => 'dropdown', + values => [sort @PREDEFINED_NOTMORETHAN], + default => $default, + }; + + return $dropdown; +} # makeNoMorThanDropdown + +sub makeFilesystemDropdown ($;$$$) { + my ($system, $label, $default, $onchange) = @_; + + $label ||= ''; + + my %filesystems; + + foreach ($clearadm->FindFilesystem ($system)) { + my %filesystem = %{$_}; + + my $value = "$filesystem{filesystem} ($filesystem{mount})"; + + $filesystems{$filesystem{filesystem}} = $value; + } # foreach + + my $dropdown .= "$label "; + $dropdown .= popup_menu { + name => 'filesystem', + class => 'dropdown', + values => [sort keys %filesystems], + labels => \%filesystems, + onChange => ($onchange) ? $onchange : '', + default => $default, + }; + + return span {id => 'filesystems'}, $dropdown; +} # makeFilesystemDropdown + +sub makeIntervalDropdown (;$$$) { + my ($label, $default, $onchange) = @_; + + $label ||= ''; + + my @intervals = ( + 'Minute', + 'Hour', + 'Day', + 'Month', + ); + + $default = ucfirst lc $default + if $default; + + my $dropdown = "$label "; + $dropdown .= popup_menu { + name => 'scaling', + id => 'scalingFactor', + class => 'dropdown', + values => [@intervals], + default => $default, + onchange => $onchange, + }; + + return span {id => 'scaling'}, $dropdown; +} # makeIntervalDropdown; + +sub makeNotificationDropdown (;$$) { + my ($label, $default) = @_; + + $label ||= ''; + + my @values; + + push @values, $$_{name} + foreach ($clearadm->FindNotification); + + my $dropdown = "$label "; + $dropdown .= popup_menu { + name => 'notification', + class => 'dropdown', + values => [sort @values], + default => $default, + }; + + return $dropdown; +} # makeNotificationDropdown + +sub makeRestartableDropdown (;$$) { + my ($label, $default) = @_; + + $label ||= ''; + + my @values = ( + 'true', + 'false', + ); + + my $dropdown = "$label "; + $dropdown .= popup_menu { + name => 'restartable', + class => 'dropdown', + values => [@values], + default => $default, + }; + + return $dropdown; +} # makeRestartableDropdown + +sub makeSystemDropdown (;$$$%) { + my ($label, $default, $onchange, %systems) = @_; + + $label ||= ''; + + foreach ($clearadm->FindSystem) { + my %system = %{$_}; + + my $value = $system{name}; + $value .= $system{alias} ? " ($system{alias})" : ''; + + $systems{$system{name}} = $value; + } # foreach + + my $systemDropdown .= "$label "; + $systemDropdown .= popup_menu { + name => 'system', + class => 'dropdown', + values => [sort keys %systems], + labels => \%systems, + onchange => ($onchange) ? $onchange : '', + default => $default, + }; + + return span {id => 'systems'}, $systemDropdown; +} # makeSystemDropdown + +sub makeTaskDropdown (;$$) { + my ($label, $default) = @_; + + $label ||= ''; + + my @values; + + push @values, $$_{name} + foreach ($clearadm->FindTask); + + my $taskDropdown = "$label "; + $taskDropdown .= popup_menu { + name => 'task', + class => 'dropdown', + values => [sort @values], + default => $default, + }; + + return $taskDropdown; +} # makeTaskDropdown + +sub makeTimeDropdown ($$$;$$$$$) { + my ( + $table, + $elementID, + $system, + $filesystem, + $label, + $default, + $interval, + $name, + ) = @_; + + $label ||= ''; + + my @times; + + $name ||= lc $label; + + push @times, 'Earliest'; + + if ($table =~ /loadavg/i) { + push @times, $$_{timestamp} + foreach ($clearadm->GetLoadavg ($system, undef, undef, undef, $interval)); + } elsif ($table =~ /filesystem/i) { + push @times, $$_{timestamp} + foreach ($clearadm->GetFS ($system, $filesystem, undef, undef, undef, $interval)); + } # if + + push @times, 'Latest'; + + unless ($default) { + $default = $name eq 'start' ? 'Earliest' : 'Latest'; + } # unless + + my $timeDropdown = "$label "; + $timeDropdown .= span {id => $elementID}, popup_menu { + name => $name, + class => 'dropdown', + values => [@times], + default => $default, + }; + + return $timeDropdown; +} # makeTimeDropdown + +sub heading (;$$) { + my ($title, $type) = @_; + + if ($title) { + $title = "$APPNAME: $title"; + } else { + $title = $APPNAME; + } # if + + display header; + display start_html { + -title => $title, + -author => 'Andrew DeFaria ', + -meta => { + keywords => 'ClearSCM Clearadm', + copyright => 'Copyright (c) ClearSCM, Inc. 2010, All rights reserved', + }, + -script => [{ + -language => 'JavaScript', + -src => 'clearadm.js', + }], + -style => ['clearadm.css', 'clearmenu.css'], + }, $title; + + return if $type; + + my $ieTableWrapStart = ''; + my $ieTableWrapEnd = ''; + + # Menubar + display div {id=>'mastheadlogo'}, h1 {class => 'title'}, $APPNAME; + display start_div {class => 'menu'}; + + # Home + display ul li a {href => '/clearadm'}, 'Home'; + + my @allSystems = $clearadm->FindSystem; + + # Systems + display start_ul; + display start_li; + display a {href => 'systems.cgi'}, "Systems$ieTableWrapStart"; + display start_ul; + foreach (@allSystems) { + my %system = %{$_}; + my $sysName = ucfirst $system{name}; + $sysName .= " ($system{alias})" + if $system{alias}; + + display li a { + href => "systemdetails.cgi?system=$system{name}" + }, ucfirst " $sysName"; + } # foreach + display end_ul; + display $ieTableWrapEnd; + display end_li; + display end_li; + display end_ul; + + # Filesystems + display start_ul; + display start_li; + display a {href => 'filesystems.cgi'}, "Filesystems$ieTableWrapStart"; + display start_ul; + foreach (@allSystems) { + my %system = %{$_}; + my $sysName = ucfirst $system{name}; + $sysName .= " ($system{alias})" + if $system{alias}; + + display li a { + href => "filesystems.cgi?system=$system{name}" + }, ucfirst " $sysName"; + } # foreach + display end_ul; + display $ieTableWrapEnd; + display end_li; + display end_ul; + + # Servers + display start_ul; + display start_li; + display a {href => '#'}, "Servers$ieTableWrapStart"; + display start_ul {class => 'skinny'}; + display start_li; + display start_a {href => 'vobs.cgi'}; + display "VOB»$ieTableWrapStart"; + display start_ul; + display li a {href => "systemdetails.cgi?system=jupiter"}, ' Jupiter (defaria.com)'; + display end_ul; + display $ieTableWrapEnd; + display end_li; + + display start_li; + display start_a {href => 'views.cgi'}; + display "View»$ieTableWrapStart"; + display start_ul; + display li a {href => "systemdetails.cgi?system=earth"}, ' Earth'; + display li a {href => "systemdetails.cgi?system=mars"}, ' Mars'; + display end_ul; + display $ieTableWrapEnd; + display end_ul; + display $ieTableWrapEnd; + display end_li; + display end_ul; + + # Vobs + display start_ul; + display start_li; + display a {href => 'vobs.cgi'}, "VOBs$ieTableWrapStart"; + display start_ul; + display li a {href => '#'}, ' /vobs/clearscm'; + display li a {href => '#'}, ' /vobs/clearadm'; + display li a {href => '#'}, ' /vobs/test'; + display li a {href => '#'}, ' /vobs/test2'; + display end_ul; + display $ieTableWrapEnd; + display end_li; + display end_ul; + + # Views + display start_ul; + display start_li; + display a {href => 'views.cgi'}, "Views$ieTableWrapStart"; + display start_ul; + display li a {href => 'viewager.cgi'}, ' View Ager'; + display li a {href => '#'}, ' Releast View'; + display end_ul; + display $ieTableWrapEnd; + display end_li; + display end_ul; + + # Configure + display start_ul; + display start_li; + display a {href => '#'}, "Configure$ieTableWrapStart"; + display start_ul; + display li a {href => 'alerts.cgi'}, ' Alerts'; + display li a {href => 'notifications.cgi'}, ' Notifications'; + display li a {href => 'schedule.cgi'}, ' Schedule'; + display li a {href => 'tasks.cgi'}, ' Tasks'; + display end_ul; + display $ieTableWrapEnd; + display end_li; + display end_ul; + + # Logs + display start_ul; + display start_li; + display a {href => '#'}, "Logs$ieTableWrapStart"; + display start_ul; + display li a {href => 'alertlog.cgi'}, ' Alert'; + display li a {href => 'runlog.cgi'}, ' Run'; + display end_ul; + display $ieTableWrapEnd; + display end_li; + display end_ul; + + # Help + display start_ul; + display start_li; + display a {href => '#'}, "Help$ieTableWrapStart"; + display start_ul {class => 'rightmenu'}; + display li a {href => 'readme.cgi'}, " About: $APPNAME $VERSION"; + display end_ul; + display $ieTableWrapEnd; + display end_li; + display end_ul; + display end_div; + + display start_div {class => 'page'}; + + return; +} # heading + +sub displayAlert (;$) { + my ($alert) = @_; + + display start_table {cellspacing => 1}; + + display start_Tr; + display th {class => 'labelCentered'}, 'Actions'; + display th {class => 'labelCentered'}, 'Name'; + display th {class => 'labelCentered'}, 'Type'; + display th {class => 'labelCentered'}, 'Who'; + display th {class => 'labelCentered'}, 'Category'; + display end_Tr; + + foreach ($clearadm->FindAlert ($alert)) { + my %alert = %{$_}; + + $alert{who} = setField $alert{who}, 'System Administrator'; + + display start_Tr; + my $areYouSure = "Are you sure you want to delete the $alert{name} alert?"; + + my $actions = start_form { + method => 'post', + action => 'processalert.cgi', + }; + + $actions .= input { + name => 'name', + type => 'hidden', + value => $alert{name}, + }; + + if (InArray $alert{name}, @PREDEFINED_ALERTS) { + $actions .= input { + name => 'delete', + disabled => 'true', + type => 'image', + src => 'delete.png', + alt => 'Delete', + value => 'Delete', + title => 'Cannot delete predefined alert', + }; + $actions .= input { + name => 'edit', + disabled => 'true', + type => 'image', + src => 'edit.png', + alt => 'Edit', + value => 'Edit', + title => 'Cannot edit predefined alert', + }; + } else { + $actions .= input { + name => 'delete', + type => 'image', + src => 'delete.png', + alt => 'Delete', + value => 'Delete', + title => 'Delete', + onclick => "return AreYouSure ('$areYouSure');", + }; + $actions .= input { + name => 'edit', + type => 'image', + src => 'edit.png', + alt => 'Edit', + value => 'Edit', + title => 'Edit', + }; + } # if + + display end_form; + + my $who = $alert{who}; + + if ($who =~ /^([a-zA-Z0-9._-]+)@([a-zA-Z0-9.-]+\.[a-zA-Z]{2,4})$/) { + $who = a {href => "mailto:$1\@$2"}, $who; + } # if + + display td {class => 'dataCentered'}, $actions; + display td {class => 'data'}, $alert{name}; + display td {class => 'data'}, $alert{type}; + display td {class => 'data'}, $who; + display td {class => 'data'}, + (InArray $alert{name}, @PREDEFINED_ALERTS) ? 'Predefined' : 'User Defined'; + display end_Tr; + } # foreach + + display end_table; + + display p {class => 'center'}, a { + href => 'processalert.cgi?action=Add', + }, 'New alert ', img { + src => 'add.png', + border => 0, + }; + + return; +} # DisplayAlerts + +sub displayAlertlog (%) { + my (%opts) = @_; + + my $optsChanged; + + unless (($opts{oldalert} and $opts{alert} and + $opts{oldalert} eq $opts{alert}) and + ($opts{oldsystem} and $opts{system} and + $opts{oldsystem} eq $opts{system}) and + ($opts{oldnotification} and $opts{notification} and + $opts{oldnotification} eq $opts{notification})) { + $optsChanged = 1; + } # unless + + my $condition; + + unless ($opts{id}) { + $condition = "alert like '%"; + $condition .= $opts{alert} ? $opts{alert} : ''; + $condition .= "%'"; + $condition .= ' and '; + $condition .= "system like '%"; + $condition .= $opts{system} ? $opts{system} : ''; + $condition .= "%'"; + $condition .= ' and '; + $condition .= "notification like '%"; + $condition .= $opts{notification} ? $opts{notification} : ''; + $condition .= "%'"; + } # unless + + my $total = $clearadm->Count ('alertlog', $condition); + + if ($opts{'nextArrow.x'}) { + $opts{start} = $opts{next}; + } elsif ($opts{'prevArrow.x'}) { + $opts{start} = $opts{prev}; + } else { + $opts{start} = 0; + } # if + + my $next = $opts{start} + $opts{page} < $total + ? $opts{start} + $opts{page} + : $opts{start}; + my $prev = $opts{start} - $opts{page} >= 0 + ? $opts{start} - $opts{page} + : $opts{start}; + + my $opts = $opts{start} + 1; + $opts .= '-'; + $opts .= $opts{start} + $opts{page} < $total + ? $opts{start} + $opts{page} + : $total; + $opts .= " of $total"; + + display start_form { + method => 'post', + action => 'alertlog.cgi' + }; + + # Hidden fields to pass along + display input {name => 'prev', type => 'hidden', value => $prev}; + display input {name => 'next', type => 'hidden', value => $next}; + + display input { + name => 'oldalert', + type => 'hidden', + value => $opts{alert}, + }; + display input { + name => 'oldsystem', + type => 'hidden', + value => $opts{system}, + }; + display input { + name => 'oldnotification', + type => 'hidden', + value => $opts{notification}, + }; + + my $caption = start_table { + class => 'caption', + cellspacing => 1, + width => '100%', + }; + + $caption .= start_Tr; + + unless ($opts{id}) { + $caption .= td {align => 'left'}, input { + name => 'prevArrow', + type => 'image', + src => 'left.png', + alt => 'Previous', + value => 'prev', + }; + } else { + $caption .= td {align => 'left'}, img { + src => 'left.png', + disabled => 'disabled', + }; + } # unless + + $caption .= td {align => 'center'}, $opts; + + unless ($opts{id}) { + $caption .= td {align => 'right'}, input { + name => 'nextArrow', + type => 'image', + src => 'right.png', + alt => 'Next', + value => 'next', + }; + } else { + $caption .= td {align => 'right'}, img { + src => 'right.png', + disabled => 'disabled', + }; + } # unless + + $caption .= end_Tr; + + $caption .= end_table; + + display start_table {cellspacing => 1, width => '98%'}; + + display caption $caption; + + display start_Tr; + display th {class => 'labelCentered'}, '#'; + display th {class => 'labelCentered'}, 'Delete'; + display th {class => 'labelCentered'}, 'Name'; + display th {class => 'labelCentered'}, 'System'; + display th {class => 'labelCentered'}, 'Notification'; + display th {class => 'labelCentered'}, 'Date/Time'; + display th {class => 'labelCentered'}, 'Runlog'; + display th {class => 'labelCentered'}, 'Message'; + display end_Tr; + + display start_Tr; + display td { + class => 'filter', + align => 'right', + colspan => 2, + }, b 'Filter:'; + display td { + class => 'filter' + }, _makeAlertlogSelection ('alert', $opts{alert}); + display td { + class => 'filter' + }, _makeAlertlogSelection ('system', $opts{system}); + display td { + class => 'filter' + }, _makeAlertlogSelection ('notification', $opts{notification}); + display td { + class => 'filter', + }, input { + type => 'submit', + value => 'Update', + }; + display end_form; + display td { + class => 'filter', + align => 'center', + colspan => 2, + # TODO: Would like to have Clear All Alerts be Clear Alerts and for it to + # clear only the alerts that have been filtered. + }, a { + href => 'deletealertlog.cgi?alertlogid=all' + }, input { + type => 'button', + value => 'Clear All Events', + onclick => "return AreYouSure('Are you sure you want to delete all alerts?');", + }; + display end_Tr; + + my $i = $opts{start}; + + foreach ($clearadm->FindAlertlog ( + $opts{alert}, + $opts{system}, + $opts{notification}, + $opts{start}, + $opts{page}, + )) { + my %alertlog = setFields 'N/A', %{$_}; + + display start_Tr; + my %system = $clearadm->GetSystem ($alertlog{system}); + + display td {class => 'dataCentered'}, ++$i; + display td {class => 'dataCentered'}, a { + href => "deletealertlog.cgi?alertlogid=$alertlog{id}" + }, img { + src => 'delete.png', + alt => 'Delete', + title => 'Delete', + border => 0, + onclick => "return AreYouSure ('Are you sure you wish to delete this alertlog entry?');", + }; + display td {class => 'data'}, a { + href => "alerts.cgi?alert=$alertlog{alert}" + }, $alertlog{alert}; + display td {class => 'data'}, a { + href => "systemdetails.cgi?system=$alertlog{system}" + }, $alertlog{system}; + display td {class => 'data'}, a { + href => "notifications.cgi?notification=$alertlog{notification}" + }, $alertlog{notification}; + display td {class => 'data'}, $alertlog{timestamp}; + display td {class => 'dataCentered'}, a { + href => "runlog.cgi?id=$alertlog{runlog}" + }, $alertlog{runlog}; + display td {class => 'data'}, $alertlog{message}; + display end_Tr; + } # foreach + + display end_form; + + display end_table; + + return; +} # displayAlertlog + +sub displayFilesystem ($) { + my ($systemName) = @_; + + display start_table {cellspacing => 1, width => '98%'}; + + display start_Tr; + display th {class => 'labelCentered'}, 'Action'; + display th {class => 'labelCentered'}, 'Name'; + display th {class => 'labelCentered'}, 'Alias'; + display th {class => 'labelCentered'}, 'Admin'; + display th {class => 'labelCentered'}, 'Filesystem'; + display th {class => 'labelCentered'}, 'Mount'; + display th {class => 'labelCentered'}, 'Type'; + display th {class => 'labelCentered'}, 'History'; + display th {class => 'labelCentered'}, 'Used'; + display th {class => 'labelCentered'}, 'Threshold'; + display th {class => 'labelCentered'}, 'Usage'; + display end_Tr; + + foreach ($clearadm->FindSystem ($systemName)) { + my %system = %{$_}; + + %system = setFields ('N/A', %system); + + my $admin = ($system{email} !~ 'N/A') + ? a {-href => "mailto:$system{email}"}, $system{admin} + : $system{admin}; + + foreach ($clearadm->FindFilesystem ($system{name})) { + my %filesystem = %{$_}; + + my %fs = $clearadm->GetLatestFS ($system{name}, $filesystem{filesystem}); + + my $size = autoScale $fs{size}; + my $used = autoScale $fs{used}; + my $free = autoScale $fs{free}; + + # TODO: Note that this percentages does not agree with df output. I'm not + # sure why. + my $usedPct = $fs{size} == 0 ? 0 + : sprintf ('%.0f', + (($fs{reserve} + $fs{used}) / $fs{size} * 100)); + + my $alias = ($system{alias} !~ 'N/A') + ? a { + href => "systemdetails.cgi?system=$system{name}" + }, $system{alias} + : $system{alias}; + + my $class = $usedPct < $filesystem{threshold} + ? 'data' + : 'dataAlert'; + my $classRight = $usedPct < $filesystem{threshold} + ? 'dataRight' + : 'dataRightAlert'; + my $classCentered = $usedPct < $filesystem{threshold} + ? 'dataCentered' + : 'dataCenteredAlert'; + my $classRightTop = $usedPct < $filesystem{threshold} + ? 'dataRightTop' + : 'dataRightAlertTop'; + + display start_Tr; + display start_td {class => 'dataCentered'}; + + my $areYouSure = 'Are you sure you want to delete ' + . "$system{name}:$filesystem{filesystem}?" . '\n' + . 'Doing so will remove all records related to this\n' + . 'filesystem and its history.'; + + display start_form { + method => 'post', + action => "processfilesystem.cgi", + }; + + display input { + type => 'hidden', + name => 'system', + value => $system{name}, + }; + display input { + type => 'hidden', + name => 'filesystem', + value => $filesystem{filesystem}, + }; + + display input { + name => 'delete', + type => 'image', + src => 'delete.png', + alt => 'Delete', + value => 'Delete', + title => 'Delete', + onclick => "return AreYouSure ('$areYouSure');" + }; + display input { + name => 'edit', + type => 'image', + src => 'edit.png', + alt => 'Edit', + value => 'Edit', + title => 'Edit', + }; + + if ($filesystem{notification}) { + display a { + href => "alertlog.cgi?system=$filesystem{system}"}, img { + src => 'alert.png', + border => 0, + alt => 'Alert!', + title => 'This filesystem has alerts', + }; + } # if + + display end_form; + + display end_td; + display td {class => $class}, + a {-href => "systemdetails.cgi?system=$system{name}"}, $system{name}; + display td {class => $class}, $alias; + display td {class => $class}, $admin; + display td {class => $class}, $filesystem{filesystem}; + display td {class => $class}, $filesystem{mount}; + display td {class => $class}, $filesystem{fstype}; + display td {class => $classCentered}, $filesystem{filesystemHist}; + display td {class => $classRightTop}, "$used ($usedPct%)
    ", + font {class => 'unknown'}, "$fs{timestamp}"; + display td {class => $classRightTop}, "$filesystem{threshold}%"; + display td {class => $class}, + a {href => + "plot.cgi?type=filesystem&system=$system{name}" + . "&filesystem=$filesystem{filesystem}&scaling=Day&points=7" + }, img { + src => "plotfs.cgi?system=$system{name}" + . "&filesystem=$filesystem{filesystem}&tiny=1", + border => 0, + }; + display end_Tr; + } # foreach + } # foreach + + display end_table; + + return; +} # displayFilesystem + +sub displayNotification (;$) { + my ($notification) = @_; + + display start_table {cellspacing => 1}; + + display start_Tr; + display th {class => 'labelCentered'}, 'Actions'; + display th {class => 'labelCentered'}, 'Name'; + display th {class => 'labelCentered'}, 'Alert'; + display th {class => 'labelCentered'}, 'Condition'; + display th {class => 'labelCentered'}, 'Not More Than'; + display th {class => 'labelCentered'}, 'Category'; + display end_Tr; + + foreach ($clearadm->FindNotification ($notification)) { + my %notification= setFields 'N/A', %{$_}; + + display start_Tr; + my $areYouSure = "Are you sure you want to delete the $notification{name} " + . 'notification?'; + + my $actions = start_form { + method => 'post', + action => 'processnotification.cgi', + }; + + $actions .= input { + name => 'name', + type => 'hidden', + value => $notification{name}, + }; + + if (InArray $notification{name}, @PREDEFINED_NOTIFICATIONS) { + $actions .= input { + name => 'delete', + disabled => 'true', + type => 'image', + src => 'delete.png', + alt => 'Delete', + value => 'Delete', + title => 'Cannot delete predefined notification', + }; + $actions .= input { + name => 'edit', + disabled => 'true', + type => 'image', + src => 'edit.png', + alt => 'Edit', + value => 'Edit', + title => 'Cannot edit predefined notification', + }; + } else { + $actions .= input { + name => 'delete', + type => 'image', + src => 'delete.png', + alt => 'Delete', + value => 'Delete', + title => 'Delete', + onclick => "return AreYouSure ('$areYouSure');", + }; + $actions .= input { + name => 'edit', + type => 'image', + src => 'edit.png', + alt => 'Edit', + value => 'Edit', + title => 'Edit', + }; + } # if + + display end_form; + + display td {class => 'dataCentered'}, $actions; + display td {class => 'data'}, $notification{name}; + display td {class => 'data'}, a { + href => "alerts.cgi?alert=$notification{alert}" + }, $notification{alert}; + display td {class => 'data'}, $notification{cond}; + display td {class => 'data'}, $notification{nomorethan}; + display td {class => 'data'}, + (InArray $notification{name}, @PREDEFINED_NOTIFICATIONS) + ? 'Predefined' + : 'User Defined'; + + display end_Tr; + } # foreach + + display end_table; + + display p {class => 'center'}, a { + href => 'processnotification.cgi?action=Add', + }, 'New notification', img { + src => 'add.png', + border => 0, + }; + + return; +} # displayNotification + +sub displayRunlog (%) { + my (%opts) = @_; + + my $optsChanged; + + unless (($opts{oldtask} and $opts{task} or + $opts{oldtask} eq $opts{task}) and + ($opts{oldsystem} and $opts{system} or + $opts{oldsystem} eq $opts{system}) and + ($opts{oldnot} and $opts{not} or + $opts{oldnot} eq $opts{not}) and + ($opts{oldstatus} and $opts{status} or + $opts{oldstatus} eq $opts{status})) { + $optsChanged = 1; + } # unless + + my $condition; + + unless ($opts{id}) { + $condition = "task like '%"; + $condition .= $opts{task} ? $opts{task} : ''; + $condition .= "%'"; + + if ($opts{system}) { + if ($opts{system} eq '') { + $condition .= ' and system is null'; + undef $opts{system} + } elsif ($opts{system} ne 'All') { + $condition .= " and system like '%$opts{system}%'";; + } # if + } # if + + if (defined $opts{status}) { + $condition .= ' and '; + unless ($opts{not}) { + $condition .= "status=$opts{status}"; + } else { + $condition .= "status<>$opts{status}"; + } # unless + } # if + } # unless + + my $total = $clearadm->Count ('runlog', $condition); + + $opts{start} = $opts{'nextArrow.x'} ? $opts{next} : $opts{prev}; + $opts{start} ||= 0; + $opts{start} = 0 + if $optsChanged; + + my $next = $opts{start} + $opts{page} < $total + ? $opts{start} + $opts{page} + : $opts{start}; + my $prev = $opts{start} - $opts{page} >= 0 + ? $opts{start} - $opts{page} + : $opts{start}; + + my $opts = $opts{'nextArrow.x'} ? $opts{next} + 1 : $opts{prev} + 1; + $opts .= '-'; + $opts .= $opts{start} + $opts{page} < $total + ? $opts{start} + $opts{page} + : $total; + $opts .= " of $total"; + + display start_form { + method => 'post', + action => 'runlog.cgi' + }; + + # Hidden fields to pass along + display input {name => 'prev', type => 'hidden', value => $prev}; + display input {name => 'next', type => 'hidden', value => $next}; + display input {name => 'oldtask', type => 'hidden', value => $opts{task}}; + display input {name => 'oldsystem', type => 'hidden', value => $opts{system}}; + display input {name => 'oldnot', type => 'hidden', value => $opts{not}}; + display input {name => 'oldstatus', type => 'hidden', value => $opts{status}}; + + my $caption = start_table { + class => 'caption', + cellspacing => 1, + width => '100%', + }; + + $caption .= start_Tr; + + unless ($opts{id}) { + $caption .= td {align => 'left'}, input { + name => 'prevArrow', + type => 'image', + src => 'left.png', + alt => 'Previous', + value => 'prev', + }; + } else { + $caption .= td {align => 'left'}, img { + src => 'left.png', + disabled => 'disabled', + }; + } # unless + + $caption .= td {align => 'center'}, $opts; + + unless ($opts{id}) { + $caption .= td {align => 'right'}, input { + name => 'nextArrow', + type => 'image', + src => 'right.png', + alt => 'Next', + value => 'next', + }; + } else { + $caption .= td {align => 'right'}, img { + src => 'right.png', + disabled => 'disabled', + }; + } # unless + + $caption .= end_Tr; + + $caption .= end_table; + + display start_table {cellspacing => 1, width => '98%'}; + + display caption $caption; + + display start_Tr; + display th {class => 'labelCentered'}, '#'; + display th {class => 'labelCentered'}, 'ID'; + display th {class => 'labelCentered'}, 'Task'; + display th {class => 'labelCentered'}, 'System'; + display th {class => 'labelCentered'}, 'Started'; + display th {class => 'labelCentered'}, 'Ended'; + display th {class => 'labelCentered'}, 'Status'; + display th {class => 'labelCentered'}, 'Message'; + display end_Tr; + + display start_Tr; + $opts{not} ||= 'false'; + + display start_form { + method => 'post', + action => 'runlog.cgi' + }; + display td { + class => 'filter', + align => 'right', + colspan => 2, + }, b 'Filter:'; + display td { + class => 'filter' + }, _makeRunlogSelection ('task', $opts{task}); + display td { + class => 'filter' + }, _makeRunlogSelection ('system', $opts{system}); + display td {class => 'filter'}, ' '; + display td { + class => 'filter', + align => 'right', + }, "Not: ", checkbox { + name => 'not', + value => 'true', + checked => $opts{not} eq 'true' ? 1 : 0, + label => '', + }; + display td { + class => 'filter' + }, _makeRunlogSelectionNumeric ('status', $opts{status}); + display td { + class => 'filter', + }, input { + type => 'submit', + value => 'Update', + }; + + display end_form; + display end_Tr; + + my $i = $opts{start}; + + my $status; + + if (defined $opts{status}) { + if ($opts{status} !~ /all/i) { + $status = $opts{not} ne 'true' ? $opts{status} : "!$opts{status}"; + } # if + } # if + + foreach ($clearadm->FindRunlog ( + $opts{task}, + $opts{system}, + $status, + $opts{id}, + $opts{start}, + $opts{page}, + )) { + my %runlog = setFields 'N/A', %{$_}; + + my $class = $runlog{status} == 0 + ? 'data' + : 'dataAlert'; + my $classCentered = $runlog{status} == 0 + ? 'dataCentered' + : 'dataAlertCentered'; + my $classRight = $runlog{status} == 0 + ? 'dataRight' + : 'dataAlertRight'; + + display start_Tr; + display td {class => 'dataCentered'}, ++$i; + display td {class => 'dataCentered'}, $runlog{id}; + display td {class => 'data'}, a { + href => "tasks.cgi?task=$runlog{task}" + }, $runlog{task}; + display td {class => 'data'}, $runlog{system} eq 'Localhost' + ? $runlog{system} + : a { + href => "systemdetails.cgi?system=$runlog{system}" + }, $runlog{system}; + display td {class => 'dataCentered'}, $runlog{started}; + display td {class => 'dataCentered'}, $runlog{ended}; + display td {class => $classRight}, $runlog{status}; + + my $message = $runlog{message}; + $message =~ s/\r\n/
    /g; + + display td {class => $class, width => '50%'}, $message; + display end_Tr; + } # foreach + + display end_table; + + return; +} # displayRunlog + +sub displaySchedule () { + display start_table {cellspacing => 1}; + + display start_Tr; + display th {class => 'labelCentered'}, 'Actions'; + display th {class => 'labelCentered'}, 'Active'; + display th {class => 'labelCentered'}, 'Name'; + display th {class => 'labelCentered'}, 'Task'; + display th {class => 'labelCentered'}, 'Notification'; + display th {class => 'labelCentered'}, 'Frequency'; + display th {class => 'labelCentered'}, 'Category'; + display end_Tr; + + foreach ($clearadm->FindSchedule) { + my %schedule = setFields 'N/A', %{$_}; + + display start_Tr; + my $areYouSure = "Are you sure you want to delete the $schedule{name} " + . "schedule?"; + + my $actions = start_form { + method => 'post', + action => 'processschedule.cgi', + }; + + $actions .= input { + name => 'name', + type => 'hidden', + value => $schedule{name}, + }; + + if (InArray $schedule{name}, @PREDEFINED_SCHEDULES) { + $actions .= input { + name => 'delete', + disabled => 'true', + type => 'image', + src => 'delete.png', + alt => 'Delete', + value => 'Delete', + title => 'Cannot delete predefined schedule', + }; + $actions .= input { + name => 'edit', + disabled => 'true', + type => 'image', + src => 'edit.png', + alt => 'Edit', + value => 'Edit', + title => 'Cannot edit predefined schedule', + }; + } else { + $actions .= input { + name => 'delete', + type => 'image', + src => 'delete.png', + alt => 'Delete', + value => 'Delete', + title => 'Delete', + onclick => "return AreYouSure ('$areYouSure');", + }; + $actions .= input { + name => 'edit', + type => 'image', + src => 'edit.png', + alt => 'Edit', + value => 'Edit', + title => 'Edit', + }; + } # if + + display end_form; + + display td {class => 'dataCentered'}, $actions; + display td {class => 'dataCentered'}, checkbox { + disabled => 'disabled', + checked => $schedule{active} eq 'true' ? 1 : 0, + }; + display td {class => 'data'}, $schedule{name}; + display td {class => 'data'}, a { + href => "tasks.cgi?task=$schedule{task}" + }, $schedule{task}; + display td {class => 'data'}, a { + href => "notifications.cgi?notification=$schedule{notification}" + }, $schedule{notification}; + display td {class => 'data'}, $schedule{frequency}; + display td {class => 'data'}, + (InArray $schedule{name}, @PREDEFINED_SCHEDULES) + ? 'Predefined' + : 'User Defined'; + + display end_Tr; + } # foreach + + display end_table; + + display p {class => 'center'}, a { + href => 'processschedule.cgi?action=Add', + }, 'New schedule', img { + src => 'add.png', + border => 0, + }; + + return; +} # displaySchedule + +sub displaySystem ($) { + my ($systemName) = @_; + + my %system = $clearadm->GetSystem ($systemName); + + unless (%system) { + displayError "Nothing known about system $systemName"; + return; + } # unless + + my $lastheardfromClass = 'dataCentered'; + my $lastheardfromData = $system{lastheardfrom}; + + my %load = $clearadm->GetLatestLoadavg ($systemName); + + unless ($clearadm->SystemAlive (%system)) { + $lastheardfromClass = 'dataCenteredAlert'; + $lastheardfromData = a { + href => "alertlog.cgi?system=$system{name}", + class => 'alert', + title => "Have not heard from $system{name} for a while" + }, $system{lastheardfrom}; + $system{notification} = 'Heartbeat'; + } # unless + + my $admin = ($system{email}) + ? a {-href => "mailto:$system{email}"}, $system{admin} + : $system{admin}; + + $system{alias} = setField $system{alias}, 'N/A'; + $system{region} = setField $system{region}, 'N/A'; + + display start_table {cellspacing => 1}; + + display start_Tr; + my $areYouSure = 'Are you sure you want to delete this system?\n' + . "Doing so will remove all records related to $system{name}" + . '\nincluding filesystem records and history as well as ' + . 'loadavg history.'; + + my $actions = start_form { + method => 'post', + action => 'processsystem.cgi', + }; + + $actions .= input { + name => 'name', + type => 'hidden', + value => $system{name}, + }; + + $actions .= input { + name => 'delete', + type => 'image', + src => 'delete.png', + alt => 'Delete', + value => 'Delete', + title => 'Delete', + onclick => "return AreYouSure ('$areYouSure');", + }; + $actions .= input { + name => 'edit', + type => 'image', + src => 'edit.png', + alt => 'Edit', + value => 'Edit', + title => 'Edit', + }; + $actions .= checkbox { + disabled => 'disabled', + checked => $system{active} eq 'true' ? 1 : 0, + }; + + if ($system{notification}) { + $actions .= a { + href => "alertlog.cgi?system=$system{name}"}, img { + src => 'alert.png', + border => 0, + alt => 'Alert!', + title => 'This system has alerts', + }; + } # if + + display th {class => 'label'}, "$actions Name:"; + display end_form; + display td {class => 'dataCentered', colspan => 2}, $system{name}; + display th {class => 'label'}, 'Alias:'; + display td {class => 'dataCentered'}, $system{alias}; + display th {class => 'label'}, 'Admin:'; + display td {class => 'dataCentered', colspan => 2}, $admin; + display th {class => 'label', colspan => 2}, 'Type:'; + display td {class => 'dataCentered'}, $system{type}; + display end_Tr; + + display start_Tr; + display th {class => 'label'}, 'OS Version:'; + display td {class => 'data', colspan => 10}, $system{os}; + display end_Tr; + + display start_Tr; + display th {class => 'label'}, 'Last Contacted:'; + display td { + class => $lastheardfromClass, + colspan => 2 + }, "$lastheardfromData ", + font {class => 'dim' }, "
    Up: $load{uptime}"; + display th {class => 'label'}, 'Port:'; + display td {class => 'dataCentered'}, $system{port}; + display th {class => 'label'}, 'Threshold:'; + display td {class => 'dataCentered'}, $system{loadavgThreshold}; + display th {class => 'label'}, 'History:'; + display td {class => 'dataCentered'}, $system{loadavgHist}; + display th {class => 'label'}, 'Load Avg:'; + display td {class => 'data'}, + a {href => + "plot.cgi?type=loadavg&system=$system{name}&scaling=Hour&points=24" + }, img { + src => "plotloadavg.cgi?system=$system{name}&tiny=1", + border => 0, + }; + + my $description = $system{description}; + $description =~ s/\r\n/
    /g; + + display start_Tr; + display th {class => 'label'}, 'Description:'; + display td {class => 'data', colSpan => 10}, $description; + display end_Tr; + + display end_table; + + display p {class => 'center'}, a { + href => 'processsystem.cgi?action=Add', + }, 'New system', img { + src => 'add.png', + border => 0, + }; + + display h1 {class => 'center'}, + 'Filesystem Details: ' . ucfirst $system{name}; + + display start_table {cellspacing => 1}; + + display start_Tr; + display th {class => 'labelCentered'}, 'Action'; + display th {class => 'labelCentered'}, 'Filesystem'; + display th {class => 'labelCentered'}, 'Type'; + display th {class => 'labelCentered'}, 'Mount'; + display th {class => 'labelCentered'}, 'Size'; + display th {class => 'labelCentered'}, 'Used'; + display th {class => 'labelCentered'}, 'Free'; + display th {class => 'labelCentered'}, 'Used %'; + display th {class => 'labelCentered'}, 'Threshold'; + display th {class => 'labelCentered'}, 'History'; + display th {class => 'labelCentered'}, 'Usage'; + display end_Tr; + + foreach ($clearadm->FindFilesystem ($system{name})) { + my %filesystem = %{$_}; + + my %fs = $clearadm->GetLatestFS ( + $filesystem{system}, + $filesystem{filesystem} + ); + + my $size = autoScale $fs{size}; + my $used = autoScale $fs{used}; + my $free = autoScale $fs{free}; + + # TODO: Note that this percentages does not agree with df output. I'm not + # sure why. + my $usedPct = $fs{size} == 0 ? 0 + : sprintf ('%.0f', + (($fs{reserve} + $fs{used}) / $fs{size} * 100)); + + my $class = $usedPct < $filesystem{threshold} + ? 'data' + : 'dataAlert'; + my $classCentered = $class . 'Centered'; + my $classRight = $class . 'Right'; + + display start_Tr; + display start_td {class => 'data'}; + + my $areYouSure = 'Are you sure you want to delete ' + . "$system{name}:$filesystem{filesystem}?" . '\n' + . 'Doing so will remove all records related to this\n' + . 'filesystem and its history.'; + + display start_form { + method => 'post', + action => 'processfilesystem.cgi', + }; + + display input { + type => 'hidden', + name => 'system', + value => $system{name}, + }; + display input { + type => 'hidden', + name => 'filesystem', + value => $filesystem{filesystem}, + }; + + display input { + name => 'delete', + type => 'image', + src => 'delete.png', + alt => 'Delete', + value => 'Delete', + title => 'Delete', + onclick => "return AreYouSure ('$areYouSure');" + }; + display input { + name => 'edit', + type => 'image', + src => 'edit.png', + alt => 'Edit', + value => 'Edit', + title => 'Edit', + }; + + if ($filesystem{notification}) { + display a { + href => "alertlog.cgi?system=$filesystem{system}"}, img { + src => 'alert.png', + border => 0, + alt => 'Alert!', + title => 'This filesystem has alerts', + }; + } # if + + display end_form; + display td {class => $class}, $filesystem{filesystem}; + display td {class => $classCentered}, $filesystem{fstype}; + display td {class => $class}, $filesystem{mount}; + display td {class => $classRight}, $size; + display td {class => $classRight}, $used; + display td {class => $classRight}, $free; + display td {class => $classRight}, "$usedPct%"; + display td {class => $classRight}, "$filesystem{threshold}%"; + display td {class => $classCentered}, $filesystem{filesystemHist}; + display td {class => $classCentered}, + a {href => + "plot.cgi?type=filesystem&system=$system{name}" + . "&filesystem=$filesystem{filesystem}" + . "&scaling=Day&points=7" + }, img { + src => "plotfs.cgi?system=$system{name}&" + . "filesystem=$filesystem{filesystem}" + . '&tiny=1', + border => 0, + }; + display end_Tr; + } # foreach + + display end_table; + + return; +} # displaySystem + +sub displayTask (;$) { + my ($task) = @_; + + display start_table {cellspacing => 1, width => '98%'}; + + display start_Tr; + display th {class => 'labelCentered'}, 'Actions'; + display th {class => 'labelCentered'}, 'Name'; + display th {class => 'labelCentered'}, 'System'; + display th {class => 'labelCentered'}, 'Description'; + display th {class => 'labelCentered'}, 'Command'; + display th {class => 'labelCentered'}, 'Restartable'; + display th {class => 'labelCentered'}, 'Category'; + display end_Tr; + + foreach ($clearadm->FindTask ($task)) { + my %task = %{$_}; + + $task{system} = 'All Systems' + unless $task{system}; + + display start_Tr; + my $areYouSure = "Are you sure you want to delete the $task{name} task?"; + + my $actions = start_form { + method => 'post', + action => 'processtask.cgi', + }; + + $actions .= input { + name => 'name', + type => 'hidden', + value => $task{name}, + }; + + if (InArray $task{name}, @PREDEFINED_TASKS) { + $actions .= input { + name => 'delete', + disabled => 'true', + type => 'image', + src => 'delete.png', + alt => 'Delete', + value => 'Delete', + title => 'Cannot delete predefined task', + }; + $actions .= input { + name => 'edit', + disabled => 'true', + type => 'image', + src => 'edit.png', + alt => 'Edit', + value => 'Edit', + title => 'Cannot edit predefined task', + }; + } else { + $actions .= input { + name => 'delete', + type => 'image', + src => 'delete.png', + alt => 'Delete', + value => 'Delete', + title => 'Delete', + onclick => "return AreYouSure ('$areYouSure');", + }; + $actions .= input { + name => 'edit', + type => 'image', + src => 'edit.png', + alt => 'Edit', + value => 'Edit', + title => 'Edit', + }; + } # if + + display end_form; + + display td {class => 'dataCentered'}, $actions; + display td {class => 'data'}, $task{name}; + display td {class => 'data'}, $task{system}; + display td {class => 'data'}, $task{description}; + display td {class => 'data'}, $task{command}; + display td {class => 'dataCentered'}, $task{restartable}; + display td {class => 'data'}, + (InArray $task{name}, @PREDEFINED_TASKS) ? 'Predefined' : 'User Defined'; + display end_Tr; + } # foreach + + display end_table; + + display p {class => 'center'}, a { + href => 'processtask.cgi?action=Add', + }, 'New task', img { + src => 'add.png', + border => 0, + }; + + return; +} # DisplayAlerts + +sub editAlert (;$) { + my ($alert) = @_; + + display start_form ( + -method => 'post', + -action => 'processalert.cgi', + -onsubmit => 'return validateAlert (this);', + ); + + my %alert; + + if ($alert) { + %alert = $clearadm->GetAlert ($alert); + + return + unless %alert; + + display input { + name => 'oldname', + type => 'hidden', + value => $alert, + }; + } else { + $alert= ''; + } # if + + display input { + name => 'action', + type => 'hidden', + value => 'Post', + }; + + display start_table {cellspacing => 1}; + + display start_Tr; + display th {class => 'labelCentered'}, 'Name'; + display th {class => 'labelCentered'}, 'Type'; + display th {class => 'labelCentered'}, 'Who'; + display end_Tr; + + display start_Tr; + display td { + class => 'data', + }, input { + class => 'inputfield', + maxlength => 255, + name => 'name', + size => 20, + type => 'text', + value => $alert ? $alert{name} : '', + }; + display td { + class => 'dataCentered', + }, popup_menu { + name => 'type', + class => 'dropdown', + values => [ 'email', 'page', 'im' ], + default => $alert ? $alert{type} : 'email', + }; + display td { + class => 'data', + }, input { + class => 'inputfield', + maxlength => 255, + name => 'who', + size => 20, + type => 'text', + value => $alert ? $alert{who} : '', + }; + display end_Tr; + display end_table; + + display '
    '; + display p submit ({value => $alert ? 'Update' : 'Add'}), reset; + display '
    '; + + display end_form; + + return; +} # editAlert + +sub editFilesystem ($$) { + my ($system, $filesystem) = @_; + + display start_form ( + -method => 'post', + -action => 'processfilesystem.cgi', + ); + + display start_table {width => '800px', cellspacing => 1}; + + display start_Tr; + display th {class => 'labelCentered'}, 'Filesystem'; + display th {class => 'labelCentered'}, 'Type'; + display th {class => 'labelCentered'}, 'Mount'; + display th {class => 'labelCentered'}, 'Size'; + display th {class => 'labelCentered'}, 'Used'; + display th {class => 'labelCentered'}, 'Free'; + display th {class => 'labelCentered'}, 'Used %'; + display th {class => 'labelCentered'}, 'History'; + display th {class => 'labelCentered'}, 'Threshold'; + display end_Tr; + + my %filesystem = $clearadm->GetFilesystem ($system, $filesystem); + my %fs = $clearadm->GetLatestFS ($system, $filesystem); + + display input { + name => 'action', + type => 'hidden', + value => 'Post', + }; + display input { + name => 'system', + type => 'hidden', + value => $filesystem{system}, + }; + display input { + name => 'filesystem', + type => 'hidden', + value => $filesystem{filesystem}, + } ; + + my $size = autoScale $fs{size}; + my $used = autoScale $fs{used}; + my $free = autoScale $fs{free}; + + display start_Tr; + display td {class => 'data'}, $filesystem{filesystem}; + display td {class => 'dataCentered'}, $filesystem{fstype}; + display td {class => 'data'}, $filesystem{mount}; + display td {class => 'dataRight'}, $size; + display td {class => 'dataRight'}, $used; + display td {class => 'dataRight'}, $free; + # TODO: Note that this percentages does not agree with df output. I'm not + # sure why. + display td {class => 'dataCentered'}, + sprintf ('%.0f%%', (($fs{reserve} + $fs{used}) / $fs{size} * 100)); + + my $historyDropdown = popup_menu { + name => 'filesystemHist', + class => 'dropdown', + values => [ + '1 month', + '2 months', + '3 months', + '4 months', + '5 months', + '6 months', + '7 months', + '8 months', + '9 months', + '10 months', + '11 months', + '1 year', + ], + default => $system ? $filesystem{filesystemHist} : '6 months', + }; + + display td { + class => 'dataRight', + }, $historyDropdown; + + my $thresholdDropdown = popup_menu { + name => 'threshold', + class => 'dropdown', + values => [1 .. 100], + default => $filesystem{threshold}, + }; + display td {class => 'dataCentered'}, $thresholdDropdown . '%'; + display end_Tr; + + display end_table; + + display '
    '; + display p submit ({value => 'Update'}), reset; + display '
    '; + + display end_form; + + return; +} # editFilesytem + +sub editNotification (;$) { + my ($notification) = @_; + + display start_form ( + -method => 'post', + -action => 'processnotification.cgi', + -onsubmit => 'return validateNotification (this);', + ); + + my %notification; + + if ($notification) { + %notification = $clearadm->GetNotification ($notification); + + return + unless %notification; + + display input { + name => 'oldname', + type => 'hidden', + value => $notification, + }; + } else { + $notification = ''; + } # if + + display input { + name => 'action', + type => 'hidden', + value => 'Post', + }; + + display start_table {cellspacing => 1}; + + display start_Tr; + display th {class => 'labelCentered'}, 'Name'; + display th {class => 'labelCentered'}, 'Alert'; + display th {class => 'labelCentered'}, 'Condition'; + display th {class => 'labelCentered'}, 'Not More Than'; + display end_Tr; + + display start_Tr; + display td { + class => 'data', + }, input { + class => 'inputfield', + maxlength => 255, + name => 'name', + size => 20, + type => 'text', + value => $notification ? $notification{name} : '', + }; + + display td { + class => 'dataCentered', + }, makeAlertDropdown undef, $notification{alert} + ? $notification{alert} + : 'Email admin'; + + display td { + class => 'data', + }, input { + class => 'inputfield', + maxlength => 255, + name => 'cond', + size => 20, + type => 'text', + value => $notification ? $notification{cond} : '', + }; + display td { + class => 'dataCentered', + }, makeNoMoreThanDropdown undef, $notification{nomorethan}; + + display end_Tr; + display end_table; + + display '
    '; + display p submit ({value => $notification ? 'Update' : 'Add'}), reset; + display '
    '; + + display end_form; + + return; +} # editNotification + +sub editSchedule (;$) { + my ($schedule) = @_; + + display start_form ( + -method => 'post', + -action => 'processschedule.cgi', + -onsubmit => 'return validateSchedule (this);', + ); + + my %schedule; + + if ($schedule) { + %schedule = $clearadm->GetSchedule ($schedule); + + return + unless %schedule; + + display input { + name => 'oldname', + type => 'hidden', + value => $schedule, + }; + } else { + $schedule = ''; + } # if + + display input { + name => 'action', + type => 'hidden', + value => 'Post', + }; + + display start_table {cellspacing => 1}; + + display start_Tr; + display th {class => 'labelCentered'}, 'Active'; + display th {class => 'labelCentered'}, 'Name'; + display th {class => 'labelCentered'}, 'Task'; + display th {class => 'labelCentered'}, 'Notification'; + display th {class => 'labelCentered'}, 'Frequency'; + display end_Tr; + + display start_Tr; + display td { + class => 'dataCentered', + }, checkbox { + name => 'active', + value => 'true', + checked => $schedule{active} eq 'false' ? 0 : 1, + label => '', + }; + display td { + class => 'data', + }, input { + class => 'inputfield', + maxlength => 255, + name => 'name', + size => 20, + type => 'text', + value => $schedule ? $schedule{name} : '', + }; + display td { + class => 'dataCentered', + }, makeTaskDropdown undef, $schedule{task}; + display td { + class => 'dataCentered', + }, makeNotificationDropdown undef, $schedule{notification}; + + my $nbr = 5; + my $multiplier = 'minutes'; + + if ($schedule{frequency} =~ /(\d+)\s(\S+)/ ) { + $nbr = $1; + $multiplier = $2; + + $multiplier .= 's' if $nbr == 1; + } # if + + display td { + class => 'data', + }, input { + class => 'inputfieldRight', + maxlength => 3, + name => 'nbr', + size => 1, + type => 'text', + value => $nbr, + }, + ' ', + makeMultiplierDropdown undef, $multiplier; + + display end_Tr; + display end_table; + + display '
    '; + display p submit ({value => $schedule ? 'Update' : 'Add'}), reset; + display '
    '; + + display end_form; + + return; +} # editSchedule + +sub editSystem (;$) { + my ($system) = @_; + + display start_form ( + -method => 'post', + -action => 'processsystem.cgi', + -onsubmit => 'return validateSystem (this);', + ); + + my %system; + + if ($system) { + %system = $clearadm->GetSystem ($system); + + return + unless %system; + + display input { + name => 'name', + type => 'hidden', + value => $system, + }; + } else { + $system = ''; + } # if + + display input { + name => 'action', + type => 'hidden', + value => 'Post', + }; + + display start_table {cellspacing => 1}; + + display start_Tr; + display th {class => 'label'}, checkbox ({ + name => 'active', + value => 'true', + checked => $system{active} eq 'false' ? 0 : 1, + label => '', + }) . ' Name: '; + + if ($system) { + display td {class => 'data'}, $system{name}; + } else { + display td { + class => 'data', + }, input { + class => 'inputfield', + maxlength => 255, + name => 'name', + size => 20, + type => 'text', + }; + } # if + + display th {class => 'label'}, 'Alias:'; + display td { + class => 'data', + }, input { + class => 'inputfield', + maxlength => 255, + name => 'alias', + size => 20, + type => 'text', + value => $system ? $system{alias} : '', + }; + + display th {class => 'label'}, 'Port:'; + display td { + class => 'dataRight', + }, input { + class => 'inputfieldRight', + maxlength => 6, + name => 'port', + size => 4, + type => 'text', + value => $system + ? $system{port} + : $Clearadm::CLEAROPTS{CLEARADM_PORT}, + }; + + my $systemTypeDropdown = popup_menu { + name => 'type', + class => 'dropdown', + values => ['Unix', 'Linux', 'Windows'], + default => $system ? $system{type} : 'Linux', + }; + + display th {class => 'label'}, 'Type:'; + display td { + class => 'dataRight', + }, $systemTypeDropdown; + display end_Tr; + + display start_Tr; + display th {class => 'label'}, 'Admin:'; + display td { + class => 'data', + }, input { + class => 'inputfield', + maxlength => 255, + name => 'admin', + size => 20, + type => 'text', + value => $system ? $system{admin} : '', + }; + display th {class => 'label'}, 'Admin Email:'; + display td { + class => 'data', + }, input { + class => 'inputfield', + maxlength => 255, + name => 'email', + size => 20, + type => 'text', + value => $system ? $system{email} : '', + }; + + display th {class => 'label'}, 'Threshold:'; + display td { + class => 'dataRight', + }, input { + class => 'inputfieldRight', + maxlength => 5, + name => 'loadavgThreshold', + size => 3, + type => 'text', + value => $system + ? $system{loadavgThreshold} + : $Clearadm::CLEAROPTS{CLEARADM_LOADAVG_THRESHOLD}, + }; + + my $historyDropdown = popup_menu { + name => 'loadavgHist', + class => 'dropdown', + values => [ + '1 month', + '2 months', + '3 months', + '4 months', + '5 months', + '6 months', + '7 months', + '8 months', + '9 months', + '10 months', + '11 months', + '1 year', + ], + default => $system ? $system{loadavgHist} : '6 months', + }; + + display th {class => 'label'}, 'History:'; + display td { + class => 'dataRight', + }, $historyDropdown; + + my $description = $system ? $system{description} : ''; + $description =~ s/\r\n/
    /g; + + display start_Tr; + display th {class => 'label'}, 'Description:'; + display td { + class => 'data', + colspan => 7, + }, textarea { + class => 'inputfield', + cols => 103, + name => 'description', + rows => 3, + value => $description, + }; + display end_Tr; + display end_table; + + display '
    '; + display p submit ({value => $system ? 'Update' : 'Add'}), reset; + display '
    '; + + display end_form; + + return; +} # editSystem + +sub editTask (;$) { + my ($task) = @_; + + display start_form ( + -method => 'post', + -action => 'processtask.cgi', + -onsubmit => 'return validateTask (this);', + ); + + my %task; + + if ($task) { + %task = $clearadm->GetTask ($task); + + return + unless %task; + + display input { + name => 'oldname', + type => 'hidden', + value => $task, + }; + } else { + $task = ''; + } # if + + display input { + name => 'action', + type => 'hidden', + value => 'Post', + }; + + display start_table {cellspacing => 1}; + + display start_Tr; + display th {class => 'labelCentered'}, 'Name'; + display th {class => 'labelCentered'}, 'System'; + display th {class => 'labelCentered'}, 'Description'; + display th {class => 'labelCentered'}, 'Command'; + display th {class => 'labelCentered'}, 'Restartable'; + display end_Tr; + + display start_Tr; + display td { + class => 'data', + }, input { + class => 'inputfield', + maxlength => 255, + name => 'name', + size => 15, + type => 'text', + value => $task ? $task{name} : '', + }; + my $systemDropdown = makeSystemDropdown ( + undef, + $task{system} ? $task{system} : 'All Systems', + undef, ( + 'All systems' => undef, + 'Localhost' => 'Localhost', + ), + ); + + display td {class => 'data'}, $systemDropdown; + + display td { + class => 'data', + }, input { + class => 'inputfield', + maxlength => 255, + name => 'description', + size => 30, + type => 'text', + value => $task ? $task{description} : '', + }; + + display td { + class => 'data', + }, input { + class => 'inputfield', + maxlength => 255, + name => 'command', + size => 40, + type => 'text', + value => $task ? $task{command} : '', + }; + + display td { + class => 'dataCentered', + }, makeRestartableDropdown undef, $task{restartable}; + + display end_Tr; + display end_table; + + display '
    '; + display p submit ({value => $task ? 'Update' : 'Add'}), reset; + display '
    '; + + display end_form; + + return; +} # editTask + +sub footing () { + my $clearscm = a {-href => 'http://clearscm.com'}, 'ClearSCM, Inc.'; + + # Figure out which script by using CLEARADM_BASE. + my $script = basename (url {-absolute => 1}); + $script = 'index.cgi' + if $script eq 'clearadm'; + + my $scriptFullPath = "$Clearadm::CLEAROPTS{CLEARADM_BASE}/$script"; + + my ($year, $mon, $mday, $hour, $min, $sec) = + ymdhms ((stat ($scriptFullPath))[9]); + + my $dateModified = "$mon/$mday/$year @ $hour:$min"; + + $script = a { + -href => "http://clearscm.com/php/cvs_man.php?file=clearadm/$script" + }, $script; + + display end_div; + + display start_div {-class => 'copyright'}; + display "$script: Last modified: $dateModified"; + display br "Copyright © $year, $clearscm - All rights reserved"; + display end_div; + + print end_html; + + return; +} # footing + +1; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Clearadm + DateUtils + Display + Utils + +=end man + +=begin html + +
    +Clearadm
    +DateUtils
    +Display
    +Utils
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this module. + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut diff --git a/clearadm/lib/Clearexec.pm b/clearadm/lib/Clearexec.pm new file mode 100644 index 0000000..4157066 --- /dev/null +++ b/clearadm/lib/Clearexec.pm @@ -0,0 +1,425 @@ +=pod + +=head1 NAME $RCSfile: Clearexec.pm,v $ + +Clearexec - Execute remote commands locally + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.18 $ + +=item Created + +Tue Dec 07 09:13:27 EST 2010 + +=item Modified + +$Date: 2012/12/16 18:00:16 $ + +=back + +=head1 SYNOPSIS + +Provides an interface to the Clearexec object. Clearexec is a daemon that runs +on a host and accepts requests to execute commands locally and send the results +back to the caller. + +=head1 DESCRIPTION + +The results are sent back as follows: + + Status: + + +This allows the caller to determine if the command execution was successful as +well as capture the commands output. + +=head1 ROUTINES + +The following methods are available: + +=cut + +package Clearexec; + +use strict; +use warnings; + +use Carp; +use FindBin; +use IO::Socket; +use Net::hostent; +use POSIX qw(:sys_wait_h); +use Errno; + +use lib "$FindBin::Bin/../../lib"; + +use DateUtils; +use Display; +use GetConfig; +use Utils; + +# Seed options from config file +our %CLEAROPTS = GetConfig ("$FindBin::Bin/etc/clearexec.conf"); + +our $VERSION = '$Revision: 1.18 $'; +($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +# Override options if in the environment +$CLEAROPTS{CLEAREXEC_HOST} = $ENV{CLEAREXEC_HOST} + if $ENV{CLEAREXEC_HOST}; +$CLEAROPTS{CLEAREXEC_PORT} = $ENV{CLEAREXEC_PORT} + if $ENV{CLEAREXEC_PORT}; +$CLEAROPTS{CLEAREXEC_MULTITHREADED} = $ENV{CLEAREXEC_MULTITHREADED} + if $ENV{CLEAREXEC_MULTITHREADED}; + +sub new () { + my ($class) = @_; + + my $clearadm = bless {}, $class; + + $clearadm->{multithreaded} = $CLEAROPTS{CLEAREXEC_MULTITHREADED}; + + return $clearadm; +} # new + +sub _tag ($) { + my ($self, $msg) = @_; + + my $tag = YMDHMS; + $tag .= ' '; + $tag .= $self->{pid} ? "[$self->{pid}] " : ''; + + return "$tag$msg"; +} # _tag + +sub _verbose ($) { + my ($self, $msg) = @_; + + verbose $self->_tag ($msg); + + return; +} # _verbose + +sub _debug ($) { + my ($self, $msg) = @_; + + debug $self->_tag ($msg); + + return; +} # _debug + +sub _log ($) { + my ($self, $msg) = @_; + + display $self->_tag ($msg); + + return; +} # log + +sub _endServer () { + display "Clearexec V$VERSION shutdown at " . localtime; + + # Kill process group + kill 'TERM', -$$; + + # Wait for all children to die + while (wait != -1) { + + # do nothing + } # while + + # Now that we are alone, we can simply exit + exit; +} # _endServer + +sub _restartServer () { + + # Not sure what to do on a restart server + display 'Entered _restartServer'; + + return; +} # _restartServer + +sub setMultithreaded ($) { + my ($self, $value) = @_; + + my $oldValue = $self->{multithreaded}; + + $self->{multithreaded} = $value; + + return $oldValue; +} # setMultithreaded + +sub getMultithreaded () { + my ($self) = @_; + + return $self->{multithreaded}; +} # getMultithreaded + +sub connectToServer (;$$) { + my ($self, $host, $port) = @_; + + $host ||= $CLEAROPTS{CLEAREXEC_HOST}; + $port ||= $CLEAROPTS{CLEAREXEC_PORT}; + + $self->{socket} = IO::Socket::INET->new ( + Proto => 'tcp', + PeerAddr => $host, + PeerPort => $port, + ); + + return unless $self->{socket}; + + $self->{socket}->autoflush + if $self->{socket}; + + $self->{host} = $host; + $self->{port} = $port; + + if ($self->{socket}) { + return 1; + } else { + return; + } # if + + return; +} # connectToServer + +sub disconnectFromServer () { + my ($self) = @_; + + undef $self->{socket}; + + return; +} # disconnectFromServer + +sub execute ($) { + my ($self, $cmd) = @_; + + return (-1, 'Unable to talk to server') + unless $self->{socket}; + + my ($status, $statusLine, @output) = (-1, '', ()); + + my $server = $self->{socket}; + + print $server "$cmd\n"; + + my $response; + + while (defined ($response = <$server>)) { + if ($response =~ /Clearexec Status: (-*\d+)/) { + $status = $1; + last; + } # if + + push @output, $response; + } # while + + chomp @output; + + return ($status, @output); +} # execute + +sub _serviceClient ($$) { + my ($self, $host, $client) = @_; + + $self->_verbose ("Serving requests from $host"); + + # Set autoflush for client + $client->autoflush + if $client; + + while () { + # Read command from client + my $cmd = <$client>; + + last unless $cmd; + + chomp $cmd; + + next if $cmd eq ''; + + last if $cmd =~ /quit|exit/i; + + $self->_debug ("$host wants us to do $cmd"); + + my ($status, @output); + + $status = 0; + + if ($cmd =~ /stopserver/i) { + if ($self->{server}) { + $self->_verbose ("$host requested to stop server [$self->{server}]"); + + # Send server hangup signal + kill 'HUP', $self->{server}; + } else { + $self->_verbose ('Shutting down server'); + + print $client "Clearexec Status: 0\n"; + + exit; + } # if + + $self->_debug ("Returning 0, undef"); + } else { + # Combines STDERR -> STDOUT if not already specified + $cmd .= ' 2>&1' + unless $cmd =~ /2>&1/; + + $self->_debug ("Executing $cmd"); + ($status, @output) = Execute $cmd; + $self->_debug ("Status: $status"); + } # if + + print $client "$_\n" foreach (@output); + print $client "Clearexec Status: $status\n"; + + $self->_debug ("Looping around for next command"); + } # while + + close $client; + + $self->_verbose ("Serviced requests from $host"); + + return; +} # _serviceClient + +sub startServer (;$) { + my ($self, $port) = @_; + + $port ||= $CLEAROPTS{CLEAREXEC_PORT}; + + # Create new socket to communicate to clients with + $self->{socket} = IO::Socket::INET->new ( + Proto => 'tcp', + LocalPort => $port, + Listen => SOMAXCONN, + Reuse => 1 + ); + + error "Could not create socket - $!", 1 + unless $self->{socket}; + + # Announce ourselves + $self->_log ("Clearexec V$VERSION accepting clients at " . localtime); + + # Now wait for an incoming request + my $client; + + while () { + $client = $self->{socket}->accept; + + if ($? == -1) { + if ($!{EINTR}) { + next; + } else { + error "Accept called failed (Error: $?) - $!", 1; + } # if + } # if + + my $hostinfo = gethostbyaddr $client->peeraddr; + my $host = $hostinfo->name || $client->peerhost; + + $self->_verbose ("$host is requesting service"); + + if ($self->getMultithreaded) { + $self->{server} = $$; + + my $childpid; + + $self->_debug ("Spawning child to handle request"); + + error "Can't fork: $!" + unless defined ($childpid = fork); + + if ($childpid) { + $self->{pid} = $$; + + # On Unix/Linux, setting SIGCHLD to ignore auto reaps dead children. + $SIG{CHLD} = "IGNORE"; + $SIG{HUP} = \&_endServer; + $SIG{USR2} = \&_restartServer; + + $self->_debug ("Parent produced child [$childpid]"); + } else { + # In child process - ServiceClient + $self->{pid} = $$; + + $self->_debug ("Calling _serviceClient"); + $self->_serviceClient ($host, $client); + $self->_debug ("Returned from _serviceClient - exiting..."); + + exit; + } # if + } else { + $self->_serviceClient ($host, $client); + } # if + } # while +} # startServer + +1; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + DateUtils + Display + GetConfig + +=end man + +=begin html + +
    +Display
    +Display
    +GetConf
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this module. + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut diff --git a/clearadm/lib/User.pm b/clearadm/lib/User.pm new file mode 100644 index 0000000..60bbfa8 --- /dev/null +++ b/clearadm/lib/User.pm @@ -0,0 +1,253 @@ +=pod + +=head2 NAME $RCSfile: User.pm,v $ + +Return information about a user + +=head2 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.4 $ + +=item Created + +Tue Jan 3 11:36:10 PST 2006 + +=item Modified + +$Date: 2011/01/09 01:03:10 $ + +=back + +=head2 SYNOPSIS + +This module implements a User object which returns information about a user. + + my $user = new User ('adefaria'); + + print "Fullname: $user->{fullname}\n"; + print "EMail: $user->{email}\n"; + +=head2 DESCRIPTION + +This module instanciates a user object for the given user identifier and +then collects information about the user such as fullname, email, etc. It does +so by contacting Active Directory in a Windows domain or other directory servers +depending on the site. As such exactly what data members are available may +change or be different from site to site. + +=cut + +package User; + +use strict; +use warnings; + +use Carp; +use Net::LDAP; + +use GetConfig; + +# Seed options from config file +our %CLEAROPTS= GetConfig ("$FindBin::Bin/etc/clearuser.conf"); + +our $VERSION = '$Revision: 1.4 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +# Override options if in the environment +$CLEAROPTS{CLEARUSER_LDAPHOST} = $ENV{CLEARUSER_LDAPHOST} + if $ENV{CLEARUSER_LDAPHOST}; +$CLEAROPTS{CLEARUSER_BIND} = $ENV{CLEARUSER_BIND} + if $ENV{CLEARUSER_BIND}; +$CLEAROPTS{CLEARUSER_USERNAME} = $ENV{CLEARUSER_USERNAME} + if $ENV{CLEARUSER_USERNAME}; +$CLEAROPTS{CLEARUSER_PASSWORD} = $ENV{CLEARUSER_PASSWORD} + if $ENV{CLEARUSER_PASSWORD}; +$CLEAROPTS{CLEARUSER_BASEDN} = $ENV{CLEARUSER_BASEDN} + if $ENV{CLEARUSER_BASEDN}; + +my ($ldap, $ad); + +sub unix2sso ($) { + my ($unix) = @_; + + my $firstchar = substr $unix, 0, 1; + my $secondchar = substr $unix, 1, 1; + + # Crazy mod 36 math! + my $num = (ord ($firstchar) - 97) * 36 + (ord ($secondchar) - 97) + 100; + + my $return = $num . substr $unix, 2, 6; + + return $return; +} # unix2sso + +sub GetOwnerInfo ($) { + my ($userid) = @_; + + my @parts = split /(\/|\\)/, $userid; + + if (@parts == 3) { + $userid = $parts[2]; + } # if + + my $sso = unix2sso ($userid); + + unless ($ldap) { + $ldap = Net::LDAP->new ($CLEAROPTS{CLEARUSER_LDAPHOST}) + or croak 'Unable to create LDAP object'; + + $ad = $ldap->bind ( + "$CLEAROPTS{CLEARUSER_USERNAME}\@$CLEAROPTS{CLEARUSER_BIND}", + password => $CLEAROPTS{CLEARUSER_PASSWORD}); + } # unless + + $ad = $ldap->search ( + base => $CLEAROPTS{CLEARUSER_BASEDN}, + filter => "(&(objectclass=user)(sAMAccountName=$sso))", + ); + + $ad->code + && croak $ad->error; + + my @entries = $ad->entries; + + my %ownerInfo; + + if (@entries == 1) { + for (my $i = 0; $i < $ad->count; $i++) { + my $entry = $ad->entry ($i); + + foreach my $attribute ($entry->attributes) { + $ownerInfo{$attribute} = $entry->get_value ($attribute) + } # foreach + } # for + + return %ownerInfo; + } else { + return; + } # if +} # GetOwnerInfo + +=pod + +=item new ($id) + +Returns a new user object based on $id + +Parameters: + +=begin html + +
    + +=end html + +=over + +=item $id + +User identifier + +=back + +=begin html + +
    + +=end html + +Returns: + +=begin html + +
    + +=end html + +=over + +=item User object + +=back + +=begin html + +
    + +=end html + +=cut + +sub new ($) { + my ($class, $userid) = @_; + + croak "Must specify userid to User constructor" + if @_ == 1; + + my %members; + + $members{id} = $userid; + + my %ownerInfo = GetOwnerInfo ($userid); + + $members{$_} = $ownerInfo{$_} + foreach (keys %ownerInfo); + + return bless \%members, $class; +} # new + +1; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + GetConfig + +=end man + +=begin html + +
    +GetConfig
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this module + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut diff --git a/clearadm/lib/clearadm.sql b/clearadm/lib/clearadm.sql new file mode 100644 index 0000000..e8236c3 --- /dev/null +++ b/clearadm/lib/clearadm.sql @@ -0,0 +1,320 @@ +-- ----------------------------------------------------------------------------- +-- +-- File: $RCSfile: clearadm.sql,v $ +-- Revision: $Revision: 1.23 $ +-- Description: Create the clearadm database +-- Author: Andrew@DeFaria.com +-- Created: Tue Nov 30 08:46:42 EST 2010 +-- Modified: $Date: 2011/02/09 13:28:33 $ +-- Language: SQL +-- +-- Copyright (c) 2010, ClearSCM, Inc., all rights reserved +-- +-- ----------------------------------------------------------------------------- +-- Warning: The following line will delete the old database! +-- drop database if exists clearadm; + +-- Create a new database +create database clearadm; + +-- Now let's focus on this new database +use clearadm; + +-- system: Define what makes up a system or machine +create table system ( + name varchar (255) not null, + alias varchar (255), + active enum ( + 'true', + 'false' + ) not null default 'true', + admin tinytext, + email tinytext, + os tinytext, + type enum ( + 'Linux', + 'Unix', + 'Windows' + ) not null, + region tinytext, + port int default 25327, + lastheardfrom datetime, + notification varchar (255), + description text, + loadavgHist enum ( + '1 month', + '2 months', + '3 months', + '4 months', + '5 months', + '6 months', + '7 months', + '8 months', + '9 months', + '10 months', + '11 months', + '1 year' + ) not null default '6 months', + loadavgThreshold float (4,2) default 5.00, + + primary key (name) +) type=innodb; -- system + +-- clearcase: Information about a Clearcase system +create table clearcase ( + system varchar (255) not null, + ccver tinytext, + hardware tinytext, + licenseHost tinytext, + registryHost tinytext, + mvfsBlocksPerDirectory int, + mvfsCleartextMnodes int, + mvfsDirectoryNames int, + mvfsFileNames int, + mvfsFreeMnodes int, + mvfsInitialMnodeTableSize int, + mvfsMinCleartextMnodes int, + mvfsMinFreeMnodes int, + mvfsNamesNotFound int, + mvfsRPCHandles int, + interopRegion int, + scalingFactor int, + cleartextIdleLifetime int, + vobHashTableSize int, + cleartextHashTableSize int, + dncHashTableSize int, + threadHashTableSize int, + processHashTableSize int, + + foreign key systemLink (system) references system (name) + on delete cascade + on update cascade, + primary key (system) +) type=innodb; -- clearcase + +-- package: A package is any software package that we wish to keep track of +create table package ( + system varchar (255) not null, + name varchar (255) not null, + version tinytext not null, + vendor tinytext, + description text, + + key packageIndex (name), + key systemIndex (system), + foreign key systemLink (system) references system (name) + on delete cascade + on update cascade, + primary key (system, name) +) type=innodb; -- package + +-- filesystem: A systems file systems that we are monitoring +create table filesystem ( + system varchar (255) not null, + filesystem varchar (255) not null, + fstype tinytext not null, + mount tinytext, + threshold int default 90, + notification varchar (255), + filesystemHist enum ( + '1 month', + '2 months', + '3 months', + '4 months', + '5 months', + '6 months', + '7 months', + '8 months', + '9 months', + '10 months', + '11 months', + '1 year' + ) not null default '6 months', + + key filesystemIndex (filesystem), + foreign key systemLink (system) references system (name) + on delete cascade + on update cascade, + primary key (system, filesystem) +) type=innodb; -- filesystem + +-- fs: Contains a snapshot reading of a filesystem at a given date and time +create table fs ( + system varchar(255) not null, + filesystem varchar(255) not null, + mount varchar(255) not null, + timestamp datetime not null, + size bigint, + used bigint, + free bigint, + reserve bigint, + + key mountIndex (mount), + primary key (system, filesystem, timestamp), + foreign key filesystemLink (system, filesystem) + references filesystem (system, filesystem) + on delete cascade + on update cascade +) type=innodb; -- fs + +-- loadavg: Contains a snapshot reading of a system's load average +create table loadavg ( + system varchar(255) not null, + timestamp datetime not null, + uptime tinytext, + users int, + loadavg float (4,2), + + primary key (system, timestamp). + foreign key systemLink (system) references system (name) + on delete cascade + on update cascade +) type=innodb; -- loadavg + +-- vobs: Describe a system's vobs +create table vob ( + system varchar (255) not null, + tag varchar (255) not null, + + key systemIndex (system), + foreign key systemLink (system) references system (name) + on delete cascade + on update cascade, + primary key (tag) +) type=innodb; -- vob + +-- view: Describe views +create table view ( + system varchar (255) not null, + region varchar (255) not null, + tag varchar (255) not null, + owner tinytext, + ownerName tinytext, + email tinytext, + type enum ( + 'dynamic', + 'snapshot', + 'web' + ) not null default 'dynamic', + gpath tinytext, + modified datetime, + timestamp datetime, + age tinytext, + ageSuffix tinytext, + + key systemIndex (system), + foreign key systemLink (system) references system (name) + on delete cascade + on update cascade, + key regionIndex (region), + primary key (region, tag) +) type=innodb; -- view + +create table task ( + name varchar (255) not null, + system varchar (255), + description text, + command text not null, + restartable enum ( + 'true', + 'false' + ) not null default 'true', + + primary key (name) + foreign key systemLink (system) references system (name) + on delete cascade + on update cascade, +) type=innodb; -- task + +create table runlog ( + id int not null auto_increment, + task varchar (255) not null, + system varchar (255), + started datetime, + ended datetime, + alerted enum ( + 'true', + 'false' + ) not null default 'false', + status int, + message text, + + primary key (id, task, system), + foreign key taskLink (task) references task (name) + on delete cascade + on update cascade + foreign key systemLink (system) references system (name) + on delete cascade + on update cascade +) type=innodb; -- runlog + +create table schedule ( + name varchar (255) not null, + task varchar (255) not null, + notification varchar (255) not null, + frequency tinytext, + active enum ( + 'true', + 'false' + ) not null default 'true', + lastrunid int, + + primary key (name), + foreign key taskLink (task) references task (name) + on delete cascade + on update cascade + foreign key notificationLink (notification) references notification (name) + on delete cascade + on update cascade +) type=innodb; -- schedule + +create table alert ( + name varchar (255) not null, + type enum ( + 'email', + 'page', + 'im' + ) not null default 'email', + who tinytext, + + primary key (name) +) type=innodb; -- alert + +create table notification ( + name varchar (255) not null, + alert varchar (255) not null, + cond tinytext not null, + nomorethan enum ( + 'Once an hour', + 'Once a day', + 'Once a week', + 'Once a month' + ) not null default 'Once a day', + + primary key (name), + foreign key alertLink (alert) references alert (name) + on delete cascade + on update cascade, + ) type=innodb; -- notification + + create table alertlog ( + id int not null auto_increment, + alert varchar (255) not null, + system varchar (255) not null, + notification varchar (255) not null, + runlog int not null, + timestamp datetime, + message text, + + primary key (id, alert), + key (system), + foreign key alertLink (alert) references alert (name) + on delete cascade + on update cascade, + foreign key notificationLink (notification) references notification (name) + on delete cascade + on update cascade, + foreigh key runlogLink (runlog) references runlog (id) + on delete cascade + on update cascade +) type=innodb; -- alertlog \ No newline at end of file diff --git a/clearadm/lib/load.sql b/clearadm/lib/load.sql new file mode 100644 index 0000000..f89e5c2 --- /dev/null +++ b/clearadm/lib/load.sql @@ -0,0 +1,190 @@ +-- ----------------------------------------------------------------------------- +-- +-- File: $RCSfile: load.sql,v $ +-- Revision: $Revision: 1.10 $ +-- Description: Create predefined data in the Clearadm database +-- Author: Andrew@ClearSCM.com +-- Created: Tue Nov 30 08:46:42 EST 2010 +-- Modified: $Date: 2012/07/04 20:51:34 $ +-- Language: SQL +-- +-- Copyright (c) 2010, ClearSCM, Inc., all rights reserved +-- +-- ----------------------------------------------------------------------------- +-- Predefined alerts +insert into alert ( + name, + type +) values ( + 'Email admin', + 'email' +); + +-- Predefined notifications +insert into notification ( + name, + alert, + cond, + nomorethan +) values ( + 'Filesystem', + 'Email admin', + 'Filesystem over threshold', + 'Once a day' +); + +insert into notification ( + name, + alert, + cond, + nomorethan +) values ( + 'Heartbeat', + 'Email admin', + 'Heartbeat Failure', + 'Once an hour' +); + +insert into notification ( + name, + alert, + cond, + nomorethan +) values ( + 'Loadavg', + 'Email admin', + 'Loadavg over threshold', + 'Once an hour' +); + +insert into notification ( + name, + alert, + cond, + nomorethan +) values ( + 'Scrub', + 'Email admin', + 'Scrub Failure', + 'Once a day' +); + +insert into notification ( + name, + alert, + cond, + nomorethan +) values ( + 'System checkin', + 'Email admin', + 'Not respoding', + 'Once an hour' +); + +insert into notification ( + name, + alert, + cond, + nomorethan +) values ( + 'Update systems', + 'Email admin', + 'Non zero return', + 'Once an hour' +); + +-- Predefined tasks +insert into task ( + name, + system, + description, + command, +) values ( + 'Loadavg', + 'Localhost', + 'Obtain a loadavg snapshot on all systems', + 'updatela.pl', +); + +insert into task ( + name, + system, + description, + command, +) values ( + 'Filesystem', + 'Localhost', + 'Obtain a filesystem snapshot on all systems/filesystems', + 'updatefs.pl', +); + +insert into task ( + name, + system, + description, + command +) values ( + 'Scrub', + 'Localhost', + 'Scrub Clearadm database', + 'clearadmscrub.pl', +); + +insert into task ( + name, + system, + description +) values ( + 'System checkin', + 'Localhost', + 'Checkin from all systems', +); + +insert into task ( + name, + system, + description, + command +) values ( + 'Update systems', + 'Localhost', + 'Update all systems', + 'updatesystem.pl -host all', +); + +-- Predefined schedule +insert into schedule ( + name, + task, + notification, + frequency +) values ( + 'Loadavg', + 'Loadavg', + 'LoadAvg', + '5 Minutes' +); + +insert into schedule ( + name, + task, + notification, + frequency +) values ( + 'Filesystem', + 'Filesystem', + 'Filesystem', + '5 Minutes' +); + +insert into schedule ( + name, + task, + notification, + frequency +) values ( + 'Scrub', + 'Scrub', + 'Scrub', + '1 day +); diff --git a/clearadm/lib/users.sql b/clearadm/lib/users.sql new file mode 100644 index 0000000..b89b5b2 --- /dev/null +++ b/clearadm/lib/users.sql @@ -0,0 +1,27 @@ +-- ----------------------------------------------------------------------------- +-- +-- File: $RCSfile: users.sql,v $ +-- Revision: $Revision: 1.1 $ +-- Description: Create users for clearscm +-- Author: Andrew@ClearSCM.com +-- Created: Tue Nov 30 08:46:42 EST 2010 +-- Modified: $Date: 2010/12/13 17:16:30 $ +-- Language: SQL +-- +-- Copyright (c) 2010, ClearSCM, Inc., all rights reserved +-- +-- ----------------------------------------------------------------------------- +grant all privileges + on clearadm.* + to clearadm@"%" +identified by 'clearscm'; + +grant select + on clearadm.* + to cleareader@"%" +identified by 'cleareader'; + +grant insert, select, update, delete + on clearadm.* + to clearwriter@"%" +identified by 'clearwriter'; diff --git a/clearadm/lib/views.sql b/clearadm/lib/views.sql new file mode 100644 index 0000000..6c9e1d9 --- /dev/null +++ b/clearadm/lib/views.sql @@ -0,0 +1,64 @@ +-- MySQL dump 10.13 Distrib 5.1.41, for debian-linux-gnu (x86_64) +-- +-- Host: localhost Database: clearadm +-- ------------------------------------------------------ +-- Server version 5.1.41-3ubuntu12.8 + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `view` +-- + +DROP TABLE IF EXISTS `view`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `view` ( + `system` varchar(255) NOT NULL, + `region` varchar(255) NOT NULL, + `tag` varchar(255) NOT NULL, + `owner` tinytext, + `ownerName` tinytext, + `email` tinytext, + `type` enum('dynamic','snapshot','web') DEFAULT 'dynamic', + `gpath` tinytext, + `modified` datetime DEFAULT NULL, + `timestamp` datetime DEFAULT NULL, + `age` tinytext, + `ageSuffix` tinytext, + PRIMARY KEY (`region`,`tag`), + KEY `systemIndex` (`system`), + KEY `regionIndex` (`region`), + CONSTRAINT `view_ibfk_1` FOREIGN KEY (`system`) REFERENCES `system` (`name`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `view` +-- + +LOCK TABLES `view` WRITE; +/*!40000 ALTER TABLE `view` DISABLE KEYS */; +INSERT INTO `view` VALUES ('jupiter','home','tomsview1','defaria','Tom Connor','TomHillConnor@yahoo.com','web','/views/tconnor/tomsview1.vws','2010-12-25 00:00:00','2011-01-01 10:10:10','30','days'),('jupiter','home','tomsview2','defaria','Tom Connor','TomHillConnor@yahoo.com','snapshot','/views/tconnor/tomsview2.vws','2010-12-25 00:00:00','2011-01-01 10:10:10','45','days'),('jupiter','home','view1','defaria','Andrew DeFaria','Andrew@DeFaria.com','dynamic','/views/defaria/view1.vws','2010-01-01 00:00:00','2011-01-01 10:10:10','350','days'),('earth','home','view2','defaria','Andrew DeFaria','Andrew@DeFaria.com','snapshot','/views/defaria/view2.vws','2010-06-28 00:00:00','2011-01-01 10:10:10','210','days'),('jupiter','home','view3','defaria','Andrew DeFaria','Andrew@DeFaria.com','snapshot','/views/defaria/view3.vws','2010-12-25 00:00:00','2011-01-01 10:10:10','30','days'); +/*!40000 ALTER TABLE `view` ENABLE KEYS */; +UNLOCK TABLES; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2011-01-13 18:37:26 diff --git a/clearadm/load.vbs b/clearadm/load.vbs new file mode 100755 index 0000000..fdbae63 --- /dev/null +++ b/clearadm/load.vbs @@ -0,0 +1,52 @@ +option explicit + +sub display (msg) + wscript.echo msg +end sub + +sub checkError (msg) + if err.number = 0 then + exit sub + end if + + display "Error " & err.number & ": " & msg + + if err.description <> "" then + display err.description + end if + + wscript.quit err.number +end sub + +dim net, server, service, enumerator, instance, loadavg, locator, namespace + +' Get localhost's name +set net = CreateObject ("Wscript.Network") +server = net.ComputerName + +set locator = CreateObject ("WbemScripting.SWbemLocator") + +checkError "Unable to create locator object" + +' Connect to the namespace which is either local or remote +set service = locator.ConnectServer (server, namespace, "", "") + +checkError "Unable to connect to server " & server + +service.Security_.impersonationlevel = 3 + +set enumerator = service.InstancesOf ("Win32_Processor") + +checkError "Unable to query Win32_Processor" + +loadavg = 0 + +for each instance in enumerator + if not (instance is nothing) then + if instance.LoadPercentage <> "" then + loadavg = loadavg + instance.LoadPercentage + end if + end if +next + +display loadavg diff --git a/clearadm/log/clearagent.pl.log b/clearadm/log/clearagent.pl.log new file mode 100644 index 0000000..5f24b8e --- /dev/null +++ b/clearadm/log/clearagent.pl.log @@ -0,0 +1,91 @@ +clearagent.pl V1.11 started at Wed May 1 16:07:48 2013 +20130501@16:07:48 Clearexec V1.18 accepting clients at Wed May 1 16:07:48 2013 +clearagent.pl V1.11 started at Wed May 1 16:10:03 2013 +20130501@16:10:03 Clearexec V1.18 accepting clients at Wed May 1 16:10:03 2013 +clearagent.pl V1.11 started at Wed May 1 16:12:57 2013 +20130501@16:12:57 Clearexec V1.18 accepting clients at Wed May 1 16:12:57 2013 +Argument "no" isn't numeric in numeric le (<=) at /opt/clearscm/clearadm/../lib/Display.pm line 1059. +clearagent.pl V1.11 started at Wed May 1 16:18:03 2013 +20130501@16:18:03 Clearexec V1.18 accepting clients at Wed May 1 16:18:03 2013 +clearagent.pl V1.11 started at Wed May 1 16:20:52 2013 +20130501@16:20:52 Clearexec V1.18 accepting clients at Wed May 1 16:20:52 2013 +clearagent.pl V1.11 started at Wed May 1 16:26:45 2013 +20130501@16:26:45 Clearexec V1.18 accepting clients at Wed May 1 16:26:45 2013 +clearagent.pl V1.11 started at Wed May 1 16:34:21 2013 +20130501@16:34:21 Clearexec V1.18 accepting clients at Wed May 1 16:34:21 2013 +clearagent.pl V1.11 started at Wed May 1 16:42:10 2013 +20130501@16:42:10 Clearexec V1.18 accepting clients at Wed May 1 16:42:10 2013 +clearagent.pl V1.11 started at Wed May 1 16:43:50 2013 +20130501@16:43:50 Clearexec V1.18 accepting clients at Wed May 1 16:43:50 2013 +clearagent.pl V1.11 started at Thu May 2 10:28:22 2013 +20130502@10:28:22 Clearexec V1.18 accepting clients at Thu May 2 10:28:22 2013 +Argument "no" isn't numeric in numeric le (<=) at /opt/clearscm/clearadm/../lib/Display.pm line 1059. +clearagent.pl V1.11 started at Sat May 4 18:42:54 2013 +20130504@18:42:54 Clearexec V1.18 accepting clients at Sat May 4 18:42:54 2013 +Argument "no" isn't numeric in numeric le (<=) at /opt/clearscm/clearadm/../lib/Display.pm line 1059. +clearagent.pl V1.11 started at Sat May 4 18:47:10 2013 +20130504@18:47:10 Clearexec V1.18 accepting clients at Sat May 4 18:47:10 2013 +clearagent.pl V1.11 started at Sat May 4 18:48:51 2013 +20130504@18:48:51 Clearexec V1.18 accepting clients at Sat May 4 18:48:51 2013 +clearagent.pl V1.11 started at Sat May 4 18:56:10 2013 +20130504@18:56:10 Clearexec V1.18 accepting clients at Sat May 4 18:56:10 2013 +clearagent.pl V1.11 started at Sat May 4 19:31:58 2013 +20130504@19:31:58 Clearexec V1.18 accepting clients at Sat May 4 19:31:58 2013 +clearagent.pl V1.11 started at Sat May 4 19:33:36 2013 +20130504@19:33:36 Clearexec V1.18 accepting clients at Sat May 4 19:33:36 2013 +clearagent.pl V1.11 started at Tue May 7 11:05:07 2013 +20130507@11:05:07 Clearexec V1.18 accepting clients at Tue May 7 11:05:07 2013 +clearagent.pl V1.11 started at Wed May 8 11:16:35 2013 +20130508@11:16:35 Clearexec V1.18 accepting clients at Wed May 8 11:16:35 2013 +Argument "no" isn't numeric in numeric le (<=) at /opt/clearscm/clearadm/../lib/Display.pm line 1059. +clearagent.pl V1.11 started at Wed May 8 13:42:50 2013 +20130508@13:42:50 Clearexec V1.18 accepting clients at Wed May 8 13:42:50 2013 +clearagent.pl V1.11 started at Thu May 9 11:00:46 2013 +20130509@11:00:46 Clearexec V1.18 accepting clients at Thu May 9 11:00:46 2013 +clearagent.pl V1.11 started at Fri May 10 08:07:53 2013 +20130510@08:07:53 Clearexec V1.18 accepting clients at Fri May 10 08:07:53 2013 +clearagent.pl V1.11 started at Fri May 10 08:50:10 2013 +20130510@08:50:10 Clearexec V1.18 accepting clients at Fri May 10 08:50:10 2013 +Argument "no" isn't numeric in numeric le (<=) at /opt/clearscm/clearadm/../lib/Display.pm line 1059. +clearagent.pl V1.11 started at Sun May 12 18:23:55 2013 +20130512@18:23:55 Clearexec V1.18 accepting clients at Sun May 12 18:23:55 2013 +Argument "no" isn't numeric in numeric le (<=) at /opt/clearscm/clearadm/../lib/Display.pm line 1059. +clearagent.pl V1.11 started at Mon May 13 14:38:22 2013 +20130513@14:38:22 Clearexec V1.18 accepting clients at Mon May 13 14:38:22 2013 +Argument "no" isn't numeric in numeric le (<=) at /opt/clearscm/clearadm/../lib/Display.pm line 1059. +clearagent.pl V1.11 started at Sun May 19 07:08:23 2013 +20130519@07:08:23 Clearexec V1.18 accepting clients at Sun May 19 07:08:23 2013 +Argument "no" isn't numeric in numeric le (<=) at /opt/clearscm/clearadm/../lib/Display.pm line 1059. +clearagent.pl V1.11 started at Tue May 28 08:35:48 2013 +20130528@08:35:48 Clearexec V1.18 accepting clients at Tue May 28 08:35:48 2013 +Argument "no" isn't numeric in numeric le (<=) at /opt/clearscm/clearadm/../lib/Display.pm line 1059. +clearagent.pl V1.11 started at Thu May 30 21:10:08 2013 +20130530@21:10:08 Clearexec V1.18 accepting clients at Thu May 30 21:10:08 2013 +clearagent.pl V1.11 started at Thu May 30 21:12:44 2013 +20130530@21:12:44 Clearexec V1.18 accepting clients at Thu May 30 21:12:44 2013 +Argument "no" isn't numeric in numeric le (<=) at /opt/clearscm/clearadm/../lib/Display.pm line 1059. +clearagent.pl V1.11 started at Sun Jun 9 16:22:12 2013 +20130609@16:22:12 Clearexec V1.18 accepting clients at Sun Jun 9 16:22:12 2013 +Argument "no" isn't numeric in numeric le (<=) at /opt/clearscm/clearadm/../lib/Display.pm line 1059. +clearagent.pl V1.11 started at Mon Jun 10 17:08:33 2013 +20130610@17:08:33 Clearexec V1.18 accepting clients at Mon Jun 10 17:08:33 2013 +clearagent.pl V1.11 started at Mon Jun 10 17:35:40 2013 +20130610@17:35:40 Clearexec V1.18 accepting clients at Mon Jun 10 17:35:40 2013 +clearagent.pl V1.11 started at Mon Jun 10 17:38:04 2013 +20130610@17:38:04 Clearexec V1.18 accepting clients at Mon Jun 10 17:38:04 2013 +clearagent.pl V1.11 started at Mon Jun 10 17:40:14 2013 +20130610@17:40:14 Clearexec V1.18 accepting clients at Mon Jun 10 17:40:14 2013 +Argument "no" isn't numeric in numeric le (<=) at /opt/clearscm/clearadm/../lib/Display.pm line 1059. +clearagent.pl V1.11 started at Thu Jun 13 13:52:55 2013 +20130613@13:52:55 Clearexec V1.18 accepting clients at Thu Jun 13 13:52:55 2013 +Argument "no" isn't numeric in numeric le (<=) at /opt/clearscm/clearadm/../lib/Display.pm line 1059. +clearagent: ERROR #1: Could not create socket - Address already in use +clearagent.pl V1.11 started at Fri Jun 14 09:23:49 2013 +clearagent.pl V1.11 started at Sat Jun 15 11:25:56 2013 +20130615@11:25:56 Clearexec V1.18 accepting clients at Sat Jun 15 11:25:56 2013 +Argument "no" isn't numeric in numeric le (<=) at /opt/clearscm/clearadm/../lib/Display.pm line 1059. +clearagent.pl V1.11 started at Wed Jun 19 09:47:28 2013 +20130619@09:47:28 Clearexec V1.18 accepting clients at Wed Jun 19 09:47:28 2013 +Argument "no" isn't numeric in numeric le (<=) at /opt/clearscm/clearadm/../lib/Display.pm line 1059. +clearagent.pl V1.11 started at Wed Jul 10 13:32:54 2013 +20130710@13:32:54 Clearexec V1.18 accepting clients at Wed Jul 10 13:32:54 2013 diff --git a/clearadm/log/cleartasks.pl.log b/clearadm/log/cleartasks.pl.log new file mode 100644 index 0000000..8980971 --- /dev/null +++ b/clearadm/log/cleartasks.pl.log @@ -0,0 +1,62 @@ +cleartasks.pl V1.25 started at Wed Jun 19 12:32:28 2013 +updatela: ERROR: 20130621@16:42:53 System: earth Loadavg 5.15 Threshold 5.00 +updatela: ERROR: 20130624@08:20:10 System: earth Loadavg 5.54 Threshold 5.00 +updatela: ERROR #1: Unable to connect to system mars:25327 +cleartasks.pl V1.25 started at Mon Jun 24 12:18:10 2013 +cleartasks: ERROR: 20130628@12:48:27: Unable to talk to DB server. + +Clearadm::_getRecords: Unable to execute statement +Error #2006: MySQL server has gone away +SQL Statement: select * from system where name like '%%' or alias like '%%' + +Will try again in 30 seconds +cleartasks: ERROR: 20130629@12:49:00: Unable to talk to DB server. + +Clearadm::_getRecords: Unable to execute statement +Error #2006: MySQL server has gone away +SQL Statement: select * from system where name like '%%' or alias like '%%' + +Will try again in 30 seconds +cleartasks: ERROR: 20130630@12:49:32: Unable to talk to DB server. + +Clearadm::_getRecords: Unable to execute statement +Error #2006: MySQL server has gone away +SQL Statement: select * from system where name like '%%' or alias like '%%' + +Will try again in 30 seconds +cleartasks: ERROR: 20130701@12:50:04: Unable to talk to DB server. + +Clearadm::_getRecords: Unable to execute statement +Error #2006: MySQL server has gone away +SQL Statement: select * from system where name like '%%' or alias like '%%' + +Will try again in 30 seconds +cleartasks: ERROR: 20130702@12:50:37: Unable to talk to DB server. + +Clearadm::_getRecords: Unable to execute statement +Error #2006: MySQL server has gone away +SQL Statement: select * from system where name like '%%' or alias like '%%' + +Will try again in 30 seconds +cleartasks.pl V1.25 started at Wed Jul 3 10:44:00 2013 +updatela: ERROR: 20130703@17:27:52 System: earth Loadavg 5.13 Threshold 5.00 +updatela: ERROR #1: Unable to connect to system neptune:25327 +updatela: ERROR: 20130708@03:40:20 System: earth Loadavg 5.43 Threshold 5.00 +updatela: ERROR: 20130708@08:25:53 System: earth Loadavg 5.04 Threshold 5.00 +cleartasks: ERROR: 20130709@12:54:29: Unable to talk to DB server. + +Clearadm::_getRecords: Unable to execute statement +Error #2006: MySQL server has gone away +SQL Statement: select * from system where name like '%%' or alias like '%%' + +Will try again in 30 seconds +cleartasks: ERROR: 20130710@12:55:01: Unable to talk to DB server. + +Clearadm::_getRecords: Unable to execute statement +Error #2006: MySQL server has gone away +SQL Statement: select * from system where name like '%%' or alias like '%%' + +Will try again in 30 seconds +updatela: ERROR: 20130710@12:55:32 System: earth Loadavg 9.53 Threshold 5.00 +cleartasks.pl V1.25 started at Wed Jul 10 13:32:54 2013 +Couldn't connect to clearadm database as clearwriter@earth at /opt/clearscm/clearadm/cleartasks.pl line 510 diff --git a/clearadm/notes b/clearadm/notes new file mode 100644 index 0000000..7bc0cb8 --- /dev/null +++ b/clearadm/notes @@ -0,0 +1,27 @@ +I've created packages.vbs to get the packages from a Windows system. Output +basically looks like this: + +Name: Acrobat.com +Version: 1.6.65 +Vendor: Adobe Systems Incorporated +Description: Acrobat.com + +To do roughly the same for Linux (Well debain based Linux): + +$ dpkg-query -W -f='Name: ${Package}\nVersion: ${Version}\nVendor: ${Vendor}\nDescription: ${Description}\n' + +Which produces output like: + +Name: alien +Version: 8.79ubuntu0.1 +Vendor: +Description: convert and install rpm and other packages + Alien allows you to convert LSB, Red Hat, Stampede and Slackware Packages + into Debian packages, which can be installed with dpkg. + . + It can also generate packages of any of the other formats. + . + This is a tool only suitable for binary packages. + +I don't have a Redhat system to see how to get similar output from there. Then +there's Sun, HP-UX, etc... \ No newline at end of file diff --git a/clearadm/notifications.cgi b/clearadm/notifications.cgi new file mode 100755 index 0000000..a311ff9 --- /dev/null +++ b/clearadm/notifications.cgi @@ -0,0 +1,146 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: notifications.cgi,v $ + +Display notifications + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.5 $ + +=item Created: + +Mon Oct 25 11:10:47 PDT 2008 + +=item Modified: + +$Date: 2011/01/24 13:51:22 $ + +=back + +=head1 SYNOPSIS + + Usage notifications.cgi: [-u|sage] [-ve|rbose] [-d|ebug] + + Where: + -u|sage: Displays usage + -ve|rbose: Be verbose + -d|ebug: Output debug messages + +=head2 DESCRIPTION + +This script displays notifications + +=cut + +use strict; +use warnings; + +use FindBin; +use Getopt::Long; +use CGI qw (:standard :cgi-lib *table start_Tr end_Tr); +use CGI::Carp 'fatalsToBrowser'; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use Clearadm; +use ClearadmWeb; +use Display; +use Utils; + +my $VERSION = '$Revision: 1.5 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +my $clearadm; + +my %opts = Vars; + +# Main +GetOptions ( + usage => sub { Usage }, + verbose => sub { set_verbose }, + debug => sub { set_debug }, +) or Usage 'Invalid parameter'; + +# Announce ourselves +verbose "$FindBin::Script v$VERSION"; + +$clearadm = Clearadm->new; + +my $title = $opts{notification} + ? "Notifications matching $opts{notification}" + : 'Notifications'; + +heading $title; + +display h1 {class => 'center'}, $title; + +displayNotification ($opts{notification}); + +footing; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Clearadm + ClearadmWeb + Display + Utils + +=end man + +=begin html + +
    +Clearadm
    +ClearadmWeb
    +Display
    +Utils
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut \ No newline at end of file diff --git a/clearadm/packages.vbs b/clearadm/packages.vbs new file mode 100755 index 0000000..a098300 --- /dev/null +++ b/clearadm/packages.vbs @@ -0,0 +1,16 @@ +sub display (msg) + wscript.echo msg +end sub + +host = "." + +set wmi = GetObject ("winmgmts:\\" & host & "\root\cimv2") +set packages = wmi.ExecQuery ("Select * from Win32_Product",, 48) + +for each package in packages + display "Name: " & package.Name + display "Version: " & package.Version + display "Vendor: " & package.Vendor + display "Description: " & package.Description + display "-------------------------------------------------------------------------------" +next \ No newline at end of file diff --git a/clearadm/plot.cgi b/clearadm/plot.cgi new file mode 100755 index 0000000..d0d9efe --- /dev/null +++ b/clearadm/plot.cgi @@ -0,0 +1,320 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: plot.cgi,v $ + +Plot statistics + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.14 $ + +=item Created: + +Mon Oct 25 11:10:47 PDT 2008 + +=item Modified: + +$Date: 2011/01/28 21:30:45 $ + +=back + +=head1 DESCRIPTION + +Display a graph of either Loadavg or Filesystem data and provide controls for +the user to manipulate the chart. + +=cut + +use strict; +use warnings; + +use FindBin; +use CGI qw (:standard :cgi-lib start_table end_table start_Tr end_Tr); +use GD::Graph::area; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use Clearadm; +use ClearadmWeb; +use Display; + +my $VERSION = '$Revision: 1.14 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +my %opts = Vars; + +my $clearadm; + +sub displayGraph () { + my $parms; + + foreach (keys %opts) { + $parms .= '&' + if $parms; + $parms .= "$_=$opts{$_}" + } # foreach + + display '
    '; + + if ($opts{type} eq 'loadavg') { + unless ($opts{tiny}) { + display img {src => "plotloadavg.cgi?$parms", class => 'chart'}; + } else { + display img {src => "plotloadavg.cgi?$parms", border => 0}; + } # unless + } elsif ($opts{type} eq 'filesystem') { + unless ($opts{tiny}) { + display img {src => "plotfs.cgi?$parms", class => 'chart'}; + } else { + display img {src => "plotfs.cgi?$parms", border => 0}; + } # unless + } # if + + display '
    '; + + return +} # displayGraph + +sub displayFSInfo () { + if ($opts{filesystem}) { + display h3 {-align => 'center'}, 'Latest Filesystem Reading'; + } else { + display p; + return; + } # if + + display start_table {width => '800px', cellspacing => 1}; + + display start_Tr; + display th {class => 'labelCentered'}, 'Filesystem'; + display th {class => 'labelCentered'}, 'Type'; + display th {class => 'labelCentered'}, 'Mount'; + display th {class => 'labelCentered'}, 'Size'; + display th {class => 'labelCentered'}, 'Used'; + display th {class => 'labelCentered'}, 'Free'; + display th {class => 'labelCentered'}, 'Used %'; + display th {class => 'labelCentered'}, 'History'; + display th {class => 'labelCentered'}, 'Threshold'; + display end_Tr; + + my %filesystem = $clearadm->GetFilesystem ( + $opts{system}, + $opts{filesystem} + ); + my %fs = $clearadm->GetLatestFS ( + $opts{system}, + $opts{filesystem} + ); + + my $size = autoScale $fs{size}; + my $used = autoScale $fs{used}; + my $free = autoScale $fs{free}; + + display start_Tr; + display td {class => 'data'}, $filesystem{filesystem}; + display td {class => 'dataCentered'}, $filesystem{fstype}; + display td {class => 'data'}, $filesystem{mount}; + display td {class => 'dataRight'}, $size; + display td {class => 'dataRight'}, $used; + display td {class => 'dataRight'}, $free; + # TODO: Note that this percentages does not agree with df output. I'm not + # sure why. + display td {class => 'dataCentered'}, + sprintf ('%.0f%%', (($fs{reserve} + $fs{used}) / $fs{size} * 100)); + display td {class => 'dataCentered'}, $filesystem{filesystemHist}; + display td {class => 'dataCentered'}, "$filesystem{threshold}%"; + display end_Tr; + + display end_table; + + return; +} # displayInfo + +sub displayControls () { + my $class = $opts{type} =~ /loadavg/i + ? 'controls' + : 'filesystemControls'; + + display start_table { + align => 'center', + class => $class, + cellspacing => 0, + width => '800px', + }; + + my $systemLink = span {id => 'systemLink'}, a { + href => "systemdetails.cgi?system=$opts{system}", + }, 'System'; + + my $systemButtons = makeSystemDropdown ( + $systemLink, + $opts{system}, + 'updateFilesystems(this.value);updateSystemLink(this.value)' + ); + + my $startButtons = makeTimeDropdown ( + $opts{type}, + 'startTimestamp', + $opts{system}, + $opts{filesystem}, + 'Start', + $opts{start}, + $opts{scaling}, + ); + + my $endButtons = makeTimeDropdown ( + $opts{type}, + 'endTimestamp', + $opts{system}, + $opts{filesystem}, + 'End', + $opts{end}, + $opts{scaling}, + ); + + my $update = $opts{type} eq 'loadavg' + ? "updateSystem('$opts{system}')" + : "updateFilesystem('$opts{system}','$opts{filesystem}')"; + + my $intervalButtons = makeIntervalDropdown ( + 'Interval', + $opts{scaling}, + $update + ); + + display start_Tr; + display td $startButtons; + display td $intervalButtons; + display td $systemButtons; + display end_Tr; + + display start_Tr; + display td $endButtons; + display td 'Points', + input { + name => 'points', + value => $opts{points}, + class => 'inputfield', + size => 7, + style => 'text-align: right', + maxlength => 7, + }; + + if ($opts{type} eq 'loadavg') { + display td input { + type => 'submit', + value => 'Draw Graph', + }; + } else { + my $filesystemButtons = makeFilesystemDropdown ( + $opts{system}, + 'Filesystem', + undef, + "updateFilesystem('$opts{system}',this.value)", + ); + + display td $filesystemButtons; + + display end_Tr; + display start_Tr; + display td {align => 'center', colspan => 3}, + input {type => 'submit', value => 'Draw Graph'}; + } # if + + display end_Tr; + + display end_table; + + return; +} # displayControls + +$clearadm = Clearadm->new; + +my $title = ucfirst ($opts{type}) . ': ' . ucfirst $opts{system}; + +$title .= ":$opts{filesystem}" + if $opts{filesystem}; + +heading $title; + +display h1 {class => 'center'}, $title; + +display start_form { + method => 'get', + action => 'plot.cgi', +}; + +# Some hidden fields to pass along +display input {type => 'hidden', name => 'type', value => $opts{type}}; + +displayGraph; +displayFSInfo; +displayControls; + +display end_form; + +footing; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Clearadm + ClearadmWeb + Display + +=end man + +=begin html + +
    +Clearadm
    +ClearadmWeb
    +Display
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut \ No newline at end of file diff --git a/clearadm/plotfs.cgi b/clearadm/plotfs.cgi new file mode 100755 index 0000000..2556a34 --- /dev/null +++ b/clearadm/plotfs.cgi @@ -0,0 +1,226 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: plotfs.cgi,v $ + +Plot Filesystem usage + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.13 $ + +=item Created: + +Mon Dec 13 09:13:27 EST 2010 + +=item Modified: + +$Date: 2011/01/14 16:37:04 $ + +=back + +=head1 SYNOPSIS + + Usage plotfs.cgi: system= filesytem= + [height=] [width=] [color=] + [scaling=] [points=] [tiny=<0|1>] + + Where: + : Name of the system defined in the Clearadm database to + retrieve filesystem snapshots for. + : Name of the filesytem to plot information for + : Height of chart (Default: 480px - tiny: 40) + : Width of chart (Default: 800px - tiny: 150) + : A GD::Color color value (Default: lblue) + : Currently one of Minute, Hour, Day or Month. Specifies how + Clearadm::GetFS will scale the data returned (Default: Minute + - tiny: Day) + : Number of points to plot (Default: all points - tiny: 7) + +=head1 DESCRIPTION + +Draws a chart of the filesystem usage for the system and filesystem passed in. +Parameters such as height, width, color, scaling and points can be set +individually though more often the user will just use the web controls to set +them. Defaults produce a nice chart. Tiny mode is used by systemdetails.cgi to +draw tiny charts in the table. Setting tiny sets a number of the other chart +options to produce a standard, tiny chart. + +=cut + +use strict; +use warnings; + +use FindBin; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use Clearadm; +use ClearadmWeb; +use Display; + +use CGI qw (:standard :cgi-lib); +use GD::Graph::area; + +my %opts = Vars; + +my $VERSION = '$Revision: 1.13 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +$opts{color} ||= 'lblue'; +$opts{height} ||= 350; +$opts{width} ||= 800; + +if ($opts{tiny}) { + $opts{height} = 40; + $opts{width} = 150; + $opts{points} = 7; + $opts{scaling} = 'Day'; +} # if + +sub labelY ($) { + my ($value) = @_; + + return $opts{tiny} ? '' : $value; +} # labelY + +my $clearadm = Clearadm->new; + +my $graph = GD::Graph::area->new ($opts{width}, $opts{height}); + +graphError "System is required" + unless $opts{system}; + +graphError "Filesystem is required" + unless $opts{filesystem}; + +graphError "Points not numeric (points: $opts{points})" + if $opts{points} and $opts{points} !~ /^\d+$/; + +my @fs = $clearadm->GetFS ( + $opts{system}, + $opts{filesystem}, + $opts{start}, + $opts{end}, + $opts{points}, + $opts{scaling} +); + +graphError "No data found for $opts{system}:$opts{filesystem}" + unless @fs; + +my (@x, @y); + +my $i = 0; + +foreach (@fs) { + $i++; + my %fs = %{$_}; + + if ($opts{tiny}) { + push @x, ''; + } else { + push @x, $fs{timestamp}; + } # if + + push @y, sprintf ('%.2f', $fs{used} / (1024 * 1024)); +} +my @data = ([@x], [@y]); + +my $x_label_skip = @x > 1000 ? 200 + : @x > 100 ? 20 + : @x > 50 ? 2 + : @x > 10 ? 1 + : 0; + +my $x_label = $opts{tiny} ? '' : 'Filesystem Usage'; +my $y_label = $opts{tiny} ? '' : 'Used (Meg)'; +my $title = $opts{tiny} ? '' : "Filesystem usage for " + . "$opts{system}:$opts{filesystem}"; + +$graph->set ( + x_label =>$x_label, + x_labels_vertical => 1, + x_label_skip => $x_label_skip, + x_label_position => .5, + y_label => $y_label, + y_number_format => &labelY, + title => $title, + dclrs => [$opts{color}], + bgclr => 'white', + transparent => 0, + long_ticks => 1, + t_margin => 5, + b_margin => 5, + l_margin => 5, + r_margin => 5, +) or graphError $graph->error; + +my $image = $graph->plot(\@data) + or croak $graph->error; + +print "Content-type: image/png\n\n"; +print $image->png; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Clearadm + ClearadmWeb + Display + +=end man + +=begin html + +
    +Clearadm
    +ClearadmWeb
    +Display
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut diff --git a/clearadm/plotloadavg.cgi b/clearadm/plotloadavg.cgi new file mode 100755 index 0000000..d33afd9 --- /dev/null +++ b/clearadm/plotloadavg.cgi @@ -0,0 +1,215 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: plotloadavg.cgi,v $ + +Plot loadavg for a system + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.15 $ + +=item Created: + +Mon Dec 13 09:13:27 EST 2010 + +=item Modified: + +$Date: 2011/01/20 14:34:24 $ + +=back + +=head1 SYNOPSIS + + Usage plotfs.cgi: system= + [height=] [width=] [color=] + [scaling=] [points=] [tiny=<0|1>] + + Where: + : Name of the system defined in the Clearadm database to + retrieve loadavgs snapshots for. + : Height of chart (Default: 480px - tiny: 40) + : Width of chart (Default: 800px - tiny: 150) + : A GD::Color color value (Default: lblue) + : Currently one of Minute, Hour, Day or Month. Specifies how + Clearadm::GetFS will scale the data returned (Default: Minute + - tiny: Day) + : Number of points to plot (Default: all points - tiny: 7) + +=head1 DESCRIPTION + +Draws a chart of loadavg for the system passed in. Parameters such as height, +width, color, scaling and points can be set individually though more often the +user will just use the web controls to set them. Defaults produce a nice chart. +Tiny mode is used by systemdetails.cgi to draw tiny charts in the table. Setting +tiny sets a number of the other chart options to produce a standard, tiny chart. + +=cut + +use strict; +use warnings; + +use FindBin; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use Clearadm; +use ClearadmWeb; +use Display; + +use CGI qw (:standard :cgi-lib); +use GD::Graph::area; + +my %opts = Vars; + +my $VERSION = '$Revision: 1.15 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +$opts{color} ||= 'lyellow'; +$opts{height} ||= 400; +$opts{width} ||= 800; + +if ($opts{tiny}) { + $opts{height} = 40; + $opts{width} = 150; + $opts{points} = 24; + $opts{scaling} = 'Hour'; +} # if + +sub labelY ($) { + my ($value) = @_; + + return $opts{tiny} ? '' : $value; +} # labelY + +my $clearadm = Clearadm->new; + +my $graph = GD::Graph::area->new ($opts{width}, $opts{height}); + +graphError "System is required" + unless $opts{system}; + +graphError "Points not numeric (points: $opts{points})" + if $opts{points} and $opts{points} !~ /^\d+$/; + +my @loads = $clearadm->GetLoadavg ( + $opts{system}, + $opts{start}, + $opts{end}, + $opts{points}, + $opts{scaling} +); + +graphError "No loadavg data found for system $opts{system}" + unless @loads; + +my (@x, @y); + +foreach (@loads) { + my %load = %{$_}; + + if ($opts{tiny}) { + push @x, ''; + } else { + push @x, $load{timestamp}; + } # if + + push @y, $load{loadavg}; +} # foreach + +my @data = ([@x], [@y]); + +my $x_label_skip = @x > 1000 ? 200 + : @x > 100 ? 20 + : @x > 50 ? 2 + : @x > 10 ? 1 + : 0; + +my $x_label = $opts{tiny} ? '' : 'Time'; +my $y_label = $opts{tiny} ? '' : 'Load'; +my $title = $opts{tiny} ? '' : "Load Average for $opts{system}"; + +$graph->set ( + x_label => $x_label, + x_labels_vertical => 1, + x_label_skip => $x_label_skip, + x_label_position => .5, + y_label => $y_label, + y_number_format => &labelY, + title => $title, + dclrs => [$opts{color}], + bgclr => 'white', + transparent => 0, + long_ticks => 1, + t_margin => 5, + b_margin => 5, + l_margin => 5, + r_margin => 5, +) or graphError $graph->error; + +my $image = $graph->plot(\@data) + or croak $graph->error; + +print "Content-type: image/png\n\n"; +print $image->png; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Clearadm + ClearadmWeb + Display + +=end man + +=begin html + +
    +Clearadm
    +ClearadmWeb
    +Display
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut \ No newline at end of file diff --git a/clearadm/processalert.cgi b/clearadm/processalert.cgi new file mode 100755 index 0000000..aca56b0 --- /dev/null +++ b/clearadm/processalert.cgi @@ -0,0 +1,212 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: processalert.cgi,v $ + +Process an alert + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.3 $ + +=item Created: + +Mon Oct 25 11:10:47 PDT 2008 + +=item Modified: + +$Date: 2011/02/14 14:51:54 $ + +=back + +=head1 SYNOPSIS + + Usage processalert.cgi: [-u|sage] [-ve|rbose] [-d|ebug] + action=[Add|Delete|Edit|Post] alert= + + Where: + -u|sage: Displays usage + -ve|rbose: Be verbose + -d|ebug: Output debug messages + + action: Specifies to add, delete, edit or post an alert + alert: Name of alert to delete or edit + +=head2 DESCRIPTION + +This script adds, deletes, edits or posts an alert + +=cut + +use strict; +use warnings; + +use FindBin; +use Getopt::Long; +use CGI qw (:standard :cgi-lib *table start_Tr end_Tr); +use CGI::Carp 'fatalsToBrowser'; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use Clearadm; +use ClearadmWeb; +use Display; +use Utils; + +my $VERSION = '$Revision: 1.3 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +my $clearadm; + +# Main +GetOptions ( + usage => sub { Usage }, + verbose => sub { set_verbose }, + debug => sub { set_debug }, +) or Usage 'Invalid parameter'; + +# Announce ourselves +verbose "$FindBin::Script v$VERSION"; + +$clearadm = Clearadm->new; + +my %opts = Vars; + +my $title = 'Alerts'; + +heading $title; + +unless ($opts{'delete.x'} or $opts{'edit.x'} or $opts{action}) { + displayError 'Action not defined!'; + exit 1; +} # unless + +unless ($opts{action} eq 'Add') { + unless ($opts{name}) { + displayError 'Alert not defined!'; + exit 1; + } # unless +} # unless + +my ($err, $msg); + +if ($opts{action} eq 'Add') { + display h1 {class => 'center'}, 'Add Alert'; + editAlert; +} elsif ($opts{'delete.x'}) { + ($err, $msg) = $clearadm->DeleteAlert ($opts{name}); + + if ($msg !~ /Records deleted/) { + displayError "Unable to delete alert $opts{name}\n$msg"; + } else { + display h1 {class => 'center'}, $title; + display h3 {class => 'center'}, "Alert '$opts{name}' deleted"; + + displayAlert; + } # if +} elsif ($opts{'edit.x'}) { + display h1 {class => 'center'}, 'Edit Alert: ', $opts{name}; + editAlert ($opts{name}); +} elsif ($opts{action} eq 'Post') { + delete $opts{action}; + + my %system = $clearadm->GetAlert ($opts{name}); + + if (%system or $opts{oldname}) { + my $name = delete $opts{oldname}; + + $name ||= $opts{name}; + + ($err, $msg) = $clearadm->UpdateAlert ($name, %opts); + + if ($err) { + displayError "$msg (Status: $err)"; + } else { + display h1 {class => 'center'}, $title; + display h3 {class => 'center'}, "Alert '$opts{name}' updated"; + + displayAlert; + } # if + } else { + ($err, $msg) = $clearadm->AddAlert (%opts); + + if ($err) { + displayError "$msg (Status: $err)"; + } else { + + display h1 {class => 'center'}, $title; + display h3 {class => 'center'}, "Alert '$opts{name}' added"; + + displayAlert; + } # if + } # if +} else { + displayError "Unknown action - $opts{action}"; +} # if + +footing; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Clearadm + ClearadmWeb + Display + Utils + +=end man + +=begin html + +
    +Clearadm
    +ClearadmWeb
    +Display
    +Utils
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut \ No newline at end of file diff --git a/clearadm/processfilesystem.cgi b/clearadm/processfilesystem.cgi new file mode 100755 index 0000000..0b4d0a1 --- /dev/null +++ b/clearadm/processfilesystem.cgi @@ -0,0 +1,208 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: processfilesystem.cgi,v $ + +Delete a filesystem + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.4 $ + +=item Created: + +Mon Oct 25 11:10:47 PDT 2008 + +=item Modified: + +$Date: 2011/02/14 14:52:40 $ + +=back + +=head1 SYNOPSIS + + Usage processfileystem.cgi: [-u|sage] [-ve|rbose] [-d|ebug] + action=[edit|delete] + system= filesystem= + + Where: + -u|sage: Displays usage + -ve|rbose: Be verbose + -d|ebug: Output debug messages + + action: "edit" or "delete" to edit or delete the filesystem + system: System + filesystem: Filesystem to delete + +=head2 DESCRIPTION + +This script edits or deletes a filessystem from Clearadm + +=cut + +use strict; +use warnings; + +use FindBin; +use Getopt::Long; +use CGI qw (:standard :cgi-lib *table start_Tr end_Tr); +use CGI::Carp 'fatalsToBrowser'; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use Clearadm; +use ClearadmWeb; +use Display; +use Utils; + +my $VERSION = '$Revision: 1.4 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +my $clearadm; + +# Main +GetOptions ( + usage => sub { Usage }, + verbose => sub { set_verbose }, + debug => sub { set_debug }, +) or Usage 'Invalid parameter'; + +# Announce ourselves +verbose "$FindBin::Script v$VERSION"; + +$clearadm = Clearadm->new; + +my %opts = Vars; + +my $title = 'Process Filesystem: ' + . ucfirst $opts{system} + . ":$opts{filesystem}"; + +heading $title; + +unless ($opts{'delete.x'} or $opts{'edit.x'} or $opts{action}) { + displayError 'Action not defined!'; + footing; + exit 1; +} # unless + +unless ($opts{system}) { + displayError 'System not defined!'; + footing; + exit 1; +} # unless + +unless ($opts{filesystem}) { + displayError 'System not defined!'; + footing; + exit 1; +} # unless + +my ($err, $msg); + +if ($opts{'delete.x'}) { + ($err, $msg) = $clearadm->DeleteFilesystem ($opts{system}, $opts{filesystem}); + + if ($msg !~ /Records deleted/) { + displayError "Unable to delete $opts{system}:$opts{filesystem}\n$msg"; + } else { + display h1 { + class => 'center' + }, 'Filesystem ' . ucfirst $opts{system} . ":$opts{filesystem} deleted"; + } # if +} elsif ($opts{'edit.x'}) { + display h1 { + class => 'center' + }, 'Edit Filesystem: ', ucfirst $opts{system} . ":$opts{filesystem}"; + + editFilesystem ($opts{system}, $opts{filesystem}); +} elsif ($opts{action} eq 'Post') { + delete $opts{action}; + delete $opts{'edit.x'} + if $opts{'edit.x'}; + delete $opts{'edit.y'} + if $opts{'edit.y'}; + + ($err, $msg) = $clearadm->UpdateFilesystem ( + $opts{system}, + $opts{filesystem}, + %opts + ); + + if ($err) { + displayError "$msg (Status: $err)"; + } else { + display h1 {class => 'center'}, ucfirst $opts{system} . ":$opts{filesystem} updated"; + + displayFilesystem ($opts{system}); + } # if +} else { + displayError "Unknown action - $opts{action}"; +} # if + +footing; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Clearadm + ClearadmWeb + Display + Utils + +=end man + +=begin html + +
    +Clearadm
    +ClearadmWeb
    +Display
    +Utils
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut \ No newline at end of file diff --git a/clearadm/processnotification.cgi b/clearadm/processnotification.cgi new file mode 100755 index 0000000..28f6df2 --- /dev/null +++ b/clearadm/processnotification.cgi @@ -0,0 +1,226 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: processnotification.cgi,v $ + +Process a notification + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.3 $ + +=item Created: + +Mon Oct 25 11:10:47 PDT 2008 + +=item Modified: + +$Date: 2011/02/14 14:53:07 $ + +=back + +=head1 SYNOPSIS + + Usage processnotification.cgi: [-u|sage] [-ve|rbose] [-d|ebug] + action=[Add|Delete|Edit|Post] + notification= + + Where: + -u|sage: Displays usage + -ve|rbose: Be verbose + -d|ebug: Output debug messages + + action: Specifies to add, delete, edit or post an alert + notification: Name of notification to delete or edit + +=head2 DESCRIPTION + +This script adds, deletes, edits or posts a notification + +=cut + +use strict; +use warnings; + +use FindBin; +use Getopt::Long; +use CGI qw (:standard :cgi-lib *table start_Tr end_Tr); +use CGI::Carp 'fatalsToBrowser'; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use Clearadm; +use ClearadmWeb; +use Display; +use Utils; + +my $VERSION = '$Revision: 1.3 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +my $clearadm; + +# Main +GetOptions ( + usage => sub { Usage }, + verbose => sub { set_verbose }, + debug => sub { set_debug }, +) or Usage 'Invalid parameter'; + +# Announce ourselves +verbose "$FindBin::Script v$VERSION"; + +$clearadm = Clearadm->new; + +my %opts = Vars; + +my $title = 'Notifications'; + +heading $title; + +unless ($opts{'delete.x'} or $opts{'edit.x'} or $opts{action}) { + displayError 'Action not defined!'; + exit 1; +} # unless + +unless ($opts{action} eq 'Add') { + unless ($opts{name}) { + displayError 'Notification not defined!'; + exit 1; + } # unless +} # unless + +my ($err, $msg); + +if ($opts{action} eq 'Add') { + display h1 {class => 'center'}, 'Add Notification'; + editNotification; +} elsif ($opts{'delete.x'}) { + ($err, $msg) = $clearadm->DeleteNotification ($opts{name}); + + if ($msg !~ /Records deleted/) { + displayError "Unable to delete notification $opts{name}\n$msg"; + } else { + display h1 {class => 'center'}, $title; + display h3 {class => 'center'}, "Notification '$opts{name}' deleted"; + + displayNotification; + } # if +} elsif ($opts{'edit.x'}) { + display h1 {class => 'center'}, 'Edit Notification: ', $opts{name}; + editNotification ($opts{name}); +} elsif ($opts{action} eq 'Post') { + delete $opts{action}; + + my %notification = $clearadm->GetNotification ($opts{name}); + + # System and Filesystem are links to tables of the same name. If specified + # they need to match up to an existing system or they can be null. If we + # have this as an edited field and the user puts nothing in them then we + # get '', which won't work. So change '' -> undef. + + # TODO: Should think about making these dropdowns instead (However that would + # require AJAX to update filesystem when system changes). For now let's do + # this. +# $opts{system} = undef +# if $opts{system} eq ''; +# $opts{filesystem} = undef +# if $opts{filesystem} eq ''; + + if (%notification or $opts{oldname}) { + my $name = delete $opts{oldname}; + + $name ||= $opts{name}; + + ($err, $msg) = $clearadm->UpdateNotification ($name, %opts); + + if ($err) { + displayError "$msg (Status: $err)"; + } else { + display h1 {class => 'center'}, $title; + display h3 {class => 'center'}, "Notification '$opts{name}' updated"; + + displayNotification; + } # if + } else { + ($err, $msg) = $clearadm->AddNotification (%opts); + + if ($err) { + displayError "$msg (Status: $err)"; + } else { + + display h1 {class => 'center'}, $title; + display h3 {class => 'center'}, "Notification '$opts{name}' added"; + + displayNotification; + } # if + } # if +} else { + displayError "Unknown action - $opts{action}"; +} # if + +footing; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Clearadm + ClearadmWeb + Display + Utils + +=end man + +=begin html + +
    +Clearadm
    +ClearadmWeb
    +Display
    +Utils
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut \ No newline at end of file diff --git a/clearadm/processrunning.pl b/clearadm/processrunning.pl new file mode 100755 index 0000000..29c42a3 --- /dev/null +++ b/clearadm/processrunning.pl @@ -0,0 +1,189 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: processrunning.pl,v $ + +Checks to see if a process is running + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.2 $ + +=item Created: + +Mon Dec 13 09:13:27 EST 2010 + +=item Modified: + +$Date: 2013/05/21 16:42:17 $ + +=back + +=head1 SYNOPSIS + + Usage processrunning.pl: [-u|sage] [-ve|rbose] [-deb|ug] + -name + + Where: + -u|sage: Displays usage + + -ve|rbose: Be verbose + -deb|ug: Output debug messages + + -name: Name of the process to check for. + +=head1 DESCRIPTION + +This script will simply check to see if the process specified is running. Note +that it uses ps(1) and relies on the presence of Cygwin when run on Windows +systems. + +=cut + +use strict; +use warnings; + +use FindBin; +use Getopt::Long; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use Display; +use OSDep; +use Utils; + +my $VERSION = '$Revision: 1.2 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +sub restart ($) { + my ($restart) = @_; + + my ($status, @output) = Execute "$restart 2>&1"; + + unless ($status) { + display "Successfully executed restart option: $restart"; + + display $_ foreach (@output); + } else { + display "Unable to restart process using $restart (Status: $status)"; + + display $_ foreach (@output); + } # unless + + return $status; +} # restart + +# Main +error "Cannot use $FindBin::Script when using Windows - hint try using Cgywin", 1 + if $ARCH eq 'windows'; + +my ($name, $restart); + +GetOptions ( + usage => sub { Usage }, + verbose => sub { set_verbose }, + debug => sub { set_debug }, + 'name=s' => \$name, + 'restart=s' => \$restart, +) or Usage "Invalid parameter"; + +Usage 'Extraneous options: ' . join ' ', @ARGV + if @ARGV; + +Usage "Must specify process name" + unless $name; + +# Announce ourselves +verbose "$FindBin::Script V$VERSION"; + +my $opts = $ARCH eq 'cygwin' ? '-eWf' : '-ef'; + +my $cmd = "ps $opts | grep -i '$name' | grep -v \"grep -i \'$name\'\""; + +my ($status, @output) = Execute $cmd; + +unless ($status) { + display "No process found with the name of $name"; + + $status = restart $restart if $restart; + + exit $status; +} elsif ($status == 2) { + error "Unable to execute $cmd (Status: $status) - $!\n" + . join ("\n", @output), $status; +} # if + +foreach (@output) { + next + if /grep -i '$name'/; + + next + if /grep -i $name/; + + next + if /$FindBin::Script/; + + display "Found processes named $name"; + exit 0; +} # foreach + +display "Did not find any processes named $name"; + +exit restart $restart if $restart; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Display + Utils + +=end man + +=begin html + +
    +Display
    +Utils
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut \ No newline at end of file diff --git a/clearadm/processschedule.cgi b/clearadm/processschedule.cgi new file mode 100755 index 0000000..37baaec --- /dev/null +++ b/clearadm/processschedule.cgi @@ -0,0 +1,225 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: processschedule.cgi,v $ + +Process a schedule + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.3 $ + +=item Created: + +Mon Oct 25 11:10:47 PDT 2008 + +=item Modified: + +$Date: 2011/02/14 14:53:36 $ + +=back + +=head1 SYNOPSIS + + Usage processschedule.cgi: [-u|sage] [-ve|rbose] [-d|ebug] + action=[Add|Delete|Edit|Post] + schedule= + + Where: + -u|sage: Displays usage + -ve|rbose: Be verbose + -d|ebug: Output debug messages + + action: Specifies to add, delete, edit or post an alert + schedule: Schedule to delete or edit + +=head2 DESCRIPTION + +This script adds, deletes, edits or posts a schedule + +=cut + +use strict; +use warnings; + +use FindBin; +use Getopt::Long; +use CGI qw (:standard :cgi-lib *table start_Tr end_Tr); +use CGI::Carp 'fatalsToBrowser'; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use Clearadm; +use ClearadmWeb; +use Display; +use Utils; + +my $VERSION = '$Revision: 1.3 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +my $clearadm; + +# Main +GetOptions ( + usage => sub { Usage }, + verbose => sub { set_verbose }, + debug => sub { set_debug }, +) or Usage 'Invalid parameter'; + +# Announce ourselves +verbose "$FindBin::Script v$VERSION"; + +$clearadm = Clearadm->new; + +my %opts = Vars; + +my $title = 'Schedule'; + +heading $title; + +unless ($opts{'delete.x'} or $opts{'edit.x'} or $opts{action}) { + displayError 'Action not defined!'; + exit 1; +} # unless + +unless ($opts{action} eq 'Add') { + unless ($opts{name}) { + displayError 'Schedule not defined!'; + exit 1; + } # unless +} # unless + +my ($err, $msg); + +if ($opts{action} eq 'Add') { + display h1 {class => 'center'}, 'Add Schedule'; + editSchedule; +} elsif ($opts{'delete.x'}) { + ($err, $msg) = $clearadm->DeleteSchedule ($opts{name}); + + if ($msg !~ /Records deleted/) { + displayError "Unable to delete schedule $opts{name}\n$msg"; + } else { + display h1 {class => 'center'}, $title; + display h3 {class => 'center'}, "Schedule $opts{name} deleted"; + + displaySchedule; + } # if +} elsif ($opts{'edit.x'}) { + display h1 {class => 'center'}, 'Edit Schedule: ', $opts{name}; + editSchedule ($opts{name}); +} elsif ($opts{action} eq 'Post') { + delete $opts{action}; + + my $nbr = delete $opts{nbr}; + my $multiplier = delete $opts{multiplier}; + + if ($nbr == 1) { + $multiplier = substr $multiplier, 0, -1; + } # if + + $opts{frequency} = "$nbr $multiplier"; + + my %schedule = $clearadm->GetSchedule ($opts{name}); + + if (%schedule or $opts{oldname}) { + my $name = delete $opts{oldname}; + + $name ||= $opts{name}; + + $opts{active} = 'false' + unless $opts{active}; + + ($err, $msg) = $clearadm->UpdateSchedule ($name, %opts); + + if ($err) { + displayError "$msg (Status: $err)"; + } else { + display h1 {class => 'center'}, $title; + display h3 {class => 'center'}, "Schedule $opts{name} updated"; + + displaySchedule; + } # if + } else { + ($err, $msg) = $clearadm->AddSchedule (%opts); + + if ($err) { + displayError "$msg (Status: $err)"; + } else { + + display h1 {class => 'center'}, $title; + display h3 {class => 'center'}, "Schedule $opts{name} added"; + + displaySchedule; + } # if + } # if +} else { + displayError "Unknown action - $opts{action}"; +} # if + +footing; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Clearadm + ClearadmWeb + Display + Utils + +=end man + +=begin html + +
    +Clearadm
    +ClearadmWeb
    +Display
    +Utils
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut \ No newline at end of file diff --git a/clearadm/processsystem.cgi b/clearadm/processsystem.cgi new file mode 100755 index 0000000..5bec9c2 --- /dev/null +++ b/clearadm/processsystem.cgi @@ -0,0 +1,202 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: processsystem.cgi,v $ + +Process a system + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.6 $ + +=item Created: + +Mon Oct 25 11:10:47 PDT 2008 + +=item Modified: + +$Date: 2011/02/14 14:53:51 $ + +=back + +=head1 SYNOPSIS + + Usage processsystem.cgi: [-u|sage] [-ve|rbose] [-d|ebug] + action=[Add|Delete|Edit|Post] system= + + + Where: + -u|sage: Displays usage + -ve|rbose: Be verbose + -d|ebug: Output debug messages + + action: Specifies to add, delete, edit or post an alert + system: Name of alert to delete or edit + +=head2 DESCRIPTION + +This script adds, deletes, edits or posts an alert + +=cut + +use strict; +use warnings; + +use FindBin; +use Getopt::Long; +use CGI qw (:standard :cgi-lib *table start_Tr end_Tr); +use CGI::Carp 'fatalsToBrowser'; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use Clearadm; +use ClearadmWeb; +use Display; +use Utils; + +my $VERSION = '$Revision: 1.6 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +my $clearadm; + +# Main +GetOptions ( + usage => sub { Usage }, + verbose => sub { set_verbose }, + debug => sub { set_debug }, +) or Usage 'Invalid parameter'; + +# Announce ourselves +verbose "$FindBin::Script v$VERSION"; + +$clearadm = Clearadm->new; + +my %opts = Vars; + +my $title = 'Process System'; + +heading $title; + +unless ($opts{'delete.x'} or $opts{'edit.x'} or $opts{action} eq 'Post') { + displayError 'Action not defined!'; + exit 1; +} # unless + +unless ($opts{action} eq 'Add') { + unless ($opts{name}) { + displayError 'System not defined!'; + exit 1; + } # unless +} # unless + +my ($err, $msg); + +if ($opts{action} eq 'Add') { + display h1 {class => 'center'}, 'Add System'; + editSystem; +} elsif ($opts{'delete.x'}) { + ($err, $msg) = $clearadm->DeleteSystem ($opts{name}); + + display h1 { class => 'center'}, ucfirst $opts{name} . ' deleted'; +} elsif ($opts{'edit.x'}) { + display h1 {class => 'center'}, 'Edit System: ', ucfirst $opts{name}; + editSystem ($opts{name}); +} elsif ($opts{action} eq 'Post') { + delete $opts{action}; + + my %system = $clearadm->GetSystem ($opts{name}); + + $opts{active} = 'false' + unless $opts{active}; + + if (%system) { + ($err, $msg) = $clearadm->UpdateSystem ($opts{name}, %opts); + + if ($err) { + displayError "$msg (Status: $err)"; + } else { + display h1 {class => 'center'}, ucfirst $opts{name} . ' updated'; + + displaySystem ($opts{name}); + } # if + } else { + ($err, $msg) = $clearadm->AddSystem (%opts); + + if ($err) { + displayError "$msg (Status: $err)"; + } else { + display h1 {class => 'center'}, ucfirst $opts{name} . ' updated'; + + displaySystem ($opts{name}); + } # if + } # if +} else { + displayError "Unknown action - $opts{action}"; +} # if + +footing; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Clearadm + ClearadmWeb + Display + Utils + +=end man + +=begin html + +
    +Clearadm
    +ClearadmWeb
    +Display
    +Utils
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut \ No newline at end of file diff --git a/clearadm/processtask.cgi b/clearadm/processtask.cgi new file mode 100755 index 0000000..d729a15 --- /dev/null +++ b/clearadm/processtask.cgi @@ -0,0 +1,213 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: processtask.cgi,v $ + +Process a task + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.2 $ + +=item Created: + +Mon Oct 25 11:10:47 PDT 2008 + +=item Modified: + +$Date: 2011/04/09 05:38:26 $ + +=back + +=head1 SYNOPSIS + + Usage processtask.cgi: [-u|sage] [-ve|rbose] [-d|ebug] + action=[Add|Delete|Edit|Post] + task= + + Where: + -u|sage: Displays usage + -ve|rbose: Be verbose + -d|ebug: Output debug messages + + action: Specifies to add, delete, edit or post an alert + task: Task to delete or edit + +=head2 DESCRIPTION + +This script adds, deletes, edits or posts a schedule + +=cut + +use strict; +use warnings; + +use FindBin; +use Getopt::Long; +use CGI qw (:standard :cgi-lib *table start_Tr end_Tr); +use CGI::Carp 'fatalsToBrowser'; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use Clearadm; +use ClearadmWeb; +use Display; +use Utils; + +my $VERSION = '$Revision: 1.2 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +my $clearadm; + +# Main +GetOptions ( + usage => sub { Usage }, + verbose => sub { set_verbose }, + debug => sub { set_debug }, +) or Usage 'Invalid parameter'; + +# Announce ourselves +verbose "$FindBin::Script v$VERSION"; + +$clearadm = Clearadm->new; + +my %opts = Vars; + +my $title = 'Tasks'; + +heading $title; + +unless ($opts{'delete.x'} or $opts{'edit.x'} or $opts{action}) { + displayError 'Action not defined!'; + exit 1; +} # unless + +unless ($opts{action} eq 'Add') { + unless ($opts{name}) { + displayError 'Task not defined!'; + exit 1; + } # unless +} # unless + +my ($err, $msg); + +if ($opts{action} eq 'Add') { + display h1 {class => 'center'}, 'Add Task'; + editTask; +} elsif ($opts{'delete.x'}) { + ($err, $msg) = $clearadm->DeleteTask ($opts{name}); + + if ($msg !~ /Records deleted/) { + displayError "Unable to delete task $opts{name}\n$msg"; + } else { + display h1 {class => 'center'}, $title; + display h3 {class => 'center'}, "Task $opts{name} deleted"; + + displayTask; + } # if +} elsif ($opts{'edit.x'}) { + display h1 {class => 'center'}, 'Edit Task: ', $opts{name}; + editTask ($opts{name}); +} elsif ($opts{action} eq 'Post') { + delete $opts{action}; + + my %task = $clearadm->GetTask ($opts{name}); + + if (%task or $opts{oldname}) { + my $name = delete $opts{oldname}; + + $name ||= $opts{name}; + + ($err, $msg) = $clearadm->UpdateTask ($name, %opts); + + if ($err) { + displayError "$msg (Status: $err)"; + } else { + display h1 {class => 'center'}, $title; + display h3 {class => 'center'}, "Task $opts{name} updated"; + + displayTask; + } # if + } else { + ($err, $msg) = $clearadm->AddTask (%opts); + + if ($err) { + displayError "$msg (Status: $err)"; + } else { + + display h1 {class => 'center'}, $title; + display h3 {class => 'center'}, "Task $opts{name} added"; + + displayTask; + } # if + } # if +} else { + displayError "Unknown action - $opts{action}"; +} # if + +footing; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Clearadm + ClearadmWeb + Display + Utils + +=end man + +=begin html + +
    +Clearadm
    +ClearadmWeb
    +Display
    +Utils
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut \ No newline at end of file diff --git a/clearadm/readme.cgi b/clearadm/readme.cgi new file mode 100755 index 0000000..3f9d3f9 --- /dev/null +++ b/clearadm/readme.cgi @@ -0,0 +1,126 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: readme.cgi,v $ + +Display the README file + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.2 $ + +=item Created: + +Mon Oct 25 11:10:47 PDT 2008 + +=item Modified: + +$Date: 2011/02/14 14:54:19 $ + +=back + +=head1 DESCRIPTION + +This script displays the README file + +=cut + +use strict; +use warnings; + +use FindBin; +use Getopt::Long; + +use CGI qw (:standard *table start_Tr end_Tr); +use CGI::Carp 'fatalsToBrowser'; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use ClearadmWeb; +use Display; +use Utils; + +# Main +GetOptions ( + 'usage' => sub { Usage }, + 'verbose' => sub { set_verbose }, + 'debug' => sub { set_debug }, +) or Usage "Invalid parameter"; + +# Announce ourselves +verbose "$ClearadmWeb::APPNAME V$ClearadmWeb::VERSION"; + +heading; + +display '
    '; + +display h1 {class => 'center'}, "$ClearadmWeb::APPNAME: README"; + +display $_ + foreach (ReadFile 'README'); + +display '
    '; + +footing; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Display + Utils + +=end man + +=begin html + +
    +ClearadmWeb
    +Display
    +Utils
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut \ No newline at end of file diff --git a/clearadm/right.png b/clearadm/right.png new file mode 100644 index 0000000000000000000000000000000000000000..b91f4008bcf5e67d737d526edbfe9fe196cc22d9 GIT binary patch literal 918 zcmV;H18Mw;P)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2ipM~ z5il(NFDx|x00S0DL_t(I%XO1)Xw-Ea$KRjN_xruy?atdycbl-yX)2}Jw!$D75tS^0 zh#|{=R1bz&4SJwOsPtrlP(gdJASoimB8iNkKM)(u1{H{Bi6~*VDf@dn&vrLG?|yf` z-}n3JfoQVk`|h~2A0tBoz2PUOVkzZm zJ^49m=#6*GZ>YSDh>ei~L=g!y5fCzjt&>Jdxz(J62Muz}(AK$NMxU)cSoaEHzc&?Owg>SXQ8f*z6D=1MA3pmA4^ppCUwX1lcLEtn53_b5}Vqh@HmdTf| z^l;NrbELj6ejYmt2O=T|>F)N2-`uXEhhmSZ$#a7cp3imbT`u zXKMsPcYzGR0qEpNfBH$cF?b(Iw5tdz)`9J18aH3Yk0nRcHw7$ARDp2OlpO%T>p8WY zn!+8_fJc;pFICd=yH2am%s|UX>+#*Yx+ZVKOMbzn6Xoz9Z(NEDcOAx!m>&}#E_?UE zt1Ewa>ri)Yy1(e!Bzf;QK03Lolpei&%>JUsQIuMqmUDWd`q$d-WGOMfP70OpFo(i_ s=UZyRrZt7Yia|9{@T&oC_!jrjzw4D*$o18B(*OVf07*qoM6N<$f@1!x`v3p{ literal 0 HcmV?d00001 diff --git a/clearadm/runlog.cgi b/clearadm/runlog.cgi new file mode 100755 index 0000000..6080c6b --- /dev/null +++ b/clearadm/runlog.cgi @@ -0,0 +1,155 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: runlog.cgi,v $ + +Display the run log + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.11 $ + +=item Created: + +Mon Oct 25 11:10:47 PDT 2008 + +=item Modified: + +$Date: 2011/06/02 06:10:02 $ + +=back + +=head1 SYNOPSIS + + Usage runlog.cgi: [-u|sage] [-ve|rbose] [-d|ebug] + + Where: + -u|sage: Displays usage + -ve|rbose: Be verbose + -d|ebug: Output debug messages + +=head2 DESCRIPTION + +This script displays the run log + +=cut + +use strict; +use warnings; + +use FindBin; +use Getopt::Long; +use CGI qw (:standard :cgi-lib *table start_Tr end_Tr); +use CGI::Carp 'fatalsToBrowser'; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use Clearadm; +use ClearadmWeb; +use Display; +use Utils; + +my $VERSION = '$Revision: 1.11 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +my $clearadm; + +my %opts = Vars; + +$opts{start} ||= 0; +$opts{page} ||= 10; + +# Main +GetOptions ( + usage => sub { Usage }, + verbose => sub { set_verbose }, + debug => sub { set_debug }, +) or Usage 'Invalid parameter'; + +# Announce ourselves +verbose "$FindBin::Script v$VERSION"; + +$clearadm = Clearadm->new; + +my $title = 'Run Log'; + +heading $title; + +undef $opts{task} + if $opts{task} and $opts{task} eq 'All'; + +$opts{system} ||= 'All'; + +undef $opts{status} + if $opts{status} and $opts{status} eq 'All'; + +display h1 {class => 'center'}, $title; + +displayRunlog (%opts); + +footing; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Clearadm + ClearadmWeb + Display + Utils + +=end man + +=begin html + +
    +Clearadm
    +ClearadmWeb
    +Display
    +Utils
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut \ No newline at end of file diff --git a/clearadm/schedule.cgi b/clearadm/schedule.cgi new file mode 100755 index 0000000..2ba703f --- /dev/null +++ b/clearadm/schedule.cgi @@ -0,0 +1,142 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: schedule.cgi,v $ + +Display schedule + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.2 $ + +=item Created: + +Mon Oct 25 11:10:47 PDT 2008 + +=item Modified: + +$Date: 2011/02/14 14:54:32 $ + +=back + +=head1 SYNOPSIS + + Usage schedule.cgi: [-u|sage] [-ve|rbose] [-d|ebug] + + Where: + -u|sage: Displays usage + -ve|rbose: Be verbose + -d|ebug: Output debug messages + +=head2 DESCRIPTION + +This script displays schedule + +=cut + +use strict; +use warnings; + +use FindBin; +use Getopt::Long; +use CGI qw (:standard :cgi-lib *table start_Tr end_Tr); +use CGI::Carp 'fatalsToBrowser'; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use Clearadm; +use ClearadmWeb; +use Display; +use Utils; + +my $VERSION = '$Revision: 1.2 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +my $clearadm; + +# Main +GetOptions ( + usage => sub { Usage }, + verbose => sub { set_verbose }, + debug => sub { set_debug }, +) or Usage 'Invalid parameter'; + +# Announce ourselves +verbose "$FindBin::Script v$VERSION"; + +$clearadm = Clearadm->new; + +my $title = 'Schedule'; + +heading $title; + +display h1 {class => 'center'}, $title; + +displaySchedule; + +footing; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Clearadm + ClearadmWeb + Display + Utils + +=end man + +=begin html + +
    +Clearadm
    +ClearadmWeb
    +Display
    +Utils
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut \ No newline at end of file diff --git a/clearadm/setup.pl b/clearadm/setup.pl new file mode 100755 index 0000000..7724992 --- /dev/null +++ b/clearadm/setup.pl @@ -0,0 +1,329 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: setup.pl,v $ + +Setup Clearadm + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.1 $ + +=item Created: + +Mon Dec 13 09:13:27 EST 2010 + +=item Modified: + +$Date: 2011/01/09 18:12:05 $ + +=back + +=head1 SYNOPSIS + + Usage setup.pl: [-u|sage] [-ve|rbose] [-deb|ug] + [-package [all|agent|database|tasks|web]] + + Where: + -u|sage: Displays usage + + -ve|rbose: Be verbose + -deb|ug: Output debug messages + + -package: Which subpackage to set up (Default: all). + +=head1 DESCRIPTION + +This script will setup Clearadm packages on machines. You must be root +(or administrator on Windows) to setup packages. Setting up web package +configures the web server. Setting up the tasks portion sets up cleartasks +poriton. Cleartasks periodically runs the predefined and user defined +tasks and should only be set up on one machine. The agent package sets up +clearagent.pl. This should be run on all machines that you intend to monitor. +The database package sets up the Clearadm database. + +Default, sets up all packages on the current machine. + +=cut + +use strict; +use warnings; + +use Socket; + +use FindBin; +use Getopt::Long; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use Clearadm; +use Display; +use OSDep; +use Utils; + +my $VERSION = '$Revision: 1.1 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +sub SetupAgent () { + verbose 'Setting up Agent...'; + + my ($status, @output, $cmd); + + if ($ARCH eq 'cygwin') { + verbose '[Cygwin] Creating up Clearagent Service'; + + $cmd = 'cygrunsrv -I clearagent -p C:/Cygwin/bin/perl '; + $cmd .= '> -a "/opt/clearscm/clearadm/clearagent.pl -nodaemon"'; + + ($status, @output) = Execute "$cmd 2>&1"; + + error "Unable to execute $cmd (Status: $status)\n" . join ("\n", @output), 1 + if $status; + + verbose '[Cygwin] Starting Clearagent Service'; + + $cmd .= 'net start clearagent'; + ($status, @output) = Execute "$cmd 2>&1"; + + error "Unable to execute $cmd (Status: $status)\n" . join ("\n", @output), 1 + if $status; + } else { + my $Arch = ucfirst $ARCH; + + verbose 'Creating clearagent user'; + + $cmd = 'useradd -Mr clearagent'; + + ($status, @output) = Execute "$cmd 2>&1"; + + if ($status == 9) { + warning "The user clearagent already exists"; + } elsif ($status != 0) { + error "Unable to execute $cmd (Status: $status)\n" . join ("\n", @output), 1; + } # if + + verbose 'Setting permissions on log and var directories'; + + $cmd = "chmod 777 $Clearadm::CLEAROPTS{CLEARADM_BASE}/var;"; + $cmd .= "chmod 777 $Clearadm::CLEAROPTS{CLEARADM_BASE}/var/run;"; + $cmd .= "chmod 777 $Clearadm::CLEAROPTS{CLEARADM_BASE}/log"; + + ($status, @output) = Execute "$cmd 2>&1"; + + error "Unable to execute $cmd (Status: $status)\n" . join ("\n", @output), 1 + if $status; + + verbose "[$Arch] Setting up clearagent daemon"; + + # Symlink $CLEARADM/etc/conf.d/clearadm -> /etc/init.d + my $confdir = '/etc/init.d'; + + error "Cannot find conf.d directory ($confdir)", 1 + unless -d $confdir; + + unless (-e "$confdir/clearadm") { + $cmd = "ln -s $FindBin::Bin/etc/init.d/clearadm $confdir"; + + ($status, @output) = Execute "$cmd 2>&1"; + + error "Unable to execute $cmd (Status: $status)\n" . join ("\n", @output), 1 + if $status; + } # unless + + # Setup runlevel links + $cmd = 'update-rc.d clearagent defaults'; + + ($status, @output) = Execute "$cmd 2>&1"; + + error "Unable to execute $cmd (Status: $status)\n" . join ("\n", @output), 1 + if $status; + + verbose 'Starting clearagent'; + + $cmd = 'service clearagent start'; + + error "Unable to execute $cmd (Status: $status)\n" . join ("\n", @output), 1 + if $status; + } # if + + verbose "Done"; + + return; +} # SetupAgent + +sub SetupTasks () { + my ($status, @output, $cmd); + + verbose 'Setting up Tasks...'; + + # Symlink $CLEARADM/etc/conf.d/cleartasks -> /etc/init.d + my $confdir = '/etc/init.d'; + + error "Cannot find conf.d directory ($confdir)", 1 + unless -d $confdir; + + unless (-e "$confdir/clearadm") { + $cmd = "ln -s $FindBin::Bin/etc/init.d/cleartasks $confdir"; + + ($status, @output) = Execute "$cmd 2>&1"; + + error "Unable to execute $cmd (Status: $status)\n" . join ("\n", @output), 1 + if $status; + } # unless + + # Setup runlevel links + $cmd = 'update-rc.d cleartasks defaults'; + + ($status, @output) = Execute "$cmd 2>&1"; + + error "Unable to execute $cmd (Status: $status)\n" . join ("\n", @output), 1 + if $status; + + verbose 'Starting cleartasks'; + + $cmd = 'service cleartasks start'; + + ($status, @output) = Execute "$cmd 2>&1"; + + error "Unable to execute $cmd (Status: $status)\n" . join ("\n", @output), 1 + if $status; + + verbose 'Done'; + + return; +} # SetupTasks + +sub SetupWeb () { + verbose 'Setting up Web...'; + + my ($status, @output, $cmd); + + # Symlink $CLEARADM/etc/conf.d/clearadm -> /etc/apache2/conf.d + my $confdir = '/etc/apache2/conf.d'; + + error "Cannot find Apache 2 conf.d directory ($confdir)", 1 + unless -d $confdir; + + unless (-e "$confdir/clearadm") { + $cmd = "ln -s $FindBin::Bin/etc/conf.d/clearadm $confdir"; + + ($status, @output) = Execute "$cmd 2>&1"; + + error "Unable to execute $cmd (Status: $status)\n" . join ("\n", @output), 1 + if $status; + } # unless + + if ($ARCH eq 'cygwin') { + $cmd = 'net stop apache2; net start apache2'; + } else { + $cmd = '/etc/init.d/apache2 restart'; + } # if + + ($status, @output) = Execute "$cmd 2>&1"; + + error "Unable to execute $cmd (Status: $status)\n" . join ("\n", @output), 1 + if $status; + + verbose 'Done'; + + return; +} # SetupWeb + +sub SetupDatabase () { + verbose 'Setting up Database'; + + my ($status, @output, $cmd); + + # TODO: Probably need to use -u root -p and prompt for MySQL root user's + # password. + $cmd = "mysql < $Clearadm::CLEAROPTS{CLEARADM_BASE}/etc/clearadm.sql"; + + ($status, @output) = Execute "$cmd 2>&1"; + + error "Unable to execute $cmd (Status: $status)\n" . join ("\n", @output), 1 + if $status; + + verbose 'Setting up database users'; + + $cmd = "mysql clearadm < $Clearadm::CLEAROPTS{CLEARADM_BASE}/etc/users.sql"; + + ($status, @output) = Execute "$cmd 2>&1"; + + error "Unable to execute $cmd (Status: $status)\n" . join ("\n", @output), 1 + if $status; + + verbose 'Setting up predefined tasks'; + + $cmd = "mysql clearadm < $Clearadm::CLEAROPTS{CLEARADM_BASE}/etc/load.sql"; + + ($status, @output) = Execute "$cmd 2>&1"; + + error "Unable to execute $cmd (Status: $status)\n" . join ("\n", @output), 1 + if $status; + + verbose 'Done'; + + return; +} # SetupDatbase + +# Main +error "Cannot setup Clearadm when using Windows - hint try using Cgywin", 1 + if $ARCH eq 'windows'; + +Usage 'You must be root' + unless $> == 0 or $ARCH eq 'cygwin'; + +my $package = 'all'; + +GetOptions ( + usage => sub { Usage }, + verbose => sub { set_verbose }, + debug => sub { set_debug }, + 'package=s' => \$package, +) or Usage "Invalid parameter"; + +Usage 'Extraneous options: ' . join ' ', @ARGV + if @ARGV; + +# Announce ourselves +verbose "$FindBin::Script V$VERSION"; + +my @validPackages = ( + 'all', + 'agent', + 'database', + 'tasks', + 'web', +); + +my $lcpackage = lc $package; + +unless (InArray $lcpackage, @validPackages) { + Usage "Invalid -package $package"; +} # unless + +if ($lcpackage eq 'all') { + SetupAgent; + SetupDatabase; + SetupTasks; + SetupWeb; +} elsif ($lcpackage eq 'agent') { + SetupAgent; +} elsif ($lcpackage eq 'database') { + SetupDatabase; +} elsif ($lcpackage eq 'tasks') { + SetupTasks; +} elsif ($lcpackage eq 'agent') { + SetupWeb; +} # if + +=pod \ No newline at end of file diff --git a/clearadm/systemdetails.cgi b/clearadm/systemdetails.cgi new file mode 100755 index 0000000..18525fb --- /dev/null +++ b/clearadm/systemdetails.cgi @@ -0,0 +1,282 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: systemdetails.cgi,v $ + +System Details + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.22 $ + +=item Created: + +Mon Oct 25 11:10:47 PDT 2008 + +=item Modified: + +$Date: 2011/01/28 21:31:25 $ + +=back + +=head1 SYNOPSIS + + Usage systemdetails.cgi: [-u|sage] [-ve|rbose] [-d|ebug] + -s|ystem + + + Where: + -u|sage: Displays usage + -ve|rbose: Be verbose + -d|ebug: Output debug messages + + -s|ystem : Name of system to display details for + +=head2 DESCRIPTION + +This script displays the details for the given system + +=cut + +use strict; +use warnings; + +use FindBin; +use Getopt::Long; +use CGI qw (:standard :cgi-lib *table start_Tr end_Tr); +use CGI::Carp 'fatalsToBrowser'; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use Clearadm; +use ClearadmWeb; +use Clearcase::Server; +use Display; +use Utils; + +my $VERSION = '$Revision: 1.22 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +my $name = param ('system'); + +my $subtitle = 'System Details'; + +my $clearadm; + +sub DisplayTable ($) { + my ($server) = @_; + + my $unknown = font {-class => 'unknown'}, 'Unknown'; + + # Data fields + my $systemName = setField ($server->name); + my $ccVer = setField ($server->ccVer); + my $osVer = setField ($server->osVer); + my $hardware = setField ($server->hardware); + my $licenseHost = setField ($server->licenseHost); + my $registryHost = setField ($server->registryHost); + my $registryRegion = setField ($server->registryRegion); + my $mvfsBlocksPerDirectory = setField ($server->mvfsBlocksPerDirectory); + my $mvfsCleartextMnodes = setField ($server->mvfsCleartextMnodes); + my $mvfsDirectoryNames = setField ($server->mvfsDirectoryNames); + my $mvfsFileNames = setField ($server->mvfsFileNames); + my $mvfsFreeMnodes = setField ($server->mvfsFreeMnodes); + my $mvfsInitialMnodeTableSize = setField ($server->mvfsInitialMnodeTableSize); + my $mvfsMinCleartextMnodes = setField ($server->mvgsMinCleartextMnodes); + my $mvfsMinFreeMnodes = setField ($server->mvfsMinFreeMnodes); + my $mvfsNamesNotFound = setField ($server->mvfsNamesNotFound); + my $mvfsRPCHandles = setField ($server->mvfsRPCHandles); + my $interopRegion = setField ($server->interopRegion); + my $scalingFactor = setField ($server->scalingFactor); + my $cleartextIdleLifetime = setField ($server->cleartextIdleLifetime); + my $vobHashTableSize = setField ($server->vobHashTableSize); + my $cleartextHashTableSize = setField ($server->cleartextHashTableSize); + my $dncHashTableSize = setField ($server->dncHashTableSize); + my $threadHashTableSize = setField ($server->threadHashTableSize); + my $processHashTableSize = setField ($server->processHashTableSize); + + display h2 {class => 'center'}, 'Clearcase Information'; + + display start_table {cellspacing => 1, class => 'main'}; + + display start_Tr; + display th {class => 'label'}, 'Name:'; + display td {class => 'data', colspan => 4}, $systemName; + display th {class => 'label'}, 'Registry Host:'; + display td {class => 'data', colspan => 4}, + a {href => "systemdetails.cgi?server=$registryHost"}, $registryHost; + display end_Tr; + + display start_Tr; + display th {class => 'label'}, 'Registry Region:'; + display td {class => 'data', -colspan => 4}, $registryRegion; + display th {class => 'label'}, 'License Host:'; + display td {class => 'data', colspan => 4}, + a {-href => "systemdetails.cgi?server=$licenseHost"}, $licenseHost; + display end_Tr; + + display start_Tr; + display th {class => 'label'}, 'Clearcase Version:'; + display td {class => 'data', colspan => 4}, $ccVer; + display th {class => 'label'}, 'OS Version:'; + display td {class => 'data', colspan => 4}, $osVer; + display end_Tr; + + display start_Tr; + display th {class => 'label'}, 'Interop Region:'; + display td {class => 'dataRight'}, $interopRegion; + display th {class => 'label'}, 'Scaling Factor:'; + display td {class => 'dataRight'}, $scalingFactor; + display th {class => 'label'}, 'Clrtxt Idle Lifetime:'; + display td {class => 'dataRight'}, $cleartextIdleLifetime; + display th {class => 'label'}, 'VOB Hash:'; + display td {class => 'dataRight', -colspan => 3}, $vobHashTableSize; + display end_Tr; + + display start_Tr; + display th {class => 'label'}, 'Clrtxt Hash:'; + display td {class => 'dataRight'}, $cleartextHashTableSize; + display th {class => 'label'}, 'DNC Hash:'; + display td {class => 'dataRight'}, $dncHashTableSize; + display th {class => 'label'}, 'Thread Hash:'; + display td {class => 'dataRight'}, $threadHashTableSize; + display th {class => 'label'}, 'Process Hash:'; + display td {class => 'dataRight', colspan => 3}, $processHashTableSize; + display end_Tr; + + display start_Tr; + display th {class => 'labelCentered', -colspan => 10}, 'MVFS Parameters'; + display end_Tr; + + display start_Tr; + display th {class => 'label'}, 'Blocks/Dir:'; + display td {class => 'dataRight'}, $mvfsBlocksPerDirectory; + display th {class => 'label'}, 'Clrtxt Mnodes:'; + display td {class => 'dataRight'}, $mvfsCleartextMnodes; + display th {class => 'label'}, 'DirNames:'; + display td {class => 'dataRight'}, $mvfsDirectoryNames; + display th {class => 'label'}, 'FileNames:'; + display td {class => 'dataRight'}, $mvfsFileNames; + display th {class => 'label'}, 'Free Mnodes:'; + display td {class => 'dataRight'}, $mvfsFreeMnodes; + display end_Tr; + + display start_Tr; + display th {class => 'label'}, 'Init Mnodes:'; + display td {class => 'dataRight'}, $mvfsInitialMnodeTableSize; + display th {class => 'label'}, 'Min Clrtxt Mnodes:'; + display td {class => 'dataRight'}, $mvfsMinCleartextMnodes; + display th {class => 'label'}, 'Min Free Mnodes:'; + display td {class => 'dataRight'}, $mvfsMinFreeMnodes; + display th {class => 'label'}, 'Names Not Found:'; + display td {class => 'dataRight'}, $mvfsNamesNotFound; + display th {class => 'label'}, 'RPC Handles:'; + display td {class => 'dataRight'}, $mvfsRPCHandles; + display end_Tr; + + display end_table; + + return; +} # DisplayTable + +# Main +GetOptions ( + usage => sub { Usage }, + verbose => sub { set_verbose }, + debug => sub { set_debug }, + 'server=s' => \$name, +) or Usage 'Invalid parameter'; + +# Announce ourselves +verbose "$FindBin::Script v$VERSION"; + +my $title = $subtitle; + $title .= $name ? ": $name" : ''; + +$clearadm = Clearadm->new; + +$subtitle = h1 {class => 'center'}, 'System Details: ' . ucfirst $name; + +heading $title; + +unless ($name) { + display 'System is required'; + exit; +} + +display h1 {class => 'center'}, $subtitle; + +displaySystem $name; + +#my $server = new Clearcase::Server ($name); + +#DisplayTable $server; + +footing; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Clearadm + ClearadmWeb + Clearcase::Server + Display + Utils + +=end man + +=begin html + +
    +Clearadm
    +ClearadmWeb
    +Clearcase::Server
    +Display
    +Utils
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut \ No newline at end of file diff --git a/clearadm/systems.cgi b/clearadm/systems.cgi new file mode 100755 index 0000000..3b46cbf --- /dev/null +++ b/clearadm/systems.cgi @@ -0,0 +1,289 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: systems.cgi,v $ + +Systems + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.15 $ + +=item Created: + +Mon Oct 25 11:10:47 PDT 2008 + +=item Modified: + +$Date: 2011/02/14 14:54:59 $ + +=back + +=head1 SYNOPSIS + + Usage systems.cgi: [-u|sage] [-ve|rbose] [-d|ebug] + + Where: + -u|sage: Displays usage + -v|erbose: Be verbose + -d|ebug: Output debug messages + +=head2 DESCRIPTION + +This script displays all known systems + +=cut + +use strict; +use warnings; + +use FindBin; +use Getopt::Long; +use CGI qw (:standard :cgi-lib *table start_Tr end_Tr start_td end_td); +use CGI::Carp 'fatalsToBrowser'; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use Clearadm; +use ClearadmWeb; +use Display; +use Utils; + +my $VERSION = '$Revision: 1.15 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +my $subtitle = 'Systems Status: All Systems'; + +my $clearadm; + +sub DisplaySystems () { + display start_table {cellspacing => 1, class => 'main'}; + + display start_Tr; + display th {class => 'labelCentered'}, 'Action'; + display th {class => 'labelCentered'}, 'Name'; + display th {class => 'labelCentered'}, 'Alias'; + display th {class => 'labelCentered'}, 'Admin'; + display th {class => 'labelCentered'}, 'Type'; + display th {class => 'labelCentered'}, 'Last Contacted'; + display th {class => 'labelCentered'}, 'Current load'; + display th {class => 'labelCentered'}, 'Threshold'; + display th {class => 'labelCentered'}, 'Load Avg'; + display end_Tr; + + foreach ($clearadm->FindSystem) { + my %system = %{$_}; + + $system{alias} = setField $system{alias}, 'N/A'; + $system{region} = setField $system{region}, 'N/A'; + + my $admin = ($system{email}) + ? a {href => "mailto:$system{email}"}, $system{admin} + : $system{admin}; + + my $alias = ($system{alias} !~ 'N/A') + ? a { + href => "systemdetails.cgi?system=$system{name}" + }, $system{alias} + : $system{alias}; + + my %load = $clearadm->GetLatestLoadavg ($system{name}); + + $load{loadavg} ||= 0; + $load{timestamp} ||= 'unknown'; + + my $class = $load{loadavg} < $system{loadavgThreshold} + ? 'data' + : 'dataAlert'; + my $classRight = $load{loadavg} < $system{loadavgThreshold} + ? 'dataRight' + : 'dataRightAlert'; + my $classRightTop = $load{loadavg} < $system{loadavgThreshold} + ? 'dataRightTop' + : 'dataRightAlertTop'; + + display start_Tr; + display start_td {class => 'data'}; + + my $areYouSure = 'Are you sure you want to delete this system?\n' + . 'Doing so will remove all records related to ' + . $system{name} + . '\nincluding filesystem records and history as well as ' + . 'loadavg history.'; + + display start_form { + method => 'post', + action => "processsystem.cgi", + }; + + display input { + name => 'name', + type => 'hidden', + value => $system{name}, + }; + + display input { + name => 'delete', + type => 'image', + src => 'delete.png', + alt => 'Delete', + title => 'Delete', + value => 'Delete', + onclick => "return AreYouSure ('$areYouSure');" + }; + display input { + name => 'edit', + type => 'image', + src => 'edit.png', + alt => 'Edit', + title => 'Edit', + value => 'Edit', + }; + display checkbox { + disabled => 'disabled', + checked => $system{active} eq 'true' ? 1 : 0, + }; + + if ($system{notification}) { + display a {href => "alertlog.cgi?system=$system{name}"}, img { + src => 'alert.png', + border => 0, + alt => 'Alert!', + title => 'This system has alerts', + }; + } # if + + display end_form; + + display end_td; + display td {class => $class}, + a {href => "systemdetails.cgi?system=$system{name}"}, $system{name}; + display td {class => $class}, $alias; + display td {class => $class}, $admin; + display td {class => $class}, $system{type}; + + my $lastheardfromClass = 'dataCentered'; + my $lastheardfromData = $system{lastheardfrom}; + + unless ($clearadm->SystemAlive (%system)) { + $lastheardfromClass = 'dataCenteredAlert'; + $lastheardfromData = a { + href => "alertlog.cgi?system=$system{name}", + class => 'alert', + title => "Have not heard from $system{name} for a while" + }, $system{lastheardfrom}; + $system{notification} = 'Heartbeat'; + } # unless + + display td {class => $lastheardfromClass}, "$lastheardfromData ", + font {class => 'dim' }, "
    Up: $load{uptime}"; + display td {class => $classRightTop}, "$load{loadavg} ", + font {class => 'dim' }, "
    $load{timestamp}"; + display td {class => $classRightTop}, $system{loadavgThreshold}; + display td {class => $class}, + a { + href => + "plot.cgi?type=loadavg&system=$system{name}&scaling=Hour&points=24" + }, img { + src => "plotloadavg.cgi?system=$system{name}&tiny=1", + border => 0, + }; + display end_Tr; + } # foreach + + display end_table; + + display p {class => 'center'}, a { + href => 'processsystem.cgi?action=Add', + }, 'New system', img { + src => 'add.png', + border => 0, + }; + + return; +} # DisplaySystems + +# Main +GetOptions ( + usage => sub { Usage }, + verbose => sub { set_verbose }, + debug => sub { set_debug }, +) or Usage 'Invalid parameter'; + +# Announce ourselves +verbose "$FindBin::Script v$VERSION"; + +$clearadm = Clearadm->new; + +heading $subtitle; + +display h1 {class => 'center'}, $subtitle; + +DisplaySystems; + +footing; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Clearadm + ClearadmWeb + Display + Utils + +=end man + +=begin html + +
    +Clearadm
    +ClearadmWeb
    +Display
    +Utils
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut \ No newline at end of file diff --git a/clearadm/tasks.cgi b/clearadm/tasks.cgi new file mode 100755 index 0000000..4796275 --- /dev/null +++ b/clearadm/tasks.cgi @@ -0,0 +1,145 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: tasks.cgi,v $ + +Display tasks + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.3 $ + +=item Created: + +Mon Oct 25 11:10:47 PDT 2008 + +=item Modified: + +$Date: 2011/01/27 01:15:13 $ + +=back + +=head1 SYNOPSIS + + Usage tasks.cgi: [-u|sage] [-ve|rbose] [-d|ebug] + + Where: + -u|sage: Displays usage + -ve|rbose: Be verbose + -d|ebug: Output debug messages + +=head2 DESCRIPTION + +This script displays tasks + +=cut + +use strict; +use warnings; + +use FindBin; +use Getopt::Long; +use CGI qw (:standard :cgi-lib *table start_Tr end_Tr); +use CGI::Carp 'fatalsToBrowser'; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use Clearadm; +use ClearadmWeb; +use Display; +use Utils; + +my $VERSION = '$Revision: 1.3 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +my $clearadm; + +my %opts = Vars; + +# Main +GetOptions ( + usage => sub { Usage }, + verbose => sub { set_verbose }, + debug => sub { set_debug }, +) or Usage 'Invalid parameter'; + +verbose "$FindBin::Script v$VERSION"; + +$clearadm = Clearadm->new; + +my $title = $opts{task} + ? "Tasks matching $opts{task}" + : 'Tasks'; + +heading $title; + +display h1 {class => 'center'}, $title; + +displayTask ($opts{task}); + +footing; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Clearadm + ClearadmWeb + Display + Utils + +=end man + +=begin html + +
    +Clearadm
    +ClearadmWeb
    +Display
    +Utils
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut \ No newline at end of file diff --git a/clearadm/test.pl b/clearadm/test.pl new file mode 100755 index 0000000..8e89bbb --- /dev/null +++ b/clearadm/test.pl @@ -0,0 +1,240 @@ +#!/usr/bin/perl +use strict; +use warnings; + +use FindBin; +use Getopt::Long; + +use lib "$FindBin::Bin/lib"; +use lib "$FindBin::Bin/../lib"; + +use Clearadm; +use Display; +use Utils; + +my $clearadm = new Clearadm; + +my %system = ( + name => 'jupiter', + alias => 'defaria.com', + admin => 'Andrew DeFaria', + os => 'Linux defaria.com 2.6.32-25-generic-pae #45-Ubuntu SMP Sat Oct 16 21:01:33 UTC 2010 i686 GNU/Linux', + type => 'Linux', + description => 'Home server', +); + +my %package = ( + 'system' => 'jupiter', + 'name' => 'MySQL', + 'version' => '5.1', +); + +my %update; + +my %filesystem = ( + 'system' => 'jupiter', + 'filesystem' => '/dev/mapper/jupiter-root', + 'fstype' => 'ext3', + 'mount' => '/', + 'threshold' => 90, +); + +my %vob = ( + 'system' => 'jupiter', + 'tag' => '/vobs/clearscm', +); + +my %view = ( + 'system' => 'jupiter', + 'tag' => 'andrew_view', +); + +GetOptions ( + 'verbose' => sub { set_verbose }, + 'usage' => sub { Usage }, +); + +sub DisplayRecord (%) { + my (%record) = @_; + + foreach (keys %record) { + if ($record{$_}) { + display "$_: $record{$_}"; + } else { + display "$_: "; + } # if + } # foreach +} # DisplayRecord + +sub DisplayRecords (@) { + my (@records) = @_; + + DisplayRecord %{$_} + foreach (@records); +} # DisplayRecords + +sub TestSystem () { + verbose "Adding system $system{name}"; + + my ($err, $msg) = $clearadm->AddSystem (%system); + + if ($err == 1062) { + warning 'You already have that record!'; + } elsif ($err) { + error $msg, $err; + } # if + + verbose "Finding systems that match \'jup\'"; + DisplayRecords $clearadm->FindSystem ('jup'); + + verbose "Getting record for \'jupiter\'"; + DisplayRecord $clearadm->GetSystem ('jupiter'); + + verbose "Finding systems that match \'def\'"; + DisplayRecords $clearadm->FindSystem ('def'); + + verbose "Getting record for \'defaria.com\'"; + DisplayRecord $clearadm->GetSystem ('defaria.com'); + + %update = ( + 'region' => 'East Coast', + ); + + verbose "Updating system $system{name}"; + + ($err, $msg) = $clearadm->UpdateSystem ($system{name}, %update); + + error $msg, $err + if $err; +} # TestaSystem + +sub TestPackage () { + verbose "Adding package $package{name}"; + + my ($err, $msg) = $clearadm->AddPackage (%package); + + if ($err == 1062) { + warning 'You already have that record!'; + } elsif ($err) { + error $msg, $err; + } # if + + %update = ( + 'vendor' => 'ClearSCM', + 'description' => 'This is not ClearSCM\'s version of MySQL', + ); + + verbose "Updating package $package{name}"; + + ($err, $msg) = $clearadm->UpdatePackage ($package{system}, $package{name}, %update); + + error $msg, $err + if $err; + + verbose "Finding packages for $system{name} that match \'My\'"; + DisplayRecords $clearadm->FindPackage ($system{name}, 'My'); + + verbose ("Getting package for $system{name} record for \'MySQL\'"); + DisplayRecord $clearadm->GetPackage ($system{name}, 'MySQL'); +} # TestPackage + +sub TestFilesystem () { + verbose "Adding filesystem $filesystem{filesystem}"; + + my ($err, $msg) = $clearadm->AddFilesystem (%filesystem); + + error $msg, $err + if $err; + + $filesystem{filesystem} = '/dev/sda5'; + $filesystem{path} = '/disk2'; + + verbose "Adding filesystem $filesystem{filesystem}"; + + ($err, $msg) = $clearadm->AddFilesystem (%filesystem); + + error $msg, $err + if $err; + + %update = ( + 'filesystem' => '/dev/sdb5', + ); + + verbose "Updating filesystem $filesystem{filesystem}"; + + ($err, $msg) = $clearadm->UpdateFilesystem ( + $filesystem{system}, $filesystem{filesystem}, %update + ); + + error $msg, $err + if $err; + + verbose "Finding filesystems for $system{name} that match \'My\'"; + DisplayRecords $clearadm->FindFilesystem ($system{name}, 'root'); + + verbose ("Getting filesystem for $system{name} record for \'/dev/sdb5\'"); + DisplayRecord $clearadm->GetFilesystem ($system{name}, '/dev/sdb5'); +} # TestFilesystem + +sub TestVob () { + verbose "Adding vob $vob{tag}"; + + my ($err, $msg) = $clearadm->AddVob (%vob); + + error $msg, $err + if $err; + + $vob{tag} = '/vobs/clearscm_old'; + + verbose "Adding vob $vob{tag}"; + + ($err, $msg) = $clearadm->AddVob (%vob); + + error $msg, $err + if $err; + + verbose "Finding vobs that match \'clearscm\'"; + DisplayRecords $clearadm->FindVob ('clearscm'); + + verbose ("Getting vob for \'clearscm\'"); + DisplayRecord $clearadm->GetVob ('clearscm'); +} # TestVob + +sub TestView () { + verbose "Adding view $view{tag}"; + + my ($err, $msg) = $clearadm->AddView (%view); + + error $msg, $err + if $err; + + $view{tag} = 'andrew2_view'; + + verbose "Adding view $view{tag}"; + + ($err, $msg) = $clearadm->AddView (%view); + + error $msg, $err + if $err; + + verbose "Finding views that match \'andrew\'"; + DisplayRecords $clearadm->FindView ('andrew'); + + verbose ("Getting view for \'view\'"); + DisplayRecord $clearadm->GetView ('andrew'); +} # TestView + +TestSystem; +TestPackage; +TestFilesystem; +TestVob; +TestView; + +######################## +verbose "Deleting system $system{name}"; + +my ($err, $msg) = $clearadm->DeleteSystem ($system{name}); + +error $msg, $err + if $err; + \ No newline at end of file diff --git a/clearadm/up.png b/clearadm/up.png new file mode 100644 index 0000000000000000000000000000000000000000..b4602e2a74012f0607455af30df83da565668a3d GIT binary patch literal 846 zcmV-U1F`&xP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2ipM- z4G1WlCaG=!00PcQL_t(2&ux=SY*cj|g}>ju(>r$_oo7osFcdOnrVS~m<)Mu=^~FMk zNQk;|B?~u(1uK^(t~4%8Ox(IM4WcC4jnPC%j4?)wD4JTZ9cd{}r@ftLd*{yF$KQ=$ zBAnG(oG)i{jt&U-OMsAo@vjdalz9-imwfR#vbjew%s8Icp-WHT`V3E={@i%}rBrgX zqy(B3e_%34eRr&}!>f)LmGh#Po}w6kmC@zb}%@{}4LAEW6q?>qK#4-_*sazytk}^VSa(Y^3j!Y12RiWon92w@Vci!gm zPruXBHPLk)H`<>-(_$LF@8kR3J`o0^rc53^f*4_(t_vdAPLEI|#{NTx@kQ{JBJjKb zE#zuA&26@dUxpLjye4SLZHV z{;q2WsK@gI@;w^ z_|)NXm5&<|jmOo#LP18y$4DppDGa7%zPzT&bC))l|K>Hko~;8YP|51b?2^@f`g=PV zNu<-_8K%_JyKuA+HK)s$^Vj+1(nX6OzJ7+C`YlB6JB7*E@v~a-;Msxv$V2gDLf^4F zzE!DI{FU-7i&x*Ly;&umhV43XZ{H#crN{zX!=owU$t-z>% literal 0 HcmV?d00001 diff --git a/clearadm/updatefs.pl b/clearadm/updatefs.pl new file mode 100755 index 0000000..75f830c --- /dev/null +++ b/clearadm/updatefs.pl @@ -0,0 +1,288 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: updatefs.pl,v $ + +Update Filesystem + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.29 $ + +=item Created: + +Mon Dec 13 09:13:27 EST 2010 + +=item Modified: + +$Date: 2011/06/16 15:12:50 $ + +=back + +=head1 SYNOPSIS + + Usage updatefs.pl: [-u|sage] [-ve|rbose] [-deb|ug] + [-host [|all]] [-fs [|all]] + + Where: + -u|sage: Displays usage + + -ve|rbose: Be verbose + -deb|ug: Output debug messages + + -host [|all]: Update host or all hosts (Default: all) + -fs [|all]: Update filesystem or all (Default: all) + +=head1 DESCRIPTION + +This script will record the state of a filesystem. + +=cut + +use strict; +use warnings; + +use Net::Domain qw(hostname); +use FindBin; +use Getopt::Long; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use Clearadm; +use Clearexec; +use DateUtils; +use Display; +use Utils; + +my $VERSION = '$Revision: 1.29 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +my $clearadm = Clearadm->new; +my $clearexec = Clearexec->new; + +my ($host, $fs); + +# Given a host and a filesystem, formulate a fs record +sub snapshotFS ($$) { + my ($systemRef, $filesystem) = @_; + + my %system = %{$systemRef}; + + my %filesystem = $clearadm->GetFilesystem ($system{name}, $filesystem); + + unless (%filesystem) { + error "Filesystem $host:$filesystem not in clearadm database - try adding it"; + + return; + } # unless + + my %fs = ( + system => $system{name}, + filesystem => $filesystem, + timestamp => Today2SQLDatetime, + ); + + # Sun is so braindead! + # TODO: Verify this works under Solaris + if ($system{type} eq 'Unix') { + foreach ('ufs', 'vxfs') { + my $cmd = "/usr/bin/df -k -F $filesystem{mount}"; + + my ($status, @unixfs) = $clearexec->execute ($cmd); + + if ($status != 0) { + error ('Unable to determine fsinfo for ' + . "$system{name}:$filesystem{mount} ($cmd)\n" . + join "\n", @unixfs + ); + + return; + } # if + + # Skip heading + shift @unixfs; + + for (my $i = 0; $i < scalar @unixfs; $i++) { + my $firstField; + + # Trim leading and trailing spaces + $unixfs[$i] =~ s/^\s+//; + $unixfs[$i] =~ s/\s+$//; + + my @fields = split /\s+/, $unixfs[$i]; + + if (@fields == 1) { + $firstField = 0; + $i++; + + @fields = split /\s+/, $unixfs[$i];; + } else { + $firstField = 1; + } #if + + $fs{size} = $fields[$firstField] * 1024; + $fs{used} = $fields[$firstField + 1] * 1024; + $fs{free} = $fields[$firstField + 2] * 1024; + $fs{reserve} = $fs{size} - $fs{used} - $fs{free}; + } # for + } # foreach + } elsif ($system{type} eq 'Linux' or $system{type} eq 'Windows') { + my $cmd = "/bin/df --block-size=1 -P $filesystem{mount}"; + + my ($status, @linuxfs) = $clearexec->execute ($cmd); + + if ($status != 0) { + error ("Unable to determine fsinfo for $system{name}:$filesystem{mount}\n" + . join "\n", @linuxfs + ); + + return; + } # if + + # Skip heading + shift @linuxfs; + + $_ = shift @linuxfs; + my @fields = split; + + $fs{size} = $fields[1]; + $fs{used} = $fields[2]; + $fs{free} = $fields[3]; + $fs{mount} = $fields[5]; + $fs{reserve} = $fs{size} - $fs{used} - $fs{free}; + } # if + + return %fs; +} # snapshotFS + +# Main +GetOptions ( + 'usage' => sub { Usage }, + 'verbose' => sub { set_verbose }, + 'debug' => sub { set_debug }, + 'host=s' => \$host, + 'fs=s' => \$fs, +) or Usage "Invalid parameter"; + +Usage 'Extraneous options: ' . join ' ', @ARGV + if @ARGV; + +# Announce ourselves +verbose "$FindBin::Script V$VERSION"; + +my $exit = 0; + +foreach my $system ($clearadm->FindSystem ($host)) { + next if $$system{active} eq 'false'; + + my $status = $clearexec->connectToServer ( + $$system{name}, + $$system{port} + ); + + unless ($status) { + verbose "Unable to connect to system $$system{name}:$$system{port}"; + next; + } # unless + + foreach my $filesystem ($clearadm->FindFilesystem ($$system{name}, $fs)) { + verbose "Snapshotting $$system{name}:$$filesystem{filesystem}"; + + my %fs = snapshotFS ($system, $$filesystem{filesystem}); + + if (%fs) { + my ($err, $msg) = $clearadm->AddFS (%fs); + + error $msg, $err if $err; + } # if + + # Check if over threshold + my %notification = $clearadm->GetNotification ('Filesystem'); + + next + unless %notification; + + my $usedPct = sprintf ( + '%.2f', + (($fs{used} + $fs{reserve}) / $fs{size}) * 100 + ); + + if ($usedPct >= $$filesystem{threshold}) { + $exit = 2; + display YMDHMS . " System: $$filesystem{system} " + . "Filesystem: $$filesystem{filesystem} Used: $usedPct% " + . "Threshold: $$filesystem{threshold}"; + } else { + $clearadm->ClearNotifications ($$system{name}, $$filesystem{filesystem}); + } # if + } # foreach + + $clearexec->disconnectFromServer; +} # foreach + +exit $exit; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Clearadm + Clearexec + DateUtils + Display + Utils + +=end man + +=begin html + +
    +Clearadm
    +Clearexec
    +DateUtils
    +Display
    +Utils
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut diff --git a/clearadm/updatela.pl b/clearadm/updatela.pl new file mode 100755 index 0000000..6995175 --- /dev/null +++ b/clearadm/updatela.pl @@ -0,0 +1,248 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: updatela.pl,v $ + +Update Load Average + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.29 $ + +=item Created: + +Mon Dec 13 09:13:27 EST 2010 + +=item Modified: + +$Date: 2011/06/16 15:14:52 $ + +=back + +=head1 SYNOPSIS + + Usage updatela.pl: [-u|sage] [-ve|rbose] [-deb|ug] + [-host [|all]] + + Where: + -u|sage: Displays usage + + -ve|rbose: Be verbose + -deb|ug: Output debug messages + + -host [|all]: Update host or all hosts (Default: all) + -fs [|all]: Update filesystem or all (Default: all) + +=head1 DESCRIPTION + +This script will record the load average of a system + +=cut + +use strict; +use warnings; + +use Net::Domain qw(hostname); +use FindBin; +use Getopt::Long; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use Clearadm; +use Clearexec; +use DateUtils; +use Display; +use Utils; + +my $VERSION = '$Revision: 1.29 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +my $clearadm = Clearadm->new; +my $clearexec = Clearexec->new; + +my $host; + +# Given a host, formulate a loadavg record +sub snapshotLoad ($) { + my ($systemRef) = @_; + + my %system = %{$systemRef}; + + my ($status, @output); + + $status = $clearexec->connectToServer ( + $system{name}, $system{port} + ); + + error "Unable to connect to system $system{name}:$system{port}", 1 + unless $status; + + verbose "Snapshotting load on $system{name}"; + + my %load = ( + system => $system{name}, + ); + + my $cmd = 'uptime'; + + ($status, @output) = $clearexec->execute ($cmd); + + return + if $status; + + # Parsing uptime is odd. Sometimes we get output like + # + # 10:11:59 up 17 days, 22:11, 6 users, load average: 1.08, 1.10, 1.10 + # + # And sometimes we get output like: + # + # 10:11:15 up 23:04, 0 users, load average: 0.00, 0.00, 0.00 + # + # Notice that if the machine was up for less than a day you don't get the + # "x days" portion of output. There is no real controls on uptime to format + # the output better, so we parse for either format. + if ($output[0] =~ /up\s+(.+?),\s+(.+?),\s+(\d+) user.*load average:\s+(.+?),/) { + $load{uptime} = "$1 $2"; + $load{users} = $3; + $load{loadavg} = "$4"; + } elsif ($output[0] =~ /up\s+(.+?),\s+(\d+) user.*load average:\s+(.+?),/) { + $load{uptime} = "$1"; + $load{users} = $2; + $load{loadavg} = "$3"; + } else { + warning "Unable to parse output of uptime from $system{name}"; + return; + } # if + + # On Windows sytems, Cygwin's uptime does not return a loadavg at all - it + # returns only 0! So we have load.vbs which give us the LoadPercentage + if ($system{type} =~ /windows/i) { + my $loadvbs = 'c:/cygwin/opt/clearscm/clearadm/load.vbs'; + $cmd = "cscript /nologo $loadvbs"; + + ($status, @output) = $clearexec->execute ($cmd); + + chop @output if $output[0] =~ /\r/; + + return + if $status; + + $load{loadavg} = $output[0] / 100; + } # if + + $clearexec->disconnectFromServer; + + return %load; +} # snapshotLoad + +# Main +GetOptions ( + 'usage' => sub { Usage }, + 'verbose' => sub { set_verbose }, + 'debug' => sub { set_debug }, + 'host=s' => \$host, +) or Usage "Invalid parameter"; + +Usage 'Extraneous options: ' . join ' ', @ARGV + if @ARGV; + +# Announce ourselves +verbose "$FindBin::Script V$VERSION"; + +my $exit = 0; + +foreach my $system ($clearadm->FindSystem ($host)) { + next if $$system{active} eq 'false'; + + my %load = snapshotLoad $system; + + if (%load) { + my ($err, $msg) = $clearadm->AddLoadavg (%load); + + error $msg, $err if $err; + } else { + error "Unable to get loadavg for system $$system{name}", 1; + } # if + + # Check if over threshold + my %notification = $clearadm->GetNotification ('Loadavg'); + + next + unless %notification; + + if ($load{loadavg} >= $$system{loadavgThreshold}) { + $exit = 2; + error YMDHMS . " System: $$system{name} " + . "Loadavg $load{loadavg} " + . "Threshold $$system{loadavgThreshold}"; + } else { + $clearadm->ClearNotifications ($$system{name}); + } # if +} # foreach + +exit $exit; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Clearadm + Clearexec + DateUtils + Display + Utils + +=end man + +=begin html + +
    +Clearadm
    +Clearexec
    +DateUtils
    +Display
    +Utils
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut \ No newline at end of file diff --git a/clearadm/updatesystem.pl b/clearadm/updatesystem.pl new file mode 100755 index 0000000..9a27a69 --- /dev/null +++ b/clearadm/updatesystem.pl @@ -0,0 +1,366 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: updatesystem.pl,v $ + +Update System + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.17 $ + +=item Created: + +Mon Dec 13 09:13:27 EST 2010 + +=item Modified: + +$Date: 2012/11/09 06:44:38 $ + +=back + +=head1 SYNOPSIS + + Usage updatesystem.pl: [-u|sage] [-ve|rbose] [-deb|ug] + [-del|ete -h|ost ] + + Where: + -u|sage: Displays usage + + -ve|rbose: Be verbose + -deb|ug: Output debug messages + + -del|ete: Delete host + -h|ost : Host to operate on (Default: Current host) + -p|ort : Clearexec port to connect to + +=head1 DESCRIPTION + +This script will add/update the system to the Clearadm database. You can also +delete a system from the Clearadm database. + +=cut + +use strict; +use warnings; + +use Sys::Hostname; + +use FindBin; +use Getopt::Long; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use Clearadm; +use Clearexec; +use Display; +use Utils; + +my $VERSION = '$Revision: 1.17 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +my $clearadm = Clearadm->new; +my $clearexec = Clearexec->new; + +my ($delete, $host, $port); + +sub GetFilesystems (%) { + my (%system) = @_; + + # TODO: Unix/Linux systems often vary as to what parameters df supports. The + # -P is to intended to make this POSIX standard. Need to make sure this works + # on other systems (i.e. Solaris, HP-UX, Redhat, etc.). + my $cmd = $system{type} eq 'Windows' ? 'df -TP' : 'df -l -TP'; + + my ($status, @output) = $clearexec->execute ($cmd); + + error "Unable to execute uname -a - $!", $status . join ("\n". @output) + if $status; + + # Real file systems start with "/" + @output = grep { /^\// } @output; + + my @filesystems; + + foreach (@output) { + if (/^(\S+)\s+(\S+).+?(\S+)$/) { + my %filesystem; + + $filesystem{system} = $system{name}; + $filesystem{filesystem} = $1; + $filesystem{fstype} = $2; + $filesystem{mount} = $3; + + push @filesystems, \%filesystem; + } # if + } # foreach + + return @filesystems; +} # GetFilesystems + +sub GatherSysInfo (;%) { + my (%system) = @_; + + # Set name if not currently set + $system{name} = $host + unless $system{name}; + + my ($status, @output); + + $system{port} ||= $port; + + # Connect to clearexec server + $status = $clearexec->connectToServer ($system{name}, $system{port}); + + unless ($status) { + warning "Unable to connect to $system{name}:$port"; + return %system; + } # if + + # Get OS info + my $cmd = 'uname -a'; + + ($status, @output) = $clearexec->execute ($cmd); + + error "Unable to execute '$cmd' - $!", $status . join ("\n". @output) + if $status; + + $system{os} = $output[0]; + + $system{clearagent} = 1; + + $cmd = 'uname -s'; + + ($status, @output) = $clearexec->execute ($cmd); + + error "Unable to execute '$cmd' - $!", $status . join ("\n". @output) + if $status; + + # TODO: Need to handle this better + $system{type} = $output[0] =~ /cygwin/i ? 'Windows' : $output[0]; + + return %system; +} # GatherSysInfo + +sub AddFilesystems (%) { + my (%system) = @_; + + my ($err, $msg); + + foreach (GetFilesystems %system) { + my %filesystem = %{$_}; + + my %oldfilesystem = $clearadm->GetFilesystem ( + $filesystem{system}, + $filesystem{filesystem} + ); + + if (%oldfilesystem) { + verbose "Updating filesystem $filesystem{system}:$filesystem{filesystem}"; + + ($err, $msg) = $clearadm->UpdateFilesystem ( + $filesystem{system}, + $filesystem{filesystem}, + %filesystem, + ); + + error 'Unable to update filesystem ' + . "$filesystem{system}:$filesystem{filesystem}" + if $err; + } else { + verbose 'Adding filesystem ' + . "$filesystem{system}:$filesystem{filesystem}"; + + ($err, $msg) = $clearadm->AddFilesystem (%filesystem); + + error 'Unable to add filesystem ' + . "$filesystem{system}:$filesystem{filesystem}" + if $err; + } # if + } # foreach + + return ($err, $msg); +} # AddFilesystems + +sub AddSystem ($) { + my ($system) = @_; + + verbose "Adding newhost $system"; + + my %system = GatherSysInfo; + + # If GatherSysInfo was able to connect to clearagent it will set this field + my $clearagent = delete $system{clearagent}; + + my ($err, $msg) = $clearadm->AddSystem (%system); + + return ($err, $msg) + if $err; + + if ($clearagent) { + return AddFilesystems %system; + } else { + return ($err, $msg); + } # if +} # AddSystem + +sub UpdateSystem (%) { + my (%system) = @_; + + my ($err, $msg); + + %system = GatherSysInfo (%system); + + # If GatherSysInfo was able to connect to clearagent it will set this field + my $clearagent = delete $system{clearagent}; + + return ($err, $msg) unless $clearagent; + + verbose "Updating existing host $system{name}"; + + ($err, $msg) = $clearadm->UpdateSystem ($system{name}, %system); + + return ($err, $msg) if $err; + + ($err, $msg) = AddFilesystems %system; + + $clearexec->disconnectFromServer; + + return ($err, $msg); +} # UpdateSystem + +# Main +$host = hostname; +$port = $Clearexec::CLEAROPTS{CLEAREXEC_PORT}; + +GetOptions ( + 'usage' => sub { Usage }, + 'verbose' => sub { set_verbose }, + 'debug' => sub { set_debug }, + 'delete' => \$delete, + 'host=s' => \$host, + 'port=s' => \$port, +) or Usage "Invalid parameter"; + +Usage 'Extraneous options: ' . join ' ', @ARGV + if @ARGV; + +if ($delete) { + error "Must specify -host if you specify -delete", 1 + unless $host; +} # if + +# Announce ourselves +verbose "$FindBin::Script V$VERSION"; + +my ($err, $msg); + +if ($delete) { + display_nolf "Delete host $host (y/N):"; + + my $answer = ; + + if ($answer =~ /(y|yes)/i) { + ($err, $msg) = $clearadm->DeleteSystem ($host); + + if ($err == 0) { + error "No host named $host in database"; + } elsif ($err < 0) { + error "Unable to delete $host" . $msg, $err; + } else { + verbose "Deleted host $host"; + } # if + } else { + display "Host $host not deleted"; + } # if +} else { + if ($host eq 'all') { + foreach ($clearadm->FindSystem) { + my %system = %$_; + + ($err, $msg) = UpdateSystem (%system); + + error "Unable to update host $system{name}\n$msg", $err + if $err; + } # foreach + } else { + my %system = $clearadm->GetSystem ($host); + + if (%system) { + ($err, $msg) = UpdateSystem (%system); + } else { + ($err, $msg) = AddSystem ($host); + } # if + + if ($err) { + my $errmsg = 'Unable to '; + $errmsg .= %system ? 'update' : 'add'; + $errmsg .= " host $host\$msg"; + + error "Unable to add host $host\n$msg", $err; + } # if + } # if +} # if + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Clearadm + Clearexec + Display + Utils + +=end man + +=begin html + +
    +Clearadm
    +Clearexec
    +Display
    +Utils
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut diff --git a/clearadm/var/run/.cvsignore b/clearadm/var/run/.cvsignore new file mode 100644 index 0000000..d25cad1 --- /dev/null +++ b/clearadm/var/run/.cvsignore @@ -0,0 +1,3 @@ +clearagent.pl.pid +.cvsignore +cleartasks.pl.pid diff --git a/clearadm/var/run/clearagent.pl.pid b/clearadm/var/run/clearagent.pl.pid new file mode 100644 index 0000000..4e6b92a --- /dev/null +++ b/clearadm/var/run/clearagent.pl.pid @@ -0,0 +1 @@ +1841 diff --git a/clearadm/var/run/cleartasks.pl.pid b/clearadm/var/run/cleartasks.pl.pid new file mode 100644 index 0000000..07bef9d --- /dev/null +++ b/clearadm/var/run/cleartasks.pl.pid @@ -0,0 +1 @@ +1848 diff --git a/clearadm/viewager.cgi b/clearadm/viewager.cgi new file mode 100755 index 0000000..b327305 --- /dev/null +++ b/clearadm/viewager.cgi @@ -0,0 +1,749 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: viewager.cgi,v $ + +View Aging + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.11 $ + +=item Created: + +Mon Oct 25 11:10:47 PDT 2008 + +=item Modified: + +$Date: 2011/01/14 16:50:54 $ + +=back + +=head1 SYNOPSIS + +This script serves 4 distinct functions. One function is to find +old views and report them to their owners via email so that view cleanup can be +done. Another function just does a quick report stdout. Yet another function is +to present the list of views in a web page. Finally there is a function +(generate) which generates a cache file containing information about views. This +function is designed to be run by a scheduler such as cron. Note that the web +page function relies on and uses this cache file too. + +=head1 DESCRIPTION + +Most Clearcase administrators wrestle with trying to keep the number of views +under control. Users often create views but seldom think to remove them. Views +grow old and forgotten. + +Many approaches have been taken, usally emailing the users telling them to clean +up their views. This script, viewager.cgi, attempts to encapsulate the task of +gathering information about old views, informing users of which of their views +are old and presenting reports in the form of a web page showing all views +including old ones. + +=head1 USAGE Email, Report and Generate modes + + Usage viewager.cgi: [-u|sage] [-region ] [-e|mail] + [-a|gethreshold ] [-n|brThreshold ] + [-ac|tion ] [-s|ort ] + [-v|erbose] [-d|ebug] + + Where: + -u|sage: Displays usage + -region : Region to use when looking for the view + -e|mail: Send email to owners of old views + -ag|eThreshold: Number of days before a view is considered old + (Default: 180) + -n|brThreshold : Number of views to report. Can be used for say a + "top 10" old views. Useful with -action report + (Default: Report all views) + -ac|tion Valid actions include 'generate' or 'report'. + Generate mode merely regenerates the cache file. + Report produces a quick report to stdout. + -s|ort : Where is one of + + -ve|rbose: Be verbose + -d|ebug: Output debug messages + +=head1 USAGE Web Page mode + +Parameters for the web page mode are provided by the CPAN module CGI and are +normally passed in as part of the URL. These parameters are specified as +name/value pairs: + + sortby= + Note: age will sort in a reverse numerical fashion + + user= + can be a partial name (e.g. 'defaria') + +=head1 DESCRIPTION + +This script seek to handle the general issue of handling old views. In generate +mode this script goes through all views collecting data about all of the views +and creates a cache file. The reason for this is that this process is length +(At one client's site with ~2500 views takes about 1 hour). As such you'd +probably want to schedule the running of this for once a day. + +Once the cache file is created other modes will read that file and report on it. +In report mode you can report to stdout. For example, the following will give +you a quick "top 10" oldest views: + + $ viewager.cgi -action report -n 10 + +You may wish to add the following to your conrtabe to generated the cachefile +nightly: + + 0 0 * * * cd //viewager && //viewager.cgi -action=generate + +=head1 User module + +Since the method for translating a user's userid into other attributes like +the users fullname and email, we rely on a User.pm module to implement a User +object that takes a string identifying the user and return useful informaiton +about the user, specifically the fullname and email address. + +=cut + +use strict; +use warnings; + +use FindBin; +use Getopt::Long; +use CGI qw (:standard :cgi-lib *table start_Tr end_Tr); +use CGI::Carp 'fatalsToBrowser'; +use File::stat; +use Time::localtime; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use Clearadm; +use ClearadmWeb; +use Clearcase; +use Clearcase::View; +use Clearcase::Views; +use DateUtils; +use Display; +use Mail; +use Utils; +use User; + +my $VERSION = '$Revision: 1.11 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +my %opts = Vars; +my $clearadm; + +$opts{sortby} ||= 'age'; +$opts{region} ||= $Clearcase::CC->region; + +my $subtitle = 'View Aging Report'; +my $email; + +my $port = CGI::server_port; + $port = ($port == 80) ? '' : ":$port"; +my $scriptName = CGI::script_name; + $scriptName =~ s/index.cgi//; +my $script = 'http://' + . $Clearadm::CLEAROPTS{CLEARADM_SERVER} + . $port + . $scriptName; + +my (%total, $action); +my $ageThreshold = 180; # Default number of days a view must be older than +my $nbrThreshold; # Number of views threshold - think top 10 + +sub GenerateRegion ($) { + my ($region) = @_; + + verbose "Processing $region"; + $total{Regions}++; + + my $views = Clearcase::Views->new ($region); + my @Views = $views->views; + my @views; + + verbose scalar @Views . " views to process"; + + my $i = 0; + + foreach my $name (@Views) { + $total{Views}++; + + if (++$i % 100 == 0) { + verbose_nolf $i; + } elsif ($i % 25 == 0) { + verbose_nolf '.'; + }# if + + my $view = Clearcase::View->new ($name, $region); + + my $gpath; + + if ($view->webview) { + # TODO: There doesn't appear to be a good way to get the gpath for a + # webview since it's set to ! Here we try to compose one using + # $view->host and $view->access_path but this is decidedly Windows centric + # and thus not portable. This needs to be fixed! + $gpath = '\\\\' . $view->host . '\\' . $view->access_path; + + # Change any ":" to "$". This is to change things like D:\path -> D$\path. + # This assumes we have permissions to access through the administrative + # $ mounts. + $gpath =~ s/:/\$/; + } else { + $gpath = $view->gpath; + } # if + + # Note if the view server is unreachable (e.g. user puts view on laptop and + # the laptop is powered off), then these fields will be undef. Change them + # to Unknown. (Should Clearcase::View.pm do this instead?). + my $type = $view->type; + $type ||= 'Unknown'; + + my $user; + + my $ownerid = $view->owner; + + if ($ownerid) { + $user = User->new ($ownerid); + + $user->{name} ||= 'Unknown'; + } else { + $ownerid = 'Unknown'; + $user->{name} = 'Unknown'; + } # if + + my $age = 0; + my $ageSuffix = ''; + + my $modified_date = $view->modified_date; + + if ($modified_date) { + $modified_date = substr $modified_date, 0, 16; + $modified_date =~ s/T/\@/; + + # Compute age + $age = Age ($modified_date); + $ageSuffix = $age != 1 ? 'days' : 'day'; + } else { + $modified_date = 'Unknown'; + } # if + + my ($err, $msg) = $clearadm->AddView ( + system => $view->shost, + region => $view->region, + tag => $view->tag, + owner => $ownerid, + ownerName => $user->{name}, + email => $user->{email}, + type => $type, + gpath => $gpath, + modified_date => $modified_date, + age => $age, + ageSuffix => $ageSuffix, + ); + + error "Unable to add view $name to Clearadm\n$msg", $err + if $err; + } # foreach + + verbose "\nProcessed $region"; + + return; +} # GenerateRegion + +sub Generate ($) { + my ($region) = @_; + + if ($region =~ /all/i) { + foreach ($Clearcase::CC->regions) { + GenerateRegion $_; + } # foreach + } else { + GenerateRegion $region; + } # if + + return; +} # Generate + +sub Report (@) { + my (@views) = @_; + + $total{'Views processed'} = @views; + + my @sortedViews; + + if ($opts{sort} eq 'age') { + # Sort by age numerically decending + @sortedViews = sort { $$b{$opts{sortby}} <=> $$a{$opts{sortby}} } @views; + } else { + @sortedViews = sort { $$a{$opts{sort}} cmp $$b{$opts{sort}} } @views; + } # if + + $total{Reported} = 0; + + foreach (@sortedViews) { + my %view = %{$_}; + + last + if ($nbrThreshold and $total{Reported} + 1 > $nbrThreshold) or + ($view{age} < $ageThreshold); + + $total{Reported}++; + + if ($view{type}) { + if ($view{type} eq 'dynamic') { + $total{Dynamic}++; + } elsif ($view{type} eq 'snapshot') { + $total{Snapshot}++; + } elsif ($view{type} eq 'webview') { + $total{Webview}++ + } else { + $total{$view{type}}++; + } # if + } else { + $total{Unknown}++; + } # if + +format STDOUT_TOP = + View Name Owner View Type Last Modified Age +------------------------------------- ---------------------- ----------- ---------------- ----------- +. +format STDOUT = +@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<< @<<<<<<<<<<<<<<< @>>>> @<<<< +$view{tag},$view{owner},$view{type},$view{modified_date},$view{age},$view{ageSuffix} +. + + write; + } # foreach + + return; +} # Report + +sub FormatTable ($@) { + my ($style, @views) = @_; + + my $table; + + my $nbrViews = @views; + + my $legend = + font ({-class => 'label'}, 'View type: ') . + font ({-class => 'dynamic'}, 'Dyanmic') . + ' ' . + font ({-class => 'snapshot'}, 'Snapshot') . + ' ' . + font ({-class => 'web'}, 'Web') . + ' ' . + font ({-class => 'unknown'}, 'Unknown'); + + my $caption; + + my $regionDropdown = start_form ( + -action => $script, + ); + + $regionDropdown .= font {-class => 'captionLabel'}, 'Region: '; + $regionDropdown .= popup_menu ( + -name => 'region', + -values => [$Clearcase::CC->regions], + -default => $Clearcase::CC->region, + -onchange => 'submit();', + ); + + $regionDropdown .= end_form; + + $caption .= start_table { + class => 'caption', + cellspacing => 1, + width => '100%', + }; + + my $registryHost = $Clearcase::CC->registry_host; + + $registryHost = font {class => 'unknown'}, 'Unknown' + unless $registryHost; + + $caption .= start_Tr; + $caption .= td { + -align => 'left', + -width => '30%', + }, font ({-class => 'label'}, 'Registry: '), + $registryHost, '
    ', + font ({-class => 'label'}, 'Views: '), + $nbrViews; + $caption .= td { + -align => 'center', + -width => '40%', + }, $legend; + $caption .= td { + -align => 'right', + -width => '30%', + }, $regionDropdown; + $caption .= end_Tr; + + $caption .= end_table; + + $table .= start_table { + cellspacing => 1, + width => '75%', + }; + + $table .= caption $caption; + $table .= start_Tr {-class => 'heading'}; + $table .= th '#'; + + # Set defaults if not set already + $opts{sortby} ||= 'age'; + $opts{reverse} ||= 0; + + my $parms = $opts{user} ? "&user=$opts{user}" : ''; + $parms .= $opts{reverse} == 1 ? '&reverse=0' : '&reverse=1'; + + if ($style eq 'full') { + my $tagLabel = 'Tag '; + my $ownerLabel = 'Owner '; + my $typeLabel = 'Type '; + my $ageLabel = 'Age '; + + if ($opts{sortby} eq 'tag') { + $tagLabel .= $opts{reverse} == 1 + ? img {src => 'up.png', border => 0} + : img {src => 'down.png', border => 0}; + } elsif ($opts{sortby} eq 'ownerName') { + $ownerLabel .= $opts{reverse} == 1 + ? img {src => 'up.png', border => 0} + : img {src => 'down.png', border => 0}; + } elsif ($opts{sortby} eq 'type') { + $typeLabel .= $opts{reverse} == 1 + ? img {src => 'up.png', border => 0} + : img {src => 'down.png', border => 0}; + } elsif ($opts{sortby} eq 'age') { + $ageLabel .= $opts{reverse} == 1 + ? img {src => 'down.png', border => 0} + : img {src => 'up.png', border => 0}; + } # if + + $table .= th a {href => "$script?region=$opts{region}&sortby=tag$parms"}, + $tagLabel; + $table .= th a {href => "$script?region=$opts{region}&sortby=ownerName$parms"}, + $ownerLabel; + $table .= th a {href => "$script?region=$opts{region}&sortby=type$parms"}, + $typeLabel; + $table .= th a {href => "$script?region=$opts{region}&sortby=age$parms"}, + $ageLabel; + } else { + $table .= th 'Tag'; + $table .= th 'Owner'; + $table .= th 'Type'; + $table .= th 'Age'; + } # if + $table .= end_Tr; + + if ($opts{sortby} eq 'age') { + # Sort by age numerically decending + @views = $opts{reverse} == 1 + ? sort { $$a{$opts{sortby}} <=> $$b{$opts{sortby}} } @views + : sort { $$b{$opts{sortby}} <=> $$a{$opts{sortby}} } @views + } else { + @views = $opts{reverse} == 1 + ? sort { $$b{$opts{sortby}} cmp $$a{$opts{sortby}} } @views + : sort { $$a{$opts{sortby}} cmp $$b{$opts{sortby}} } @views + } # if + + my $i; + + foreach (@views) { + my %view = %{$_}; + + my $owner = $view{owner}; + + if ($view{owner} =~ /\S+(\\|\/)(\S+)/) { + $owner = $2; + } # if + + $owner = $view{ownerName} ? $view{ownerName} : 'Unknown'; + + my $rowClass= $view{age} > $ageThreshold ? 'oldview' : 'view'; + + $table .= start_Tr { + class => $rowClass + }; + $table .= td { + class => 'center', + }, ++$i; + $table .= td { + align => 'left', + }, a { + href => "viewdetails.cgi?tag=$view{tag}®ion=$opts{region}" + }, $view{tag}; + $table .= td { + align => 'left', + }, a { + href => "$script?region=$opts{region}&user=$owner" + }, $owner; + $table .= td { + class => 'center' + }, font { + class => $view{type} + }, $view{type}; + $table .= td { + class => 'right' + }, font ({ + class => $view{type} + }, $view{age}, ' ', $view{ageSuffix}); + $table .= end_Tr; + } # foreach + + $table .= end_table; + + return $table +} # FormatTable + +# TODO: Add an option to remove views older than a certain date + +sub EmailUser ($@) { + my ($emailTo, @oldViews) = @_; + + @oldViews = sort { $$b{age} <=> $$a{age} } @oldViews; + + my $msg = ''; + $msg .= <<"END"; +

    You have old Clearcase Views

    + +

    Won't you take a moment to review this message and clean up any views you no +longer need?

    + +

    The following views are owned by you and have not been modified in $ageThreshold +days:

    +END + + $msg .= FormatTable 'partial', @oldViews; + $msg .= <<"END"; + +

    How to remove views you no longer need

    + +

    There are several ways to remove Clearcase views, depending on the view +type and the tools you are using.

    + +
    +

    Dynamic Views: If the view is a dynamic view you can use Clearcase + Explorer to remove the view. Find the view in your Clearcase Explorer. If + it's not there then add it as a standard view shortcut. Then right click on + the view shortcut and select Remove View (not Remove View + Shortcut).

    + +

    Snapshot Views: A snapshot view is a view who's source storage can + be located locally. You can remove a snapshot view in a similar manner as a + dynamic view, by adding it to Clearcase Explorer if not already present. By + doing so you need to tell Clearcase Explorer where the snapshot view storage + is located.

    + +

    Webviews: Webviews are like snapshot views but stored on the web + server. If you are using CCRC or the CCRC plugin to Eclipse you would select + the view and then do Environment: Remove Clearcase View.

    +
    + +

    If you have any troubles removing your old views then submit a case and we +will be happy to assist you.

    + +

    But I need for my view to stay around even if it hasn't been modified

    + +

    If you have a long lasting view who does not get modified but needs to +remain, contact us and we can arrange for it to be removed from consideration +which will stop it from being reported as old.

    + +

    Thanks.

    +--
    +Your friendly Clearcase Administrator +END + + mail ( + to => $emailTo, +# to => 'Andrew@DeFaria.com', + mode => 'html', + subject => 'Old views', + data => $msg, + ); + + return +} # EmailUser + +sub EmailUsers (@) { + my (@views) = @_; + + @views = sort { $$a{ownerName} cmp $$b{ownerName} } @views; + + my @userViews; + my $currUser = $views [0]->{ownerName}; + + foreach (@views) { + my %view = %{$_}; + + next + unless $view{email}; + + if ($currUser ne $view{ownerName}) { + EmailUser $view{email}, @userViews + if @userViews; + + $currUser = $view{ownerName}; + + @userViews =(); + } else { + if ($view{age} > $ageThreshold) { + push @userViews, \%view + if !-f "$view{gpath}/ageless"; + } # if + } # if + } # foreach + + display"Done"; + + return; +} # EmailUsers + +# Main +GetOptions ( + \%opts, + 'usage' => sub { Usage }, + 'verbose' => sub { set_verbose }, + 'debug' => sub { set_debug }, + 'region=s', + 'sortby=s', + 'action=s', + 'email', + 'ageThreshold=i', + 'nbrThreshold=i', +) or Usage "Invalid parameter"; + +local $| = 1; + +$opts{region} ||= ''; + +# Announce ourselves +verbose "$FindBin::Script v$VERSION"; + +$clearadm = Clearadm->new; + +if ($action and $action eq 'generate') { + Generate $opts{region}; + Stats \%total; +} else { + if ($opts{region} and ($opts{region} eq 'Clearcase not installed')) { + heading; + displayError $opts{region}; + footing; + exit 1; + } # if + + my @views = $clearadm->FindView ( + 'all', + $opts{region}, + $opts{tag}, + $opts{user} + ); + + if ($action and $action eq 'report') { + Report @views; + Stats \%total; + } elsif ($email) { + EmailUsers @views; + } else { + heading $subtitle; + + display h1 { + -class => 'center', + }, $subtitle; + + display FormatTable 'full', @views; + + footing; + } # if +} # if + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +L + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + Clearadm + ClearadmWeb + Clearcase + Clearcase::View + Clearcase::Views + DateUtils + Display + Mail + Utils + +=end man + +=begin html + +
    +Clearadm
    +ClearadmWeb
    +Clearcase
    +Clearcase::View
    +Clearcase::Views
    +DateUtils
    +Display
    +Mail
    +Utils
    +
    + +=end html + +=head2 User module + +L + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut \ No newline at end of file diff --git a/clearadm/viewdetails.cgi b/clearadm/viewdetails.cgi new file mode 100755 index 0000000..8bb8ad0 --- /dev/null +++ b/clearadm/viewdetails.cgi @@ -0,0 +1,325 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: viewdetails.cgi,v $ + +View Details + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.11 $ + +=item Created: + +Mon Oct 25 11:10:47 PDT 2008 + +=item Modified: + +$Date: 2011/01/14 16:51:58 $ + +=back + +=head1 SYNOPSIS + + Usage viewdetails.cgi: [-u|sage] [-r|egion ] -vi|ew + [-ve|rbose] [-d|ebug] + + Where: + -u|sage: Displays usage + -r|egion : Region to use when looking for the view + -vi|ew: Name of view to display details for + + -ve|rbose: Be verbose + -d|ebug: Output debug messages + +=head2 DESCRIPTION + +This script display the details for the given view + +=cut + +use strict; +use warnings; + +use FindBin; +use Getopt::Long; +use CGI qw (:standard :cgi-lib *table start_Tr end_Tr); +use CGI::Carp 'fatalsToBrowser'; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use ClearadmWeb; +use Clearcase; +use Clearcase::View; +use Clearcase::Views; +use Display; +use Utils; + +my %opts = Vars; + +my $subtitle = 'View Details'; + +if ($Clearcase::CC->region) { + $opts{region} ||= $Clearcase::CC->region; +} else { + $opts{region} ||= 'Clearcase not installed'; +} # if + +my $VERSION = '$Revision: 1.11 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +sub DisplayTable ($) { + my ($view) = @_; + + # Data fields + my $tag = setField $view->tag; + my $server = setField $view->shost; + my $region = setField $view->region; + my $properties = setField $view->properties; + my $text_mode = setField $view->text_mode; + my $permissions = setField $view->owner_mode + . setField $view->group_mode + . setField $view->other_mode; + my $owner = setField $view->owner; + my $active = ($view->active) ? 'YES' : 'NO'; + my $created_by = setField $view->created_by; + my $created_date = setField $view->created_date; + my $cs_updated_by = setField $view->cs_updated_by; + my $cs_updated_date = setField $view->cs_updated_date; + my $gpath = setField $view->gpath; + my $access_path = setField $view->access_path; + my $uuid = setField $view->uuid; + + $gpath = font {-class => 'unknown'}, '<no-gpath>' + if $gpath eq ''; + + display start_table { + -cellspacing => 1, + -class => 'main', + }; + + display start_Tr; + display th {class => 'label'}, 'Tag:'; + display td {class => 'data', colspan => 3}, $tag; + display th {class => 'label'}, 'Server:'; + display td {class => 'data'}, a { + href => "serverdetails.cgi?server=$server" + }, $server; + display th {class => 'label'}, 'Region:'; + display td {class => 'data'}, $region; + display end_Tr; + + display start_Tr; + display th {class => 'label'}, 'Properties:'; + display td {class => 'data', colspan => 3}, $properties; + display th {class => 'label'}, 'Text Mode:'; + display td {class => 'data'}, $text_mode; + display th {class => 'label'}, 'Permission:'; + display td {class => 'data'}, $permissions; + display end_Tr; + + display start_Tr; + display th {class => 'label'}, 'Owner:'; + display td {class => 'data', colspan => 3}, $owner; + display th {class => 'label'}, 'Active:'; + display td {class => 'data', colspan => 3}, $active; + display end_Tr; + + display start_Tr; + display th {class => 'label'}, 'Created by:'; + display td {class => 'data', colspan => 3}, $created_by; + display th {class => 'label'}, 'on:'; + display td {class => 'data', colspan => 3}, $created_date; + display end_Tr; + + display start_Tr; + display th {class => 'label'}, 'CS Updated by:'; + display td {class => 'data', colspan => 3}, $cs_updated_by; + display th {class => 'label'}, 'on:'; + display td {class => 'data', colspan => 3}, $cs_updated_date; + display end_Tr; + + display start_Tr; + display th {class => 'label'}, 'Global Path:'; + display td {class => 'data', colspan => 7}, $gpath; + display end_Tr; + + display start_Tr; + display th {class => 'label'}, 'Access Path:'; + display td {class => 'data', colspan => 7}, $access_path; + display end_Tr; + + display start_Tr; + display th {class => 'label'}, 'UUID:'; + display td {class => 'data', colspan => 7}, $uuid; + display end_Tr; + + display end_table; + + return +} # DisplayTable + +sub DisplayRegion { + display start_form (action => 'viewdetails.cgi'); + + display 'Region '; + + my ($defaultRegion, @regions) = ('', ('Clearcase not installed')); + + display popup_menu ( + -name => 'region', + -values => [@regions], + -default => $defaultRegion, + -onchange => 'submit();', + ); + + display submit ( + -value => 'Go', + ); + + display end_form; + + return +} # DisplayRegion + +sub DisplayViews ($) { + my ($region) = @_; + + my $views = Clearcase::Views->new ($region); + my @views = $views->views; + + unless (@views) { + push @views, 'No Views'; + } # unless + + display start_form (action => 'viewdetails.cgi'); + + display 'Region '; + + display popup_menu ( + -name => 'region', + -values => [$Clearcase::CC->regions], + -default => $region, + -onchange => 'submit();', + ); + + display b ' View: '; + + display popup_menu ( + -name => 'view', + -values => \@views, + -onchange => 'submit();', + ); + + display submit ( + -value => 'Go', + ); + + display end_form; + + return; +} # DisplayViews + +# Main +GetOptions ( + \%opts, + 'usage' => sub { Usage }, + 'verbose' => sub { set_verbose }, + 'debug' => sub { set_debug }, + 'view=s', + 'region=s', +) or Usage "Invalid parameter"; + +# Announce ourselves +verbose "$FindBin::Script v$VERSION"; + +heading $subtitle; + +display h1 { + -class => 'center', +}, $subtitle; + +unless ($opts{tag}) { + unless ($opts{region}) { + DisplayRegion; + } else { + DisplayViews $opts{region}; + } # unless + + exit; +} # unless + +my $view = Clearcase::View->new ($opts{tag}, $opts{region}); + +DisplayTable $view; + +footing; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + ClearadmWeb + Clearcase + Clearcase::View + Clearcase::Views + Display + Utils + +=end man + +=begin html + +
    +ClearadmWeb
    +Clearcase
    +Clearcase::View
    +Clearcase::Views
    +Display
    +Utils
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut \ No newline at end of file diff --git a/clearadm/viewservers.cgi b/clearadm/viewservers.cgi new file mode 100755 index 0000000..a96a770 --- /dev/null +++ b/clearadm/viewservers.cgi @@ -0,0 +1,224 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: viewservers.cgi,v $ + +View Details + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.9 $ + +=item Created: + +Mon Oct 25 11:10:47 PDT 2008 + +=item Modified: + +$Date: 2011/01/02 15:25:23 $ + +=back + +=head1 SYNOPSIS + + Usage viewservers.cgi: [-u|sage] [-r|egion ] + [-ve|rbose] [-d|ebug] + + Where: + -u|sage: Displays usage + -r|egion : Region to use when looking for the view + + -ve|rbose: Be verbose + -d|ebug: Output debug messages + +=head1 DESCRIPTION + +This script display the details for all view servers in the region + +=cut + +use strict; +use warnings; + +use FindBin; +use Getopt::Long; +use CGI qw (:standard :cgi-lib *table start_Tr end_Tr); +use CGI::Carp 'fatalsToBrowser'; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use ClearadmWeb; +use Clearcase; +use Clearcase::Server; +use Display; +use Utils; + +my %opts = Vars; + +$opts{region} ||= $Clearcase::CC->region; + +my $subtitle = 'View Servers'; + +my $VERSION = '$Revision: 1.9 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +sub DisplayTable (@) { + my (@viewServers) = @_; + + my $unknown = font {-class => 'unknown'}, 'Unknown'; + + display start_table { + -cellspacing => 1, + -class => 'main', + }; + + display start_Tr; + display th { + -class => 'labelCentered', + }, '#'; + display th { + -class => 'labelCentered', + }, 'Server'; + display th { + -class => 'labelCentered', + }, 'CC Version'; + display th { + -class => 'labelCentered', + }, 'OS Version'; + display end_Tr; + + my $i = 0; + + foreach (@viewServers) { + my $server = Clearcase::Server->new ($_, $opts{region}); + + # Data fields + my $name = $server->name; + my $ccVer = $server->ccVer; + my $osVer = $server->osVer; + + $ccVer ||= $unknown; + $osVer ||= $unknown; + + display start_Tr; + display td { + -class => 'dataCentered', + }, ++$i; + display td { + -class => 'data', + }, a {-href => "serverdetails.cgi?server=$name"}, $name; + display td { + -class => 'data', + }, $ccVer; + display td { + -class => 'data', + }, $osVer; + display end_Tr; + } # foreach + + display end_table; + + return; +} # DisplayTable + +# Main +GetOptions ( + \%opts, + 'usage' => sub { Usage }, + 'verbose' => sub { set_verbose }, + 'debug' => sub { set_debug }, + 'region=s', +) or Usage "Invalid parameter"; + +# Announce ourselves +verbose "$FindBin::Script v$VERSION"; + +heading $subtitle; + +display h1 { + -class => 'center', +}, $subtitle; + +my ($status, @output) = $Clearcase::CC->execute ("lsview -region $opts{region} -long"); + +error "Unable to list all views in the region $opts{region}" . join ("\n", @output), 1 + if $status; + +my %viewServers; + +foreach (@output) { + if (/Server host: (.*)/) { + $viewServers{$1} = undef; + } # if +} # foreach + +DisplayTable sort (keys (%viewServers)); + +footing; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + ClearadmWeb + Clearcase + Clearcase::Server + Display + Utils + +=end man + +=begin html + +
    +ClearadmWeb
    +Clearcase
    +Clearcase::Server
    +Display
    +Utils
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut \ No newline at end of file diff --git a/clearadm/vobservers.cgi b/clearadm/vobservers.cgi new file mode 100755 index 0000000..ee82c81 --- /dev/null +++ b/clearadm/vobservers.cgi @@ -0,0 +1,227 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: vobservers.cgi,v $ + +View Details + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.9 $ + +=item Created: + +Mon Oct 25 11:10:47 PDT 2008 + +=item Modified: + +$Date: 2011/01/02 15:25:42 $ + +=back + +=head1 SYNOPSIS + + Usage vobservers.cgi: [-u|sage] [-r|egion ] + [-ve|rbose] [-d|ebug] + + Where: + -u|sage: Displays usage + -r|egion : Region to use when looking for the view + + -ve|rbose: Be verbose + -d|ebug: Output debug messages + +=head1 DESCRIPTION + +This script display the details for all vob servers in the region + +=cut + +use strict; +use warnings; + +use FindBin; +use Getopt::Long; +use CGI qw (:standard :cgi-lib *table start_Tr end_Tr); +use CGI::Carp 'fatalsToBrowser'; + +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib"; + +use ClearadmWeb; +use Clearcase; +use Clearcase::Server; +use Display; +use Utils; + +my %opts = Vars; + +$opts{region} ||= $Clearcase::CC->region if $Clearcase::CC; + +my $subtitle = 'Vob Servers'; + +my $VERSION = '$Revision: 1.9 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +sub DisplayTable (@) { + my (@vobServers) = @_; + + my $unknown = font {-class => 'unknown'}, 'Unknown'; + + display start_table { + -cellspacing => 1, + -class => 'main', + }; + + display start_Tr; + display th { + -class => 'labelCentered', + }, '#'; + display th { + -class => 'labelCentered', + }, 'Server'; + display th { + -class => 'labelCentered', + }, 'CC Version'; + display th { + -class => 'labelCentered', + }, 'OS Version'; + display end_Tr; + + my $i = 0; + + foreach (@vobServers) { + my $server = Clearcase::Server->new ($_, $opts{region}); + + # Data fields + my $name = $server->name; + my $ccVer = $server->ccVer; + my $osVer = $server->osVer; + + $ccVer ||= $unknown; + $osVer ||= $unknown; + + display start_Tr; + display td { + -class => 'dataCentered', + }, ++$i; + display td { + -class => 'data', + }, a {-href => "serverdetails.cgi?server=$name"}, $name; + display td { + -class => 'data', + }, $ccVer; + display td { + -class => 'data', + }, $osVer; + display end_Tr; + } # foreach + + display end_table; + + return; +} # DisplayTable + +# Main +GetOptions ( + \%opts, + 'usage' => sub { Usage }, + 'verbose' => sub { set_verbose }, + 'debug' => sub { set_debug }, + 'region=s', +) or Usage "Invalid parameter"; + +# Announce ourselves +verbose "$FindBin::Script v$VERSION"; + +heading $subtitle; + +display h1 { + -class => 'center', +}, $subtitle; + +my ($status, @output) = $Clearcase::CC->execute ( + "lsvob -region $opts{region} -long" +); + +error "Unable to list all vobs in the region $opts{region}" + . join ("\n", @output), 1 + if $status; + +my %vobServers; + +foreach (@output) { + if (/Server host: (.*)/) { + $vobServers{$1} = undef; + } # if +} # foreach + +DisplayTable sort (keys (%vobServers)); + +footing; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + ClearadmWeb + Clearcase + Clearcase::Server + Display + Utils + +=end man + +=begin html + +
    +ClearadmWeb
    +Clearcase
    +Clearcase::Server
    +Display
    +Utils
    +
    + +=end html + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this script + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2010, ClearSCM, Inc. All rights reserved. + +=cut \ No newline at end of file diff --git a/cq/.-_hist b/cq/.-_hist new file mode 100644 index 0000000..e69de29 diff --git a/cq/CheckCodePage.pl b/cq/CheckCodePage.pl new file mode 100644 index 0000000..9a886b6 --- /dev/null +++ b/cq/CheckCodePage.pl @@ -0,0 +1,132 @@ +#!cqperl +################################################################################ +# +# File: CheckCodePage.pl +# Description: With Clearquest 2003.06.15 there is more support for +# internationalization. This means that Clearquest now +# implements a Code Page which essentially defines the +# valid character set for data. If it encounters invalid +# characters the user must correct them. +# +# This script will check a Clearquest database to see if +# there are any invalid ASCII characters in string oriented +# fields. +# Author: Andrew@DeFaria.com +# Created: Fri Sep 23 17:27:58 PDT 2005 +# Language: Perl +# +# (c) Copyright 2005, Andrew@DeFaria.com, all rights reserved +# +################################################################################ +use strict; +use warnings; +use CQPerlExt; +use File::Spec; + +our ($me, $SEPARATOR); + +my ($abs_path, $lib_path); + +BEGIN { + # Extract relative path and basename from script name. + $0 =~ /(.*)[\/\\](.*)/; + + $abs_path = (!defined $1) ? "." : File::Spec->rel2abs ($1); + $me = (!defined $2) ? $0 : $2; + $me =~ s/\.pl$//; + + # Remove .pl for Perl scripts that have that extension + $me =~ s/\.pl$//; + + # Define the path separator + $SEPARATOR = ($^O =~ /MSWin/) ? "\\" : "/"; + + # Setup paths + $lib_path = "$abs_path" . $SEPARATOR . ".." . $SEPARATOR . "lib"; + + # Add the appropriate path to our modules to @INC array. + unshift (@INC, "$abs_path"); + unshift (@INC, "$lib_path"); +} # BEGIN + +use PQA; +use Display; +use Logger; +use TimeUtils; + +my $from_db_connection_name = "Controller"; + +sub Usage { + my $msg = shift; + + display "ERROR: $msg\n" if defined $msg; + + display "Usage: $me\t[-u] [-v] [-d] [-from ] + +Where: + + -u: Display usage + -v: Turn on verbose mode + -d: Turn on debug mode + -from : Specify the from connection name + (Default: $from_db_connection_name)"; + exit 1; +} # Usage + +while ($ARGV [0]) { + if ($ARGV [0] eq "-v") { + Display::set_verbose; + Logger::set_verbose; + } elsif ($ARGV [0] eq "-d") { + set_debug; + } elsif ($ARGV [0] eq "-from") { + shift; + if (!$ARGV [0]) { + Usage "Must specify after -from"; + } else { + $from_db_connection_name = $ARGV [0]; + } # if + } elsif ($ARGV [0] eq "-u") { + Usage; + } else { + Usage "Unknown argument found: " . $ARGV [0]; + } # if + + shift (@ARGV); +} # while + +my $log = Logger->new (path => "."); + +my $process_start_time = time; +my $start_time; + +$log->msg ("Starting Cont session"); +my $session = StartSession ("Cont", $from_db_connection_name); + +$start_time = time; + +#$log->msg ("Checking customer record..."); +#CheckRecord $log, $session, "dbid", "customer", undef, @customer_fields; + +#$log->msg ("Checking project record..."); +#CheckRecord $log, $session, "dbid", "project", undef, @project_fields; + +$log->msg ("Checking defect record..."); +#CheckRecord $log, $session, "id", "defect", undef, @new_Cont_defect_fields; +CheckRecord $log, $session, "id", "defect", "Cont00022003", @new_Cont_defect_fields; + +$log->msg ("Ending Cont session..."); +EndSession $session; + +display_duration $start_time, $log; + + +$log->msg ("\nInvalid character analysis\n"); + +my $i = 0; + +foreach (sort (keys (%bad_chars))) { + $log->msg (++$i . "\t$_\t$bad_chars{$_}\n"); +} # foreach + +display_duration $process_start_time, $log; diff --git a/cq/PQA.pm b/cq/PQA.pm new file mode 100644 index 0000000..71afacf --- /dev/null +++ b/cq/PQA.pm @@ -0,0 +1,1040 @@ +#!/usr/bin/perl +################################################################################ +# +# File: PQA.pm +# Description: Perl module PQA conversion routines +# Author: Andrew@DeFaria.com +# Created: Thu Oct 6 09:51:38 PDT 2005 +# Language: Perl +# Modifications: +# +# (c) Copyright 2005, Andrew@DeFaria.com, all rights reserved +# +################################################################################ +#use strict; +use warnings; +use CQPerlExt; + +package PQA; + use File::Spec; + + require (Exporter); + @ISA = qw (Exporter); + + @EXPORT = qw ( + @old_Prod_defect_fields + @old_TO_defect_fields + @new_Cont_defect_fields + @customer_fields + @project_fields + %bad_chars + AddToFieldChoiceList + AddToProject + CheckField + CheckRecord + DeleteDynamicLists + DeleteRecords + EndSession + GetAllDefectRecords + GetDefectRecord + ProjectExists + StartSession + TransferAttachments + TransferHistory + TransferRecords + ); + + # Forwards + sub AddToFieldChoiceList; + sub AddToProject; + sub CheckField; + sub CheckRecord; + sub DeleteDynamicLists; + sub DeleteRecords; + sub EndSession; + sub GetAllDefectRecords; + sub GetDefectRecord; + sub ProjectExists; + sub StartSession; + sub TransferAttachemnts; + sub TransferHistory; + sub TransferRecords; + + our ($me, $verbose, $debug); + my $abs_path; + + BEGIN { + # Check environment variables + $verbose = $ENV {VERBOSE} ? "yes" : "no"; + $debug = $ENV {DEBUG} ? "yes" : "no"; + } # BEGIN + + use Display; + use Logger; + + ## Exported variables ## + + # Field Definitions + our @old_Prod_defect_fields = ( + "ActionNotes", # SHORT_STRING + "AdvancedFeature", # SHORT_STRING, CONSTANT_LIST + "Assigned_Date", # DATE_TIME + "AttachmentBRCM", # ATTACHMENT_LIST + "Audit_Log", # MULTILINE_STRING + "Category", # SHORT_STRING, CONSTANT_LIST + "Close_Date", # DATE_TIME + "CommitmentLevel", # SHORT_STRING, CONSTANT_LIST + "CommittedDate", # DATE_TIME + "CommittedToProject", # SHORT_STRING, CONSTANT_LIST + "CustomerID", # SHORT_STRING + "DataPendingNote", # MULTILINE_STRING + "DeferredToChip", # SHORT_STRING + "DeferredToProject", # SHORT_STRING, CONSTANT_LIST + "Description", # MULTILINE_STRING + "DoesNotVerifyNote", # MULTILINE_STRING + "Entry_Type", # SHORT_STRING, CONSTANT_LIST + "Est_Time_To_Fix", # SHORT_STRING + "Fixed_In_HW_Version", # SHORT_STRING + "Fixed_In_Project", # SHORT_STRING, CONSTANT_LIST + "Fixed_In_SW_Version", # SHORT_STRING + "GatingItem", # SHORT_STRING, CONSTANT_LIST + "HUT", # SHORT_STRING, DYNAMIC_LIST + "HUT_Revision", # SHORT_STRING, CONSTANT_LIST + "HUT_Version", # SHORT_STRING, CONSTANT_LIST + "History", # JOURNAL + "Issue_Classification", # SHORT_STRING, CONSTANT_LIST + "Keywords", # MULTILINE_STRING, CONSTANT_LIST + "NoteBRCMOnly", # MULTILINE_STRING + "NoteBugReview", # MULTILINE_STRING + "Note_Entry", # MULTILINE_STRING + "Notes_Log", # MULTILINE_STRING + "OEMSubmitterName", # SHORT_STRING + "OS", # CONSTANT_LIST + "Open_Close_Status", # SHORT_STRING, CONSTANT_LIST + "Owner", # REFERENCE + "PendingHWSWReleases", # INT + "Priority", # SHORT_STRING, CONSTANT_LIST + "Project", # REFERENCE + "Project_Name", # SHORT_STRING, CONSTANT_LIST + "RelatedID", # MULTILINE_STRING + "ReportedBy", # SHORT_STRING, CONSTANT_LIST + "Resolution", # SHORT_STRING + "ResolveNote", # MULTILINE_STRING + "ResolvedBy", # REFERENCE + "Resolved_Date", # DATE_TIME + "SQATestCase", # SHORT_STRING, CONSTANT_LIST + "Service_Pack", # SHORT_STRING + "Severity", # SHORT_STRING, CONSTANT_LIST + "Software", # SHORT_STRING, CONSTANT_LIST + "Software_Version", # SHORT_STRING + "Submit_Date", # DATE_TIME + "Submitter", # REFERENCE + "Symptoms", # MULTILINE_STRING, CONSTANT_LIST + "TCProcedure", # MULTILINE_STRING + "TestBlocking", # SHORT_STRING, CONSTANT_LIST + "TestCaseID", # INT + "TestcaseComment", # MULTILINE_STRING + "TimeFromSubmitToVerify", # SHORT_STRING + "TimeSubmitToResolve", # SHORT_STRING + "TimeSubmitToResolve", # SHORT_STRING + "TimeToVerify", # SHORT_STRING + "Title", # SHORT_STRING + "VerifiedBy", # REFERENCE + "VerifyNote", # MULTILINE_STRING + "Verified_Date", # DATE_TIME + "Verified_In_HW_Version", # SHORT_STRING + "Verified_In_SW_Version", # SHORT_STRING + "Visibility", # SHORT_STRING, CONSTANT_LIST + "VisibleTo3com", # INT + "VisibleToAltima", # INT + "VisibleToCompaq", # INT + "VisibleToDell", # INT + "customer", # REFERENCE + "customer_severity", # SHORT_STRING, CONSTANT_LIST + "old_id", # SHORT_STRING, CONSTANT_LIST + ); + + # This decribes the fields in the old TO defect record + our @old_TO_defect_fields = ( + "ActionNotes", # SHORT_STRING + "AdvancedFeature", # SHORT_STRING, DYNAMIC_LIST + "Assigned_Date", # DATE_TIME + "AttachmentsBRCM", # ATTACHMENT_LIST + "Audit_Log", # MULTILINE_STRING + "Category", # SHORT_STRING, CONSTANT_LIST + "Close_Date", # DATE_TIME + "CommitmentLevel", # SHORT_STRING, CONSTANT_LIST + "CommittedDate", # DATE_TIME + "CommittedToProject", # SHORT_STRING, DYNAMIC_LIST + "CustomerID", # SHORT_STRING + "DataPendingNote", # MULTILINE_STRING + "DeferredToChip", # SHORT_STRING + "DeferredToProject", # SHORT_STRING, DYNAMIC_LIST + "Description", # MULTILINE_STRING + "DoesNotVerifyNote", # MULTILINE_STRING + "Entry_Type", # SHORT_STRING, CONSTANT_LIST + "Est_Time_To_Fix", # SHORT_STRING + "Fixed_In_HW_Version", # SHORT_STRING + "Fixed_In_Project", # SHORT_STRING, DYNAMIC_LIST + "Fixed_In_SW_Version", # SHORT_STRING + "Found_In_Project", # SHORT_STRING, DYNAMIC_LIST + "GatingItem", # SHORT_STRING, CONSTANT_LIST + "HUT", # SHORT_STRING, DYNAMIC_LIST + "HUT_Revision", # SHORT_STRING, DYNAMIC_LIST + "HUT_Version", # SHORT_STRING, DYNAMIC_LIST + "Headline", # SHORT_STRING + "History", # JOURNAL + "Issue_Classification", # SHORT_STRING, CONSTANT_LIST + "Keywords", # MULTILINE_STRING, CONSTANT_LIST + "NoteBRCMOnly", # MULTILINE_STRING + "NoteBugReview", # MULTILINE_STRING + "Note_Entry", # MULTILINE_STRING + "Notes_Log", # MULTILINE_STRING + "OEMSubmitterName", # SHORT_STRING + "OS", # SHORT_STRING, DYNAMIC_LIST + "Open_Close_Status", # SHORT_STRING, CONSTANT_LIST + "Owner", # REFERENCE + "PendingHWSWReleases", # INT + "Priority", # SHORT_STRING, CONSTANT_LIST + "Project", # REFERENCE + "ReportedBy", # REFERENCE + "Resolution", # SHORT_STRING, CONSTANT_LIST + "ResolveNote", # MULTILINE_STRING + "ResolvedBy", # REFERENCE + "Resolved_Date", # DATE_TIME + "SQATestCase", # SHORT_STRING, CONSTANT_LIST + "Service_Pack", # SHORT_STRING, DYNAMIC_LIST + "Severity", # SHORT_STRING, CONSTANT_LIST + "Software", # SHORT_STRING, DYNAMIC_LIST + "Software_Version", # SHORT_STRING + "Submit_Date", # DATE_TIME + "Submitter", # REFERENCE + "Symptoms", # MULTILINE_STRING, CONSTANT_LIST + "TCProcedure", # MULTILINE_STRING + "TestBlocking", # SHORT_STRING, CONSTANT_LIST + "TestCaseID", # INT + "TestcaseComment", # MULTILINE_STRING + "TimeFromSubmitToVerify", # SHORT_STRING + "TimeSubmitToResolve", # SHORT_STRING + "TimeToVerify", # SHORT_STRING + "Title_2", # SHORT_STRING + "VerifiedBy", # REFERENCE + "Verified_Date", # DATE_TIME + "Verified_In_HW_Version", # SHORT_STRING + "Verified_In_SW_Version", # SHORT_STRING + "VerifyNote", # MULTILINE_STRING + "Visibility", # SHORT_STRING, DYNAMIC_LIST + "customer", # REFERENCE_LIST + "customer_severity", # SHORT_STRING, CONSTANT_LIST + "old_id", # SHORT_STRING + ); + + # This describes the fields in the new Cont Defect record + our @new_Cont_defect_fields = ( + "ActionNotes", # SHORT_STRING +# Prod: , TO: -> Cont: Active_Deferred_Status + "Active_Deferred_Status", # SHORT_STRING, CONSTANT_LIST + "Advanced_Feature", # SHORT_STRING, DYNAMIC_LIST + "Assigned_Date", # DATE_TIME + "AttachmentsBRCM", # ATTACHMENT_LIST + "Audit_Log", # MULTILINE_STRING + "Board_Revision", # SHORT_STRING, DYNAMIC_LIST +# Prod: NoteBRCMOnly, TO: NoteBRCMOnly -> Cont: Broadcom_Only_Note + "Broadcom_Only_Note", # MULTILINE_STRING +# Prod: NoteBugReview, TO: NoteBugReview -> Cont: Bug_Review_Note + "Bug_Review_Note", # MULTILINE_STRING + "Category", # SHORT_STRING, CONSTANT_LIST + "Close_Date", # DATE_TIME + "CommitmentLevel", # SHORT_STRING, CONSTANT_LIST + "CommittedDate", # DATE_TIME + "CommittedToProject", # SHORT_STRING, DYNAMIC_LIST + "CustomerID", # SHORT_STRING + "DataPendingNote", # MULTILINE_STRING + "DeferredToChip", # SHORT_STRING + "DeferredToProject", # SHORT_STRING, DYNAMIC_LIST + "Description", # MULTILINE_STRING + "DoesNotVerifyNote", # MULTILINE_STRING + "Entry_Type", # SHORT_STRING, CONSTANT_LIST + "Est_Time_To_Fix", # SHORT_STRING + "Fixed_In_HW_Version", # SHORT_STRING + "Fixed_In_Project", # SHORT_STRING, DYNAMIC_LIST + "Fixed_In_SW_Version", # SHORT_STRING +# Prod: Project (REFERENCE), TO: Project (REFERENCE) -> Cont: Found_In_Project (REFERENCE) + "Found_In_Project", # REFERENCE +# Prod: , TO: -> Cont: Found_On_Gold + "Found_On_Gold", # SHORT_STRING, CONSTANT_LIST + "Gating_Item_HW", # SHORT_STRING, CONSTANT_LIST +# Prod: GatingItem, TO: GatingItem -> Cont: Gating_Item_SW, Gating_Item_HW + "Gating_Item_SW", # SHORT_STRING, CONSTANT_LIST + "HUT", # SHORT_STRING, DYNAMIC_LIST + "HUT_Revision", # SHORT_STRING, DYNAMIC_LIST +# Prod: Title, TO: Headline -> Cont: Headline + "Headline", # SHORT_STRING + "Issue_Classification", # SHORT_STRING, CONSTANT_LIST + "Keywords", # MULTILINE_STRING, CONSTANT_LIST +# Prod: , TO: -> Cont: Newly_Introduce + "Newly_Introduce", # SHORT_STRING, CONSTANT_LIST + "Note_Entry", # MULTILINE_STRING + "Notes_Log", # MULTILINE_STRING + "OEMSubmitterName", # SHORT_STRING + "OS", # SHORT_STRING, DYNAMIC_LIST +# Prod: , TO: -> Cont: Other_HUT + "Other_HUT", # MULTILINE_STRING + "Owner", # REFERENCE +# Prod: , TO: -> Cont: PQATestCase + "PQATestCase", # SHORT_STRING, CONSTANT_LIST + "Priority", # SHORT_STRING, CONSTANT_LIST +# Prod: ReportedBy, TO: ReportedBy -> Cont: Reported_By + "Reported_By", # REFERENCE + "Resolution", # SHORT_STRING, CONSTANT_LIST + "ResolveNote", # MULTILINE_STRING + "ResolvedBy", # REFERENCE + "Resolved_Date", # DATE_TIME +# Prod: , TO: -> Cont: Root_Caused + "Root_Caused", # SHORT_STRING, CONSTANT_LIST +# Prod: , TO: -> Cont: Root_Caused_Note + "Root_Caused_Note", # MULTILINE_STRING + "Service_Pack", # SHORT_STRING, DYNAMIC_LIST + "Severity", # SHORT_STRING, CONSTANT_LIST + "Software", # SHORT_STRING, DYNAMIC_LIST + "Software_Version", # SHORT_STRING + "Submit_Date", # DATE_TIME + "Submitter", # REFERENCE + "Symptoms", # MULTILINE_STRING, CONSTANT_LIST + "TCProcedure", # MULTILINE_STRING + "TestCaseID", # INT + "TestcaseComment", # MULTILINE_STRING + "TimeFromSubmitToVerify", # SHORT_STRING + "TimeSubmitToResolve", # SHORT_STRING + "TimeToVerify", # SHORT_STRING +# Prod: Title_2, TO: Title_2 -> Cont: Title + "Title", # SHORT_STRING + "VerifiedBy", # REFERENCE + "Verified_Date", # DATE_TIME + "Verified_In_HW_Version", # SHORT_STRING + "Verified_In_SW_Version", # SHORT_STRING + "VerifyNote", # MULTILINE_STRING +# Prod: , TO: -> Cont: + "Visibility", # SHORT_STRING, DYNAMIC_LIST +# Prod: , TO: -> Cont: WorkAroundNote + "WorkAroundNote", # MULTILINE_STRING + "customer", # REFERENCE_LIST + "customer_severity", # SHORT_STRING, CONSTANT_LIST + "old_id", # SHORT_STRING +# Prod: , TO: Found_In_Project -> Cont: +# "Found_In_Project", # SHORT_STRING, DYNAMIC_LIST +# Deleted fields: +# "HUT_Version", # SHORT_STRING, DYNAMIC_LIST +# "Open_Close_Status", # SHORT_STRING, CONSTANT_LIST +# "PendingHWSWReleases", # INT +# "SQATestCase", # SHORT_STRING, CONSTANT_LIST +# "TestBlocking", # SHORT_STRING, CONSTANT_LIST + ); + + # Customer and Project records appear in both instances of the old + # databases as well as the new Cont database and have not changed. + our @customer_fields = ( + "Name", # SHORT_STRING + "Phone", # SHORT_STRING + "Fax", # SHORT_STRING + "Email", # SHORT_STRING + "CallTrackingID", # SHORT_STRING + "Description", # MULTILINE_STRING + "Company", # SHORT_STRING + "Attachment", # ATTACHMENT_LIST + ); + + our @project_fields = ( + "Name", # SHORT_STRING + "Description", # MULTILINE_STRING + ); + + # Collect bad characters + our %bad_chars; + + ## Internal variables ## + my $login = ""; + my $password = ""; + my $db_name; + + my $id; + + my $nbr_chars = 40; + my $half = $nbr_chars / 2; + + # Derived from http://hotwired.lycos.com/webmonkey/reference/special_characters/ + my %char_map = ( + 128 => "€", + 129 => "", + 130 => "‚", + 131 => "ƒ", + 132 => "„", + 133 => "…", + 134 => "†", + 135 => "‡", + 136 => "ˆ", + 137 => "‰", + 138 => "Š", + 139 => "‹", + 140 => "Œ", + 141 => "", + 142 => "Ž", + 143 => "", + 144 => "", + 145 => "'", # Signal "smart quote" left + 146 => "'", # Signal "smart quote" right + 147 => "\"", # Double "smart quote" left + 148 => "\"", # Double "smart quote" right + 149 => "•", + 150 => "–", # En dash + 151 => "—", # Em dash + 152 => "˜", + 153 => "™", + 154 => "š", + 155 => "›", + 156 => "œ", + 157 => "", + 158 => "ž", + 159 => "Ÿ", + 160 => " ", # Nonbreaking space + 161 => "¡", # Inverted exclamation (¡) + 162 => "¢", # Cent sign (¢) + 163 => "£", # Pound sterling (£) + 164 => "¤", # General currency sign (¤) + 165 => "¥", # Yen sign (¥) + 166 => "&brkbar;", # Broken vertical bar (¦) + 167 => "§", # Section sign (§) + 168 => "¨", # Umlaut (¨) + 169 => "©", # Copyright (©) + 170 => "ª", # Feminine ordinal (ª) + 171 => "«", # Left angle quote («) + 172 => "¬", # Not sign (¬) + 173 => "­", # Soft hyphen + 174 => "®", # Registered trademark (®) + 175 => "¯", # Macron accent (¯) + 176 => "°", # Degree sign (°) + 177 => "±", # Plus or minus (±) + 178 => "²", # Superscript two (²) + 179 => "³", # Superscript three (³) + 180 => "´", # Acute accent (´) + 181 => "µ", # Micro sign (µ) + 182 => "¶", # Paragraph sign (¶) + 183 => "·", # Middle dot (·) + 184 => "¸", # Cedilla (¸) + 185 => "¹", # Superscript one (¹) + 186 => "º", # Masculine ordinal (º) + 187 => "»", # Right angle quote (») + 188 => "¼", # One-forth (¼) + 189 => "½", # One-half (½) + 190 => "&frac24;", # Three-fourths (¾) + 191 => "¿", # Inverted question mark (¿) + 192 => "À", # Uppercase A, grave accent (À) + 193 => "Á", # Uppercase A, acute accent (Á) + 194 => "Â", # Uppercase A, circumflex accent (Â) + 195 => "Ã", # Uppercase A, tilde (Ã) + 196 => "Ä", # Uppercase A, umlaut (Ä) + 197 => "Å", # Uppercase A, ring (Å) + 198 => "Æ", # Uppercase AE (Æ) + 199 => "Ç", # Uppercase C, cedilla (Ç) + 200 => "È", # Uppercase E, grave accent (È) + 201 => "É", # Uppercase E, acute accent (É) + 202 => "Ê", # Uppercase E, circumflex accent (Ê) + 203 => "Ë", # Uppercase E, umlaut (Ë) + 204 => "Ì", # Uppercase I, grave accent (Ì) + 205 => "Í", # Uppercase I, acute accent (Í) + 206 => "Î", # Uppercase I, circumflex accent (Î) + 207 => "Ï", # Uppercase I, umlaut (Ï) + 208 => "Ð", # Uppercase Eth, Icelandic (Ð) + 209 => "Ñ", # Uppercase N, tilde (Ñ) + 210 => "Ò", # Uppercase O, grave accent (Ò) + 211 => "Ó", # Uppercase O, acute accent (Ó) + 212 => "Ô", # Uppercase O, circumflex accent (Ô) + 213 => "Õ", # Uppercase O, tilde (Õ) + 214 => "Ö", # Uppercase O, umlaut (Ö) + 215 => "×", # Muliplication sign (×) + 216 => "Ø", # Uppercase O, slash (Ø) + 217 => "Ù", # Uppercase U, grave accent (Ù) + 218 => "Ú", # Uppercase U, acute accent (Ú) + 219 => "Û", # Uppercase U, circumflex accent (Û) + 220 => "Ü", # Uppercase U, umlaut (Ü) + 221 => "Ý", # Uppercase Y, acute accent (Ý) + 222 => "Þ", # Uppercase THORN, Icelandic (Þ) + 223 => "ß", # Lowercase sharps, German (ß) + 224 => "à", # Lowercase a, grave accent (à) + 225 => "á", # Lowercase a, acute accent (á) + 226 => "â", # Lowercase a, circumflex acirc (â) + 227 => "ã", # Lowercase a, tilde (ã) + 228 => "ä", # Lowercase a, umlaut (ä) + 229 => "å", # Lowercase a, ring (å) + 230 => "æ", # Lowercase ae (æ) + 231 => "ç", # Lowercase c, cedilla (ç) + 232 => "è", # Lowercase e, grave accent (è) + 233 => "é", # Lowercase e, acute accent (é) + 234 => "ê", # Lowercase e, circumflex accent (ê) + 235 => "ë", # Lowercase e, umlaut (ë) + 236 => "ì", # Lowercase i, grave accent (ì) + 237 => "í", # Lowercase i, acute accent (í) + 238 => "î", # Lowercase i, circumflex accent (î) + 239 => "ï", # Lowercase i, umlaut (ï) + 240 => "ð", # Lowercase eth, Icelandic (ð) + 241 => "ñ", # Lowercase n, tilde (ñ) + 242 => "ò", # Lowercase o, grave accent (ò) + 243 => "ó", # Lowercase o, acute accent (ó) + 244 => "ô", # Lowercase o, circumflex accent (ô) + 245 => "õ", # Lowercase o, tilde (õ) + 246 => "ö", # Lowercase o, umlaut (ö) + 247 => "÷", # Division sign (÷) + 248 => "ø", # Lowercase o, slash (ø) + 249 => "ù", # Lowercase u, grave accent (ù) + 250 => "ú", # Lowercase u, acute accent (ú) + 251 => "û", # Lowercase u, circumflex accent (û) + 252 => "ü", # Lowercase u, umlaut (ü) + 253 => "ý", # Lowercase y, acute accent (ý) + 254 => "þ", # Lowercase thorn, Icelandic (þ) + 255 => "ÿ", # Lowercase y, umlaut (ÿ) + ); + + ## Exported functions ## + # Add a value to a field's dynamic list + sub AddToFieldChoiceList { + my $session = shift; + my $entity = shift; + my $dynamic_list = shift; + my $name = shift; + my $value = shift; + + return if $value eq ""; + + # It seems that adding the entry to the dynamic list is not enough. + # I believe that Clearquest caches entries on a dynamic list so we + # need to tell Clearquest about this new entry. + my $add_value = 1; + my @values = @{$entity->GetFieldChoiceList ($name)}; + + # Ack! Seems now we have values like Service_Pack = "1.A" and + # Service_Pack = "1.a", which translate to the same value as far + # as a dynamic list is concerned, so we'll do the comparison + # ignoring case... Additionally there can be regex meta characters + # in the value so we'll need to protect from that. + foreach (@values) { + if ("\L$value\E" eq "\L$_\E") { + $add_value = 0; + last; + } # if + } # foreach + + if ($add_value) { + push @values, $value; + + $entity->SetFieldChoiceList ($name, \@values); + } # if + + # Get the current values, if any + @values = @{$session->GetListMembers ($dynamic_list)}; + + # Search to see if the item is already on the list + foreach (@values) { + return if ("\L$value\E" eq "\L$_\E"); + } # if + + $session->AddListMember ($dynamic_list, $value); + + push @values, $value; + + $session->SetListMembers ($dynamic_list, \@values); + } # AddToDynamicList + + # TO: defect: Found_In_Project is currently a dynamic list but is + # going to Cont: defect: Found_In_Project which is a reference to + # Cont: Project. So we need to dynamically add those. + sub AddToProject { + my $log = shift; + my $to = shift; + my $project = shift; + + if (ProjectExists $to, $project) { + return; + } # if + + my $entity = $to->BuildEntity ("Project"); + + $entity->SetFieldValue ("name", $project); + + # Call the Validate method + my $errmsg = $entity->Validate; + + $log->err ("Unable to validate Project record: $project:\n$errmsg", 1) if $errmsg ne ""; + + # Post record to database + $entity->Commit if $errmsg eq ""; + } # AddToProject + + sub CheckField { + my $log = shift; + my $db_name = shift; + my $record_name = shift; + my $id = shift; + my $field_name = shift; + my $str = shift; + + return $str if length $str eq 0; # Ignore empty strings + + if ($str =~ /[^\t\n\r -\177]/) { + for (my $x = 0; $x < length $str; $x++) { + my $y = substr $str, $x, 1; + if ($y =~ /[^\t\n\r -\177]/) { + my $o = ord ($y); + display "At char #$x found \"$y\" ($o)"; + my $s = substr $str, $x - 20, 40; + display "\"$s\""; + } # if + } # for + error "$field_name match", 1; + } # if + + for (my $i = 0; $i < length $str; $i++) { + my $ord = ord (substr $str, $i, 1); + + if ($ord < 0 or $ord > 127) { + # $id is undefined at this point... + $log->msg ("$db_name:$record_name:$id:$field_name:$i"); + $log->msg ("Old Contents:\n$str"); + $str = FixChar ($str, $i); + $log->msg ("New Contents:\n$str"); + } # if + } # foreach + + return $str; + } # CheckField + + sub CheckRecord { + my $log = shift; + my $session = shift; + my $id_name = shift; + my $record_name = shift; + my $id = shift; + my @fields = @_; + + my $result; + + if (defined $id) { + $result = GetDefectRecord $log, $session, $record_name, $id; + } else { + $result = GetAllDefectRecords $log, $session, $record_name; + } # if + + while ($result->MoveNext == $CQPerlExt::CQ_SUCCESS) { + # GetEntity by using $id + $id = $result->GetColumnValue (1); + my $entity = $session->GetEntity ($record_name, $id); + + $log->msg ($id); + + foreach (@fields) { + my $name = $_; + my $value = $entity->GetFieldValue ($name)->GetValue; + + $value = CheckField $log, $db_name, $record_name, $id, $name, $value; + } # for + } # for + } # CheckRecord + + sub DeleteDynamicLists { + my $log = shift; + my $from = shift; + + my @dynamic_lists = ( + "Advanced_Feature", + "Board_Revision", + "HUT", + "HUT_Revision", + "OS", + "OS_Service_Pack", + "Other_HUT", + "Project", + "Reported_By", + "Software", + "Visibility", + ); + + $log->msg ("Clearing dynamic lists..."); + + foreach my $name (@dynamic_lists) { + my @values = @{$from->GetListMembers ($name)}; + + foreach my $value (@values) { + $from->DeleteListMember ($name, $value); + } # foreach + } # foreach + } # DeleteDynamicLists + + sub DeleteRecords { + my $log = shift; + my $from = shift; + my $record_name = shift; + + # Create a query for $record_name + my $query = $from->BuildQuery ($record_name); + + $query->BuildField ("dbid"); + + # Build the result set + my $result = $from->BuildResultSet ($query); + + # Execute the query + my $record_count = $result->ExecuteAndCountRecords; + + $log->msg ("Found $record_count $record_name records to delete..."); + + return if $record_count eq 0; + + my $old_bufffer_status = $|; + $| = 1; # Turn off buffering + + # Now for each record returned by the query... + while ($result->MoveNext == 1) { + my $id = $result->GetColumnValue (1); + + # Get entity + my $entity = $from->GetEntityByDbId ($record_name, $id); + + # Delete it + my $errmsg = $from->DeleteEntity ($entity, "delete"); + + verbose ".", undef, "nolf"; + $log->err ("\n$errmsg\n") if $errmsg ne ""; + } # while + + verbose ""; + + $| = $old_bufffer_status; # Restore buffering + } # DeleteRecords + + sub EndSession { + my $session = shift; + + CQSession::Unbuild $session; + } # EndSession + + sub GetAllDefectRecords { + my $log = shift; + my $from = shift; + my $record_name = shift; + + # Create a query for the record + my $query = $from->BuildQuery ($record_name); + + # Add only dbid to the query. We'll retrieve the whole entity record later. + $query->BuildField ("id"); + + # Build the result set + my $result = $from->BuildResultSet ($query); + + # Execute the query + my $record_count = $result->ExecuteAndCountRecords; + + $log->msg ("Found $record_count $record_name records..."); + + if ($record_count eq 0) { + return undef; + } else { + return $result; + } # if + } # GetAllDefectRecords + + sub GetDefectRecord { + my $log = shift; + my $from = shift; + my $record_name = shift; + my $id = shift; + + my $query = $from->BuildQuery ($record_name); + my $filter = $query->BuildFilterOperator ($CQPerlExt::AD_BOOL_OP_AND); + + $query->BuildField ("id"); + + # BuildFilter requires an array reference + my @ids; + push @ids, $id; + $filter->BuildFilter ("id", $CQPerlExt::CQ_COMP_OP_EQ, \@ids); + + my $result = $from->BuildResultSet ($query); + my $record_count = $result->ExecuteAndCountRecords; + + $log->msg ("Found $record_count $record_name record..."); + + if ($record_count eq 0) { + return undef; + } else { + return $result; + } # if + } # GetDefectRecord + + sub ProjectExists { + my $to = shift; + my $project = shift; + + my $query = $to->BuildQuery ("Project"); + + my $filter = $query->BuildFilterOperator ($CQPerlExt::AD_BOOL_OP_AND); + + $query->BuildField ("name"); + + # BuildFilter requires an array reference + my @projects; + push @projects, $project; + $filter->BuildFilter ("name", $CQPerlExt::CQ_COMP_OP_EQ, \@projects); + + my $result = $to->BuildResultSet ($query); + + my $record_count = $result->ExecuteAndCountRecords; + + return $record_count; + } # ProjectExists + + sub StartSession { + $db_name = shift; + $masterdb = shift; + + my $session = CQPerlExt::CQSession_Build (); + + $masterdb = "" if !defined $masterdb; + + $session->UserLogon ($login, $password, $db_name, $masterdb); + + return $session; + } # StartSession + + sub TransferAttachments { + my $log = shift; + my $from = shift; + my $to = shift; + + my @files_created; + + my $from_attachment_fields = $from->GetAttachmentFields; + + for (my $i = 0; $i < $from_attachment_fields->Count; $i++) { + my $from_attachment_field = $from_attachment_fields->Item ($i); + my $field_name = $from_attachment_field->GetFieldName; + + # At this point we don't have any info about whether we are + # coming from Prod or TO, however, there are the following fields: + # + # TO Prod Cont + # ----------------------- ----------------------- ---------------- + # Attachments Attachments Attachments + # AttachmentsBRCM AttachmentBRCM AttachmentsBRCM + # + # You may notice that Prod: AttachmentBRCM is missing the "s". + # Therefore: + $field_name = "AttachmentsBRCM" if $field_name eq "AttachmentBRCM"; + + my $from_attachments = $from_attachment_field->GetAttachments; + + my $filename_suffix = 0; + + for (my $j = 0; $j < $from_attachments->Count; $j++) { + my $from_attachment = $from_attachments->Item ($j); + my $description = $from_attachment->GetDescription; + my $filename = $from_attachment->GetFileName; + + debug "Processing attachment #$j: $filename: $description"; + + # Extract the attached file to the file named attachment; + # Argh! Sometimes people attach files with the same filename! + # This works because filename is not really used except when + # you initially load the file. So the user could have, for + # example, captured say a logfile.txt, attached it, + # regenerated a new logfile.txt and attached it! This is + # perfectly acceptable since logfile.txt is copied into the + # database. However, when we extract it here we just use + # $filename. The result is that the second logfile.txt + # overwrites the first logfile.txt! We need to check for + # clashes (only a handful of them) and generate a new + # filename. + if (-f $filename) { + $filename_suffix++; + $filename = "$filename.$filename_suffix"; + } # if + + $from_attachment->Load ($filename); + + $to->AddAttachmentFieldValue ($field_name, $filename, $description); + + push @files_created, $filename; + } # for + } # for + + return @files_created; + } # TransferAttachments + + sub TransferHistory { + my $from_entity = shift; + my $to_entity = shift; + my $filename = shift; + + my $history_fields = $from_entity->GetHistoryFields; + my $nbr_history_fields = $history_fields->Count; + + return if $nbr_history_fields eq 0; + + for (my $i = 0; $i < $nbr_history_fields; $i++) { + my $histories = $history_fields->Item ($i)->GetHistories; + my $nbr_histories = $histories->Count; + + return if $nbr_histories eq 0; + + # Write out history to History.txt + open HISTORY, ">$filename" + or error "Unable to open $filename", 1; + + print HISTORY "Previous History:\n"; + print HISTORY "-----------------\n"; + + for (my $j = 0; $j < $nbr_histories; $j++) { + my $history_item = $histories->Item ($j); + my $history_value = $history_item->GetValue; + + # Remove dbid + $history_value =~ /\S*\s*(.*$)/; + print HISTORY "$1\n"; + } # for + + close HISTORY; + } # for + + # Add previous history as an AttachmentsBRCM + $to_entity->AddAttachmentFieldValue ("AttachmentsBRCM", $filename, "Previous history"); + } # TransferHistory + + sub TransferRecords { + my $log = shift; + my $from = shift; + my $to = shift; + my $dbname = shift; + my $record_name = shift; + my @field_list = @_; + + # Create a query for the record + my $query = $from->BuildQuery ($record_name); + + # Always get the $id_name field + $query->BuildField ("dbid"); + + # Add all of @field_list to the query + foreach (@field_list) { + $query->BuildField ($_); + } # foreach + + # Build the result set + my $result = $from->BuildResultSet ($query); + + # Execute the query + my $record_count = $result->ExecuteAndCountRecords; + + verbose "Found $record_count $record_name records to merge..."; + + return if $record_count eq 0; + + my $old_bufffer_status = $|; + $| = 1; # Turn off buffering + + # Now for each record returned by the query... + while ($result->MoveNext == 1) { + # Create a new entity + my $entity = $to->BuildEntity ($record_name); + + my $cols = $result->GetNumberOfColumns; + + my $id = $result->GetColumnValue (1); + + # Get the fields... + for (my $i = 2; $i <= $cols; $i++) { + my $name = $result->GetColumnLabel ($i); + my $value = $result->GetColumnValue ($i); + + # Check field for non US ASCII characters and fix them + $value = CheckField $dbname, $record_name, $id, $name, $value; + + # Set the field's value + $entity->SetFieldValue ($name, $value); + } # for + + # Call the Validate method + my $errmsg = $entity->Validate; + + $log->err ("Unable to validate $record_name record:\n$errmsg", 1) if $errmsg ne ""; + + # Post record to database + $entity->Commit; + verbose ".", undef, "nolf"; + } # while + + $| = $old_bufffer_status; # Restore buffering + verbose " done"; + } # TransferRecords + + # Internal functions + sub DisplayWord { + my $str = shift; + my $start = shift; + + my $ord = ord (substr $str, $start, 1); + my $end = $start; + my $orig_start = $start; + + # Let's just show a small subset of characters + if (length $str < $nbr_chars) { + $end = length $str; + $start = 0; + } elsif (($start + $half) > length $str) { + $end = length $str; + my $right = length $str - $start; + if (($start - ($half + ($half - $right))) lt 0) { + $start = 0; + } else { + $start = $start - ($half + $right); + } # if + } elsif (($start - $half) < 0) { + $start = 0; + if ($start + ($half + $start) gt length $str) { + $end = length $str; + } else { + $end = $start + ($half + $start); + } # if + } else { + $end = $start + $half; + $start = $start - $half; + } # if + + my $word = substr $str, $start, $end - $start; + + debug "\t@ pos $orig_start ($ord)\n\t\"$word\"\n"; + } # DisplayWord + + sub FixChar { + my $str = shift; + my $pos = shift; + + my $ord = ord (substr $str, $pos, 1); + + error "Unknown character found ($ord) \"" . substr ($str, $pos, 1) . "\"", 1 + if (!defined $char_map {$ord}); + + if ($debug eq "yes") { + debug "Before:\n"; + DisplayWord $str, $pos; + } # if + + substr ($str, $pos, 1) = $char_map {$ord}; + + if ($debug eq "yes") { + debug "After:\n"; + DisplayWord $str, $pos; + } # if + + return $str; + } # FixChar + +1; diff --git a/cq/check_attachments b/cq/check_attachments new file mode 100644 index 0000000..97206d2 --- /dev/null +++ b/cq/check_attachments @@ -0,0 +1,122 @@ +#!cqperl +use strict; +use warnings; +use CQPerlExt; +use File::Spec; + +our ($me, $SEPARATOR); + +my ($abs_path, $lib_path); + +BEGIN { + # Extract relative path and basename from script name. + $0 =~ /(.*)[\/\\](.*)/; + + $abs_path = (!defined $1) ? "." : File::Spec->rel2abs ($1); + $me = (!defined $2) ? $0 : $2; + $me =~ s/\.pl$//; + + # Define the path SEPARATOR + $SEPARATOR = ($^O =~ /MSWin/) ? "\\" : "/"; + + # Setup paths + $lib_path = "$abs_path" . $SEPARATOR . ".." . $SEPARATOR . "lib"; + + # Add the appropriate path to our modules to @INC array. + unshift (@INC, "$abs_path"); + unshift (@INC, "$lib_path"); +} # BEGIN + +use PQA; +use Display; + +sub TotalAttachment { + my $log = shift; + my $id = shift; + my $from = shift; + + my $attachments_size = 0; + + my $from_attachment_fields = $from->GetAttachmentFields; + + for (my $i = 0; $i < $from_attachment_fields->Count; $i++) { + my $from_attachment_field = $from_attachment_fields->Item ($i); + my $field_name = $from_attachment_field->GetFieldName; + + # Process attachments in this attachment field + my $from_attachments = $from_attachment_field->GetAttachments; + + for (my $j = 0; $j < $from_attachments->Count; $j++) { + my $from_attachment = $from_attachments->Item ($j); + my $description = $from_attachment->GetDescription; + my $filename = $from_attachment->GetFileName; + my $size = $from_attachment->GetFileSize; + + next if $filename eq "history.txt"; + $log->msg ("$id,$filename,$size"); + $attachments_size += $size; + } # for + } # for + + $log->msg ("$id,Total attachment size,$attachments_size") if $attachments_size ne 0; + + return $attachments_size; +} # TotalAttachment + +my $log = Logger->new (path => "."); + +# Open databases +my $record_name = "defect"; + +my $connection = "2005.02.00"; +my $cont = StartSession "Cont", $connection; + +$connection = "2003.06.00"; +my $teton = StartSession "TO", $connection; +my $prod = StartSession "Prod", $connection; + +my $result = GetAllDefectRecords $log, $cont, $record_name; + +my $grand_total_old = 0; +my $grand_total_new = 0; + +while ($result->MoveNext == $CQPerlExt::CQ_SUCCESS) { + # GetEntity by using $id + my $id = $result->GetColumnValue (1); + my $from = $cont->GetEntity ($record_name, $id); + + my $new_size = TotalAttachment $log, $id, $from; + + my $old_id = $from->GetFieldValue ("old_id")->GetValue; + + my $to; + + if ($old_id =~ /^TO/) { + $to = $teton->GetEntity ($record_name, $old_id); + } elsif ($old_id =~ /^Prod/) { + $to = $prod->GetEntity ($record_name, $old_id); + } else { + error "Old_id is not set! $old_id"; + } # if + + my $old_size = TotalAttachment $log, $id, $to; + + $grand_total_old += $old_size; + $grand_total_new += $new_size; + + if ($new_size gt $old_size) { + display "$id:$new_size > $old_id:$old_size"; + } elsif ($new_size lt $old_size) { + display "$id:$new_size < $old_id:$old_size"; +# } else { +# display "$id:$new_size = $old_id:$old_size"; + } # if + +} # while + +display "Grand total (old): $grand_total_old"; +display "Grand total (new): $grand_total_new"; + +EndSession $cont; +EndSession $teton; +EndSession $prod; diff --git a/cq/convertList.pl b/cq/convertList.pl new file mode 100644 index 0000000..07c0e0f --- /dev/null +++ b/cq/convertList.pl @@ -0,0 +1,213 @@ +#!/usr/bin/perl +use strict; +use warnings; + +=pod + +=head1 NAME $RCSfile: convertList.pl,v $ + +This script allows you to convert a Clearquest Dynamic List to a stateless +table. You must specify what the dynamic list name is, the stateless table name +you wish to convert it to and the field name that serves as the key. + +This script will note duplicate and skip them. It will not remove the dynamic +list. + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 2.2 $ + +=item Created: + +Mon Oct 24 16:19:15 PDT 2011 + +=item Modified: + +$Date: 2012/12/18 19:44:10 $ + +=back + +=head1 SYNOPSIS + + Usage: convertList.pl -list -table -field + [-u|sage] [-v|erbose] [-d|ebug] + [-username ] [-password ] + [-database ] [-dbset ] + [-module] [-server ] [-port ] + + Where: + -l|ist: Dynamic list name to convert + -t|able: Name of the stateless table to convert the dynamic + list to + -field: Name of the field to fill in with the values from + -list + + -usa|ge: Displays usage + -v|erbose: Be verbose + -de|bug: Output debug messages + + -use|rname: Username to open database with (Default: from config file) + -p|assword: Password to open database with (Default: from config file) + -da|tabase: Database to open (Default: from config file) + -db|set: Database Set to use (Default: from config file) + -m|odule: Type of Clearquest module to use. Must be one of 'api', + 'client', or 'rest'. The 'api' module can only be used if + Clearquest is installed locally. The 'client' module can + only be successful if a corresponding server is running. And + the 'rest' module can only be used if a CQ Web server has + been set up and configured (Default: rest) + -s|erver: For module = client or rest this is the name of the server + that will be providing the service + -p|ort: For module = client, this is the point on the server to talk + through. + +=head1 Options + +Options are keep in the cq.conf file in etc. They specify the default options +listed below. Or you can export the option name to the env(1) to override the +defaults in cq.conf. Finally you can programmatically set the options when you +call new by passing in a %parms hash. To specify the %parms hash key remove the +CQ_ portion and lc the rest. + +=for html
    + +=over + +=item CQ_WEBHOST + +The web host to contact with leading http:// + +=item CQ_DATABASE + +Name of database to connect to (Default: from config file) + +=item CQ_USERNAME + +User name to connect as (Default: from config file) + +=item CQ_PASSWORD + +Password for CQ_USERNAME + +=item CQ_DBSET + +Database Set name (Default: from config file) + +=item CQ_SERVER + +Clearquest::Server name to connect to (Default: from config file) + +=item CQ_PORT + +Clearquest::Server port to connect to (Default: from config file) + +=back + +=cut + +use FindBin; +use Getopt::Long; + +use lib "$FindBin::Bin/../lib"; + +use Clearquest; +use Display; +use Logger; +use TimeUtils; +use Utils; + +my $VERSION = '$Revision: 2.2 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +my (%opts, $cq, $log, %totals); + +## Main +local $| = 1; + +my $startTime = time; + +GetOptions ( + \%opts, + usage => sub { Usage }, + verbose => sub { set_verbose }, + debug => sub { set_debug }, + 'module=s', + 'username=s', + 'database=s', + 'password=s', + 'dbset=s', + 'list=s', + 'table=s', + 'field=s', + 'server=s', + 'port=i', +) || Usage; + +$log = Logger->new; + +$log->msg ("$FindBin::Script v$VERSION"); + +Usage 'Must specify -list' unless $opts{list}; +Usage 'Must specify -table' unless $opts{table}; +Usage 'Must specify -field' unless $opts{field}; + +# Translate any options to ones that the lib understands +$opts{CQ_USERNAME} = delete $opts{username}; +$opts{CQ_PASSWORD} = delete $opts{password}; +$opts{CQ_DATABASE} = delete $opts{database}; +$opts{CQ_DBSET} = delete $opts{dbset}; +$opts{CQ_SERVER} = delete $opts{server}; +$opts{CQ_PORT} = delete $opts{port}; +$opts{CQ_MODULE} = delete $opts{module}; + +$cq = Clearquest->new (%opts); + +my $connection = $cq->username . '@' . $cq->database . '/' . $cq->dbset; + $connection .= ' (Server: ' . $cq->host . ':' . $cq->port . ')' + if ref $cq eq 'Clearquest::Client'; + +$log->msg ("Connecting to $connection...", 1); + +$cq->connect; + +$log->msg (' connected'); + +foreach ($cq->getDynamicList ($opts{list})) { + verbose_nolf '.'; + + $totals{Processed}++; + + my $errmsg = $cq->add ($opts{table}, ($opts{field} => $_)); + + if ($errmsg ne '') { + if ($errmsg =~ /duplicate entries in the database/ or + $errmsg =~ /Record with same displayname exists/) { + $totals{Duplicates}++; + } else { + $log->err ($errmsg); + } # if + } else { + $totals{Added}++; + } # if +} # foreach + +$totals{Errors} = $log->errors; + +error 'Errors occured - check ' . $log->fullname . ' for more info' + if $totals{Errors}; + +Stats \%totals, $log; + +display_duration $startTime, $log; + +$cq->disconnect; + +exit $log->errors; diff --git a/cq/cqaction.pl b/cq/cqaction.pl new file mode 100644 index 0000000..65b0731 --- /dev/null +++ b/cq/cqaction.pl @@ -0,0 +1,230 @@ +#!/usr/bin/perl +use strict; +use warnings; + +=pod + +=pod + +=head1 NAME $RCSfile: cqaction.pl,v $ + +Clearquest Action + +This script attempt to apply an action to a statefull Clearquest record. + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 2.2 $ + +=item Created: + +Mon Jul 30 12:05:45 PDT 2012 + +=item Modified: + +$Date: 2012/12/18 19:44:10 $ + +=back + +=head1 SYNOPSIS + + Usage: cqaction.pl [-u|sage] [-v|erbose] [-d|ebug] + [-username ] [-password ] + [-database ] [-dbset ] + [-record ] [-key ] + [-action ] + [-module] [-server ] [-port ] + + + Where: + -u|sage: Displays usage + -v|erbose: Be verbose + -de|bug: Output debug messages + + -record: Record to apply the action to (Default: Defect) + -key: Key to locate the record with (Note that if you supply simply + a number (e.g. 1234) then we will expand that with leading + zeroes to the length of 8 digits and prepend the database name) + -action: Action to apply (Default: Modify) + + -use|rname: Username to open database with (Default: from config file) + -p|assword: Password to open database with (Default: from config file) + -da|tabase: Database to open (Default: from config file) + -db|set: Database Set to use (Default: from config file) + -m|odule: Type of Clearquest module to use. Must be one of 'api', + 'client', or 'rest'. The 'api' module can only be used if + Clearquest is installed locally. The 'client' module can + only be successful if a corresponding server is running. And + the 'rest' module can only be used if a CQ Web server has + been set up and configured (Default: rest) + -s|erver: For module = client or rest this is the name of the server + that will be providing the service + -p|ort: For module = client, this is the point on the server to talk + through. + +=head1 Options + +Options are keep in the cq.conf file in etc. They specify the default options +listed below. Or you can export the option name to the env(1) to override the +defaults in cq.conf. Finally you can programmatically set the options when you +call new by passing in a %parms hash. To specify the %parms hash key remove the +CQ_ portion and lc the rest. + +=for html
    + +=over + +=item CQ_WEBHOST + +The web host to contact with leading http:// + +=item CQ_DATABASE + +Name of database to connect to (Default: from config file) + +=item CQ_USERNAME + +User name to connect as (Default: from config file) + +=item CQ_PASSWORD + +Password for CQ_USERNAME + +=item CQ_DBSET + +Database Set name (Default: from config file) + +=item CQ_SERVER + +Clearquest::Server name to connect to (Default: from config file) + +=item CQ_PORT + +Clearquest::Server port to connect to (Default: from config file) + +=back + +=head1 Modifying fields while changing state + +If you need to modify fields while changing state then feed them to this +script's stdin in the form of: + + = + +B Don't forget that you will be prompted field=value and you'll need +to signal that you have entered all of the field/value pairs you intended with +Ctrl-D (or Ctrl-Z on Windows). You can short circut this by feeding something +like /dev/null to stdin like so: + + $ cat /dev/null > cqaction.pl + +=cut + +use FindBin; +use Getopt::Long; + +use lib "$FindBin::Bin/../lib"; + +use Display; +use Utils; + +my %opts; + +sub getFields () { + my %values; + + verbose "Enter = pairs and Ctrl-D to end input"; + + while () { + if (/^(\s+)(=|:)(\.*)/) { + $values{$1} = $2; + } # if + } # while + + verbose "All = pairs accepted"; + + return %values; +} # getFields + +$opts{module} = 'rest'; + +GetOptions ( + \%opts, + usage => sub { Usage }, + verbose => sub { set_verbose }, + debug => sub { set_debug }, + 'module=s', + 'username=s', + 'password=s', + 'database=s', + 'dbset=s', + 'record=s', + 'key=s', + 'server=s', + 'port=i', + 'action=s', +) || Usage; + +Usage "You must specify -key" unless $opts{key}; + +# Default to Defect +my $record = delete $opts{record} || 'Defect'; +my $key = delete $opts{key}; +my $action = delete $opts{action} || 'Modify'; + +# Translate any options to ones that the lib understands +$opts{CQ_USERNAME} = delete $opts{username}; +$opts{CQ_PASSWORD} = delete $opts{password}; +$opts{CQ_DATABASE} = delete $opts{database}; +$opts{CQ_DBSET} = delete $opts{dbset}; +$opts{CQ_SERVER} = delete $opts{server}; +$opts{CQ_PORT} = delete $opts{port}; + +my $cq; + +my $module = lc delete $opts{module}; + +if ($module eq 'rest') { + require Clearquest::REST; + + $cq = Clearquest::REST->new (%opts); +} elsif ($module eq 'client') { + require Clearquest::Client; + + $cq = Clearquest::Client->new (%opts); + + $cq->connect; +} elsif ($module eq 'api') { + require Clearquest; + + $cq = Clearquest->new (%opts); + + $cq->connect; +} else { + Usage "Invalid module - $opts{module}"; +} # if + +# Fix key if necessary +if ($key =~ /^(\d+)$/) { + $key = $cq->{database} . 0 x (8 - length $1) . $1; +} # if + +my %values = getFields; + +my $errmsg = $cq->modify ($record, $key, $action, %values); + +unless ($cq->cqerror) { + verbose "Successfully applied $action to $record:$key"; + + exit 0; +} else { + error "Unable to apply $action to $record:$key\n" . $cq->cqerrmsg, $cq->cqerror; +} # unless \ No newline at end of file diff --git a/cq/cqd.pl b/cq/cqd.pl new file mode 100644 index 0000000..b0891e1 --- /dev/null +++ b/cq/cqd.pl @@ -0,0 +1,175 @@ +#!cqperl +use strict; +use warnings; + +=pod + +=head1 NAME $RCSfile: cqd.pl,v $ + +Clearquest Daemon - Daemon to provide access to Clearquest database + +This daemon instanciates an instance of the Clearquest::DBService to service +requests for information of a Clearquest database or to update a Clearquest +database. + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 2.4 $ + +=item Created: + +Mon Oct 24 16:19:15 PDT 2011 + +=item Modified: + +$Date: 2013/03/15 00:15:32 $ + +=back + +=head1 SYNOPSIS + + Usage: cqd.pl [-u|sage] [-v|erbose] [-d|ebug] + [-logfile ] [-[no]daemon] + [-s|erver ] [-p|ort ] + + Where: + -u|sage: Displays usage + -v|erbose: Be verbose + -de|bug: Output debug messages + + -s|erver : Server to talk to (Default: from conf file or + environment) + -p|ort Port nbr to use (Default: from conf file or + environment) + -m|ultithreaded + -logfile : Where to log output (Default: STDOUT) + -[no]daemon: Enter daemon mode (Default: Enter daemon mode) + + -s|erver: For module = client or rest this is the name of the server + that will be providing the service + -p|ort: For module = client, this is the point on the server to talk + through. + +=head1 Options + +Options are keep in the cq.conf file in etc. They specify the default options +listed below. Or you can export the option name to the env(1) to override the +defaults in cq.conf. Finally you can programmatically set the options when you +call new by passing in a %parms hash. To specify the %parms hash key remove the +CQ_ portion and lc the rest. + +=for html
    + +=over + +=item CQ_SERVER + +Clearquest::Server name to connect to (Default: from config file) + +=item CQ_PORT + +Clearquest::Server port to connect to (Default: from config file) + +=back + +=cut + +use Config; +use File::Spec; +use FindBin; +use Getopt::Long; + +use CQPerlExt; + +use lib "$FindBin::Bin/../lib"; + +use Clearquest::Server; +use Display; +use Utils; + +my $VERSION = '$Revision: 2.4 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +my %opts; + +GetOptions ( + \%opts, + verbose => sub { set_verbose }, + debug => sub { set_debug }, + usage => sub { Usage }, + 'server=s', + 'port=i', + 'logfile=s', + 'multithreaded!', + 'daemon!', + 'serviceClient=s', + 'socket=s', +) || Usage; + +my %parms = ( + CQ_SERVER => $opts{server}, + CQ_PORT => $opts{port}, + CQ_MULTITHREADED => $opts{multithreaded}, +); + +my $cqservice = Clearquest::Server->new (%parms); + +if ($opts{serviceClient}) { + $cqservice->{clientname} = $opts{serviceClient}; + + debug "In cqd.pl with -serviceClient $cqservice->{clientname} - opening socket"; + + open my $client, '+<&=', *STDIN + or error "Unable to open socket connection to client", 1; + + $client->autoflush (1); + + debug "Socket open - servicing client = $client"; + $cqservice->_serviceClient ($client); + debug "Returned from servicing client"; + + exit; +} # if + +my $announcement = "$FindBin::Script v$VERSION "; + $announcement .= $cqservice->multithreaded + ? '(Multithreaded)' + : '(Singlethreaded)'; + +verbose $announcement; + +if ($opts{daemon} and !get_debug and !defined $DB::OUT) { + print $DB::OUT "Debugging\n" if get_debug; + + my ($logfile) = ($FindBin::Script =~ /(.*)\.pl$/); + + $opts{logfile} ||= "$logfile.log"; + + $logfile = File::Spec->rel2abs ($opts{logfile}); + + verbose "Entering daemon mode (Server pid: $$ - logging to $logfile)"; + + if ($Config{perl} eq 'ratlperl') { + error "Unable to daemonize with cqperl", 1; + } else { + EnterDaemonMode $opts{logfile}, $opts{logfile}; + } # if +} # if + +delete $opts{daemon}; +delete $opts{multithreaded}; + +verbose 'Starting Server'; +$cqservice->startServer; + +verbose 'Shutting down server'; + +exit; \ No newline at end of file diff --git a/cq/cqd/CheckinPreop.pl b/cq/cqd/CheckinPreop.pl new file mode 100644 index 0000000..574d314 --- /dev/null +++ b/cq/cqd/CheckinPreop.pl @@ -0,0 +1,293 @@ +#!/usr/bin/perl +################################################################################ +# +# File: CheckinPreop.pl +# Description: This trigger script is run when the user is attempting to +# checkin. Several checks are performed on the check in comment. +# The comment should contain the bug ID, which we will later used +# to label this element checkin (See CheckinPostop.pl). We will +# also check to insure the bug ID is valid in Clearquest and that +# the bug is in the proper state. +# +# If the check in is on the "main" or "trial" branch then we will +# consult a file to insure that the bug ID is listed. This is an +# additional method for limiting checkins. +# Assumptions: Clearprompt is in the users PATH +# Author: Andrew@DeFaria.com +# Created: Fri Oct 26 15:32:12 2001 +# Language: Perl +# Modifications:6/25/2002: Added check to see if a bug ID label exists and it +# is locked. If so then that's an indication that we should not +# allow the checkin. +# 6/20/2002: Added interface to cqd to verify that the bug exists +# in Clearquest, is of a certain state and has an owner +# 5/15/2002: Added tests so that bug IDs must exist in +# mainbugs.txt or trialbugs.txt for the main and trial branches. +# 5/17/2002: Exempted EMS code. +# 5/31/2002: Exempted hardware code. +# 10/22/2002: Changed to allow checkins to main branch with no +# bug IDs. Removed $mainbugs. +# 11/20/2002: It was determined to relax restrictions of checkins +# for non 1.0 branches such that bug ID's are not required, in fact +# they are not allowed. +# 04/11/2003: Added support for multiple bug IDs in the comment +# 05/18/2003: Changed code to only check for bug IDs in comments +# for check ins on certain branches. +# +# (c) Copyright 2003, Andrew@DeFaria.com, all rights reserved +# +################################################################################ +use strict; + +my $site; + +BEGIN { + # Add the appropriate path to our modules to @INC array. We use ipconfig to + # get the current host's IP address then determine whether we are in the US + # or China. + my @ipconfig = grep (/IP Address/, `ipconfig`); + my ($ipaddr) = ($ipconfig[0] =~ /(\d{1,3}\.\d{1,3}.\d{1,3}\.\d{1,3})/); + + # US is in the subnets of 192 and 172 while China is in the subnet of 10 + if ($ipaddr =~ /^192|^172/) { + $site = "US"; + unshift (@INC, "//sons-clearcase/Views/official/Tools/lib"); + } elsif ($ipaddr =~ /^10/) { + $site = "CN"; + unshift (@INC, "//sons-cc/Views/official/Tools/lib"); + } else { + die "Internal Error: Unable to find our modules!\n" + } # if +} # BEGIN + +use TriggerUtils; +use cqc; + +%cqc::fields; + +# The following environment variables are set by Clearcase when this +# trigger is called +my $comment = $ENV{CLEARCASE_COMMENT}; +my $branch = $ENV{CLEARCASE_BRTYPE}; +my $pname = $ENV{CLEARCASE_PN}; + +# Which vob we will look up labels in +my $vob = "salira"; + +my $bugid; + +sub ExtractBugID { + my $comment = shift; + + my @fields = split (/\W/,$comment); + my $bugid = "unknown"; + + foreach (@fields) { + if (/BUGS2[0-9]{8}/) { + $bugid = $_; + last; + } # if + } # foreach + + return $bugid; +} # ExtractBugID + +sub ExtractBugIDs { + my $comment = shift; + + my @fields = split (/\W/,$comment); + + # Use associative array to insure uniqueness + my %bugids; + # Return unique array + my @bugids; + + foreach (@fields) { + if (/BUGS2[0-9]{8}/) { + $bugids{$_} = $_; + } # if + } # foreach + + foreach (keys %bugids) { + push @bugids, $_; + } + + return @bugids; +} # ExtractBugIDs + +sub BugOnList { + my $bugid = shift; + my $branch = shift; + + my $found_bugid = 0; + my $bug = "unknown"; + + # Excempt EMS code + return 1 if $pname =~ /salira\\ems/i; + + # Excempt Hardware code + return 1 if $pname =~ /salira\\hardware/i; + + # Exempt bug ID 2912 + return 1 if $bugid eq "BUGS200002912"; + + # Exempt bug ID 3035 + return 1 if $bugid eq "BUGS200003035"; + + my $filename; + + if ($site eq "US") { + $filename = "//sons-clearcase/Views/official/Tools/bin/clearcase/triggers/data/$branch.lst"; + } elsif ($site eq "CN") { + $filename = "//sons-cc/Views/official/Tools/bin/clearcase/triggers/data/$branch.lst"; + } else { + die "Internal Error: Site not set properly! ($site)\n"; + } # if + + if (-f $filename) { + open (FILE, $filename) || die "Can't open $filename!\n"; + + while () { + $bug = ExtractBugID $_; + next if ($bug eq "unknown"); + if ($bug eq $bugid) { + $found_bugid = 1; + last; + } # if + } # while + + close (FILE); + } else { + clearlog "Skipping check because $filename does not exist!"; + # Since there is no file list to check return that the bug id was found + $found_bugid = 1; + } # if + + return $found_bugid; +} # BugOnList + +sub LabelLocked { + # 04/28/2003: Oddity! All of a sudden this subroutine broke! I don't know + # why but even though we used to cd to the official view and issue our + # cleartool lslock command we started getting "Unable to determine VOB + # from pname" errors. Weird! Anyways we have changed to use the @ syntax instead. This means we must now specify the vob + # specifically. Fortunately we only have one vob to worry about at this + # time. On the plus side we no longer need to rely on the "official" view. + my $bugid = shift; + + my $output = `cleartool lslock -short lbtype:$bugid@\\$vob 2>&1`; + + if ($? == 0) { + return $output; + } else { + return 0; + } # if +} # LabelLocked + +sub CheckComment { + my $comment = shift; + my $branch = shift; + + my @valid_branches = ( + "main", + "rel_1.0", + "rel_2.0", + "rel_2.1", + "rel_2.2", + "rel_2.3", + "china_1.0", + "china_2.0", + "china_2.1", + "china_2.2", + "china_2.3", + "2.0_ga" + ); + + if ($comment eq "") { + clearlogmsg "You need to specify checkin comments"; + return 1; + } # if + + if (length $comment <= 4) { + clearlogmsg "The comment, '$comment' is too short!"; + return 1; + } # if + + if ($comment !~ m/.*BUGS2[0-9]{8}.*/) { + # Bug ID's are only required on certain branches + my $found = 0; + + foreach (@valid_branches) { + if ($branch eq $_) { + $found = 1; + last; + } # if + } # foreach + + if ($found == 1) { + clearlogmsg "Could not find bug ID in comment! This is required for the $branch branch"; + return 1; + } # if + } # if + + return 0; +} # CheckComment + +sub CheckBugIDs { + my @bugs = @_; + + my $result; + + foreach my $bugid (@bugs) { + # Check if label is locked + if (LabelLocked ($bugid)) { + clearlog "Bug id $bugid is locked!"; + clearmsg "Bug id $bugid is locked!\nSee your Clearcase Admin to unlock it"; + return 1; + } # if + + # Get Clearquest information + $result = cqc::GetBugRecord ($bugid, %fields); + + if ($result == 0) { + # Make sure bug is owned + if ($fields {owner} eq "") { + clearlogmsg "No owner specified in Clearquest for bug ID $bugid."; + return 1; + } # if + + # Make sure bug is in the correct state + if ($fields {state} ne "Assigned" and $fields {state} ne "Resolved") { + clearlogmsg "Bug ID $bugid is in the wrong state. It is in the " . $fields {state}. " state but should be in Assigned or Resolved state."; + return 1; + } # if + } elsif ($result > 0) { + clearlogmsg "Bug ID $bugid is not in Clearquest."; + return 1; + } else { + clearlogmsg "Clearquest Daemon (cqd) is not running! +Please contact the Clearquest Administrator."; + return 1; + } # if + + # Check if bug is on a branch list file + if (! BugOnList ($bugid, $branch)) { + clearlog "Bug ID $bugid is not on the list of acceptable bugs for the $branch branch!"; + clearmsg "Bug ID $bugid is not on the list\nof acceptable bugs for the $branch branch!"; + return 1; + } # if + } # foreach +} # CheckBugIDs + +clearlog "Checkin checks started for $pname on $branch branch"; + +if (CheckComment ($comment, $branch)) { + exit 1; +} elsif (CheckBugIDs (ExtractBugIDs $comment)) { + exit 1; +} # if + +clearlog "Successful precheckin of $pname on $branch branch with bug ID $bugid"; + +exit 0; diff --git a/cq/cqd/cqc b/cq/cqd/cqc new file mode 100644 index 0000000..2ac0cf1 --- /dev/null +++ b/cq/cqd/cqc @@ -0,0 +1,147 @@ +#!/usr/bin/perl +################################################################################ +# +# File: cqc,v +# Revision: 1.1.1.1 +# Description: This script is a test client for cqd. +# Author: Andrew@DeFaria.com +# Created: Fri May 31 15:34:50 2002 +# Modified: 2007/05/17 07:45:48 +# Language: Perl +# +# (c) Copyright 2007, ClearSCM, Inc. , all rights reserved. +# +################################################################################ +use strict; + +BEGIN { + # Add the appropriate path to our modules to @INC array. We use ipconfig to + # get the current host's IP address then determine whether we are in the US + # or China. + my @ipconfig = grep (/IP Address/, `ipconfig`); + my ($ipaddr) = ($ipconfig[0] =~ /(\d{1,3}\.\d{1,3}.\d{1,3}\.\d{1,3})/); + + # US is in the subnets of 192 and 172 while China is in the subnet of 10 + if ($ipaddr =~ /^192|^172/) { + unshift (@INC, "//sons-clearcase/Views/official/Tools/lib"); + } elsif ($ipaddr =~ /^10/) { + unshift (@INC, "//sons-cc/Views/official/Tools/lib"); + } else { + die "Internal Error: Unable to find our modules!\n" + } # if +} # BEGIN + +use cqc; + +%cqc::fields; +$cqc::command; + +my $len; +my $key; +my $value; +my $servername = $ENV {CQDSERVER}; +my $bugid; +my @query_fields; +my $result; + +sub Usage { + print "Usage: cqc [ -s servername ] bugid [ fieldname... ]\n"; + exit 1; +} # Usage + +$bugid = ""; +@query_fields = (); + +sub GetParms { + my $i = 0; + + if ($ARGV [0] && $ARGV [0] eq "-s") { + shift (@ARGV); + if (!$ARGV [0]) { + Usage; + } else { + $servername = shift (@ARGV); + } # if + } # if + + if ($ARGV [0]) { + $bugid = shift (@ARGV); + } # if + + @query_fields = @ARGV; + + # Downshift any query_fields + foreach (@query_fields) { + $query_fields [$i++] = lc $_; + } # foreach +} # GetParms + +sub fix_bugid { + my $bugid = shift; + + if ($bugid =~ /^\d+$/) { + if (length ($bugid) < 13) { + $len = 13 - length ($bugid); + if ($len < 5) { + # Can't even prepend "BUGS2"! + print "Invalid bug id \"$bugid\" encountered!\n"; + exit 1; + } else { + $bugid = "BUGS2" . "0" x ($len - 5) . $bugid; + } # if + } # if + } # if + + return $bugid; +} # fix_bugid + +# Main code +GetParms; + +if (defined ($servername)) { + die "Unable to connect to $servername\n" if cqc::Connect ($servername) < 0; +} # if + +if ($bugid) { + $result = cqc::GetBugRecord (fix_bugid ($bugid), %fields); + die "Unable to connect to server\n" if $result < 0; + if ($result) { + print "Bug ID $bugid was not found\n"; + } else { + if (@query_fields) { + foreach (@query_fields) { + if (@query_fields > 1) { + print "$_: $cqc::fields{$_}\n"; + } else { + print "$cqc::fields{$_}\n"; + } # if + } # foreach + } else { + while (($key, $value) = each (%fields)) { + $value =~ s/\r/\r\n/g; + print "$key: $value\n"; + } # while + } # if + } # if +} else { + print "Enter bug ID:"; + + while ($command = ) { + chomp $command; + last if $command =~ m/exit|quit|shutdown/; + + $bugid = fix_bugid ($command); + $result = cqc::GetBugRecord ($bugid, %fields); + die "Unable to connect to server\n" if $result < 0; + if ($result) { + print "Bug ID $bugid was not found\n"; + } else { + while (($key, $value) = each (%fields)) { + $value =~ s/\r/\r\n/g; + print "$key: $value\n"; + } # while + } # if + + print "Enter bug ID:"; + } # while +} # if diff --git a/cq/cqd/cqc.pm b/cq/cqd/cqc.pm new file mode 100644 index 0000000..6d60b8d --- /dev/null +++ b/cq/cqd/cqc.pm @@ -0,0 +1,169 @@ +#!/usr/bin/perl +################################################################################ +# +# File: cqc.pm,v +# Revision: 1.1.1.1 +# Description: Perl Module interface to cqd (ClearQuest Daemon). This is used +# by cqc and cgi script to talk to cqd. +# Author: Andrew@DeFaria.com +# Created: Fri May 31 15:34:50 2002 +# Modified: 2007/05/17 07:45:48 +# Language: Perl +# +# (c) Copyright 2007, ClearSCM, Inc., all rights reserved. +# +################################################################################ +use IO::Socket; + +package cqc; + require (Exporter); + @ISA = qw (Exporter); + + @EXPORT = qw ( + Connect + GetBugRecord + Disconnect + %fields + $command + $verbose + ); + + my $host; + my $port = 1500; + my $command; + my $default_server = "sons-clearcase"; + my $verbose; + + BEGIN { + my $cqcversion = "1.0"; + + # Reopen STDOUT. This is because cqperl screws around with STDOUT in some + # weird fashion + open STDOUT, ">-" or die "Unable to reopen STDOUT\n"; + # Set unbuffered output for the same reason (cqperl) + $| = 1; + } # BEGIN + + sub verbose { + print "@_\n" if defined ($verbose); + } # verbose + + sub Connect { + my $host = shift; + + my $result; + + if (!defined ($host)) { + $host = "localhost"; + } # if + + $cqserver = ConnectToServer ($host); + + if ($cqserver) { + verbose "Connected to $host"; + SendServerAck ($cqserver); + } # if + + return $cqserver; + } # Connect + + sub Disconnect { + my $msg; + if ($cqserver) { + if ($cqc::command eq "shutdown") { + $msg = "Disconnected from server - shutdown server"; + } else { + $cqc::command = "quit"; + $msg = "Disconnected from server"; + } # if + SendServerCmd ($cqserver, $cqc::command); + GetServerAck ($cqserver); + verbose "$msg"; + close ($cqserver); + undef $cqserver; + } # if + } # Disconnect + + sub GetBugRecord { + my $bugid = shift; + %fields = @_; + + my $result; + + if (!$cqserver) { + verbose "Not connected to server yet!\n"; + verbose "Attempting connection to $default_server...\n"; + $result = Connect ($default_server); + return -1 if !defined ($result); + } # if + + SendServerCmd ($cqserver, $bugid); + GetServerAck ($cqserver); + $result = GetServerResponse ($cqserver, %fields); + SendServerAck ($cqserver); + + return $result; + } # GetBugRecord + + END { + Disconnect; + } # END + + sub ConnectToServer { + my $host = shift; + + # create a tcp connection to the specified host and port + return IO::Socket::INET->new(Proto => "tcp", + PeerAddr => $host, + PeerPort => $port); + } # ConnectToServer + + sub SendServerAck { + my $server = shift; + + print $server "ACK\n"; + } # SendServerAck + + sub GetServerAck { + my $server = shift; + my $srvresp; + + while (defined ($srvresp = <$server>)) { + chomp $srvresp; + if ($srvresp eq "ACK") { + return; + } # if + print "Received $srvresp from server - expected ACK\n"; + } # while + } # GetServerAck + + sub GetServerResponse { + my $server = shift; + %fields = @_; + + %fields = (); + my $srvresp; + my $result = 0; + + while (defined ($srvresp = <$server>)) { + chomp $srvresp; + last if $srvresp eq "ACK"; + if ($srvresp =~ m/Bug ID.*was not found/) { + $result = 1; + } else { + $srvresp =~ /(^\w+):\s+(.*)/s; + $fields {$1} = $2; + } # if + } # while + + return $result; + } # GetServerResponse + + sub SendServerCmd { + my $server = shift; + my $command = shift; + + print $server "$command\n"; + } # SendServerCmd + +1; diff --git a/cq/cqd/cqc.pm.php b/cq/cqd/cqc.pm.php new file mode 100644 index 0000000..04c83fa --- /dev/null +++ b/cq/cqd/cqc.pm.php @@ -0,0 +1,51 @@ + + + + + + + ClearSCM: Clearquest: Deamon: CQD + + + + + + + + + + + + + + + +
    +
    + +

    Clearquest Daemon API (code)

    + +

    Defines the API to the Clearquest Daemon.

    + + + + +
    + +
    + + + + + diff --git a/cq/cqd/cqd b/cq/cqd/cqd new file mode 100644 index 0000000..4b6395e --- /dev/null +++ b/cq/cqd/cqd @@ -0,0 +1,336 @@ +#!C:/Progra~1/Rational/ClearQuest/CQPerl +################################################################################ +# +# File: cqd,v +# Revision: 1.1.1.1 +# Description: This script implements a daemon that handles requests for +# queries to the Clearquest database. Opening up the Clearquest +# database takes a long time, therefore this daemon will run in +# the background and handle requests. +# Author: Andrew@DeFaria.com +# Created: Fri May 31 15:34:50 2002 +# Modified: 2007/05/17 07:45:48 +# Language: Perl +# +# (c) Copyright 2007, ClearSCM, Inc., all rights reserved. +# +################################################################################ +use strict; +use CQPerlExt; +use IO::Socket; +use Net::hostent; +use POSIX qw(setsid); + +# Generic, harmless, user reporter +my $cquser = "reporter"; +my $cqpasswd = "news"; +my $cqdb = "BUGS2"; +my $port = 1500; + +my $session; +my $verbose; +my $daemon_mode; +my $quiet_mode; +my $multithreaded; +my $pid = $$; + +my $me = `basename $0`; +chomp $me; +my $cqdversion = "2.0"; + +my @all_fields = ( + "cc", "description", "field_trial", + "fixed_date", "fixed_in", "found_in", + "headline", "manager", "module", + "must_fix", "note_entry", "notes_log", + "owner", "pending_reason", "priority", + "product", "project", "resolution", + "severity", "state", "submit_date", + "submitter", "symptoms", "verified_by", + "verified_date", "resolution_statetype", "keywords", + "fixed_by" +); + +my %fields= (); + +sub log_message { + print "[$pid] @_\n" if defined ($verbose); +} # log_message + +sub display_message { + print "[$pid] @_\n" if !defined ($quiet_mode); +} # display_message + +sub log_error { + print STDERR "[$pid] ERROR: @_\n" +} # log_error + +sub log_warning { + print STDERR "[$pid] WARNING: @_\n" +} # log_error + +sub GetClientAck { + my $client = shift; + my $clientresp; + + while (defined ($clientresp = <$client>)) { + chomp $clientresp; + if ($clientresp eq "ACK") { + return + } # if + log_warning "Received $clientresp from client - expected ACK"; + } # while +} # GetClientAck + +sub GetClientCmd { + my $client = shift; + my $clientresp; + + while (defined ($clientresp = <$client>)) { + chomp $clientresp; + return $clientresp; + } # while +} # GetClientResponse + +sub SendClientAck { + my $client = shift; + + print $client "ACK\n"; +} # SendClientAck + +sub SendClientResponse { + my $client = shift; + my $response = shift; + + print $client "$response\n"; +} # SendClientResponse + +sub EnterDaemonMode { + my $logfile = shift (@_); + my $errorlog = shift (@_); + + log_message "Entering Daemon Mode (\"$logfile\", \"$errorlog\")"; + if ($logfile eq '') { + $logfile = "/dev/null"; + } # if + + if ($errorlog eq '') { + $errorlog = "/dev/null"; + } # if + + # Change the current directory to / + chdir 'C:\\' or die "$me: Error: Can't chdir to C:\\ ($!)"; + + # Turn off umask + umask 0; + + # Redirect STDIN to /dev/null + open STDIN, '/dev/null' + or die "$me: Error: Can't read /dev/null ($!)"; + + # Redirect STDOUT to logfile + open STDOUT, ">>$logfile" + or die "$me: Error: Can't write to $logfile ($!)"; + + # Redirect STDERR to errorlog + open STDERR, ">>$errorlog" + or die "$me: Error: Can't write to $errorlog ($!)"; + + # Now fork the daemon + defined (my $pid = fork) + or die "$me: Error: Can't create daemon ($!)"; + + # Now the parent exits + exit if $pid; + + # Set process to be session leader + setsid + or die "$me: Error: Can't start a new session ($!)"; + log_message "Entered Daemon Mode"; +} # EnterDaemonMode + +sub OpenDB { + log_message "Opening $cqdb database"; + $session = CQPerlExt::CQSession_Build (); + $session->UserLogon ($cquser, $cqpasswd, $cqdb, ""); + log_message "Opened $cqdb database"; +} # OpenDB + +sub CloseDB { + CQSession::Unbuild ($session); +} # CloseDB + +sub Usage { + print "Usage: $me [ -d ] [ -v ] [ -m ] [ -q ]\n\n"; + print "Where:\t-d\tEnter Daemon mode (currently not working)\n"; + print "\t-v\tVerbose mode\n"; + print "\t-m\tMultithreaded (currently not working)\n"; + print "\t-q\tQuiet mode\n"; + exit 1; +} # Usage + +sub GetBugRecord { + my $bugid = shift; + %fields = @_; + + my $record; + my $value; + + # Use eval because the bug ID passed in may not be found. If there is + # an error with this call we assume the bug ID is not valid. + eval { + $record = $session->GetEntity ("defect", $bugid); + } or log_error "Bug ID $bugid not found!", return 0; + + foreach (@all_fields) { + # The field name specified may be undefined. It may also just be + # not filled in. We need to use eval to attempt to get the field and + # then determine which error it was: Undefined field or simply a field + # that was not filled in. + eval { + $value = $record->GetFieldValue ($_)->GetValue + }; + if ($@ =~ m/object that does not exist/) { + $value = ""; + } elsif ($value eq "") { + $value = ""; + } # if + $value =~ tr/\n/ /s; + $fields {$_} = $value; + } # foreach + + return 1; +} # GetBugRecord + +sub ServiceClient { + my $cqclient = shift; + + # Service this client + my $hostinfo = gethostbyaddr ($cqclient->peeraddr); + my $host = $hostinfo->name || $cqclient->peerhost; + + display_message "Connect from $host"; + log_message "Waiting for command from $host"; + while () { + GetClientAck ($cqclient); + $_ = GetClientCmd ($cqclient); + next unless /\S/; # Skip blank requests + last if /quit|exit|shutdown/i; + log_message "$host requests information about bug ID $_"; + SendClientAck ($cqclient); + if (GetBugRecord ($_, %fields)) { + SendClientResponse ($cqclient, "id: $_"); + my $key; + my $value; + while (($key, $value) = each (%fields)) { + SendClientResponse ($cqclient, "$key: $value"); + } # while + } else { + SendClientResponse ($cqclient, "Bug ID $_ was not found"); + } # if + SendClientAck ($cqclient); + } # while + + display_message "Closing connection from $host at client's request"; + close $cqclient; +} # ServiceClient + +sub Funeral { + my $childpid = wait; + $SIG{CHLD} = \&Funeral; + log_message "Child has died" . ($? ? " with status $?" : ""); +} # Funeral + +sub ProcessRequests { + # The subroutine handles processing of requests by using a socket to + # communicate with clients. + my $cqserver = IO::Socket::INET->new ( + Proto => 'tcp', + LocalPort => $port, + Listen => SOMAXCONN, + Reuse => 1 + ); + + die "$me: Error: Could not create socket (%!)\n" unless $cqserver; + + display_message "Clearquest DB Server (cqd V$cqdversion) accepting clients"; + + # Now wait for an incoming request + while (my $cqclient = $cqserver->accept ()) { + my $hostinfo = gethostbyaddr ($cqclient->peeraddr); + my $host = $hostinfo->name || $cqclient->peerhost; + log_message "$host is requesting service"; + if (defined ($multithreaded)) { + my $childpid; + + log_message "Spawning child to handle request"; + + die "$me: ERROR: Can't fork: %!" unless defined ($childpid = fork ()); + + if ($childpid) { + # In parent - set up for clean up of child process + log_message "In parent"; + $childpid = -$childpid; + log_message "Parent produced child ($childpid)"; + $SIG{CHLD} = \&Funeral; + log_message "Parent looking for another request to service"; + } else { + # In child process - ServiceClient + log_message "In child"; + $pid = -$$; + log_message "Child has been born"; + ServiceClient ($cqclient); + log_message "Child finished servicing requests"; + kill ("TERM", $$); + exit; + } # if + } else { + ServiceClient ($cqclient); + } # if + } # while + + display_message "Shutting down server"; + close ($cqserver); + +} # ProcessRequests + +# Start main code +# Reopen STDOUT. This is because cqperl screws around with STDOUT in some +# weird fashion +open STDOUT, ">-" or die "Unable to reopen STDOUT\n"; +# Set unbuffered output for the same reason (cqperl) +$| = 1; + +while ($ARGV [0]) { + if ($ARGV [0] eq "-d") { + $daemon_mode = 1; + } elsif ($ARGV [0] eq "-v") { + $verbose = 1; + undef ($quiet_mode); + } elsif ($ARGV [0] eq "-m") { + $multithreaded = 1; + } elsif ($ARGV [0] eq "-q") { + $quiet_mode = 1; + undef ($verbose); + } else { + Usage; + } # if + shift (@ARGV); +} # while + +my $tmp = $ENV {"TMP"}; +my $cqd_logfile = "$tmp\\$me.log"; +my $cqd_errfile = "$tmp\\$me.err"; + +EnterDaemonMode ($cqd_logfile, $cqd_errfile) if defined ($daemon_mode); + +OpenDB; + +ProcessRequests; + +display_message "Shutting down"; + +CloseDB; +display_message "Closed $cqdb database"; + +exit 0; diff --git a/cq/cqd/releasenotes.cgi b/cq/cqd/releasenotes.cgi new file mode 100644 index 0000000..75f4978 --- /dev/null +++ b/cq/cqd/releasenotes.cgi @@ -0,0 +1,224 @@ +#!/usr/bin/perl +################################################################################ +# +# File: releasenotes.cgi,v +# Revision: 1.1.1.1 +# Description: Produce an HTML table of bugs for a release page +# Author: Andrew@DeFaria.com +# Created: Fri May 31 15:34:50 2002 +# Modified: 2007/05/17 07:45:48 +# Language: Perl +# +# (c) Copyright 2007, ClearSCM, Inc., all rights reserved. +# +################################################################################ +use strict; +use CGI qw/:standard *table/; +use Cwd; +use lib qw(//sonscentral/users/adefaria/www/cgi-bin); +use cqc; + +#%cqc::fields; +#$cqc::command; + +my $page = new CGI; + +my $release = $page->param ("release"); +my @intro_notes; +my @buglines; + +# Colors +my $header_background = "#ffffcc"; +my $header_foreground = "#000000"; +my $data_background = "#ffffff"; +my $data_foreground = "#000000"; + +sub Error; +sub Footing; + +sub ReleaseForm { + print + start_form ({-action => "/Release/releasenotes.cgi", + -method => "post"}) . + h4 ("Look up other Release:", + textfield ({-name => "release", + -size => 12, + -value => "Please specify"}), + submit ({-value => "Display"}) + ) . end_form . "\n"; +} # ReleaseForm + +sub Heading { + my $release = shift; + + if ($release) { + print header (-title => "Release $release") . "\n" . + start_html (-title => "Release $release", + -author => "Andrew\@DeFaria.com", + -link => "#0000ee", + -vlink => "#cc33cc", + -alink => "#ff0000", + -bgcolor => "#eeffff", + -text => "#000000", + -script => {-language => "JavaScript1.2", + -src => "/Javascript/Heading.js"}), + p ({-align => "right"}, + a ({-href => "/Release/addbug"}, "Add a bug to a release") . "\n" . br + a ({-href => "file://///sons-clearcase/Views/official/Tools/bin/clearcase/triggers/data/rel_2.2.lst"}, "Official US 2.2 list") . "\n" . br + a ({-href => "file://///sons-cc/Views/official/Tools/bin/clearcase/triggers/data/china_2.2.lst"}, "Official Shanghai 2.2 list")) . "\n" . + h1 ({-align=>"CENTER"}, "Release $release") . "\n" . + h2 ("Introduction") . "\n"; + } else { + print header (-title => "Release $release") . "\n" . + start_html (-title => "Release $release", + -author => "Andrew\@DeFaria.com") . "\n"; + Error "Release not specified!"; + } # if +} # Heading + +sub Footing { + ReleaseForm; + print script ({-language => "JavaScript1.2", + -src => "/JavaScript/Footing.js"}) . "\n"; + print end_html; +} # Footing + +sub PrintIntroNotes { + (scalar (@intro_notes) == 0) ? return : print ul (@intro_notes) . "\n"; +} # PrintIntroNotes + +sub LockedLabel { + my $bugid = shift; + + # We need to set a view context. Use the official view + my $cwd = cwd; + + my $vob_server = "sons-clearcase"; + my $view_path = "Views"; + my $view_name = "official"; + my $vob = "salira"; + my $official_view = '\\\\' . + $vob_server . + '\\' . + $view_path . + '\\' . + $view_name . + '\\' . + $vob; + + chdir $official_view or die "Unable to set view context"; + my $output = `cleartool lslock -short lbtype:$bugid`; + chomp $output; + chdir $cwd or die "Unable to return from view context\n"; + + # lslock returns the label if it is locked, otherwise it returns + # an empty string + return $output; +} # LabelLocked + +sub ParseBugFile { + my $buglist = shift; + my ($result, $owner, $description, $bugid, $state, $line); + my $bugnbr = 0; + my $locked; + + open BUGLIST, "$buglist" or Error "Unable to open buglist: $buglist"; + + while ($line = ) { + next if $line =~ /^\#/; # Skip comments + chomp $line; + if ($line =~ /^\*/) { + ($result, $line) = split (/\* /, $line); + push (@intro_notes, li ([$line]) . "\n"); + } else { + ($bugid) = split (/\s+/, $line); + $result = cqc::GetBugRecord ($bugid, %fields); + ($result <= 0) ? $owner = "Unknown" : $owner = $fields {owner}; + if ($result < 0) { + $description = "Unable to connect to server!"; + } elsif ($result > 0) { + $description = "Bug ID not found in Clearquest!"; + } else { + # Description's too large. Use headline instead. + $description = $fields {headline}; + } # if + + if (LockedLabel ($bugid)) { + $locked = img ({-src => "/Images/CheckMark.gif"}); + } else { + $locked = " "; + } #if + + if ($fields {state} eq "Verified" or $fields {state} eq "Closed") { + $state = $fields {state}; + $locked = img ({-src => "/Images/CheckMark.gif"}); + } else { + $state = b (font ({-color => "Red"}, $fields {state})); + } # if + + push (@buglines, + td ({-width => "25", + -align => "center", + -bgcolor => $data_background}, + small ++$bugnbr) . + td ({-bgcolor => $data_background}, + small (a ({-href => "/cgi-bin/bugdetails.cgi?bugid=$bugid"}, $bugid))) . + td ({-bgcolor => $data_background}, + small $state) . + td ({-align => "center", + -bgcolor => $data_background}, + small (a ({-href => "mailto:$owner\@salira.com"}, $owner))) . + td ({-align => "center", + -valign => "center", + -bgcolor => $data_background}, + $locked) . + td ({-bgcolor => $data_background}, + small $description) . "\n"); + } # if + } # while +} # ParseBugFile + +sub PrintBugTable { + if (scalar (@buglines) == 0) { + print h3 ("No bugs found!"); + } else { + my $bugs = (scalar (@buglines) > 1) ? " bugs" : " bug"; + print "
    \n"; + print caption (small (strong (scalar (@buglines) . $bugs . " in this release"))) . "\n"; + print ""; + print end_table; + + print start_table ({-align => "center", + -bgcolor => "black", + -border => 0, + -cellspacing => 0, + -cellpadding => 2, + -width => "100%"}) . "\n"; + print "\n"; + print end_table; + print "\n"; + print end_table; + print end_table; +} # Body + +$userid = Heading ( + "getcookie", + "", + "Email message from $sender", + "Email message from $sender", + "", + $table_name, +); + +SetContext $userid; +NavigationBar $userid; + +Body $msg_nbr; + +Footing $table_name; diff --git a/maps/bin/domains b/maps/bin/domains new file mode 100755 index 0000000..8eb640f --- /dev/null +++ b/maps/bin/domains @@ -0,0 +1,72 @@ +#!/usr/bin/perl +################################################################################ +# +# File: $RCSfile: domains,v $ +# Revision: $Revision: 1.1 $ +# Description: Display entries from the list table where there is at least one +# entry with a null pattern (nuke the domain) and yet still other +# entries with the same domain name but having a pattern. We may +# want to eliminate the other entries since we're nuking the +# whole domain anyway. +# Author: Andrew@DeFaria.com +# Created: Sat Oct 20 23:28:19 MST 2007 +# Modified: $Date: 2013/06/12 14:05:47 $ +# Language: Perl +# +# (c) Copyright 2007, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +use strict; +use warnings; + +use FindBin; +use Getopt::Long; + +use lib $FindBin::Bin, '/opt/clearscm/lib'; + +use MAPS; +use MAPSDB; + +use Display; + +sub Usage () { + display < sub { set_verbose }, + "debug" => sub { set_debug }, + "usage" => sub { Usage }, +) || Usage; + +my $userid = $ENV{MAPS_USERNAME} ? $ENV{MAPS_USERNAME} : $ENV{USER}; + +# Main +SetContext $userid; + +my $statement = "select domain from list where userid=\"$userid\" and type=\"null\" and pattern is null"; + +my $need_requence = 0; + +foreach my $domain (sort (&MAPSDB::GetRows ($statement))) { + verbose "Processing domain $domain"; + $statement = "select sequence from list where userid = \"$userid\" and domain = \"$domain\" and type = \"null\" and pattern is not null"; + + foreach my $sequence (MAPSDB::GetRows $statement) { + display "Deleting $domain ($sequence)"; + $need_requence = 1; + DeleteList "null", $sequence; + } # foreach +} # foreach + +if ($need_requence) { + verbose "Resequencing null list..."; + ResequenceList $userid, "null"; + verbose "done"; +} # if + +exit; diff --git a/maps/bin/editprofile.cgi b/maps/bin/editprofile.cgi new file mode 100755 index 0000000..4254326 --- /dev/null +++ b/maps/bin/editprofile.cgi @@ -0,0 +1,213 @@ +#!/usr/bin/perl +################################################################################ +# +# File: $RCSfile: editprofile.cgi,v $ +# Revision: $Revision: 1.1 $ +# Description: Edit the user's profile +# Author: Andrew@DeFaria.com +# Created: Mon Jan 16 20:25:32 PST 2006 +# Modified: $Date: 2013/06/12 14:05:47 $ +# Language: perl +# +# (c) Copyright 2000-2006, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +use strict; +use warnings; + +use FindBin; +$0 = $FindBin::Script; + +use lib $FindBin::Bin; + +use MAPS; +use MAPSWeb; + +use CGI qw (:standard *table); + +my $userid; +my $table_name = "profile"; + +sub Body { + my $handle = FindUser $userid; + + my ($fullname, $email, $password); + ($_, $fullname, $email, $password) = GetUser ($handle); + + $handle->finish; + + my %options = GetUserOptions $userid; + + print start_form { + -method => "post", + -action => "updateprofile.cgi", + -onSubmit => "return validate (this);" + }; + print start_table { + -align => "center", + -id => $table_name, + -border => 1, + -cellspacing => 0, + -cellpadding => 2, + -width => "100%"}; + print Tr ([ + td {-class => "label", + -width => 134}, + "Username:", + td {-width => 290}, + $userid, + td {-class => "notetext"}, + "Specify a username to log into MAPS" + ]) . "\n"; + print Tr ([ + td {-class => "label"}, + "Full name:", + td ( + textfield {-class => "inputfield", + -size => 50, + -name => "fullname", + -value => "$fullname"}), + td {-class => "notetext"}, + "Specify your full name" + ]) . "\n"; + print Tr [ + td {-class => "label"}, + "Email:", + td ( + textfield {-class => "inputfield", + -size => 50, + -name => "email", + -value => $email}), + td {-class => "notetext"}, + "Your email address is used if you are a " . + i ("Tag & Forward") . + " user. This is the email address that MAPS will forward your email to after it tags it. This email address is also used in case you forget your password so that we can email you your password." + ]; + print Tr [ + td {-class => "label"}, + "Old Password:", + td ( + password_field {-class => "inputfield", + -size => 20, + -name => "old_password"}), + td {-class => "notetext"}, + "Enter your old password" + ]; + print Tr [ + td {-class => "label"}, + "New Password:", + td ( + password_field {-class => "inputfield", + -size => 20, + -name => "new_password", + -value => ""}), + td {-class => "notetext"}, + "Choose a new password greater than 6 characters." + ]; + print Tr [ + td {-class => "label"}, + "Repeat Password:", + td ( + password_field {-class => "inputfield", + -size => 20, + -name => "repeated_password", + -value => ""}), + td {-class => "notetext"}, + "Re-enter your password so we can be sure you typed it correctly." + ]; + print Tr [ + td {-class => "label"}, + "MAPSPOP user:", + td ( + font ({-class => "label"}, + radio_group {-name => "MAPSPOP", + -values => ["yes", "no"], + -default => "no", + -labels => {"yes" => "Yes", + "no" => "No"}})), + td {-class => "notetext"}, + "MAPSPOP users need to download " . + a ({-href => "/maps/bin/MAPSPOP.exe"}, "MAPSPOP") . + ". See " . + a ({-href => "/maps/doc/UsingMAPSPOP.html"}, "Using MAPSPOP") . + " for more information." + ]; + print Tr [ + td {-class => "label"}, + "Keep history for:", + td ( + font ({-class => "label"}, + popup_menu {-class => "inputfield", + -name => "history", + -values => ["7", "14", "30", "60", "90"], + -default => $options{"History"}}), + font ({-class => "label"}, " days")), + td {-class => "notetext"}, + "This specifies how many days of history that MAPS will keep before discarding returned messages." + ]; + print Tr [ + td {-class => "label"}, + "Dates in Stats Page:", + td ( + font ({-class => "label"}, + popup_menu {-class => "inputfield", + -name => "dates", + -values => ["7", "14", "21", "30"], + -default => $options{"Dates"}})), + td {-class => "notetext"}, + "This specifies how many days are displayed in the MAPS Stats Page." + ]; + print Tr [ + td {-class => "label"}, + "Entries per page:", + td ( + font ({-class => "label"}, + popup_menu {-class => "inputfield", + -name => "days", + -values => ["10", "20", "30", "40", "50"], + -default => $options{"Page"}})), + td {-class => "notetext"}, + "This specifies how many entries are displayed per page in the online MAPS Reports." + ]; + print Tr [ + td {-class => "label"}, + i ("Tag & Forward:"), + td ( + font ({-class => "label"}, + radio_group {-name => "tag_and_forward", + -values => ["yes", "no"], + -default => "no", + -labels => {"yes" => "Yes", + "no" => "No"}})), + td {-class => "notetext"}, + i ("Tag and Forward") . + " means that MAPS will not filter or save any email for you. Instead it will simply add an X-MAPS header to your email indicating what MAPS would have done with the email. This allows you to filter your email in your local email client." + ]; + print end_table; + print br (div {-align => "center"}, + submit (-name => "submit", + -value => "Update Profile")); + print end_form; +} # Body + +# Main +my @scripts = ("MAPSUtils.js", "CheckEditProfile.js"); + +$userid = Heading ( + "getcookie", + "", + "Edit Profile", + "Spam Elimination System", + "", + $table_name, + @scripts +); + +SetContext $userid; +NavigationBar $userid; + +Body; + +Footing $table_name; + +exit; diff --git a/maps/bin/exportlist.cgi b/maps/bin/exportlist.cgi new file mode 100755 index 0000000..c6cb30c --- /dev/null +++ b/maps/bin/exportlist.cgi @@ -0,0 +1,68 @@ +#!/usr/bin/perl +################################################################################ +# +# File: $RCSfile: exportlist.cgi,v $ +# Revision: $Revision: 1.1 $ +# Description: Export an address list +# Author: Andrew@DeFaria.com +# Created: Mon Jan 16 20:25:32 PST 2006 +# Modified: $Date: 2013/06/12 14:05:47 $ +# Language: perl +# +# (c) Copyright 2000-2006, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +use strict; + +use FindBin; +$0 = $FindBin::Script; + +use lib $FindBin::Bin; + +use MAPS; +use MAPSWeb; + +use CGI qw/:standard *table/; +use CGI::Carp "fatalsToBrowser"; + +my $type = param ("type"); +my $userid = cookie ("MAPSUser"); +my $Userid = ucfirst $userid; + +sub PrintList { + my $type = shift; + + my $year = substr ((scalar (localtime)), 20, 4); + + my ($pattern, $domain, $comment, $hit_count, $last_hit); + my $sth = FindList $type; + + print "\################################################################################\n"; + print "\#\n"; + print "\# MAPS:\t\tMail Authorization and Permission System (MAPS)\n"; + print "\# $type.list:\t${Userid}'s $type.list file\n"; + print "\# Exported:\t" . localtime . "\n"; + print "\#\n"; + print "\# Copyright 2001-" . $year . ", Andrew\@DeFaria.com, all rights reserved.\n"; + print "\#\n"; + print "\################################################################################\n"; + + while (($_, $_, $pattern, $domain, $comment, $_, $hit_count, $last_hit) = GetList $sth) { + last if !(defined $pattern or defined $domain); + $pattern = !defined $pattern ? "" : $pattern; + $domain = !defined $domain ? "" : $domain; + if ($domain eq "") { + print "$pattern,$comment,$hit_count,$last_hit\n"; + } else { + print "$pattern\@$domain,$comment,$hit_count,$last_hit\n"; + } # if + } # while +} # PrintList + +# Main +SetContext $userid; + +print header (-type => "application/octet-stream", + -attachment => "$type.list"); +PrintList $type; +exit; diff --git a/maps/bin/list.cgi b/maps/bin/list.cgi new file mode 100755 index 0000000..3d82b09 --- /dev/null +++ b/maps/bin/list.cgi @@ -0,0 +1,186 @@ +#!/usr/bin/perl +################################################################################ +# +# File: $RCSfile: list.cgi,v $ +# Revision: $Revision: 1.1 $ +# Description: Manage lists +# Author: Andrew@DeFaria.com +# Created: Mon Jan 16 20:25:32 PST 2006 +# Modified: $Date: 2013/06/12 14:05:47 $ +# Language: perl +# +# (c) Copyright 2000-2006, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +use strict; +use warnings; + +use FindBin; +$0 = $FindBin::Script; + +use lib $FindBin::Bin; + +use MAPS; +use MAPSLog; +use MAPSUtil; +use MAPSWeb; +use CGI qw (:standard *table start_div end_div); +use CGI::Carp "fatalsToBrowser"; + +my $next = param ("next"); +my $lines = param ("lines"); +my $type = param ("type"); +my $message = param ("message"); +my $Type = ucfirst $type; +my $userid; +my $prev; +my $total; +my $last; +my $table_name = "list"; + +sub Body { + my $type = shift; + + if (defined $message) { + print div {-align => "center"}, + font {-class => "error"}, $message; + } # if + + print start_form { + -method => "post", + -action => "processaction.cgi", + -name => "list" + }; + + # Print some hidden fields to pass along + print + hidden (-name => "type", + -default => $type), + hidden (-name => "next", + -default => $next); + + my $current = $next + 1; + + print div {-align => "center"}, b ( + "(" . $current . "-" . $last . " of " . $total . ")"); + print start_div {-class => "toolbar", + -align => "center"}; + my $prev_button = $prev >= 0 ? + a ({-href => "list.cgi?type=$type;next=$prev"}, + "Previous") : ""; + my $next_button = ($next + $lines) < $total ? + a {-href => "list.cgi?type=$type;next=" . ($next + $lines)}, + "Next" : ""; + print $prev_button, + submit ({-name => "action", + -value => "Add New Entry", + -onClick => "return NoneChecked (document.list);"}), + submit ({-name => "action", + -value => "Delete Marked", + -onClick => "return CheckAtLeast1Checked (document.list) && AreYouSure ('Are you sure you want to delete these entries?');"}), + submit ({-name => "action", + -value => "Modify Marked", + -onClick => "return CheckAtLeast1Checked (document.list);"}), + submit ({-name => "action", + -value => "Reset Marks", + -onClick => "return ClearAll (document.list);"}), + $next_button; + print end_div; + print start_table {-align => "center", + -id => $table_name, + -border => 0, + -cellspacing => 0, + -cellpadding => 4, + -width => "100%"}; + print Tr [ + th {-class => "tableleftend"}, "Seq", + th {-class => "tableheader"}, "Mark", + th {-class => "tableheader"}, "Username", + th {-class => "tableheader"}, "@", + th {-class => "tableheader"}, "Domain", + th {-class => "tablerightend"}, "Comments" + ]; + + my @list = ReturnList $type, $next, $lines; + my %record; + my $i = 1; + + foreach (@list) { + %record = %{$_}; + $record{pattern} = " " if !defined $record{pattern}; + $record{domain} = " " if !defined $record{domain}; + $record{comment} = " " if !defined $record{comment}; + + my $leftclass = ($i eq $lines || $record{sequence} eq $total) ? + "tablebottomleft" : "tableleftdata"; + my $dataclass = ($i eq $lines || $record{sequence} eq $total) ? + "tablebottomdata" : "tabledata"; + my $rightclass = ($i eq $lines || $record{sequence} eq $total) ? + "tablebottomright" : "tablerightdata"; + $i++; + + print Tr [ + td {-class => $leftclass, + -align => "center"}, $record{sequence}, + td {-class => $dataclass, + -align => "center"}, + checkbox ({-name => "action$record{sequence}", + -label => ""}), + td {-class => $dataclass, + -align => "right"}, $record{pattern}, + td {-class => $dataclass, + -align => "center"}, "\@", + td {-class => $dataclass, + -align => "left"}, $record{domain}, + td {-class => $rightclass, + -align => "left"}, $record{comment} + ]; + } # foreach + print end_table; + print end_form; + + print div ({-align => "center"}, + a ({-href => "/maps/bin/exportlist.cgi?type=$type"}, + submit ({-name => "export", + -value => "Export list"})), + a ({-href => "/maps/bin/importlist.cgi?type=$type"}, + submit ({-name => "import", + -value => "Import List"}))); +} # Body + +# Main +my @scripts = ("ListActions.js"); + +$userid = Heading ( + "getcookie", + "", + "Manage $Type List", + "Manage $Type List", + "", + $table_name, + @scripts +); + +SetContext $userid; +NavigationBar $userid; + +if (!defined $lines) { + my %options = GetUserOptions $userid; + $lines = $options{"Page"}; +} # if + +$total = MAPSDB::count "list", "userid = \"$userid\" and type = \"$type\"";; + +$next = !defined $next ? 0 : $next; +$last = $next + $lines < $total ? $next + $lines : $total; + +if (($next - $lines) > 0) { + $prev = $next - $lines; +} else { + $prev = $next eq 0 ? -1 : 0; +} # if + +Body $type; +Footing $table_name; + +exit; diff --git a/maps/bin/main.cgi b/maps/bin/main.cgi new file mode 100755 index 0000000..d54a8b6 --- /dev/null +++ b/maps/bin/main.cgi @@ -0,0 +1,109 @@ +#!/usr/bin/perl +################################################################################ +# +# File: $RCSfile: main.cgi,v $ +# Revision: $Revision: 1.1 $ +# Description: This is the main or home page for maps. It is presented when the +# user logs in. +# Author: Andrew@DeFaria.com +# Created: Fri Nov 29 14:17:21 2002 +# Modified: $Date: 2013/06/12 14:05:47 $ +# Language: perl +# +# (c) Copyright 2000-2006, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +use strict; +use warnings; + +use FindBin; +$0 = $FindBin::Script; + +use lib $FindBin::Bin; + +use MAPS; +use MAPSLog; +use MAPSUtil; +use MAPSWeb; + +use CGI qw (:standard *table start_Tr end_Tr start_div end_div); +use CGI::Carp "fatalsToBrowser"; + +my $new_userid = param ("userid"); +my $password = param ("password"); + +sub Body { + print + h3 ("Welcome to MAPS!"), + p "This is the main or home page of MAPS. To the left + you see a menu of choices that you can use to explore MAPS + functionality.", + a ({-href => "/maps/bin/stats.cgi"}, + "Statistics"), + "gives you a view of the spam that MAPS has been trapping for you + in tabular format. You can use", + a ({-href => "/maps/bin/editprofile.cgi"}, + "Edit Profile"), + "to change your profile information or to change your password."; + print + p "MAPS also offers a series of web based", + a ({-href => "/maps/Reports.html"}, + "Reports"), + "to analyze your mail flow. You can manage your", + a ({-href => "/maps/bin/list.cgi?type=white"}, + "White") . ",", + a ({-href => "/maps/bin/list.cgi?type=black"}, + "Black"), "and", + a ({-href => "/maps/bin/list.cgi?type=null"}, + "Null"), + "lists although MAPS seeks to put that responsibility on those + who wish to email you. You can use this to pre-register somebody + or to black or null list somebody. You can also import/export + your lists through these pages."; + print + p a ({-href => "/maps/Admin.html"}, + "MAPS Administration"), + "is to administer MAPS itself and is only available to MAPS + Administrators."; + print + p "Also on the left you will see ", i ("Today's Activity"), + "which quickly shows you what mail MAPS processed today for you."; +} # Body + +# Main +my $action; + +if (defined $new_userid) { + my $result = Login $new_userid, $password; + + if ($result == -1) { + if ($new_userid eq "") { + print redirect ("/maps/?errormsg=Please specify a username"); + exit $result; + } else { + print redirect ("/maps/?errormsg=User \"$new_userid\" does not exist"); + exit $result; + } # if + } elsif ($result == -2) { + print redirect ("/maps/?errormsg=Invalid password"); + exit $result; + } else { + $action = "setcookie"; + } # if +} else { + $action = "getcookie" +} # if + +my $userid = Heading ( + $action, + $new_userid, + "Home", + "Spam Elimination System" +); + +SetContext $userid; +NavigationBar $userid; +Body; +Footing; + +exit; diff --git a/maps/bin/maps b/maps/bin/maps new file mode 100755 index 0000000..fca1586 --- /dev/null +++ b/maps/bin/maps @@ -0,0 +1,224 @@ +#!/usr/bin/perl + +=pod + +=head1 NAME $RCSfile: maps,v $ + +This script filters mail based on the files nulllist, blacklist and whitelist. +Input is an email message. This script extracts the From line and then parses +the email address. If the email is from a sender who should be /dev/null'ed +(e.g. bounce messages from mail daemons) the message will be discarded. If the +sender is on the blacklist then a message is sent back informing the sender that +he's been blacklisted. If the sender is on the white list then the email is +appended to the mail drop file. Otherwise a message is sent back informing the +sender that in order to successfully send email the sender must register for the +permission to do so, along with a URL that allows the sender to sign up. + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.1 $ + +=item Created: + +Fri Nov 29 14:17:21 2002 + +=item Modified: + +$Date: 2013/06/12 14:05:47 $ + +=back + +=head1 SYNOPSIS + + + Usage maps: [-u|ser ] [-ve|rbose] [-deb|ug] [-e|xecute] + + Where: + -u|ser : Set context to this username + + -v|erbose: Be verbose + -de|bug: Output debug messages + + -[no]e|xecute: Set execute mode. + +# (c) Copyright 2000-2006, Andrew@DeFaria.com, all rights reserved. + +=cut + +use strict; +use warnings; + +use Getopt::Long; +use FindBin; +use File::Temp qw (tempfile); +use Net::Domain qw (hostdomain); + +use lib $FindBin::Bin, '/opt/clearscm/lib'; + +use MAPS; +use MAPSLog; + +use Display; +use Utils; + +my $verbose = 0; +my $execute = 1; +my $userid = $ENV{USER}; + +my $logpath = "$FindBin::Bin/../log"; +my $logfile = "$logpath/debug.log"; + +# For some reason I'm not parsing messages correctly but it only seems to +# happen when the message is piped in from the MTA. This routine will +# temporarily save the messages in a file. +sub SaveStdin () { + # Generate tempfile + my $msgfile = tempfile (); + + # Read STDIN and write it out to the tempfile + while () { + print $msgfile $_; + } # while + + # Seek to the start of the file (Note if we closed it, it would be deleted) + seek $msgfile, 0, 0; + + # Return the filehandle + return $msgfile; +} # SaveStdin + +sub save_msg { + my ($sender, $sender_long, $reply_to, $subject, $data) = @_; + + open SAVED_MSG, ">>$logpath/$sender" + or die "Unable to open $logpath/$sender - $!\n"; + + print SAVED_MSG "Sender = $sender\n"; + print SAVED_MSG "Sender long = $sender\n"; + print SAVED_MSG "reply_to = $reply_to\n"; + print SAVED_MSG "subject = $subject\n"; + print SAVED_MSG "data:\n\n"; + print SAVED_MSG $data; + print SAVED_MSG "*** END OF DATA***\n"; +} # save_msg + +sub ValidDomainUser ($) { + my ($sender) = @_; + + my ($username, $domainname); + + if ($sender =~ /(.*)\@(.*)/) { + $username = $1; + $domainname = $2; + } else { + return 1; + } # if + + return 1 if $domainname ne hostdomain; + + # Let BICE email come through + return 1 if $username eq "bice"; + + my $uid = getpwnam $username; + + return defined $uid ? 1 : 0; +} # ValidDomainUser + +sub ProcessMsgs ($$$) { + my ($msgfile, $username, $user_email) = @_; + + return + unless $execute; + + while (!eof *$msgfile) { + my ($sender, $sender_long, $reply_to, $subject, $data) = ReadMsg (*$msgfile); + + my ($onlist, $rule, $sequence, $hit_count); + + if ($sender eq "" or $sender eq "@" or $sender =~ /.*\@$/) { + verbose "Sender not found in message"; + next; + } elsif ($sender eq $user_email and + (lc ($sender_long) !~ lc ("\"$username\" <$user_email>") and + lc ($sender_long) !~ lc ("$username <$user_email>"))) { + verbose "Nulllisting message from sender ($sender_long) pretending to be $user_email"; + Nulllist $sender; + next; + } # if + + ($onlist, $rule, $sequence, $hit_count) = OnNulllist $sender; + + if ($onlist) { + verbose "Nulllisting $sender"; + Nulllist $sender, $sequence, $hit_count; + next; + } # if + + ($onlist, $rule, $sequence, $hit_count) = OnBlacklist $sender; + + if ($onlist) { + verbose "Blacklisting $sender"; + my @msg = split /\n/, $data; + + Blacklist $sender, $sequence, $hit_count, @msg; + next; + } # if + + ($onlist, $rule, $sequence, $hit_count) = OnWhitelist $sender; + + if ($onlist) { + if (ValidDomainUser $sender) { + verbose "Whitelisting $sender"; + Whitelist $sender, $data, $sequence, $hit_count; + } else { + verbose "Sender from this domain but user not found"; + Nulllist $sender; + } # if + } else { + if ($sender !~ /\@/) { + verbose "Sender ($sender) does not appear to be a valid email address"; + } else { + verbose "Returning message from $sender"; + ReturnMsg $sender, $reply_to, $subject, $data; + } # if + } # if + } # while +} # ProcessMsgs + +# Main +GetOptions ( + 'user=s' => \$userid, + 'verbose' => sub { set_verbose }, + 'debug' => sub { set_debug }, + 'execute!' => \$execute, +) || Usage; + +my $msgfile; + +if ($ARGV[0] and $ARGV[0] ne "") { + open $msgfile, $ARGV[0]; + + if (!$msgfile) { + Error "Unable to open file ($ARGV[0]): $!\n"; + exit 1; + } # if +} else { + $msgfile = SaveStdin; +} # if + +verbose "Starting MAPS...."; + +my ($username, $user_email) = SetContext $userid + or die "$userid is not a registered MAPS user\n"; + +ProcessMsgs $msgfile, $username, $user_email; + +exit 0; diff --git a/maps/bin/mapsscrub b/maps/bin/mapsscrub new file mode 100755 index 0000000..2f854b4 --- /dev/null +++ b/maps/bin/mapsscrub @@ -0,0 +1,101 @@ +#!/usr/bin/perl +################################################################################ +# +# File: $RCSfile: mapsscrub,v $ +# Revision: $Revision: 1.1 $ +# Description: This script scrubs messages from the MAPS database based on the +# users settings. +# Author: Andrew@DeFaria.com +# Created: Fri Nov 29 14:17:21 2002 +# Modified: $Date: 2013/06/12 14:05:47 $ +# Language: perl +# +# (c) Copyright 2000-2006, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +use strict; +use warnings; + +use FindBin; + +use lib $FindBin::Bin; + +use MAPS; +use MAPSUtil; + +my $userid; +my $verbose = defined $ARGV[0] && $ARGV[0] eq "-v" ? 1 : 0; +my $total_emails = 0; +my $total_log_entries = 0; +my $total_list_entries = 0; +my $total_users_emails = 0; + +my ($history, $nbr_emails, $nbr_log_entries, $nbr_list_entries, $users_emails); + +format = +@<<<<<<<<<<<<<<<< @>> @##### @##### @##### @##### +$userid,$history,$nbr_emails,$nbr_log_entries,$nbr_list_entries,$users_emails +. +format STDOUT_TOP = +@|||||||||||||||||||||||||||||||||||||||||||||||| +"MAPS Scrubber" + +User ID Age Email Log List User's Emails +----------------- --- ------ ------ ------ ------------- +. + +sub verbose { + my $msg = shift; + + return if $verbose eq 0; + + print "$msg\n"; +} # verbose + +sub CleanUp { + my $userid = shift; + + my %options = GetUserOptions $userid; + $history = $options{"History"}; + my $timestamp = SubtractDays (Today2SQLDatetime, $history); + + $nbr_emails = CleanEmail $timestamp; + $nbr_log_entries = CleanLog $timestamp; + $nbr_list_entries = CleanList $timestamp, "null"; + $users_emails = MAPSDB::count ("email", "userid = \"$userid\""); + write () if $verbose; + + return ($nbr_emails, $nbr_log_entries, $nbr_list_entries, $users_emails); +} # CleanUp + +# Main +my $handle = FindUser; + +#$~ = "REPORT" if $verbose; + +while (($userid) = GetUser $handle) { + last if !defined $userid; + SetContext $userid; + my ($emails, $log_entries, $list_entries, $users_emails) = CleanUp $userid; + $total_emails += $emails; + $total_log_entries += $log_entries; + $total_list_entries += $list_entries; + $total_users_emails += $users_emails; +} # while + +$handle->finish; + +if ($verbose) { + $userid = "Total:"; + $history = "n/a"; + $nbr_emails = $total_emails; + $nbr_log_entries = $total_log_entries; + $nbr_list_entries = $total_list_entries; + $users_emails = $total_users_emails; + write (); +} # if + +# Now optimize the database +OptimizeDB; + +exit; diff --git a/maps/bin/mapsutil b/maps/bin/mapsutil new file mode 100755 index 0000000..fd623a3 --- /dev/null +++ b/maps/bin/mapsutil @@ -0,0 +1,548 @@ +#!/usr/bin/perl +################################################################################# +# File: $RCSfile: mapsutil,v $ +# Revision: $Revision: 1.1 $ +# Description: This script implements a small command interpreter to exercise +# MAPS functions. +# Author: Andrew@DeFaria.com +# Created: Fri Nov 29 14:17:21 2002 +# Modified: $Date: 2013/06/12 14:05:47 $ +# Language: perl +# +# (c) Copyright 2000-2006, Andrew@DeFaria.com, all rights reserved. +# +################################################################################use strict; +use warnings; + +use FindBin; + +use lib $FindBin::Bin; + +use MAPS; +use MAPSLog; +use Term::ReadLine; +use Term::ReadLine::Gnu; +use Term::ReadKey; + +sub EncryptPassword { + my $password = shift; + my $userid = shift; + + my $encrypted_password = Encrypt $password, $userid; + + print "Password: $password = $encrypted_password\n"; +} # EncryptPassword + +sub DecryptPassword { + my $password = shift; + my $userid = shift; + + my $decrypted_password = Decrypt $password, $userid; + + print "Password: $password = $decrypted_password\n"; +} # DecryptPassword + +sub Resequence { + my $userid = shift; + my $type = shift; + + ResequenceList $userid, $type; +} # Resequence + +sub GetPassword { + print "Password:"; + ReadMode "noecho"; + my $password = ReadLine (0); + chomp $password; + print "\n"; + ReadMode "normal"; + + return $password +} # GetPassword + +sub Login2MAPS { + my $username = shift; + my $password = shift; + + if ($username ne "") { + $password = GetPassword if !defined $password or $password eq ""; + } # if + + while (Login ($username, $password) != 0) { + print "Login failed!\n"; + print "Username:"; + $username = <>; + if ($username eq "") { + print "Login aborted!\n"; + return undef; + } # if + chomp $username; + $password = GetPassword; + } # if + + return $username; +} # Login2MAPS + +sub LoadListFile { + # This function loads a ".list" file. This is to "import" our old ".list" + # files. Note it assumes that the ".list" files have specific names. + my $listfilename = shift; + + my $listtype; + + if ($listfilename eq "white.list") { + $listtype = "white"; + } elsif ($listfilename eq "black.list") { + $listtype = "black"; + } elsif ($listfilename eq "null.list") { + $listtype = "null"; + } else { + print "Unknown list file: $listfilename\n"; + return; + } # if + + if (!open LISTFILE, "<$listfilename") { + print "Unable to open $listfilename\n"; + return; + } # if + + my $sequence = 0; + + Info "Adding $listfilename to $listtype list"; + + while () { + chomp; + next if m/^#/ || m/^$/; + + my ($pattern, $comment) = split /\,/; + + AddList $listtype, $pattern, 0, $comment; + $sequence++; + } # while + + if ($sequence == 0) { + print "No messages found to load "; + } elsif ($sequence == 1) { + print "Loaded 1 message "; + } else { + print "Loaded $sequence messages "; + } # if + print "from $listfilename\n"; + + close LISTFILE; +} # LoadListFile + +sub LoadEmail { + # This function loads an mbox file. + my $file = shift; + + if (!open FILE, "<$file") { + print "Unable to open \"$file\" - $!\n"; + return; + } # if + + binmode FILE; + + my $nbr_msgs; + + while (! eof FILE) { + my ($sender, $reply_to, $subject, $data) = ReadMsg (*FILE); + + $nbr_msgs++; + + AddEmail $sender, $subject, $data; + + Info "Added message from $sender to email"; + } # while + + if ($nbr_msgs == 0) { + print "No messages found to load "; + } elsif ($nbr_msgs == 1) { + print "Loaded 1 message "; + } else { + print "Loaded $nbr_msgs messages "; + } # if + print "from $file\n"; +} # LoadEmail + +sub DumpEmail { + # This function unloads email to a mbox file. + my $file = shift; + + if (!open FILE, ">$file") { + print "Unable to open \"$file\" - $!\n"; + return; + } # if + + binmode FILE; + + my $i = 0; + my $handle = FindEmail; + my ($userid, $sender, $subject, $timestamp, $message); + + while (($userid, $sender, $subject, $timestamp, $message) = GetEmail $handle) { + print FILE $message; + $i++; + } # while + + print "$i messages dumped to $file\n"; + + close FILE; +} # DumpEmail + +sub SwitchUser { + my $new_user = shift; + + if ($new_user = Login2MAPS $new_user) { + print "You are now logged in as $new_user\n"; + } # if +} # SwitchContext + +sub ShowSpace { + my $detail = shift; + + my $userid = GetContext; + + if (defined $detail) { + my %msg_space = MAPS::Space $userid; + + foreach (sort (keys (%msg_space))) { + my $sender = $_; + my $size = $msg_space {$_}; + format PER_MSG= +@######### @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< +$size,$sender +. +$~ = "PER_MSG"; + write (); + } # foreach + } else { + my $total_space = MAPS::Space $userid; + $total_space = $total_space / (1024 * 1024); + format TOTALSIZE= +Total size @###.### Meg +$total_space +. +$~ = "TOTALSIZE"; + write (); + } # if +} # ShowSpace + +sub ShowUser { + print "Current userid is " . GetContext () . "\n"; +} # ShowContext + +sub ShowUsers { + my $handle = FindUser; + + my ($userid, $name, $email); + + format USERLIST = +User ID: @<<<<<<<<< Name: @<<<<<<<<<<<<<<<<<<< Email: @<<<<<<<<<<<<<<<<<<<<<<< +$userid,$name,$email +. +$~ = "USERLIST"; + while (($userid, $name, $email) = GetUser $handle) { + last if ! defined $userid; + write (); + } # while + + $handle->finish; +} # ShowUsers + +sub ShowEmail { + my $handle = FindEmail; + + my ($userid, $sender, $subject, $timestamp, $message); + +format EMAIL = +@<<<<<<<<<<<<<<<<<<<@<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< +$timestamp,$sender,$subject +. +$~ = "EMAIL"; + while (($userid, $sender, $subject, $timestamp, $message) = GetEmail $handle) { + last if ! defined $userid; + write (); + } # while + + $handle->finish; +} # ShowEmail + +sub ShowLog { + my $how_many = shift; + + $how_many = defined $how_many ? $how_many : -20; + + my $handle = FindLog $how_many; + + my ($userid, $timestamp, $sender, $type, $message); + +format LOG = +@<<<<<<<<<<<<<<<<<<<@<<<<<<<<< @<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< +$timestamp,$type,$sender,$message +. +$~ = "LOG"; + while (($userid, $timestamp, $sender, $type, $message) = GetLog $handle) { + last if ! defined $userid; + write (); + } # while + + $handle->finish; +} # ShowLog + +sub ShowList { + my $type = shift; + + my $lines = 10; + my $next = 0; + my @list; + my %record; + +format LIST = +@>> @<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<< +$record{sequence},$record{pattern},$record{domain},$record{comment} +. +$~ = "LIST"; + + while (@list = ReturnList $type, $next, $lines) { + foreach (@list) { + %record = %{$_}; + write (); + } # foreach + print "Hit any key to continue"; + ReadLine (0); + $next += $lines; + } # while +} # ShowList + +sub ShowStats { + my $nbr_days = shift; + + $nbr_days = 1 if !defined $nbr_days; + + my %dates = GetStats $nbr_days; + + foreach my $date (keys (%dates)) { + foreach (keys (%{$dates{$date}})) { + print "$date $_:"; + print "\t$dates{$date}{$_}\n"; + } # foreach + } # foreach +} # ShowStats + +sub Deliver { + my $file = shift; + + if (!open MESSAGE, "<$file") { + print "Unable to open message file $file\n"; + return; + } # if + + my $data; + while () { + $data = $data . $_; + } # while + + Whitelist "Andrew\@DeFaria.com", $data; +} # Deliver + +sub ParseCommand { + # Crude parser... + my $cmd = shift; + my $parm1 = shift; + my $parm2 = shift; + my $parm3 = shift; + my $parm4 = shift; + + $_ = $cmd . " "; + SWITCH: { + /^$/ && do { + last SWITCH + }; + + /^resequence / && do { + Resequence GetContext (), $parm1; + last SWITCH + }; + + /^encrypt / && do { + EncryptPassword $parm1, $parm2; + last SWITCH + }; + + /^decrypt / && do { + my $password = UserExists (GetContext()); + DecryptPassword $password; + last SWITCH + }; + + /^deliver / && do { + Deliver $parm1; + last SWITCH + }; + + /^add2whitelist / && do { + Add2Whitelist $parm1, GetContext (), $parm2; + last SWITCH + }; + + /^showusers / && do { + ShowUsers; + last SWITCH + }; + + /^adduser / && do { + AddUser $parm1, $parm2, $parm3, $parm4; + last SWITCH; + }; + + /^cleanemail / && do { + if ($parm1 eq "") { + $parm1 = "9999-12-31 23:59:59"; + } # if + my $nbr_entries = CleanEmail $parm1; + print "$nbr_entries email entries cleaned\n"; + last SWITCH; + }; + + /^deleteemail / && do { + my $nbr_entries = DeleteEmail $parm1; + print "$nbr_entries email entries deleted\n"; + last SWITCH; + }; + + /^cleanlog / && do { + if ($parm1 eq "") { + $parm1 = "9999-12-31 23:59:59"; + } # if + my $nbr_entries = CleanLog $parm1; + print "$nbr_entries log entries cleaned\n"; + last SWITCH; + }; + + /^loadlist / && do { + LoadListFile $parm1; + last SWITCH; + }; + + /^loademail / && do { + LoadEmail $parm1; + last SWITCH; + }; + + /^dumpemail / && do { + DumpEmail $parm1; + last SWITCH; + }; + + /^log / && do { + Logmsg "info", "$parm1 $parm2", $parm3; + last SWITCH; + }; + + /^switchuser / && do { + SwitchUser $parm1; + last SWITCH; + }; + + /^showuser / && do { + ShowUser; + last SWITCH; + }; + + /^showemail / && do { + ShowEmail; + last SWITCH + }; + + /^showlog / && do { + ShowLog $parm1; + last SWITCH + }; + + /^showlist / && do { + ShowList $parm1; + last SWITCH + }; + + /^space / && do { + ShowSpace $parm1; + last SWITCH + }; + + /^showstats / && do { + ShowStats $parm1; + last SWITCH + }; + + /^help / && do { + print "Valid commands are:\n\n"; + print "adduser \tAdd user to DB\n"; + print "add2whitelist \t\tAdd sender to whitelist\n"; + print "cleanlog [timestamp]\t\tCleans out old log entries\n"; + print "log \t\t\tLogs a message\n"; + print "loadlist \t\t\tLoad a list file\n"; + print "cleanemail [timestamp]\t\tCleans out old email entries\n"; + print "deliver \t\t\tDelivers a message\n"; + print "loademail \t\t\tLoad an mbox file\n"; + print "dumpemail \t\t\tDump email from DB to an mbox file\n"; + print "deleteemail \t\t\tDelete email from sender\n"; + print "switchuser \t\t\tSwitch to user\n"; + print "showuser\t\t\t\tShow current user\n"; + print "showusers\t\t\t\tShows users in the DB\n"; + print "showemail\t\t\t\tDisplays email\n"; + print "showlog \t\t\tDisplays log entries\n"; + print "space\t \t\t\tDisplay space usage\n"; + print "showlist \t\t\tShow list by type\n"; + print "showstats \t\t\tDisplays days of stats\n"; + print "encrypt \t\t\tEncrypt a password\n"; + print "resequence \t\t\tResequences a list\n"; + print "help\t\t\t\t\tThis screen\n"; + print "exit\t\t\t\t\tExit mapsutil\n"; + last SWITCH; + }; + + print "Unknown command: $_"; + + print " ($parm1" if defined $parm1; + print ", $parm2" if defined $parm2; + print ", $parm3" if defined $parm3; + print ", $parm4" if defined $parm4; + + if (defined $parm1) { + print ")\n"; + } else { + print "\n"; + } # if + } # SWITCH +} # ParseCommand + +sub GetOpts { +} # GetOpts + +my $maps_username = $ENV{MAPS_USERNAME} ? $ENV{MAPS_USERNAME} : $ENV{USER}; +my $username = Login2MAPS $maps_username, $ENV{MAPS_PASSWORD}; + +if (defined $ARGV [0]) { + ParseCommand $ARGV [0], $ARGV [1], $ARGV [2], $ARGV [3]; + exit; +} # if + +# Use ReadLine +my $term = new Term::ReadLine 'mapsutil'; + +while (1) { + $_ = $term->readline ("MAPSUtil:"); + + last if !defined $_; + + my ($cmd, $parm1, $parm2, $parm3, $parm4) = split; + + last if ($cmd =~ /exit/i || $cmd =~ /quit/i); + + ParseCommand $cmd, $parm1, $parm2, $parm3, $parm4 if defined $cmd; +} # while + +print "\n" if !defined $_; + +exit; diff --git a/maps/bin/modifyentries.cgi b/maps/bin/modifyentries.cgi new file mode 100755 index 0000000..0d92c27 --- /dev/null +++ b/maps/bin/modifyentries.cgi @@ -0,0 +1,72 @@ +#!/usr/bin/perl +################################################################################ +# +# File: $RCSfile: modifyentries.cgi,v $ +# Revision: $Revision: 1.1 $ +# Description: Modify list entries +# Author: Andrew@DeFaria.com +# Created: Mon Jan 16 20:25:32 PST 2006 +# Modified: $Date: 2013/06/12 14:05:47 $ +# Language: perl +# +# (c) Copyright 2000-2006, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +use strict; +use warnings; + +use FindBin; +$0 = $FindBin::Script; + +use lib $FindBin::Bin; + +use MAPS; +use MAPSLog; +use MAPSWeb; + +use CGI qw/:standard/; +use CGI::Carp 'fatalsToBrowser'; + +my $userid = cookie ('MAPSUser'); +my $type = param ('type'); +my $next = param ('next'); + +$userid ||= $ENV{USER}; + +sub ReturnSequenceNbrs { + my @names = param; + my @sequence_nbrs; + + foreach (@names) { + if (/pattern(\d+)/) { + push @sequence_nbrs, $1; + } # if + } # foreach + + return @sequence_nbrs; +} # ReturnSequenceNbrs + +# Main +my $i = 0; + + +foreach (ReturnSequenceNbrs) { + UpdateList + $userid, + $type, + param ("pattern$_"), + param ("domain$_"), + param ("comment$_"), + $_; + $i++; +} # foreach + +if ($i eq 0) { + print redirect ("/maps/php/list.php?type=$type&next=$next&message=Unable to update entries"); +} elsif ($i eq 1) { + print redirect ("/maps/php/list.php?type=$type&next=$next&message=Modified entry"); +} else { + print redirect ("/maps/php/list.php?type=$type&next=$next&message=Modified entries"); +} # if + +exit; diff --git a/maps/bin/nuke b/maps/bin/nuke new file mode 100755 index 0000000..2b8c4ac --- /dev/null +++ b/maps/bin/nuke @@ -0,0 +1,115 @@ +#!/usr/bin/perl +################################################################################ +# +# File: $RCSfile: nuke,v $ +# Revision: $Revision: 1.1 $ +# Description: Displays list of email addresses based on report type. +# Author: Andrew@DeFaria.com +# Created: Fri Nov 29 14:17:21 2002 +# Modified: $Date: 2013/06/12 14:05:47 $ +# Language: perl +# +# (c) Copyright 2000-2006, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +use strict; +use warnings; + +use FindBin; + +use lib $FinBin::Bin, '/opt/clearscm/lib'; + +use MAPS; + +use Display; + +# Just me +my $userid = "andrew"; + +sub GetMailLoops { + my $type = "mailloop"; + + my @emails; + + # Hack: ReturnEmails normally wants a start and end range of what + # emails to get. We really want all of them so let's just use 10000. + @emails = ReturnEmails $userid, $type, 0, 10000; + + my %senders; + + foreach (@emails) { + my $sender = shift @{$_}; + my @msgs = @{$_}; + my ($pattern, $domain) = split (/@/, $sender); + + if (scalar @msgs > 0) { + $senders{$domain} = scalar @msgs; + } # if + } # foreach + + return %senders; +} # GetMailLoops + +sub Add2List { + my $type = shift; + my @items = @_; + + my $sender = ""; + my $nextseq = MAPSDB::GetNextSequenceNo $userid, $type; + + foreach (@items) { + my $domain = "\@$_"; + + display_nolf "Adding $domain to null list ($nextseq)..."; + + if (OnNulllist $domain) { + display_nolf " Already on list"; + DeleteLog $domain; + display " - cleaned"; + } else { + Add2Nulllist $domain, $userid, ""; + display " done"; + + # Now remove this entry from the other lists (if present) + foreach my $otherlist ("white", "black") { + my $sth = FindList $otherlist, $domain; + my ($sequence, $count); + ($_, $_, $_, $_, $_, $sequence) = GetList $sth; + if (defined $sequence) { + $count = DeleteList $otherlist, $sequence; + } # if + } # foreach + } # if + $nextseq++; + } # while +} # Add2List + +# Main + +# Set no output buffering +$| = 1; + +# Let's be nice +setpriority 0, 0, 10; + +SetContext $userid; + +my %senders = GetMailLoops; + +foreach (sort (keys (%senders))) { + print "\@$_\n"; +} # foreach + +if (scalar keys (%senders) eq 0) { + display "No mailloops detected"; + exit 0; +} # if + +print "Nuke these domains? "; +$_ = ; + +if (/y/i) { + Add2List "null", (sort (keys (%senders))); +} # if + +exit; diff --git a/maps/bin/processaction.cgi b/maps/bin/processaction.cgi new file mode 100755 index 0000000..d294bc8 --- /dev/null +++ b/maps/bin/processaction.cgi @@ -0,0 +1,420 @@ +#!/usr/bin/perl +################################################################################# +# +# File: $RCSfile: processaction.cgi,v $ +# Revision: $Revision: 1.1 $ +# Description: Process the action +# Author: Andrew@DeFaria.com +# Created: Mon Jan 16 20:25:32 PST 2006 +# Modified: $Date: 2013/06/12 14:05:47 $ +# Language: Perl +# +# (c) Copyright 2000-2006, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +use strict; +use warnings; + +use FindBin; +$0 = $FindBin::Script; + +use lib $FindBin::Bin; + +use MAPS; +use MAPSWeb; + +use CGI qw (:standard *table start_Tr end_Tr); +use CGI::Carp 'fatalsToBrowser'; + +my $type = param 'type'; +my $action = param 'action'; +my $next = param 'next'; +my $userid = cookie 'MAPSUser'; +my $lines; +my $total; +my $table_name = 'list'; + +my @scripts = ('ListActions.js'); + +sub ReturnSequenceNbrs { + my @names = param; + my @sequence_nbrs; + + Debug "Entered ReturnSequenceNbrs"; + + foreach (@names) { + if (/action(\d+)/) { + push @sequence_nbrs, $1; + } # if + } # foreach + + Debug "Returning sequence nbrs " . join ' ', @sequence_nbrs; + + return @sequence_nbrs; +} # ReturnSequenceNbrs + +sub DeleteEntries { + my ($type) = @_; + + my @sequence_nbrs = ReturnSequenceNbrs; + + my $count; + + foreach (@sequence_nbrs) { + $count += DeleteList $type, $_; + } # foreach + + if ($count eq 0) { + DisplayError 'Nothing to delete!'; + } else { + ResequenceList $userid, $type; + + if ($count eq 1) { + print redirect ("/maps/php/list.php?type=$type&next=$next&message=Deleted entry"); + } else { + print redirect ("/maps/php/list.php?type=$type&next=$next&message=Deleted $count entries"); + } # if + } # if + + return $count; +} # DeleteEntries + +sub PrintInputLine ($$$$$) { + my ($nextseq, $email_nbr, $leftclass, $dataclass, $rightclass) = @_; + + my $email = ''; + my $pattern = ''; + my $domain = ''; + + if (defined $email_nbr && $email_nbr ne '') { + $email = param "email$email_nbr"; + if ($email && $email ne '') { + ($pattern, $domain) = split /\@/, $email; + } # if + } # if + + print Tr [ + td {-class => $leftclass, + -align => 'center'}, "$nextseq", + td {-class => $dataclass, + -align => 'right'}, + (textfield {-class => 'inputfield', + -style => 'width:100%', + -align => 'right', + -size => 25, + -maxlength => '255', + -name => "pattern$nextseq", + -value => $pattern}), + td {-class => $dataclass, + -align => 'center'}, '@', + td {-class => $dataclass}, + (textfield {-class => 'inputfield', + -style => 'width:100%', + -align => 'left', + -size => 25, + -maxlength => '255', + -name => "domain$nextseq", + -value => $domain}), + td {-class => $rightclass}, + (textfield {-class => 'inputfield', + -style => 'width:100%', + -align => 'left', + -size => 25, + -maxlength => '255', + -name => "comment$nextseq", + -value => ''}), + ]; +} # PrintInputLine + +sub AddNewEntry { + my ($type, @selected) = @_; + + # First display the last page and add the appropriate number of + # empty, editable entries (possibly filled in) for the user to add + # the new entry + my $selected = @selected; + my $nextseq = MAPSDB::GetNextSequenceNo $userid, $type; + my $next = ($nextseq - $lines) + $selected - 1; + + $next = 0 + if $next < 0; + + my $Type = ucfirst $type; + + Heading ( + 'getcookie', + '', + "Add to $Type List", + "Add to $Type List", + '', + $table_name, + @scripts + ); + + NavigationBar $userid; + + # Now display table and new entry + print start_form { + -method => 'post', + -action => 'add2' . $type . 'list.cgi', + -name => 'list' + }; + + print start_table {-align => 'center', + -id => $table_name, + -border => 0, + -cellspacing => 0, + -cellpadding => 4, + -width => '100%'}; + print Tr [ + th {-class => 'tableleftend'}, 'Seq', + th {-class => 'tableheader'}, 'Username', + th {-class => 'tableheader'}, '@', + th {-class => 'tableheader'}, 'Domain', + th {-class => 'tablerightend'}, 'Comments' + ]; + + my @list = ReturnList $type, $next, $lines; + my %record; + my $i = 1; + + foreach (@list) { + $i++; + + %record = %{$_}; + + # Normalize fields + my $sequence = $record{sequence}; + my $pattern = $record{pattern} ? $record{pattern} : ' '; + my $domain = $record{domain} ? $record{domain} : ' '; + my $comment = $record{comment} ? $record{comment} : ' '; + + print Tr [ + td {-class => 'tableleftdata', + -align => 'center'}, $sequence, + td {-class => 'tabledata', + -align => 'right'}, $pattern, + td {-class => 'tabledata', + -align => 'center'}, '@', + td {-class => 'tabledata', + -align => 'left'}, $domain, + td {-class => 'tablerightdata', + -align => 'left'}, $comment + ]; + } # foreach + + # Now the input line(s) + if (@selected eq 0) { + PrintInputLine $nextseq, undef, 'tablebottomleft', 'tablebottomdata', + 'tablebottomright'; + } else { + foreach (@selected) { + my $leftclass = $i == $lines ? 'tablebottomleft' : 'tableleftdata'; + my $dataclass = $i == $lines ? 'tablebottomdata' : 'tabledata'; + my $rightclass = $i == $lines ? 'tablebottomright' : 'tablerightdata'; + $i++; + PrintInputLine $nextseq++, $_, $leftclass, $dataclass, $rightclass; + } # foreach + } # for + + print end_table; + print br, + '
    ', + submit ({-name => 'update', + -value => 'Update', + -onClick => 'return CheckEntry (document.list);'}), + submit ({-name => 'Reset', + -value => 'Reset', + -onClick => 'history.back(); return false'}), + '
    '; + print end_form; +} # AddNewEntry + +sub ModifyEntries { + my ($type) = @_; + + my @selected = ReturnSequenceNbrs; + + my $Type = ucfirst $type; + + Heading ( + 'getcookie', + '', + "Modify $Type List", + "Modify $Type List", + '', + $table_name, + @scripts + ); + + NavigationBar $userid; + + # Redisplay the page but open up the lines that are getting modified + print start_form { + -method => 'post', + -action => 'modifyentries.cgi', + -name => 'list' + }; + + # Print some hidden fields to pass along + print + hidden ({-name => 'type', + -default => $type}), + hidden ({-name => 'next', + -default => $next}); + + print start_table {-align => 'center', + -id => $table_name, + -border => 0, + -cellspacing => 0, + -cellpadding => 4, + -width => '100%'}; + print Tr [ + th {-class => 'tableleftend'}, 'Seq', + th {-class => 'tableheader'}, 'Username', + th {-class => 'tableheader'}, '@', + th {-class => 'tableheader'}, 'Domain', + th {-class => 'tablerightend'}, 'Comments' + ]; + + my @list = ReturnList $type, $next, $lines; + my %record; + my $s = 0; + my $i = 1; + + foreach (@list) { + %record = %{$_}; + + my $sequence = $record{sequence}; + my $leftclass = ($i eq $lines || $sequence eq $total) ? + 'tablebottomleft' : 'tableleftdata'; + my $dataclass = ($i eq $lines || $sequence eq $total) ? + 'tablebottomdata' : 'tabledata'; + my $rightclass = ($i eq $lines || $sequence eq $total) ? + 'tablebottomright' : 'tablerightdata'; + + $i++; + + print start_Tr, + td {-class => $leftclass, + -align => 'center'}, $record{sequence}; + + if ($record{sequence} eq $selected[$s]) { + $s++; + # Normalize fields + my $pattern = $record{pattern} ? $record{pattern} : ''; + my $domain = $record{domain} ? $record{domain} : ''; + my $comment = $record{comment} ? $record{comment} : ''; + + print + td {-class => $dataclass, + -align => 'right'}, + (textfield {-class => 'inputfield', + -style => 'width:100%', + -align => 'right', + -size => 25, + -maxlength => '255', + -name => "pattern$sequence", + -value => $pattern}), + td {-class => $dataclass, + -align => 'center'}, '@', + td {-class => $dataclass}, + (textfield {-class => 'inputfield', + -style => 'width:100%', + -align => 'left', + -size => 25, + -maxlength => '255', + -name => "domain$sequence", + -value => $domain}), + td {-class => $rightclass}, + (textfield {-class => 'inputfield', + -style => 'width:100%', + -align => 'left', + -size => 25, + -maxlength => '255', + -name => "comment$sequence", + -value => $comment}); + } else { + # Put in ' ' for undefined fields + my $pattern = $record{pattern} ? $record{pattern} : ' '; + my $domain = $record{domain} ? $record{domain} : ' '; + my $comment = $record{comment} ? $record{comment} : ' '; + + print + td {-class => $dataclass, + -align => 'right'}, $pattern, + td {-class => $dataclass, + -align => 'center'}, '@', + td {-class => $dataclass, + -align => 'left'}, $domain, + td {-class => $rightclass, + -align => 'left'}, $comment; + } # if + + print end_Tr; + } # foreach + + print end_table; + print br, + '
    ', + submit ({-name => 'update', + -value => 'Update', + -onClick => 'return CheckEntry (document.list);'}), + submit ({-name => 'Reset', + -value => 'Reset', + -onClick => 'history.back(); return false'}), + '
    '; + print end_form; +} # ModifyEntries + +sub WhitelistMarked { + AddNewEntry 'white', ReturnSequenceNbrs; +} # WhitelistMarked + +sub BlacklistMarked { + AddNewEntry 'black', ReturnSequenceNbrs; +} # BlacklistMarked + +sub NulllistMarked { + AddNewEntry 'null', ReturnSequenceNbrs; +} # NulllistMarked + +# Main +$userid ||= $ENV{USER}; + +SetContext $userid; + +my %options = GetUserOptions $userid; + +$lines = $options{'Page'}; + +$total = MAPSDB::count 'list', "userid = \"$userid\" and type = \"$type\"" + if $type; + +if ($action eq 'Add New Entry') { + AddNewEntry $type; +} elsif ($action eq 'Delete Marked') { + DeleteEntries $type; +} elsif ($action eq 'Modify Marked') { + ModifyEntries $type; +} elsif ($action eq 'Whitelist Marked') { + WhitelistMarked; +} elsif ($action eq 'Blacklist Marked') { + BlacklistMarked; +} elsif ($action eq 'Nulllist Marked') { + NulllistMarked; +} else { + Heading ( + 'getcookie', + '', + "Unknown action ($action)", + "Unknown action ($action)" + ); + + NavigationBar $userid; + DisplayError "Unknown action encountered ($action)"; +} # if + +Footing $table_name; + +exit; diff --git a/maps/bin/register.cgi b/maps/bin/register.cgi new file mode 100755 index 0000000..63975b4 --- /dev/null +++ b/maps/bin/register.cgi @@ -0,0 +1,94 @@ +#!/usr/bin/perl +################################################################################ +# +# File: $RCSfile: register.cgi,v $ +# Revision: $Revision: 1.1 $ +# Description: Register a MAPS user +# Author: Andrew@DeFaria.com +# Created: Mon Jan 16 20:25:32 PST 2006 +# Modified: $Date: 2013/06/12 14:05:47 $ +# Language: perl +# +# (c) Copyright 2000-2006, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +use strict; +use warnings; + +use FindBin; +$0 = $FindBin::Script; + +use lib $FindBin::Bin; + +use MAPS; +use MAPSLog; +use MAPSWeb; + +use CGI qw/:standard/; + +my $fullname = param ("fullname"); +my $sender = lc (param ("sender")); +my $userid = param ("userid"); + +sub MyFooting { + print div ({-align => "center"}, + button (-name => "close", + -value => "Close Window", + -onClick => "window.close ()")); + print end_html; +} # MyFooting + +sub MyError { + my $errmsg = shift; + + print h3 ({-class => "error", + -align => "center"}, "ERROR: " . $errmsg); + + MyFooting; + + exit 1; +} # MyError + +sub MyHeading { + print + header (-title => "MAPS Registration"), + start_html (-title => "MAPS Registration", + -author => "Andrew\@DeFaria.com", + -style => {-src => "/maps/css/MAPSPlain.css"}); + print + h2 ({-class => "header", + -align => "center"}, + font ({-class => "standout"}, + "MAPS"), "Registration Results"); +} # MyHeading + +# Main +MyHeading; + +if ($sender eq "") { + MyError "Sender not specified!"; +} + +my $rule; + +if (OnWhitelist $sender, $userid) { + MyError "The email address $sender is already on ${userid}'s list" +} # if + +my $messages = Add2Whitelist $sender, $userid, $fullname; + +print p "$fullname, your email address, $sender, has been added to ${userid}'s white list."; + +if ($messages > 0) { + if ($messages == 1) { + print p "Your previous message has been delivered\n"; + } else { + print p "Your previous $messages messages have been delivered\n"; + } # if +} elsif ($messages == -1) { + MyError "Unable to deliver message"; +} else { + print p "Unable to find any old messages but future messages will now be delivered."; +} # if + +MyFooting; diff --git a/maps/bin/registerform.cgi b/maps/bin/registerform.cgi new file mode 100755 index 0000000..eb762c6 --- /dev/null +++ b/maps/bin/registerform.cgi @@ -0,0 +1,127 @@ +#!/usr/bin/perl +################################################################################ +# +# File: $RCSfile: registerform.cgi,v $ +# Revision: $Revision: 1.1 $ +# Description: Register a MAPS user +# Author: Andrew@DeFaria.com +# Created: Mon Jan 16 20:25:32 PST 2006 +# Modified: $Date: 2013/06/12 14:05:47 $ +# Language: perl +# +# (c) Copyright 2000-2006, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +use strict; +use warnings; + +use FindBin; +$0 = $FindBin::Script; + +use CGI qw/:standard *table start_div end_div/; + +use MAPS; +use MAPSWeb; + +my $userid = param ("userid"); +my $Userid = ucfirst $userid; +my $sender = param ("sender"); +my $errormsg = param ("errormsg"); + +sub Heading { + print + header (-title => "MAPS Registration"), + start_html (-title => "MAPS Registration", + -author => "Andrew\@DeFaria.com", + -style => {-src => "/maps/css/MAPSPlain.css"}, + -script => [{ -language => "JavaScript1.2", + -src => "/maps/JavaScript/MAPSUtils.js"}, + { -language => "JavaScript1.2", + -src => "/maps/JavaScript/CheckRegistration.js"} + ]); + print + h2 ({-class => "header", -align => "center"}, + font ({-class => "standout"}, "MAPS"), + "Mail Authorization and Permission System"); + + if (defined $errormsg) { + DisplayError $errormsg; + exit; + } # if +} # Heading + +sub Body { + print start_div {-class => "content"}; + print p ("${Userid}'s email is protected by MAPS, a spam elimination + system. In order to email $Userid you must register. You need + only register once to be added to ${Userid}'s white list, + thereafter you should have no problems emailing them. This is not + unlike the acceptance procedure for many instant messaging clients."); + print p ("Please enter your full name and click on Register to complete the + registration."); + print start_form { + -method => "post", + -action => "register.cgi", + -onSubmit => "return validate (this);" + }; + print start_table { + -cellpadding => 2, + -cellspacing => 0, + -border => 0, + -align => "center", + -width => "360" + }; + print hidden (-name => "userid", + -value => "$userid"); + print Tr [ + td ({-class => "header"}, "Full name:") . + td (textfield {-class => "inputfield", + -size => 50, + -name => "fullname"}) + ]; + print hidden (-name => "sender", + -value => "$userid"); + print end_table; + print p {-align => "center"}, + submit (-name => "submit", + -value => "Register"); + print end_form; + print p ("Tired of dealing with unsolicited email (AKA SPAM)? Want to know + more about MAPS, the Mail Authorization and Permission System for + eliminating SPAM? Click", + a ({-href => "/maps/", + -target => "_blank"}, + "here"), + "to find out more."); + print start_table { + -cellpadding => 2, + -cellspacing => 0, + -border => 1, + -align => "center", + -width => "50%" + }; + print Tr [ + td ({-class => "note", + -align => "center"}, "Note") + ]; + print Tr [ + td ({-class => "notetext"}, + "This registration process is instantaneous however we reserve the + right to remove you from the ${Userid}'s white list should you abuse + this privilege.") + ]; + print end_table; + print end_div; +} # Body + +if (!defined $userid) { + $errormsg = "Internal error: Userid not specified"; +} else { + if (!UserExists ($userid)) { + $errormsg = "Sorry but $userid is no longer a MAPS user"; + } # if +} + +Heading; +Body; +Footing; diff --git a/maps/bin/search.cgi b/maps/bin/search.cgi new file mode 100755 index 0000000..2584f39 --- /dev/null +++ b/maps/bin/search.cgi @@ -0,0 +1,176 @@ +#!/usr/bin/perl +################################################################################ +# +# File: $RCSfile: search.cgi,v $ +# Revision: $Revision: 1.1 $ +# Description: Search by sender and subject +# Author: Andrew@DeFaria.com +# Created: Mon Jan 16 20:25:32 PST 2006 +# Modified: $Date: 2013/06/12 14:05:47 $ +# Language: perl +# +# (c) Copyright 2000-2006, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +use strict; +use warnings; + +use FindBin; +$0 = $FindBin::Script; + +use lib $FindBin::Bin; + +use MAPS; +use MAPSWeb; +use MAPSUtil; +use CGI qw (:standard *table start_Tr start_td start_div end_Tr end_td end_div); +use CGI::Carp "fatalsToBrowser"; + +my $str = param ("str"); +my $next = param ("next"); +my $lines = param ("lines"); +my $userid; +my $prev; +my $total; +my $last; +my $table_name = "searchresults"; + +sub MakeButtons { + my $prev_button = $prev >= 0 ? + a ({-href => "search.cgi?str=$str;next=$prev"}, + "Previous") : ""; + my $next_button = ($next + $lines) < $total ? + a {-href => "search.cgi?str=$str;next=" . ($next + $lines)}, + "Next" : ""; + + my $buttons = $prev_button; + + $buttons = $buttons . + submit ({-name => "action", + -value => "Whitelist Marked", + -onClick => "return CheckAtLeast1Checked (document.detail);"}) . + submit ({-name => "action", + -value => "Blacklist Marked", + -onClick => "return CheckAtLeast1Checked (document.detail);"}) . + submit ({-name => "action", + -value => "Nulllist Marked", + -onClick => "return CheckAtLeast1Checked (document.detail);"}) . + submit ({-name => "action", + -value => "Reset Marks", + -onClick => "return ClearAll (document.detail);"}); + + return $buttons . $next_button; +} # MakeButtons + +sub HighlightSearchStr { + $_ = shift; + + my $highlighted_str = font {-class => "found"}, $str; + + s/$str/$&<\/font>/gi; + + return $_; +} # HighlightSearchStr + +sub Body { + my @emails; + + @emails = SearchEmails $userid, $str; + + my $current = $next + 1; + + print div {-align => "center"}, b ( + "(" . $current . "-" . $last . " of " . $total . ")"); + print start_form { + -method => "post", + -action => "processaction.cgi", + -name => "detail" + }; + my $buttons = MakeButtons; + print div {-align => "center", + -class => "toolbar"}, $buttons; + print start_table ({-align => "center", + -id => $table_name, + -border => 0, + -cellspacing => 0, + -cellpadding => 0, + -width => "100%"}) . "\n"; + print + Tr [ + th {-class => "tableleftend"}, + th {-class => "tableheader"}, "Sender", + th {-class => "tableheader"}, "Subject", + th {-class => "tablerightend"}, "Date" + ]; + + foreach (@emails) { + my $sender = shift @{$_}; + my $subject = shift @{$_}; + my $date = shift @{$_}; + + my $display_sender = HighlightSearchStr $sender; + $subject = HighlightSearchStr $subject; + $subject = $subject eq "" ? "<Unspecified>" : $subject; + + $next++; + + print Tr [ + td {-class => "tableleftdata", + -align => "center"}, + (checkbox {-name => "action$next", + -label => ""}), + hidden ({-name => "email$next", + -default => $sender}), + td {-class => "sender"}, + a {-href => "mailto:$sender"}, $display_sender, + td {-class => "subject"}, + a {-href => "display.cgi?sender=$sender"}, $subject, + td {-class => "dateright", + -width => "115"}, SQLDatetime2UnixDatetime $date + ]; + } # foreach + print end_table; +} # Body + +# Main +my @scripts = ("ListActions.js"); + +$userid = Heading ( + "getcookie", + "", + "Search Results", + "Search Results for \"$str\"", + "", + $table_name, + @scripts +); + +SetContext $userid; +NavigationBar $userid; + +DisplayError "No search string specified" if !defined $str; + +if (!defined $lines) { + my %options = GetUserOptions $userid; + $lines = $options{"Page"}; +} # if + +$total = MAPSDB::count "email", + "userid = \"$userid\" and (subject like \"%$str%\" or sender like \"%$str%\")"; + +DisplayError "Nothing matching!" if $total eq 0; + +$next = !defined $next ? 0 : $next; +$last = $next + $lines < $total ? $next + $lines : $total; + +if (($next - $lines) > 0) { + $prev = $next - $lines; +} else { + $prev = $next eq 0 ? -1 : 0; +} # if + +Body; + +Footing $table_name; + +exit; diff --git a/maps/bin/signup.cgi b/maps/bin/signup.cgi new file mode 100755 index 0000000..a429698 --- /dev/null +++ b/maps/bin/signup.cgi @@ -0,0 +1,108 @@ +#!/usr/bin/perl +################################################################################ +# +# File: $RCSfile: signup.cgi,v $ +# Revision: $Revision: 1.1 $ +# Description: Sign up a MAPS user +# Author: Andrew@DeFaria.com +# Created: Mon Jan 16 20:25:32 PST 2006 +# Modified: $Date: 2013/06/12 14:05:47 $ +# Language: perl +# +# (c) Copyright 2000-2006, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +use strict; +use warnings; + +use FindBin; +$0 = $FindBin::Script; + +use lib $FindBin::New; + +use MAPS; +use MAPSWeb; + +use CGI qw (:standard); + +my $userid = param ("userid"); +my $fullname = param ("fullname"); +my $email = param ("email"); +my $password = param ("password"); +my $repeated_password = param ("repeated_password"); +my $mapspop = param ("MAPSPOP"); +my $history = param ("history"); +my $days = param ("days"); +my $dates = param ("dates"); +my $tag_and_forward = param ("tag_and_forward"); +my $message; + +sub MyError { + my $errmsg = shift; + + $userid = Heading ( + "getcookie", + "", + "Signup", + "Signup" + ); + + NavigationBar $userid; + + print h2 {-align => "center", + -class => "error"}, "Error: " . $errmsg; + + Footing; + + exit 1; +} # MyError + +sub Body { + # Check required fields + if ($userid eq "" ) { + MyError "You must specify a userid!"; + } # if + if ($email eq "" ) { + MyError "You must specify an email address!"; + } # if + if ($password eq "") { + MyError "You must specify a password!"; + } # if + if ($fullname eq "") { + MyError "You must specify your full name!"; + } # if + + # Password field checks + if (length $password < 6) { + MyError "Password must be longer than 6 characters!"; + } # if + if ($password ne $repeated_password) { + MyError "Passwords do not match"; + } # if + + my $status = AddUser $userid, $fullname, $email, $password; + + if ($status ne 0) { + MyError "Username already exists"; + } # if + + my %options = ( + "MAPSPOP" => $mapspop, + "History" => $history, + "Page" => $days, + "Dates" => $dates, + "Tag&Forward" => $tag_and_forward + ); + + my $status = AddUserOptions $userid, %options; + + if ($status == 0) { + print redirect ("/maps/?errormsg=User account \"$userid\" created.
    You may now login"); + } elsif ($status == 1) { + MyError "Username \"$userid\" already exists"; + } else { + MyError "Unable to add useropts for \"$userid\""; + } # if +} # Body + +Body; diff --git a/maps/bin/stats.cgi b/maps/bin/stats.cgi new file mode 100755 index 0000000..9964f4a --- /dev/null +++ b/maps/bin/stats.cgi @@ -0,0 +1,137 @@ +#!/usr/bin/perl +################################################################################ +# +# File: $RCSfile: stats.cgi,v $ +# Revision: $Revision: 1.1 $ +# Description: This script produces a table of statistics of mail processed for +# the user. +# Author: Andrew@DeFaria.com +# Created: Fri Nov 29 14:17:21 2002 +# Modified: $Date: 2013/06/12 14:05:47 $ +# Language: perl +# +# (c) Copyright 2000-2006, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +use strict; +use warnings; + +use FindBin; +$0 = $FindBin::Script; + +use lib $FindBin::Bin; + +use MAPS; +use MAPSLog; +use MAPSUtil; +use MAPSWeb; + +use CGI qw (:standard *table start_Tr end_Tr); +use CGI::Carp "fatalsToBrowser"; + +my $nbr_days = param ("nbr_days"); +my $date = param ("date"); + +my $table_name = "stats"; + +$date = defined $date ? $date : Today2SQLDatetime; + +sub Body { + print start_table ({-align => "center", + -id => $table_name, + -border => 0, + -cellspacing => 0, + -cellpadding => 2, + -cols => 9, + -width => "100%"}); + print start_Tr {-valign => "bottom"}; + print th {-class => "tableleftend"}, "Date"; + + foreach (@Types) { + print th {-class => "tableheader"}, ucfirst; + } # foreach + + print th {-class => "tablerightend"}, "Total"; + + my %dates = GetStats $nbr_days, $date; + my %totals; + + foreach my $date (sort {$b cmp $a} (keys (%dates))) { + print start_Tr; + print td {-class => "tablerightleftdata", + -align => "center"}, FormatDate $date; + + my $day_total = 0; + + foreach (@Types) { + my $value = $dates{$date}{$_}; + if ($value eq 0) { + print td {-class => "tabledata"}, " "; + } else { + print td {-class => "tabledata", + -align => "center"}, + a {-href => "detail.cgi?type=$_;date=$date"}, + $value; + } # if + $totals{$_} += $value; + $day_total += $value; + } # foreach + + if ($day_total eq 0) { + print td {-class => "tableleftrightdata"}, " "; + } else { + print td {-class => "tableleftrightdata", + -align => "center"}, $day_total; + } # if + + print end_Tr; + } # foreach + + my $grand_total = 0; + + print start_Tr; + print th {-class => "tablebottomlefttotal"}, "Totals"; + + foreach (@Types) { + if ($totals{$_} eq 0) { + print td {-class => "tablebottomtotal"}, " "; + } else { + print td {-class => "tablebottomtotal", + -align => "center"}, + a {-href => "detail.cgi?type=$_"}, $totals{$_}; + } # if + + $grand_total += $totals{$_}; + } # foreach + + print td {-class => "tablebottomrighttotal", + -align => "center"}, $grand_total; + + print end_Tr; + print end_table; +} # Body + +# Main +my $userid = Heading ( + "getcookie", + "", + "Statistics", + "Statistics", + "", + $table_name +); + +SetContext $userid; + +if (!defined $nbr_days) { + my %options = GetUserOptions $userid; + $nbr_days = $options{"Dates"}; +} # if + +NavigationBar $userid; + +Body; + +Footing $table_name; + +exit; diff --git a/maps/bin/updateprofile.cgi b/maps/bin/updateprofile.cgi new file mode 100755 index 0000000..a2f9a82 --- /dev/null +++ b/maps/bin/updateprofile.cgi @@ -0,0 +1,82 @@ +#!/usr/bin/perl +################################################################################ +# +# File: $RCSfile: updateprofile.cgi,v $ +# Revision: $Revision: 1.1 $ +# Description: Update the users profile +# Author: Andrew@DeFaria.com +# Created: Mon Jan 16 20:25:32 PST 2006 +# Modified: $Date: 2013/06/12 14:05:47 $ +# Language: perl +# +# (c) Copyright 2000-2006, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +use strict; +use warnings; + +use FindBin; +$0 = $FindBin::Script; + +use lib $FindBin::Bin; + +use MAPS; +use MAPSWeb; + +use CGI qw (:standard); + +my $userid; +my $Userid; +my $fullname = param ("fullname"); +my $email = param ("email"); +my $old_password = param ("old_password"); +my $new_password = param ("new_password"); +my $repeated_password = param ("repeated_password"); +my $mapspop = param ("MAPSPOP"); +my $history = param ("history"); +my $days = param ("days"); +my $dates = param ("dates"); +my $tag_and_forward = param ("tag_and_forward"); + +sub Body { + my %options = ( + "MAPSPOP" => $mapspop, + "History" => $history, + "Page" => $days, + "Dates" => $dates, + "Tag&Forward" => $tag_and_forward + ); + + if (defined $old_password && $old_password ne "") { + my $dbpassword = UserExists $userid; + my $encrypted_old_password = Encrypt $old_password, $userid; + + if ($dbpassword ne $encrypted_old_password) { + DisplayError "Your old password was not correct!"; + } # if + } # if + + if (UpdateUser ($userid, $fullname, $email, $new_password) != 0) { + DisplayError "Unable to update user record for user $userid"; + } # if + + if (UpdateUserOptions ($userid, %options) != 0) { + DisplayError "Unable to update user options for user $userid"; + } # if + + print h2 {-class => "header", + -align => "center"}, + "${Userid}'s profile has been updated"; +} # Body + +$userid = Heading ( + "getcookie", + "", + "Update Profile", + "Update user's profile" +); +$Userid = ucfirst $userid; +SetContext $userid; +NavigationBar $userid; +Body; +Footing; diff --git a/maps/bin/weed b/maps/bin/weed new file mode 100755 index 0000000..2160653 --- /dev/null +++ b/maps/bin/weed @@ -0,0 +1,169 @@ +#!/usr/bin/perl +################################################################################ +# +# File: $RCSfile: weed,v $ +# Revision: $Revision: 1.1 $ +# Description: Weed out obvious spams from the mail store +# Author: Andrew@DeFaria.com +# Created: Mon Feb 19 22:37:30 CST 2007 +# Modified: $Date: 2013/06/12 14:05:47 $ +# Language: perl +# +# (c) Copyright 2007, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +use strict; +use warnings; + +use lib $FindBin::Bin, '/opt/clearscm/lib'; + +use Getopt::Long; + +use MAPS; + +use Display; +use Utils; + +my $mailstore = "/var/spool/exim/input"; + +sub Usage { + display "Usage: weed: [ -v|erbose ] [ -d|ebug ]\n"; + display "Where:"; + display " -v|erbose\tTurn on verbose mode (default off)"; + display " -d|ebug\tTurn on debug mode (default off)"; + display " -u|sage\tDisplay this usage message"; + exit 1; +} # usage + +# Just me +my $userid = "andrew"; + +my ($username, $user_email) = SetContext $userid; + +sub GetEmailsInSpool () { + my %emails; + + # Open mailstore directory. Note must have read access to this + # directory and the files in this directory. IOW we probably need to + # be running as root. Also, the MTA should not be running because we + # may be removing files... + opendir MAILSTORE, $mailstore + or error "Unable to open mailstore $mailstore - $!", 1; + + # Weed out . and .. + my @msgs = grep {!/^\./} readdir MAILSTORE; + + # Don't need the directory opened anymore... + closedir MAILSTORE; + + # Select only the "-H" header files... + @msgs = grep {/-H$/} @msgs; + + # Now search for "From:" in the header files, extract email address + # and put into return hash. + my $msg_nbr; + + foreach (@msgs) { + $msg_nbr = $_; + + my @lines = ReadFile "$mailstore/$msg_nbr"; + + foreach (@lines) { + if (/From:\s*(.*)/) { + my $sender = $1; + + if ($sender =~ /<(\S*)@(\S*)>/) { + $sender = lc ("$1\@$2"); + } elsif ($sender =~ /(\S*)@(\S*)\ /) { + $sender = lc ("$1\@$2"); + } elsif ($sender =~ /(\S*)@(\S*)/) { + $sender = lc ("$1\@$2"); + } # if + + $emails {$msg_nbr} = $sender; + } # if + } # foreach + } # foreach + + verbose scalar (keys (%emails)) . " emails to process"; + + return %emails; +} # GetEmailsInSpool + +sub RemoveEmailInSpool ($) { + my ($msg_nbr) = @_; + + my $datafile = "$mailstore/${msg_nbr}-D"; + my $header_file = "$mailstore/${msg_nbr}-H"; + my $j_file = "$mailstore/${msg_nbr}-J"; + + if (-f $datafile) { + unlink $datafile + or error "Unable to unlink $datafile - $!"; + } # if + + if (-f $header_file) { + unlink $header_file + or error "Unable to unlink $header_file - $!"; + } # if + + if (-f $j_file) { + unlink $j_file + or error "Unable to unlink $j_file - $!"; + } # if +} # RemoveEmailInSpool + +sub FilterEmails (%) { + my %emails= @_; + + my $removed = 0; + + foreach (sort (keys (%emails))) { + my $msg_nbr; + + if (/(\S+)-H$/) { + $msg_nbr = $1; + } # if + + my $sender = $emails {"${msg_nbr}-H"}; + + if ($sender eq "maps\@defaria.com" || + $sender eq "mailer-daemon\@defaria.com" || + $sender =~ /^defaria.*\@defaria.com$/) { + verbose "Removing email $msg_nbr with sender of $sender"; + RemoveEmailInSpool $msg_nbr; + $removed++; + +# Need to get $sender_long. Should call ReadMsg from maps. Have to +# reorganize how this program flows... +# +# } elsif ($sender eq $user_email and +# (lc ($sender_long) !~ lc ("\"$username\" <$user_email>") and +# lc ($sender_long) !~ lc ("$username <$user_email>"))) { +# RemoveEmailInSpool $msg_nbr; +# $removed++; + } elsif (OnNulllist $sender) { + verbose "Nulllist $msg_nbr ($sender)"; + + Nulllist $sender; + RemoveEmailInSpool $msg_nbr; + $removed++; + } # if + } # foreach + + return $removed; +} # FilterEmails +# Main + +my %opts; +my $result = GetOptions (\%opts, + "usage" => sub { Usage }, + "verbose" => sub { set_verbose }, + "debug" => sub { set_debug }, + ); + +my $removed = FilterEmails (GetEmailsInSpool ()); + +verbose "$removed emails removed from the mail store"; + +exit; diff --git a/maps/bin/world.gif b/maps/bin/world.gif new file mode 100644 index 0000000000000000000000000000000000000000..d4e1a939e7ba0889342ac5aa8ab7aa11540a7aa6 GIT binary patch literal 94570 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 + + + + + MAPS Black list + + + + +

    Mail Authorization and Permission System (MAPS)

    + +

    You've been blacklisted!

    + +

    Sorry but I am no longer accepting email from your email address. You've +been blacklisted. This can be because you've abused the privilege of being +able to email me by sending me SPAM or you have otherwise offended me +personally. Your email is discarded and this message is sent to you in an +effort to inform you that continued attempts to email me is fruitless.

    + + + + + diff --git a/maps/css/MAPSPlain.css b/maps/css/MAPSPlain.css new file mode 100644 index 0000000..646cc12 --- /dev/null +++ b/maps/css/MAPSPlain.css @@ -0,0 +1,317 @@ +/************************************************************************/ +/* File: MAPSPlain.css */ +/* Description: Cascading Style Sheet definitions for MAPS (plain */ +/* version) */ +/* Author: Andrew@DeFaria.com */ +/* Created: Mon Nov 3 21:55:05 PST 2003 */ +/* Language: Cascading Style Sheet */ +/* */ +/* (c) Copyright 2003, Andrew@DeFaria.com, all rights reserved. */ +/************************************************************************/ +body { + background-color: white; + color: #000066; + font-family: trebuchet MS, + trebuchet, + verdana, + arial, + sans-serif; + font-size: 16px; + margin: 10px; +} + +.heading { + margin-left: 140px; + margin-top: 5px; + padding: 5px; +} + +.leftbar { + height: auto; + left: 2px; + position: absolute; + top: 5px; + width: 135px; +} + +.username { + color: white; + font-family: veranda, arial; + font-style: bold; + font-size: 14px; + margin-left: 2px; + margin-bottom: 5px; + text-align: center; + width: 125px; +} + +.menu { + background-color: #579; + background-image: url(/maps/images/world.gif); + border: 2px groove black; + font-family: verdana, geneva, arial, helvetica, sans-serif; + font-size: 14px; + font-weight: bold; + line-height: 150%; + margin: 2px; + padding: 2px; + width: 122px; +} + +.intromenu { + background-color: #579; + border: 2px groove black; + font-family: verdana, geneva, arial, helvetica, sans-serif; + font-size: 14px; + font-weight: bold; + line-height: 150%; + margin: 2px; + padding: 2px; + width: 125px; +} + +.search { + background: #4682b4; + border: 2px groove black; + color: white; + font-family: veranda, arial; + font-style: bold; + margin-left: 2px; + margin-right: 5px; + text-align: center; + width: 125px; +} + +.quickstats { + background-color: #ffffcc; + border: 2px groove #336699; + color: black; + font-size: 10px; + line-height: 10px; + margin: 2px; + padding: 2; + width: 125px; +} + +.content { + background: #fff; + border: 0.1px solid #fff; + color: #666; + font-family: trebuchet MS, trebuchet, verdana, arial, sans-serif; + margin: 5px; + padding: 5px; + width: auto; +} + +.copyright { + border-bottom: 1px dotted #ccc; + border-top: 1px dotted #ccc; + color: #666; + font-family: verdana, arial, sans-serif; + font-size: 10px; + margin-top: 5px; + text-align: center; + width: auto; +} + +.label { + color: #993333; + font-weight: bold; + font-size: 14px; +} + +.smalllabel { + color: #993333; + font-weight: bold; + font-size: 12px; +} + +.smallnumber { + color: black; + font-size: 12px; +} + +.header { + color: #000099; + font-weight: bold; +} + +.standout { + color: red +} + +.error { + color: red; + font-style: bold; +} + +.dim { + color: #999999; +} + +.dimsmall { + color: #999999; + font-size: 0.9em; + font-family: Times; +} + +.highlite { + color: #000099; + font-weight: bold; +} + +.inputfield { + background: #ece9d8; + color: Black; + font-family: Veranda, + Times; + font-size: 12px; + padding-top: 0px; + padding-bottom: 0px; +} + +.note { + background: #339999; + color: White; + font-weight: bold; +} + +.notetext { + color: #333; + font-size: 12px; + font-weight: italic; +} + +# Headers +h1, h2, h3, h4, h5 { + color: #000099; +} + +h1 { + font-size: 18pt; +} + +h2 { + font-size: 14pt; +} + +h3 { + font-size: 10pt; +} + +h4 { + font-size: 8pt; +} + +# Global anchor effects +a { + background: transparent; + text-decoration: none; +} + +a:link { + color: #0000ee; +} + +a:visited { + color: #cc33cc; +} + +a:hover { + color: White; + background: #0054e3; +} + +a:active { + color: #ff0000; +} + +# Special anchor effects +.sender { + font-weight: bold +} + +.sender a:link { + color: Red; +} + +.sender a:visited { + color: #0054e3; +} + +.sender a:hover { + color: White; + background: Red; +} + +.sender a:active { + color: Yellow; +} + +# Menu anchors +.menu { + font-weight: bold; +} + +.menu a:link { + color: Red; +} + +.menu a:visited { + color: white; +} + +.menu a:hover { + color: White; + background: Red; +} + +.menu a:active { + color: Yellow; +} + +# Intromenu anchors +.intromenu { + font-weight: bold; +} + +.intromenu a:link { + color: Red; +} + +.intromenu a:visited { + color: white; +} + +.intromenu a:hover { + color: White; + background: Red; +} + +.intromenu a:active { + color: Yellow; +} + +.leftbox { + border: thin silver solid; + float: left; + width: 25%; + margin: 0.5em; + padding: 0.5em; + background: #f8f8f8; + color: black; + font-size: 12px; + -moz-border-radius: 10px; +} + +.rightbox { + border: thin silver solid; + float: right; + width: 25%; + margin: 0.5em; + padding: 0.5em; + background: #f8f8f8; + color: black; + font-size: 12px; + -moz-border-radius: 10px; +} diff --git a/maps/css/MAPSStyle.css b/maps/css/MAPSStyle.css new file mode 100644 index 0000000..7e80d92 --- /dev/null +++ b/maps/css/MAPSStyle.css @@ -0,0 +1,577 @@ +/************************************************************************/ +/* File: MAPSStyle.css */ +/* Description: Cascading Style Sheet definitions for MAPS */ +/* Author: Andrew@DeFaria.com */ +/* Created: Mon Nov 3 21:55:05 PST 2003 */ +/* Language: Cascading Style Sheet */ +/* */ +/* (c) Copyright 2003, Andrew@DeFaria.com, all rights reserved. */ +/************************************************************************/ +body { + background-color: #fff; + background-image: url(/maps/images/Pattern1.gif); + background-repeat: repeat-y; + color: black; + font-family: trebuchet MS, + trebuchet, + verdana, + arial, + sans-serif; + font-size: 14px; +margin: 0px; +} + +.heading { +margin-left: 140px; +margin-top: 5px; +padding: 5px; +} + +#leftbar { +line-height: 18px; +height: auto; +left: 2px; +position: absolute; +top: 5px; +width: 135px; +} + +.username { +color: white; +font-family: veranda, arial; +font-style: bold; +font-size: 14px; +margin-left: 2px; +margin-bottom: 5px; +text-align: center; + width: 125px; +} + +.menu { + background-color: #579; + background-image: url(/maps/images/world.gif); + border: 2px groove black; + font-family: verdana, geneva, arial, helvetica, sans-serif; + font-size: 14px; + font-weight: bold; + line-height: 150%; + margin: 2px; + padding: 2px; + width: 122px; +} + +.intromenu { + background-color: #579; + border: 2px groove black; + font-family: verdana, geneva, arial, helvetica, sans-serif; + font-size: 14px; + font-weight: bold; + line-height: 150%; + margin: 2px; + padding: 2px; + width: 125px; +} + +.search { + background: #4682b4; + border: 2px groove black; + color: white; + font-family: veranda, arial; + font: bold; + font-size: 70%; + margin-left: 2px; + margin-right: 5px; + text-align: center; + width: 125px; +} + +.quickstats { + background-color: #ffffcc; + border: 2px groove #336699; + color: black; + font-size: 10px; + line-height: 10px; + margin: 2px; + width: 125px; +} + +.quickstats a:link { + text-decoration: none; +} + +.quickstats a:hover { + background: blue; + color: white; +} + +.toolbar a:hover { + background: transparent; +} + +.content { + background: #fff; + border: 0.1px solid #fff; + color: black; + font-family: trebuchet MS, + trebuchet, + verdana, + arial, + sans-serif; + margin-left: 140px; + padding: 5px; + width: auto; +} + +.copyright { + border-bottom: 1px dotted #ccc; + border-top: 1px dotted #ccc; + color: #999; + font-family: verdana, arial, sans-serif; + font-size: 10px; + margin-top: 5px; + text-align: center; + width: auto; +} + +.label { + color: #993333; + font-weight: bold; + font-size: 14px; +} + +.smalllabel { + color: #993333; + font-weight: bold; + line-height: 12px; + font-size: 12px; +} + +.smallnumber { + color: black; + line-height: 12px; + font-size: 12px; +} + +.header { + color: #000099; + line-height: 12px; + font-weight: bold; +} + +.standout { + color: red; +} + +.found { + color: black; + background: #ffffcc; + font-style: italic; +} + +.error { + color: red; + font-style: bold; +} + +.dim { + color: #999999; +} + +.dimsmall { + color: #999999; + font-size: 0.9em; + font-family: Times; +} + +.highlite { + color: #000099; + font-weight: bold; +} + +.inputfield { + background: #ece9d8; + color: Black; + font-family: Veranda, + Times; + font-size: 12px; + padding-top: 0px; + padding-bottom: 0px; +} + +#searchfield { + font-size: 95%; + font-weight: normal; + background: #4693c5; + border: solid 1px #00507d; + border-bottom-color: #007de1; + border-right-color: #007de1; + width: 90% +} +#searchfield:hover { + background: #c3e1ff; +} +#searchfield:focus { + background: white; +} + +.note { + background: #339999; + color: White; + font-weight: bold; +} + +.notetext { + color: #333; + font-size: 10px; + font-weight: italic; +} + +/* Headers */ +h1, h2, h3, h4, h5 { + color: #000099; +} + +h1 { + font-size: 18pt; +} + +h2 { + font-size: 14pt; +} + +h3 { + font-size: 10pt; +} + +h4 { + font-size: 8pt; +} + +/* Global anchor effects */ +a { + background: transparent; + text-decoration: none; +} + +a:link { + color: #0080c0; +/* color: #0000ee; */ +} + +a:visited { + color: #cc3300; +} + +a:hover { + color: blue; + background: #ffff80; +} + +a:active { + color: #ff0000; +} + +img { + border: none; +} + +/* Table colors */ +.tableleftend { + background: #804000; + color: white; + font-style: bold; + font-size: 14; + text-align: center; + -moz-border-radius-topleft: 7px; + border-top-left-radius: 7px; +} +.tablerightend { + background: #804000; + color: white; + font-style: bold; + font-size: 14; + text-align: center; + -moz-border-radius-topright: 7px; + border-top-right-radius: 7px; +} + +.tablebordertopleft { + background: #804000; + color: white; + font-style: bold; + font-size: 14; + text-align: center; + -moz-border-radius-topleft: 7px; + border-top-left-radius: 7px; +} + +.tablebordertopright { + background: #804000; + color: white; + font-style: bold; + font-size: 14; + text-align: center; + -moz-border-radius-topright: 7px; + border-top-right-radius: 7px; +} + +.tableborderbottomleft { + background: #804000; + color: white; + font-style: bold; + font-size: 14; + text-align: center; + -moz-border-radius-bottomleft: 7px; + border-bottom-left-radius: 7px; +} + +.tableborderbottomright { + background: #804000; + color: white; + font-style: bold; + font-size: 14; + text-align: center; + -moz-border-radius-bottomright: 7px; + border-bottom-right-radius: 7px; +} + +.tableborder { + background: #804000; + color: white; + font-style: bold; + font-size: 14; + text-align: center; +} + +.tablebordertopleft a:hover { + background: transparent; +} + +.tablebordertopright a:hover { + background: transparent; +} + +.tableborderbottomleft a:hover { + background: transparent; +} + +.tableborderbottomright a:hover { + background: transparent; +} + +.tablelabel { + background: #ece9d8; + text-align: right; + font-family: arial, sans-serif; + font-size: 10px; + font-weight: bold; + -moz-border-radius: 7px; + border-radius: 7px; +} + +.tableheader { + background: #804000; + color: white; + text-align: center; + font-family: arial, sans-serif; + font-size: 14px; + font-weight: bold; +} + +.msgtable { + background: #d4d0c8; +} + +.msgnbr { + font-size: 8px; + text-align: center; +} + +.tableleftdata { + background: #ffffee; + border-left: solid 3px #804000; + border-bottom: 1px dotted #ccc; + font-size: 14; +} +.tableleftrightdata { + background: #ece9d8; + border-right: solid 3px #804000; + border-left: solid 1px #804000; + border-bottom: 1px dotted #ccc; + font-size: 14; +} +.tablerightleftdata { + background: #ece9d8; + border-right: solid 1px #804000; + border-left: solid 3px #804000; + border-bottom: 1px dotted #ccc; + font-size: 14; +} +.tablerightdata { + background: #ffffee; + border-right: solid 3px #804000; + border-left: 1px dotted #ccc; + border-bottom: 1px dotted #ccc; + font-size: 14; +} +.tablebottomleft { + background: #ffffee; + border-left: solid 3px #804000; + border-right: 1px dotted #ccc; + border-bottom: solid 3px #804000; + font-size: 14; + -moz-border-radius-bottomleft: 7px; + border-bottom-left-radius: 7px; +} +.tablebottomright { + background: #ffffee; + border-right: solid 3px #804000; + border-left: 1px dotted #ccc; + border-bottom: solid 3px #804000; + font-size: 14; + -moz-border-radius-bottomright: 7px; + border-bottom-right-radius: 7px; +} +.tablebottomdata { + background: #ffffee; + border-left: 1px dotted #ccc; + border-bottom: solid 3px #804000; + font-size: 14; +} +.tablebottomlefttotal { + background: #ece9d8; + border-left: solid 3px #804000; + border-bottom: solid 3px #804000; + border-right: 1px dotted #ccc; + font-size: 14; + -moz-border-radius-bottomleft: 7px; + border-bottom-left-radius: 7px; +} +.tablebottomrighttotal { + background: #ece9d8; + border-right: solid 3px #804000; + border-bottom: solid 3px #804000; + font-size: 14; + -moz-border-radius-bottomright: 7px; + border-bottom-right-radius: 7px; +} +.tablebottomtotal { + background: #ece9d8; + border-bottom: solid 3px #804000; + border-top: solid 1px #804000; + border-right: 1px dotted #ccc; + font-size: 14; +} +.tabledata { + background: #ffffee; + border-left: 1px dotted #ccc; + border-bottom: 1px dotted #ccc; + font-size: 14; +} + +.date { + background: #ffffee; + font-size: 10px; +} + +.dateright { + background: #ffffee; + font-size: 10px; + border-right: solid 3px #804000; + border-left: 1px dotted #ccc; + border-bottom: 1px dotted #ccc; +} + +/* Special anchor effects */ +.sender { + background: #ffffee; + font-family: arial, sans-serif; + font-size: 12px; + font-weight: bold; +} + +.sender a:link { + color: Red; +} + +.sender a:visited { + color: #0054e3; +} + +.sender a:hover { + color: White; + background: Red; +} + +.sender a:active { + color: Yellow; +} + +.subject { + background: #ffffee; + font-family: arial, sans-serif; + font-size: 10px; + font-weight: bold; +} + +.subject a:link { + color: #0000ee; +} + +.subject a:visited { + color: #cc33cc; +} + +.subject a:hover { + color: White; + background: #0054e3; +} + +.subject a:active { + color: #ff0000; +} + +/* Menu anchors */ +.menu { + font-weight: bold; +} + +.menu a:link { + color: White; + text-decoration: none; +} + +.menu a:visited { + color: white; +} + +.menu a:hover { + color: Yellow; + background: none; +} + +.menu a:active { + color: Yellow; +} + +/* Intromenu anchors */ +.intromenu { + font-weight: bold; +} + +.intromenu a:link { + color: Red; +} + +.intromenu a:visited { + color: white; +} + +.intromenu a:hover { + color: White; + background: Red; +} + +.intromenu a:active { + color: Yellow; +} diff --git a/maps/doc/CommonProblems.html b/maps/doc/CommonProblems.html new file mode 100644 index 0000000..051b844 --- /dev/null +++ b/maps/doc/CommonProblems.html @@ -0,0 +1,16 @@ + + + + MAPS: How does it work? + + + + + + + +

    Unfinished Item

    +
    + diff --git a/maps/doc/Costs.html b/maps/doc/Costs.html new file mode 100644 index 0000000..051b844 --- /dev/null +++ b/maps/doc/Costs.html @@ -0,0 +1,16 @@ + + + + MAPS: How does it work? + + + + + + + +

    Unfinished Item

    +
    + diff --git a/maps/doc/Details.html b/maps/doc/Details.html new file mode 100644 index 0000000..051b844 --- /dev/null +++ b/maps/doc/Details.html @@ -0,0 +1,16 @@ + + + + MAPS: How does it work? + + + + + + + +

    Unfinished Item

    +
    + diff --git a/maps/doc/Download.html b/maps/doc/Download.html new file mode 100644 index 0000000..051b844 --- /dev/null +++ b/maps/doc/Download.html @@ -0,0 +1,16 @@ + + + + MAPS: How does it work? + + + + + + + +

    Unfinished Item

    +
    + diff --git a/maps/doc/FAQ.html b/maps/doc/FAQ.html new file mode 100644 index 0000000..051b844 --- /dev/null +++ b/maps/doc/FAQ.html @@ -0,0 +1,16 @@ + + + + MAPS: How does it work? + + + + + + + +

    Unfinished Item

    +
    + diff --git a/maps/doc/ForgotPassword.html b/maps/doc/ForgotPassword.html new file mode 100644 index 0000000..051b844 --- /dev/null +++ b/maps/doc/ForgotPassword.html @@ -0,0 +1,16 @@ + + + + MAPS: How does it work? + + + + + + + +

    Unfinished Item

    +
    + diff --git a/maps/doc/Forwarding.html b/maps/doc/Forwarding.html new file mode 100644 index 0000000..051b844 --- /dev/null +++ b/maps/doc/Forwarding.html @@ -0,0 +1,16 @@ + + + + MAPS: How does it work? + + + + + + + +

    Unfinished Item

    +
    + diff --git a/maps/doc/Lists.html b/maps/doc/Lists.html new file mode 100644 index 0000000..bb11067 --- /dev/null +++ b/maps/doc/Lists.html @@ -0,0 +1,38 @@ + + + + Mail Authorization and Permission System + + + + + +

    Null List

    +

    First the null list. As you know sending a message back to a potential +spammer will often not work. Many times their return addresses are fake or +their mail boxes are full. The spammers ISP will often send a message back to +you to tell you that the email address is invalid or the user's mail box is +full. You don't care about these messages and they would only create more spam, +though not commerical junk email - unwanted email nonetheless. So you add email +addresses to your null list to tell MAPS to just discard these messages. +Entries in your null list can be generic in nature. For example, "mail-daemon" +is a common string in a bounced email address from an ISP. Entering +"mail-daemon" will discard messages from mail-daemon@aol.com as well as +mail-daemon@msn.com. Most users do not care to receive email messages from +mailer daemons.

    +

    Black List

    +

    The black list is for people who you are effectively ignoring. It works +similarly to null lists except that a message telling the sender that you are +ignoring them is returned to the sender. Use this when you want to make sure +the sender knows that you are ignoring them.

    +

    White List

    +

    The white list is the list of people who are allowed to email you. +Management of the white list is up to the people who wish to email you, thus +you are not bothered by the hassle of having to maintain a white list. You +can manage your white list, pre-registering a user or say initializing your +white list from your addressbook (called Seeding your White List). But by and +large the white list will be managed by your legitimate email subscribers. + + + diff --git a/maps/doc/MAPSLocal.html b/maps/doc/MAPSLocal.html new file mode 100644 index 0000000..051b844 --- /dev/null +++ b/maps/doc/MAPSLocal.html @@ -0,0 +1,16 @@ + + + + MAPS: How does it work? + + + + + + + +

    Unfinished Item

    +
    + diff --git a/maps/doc/MailLoops.html b/maps/doc/MailLoops.html new file mode 100644 index 0000000..051b844 --- /dev/null +++ b/maps/doc/MailLoops.html @@ -0,0 +1,16 @@ + + + + MAPS: How does it work? + + + + + + + +

    Unfinished Item

    +
    + diff --git a/maps/doc/Popsettings.html b/maps/doc/Popsettings.html new file mode 100644 index 0000000..051b844 --- /dev/null +++ b/maps/doc/Popsettings.html @@ -0,0 +1,16 @@ + + + + MAPS: How does it work? + + + + + + + +

    Unfinished Item

    +
    + diff --git a/maps/doc/RegExs.html b/maps/doc/RegExs.html new file mode 100644 index 0000000..051b844 --- /dev/null +++ b/maps/doc/RegExs.html @@ -0,0 +1,16 @@ + + + + MAPS: How does it work? + + + + + + + +

    Unfinished Item

    +
    + diff --git a/maps/doc/Requirements.php b/maps/doc/Requirements.php new file mode 100644 index 0000000..5915f11 --- /dev/null +++ b/maps/doc/Requirements.php @@ -0,0 +1,44 @@ + + + + + MAPS: Requirements + + + + +
    +

    + MAPS Requirements

    +
    + +
    + + +

    Requirements

    + +

    Requirements for MAPS are minimal. All you need is to do is to Signup for a MAPS account. Other + than that we believe that you local email client is the best way of + reading and handling your email. Any email client that supports POP + will work. If you use MAPS as your email server then you would + configure your email client to POP off of defaria.com. Alternately + you can use MAPSPOP to retrieve your + email from any email address but filter it through MAPS.

    + +

    Additionally you can visit the MAPS web site + to view spam activity, manage your white, black and + null lists.

    + + + +
    + + diff --git a/maps/doc/SPAM.php b/maps/doc/SPAM.php new file mode 100644 index 0000000..be32ed3 --- /dev/null +++ b/maps/doc/SPAM.php @@ -0,0 +1,86 @@ + + + + + MAPS: What is SPAM? + + + + +
    +

    + MAPS What is SPAM?

    +
    + +
    + + +

    What is SPAM?

    + +

    SPAM, also known as unsolicited email, has many definitions to + many people. Some people consider it only unsolicited commerical + email - the kind of email that is trying to sell you + something. Others consider it any email that you did not wish to + see. MAPS does not really attempt to define SPAM rather it simply + classifies email as either permitted or not. Initially all email is + considered not permitted. It is only when others register for + permission to email you that MAPS considers email as wanted.

    + +

    Initially all email will be returned to the sender with a message + that describes how to register for permission to email you. Returned + email is saved for up to 30 days (configurable) so that if the + sender decides to register their previous email(s) will be + delivered. If they register then all previous emails will be + delivered and they will be added to your white list. Future emails + from them will be delivered instead of returned.

    + +

    Typically spammers are really robots or scripts that send + thousands or millions of emails to address lists. They don't read + returned messages so they will not register for permission to email + you. Occasionally a spammer, usually a small operation, will read + the returned message and may register. If this happens then you can + easily blacklist that spammer and not be bothered by them + again. As a MAPS user myself who receives probably more SPAM than + you will ever see I can say that perhaps one to two real spammers + will register every other month. So you can easily deal with such + annoyances.

    + +

    Because spammers often use invalid email addresses or email + address that quickly fill up with "Please don't bother me" return + messages, often a MAPS register message will be returned by a + mailer daemon telling you that the spammer's email address + doesn't exist or is full. You don't want to be bothered with such + return messages so MAPS seeds your null list with entries to + prevent this. If you receive emails from such mailer daemons and do + not wish to receive them simply null list them. The null list + is also good for other annoying email that you receive that you'd + rather not be bothered with. For example, you might receive a + newsletter sort of email from a company you normally wish to deal + with but are not really interested in their newsletters. Perhaps the + newsletters are send from an address of + newsletters@<company I care about>.com where + other email might come from support@<company I care + about>.com. In that case you can safely null list + newsletters@<company I care about>.com. For + exmaple, I null list discship@netflix.com because I + do not wish to receive those information emails from Netflix.com about shipments.

    + +

    Your black list is similar to your null list except + instead of merely discarding the email, a return message is sent to + the sender saying that they are blacklisted. This is good for people + who you wish to make sure know that you are consciously ignoring + them.

    + + + +
    + + diff --git a/maps/doc/Signup.html b/maps/doc/Signup.html new file mode 100644 index 0000000..051b844 --- /dev/null +++ b/maps/doc/Signup.html @@ -0,0 +1,16 @@ + + + + MAPS: How does it work? + + + + + + + +

    Unfinished Item

    +
    + diff --git a/maps/doc/Using.php b/maps/doc/Using.php new file mode 100644 index 0000000..6a2c267 --- /dev/null +++ b/maps/doc/Using.php @@ -0,0 +1,33 @@ + + + + + MAPS: Using + + + + +
    +

    + MAPS Using

    +
    + +
    + + +

    Using MAPS

    + +

    To be completed...

    + + + +
    + + diff --git a/maps/doc/Whitelist.html b/maps/doc/Whitelist.html new file mode 100644 index 0000000..051b844 --- /dev/null +++ b/maps/doc/Whitelist.html @@ -0,0 +1,16 @@ + + + + MAPS: How does it work? + + + + + + + +

    Unfinished Item

    +
    + diff --git a/maps/doc/add2blacklist.html b/maps/doc/add2blacklist.html new file mode 100644 index 0000000..96b850e --- /dev/null +++ b/maps/doc/add2blacklist.html @@ -0,0 +1,99 @@ + + + + MAPS Add to Black List + + + + + + + +

    MAPS: Add to Black List
    +

    + This screen allows you to add to your black list. Note that regular expressions + can be used so you can modify the Email address below to be any +portion thereof or specify a regular expression. Here are some examples. +Given an email address of Spammer@spamdomain.com you can:
    + +
      +
    • Specify just the domain (e.g. @spamdomain.com). This will effectively + black list everybody from that domain.
    • +
    • Specify just the username portion (e.g. Spammer@). This will effectively + black list anybody using the username of spammer (note that email addresses +are not case sensitive) from any domain.
    • +
    • Use regular expression characters to further refine your filter. + For example, "^spammer.*@" means any email address that starts (^) with +the word "spammer", has any number of characters after (.*), then an "@" +sign will be black listed.
    • + +
    + +
    \n"; + print start_table({-align => "center", + -border => 1, + -cellspacing => 1, + -cellpadding => 2, + -width => "100%"}) . "\n" . + Tr ({-valign => "top", -bgcolor => $header_background}, [ + th ({-width => "25"}, + font ({-color => $header_foreground}, small ("#"))) . + th (font ({-color => $header_foreground}, small ("Bug ID"))) . + th (font ({-color => $header_foreground}, small ("State"))) . + th (font ({-color => $header_foreground}, small ("Owner"))) . + th (font ({-color => $header_foreground}, small ("Locked?"))) . + th (font ({-color => $header_foreground}, small ("Description"))) + ]) . "\n" . + Tr({-valign=>"TOP"}, \@buglines) . "\n" . + end_table . "\n" . + end_table; + } # if +} # PrintBugTable + +sub Error { + my $errmsg = shift; + print h3 ({-style => "Color: red;", + -align => "CENTER"}, "ERROR: " . $errmsg); + Footing; + exit 1; +} # Error + +# Main +Heading $release; +if ($release) { + ParseBugFile ($release . ".bugs"); + PrintIntroNotes (@intro_notes); + PrintBugTable (@buglines); + Footing; +} # if diff --git a/cq/cqinfo.pl b/cq/cqinfo.pl new file mode 100644 index 0000000..615312c --- /dev/null +++ b/cq/cqinfo.pl @@ -0,0 +1,202 @@ +#!/usr/bin/perl +use strict; +use warnings; + +=pod + +=pod + +=head1 NAME $RCSfile: cqinfo.pl,v $ + +Clearquest Info + +This script takes some parameters and gets information from a Clearquest +database. + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.6 $ + +=item Created: + +Mon Jul 30 12:05:45 PDT 2012 + +=item Modified: + +$Date: 2013/03/15 00:19:36 $ + +=back + +=head1 SYNOPSIS + + Usage: cqinfo.pl [-u|sage] [-v|erbose] [-d|ebug] + [-username ] [-password ] + [-database ] [-dbset ] + [-record ] [-key ] + [-fields ,,...] + [-module] [-server ] [-port ] + + Where: + -u|sage: Displays usage + -v|erbose: Be verbose + -de|bug: Output debug messages + + -r|ecord: Record to interrogate (Default: Defect) + -k|ey: Key to locate the record with (Note that if you supply + simply a number (e.g. 1234) then we will expand that with + leading zeroes to the length of 8 digits and prepend the + database name) + -f|ields: List of fields to display (Default: All fields) + + -use|rname: Username to open database with (Default: from config file) + -p|assword: Password to open database with (Default: from config file) + -da|tabase: Database to open (Default: from config file) + -db|set: Database Set to use (Default: from config file) + -m|odule: Type of Clearquest module to use. Must be one of 'api', + 'client', or 'rest'. The 'api' module can only be used if + Clearquest is installed locally. The 'client' module can + only be successful if a corresponding server is running. And + the 'rest' module can only be used if a CQ Web server has + been set up and configured (Default: rest) + -s|erver: For module = client or rest this is the name of the server + that will be providing the service + -p|ort: For module = client, this is the point on the server to talk + through. + +=head1 Options + +Options are keep in the cq.conf file in etc. They specify the default options +listed below. Or you can export the option name to the env(1) to override the +defaults in cq.conf. Finally you can programmatically set the options when you +call new by passing in a %parms hash. To specify the %parms hash key remove the +CQ_ portion and lc the rest. + +=for html
    + +=over + +=item CQ_WEBHOST + +The web host to contact with leading http:// + +=item CQ_DATABASE + +Name of database to connect to (Default: from config file) + +=item CQ_USERNAME + +User name to connect as (Default: from config file) + +=item CQ_PASSWORD + +Password for CQ_USERNAME + +=item CQ_DBSET + +Database Set name (Default: from config file) + +=item CQ_SERVER + +Clearquest::Server name to connect to (Default: from config file) + +=item CQ_PORT + +Clearquest::Server port to connect to (Default: from config file) + +=back + +=cut + +use FindBin; +use Getopt::Long; + +use lib "$FindBin::Bin/../lib"; + +use Clearquest; +use Display; +use Utils; + +my %opts; + +GetOptions ( + \%opts, + usage => sub { Usage }, + verbose => sub { set_verbose }, + debug => sub { set_debug }, + 'module=s', + 'username=s', + 'password=s', + 'database=s', + 'dbset=s', + 'record=s', + 'key=s', + 'server=s', + 'port=i', + 'fields=s@', +) || Usage; + +Usage "You must specify -key" unless $opts{key}; + +$opts{module} = lc $opts{module} if $opts{module}; + +# Default to Defect +my $record = delete $opts{record} || 'Defect'; +my $key = delete $opts{key}; +my @fields; + +if ($opts{fields}) { + push @fields, split /\s*,\s*/ foreach (@{$opts{fields}}); +} # if + +# Translate any options to ones that the lib understands +$opts{CQ_USERNAME} = delete $opts{username}; +$opts{CQ_PASSWORD} = delete $opts{password}; +$opts{CQ_DATABASE} = delete $opts{database}; +$opts{CQ_DBSET} = delete $opts{dbset}; +$opts{CQ_SERVER} = delete $opts{server}; +$opts{CQ_PORT} = delete $opts{port}; + +my $cq; + +my $module = delete $opts{module}; + +$cq = Clearquest->new (%opts); + +$cq->connect; + +# Fix key if necessary +if ($key =~ /^(\d+)$/) { + $key = $cq->{database} . 0 x (8 - length $1) . $1; +} # if + +my %record = $cq->get ($record, $key, @fields); + +unless ($cq->error) { + foreach my $field (sort keys %record) { + if (ref $record{$field} eq 'ARRAY') { + display "$field (LIST):"; + + display "\t$_" foreach (@{$record{$field}}); + } else { + display_nolf "$field: "; + + if ($record{$field}) { + display $record{$field}; + } else { + display ''; + } # if + } # if + } # foreach + + exit 0; +} else { + error "Unable to get $record with key $key\n" . $cq->errmsg, $cq->error; +} # unless diff --git a/cq/cqquery.pl b/cq/cqquery.pl new file mode 100644 index 0000000..247f6bd --- /dev/null +++ b/cq/cqquery.pl @@ -0,0 +1,438 @@ +#!/usr/bin/perl +use strict; +use warnings; + +=pod + +=head1 NAME $RCSfile: cqquery.pl,v $ + +Clearquest Query + +This command line tool allows for a simplified access to Clearquest database +and supports an SQL like syntax to allow you to select and update data quickly. +It has the ability to talk to a running Clearquest::Server process so you can +use it on systems that do not have Clearques installed. + +Currently the command langauge is limited - no joins or multiple tables, only +very simple where conditions, etc. This may improve over time. + +All actions are logged to cqquery.log. + +Note that CmdLine is in use so you have a fully command history stack (subject, +of course, to whether or not you have Term::ReadLine::Gnu installed. For cqperl +that's a no go. For Cygwin's Perl or Linux based Perl's you do or can install it +from CPAN) as well as CmdLine builtins like history and help. + +Control-C handling is also supported. + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.3 $ + +=item Created: + +Mon Oct 24 16:19:15 PDT 2011 + +=item Modified: + +$Date: 2012/12/18 19:44:10 $ + +=back + +=head1 SYNOPSIS + + Usage: cqquery [-u|sage] [-v|erbose] [-d|ebug] + [-username ] [-password ] + [-database ] [-dbset ] + [-histfile ] + [-[no]c|qd] + + Where: + -usa|ge: Displays usage + -v|erbose: Be verbose + -de|bug: Output debug messages + + -h|istfile : History file to use + + -use|rname : Username name to use + -p|assword : Password to use + -da|tabase : Database to use + -db|set : DB Set to use + -[no]c|qd: If set then look for a Clearquest::Server + +=head1 FILES + +Configuration data is stored in ../etc/cqdservice.conf which defines the +defaults for things like username/password/db, etc. These are overridden by the +environent (-username is CQD_USERNAME, -password is CQDPASSWORD, etc. for +server based connections, CQ_USERNAME, CQPASSWORD, etc. for direct connections). +Command line options (e.i. -username) override both the environment and the +config file. + +=cut + +# TODO: This needs major revision... + +use FindBin; +use Term::ANSIColor; +use Getopt::Long; + +use lib "$FindBin::Bin/../lib"; + +use CmdLine; +use Display; +use Logger; +use Utils; + +my $VERSION = '$Revision: 1.3 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +my %cmds = ( + select => { + help => 'select from [where ]', + description => 'Selects fields from a table with an optional condiiton. +Currently conditions are limited. +', + }, + + update => { + help => 'update
    set = [where ]', + description => 'Update a field in a table based on an optional condition', + }, + + insert => { + help => 'insert [into]
    values ', + description => 'Insert a new record into table', + }, + + delete => { + help => 'delete [from]
    [where ]', + description => 'Delete records from table based on condition (not implemented)', + }, +); + +my (%opts, $cq, $log, $pipe); + +sub interrupt () { + display_nolf + color ('yellow') + . '' + . color ('reset') + . '... ' + . color ('red') + . "Abort current operation (y/N)?" + . color ('reset'); + + my $response = ; + chomp $response; + + die "Operation aborted\n" if $response =~ /^\s*(y|yes)/i; + + display color ('cyan') . 'Continuing...' . color ('reset'); +} # interrupt + +sub pipeInterrupt () { + StopPipe $pipe; + + undef $pipe; +} # pipeInterrupt + +sub findRecords ($$;@) { + my ($table, $condition, @fields) = @_; + + my ($result, $nbrRecs) = $cq->find ($table, $condition, @fields); + + $nbrRecs ||= 0; + + my $msg = "$nbrRecs records qualified"; + + $SIG{PIPE} = \&pipeInterrupt; + + $pipe = StartPipe $ENV{PAGER}; + + PipeOutput $msg, $pipe; + + $log->log ($msg); + + return ($result, $nbrRecs); +} # findRecords + +sub select ($$@) { + my ($table, $condition, @fields) = @_; + + my ($result, $nbrRecs) = findRecords ($table, $condition, @fields); + + if ($cq->errnbr) { + error $result; + + return; + } # if + + while (my %record = $cq->getNext ($result)) { + last unless $pipe; + + foreach (@fields) { + last unless $pipe; + + my $line = $record{$_} ? "$_: $record{$_}" : "$_ "; + + $log->log ($line); + + PipeOutput $line, $pipe; + } # foreach + } # while + + StopPipe $pipe; + + undef $pipe; +} # select + +sub update ($$%) { + my ($table, $condition, %update) = @_; + + my ($result, $nbrRecs) = findRecords ($table, $condition); + + if ($cq->errnbr) { + error $result; + + return; + } # if + + $nbrRecs ||= 0; + + $log->disp ("$nbrRecs records qualified"); + + my ($processed, $updated) = (0, 0); + + while (my %record = $cq->getNext ($result)) { + $processed++; + + my $key = $cq->key ($table, $record{dbid}); + + $log->disp ("Updating $key", 1); + + my $errmsg = $cq->updateRec ($table, $record{dbid}, %update); + + if ($errmsg ne '') { + $log->disp (color ('red') . ' failed!!' . color ('reset')); + $log->incrementErr; + $log->log ($errmsg); + } else { + $log->disp (color ('green' ). ' succeeded.' . color ('reset')); + + $updated++; + } # if + } # while + + my $errors = $log->errors; + + return unless $processed; + + my $msg; + + $msg = $processed; + + if ($processed == 1) { + $log->disp ('One record processed'); + } else { + $log->disp ("$processed records processed"); + } # if + + if ($updated == 1) { + $log->disp ('One record updated'); + } else { + $log->disp ("$updated records updated"); + } # if + + if ($errors == 1) { + $log->disp ('One error (Check ' . $log->fullname . ' for more info)'); + } elsif ($errors > 1) { + $log->disp ("$errors errors (Check " . $log->fullname . ' for more info)'); + } else { + $log->disp ("$errors errors"); + } # if +} # update + +sub insert ($%) { + my ($table, %values) = @_; + + my $errmsg = $cq->insert ($table, %values); + + if ($errmsg ne '') { + $log->err ("Unable to insert record:\n$errmsg"); + } else { + $log->disp ("Inserted record"); + } # if +} # insert + +sub evaluate ($) { + my ($line) = @_; + + my @fields; + + # Mimic simple SQL statements... + if ($line =~ /^\s*select\s+([\w, ]+)\s+from\s+(\S+)(.*)\;*/i) { + my ($table, $condition, $rest); + + @fields = split (/\s*,\s*/, $1); + $table = $2; + $rest = $3; + + # Trim any trailing ';' from table in case the person didn't enter a where + # clause + $table =~ s/\;$//; + + if ($rest =~ /\s*where\s+(.*?)\;*$/i) { + $condition = $1; + } elsif ($rest !~ /^\s*$/) { + error "Syntax error in select statement\n\n\t$line"; + + return 1; + } # if + + return ::select ($table, $condition, @fields); + } elsif ($line =~ /^\s*update\s+(\S+)\s+set\s+(\S+)\s*=\s*(.*)/i) { + my ($table, $condition, %update, $rest); + + $table = $1; + $rest = $3; + + my $fieldName = $2; + + my $value; + + if ($rest =~ /(.*)\s+where\s+(.*)/) { + $value = $1; + $condition = $2; + } else { + $value = $rest; + } # if + + # Fix up $value; + $value =~ s/^\s*["'](.*)/$1/; + $value =~ s/(.*)["']\s*$/$1/; + + $update{$fieldName} = $value; + + return update ($table, $condition, %update); + } elsif ($line =~ /^\s*insert\s+(into)*\s+(\S+)\s+([\w, ]+)\s+values*\s+([\w, ]+)\;*/i) { + my ($table, @values); + + $table = $2; + @fields = split /\s*,\s*/, $3; + @values = split /\s*,\s*/, $4; + + my %values; + + $values{$_} = shift @values foreach (@fields); + + return ::insert ($table, %values); + } elsif ($line =~/^\s*shutdown\s*$/) { + $cq->shutdown; + + exit; + } elsif ($line =~ /^\s*$/) { + return; + } else { + $log->err ("Unknown command: $line"); + + return 1; + } # if +} # evaluate + +## Main +$| = 1; + +# Use test database for now... +$opts{database} = 'mobct'; +$opts{histfile} = $ENV{CQQUERY_HISTFILE} || "./${FindBin::Script}_hist"; +$opts{cqd} = 1; + +GetOptions ( + \%opts, + usage => sub { Usage }, + verbose => sub { set_verbose }, + debug => sub { set_debug }, + 'cqd!', + 'username=s', + 'database=s', + 'password=s', + 'histfile=s', + 'dbset=s', +) || Usage; + +display "$FindBin::Script v$VERSION"; + +$SIG{INT} = \&interrupt; + +if ($opts{cqd}) { + require Clearquest::Client; + $cq = Clearquest::Client->new (%opts); +} else { + require Clearquest; + $cq = Clearquest->new (\%opts); +} # if + +$log = Logger->new; + +my $me = $FindBin::Script; + $me =~ s/\.pl$//; + +my $prompt = color ('bold green') . "$me:" . color ('reset'); +$prompt="$me:"; + +$CmdLine::cmdline->set_histfile ($opts{histfile}); +$CmdLine::cmdline->set_prompt ($prompt); +$CmdLine::cmdline->set_cmds (%cmds); +$CmdLine::cmdline->set_eval (\&evaluate); + +my ($line, $result); + +my $dbconnection = $cq->username . '@' . $cq->database . '/' . $cq->dbset; + $dbconnection .= ' (Server: ' . $cq->host . ':' . $cq->port . ')' + if ref $cq eq 'Clearquest::Client'; + +my $msg = "Opening database $dbconnection"; + +verbose_nolf color ('dark white') . "$msg..." . color ('reset'); +$log->log ($msg, 1); + +unless ($cq->connect) { + $log->msg (color ('red') . ' Failed!' . color ('reset')); + + $log->err ("Unable to connect to database $dbconnection", 1); +} else { + verbose color ('dark white') . ' connected' . color ('reset'); + $log->log (' connected'); +} # unless + +# Single execution from command line +if ($ARGV[0]) { + my $result = evaluate join ' ', @ARGV; + + $result ||= 1; + + exit $result; +} # if + +while (($line, $result) = $CmdLine::cmdline->get ()) { + last unless defined $line; + + $log->log ("$me: $line"); + + last if $line =~ /exit|quit/i; + + my $result = evaluate ($line); +} # while + +$cq->disconnect; + +exit; diff --git a/cq/enable_ldap b/cq/enable_ldap new file mode 100644 index 0000000..cceaa44 --- /dev/null +++ b/cq/enable_ldap @@ -0,0 +1,610 @@ +#!/usr/bin/perl +################################################################################ +# +# File: enable_ldap +# Description: This script enables LDAP Authentication on a DB set. LDAP +# Authentication is supported in Clearquest 2003.06.15 and higher. +# +# Author: Andrew@DeFaria.com +# Created: Fri Sep 23 17:27:58 PDT 2005 +# Language: Perl +# Modules: Term::ReadLine, Term::ReadKey +# +# (c) Copyright 2005, Andrew@DeFaria.com, all rights reserved +# +################################################################################ +use strict; +use warnings; +use Term::ReadLine; +use Term::ReadKey; + +$0 =~ /(.*)[\/\\](.*)/; +my $me = (!defined $2) ? $0 : $2; + +my $execute = 1; +my $verbose = 0; + +sub Usage { + my $msg = shift; + + print "ERROR: $msg\n" if defined $msg; + + print "Usage: $me [-n] [-v] [-u] + +Where: + + -n: No execute mode (Default Execute) + -v: Turn on verbose mode (Default off) + -f: Configuration file (Default ldap_settings.cfg) + -u: Display this usage +"; + + exit 1; +} # Usage + +sub verbose { + my $msg = shift; + + print "$msg" if $verbose; +} # verbose + +sub error { + my $msg = shift; + my $errno = shift; + + if (!defined $errno) { + $msg = "$me: ERROR: $msg"; + } else { + $msg = "$me: ERROR: #$errno: $msg" + } # if + + print $msg; + + exit $errno if defined $errno; +} # error + +sub DisplayLDAPParms { + my %ldap_parms = @_; + + print "\nLDAP Parms:\n"; + + foreach (sort (keys (%ldap_parms))) { + if (/password/) { + print "$_: \n"; + } else { + print "$_: ${ldap_parms {$_}}\n"; + } # if + } # foreach +} # DisplayLDAPParms + +sub ParseSettings { + my $config_file = shift; + + my %ldap_parms; + + open SETTINGS, $config_file + or error "Unable to open $config_file ($!)", 1; + + while () { + chomp; chop if /\r/; + + next if /^$/; # Skip blank lines + next if /^\#/; # and comments + + if (/^dbset:\s*(.*)/i) { + $ldap_parms{dbset} = $1; + } elsif (/^admin_username:\s*(.*)/i) { + $ldap_parms{admin_username} = $1; + } elsif (/^admin_password:\s*(.*)/i) { + $ldap_parms{admin_password} = $1; + } elsif (/^servers:\s*(.*)/i) { + $ldap_parms{servers} = $1; + } elsif (/^port:\s*(.*)/i) { + $ldap_parms{port} = $1; + } elsif (/^port:\s*(.*)/i) { + $ldap_parms{port} = $1; + } elsif (/^search_distinguished_name:\s*(.*)/i) { + $ldap_parms{search_distinguished_name} = $1; + } elsif (/^search_password:\s*(.*)/i) { + $ldap_parms{search_password} = $1; + } elsif (/^basedn:\s*(.*)/i) { + $ldap_parms{basedn} = $1; + } elsif (/^scope:\s*(.*)/i) { + $ldap_parms{scope} = $1; + } elsif (/^account_attribute:\s*(.*)/i) { + $ldap_parms{account_attribute} = $1; + } elsif (/^search_filter:\s*(.*)/i) { + $ldap_parms{search_filter} = $1; + } elsif (/^cq_field:\s*(.*)/i) { + $ldap_parms{cq_field} = $1; + } elsif (/^attribute_search_entry:\s*(.*)/i) { + $ldap_parms{attribute_search_entry} = $1; + } elsif (/^test_username:\s*(.*)/i) { + $ldap_parms{test_username} = $1; + } # if + } # while + + close SETTINGS; + + return %ldap_parms; +} # ParseSettings + +sub Prompt { + my $prefix = shift; # Prefix or question being asked + my $default = shift; # default value - if any + my $suffix = shift; # Suffix (default ":") + my $password = shift; # Whether or not to turn off echo (default "no"); + + $default = "" if !defined $default; + $suffix = ":" if !defined $suffix; + $password = "no" if !defined $password; + + my $value; + + do { + print "\n$prefix"; + print " [$default]" if $default ne "" and $password ne "yes"; + print "$suffix "; + + if ($password eq "yes") { + ReadMode "noecho"; + $value = ReadLine (0); + ReadMode "normal"; + } else { + $value = ; + } # if + + chomp $value; + $value = $default if $value eq ""; + } until $value ne ""; + + return $value +} # Prompt + +sub SaveSettings { + my $config_file = shift; + my %ldap_parms = @_; + + open SETTINGS, ">$config_file" + or error "Unable to open $config_file ($!)", 2; + + foreach (sort (keys (%ldap_parms))) { + if ($_ eq "cq_field") { + my $value = ""; + $value = "CQ_EMAIL" if $ldap_parms {$_} eq "1"; + $value = "CQ_FULLNAME" if $ldap_parms {$_} eq "2"; + $value = "CQ_LOGIN_NAME" if $ldap_parms {$_} eq "3"; + $value = "CQ_MISC_INFO" if $ldap_parms {$_} eq "4"; + $value = "CQ_PHONE" if $ldap_parms {$_} eq "5"; + print SETTINGS "$_:\t$value\n" if $value ne ""; + } else { + print SETTINGS "$_:\t${ldap_parms {$_}}\n"; + } # if + } # foreach + + close SETTINGS; +} # SaveSettings + +sub GetLDAPParms { + my %ldap_parms = @_; + + print "DBSET name: This is the name of the Clearquest database set - also +known as database connection name. This can be found in the Clearquest +Maintainance Tool. Often this is something like \"2003.06.15\". +"; + + $ldap_parms {dbset} = Prompt "What is the DBSET name that you wish to enable LDAP on", $ldap_parms {dbset}; + + print " +Now we need to know the username and password of the administrative +user for the $ldap_parms{dbset} DBSET: +"; + + $ldap_parms {admin_username} = Prompt "Admin username", $ldap_parms {admin_username}; + $ldap_parms {admin_password} = Prompt "${ldap_parms {admin_username}}'s password", $ldap_parms {admin_password}, undef, "yes"; + + # A: LDAP Server + print "\nA: LDAP Server\n"; + print " +Now we need to know the name of the LDAP server to authenticate to. + +What is the host name of the LDAP server? You can specify multiple +hosts so that ClearQuest can attempt to connect to an alternate host +if it cannot connect to the first one. + +You can specify multiple servers separated by commas. +"; + + $ldap_parms {servers} = Prompt "LDAP Server(s)", $ldap_parms {servers}; + + # B: LDAP Port + print "\nB: LDAP Port\n"; + print " +What is the TCP port number (non-SSL) where the LDAP server listens +for communications? +"; + + $ldap_parms {port} = Prompt "LDAP port", $ldap_parms {port}; + + # C: LDAP Search Username/Password + print "\nC: LDAP Distinguished Name for Search Account/Password\n"; + print "\nDoes the LDAP server allow anonymous searches (Y/n)? "; + $_ = ; chomp; + + if ($_ !~ /^y|^yes|^$/i) { + # C1: LDAP Search username + print " +What is the distinguished name (DN) of the search account? + +For example: cn=search_user,cn=Users, dc=cqldapmsft,dc=com +"; + + $ldap_parms {search_distinguished_name} = Prompt "ClearQuest users. LDAP Search user name", $ldap_parms {search_distinguished_name}; + + # C2: LDAP Search passwrod + $ldap_parms {search_password} = Prompt "Password", $ldap_parms {search_password}, undef, "yes"; + } # if + + # D: LDAP BaseDN + print "\nD: LDAP BaseDN\n"; + print " +Now here's where things get tricky. LDAP uses a BaseDN or Base +Distinguished Name as a sort of path into the LDAP directory. Your +LDAP Administrator should be able to provide you with this +information. + +What is the base DN from which to start searching for LDAP user +directory entries that correspond to ClearQuest users? The base DN +must be high enough in the directory hierarchy to include all users +that might need to be authenticated; however, a base DN that is too +high in the hierarchy might slow login performance. +"; + + $ldap_parms {basedn} = Prompt "LDAP BaseDN", $ldap_parms {basedn}; + + # E: LDAP Scope + while (!defined $ldap_parms {scope} or + ($ldap_parms {scope} ne "sub" and + $ldap_parms {scope} ne "one" and + $ldap_parms {scope} ne "base")) { + print "\nE: LDAP Scope\n"; + print " +What is the scope of the search from the base DN?: sub (subtree); one +(one level below); or base (base DN only). +"; + + $ldap_parms {scope} = Prompt "LDAP Scope [sub|one|base]", $ldap_parms {scope}; + } # while + + # F: LDAP Account Attribute + print "\nF: LDAP Account Attribute\n"; + print " +What is the LDAP attribute that is used to store the user entry login +name values? ClearQuest uses the text string entered in the ClearQuest +Login window to search the LDAP directory for a user entry whose LDAP +attribute value matches the login name. This LDAP attribute must store +unique values for all user entries that ClearQuest searches. You also +use this attribute in the answer to the next question. + +For example: samAccountName +"; + + $ldap_parms {account_attribute} = Prompt "LDAP Account Attribute", $ldap_parms {account_attribute}; + + # G: LDAP Search Filter + print "\nG: LDAP Search Filter\n"; + print " +What is the LDAP search filter that ClearQuest must use to select the +LDAP user entry based on the attribute specified in the previous +question? Use \%login\% as the user's login name; ClearQuest substitutes +the text string the user enters in the ClearQuest login window. + +For example: ${ldap_parms {account_attribute}}=\%login\% +"; + + $ldap_parms {search_filter} = Prompt "LDAP Search Filter", $ldap_parms {search_filter}; + + # H: LDAP Attribute Search Entry + print "\nH: LDAP Attribute Search Entry\n"; + print " +What is the LDAP attribute of the user entry to be used to map the +user to a corresponding ClearQuest user profile record? You can map an +attribute to one of the following ClearQuest user profile record +fields: CQ_EMAIL, CQ_FULLNAME, CQ_LOGIN_NAME, CQ_MISC_INFO, or +CQ_PHONE. The ClearQuest administrator and LDAP administrator need to +work together to determine this mapping. + +First specify the Clearquest field you wish to map to: + +1) CQ_EMAIL +2) CQ_FULLNAME +3) CQ_LOGIN_NAME +4) CQ_MISC_INFO +5) CQ_PHONE +"; + + my $default_cq_field; + + if ($ldap_parms {cq_field} eq "CQ_EMAIL") { + $default_cq_field = 1; + } elsif ($ldap_parms {cq_field} eq "CQ_FULLNAME") { + $default_cq_field = 2; + } elsif ($ldap_parms {cq_field} eq "CQ_LOGIN_NAME") { + $default_cq_field = 3; + } elsif ($ldap_parms {cq_field} eq "CQ_MISC_INFO") { + $default_cq_field = 4; + } elsif ($ldap_parms {cq_field} eq "CQ_PHONE") { + $default_cq_field = 5; + } else { + $default_cq_field = 0; + } # if + + do { + $ldap_parms {cq_field} = Prompt "Enter choice (1-5)", $default_cq_field; + } until ($ldap_parms {cq_field} > 0 and $ldap_parms {cq_field} < 6); + + print "\nH: LDAP Attribute Search Entry\n"; + print " +Now enter the corresponding LDAP field that this maps to. +"; + + $ldap_parms {attribute_search_entry} = Prompt "LDAP Attribute Search Entry", $ldap_parms {attribute_search_entry}; + + # I: LDAP Test Username + print "\nI: LDAP Test Username\n"; + print " +What is the login name of a user entry that can be used to validate +that ClearQuest can correctly authenticate a user against the LDAP +directory? This can be a test account or an actual user account. +"; + + $ldap_parms {test_username} = Prompt "LDAP Test Username", $ldap_parms {test_username}; + + # J: LDAP Test Password + print "\nJ: LDAP Test Password\n"; + print" +What is the password for the user entry specified in the previous +question? +"; + + $ldap_parms {test_password} = Prompt "LDAP Test Password", $ldap_parms {test_password}, undef, "yes"; + + return %ldap_parms; +} # GetLDAPParms + +sub SetAuthentication2CQOnly { + my %ldap_parms = @_; + + my $cmd = "installutil setauthenticationalgorithm " . + $ldap_parms {dbset} . " " . + $ldap_parms {admin_username} . " " . + $ldap_parms {admin_password} . " " . + "CQ_ONLY"; + + verbose "$cmd\n"; + + return if !$execute; + + my @output = `$cmd`; + + if ($? ne 0) { + print "Error executing $cmd\n"; + + foreach (@output) { + print $_; + } # foreach + + exit 1; + } # if +} # SetAuthentication2CQOnly + +sub SetLDAPInit { + my %ldap_parms = @_; + + my $cmd = "installutil setldapinit " . + $ldap_parms {dbset} . " " . + $ldap_parms {admin_username} . " " . + $ldap_parms {admin_password} . " \"" . + "-h " . $ldap_parms {servers} . " " . + "-p " . $ldap_parms {port}; + + if (defined $ldap_parms {search_distinguished_name}) { + $cmd .= " -D " . $ldap_parms {search_distinguished_name} . + " -w " . $ldap_parms {search_password}; + } # if + + $cmd .= "\""; + + verbose "$cmd\n"; + + return if !$execute; + + my @output = `$cmd`; + + if ($? ne 0) { + print "Error executing $cmd\n"; + + foreach (@output) { + print $_; + } # foreach + + exit 1; + } # if +} # SetLDAPInit + +sub SetLDAPSearch { + my %ldap_parms = @_; + + my $cmd = "installutil setldapsearch " . + $ldap_parms {dbset} . " " . + $ldap_parms {admin_username} . " " . + $ldap_parms {admin_password} . " \"" . + "-s " . $ldap_parms {scope} . " " . + "-b " . $ldap_parms {basedn} . " " . + $ldap_parms {search_filter} . "\""; + + print "$cmd\n"; + return; + + my @output = `$cmd`; + + if ($? ne 0) { + print "Error executing $cmd\n"; + + foreach (@output) { + print $_; + } # foreach + + exit 1; + } # if +} # SetLDAPSearch + +sub MapLDAPFields { + my %ldap_parms = @_; + + my @cq_fields = ( + "CQ_EMAIL", + "CQ_FULLNAME", + "CQ_LOGIN_NAME", + "CQ_MISC_INFO", + "CQ_PHONE", + ); + + my $cq_field = $cq_fields [($ldap_parms {cq_field} - 1)]; + + my $cmd = "installutil setcqldapmap " . + $ldap_parms {dbset} . " " . + $ldap_parms {admin_username} . " " . + $ldap_parms {admin_password} . " " . + $cq_field . " " . + $ldap_parms {attribute_search_entry}; + + verbose "$cmd\n"; + + return if !$execute; + + my @output = `$cmd`; + + if ($? ne 0) { + print "Error executing $cmd\n"; + + foreach (@output) { + print $_; + } # foreach + + exit 1; + } # if +} # MapLDAPFields + +sub ValidateLDAPConfig { + my %ldap_parms = @_; + + my $cmd = "installutil validateldap " . + $ldap_parms {dbset} . " " . + $ldap_parms {admin_username} . " " . + $ldap_parms {admin_password} . " " . + $ldap_parms {test_username} . " " . + $ldap_parms {test_password}; + + verbose "$cmd\n"; + + return if !$execute; + + my @output = `$cmd`; + + if ($? ne 0) { + print "Error executing $cmd\n"; + + foreach (@output) { + print $_; + } # foreach + + exit 1; + } # if + +} # ValidateLDAPConfig + +sub SetAuthentication2CQFirst { + my %ldap_parms = @_; + my $cmd = "installutil setauthenticationalgorithm " . + $ldap_parms {dbset} . " " . + $ldap_parms {admin_username} . " " . + $ldap_parms {admin_password} . " " . + "CQ_FIRST"; + + verbose "$cmd\n"; + + return if !$execute; + + my @output = `$cmd`; + + if ($? ne 0) { + print "Error executing $cmd\n"; + + foreach (@output) { + print $_; + } # foreach + + exit 1; + } # if +} # SetAuthentication2CQFirst + +my $config_file = "ldap_settings.cfg"; + +while ($ARGV [0]) { + if ($ARGV [0] eq "-v") { + $verbose = 1; + } elsif ($ARGV [0] eq "-n") { + $execute = 0; + } elsif ($ARGV [0] eq "-u") { + Usage; + } elsif ($ARGV [0] eq "-f") { + shift; + if ($ARGV [0] eq "") { + Usage "Must specify config file after -f"; + } # if + $config_file = $ARGV [0]; + } else { + Usage "Unknown argument found: " . $ARGV [0]; + } # if + + shift (@ARGV); +} # while + +my %ldap_parms = ParseSettings $config_file; + +print "$me: Enable Clearquest LDAP Authentication on a dbset + +First we need to ask some questions... + +"; + +%ldap_parms = GetLDAPParms %ldap_parms; + +DisplayLDAPParms %ldap_parms; + +print "Proceed (Y/n)? "; +$_ = ; chomp; + +if ($_ =~ /^y|^yes/i) { + print "OK, quitting...\n"; + exit 1; +} # if + +if (-f $config_file) { + print "Save settings overwriting $config_file (y/N)? "; + $_ = ; chomp; + + if ($_ =~ /^y|^yes/i) { + SaveSettings $config_file, %ldap_parms; + } # if +} else { + SaveSettings $config_file, %ldap_parms; +} # if + +SetAuthentication2CQOnly %ldap_parms; +SetLDAPInit %ldap_parms; +SetLDAPSearch %ldap_parms; +MapLDAPFields %ldap_parms; +ValidateLDAPConfig %ldap_parms; +SetAuthentication2CQFirst %ldap_parms; diff --git a/cq/ldap_settings.cfg b/cq/ldap_settings.cfg new file mode 100644 index 0000000..e3906dd --- /dev/null +++ b/cq/ldap_settings.cfg @@ -0,0 +1,26 @@ +################################################################################# +# +# File: ldap_settings.cfg +# Description: Describes the various LDAP parameters +# Author: Andrew@DeFaria.com +# Created: Wed Nov 2 11:19:04 PST 2005 +# Language: None +# +# (c) Copyright 2005, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +DBSet: 2005.02.00 +Admin_username: admin +#Admin_password: +Servers: stupid_server_at.sexy.broads.com +Port: 389 +Search_distinguished_name: cn=china,cn=Users,dc=washington,dc=ad,dc=sexy.broads,dc=com +#Search_password: +BaseDN: dc=corp,dc=ad,dc=sexy.broads,dc=com +Scope: sub +Account_attribute: samAccountName +Search_filter: samAccountName=%login% +CQ_field: CQ_LOGIN_NAME +attribute_search_entry: samAccountName +Test_username: adefaria +#Test_password: \ No newline at end of file diff --git a/cq/listdynlists b/cq/listdynlists new file mode 100644 index 0000000..f9a5473 --- /dev/null +++ b/cq/listdynlists @@ -0,0 +1,75 @@ +#!cqperl +################################################################################ +# +# File: listdynlists +# Description: This script lists the dynamic lists in the database... +# +# Author: Andrew@DeFaria.com +# Created: Fri Sep 23 17:27:58 PDT 2005 +# Language: Perl +# +# (c) Copyright 2005, Andrew@DeFaria.com, all rights reserved +# +################################################################################ +use strict; +use warnings; +use CQPerlExt; +use File::Spec; + +our ($me, $SEPARATOR); + +my ($abs_path, $lib_path); + +BEGIN { + # Extract relative path and basename from script name. + $0 =~ /(.*)[\/\\](.*)/; + + $abs_path = (!defined $1) ? "." : File::Spec->rel2abs ($1); + $me = (!defined $2) ? $0 : $2; + $me =~ s/\.pl$//; + + # Remove .pl for Perl scripts that have that extension + $me =~ s/\.pl$//; + + # Define the path SEPARATOR + $SEPARATOR = ($^O =~ /MSWin/) ? "\\" : "/"; + + # Setup paths + $lib_path = "$abs_path" . $SEPARATOR . ".." . $SEPARATOR . "lib"; + + # Add the appropriate path to our modules to @INC array. + unshift (@INC, "$abs_path"); + unshift (@INC, "$lib_path"); +} # BEGIN + +use PQA; +use Display; + +my @dynamic_lists = ( + "Advanced_Feature", + "Board_Revision", + "HUT", + "HUT_Revision", + "OS", + "OS_Service_Pack", + "Other_HUT", + "Project", + "Reported_By", + "Software", + "Visibility", +); + +my $to_db_connection_name = "2005.02.00"; +my $controller = StartSession "Cont", $to_db_connection_name; + +foreach (@dynamic_lists) { + display "\nDynamic List: $_"; + my @values = @{$controller->GetListMembers ($_)}; + my $i = 0; + + foreach (@values) { + display "\t" . ++$i . ") $_"; + } # foreach +} # foreach + +EndSession $controller; diff --git a/cq/pqaclean b/cq/pqaclean new file mode 100644 index 0000000..5e805d5 --- /dev/null +++ b/cq/pqaclean @@ -0,0 +1,118 @@ +#!cqperl +################################################################################ +# +# File: pqaclean +# Description: Cleans destination PQA Cont database by removing all defects +# then Customer and Project stateless records. Useful when +# debugging and performing multiple runs of pqamerge. +# +# Author: Andrew@DeFaria.com +# Created: Fri Sep 23 17:27:58 PDT 2005 +# Language: Perl +# +# (c) Copyright 2005, Andrew@DeFaria.com, all rights reserved +# +################################################################################ +use strict; +use warnings; +use CQPerlExt; +use File::Spec; + +our ($me, $SEPARATOR); + +my ($abs_path, $lib_path); + +BEGIN { + # Extract relative path and basename from script name. + $0 =~ /(.*)[\/\\](.*)/; + + $abs_path = (!defined $1) ? "." : File::Spec->rel2abs ($1); + $me = (!defined $2) ? $0 : $2; + $me =~ s/\.pl$//; + + # Remove .pl for Perl scripts that have that extension + $me =~ s/\.pl$//; + + # Define the path SEPARATOR + $SEPARATOR = ($^O =~ /MSWin/) ? "\\" : "/"; + + # Setup paths + $lib_path = "$abs_path" . $SEPARATOR . ".." . $SEPARATOR . "lib"; + + # Add the appropriate path to our modules to @INC array. + unshift (@INC, "$abs_path"); + unshift (@INC, "$lib_path"); +} # BEGIN + +use PQA; +use Display; +use Logger; +use TimeUtils; + +my $from_db_connection_name = "2005.02.00"; + +sub Usage { + my $msg = shift; + + display "ERROR: $msg\n" if defined $msg; + + display "Usage: $me\t[-u] [-v] [-d] [-from ] + +Where: + -u: Display usage + -v: Turn on verbose mod + -d: Turn on debug mode + -from : Specify the from connaction name + (Default $from_db_connection_name)"; + exit 1; +} # Usage + + +my $log = Logger->new (path => "."); + +while ($ARGV [0]) { + if ($ARGV [0] eq "-v") { + Display::set_verbose; + Logger::set_verbose; + } elsif ($ARGV [0] eq "-d") { + set_debug; + } elsif ($ARGV [0] eq "-from") { + shift; + if (!$ARGV [0]) { + Usage "Must specify after -from"; + } else { + $from_db_connection_name = $ARGV [0]; + } # if + } elsif ($ARGV [0] eq "-u") { + Usage; + } else { + Usage "Unknown argument found: " . $ARGV [0]; + } # if + + shift (@ARGV); +} # while + +my $process_start_time = time; +my $controller = StartSession "Cont", $from_db_connection_name; +$log->msg ("Opened Controller (Cont) database from \"$from_db_connection_name\" connection"); + +my $start_time; + +$start_time = time; +DeleteRecords $log, $controller, "defect"; +display_duration $start_time, $log; + +$start_time = time; +DeleteRecords $log, $controller, "Customer"; +display_duration $start_time, $log; + +$start_time = time; +DeleteRecords $log, $controller, "Project"; +display_duration $start_time, $log; + +$start_time = time; +DeleteDynamicLists $log, $controller; +display_duration $start_time, $log; + +EndSession $controller; +display_duration $process_start_time, $log; diff --git a/cq/pqamerge b/cq/pqamerge new file mode 100644 index 0000000..5592481 --- /dev/null +++ b/cq/pqamerge @@ -0,0 +1,656 @@ +#!cqperl +################################################################################ +# +# File: pqamerge +# Description: Merge the old TO (Teton) and Prod databases to the new Cont +# (Controller) database. This process assumes the new database +# is empty and that there are two "masterdb"'s named From and To. +# These are Clearquest connection profiles and From and To refer +# to the names given in the Clearquest Maintainance Tool for the +# connections. From contains both the TO and Prod databases and +# the To connection contains the Cont database. +# +# Note that it is also assumed that the Cont database has had it's +# code page set to US ASCII. This script will translate non US +# ASCII characters in the from databases to HTML equivalents. +# +# Author: Andrew@DeFaria.com +# Created: Fri Sep 23 17:27:58 PDT 2005 +# Language: Perl +# +# (c) Copyright 2005, Andrew@DeFaria.com, all rights reserved +# +################################################################################ +use strict; +use warnings; +use CQPerlExt; +use File::Spec; + +our ($me, $SEPARATOR); + +my ($abs_path, $lib_path); + +BEGIN { + # Extract relative path and basename from script name. + $0 =~ /(.*)[\/\\](.*)/; + + $abs_path = (!defined $1) ? "." : File::Spec->rel2abs ($1); + $me = (!defined $2) ? $0 : $2; + $me =~ s/\.pl$//; + + # Remove .pl for Perl scripts that have that extension + $me =~ s/\.pl$//; + + # Define the path SEPARATOR + $SEPARATOR = ($^O =~ /MSWin/) ? "\\" : "/"; + + # Setup paths + $lib_path = "$abs_path" . $SEPARATOR . ".." . $SEPARATOR . "lib"; + + # Add the appropriate path to our modules to @INC array. + unshift (@INC, "$abs_path"); + unshift (@INC, "$lib_path"); +} # BEGIN + +use PQA; +use Display; +use Logger; +use TimeUtils; + +my $from_db_connection_name = "2003.06.00"; +my $to_db_connection_name = "2005.02.00"; +my $id; + +sub Usage { + my $msg = shift; + + display "ERROR: $msg\n" if defined $msg; + + display "Usage: $me\t[-u] [-v] [-d] [-id ] + [-from ] + [-to ] + +Where: + + -u: Display usage + -v: Turn on verbose mode + -d: Turn on debug mode + -id : Process only the specified defect + (Default: Process all defects) + -from : Specify the from connection name + (Default: $from_db_connection_name) + -to : Specify the to connection name + (Default: $to_db_connection_name)"; + exit 1; +} # Usage + +sub BurnIDsTil { + my $log = shift; + my $to = shift; + my $record_name = shift; + my $current_id = shift; + my $dest_id = shift; + + my $entity; + + while ($current_id < $dest_id) { + # Create a new entity and get it's ID until we reach $dest_id + $entity = $to->BuildEntity ($record_name); + $current_id = $entity->GetFieldValue ("id")->GetValue; + + # Change $current_id to just the number portion + $current_id = substr $current_id, 4, 8; + + # Burn the id if it is not equal to the $dest_id + $entity->Revert if $current_id < $dest_id; + } # while + + return $entity; +} # BurnIDsTil + +sub TransferState { + my $log = shift; + my $record_name = shift; + my $db = shift; + my $id = shift; + my $state = shift; + + # There is no corresponding Submit state in Cont so we cannot + # transition it's state. For now we will leave it Assigned. + return if $state eq "Submit"; + + # State transition matrix: This hash defines a state to get to and + # an array of how to get there. + my %state_transition_matrix = ( + "Assigned" => [ + ], + "Resolved" => [ + "Resolve" + ], + "Unassigned" => [ + "Unassign" + ], + "Data_Pending" => [ + "Data_Pending" + ], + "Verified" => [ + "Resolve", + "Verify" + ], + "Awaiting_Cust_Verify" => [ + "Resolve", + "Verify", + "VerifiedPendingCustVerify" + ], + "Closed" => [ + "Resolve", + "Verify", + "Close" + ], + "Verified_Cust_Accepted" => [ + "Resolve", + "Verify", + "VerifiedPendingCustVerify", + "CustomerVerified" + ], + ); + + # Not transition through the necessary states + my $current_state = $state; + my @actions = @{$state_transition_matrix {$current_state}}; + + debug "Transitioning $id to $current_state State"; + + foreach (@actions) { + debug "Applying action $_"; + + my $new_entity = $db->GetEntity ($record_name, $id); + + $db->EditEntity ($new_entity, $_); + + my $errmsg = $new_entity->Validate; + + if ($errmsg ne "") { + verbose ""; + $log->err ("\n$id\n$errmsg"); + return; + } else { + # Post record to database + $new_entity->Commit; + } # if + } # foreach +} # TransferState + +sub TransferDefects { + my $log = shift; + my $from = shift; + my $to = shift; + my $dbname = shift; + my $record_name = shift; + my $search_id = shift; + my @field_list = @_; + + my $result; + my $new_id; + + if (defined $search_id) { + $result = GetDefectRecord $log, $from, $record_name, $search_id; + } else { + $result = GetAllDefectRecords $log, $from, $record_name; + } # if + + return if !$result; + + my $old_bufffer_status = $|; + $| = 1; # Turn off buffering + + my $nbr = 0; + + # Seed $current_id - IOW what is the current ID in the destination + # database? + my $current_id; + if (!defined $search_id) { + my $entity = $to->BuildEntity ($record_name); + $current_id = $entity->GetFieldValue ("id")->GetValue; + } # if + + # Now for each record returned by the query... + while ($result->MoveNext == $CQPerlExt::CQ_SUCCESS) { + # GetEntity by using $id + my $id = $result->GetColumnValue (1); + my $from_entity = $from->GetEntity ($record_name, $id); + my $title; + my @files_created; + my $history_filename = "history.txt"; + my $to_entity; + + if (!defined $search_id) { + # Check to see if $id > $current_id. If so then we can't + # proceed. If not then we need to burn up some IDs. + my $current_id_nbr = substr $current_id, 4, 8; + my $dest_id_nbr; + + if ($id =~ /^Prod/) { + $dest_id_nbr = substr $id, 4, 8; + } else { + $dest_id_nbr = 20000 + (substr $id, 2, 8); + } # if + + if ($current_id_nbr > $dest_id_nbr) { + error "Unable to sequence merge", 1; + } elsif ($current_id_nbr < $dest_id_nbr) { + $to_entity = BurnIDsTil $log, $to, $record_name, $current_id_nbr, $dest_id_nbr; + } # if + } else { + # Since $search_id is defined we're doing a single ID, in test + # mode, so generate a new $to_entity. IOW there is no sequencing + # going on... + $to_entity = $to->BuildEntity ($record_name); + } # if + + $log->msg (++$nbr . ": Merging ID $id ", "nolf"); + + # Get the fields... + foreach (@field_list) { + my $name = $_; + my $value = $from_entity->GetFieldValue ($name)->GetValue; + + # Here we handle the differences between records.. + if ($dbname eq "TO") { + ## Field Translations + + # TO: defect: AdvancedFeature -> Cont: defect: Advanced_Feature + if ($name eq "AdvancedFeature") { + $name = "Advanced_Feature"; + AddToFieldChoiceList $to, $to_entity, $name, $name, $value; + } # if + + # TO: defect: Fixed_In_Project -> Cont: defect: Fixed_In_Project + # but as a reference to Cont: Project + AddToFieldChoiceList $to, $to_entity, "Project", $name, $value if $name eq "Fixed_In_Project"; + + # TO: defect: Found_In_Project -> Cont: defect: Found_In_Project + # but as a reference to Cont: Project + AddToProject $log, $to, $value if $name eq "Found_In_Project"; + + # TO: defect: Fixed_In_SW_Version -> Cont: defect: Fixed_In_SW_Version + if ($name eq "Fixed_In_SW_Version") { + $value = "N/A" if $value eq ""; + } # if + + # TO: defect: History -> Cont: defect: + # Transfer history item to an attachment + if ($name eq "History") { + TransferHistory ($from_entity, $to_entity, $history_filename); + } # if + + ## Field renames + + # TO: defect: GatingItem -> Cont: defect: Gating_Item_HW + $name = "Gating_Item_SW" if $name eq "GatingItem"; + + # TO: defect: HUT_Version -> Cont: defect: Board_Revision + if ($name eq "HUT_Version") { + $name = "Board_Revision"; + $value = "Not Applicable" if $value eq "N/A"; + AddToFieldChoiceList $to, $to_entity, $name, $name, $value; + } # if + + # TO: defect: ReportedBy -> Cont: defect: Reported_By + if ($name eq "ReportedBy") { + $name = "Reported_By"; + AddToFieldChoiceList $to, $to_entity, $name, $name, $value + } # if + + # TO: defect: NoteBugReview -> Cont: defect: Bug_Review_Note + $name = "Bug_Review_Note" if $name eq "NoteBugReview"; + + # TO: defect: NoteBRCMOnly -> Cont: defect: Broadcom_Only_Note + $name = "Broadcom_Only_Note" if $name eq "NoteBRCMOnly"; + + # TO: defect: Open_Close_Status -> Cont: defect: Active_Deferred_Status + $name = "Active_Deferred_Status" if $name eq "Open_Close_Status"; + + # TO: defect: SQATestCase -> Cont: defect: PQATestCase + if ($name eq "SQATestCase") { + $name = "PQATestCase"; + $value = "N/A" if $value eq ""; + } # if + + # TO: defect: Title_2 -> Cont: defect: Title + if ($name eq "Title_2") { + # There are some blank titles! + $value = "N/A" if $value eq ""; + $title = $value; + $name = "Title"; + } # if + + ## Field deletes + next if $name eq "AttachmentsBRCM" or + $name eq "Project" or + $name eq "PendingHWSWReleases" or + $name eq "TestBlocking"; + } elsif ($dbname eq "Prod") { + ## Field Translations + + # Prod: defect: AdvancedFeature -> Cont: defect: Advanced_Feature + if ($name eq "AdvancedFeature") { + $name = "Advanced_Feature"; + AddToFieldChoiceList $to, $to_entity, $name, $name, $value; + } # if + + # Prod: defect: Fixed_In_Project -> Cont: defect: Project + # but as a reference to Cont: Project + AddToFieldChoiceList $to, $to_entity, "Project", $name, $value if $name eq "Fixed_In_Project"; + + # Prod: defect: Fixed_In_SW_Version -> Cont: defect: Fixed_In_SW_Version + if ($name eq "Fixed_In_SW_Version") { + $value = "N/A" if $value eq ""; + } # if + + # Prod: defect: History -> Cont: defect: + # Transfer history item to an attachment + if ($name eq "History") { + TransferHistory ($from_entity, $to_entity, $history_filename); + } # if + + # Prod: defect: Category -> Cont: defect: Category + if ($name eq "Category") { + # There is no "Hardware" anymore so translating them to "Hardware - Board" + if ($value eq "Hardware") { + $value = "Hardware - Board"; + } # if + } # if + + # Prod: defect: GatingItem -> Cont: defect: Gating_Item_HW + $name = "Gating_Item_SW" if $name eq "GatingItem"; + + # Prod: defect: HUT_Version -> Cont: defect: Board_Revision + if ($name eq "HUT_Version") { + $name = "Board_Revision"; + $value = $value ne "" ? $value : "Not Applicable"; + $value = "Not Applicable" if $value eq "N/A"; + if ($value eq "BCM95704CA40 v1.0 revA0 ") { + # Trailing blank is wrong! - Removing it + $value = "BCM95704CA40 v1.0 revA0"; + } # if + AddToFieldChoiceList $to, $to_entity, $name, $name, $value; + } # if + + # Prod: defect: Issue_Classification -> Cont: defect: Issue_Classification + # There are no: Hardware in the new Cont database so we'll map it to + # "Requirement" + if ($name eq "Issue_Classification") { + $value = "Requirement" if $value eq "Hardware"; + } # if + + # Prod: defect: NoteBugReview -> Cont: defect: Bug_Review_Note + $name = "Bug_Review_Note" if $name eq "NoteBugReview"; + + # Prod: defect: NoteBRCMOnly -> Cont: defect: Broadcom_Only_Note + $name = "Broadcom_Only_Note" if $name eq "NoteBRCMOnly"; + + # Prod: defect: Open_Close_Status -> Cont: defect: Active_Deferred_Status + $name = "Active_Deferred_Status" if $name eq "Open_Close_Status"; + + # Prod: defect: Project -> Cont: defect: Found_In_Project + if ($name eq "Project") { + AddToProject $log, $to, $value; + $name = "Found_In_Project"; + } # if + + # Prod: defect: ReportedBy -> Cont: defect: Reported_By + if ($name eq "ReportedBy") { + $name = "Reported_By"; + AddToFieldChoiceList $to, $to_entity, $name, $name, $value + } # if + + # Prod: defect: Resolution -> Cont: defect: Resolution + if ($name eq "Resolution") { + # There is no "HW Fix" anymore so translating them to "Hw Fix - Board" + if ($value eq "HW Fix") { + $value = "HW Fix - Board"; + } elsif ($value eq "MAC Core") { + $value = "HW Fix - MAC Core"; + }# if + } # if + + # Prod: defect: Software_Version -> Cont: defect: Software_Version + if ($name eq "Software_Version") { + $value = "N/A" if $value eq "" or $value eq " "; + } # if + + # Prod: defect: Title -> Cont: defect: Title + if ($name eq "Title") { + $value = $value ne "" ? $value : ""; + $title = $value; + } # if + + # Prod: defect: SQATestcase -> Cont: defect: PQATestCase + if ($name eq "SQATestCase") { + $name = "PQATestCase"; + $value = "N/A" if $value eq ""; + } # if + + # Prod: defect: Title_2 -> Cont: defect: Title + $name = "Title" if $name eq "Title_2"; + + ## Field deletes + next if $name eq "AttachmentBRCM" or + $name eq "Project_Name" or + $name eq "PendingHWSWReleases" or + $name eq "TestBlocking"; + } # if + + # Check field for non US ASCII characters and fix them + $value = CheckField $dbname, $record_name, $id, $name, $value; + + ## Handle dynamic choice lists + + # While the field name is DeferredToProject, it's corresponding + # Dynamic list name is actually Project + AddToFieldChoiceList $to, $to_entity, "Project", $name, $value if $name eq "DeferredToProject"; + + # While the field name is CommittedToProject, it's corresponding + # Dynamic list name is actually Project + AddToFieldChoiceList $to, $to_entity, "Project", $name, $value if $name eq "CommittedToProject"; + + if ($name eq "HUT") { + $value = "BRCM Copper (do not use)" if $value eq "Broadcom Copper"; + $value = "BRCM Fiber (do not use)" if $value eq "Broadcom Fiber Optic"; + } # if + + AddToFieldChoiceList $to, $to_entity, $name, $name, $value if $name eq "HUT"; + + if ($name eq "HUT_Revision") { + $value = "N/A" + if $value eq "" or + $value eq "\?" or + $value eq "\?\?\?" or + $value eq "A0-A4,B0-B1" or + $value eq "All" or + $value eq "all revisions" or + $value eq "n" or + $value eq "n/" or + $value eq "n\a" or + $value eq "na" or + $value eq "n/a "; + $value = "A0" if $value eq "BCM5752 A0"; + $value = "A1" if $value eq "BCM5752 A1 10x10 package"; + $value = "A2" if $value eq "A2 (A3 Silent)"; + $value = "A3" if $value eq "A3 silent (A2)"; + $value = "B1" if $value eq "B1/A1"; + AddToFieldChoiceList $to, $to_entity, $name, $name, $value; + } # if + + if ($name eq "Service_Pack") { + $value = "Not Applicable" + if $value eq "" or + $value eq "\?" or + $value eq "na" or + $value eq "N/A" or + $value eq "none" or + $value eq "Notice that QA applies to bootcode + Win + Linux d"; + $value = "SP3" if $value eq "SP3 "; + $value = "SP4" if $value eq "SP4 "; + $value = "Suse 9" if $value eq "Suse 9 "; + } # if + + # While the field name is Service_Pack, it's corresponding + # Dynamic list name is actually OS_Service_Pack! + AddToFieldChoiceList $to, $to_entity, "OS_Service_Pack", $name, $value if $name eq "Service_Pack"; + + AddToFieldChoiceList $to, $to_entity, $name, $name, $value if $name eq "Software"; + AddToFieldChoiceList $to, $to_entity, $name, $name, $value if $name eq "Visibility"; + if ($name eq "OS") { + $value = "Novell 6 Pack Beta 3" if $value eq "Novell 6 Pack Beta 3 "; + AddToFieldChoiceList $to, $to_entity, $name, $name, $value if $name eq "OS"; + } # if + + # Set the field's value + $to_entity->SetFieldValue ($name, $value); + } # for + + ## New fields + + # Found_On_Gold: Default to "No" + $to_entity->SetFieldValue ("Found_On_Gold", "No"); + + # Gating_Item_HW: Default to "No" + $to_entity->SetFieldValue ("Gating_Item_HW", "No"); + + # Newly_Introduce: Default to "No" + $to_entity->SetFieldValue ("Newly_Introduce", "No"); + + # Root_Caused: Default to "No" + $to_entity->SetFieldValue ("Root_Caused", "No"); + + # Throw old ID from Prod or TO into old_id. This can then serve + # As a cross reference + $to_entity->SetFieldValue ("old_id", $id); + + # Need to handle attachments differently... + @files_created = TransferAttachments $log, $from_entity, $to_entity; + + # Call the Validate method + my $errmsg = $to_entity->Validate; + + if ($errmsg ne "") { + verbose ""; + $log->err ("\n$id\n$errmsg"); + } else { + # Post record to database + $to_entity->Commit; + $new_id = $to_entity->GetFieldValue ("id")->GetValue; + $log->msg ("-> $new_id"); + } # if + + # Clean up files created by TransferAttachments - if any + foreach (@files_created) { + unlink $_; + } # foreach + + # Clean up files created by TransferHistory + unlink $history_filename; + + # Transfer State: The entity we just created is now in the + # Assigned state. But that's not the same as the state of the + # original entity. The following code attempts to fix this. + my $old_state = $from_entity->GetFieldValue ("State")->GetValue; + + TransferState $log, $record_name, $to, $new_id, $old_state; + } # while + + $| = $old_bufffer_status; # Restore buffering + + return $new_id; +} # TransferDefects + +while ($ARGV [0]) { + if ($ARGV [0] eq "-v") { + Display::set_verbose; + Logger::set_verbose; + } elsif ($ARGV [0] eq "-d") { + set_debug; + } elsif ($ARGV [0] eq "-id") { + shift; + if (!$ARGV [0]) { + Usage "Must specify ID after -id"; + } else { + $id = $ARGV [0]; + } # if + } elsif ($ARGV [0] eq "-from") { + shift; + if (!$ARGV [0]) { + Usage "Must specify after -from"; + } else { + $from_db_connection_name = $ARGV [0]; + } # if + } elsif ($ARGV [0] eq "-to") { + shift; + if (!$ARGV [0]) { + Usage "Must specify after -to"; + } else { + $to_db_connection_name = $ARGV [0]; + } # if + } elsif ($ARGV [0] eq "-u") { + Usage; + } else { + Usage "Unknown argument found: " . $ARGV [0]; + } # if + + shift (@ARGV); +} # while + +my $log = Logger->new (path => "."); + +my $process_start_time = time; +my $start_time; + +$log->msg ("Starting Cont session"); +my $controller = StartSession "Cont", $to_db_connection_name; + +my $do_prod = 1; +my $do_teton = 1; +my $current_id; +my $record_name = "defect"; + +if ($do_prod) { + $log->msg ("Starting Prod session"); + my $prod = StartSession ("Prod", $from_db_connection_name); + + $log->msg ("Transferring Prod:defect -> Cont:defect"); + $start_time = time; + $current_id = TransferDefects $log, $prod, $controller, "Prod", $record_name, $id, @old_Prod_defect_fields; + $log->msg ("Completed transfer of Prod:defect records"); + display_duration $start_time, $log; + + $log->msg ("Ending Prod session"); + EndSession $prod; +} # if + +if ($do_teton) { + $log->msg ("Starting TO session"); + my $teton = StartSession "TO", $from_db_connection_name; + + $log->msg ("Transferring TO:defect -> Cont:defect"); + $start_time = time; + + if (!defined $id) { + my $current_id_nbr = substr $current_id, 4, 8; + # Start numbering TO at 20000 + BurnIDsTil $log, $controller, $record_name, $current_id_nbr, "20000"; + } # if + TransferDefects $log, $teton, $controller, "TO", $record_name, $id, @old_TO_defect_fields; + $log->msg ("Completed transfer of TO:defect records"); + display_duration $start_time, $log; + + $log->msg ("Ending TO session"); + EndSession $teton; +} # if + +$log->msg ("Ending Cont session"); +EndSession $controller; + +verbose "Total processing time:"; +display_duration $process_start_time, $log; diff --git a/cvsbin/cvsims b/cvsbin/cvsims new file mode 100644 index 0000000..1c3effe --- /dev/null +++ b/cvsbin/cvsims @@ -0,0 +1,244 @@ +#!/usr/local/bin/perl5.8.4 +################################################################################ +# +# File: cvsims,v +# Revision: 1.1.1.1 +# Author: Andrew@DeFaria.com +# Description: This script will read CVS commit information searching for Issue +# IDs and formulate a "change set" including the files committed +# and update IMS. +# Created: Fri Dec 9 15:10:56 PST 2005 +# Modified: 2007/05/17 07:45:48 +# Language: perl +# +# (c) Copyright 2005, Andrew@DeFaria.com, all rights reserved +# +################################################################################ +use strict; +use warnings; +use File::Spec; + +my $me; +my $version = "1.0"; + +BEGIN { + # Set $lib_path + my $lib_path = $^O =~ /MSWin/ ? "\\\\brcm-irv\\dfs\\projects\\ccase\\SCM\\lib" + : "/projects/ccase/SCM/lib"; + + # Extract relative path and basename from script name. + $0 =~ /(.*)[\/\\](.*)/; + + my $abs_path = (!defined $1) ? "." : File::Spec->rel2abs ($1); + $me = (!defined $2) ? $0 : $2; + $me =~ s/\.pl$//; + + # Add the appropriate path to our modules to @INC array. + unshift @INC, $ENV {SITE_PERL_LIBPATH} if defined $ENV {SITE_PERL_LIBPATH}; + unshift @INC, "$lib_path"; + unshift @INC, "$abs_path"; +} # BEGIN + +use IMS; +use Display; + +sub Usage { + my $msg = shift; + + display "ERROR: $msg\n" if defined $msg; + + display "Usage: $me\t[-u] [-v] [-d] [-passthru] +\t\t[{-pre -logfile } | +\t\t {-post -repository -path +\t\t { ...}}] + +Where: + + -u: Display usage + -v: Turn on verbose mode + -d: Turn on debug mode + -passthru: Passthru stdin to stdout + +Pre options: + -pre: Perform pre commit checking. + -logfile Path to logfile containing commit message + +Post options: + -post: Perform post commit updating + -repository: Repository (path portion of \$CVSROOT) + -path: Path relative to repository + : Files and revisions commited (Format: \"file rev file rev ...\") +"; + exit 1; +} # Usage + +sub UpdateIMS { + my $issueid = shift; + my $repository = shift; + my $path = shift; + my %filerevs = @_; + + my $change_set; + + foreach (sort (keys (%filerevs))) { + $change_set .= "${filerevs {$_}}\t$repository/$path/$_\n"; + } # foreach + + my $error = AddToChangeSet ($issueid, $change_set); + + return $error; +} # UpdateIMS + +sub ParseInput { + my @input = @_; + + my $sentinal = "csp: "; + my %issue_ids; + my @issue_ids; + # Issue id information in the input must have the following format: + # + # $sentinal , {n} + foreach my $line (@input) { + if ($line =~ /^$sentinal/i) { + # Remove $sentinal + $line = substr $line, length ($sentinal); + # Remove and commas + $line =~ tr /,/ /; + @issue_ids = split /\s+/, $line; + last; + } # if + } # foreach + + # Eliminate duplicates on return + return grep (!$issue_ids {$_}++, @issue_ids); +} # ParseInput + +sub ReadFile { + my $file = shift; + my $passthru = shift; + + my @lines = <$file>; + + my @cleansed_lines; + + foreach (@lines) { + print $_ if $passthru; + chomp; + chop if /\r/; + push @cleansed_lines, $_ if !/^#/; # Discard comment lines + } # foreach + + return @cleansed_lines; +} # ReadFile + +my $optkind; +my $repository; +my $path; +my $passthru = 0; +my $logfile; +my %filerevs; + +while ($ARGV [0]) { + if ($ARGV [0] eq "-v") { + Display::set_verbose; + } elsif ($ARGV [0] eq "-d") { + set_debug; + } elsif ($ARGV [0] eq "-passthru") { + $passthru = 1; + } elsif ($ARGV [0] eq "-repository") { + shift @ARGV; + if (!$ARGV [0]) { + Usage "Must specify repository after -repository"; + } else { + $repository = $ARGV [0]; + } # if + } elsif ($ARGV [0] eq "-path") { + shift @ARGV; + if (!$ARGV [0]) { + Usage "Must specify path after -path"; + } else { + $path = $ARGV [0]; + } # if + } elsif ($ARGV [0] eq "-pre") { + $optkind = "pre"; + } elsif ($ARGV [0] eq "-logfile") { + shift @ARGV; + if (!$ARGV [0]) { + Usage "Must specify log filename after -logfile"; + } else { + $logfile = $ARGV [0]; + } # if + } elsif ($ARGV [0] eq "-post") { + $optkind = "post"; + } elsif ($ARGV [0] eq "-u") { + Usage; + } else { + %filerevs = @ARGV; + last; + } # if + + shift (@ARGV); +} # while + +Usage "Must specify -pre or -post" if !defined $optkind; + +if ($optkind eq "pre") { + Usage "No logfile to parse" if !defined $logfile; +} elsif ($optkind eq "post") { + Usage "No files committed" if !%filerevs; +} # if + +my @issue_ids; + +if ($optkind eq "pre") { + verbose "$me v$version: Checking for Issue ID(s)..."; + open LOGFILE, $logfile + or error "Unable to open logfile $logfile - $!", 4; + + @issue_ids = ParseInput ReadFile (*LOGFILE); + + close LOGFILE; +} else { + # Special case here. Seems cvs will call this script through loginfo + # even though directories are not versioned in cvs. If so then + # %filerevs will contain "- New directory" and "NONE". In this case + # we simply exit 0. + if (defined $filerevs {"- New directory"}) { + exit 0; + } # if + + verbose "$me v$version: Updating Issue ID(s)..."; + @issue_ids = ParseInput ReadFile (*STDIN, $passthru); +} # if + +if (scalar (@issue_ids) eq 0) { + error "No issue ID(s) found", 1; +} # if + +verbose "Verifying Issue IDs"; + +foreach (@issue_ids) { + my %issue_info = GetIssue $_; + + if (%issue_info) { + verbose "Issue $_ exists in IMS"; + } else { + error "Issue ID $_ does not exist in IMS", 2 if !%issue_info; + } # if +} # foreach + +if ($optkind eq "post") { + foreach my $issue_id (@issue_ids) { + verbose "Updating Issue ID ${issue_id}'s Change Set"; + + my $error = UpdateIMS $issue_id, $repository, $path, %filerevs; + + if ($error ne "") { + error $error, 3; + } else { + verbose "Issue ID ${issue_id}'s Change Set updated"; + } # if + } # foreach +} # if + +exit 0; diff --git a/ecrc/ecrc b/ecrc/ecrc new file mode 100644 index 0000000..7e47a9f --- /dev/null +++ b/ecrc/ecrc @@ -0,0 +1,177 @@ +#!/usr/bin/perl +################################################################################ +# +# File: ecrc: ECR client +# Description: This script is a test client for ecrd. +# Author: Andrew@DeFaria.com +# Created: Tue Feb 15 11:01:24 PST 2005 +# Modified: +# Language: Perl +# +# (c) Copyright 2005, LynuxWorks, all rights reserved. +# +################################################################################ +use strict; +use warnings; +use File::Spec; + +my ($me, $abs_path, $lib_path, $bin_path, $log_path); + +BEGIN { + # Extract relative path and basename from script name. + $0 =~ /(.*)[\/\\](.*)/; + + $abs_path = (!defined $1) ? "." : File::Spec->rel2abs ($1); + $me = (!defined $2) ? $0 : $2; + $me =~ s/\.pl$//; + + # Setup paths + $bin_path = "$abs_path"; + $lib_path = "$abs_path/../lib"; + $log_path = "$abs_path/../log"; + + # Add the appropriate path to our modules to @INC array. + unshift (@INC, "$lib_path"); +} # BEGIN + +use ecrc; + +# Global variables +my $servername = (!defined $ENV {ECRDSERVER}) ? "lynx12" : $ENV {ECRDSERVER}; +my $port = (!defined $ENV {ECRDPORT}) ? 1500 : $ENV {ECRDPORT}; +my $ecr = ""; +my @query_fields = (); +my $verbose; +my $debug; +my $key; +my $value; +my %fields; +my @ecrs; + +sub Usage { + my $msg = shift; + + print "ERROR: $msg\n\n" if defined $msg; + + print "Usage: ecrc [-u] [-v] [-d] [ -s <server> ] [ -p <port> ] "; + print "ECR [ fieldname... ]\n"; + print "\nWhere:\n\n"; + print "\t-u:\t\tDisplay usage\n"; + print "\t-v:\t\tTurn on verbose mode (Default off)\n"; + print "\t-d:\t\tTurn on debug mode (Default off)\n"; + print "\t-s:\t\tUse server named servername (Default lynx12)\n"; + print "\t-s:\t\tUse port (Default 1500)\n"; + print "\tECR:\t\tECR number to obtain info about\n"; + print "\tfieldname:\tECR field names to retrieve info about (Default all)\n"; + + exit 1; +} # Usage + +sub GetParms { + while ($ARGV [0]) { + if ($ARGV [0] eq "-v") { + $verbose = 1; + ecrc::set_verbose; + } elsif ($ARGV [0] eq "-d") { + $debug = 1; + ecrc::set_debug; + } elsif ($ARGV [0] eq "-u") { + Usage; + } elsif ($ARGV [0] eq "-p") { + shift @ARGV; + Usage "Port not specified" if !$ARGV [0]; + $port = shift @ARGV; + } elsif ($ARGV [0] eq "-s") { + shift @ARGV; + Usage "Server name not specified" if !$ARGV [0]; + $servername = shift @ARGV; + } else { + $ecr = shift (@ARGV); + last; + } # if + shift @ARGV; + } # while + + @query_fields = @ARGV; + + # Downshift any query_fields + my $i = 0; + + foreach (@query_fields) { + $query_fields [$i++] = lc $_; + } # foreach +} # GetParms + +# Main code +GetParms; + +die "Unable to connect to $servername:$port\n" if !ecrc::Connect ($servername, $port); + +if ($ecr) { + if ($ecr eq "\*") { + @ecrs = ecrc::GetECRRecord $ecr; + + foreach (@ecrs) { + print "$_\n"; + } # foreach + + exit; + } # if + + %fields = ecrc::GetECRRecord ($ecr); + + if (!%fields) { + print "ECR $ecr was not found\n"; + } else { + if (@query_fields) { + foreach (@query_fields) { + if (@query_fields > 1) { + if (defined $fields{$_}) { + print "$_: $fields{$_}\n"; + } else { + print "$_: \n"; + } # if + } else { + if (defined $fields{$_}) { + print "$fields{$_}\n"; + } else { + print "$_: \n"; + } # if + } # if + } # foreach + } else { + while (($key, $value) = each (%fields)) { + print "$key: $value\n"; + } # while + } # if + } # if +} else { + print "Enter ECR:"; + + while (my $command = ) { + chomp $command; + last if $command =~ m/exit|quit/i; + + $ecr = $command; + + if ($ecr eq "\*") { + my @ecrs = ecrc::GetECRRecord $ecr; + + foreach (@ecrs) { + print "$_\n"; + } # foreach + } else { + %fields = ecrc::GetECRRecord $ecr; + + if (!%fields) { + print "ECR $ecr was not found\n"; + } else { + while (($key, $value) = each (%fields)) { + print "$key: $value\n"; + } # while + } # if + } # if + + print "Enter ECR:"; + } # while +} # if diff --git a/ecrc/ecrc.php b/ecrc/ecrc.php new file mode 100644 index 0000000..f19eeeb --- /dev/null +++ b/ecrc/ecrc.php @@ -0,0 +1,182 @@ +disconnect (); + } // if +} // Disconnect + +function GetECRRecord ($ecr) { + global $ecrserver; + + $fields; + + debug ("ENTER GetECRRecord ($ecr)"); + if (!$ecrserver) { + verbose ("Not connected to server yet!"); + verbose ("Attempting connection to $default_server..."); + if (!Connect (SERVER)) { + print "Unable to connect to server ". SERVER . "<br>"; + exit (1); + } // if + } // if + + SendServerCmd ($ecrserver, $ecr); + GetServerAck ($ecrserver); + + if ($ecr == "*") { + verbose ("Getting all ECRs"); + $fields = GetServerList ($ecrserver); + } else { + verbose ("Getting specific ECR $ecr"); + $fields = GetServerResponse ($ecrserver); + } // if + + SendServerAck ($ecrserver); + + return $fields; +} // GetECRRecord + +function Shutdown () { + global $command; + + verbose ("Sending disconnect command to server"); + $command = "quit"; + Disconnect (); +} // Shutdown + +function ConnectToServer ($host, $port = 1500) { + $socket = new Net_Socket (); + + debug ("Socket created... Attempting to connect to $host:$port"); + // create a tcp connection to the specified host and port + if (@$socket->connect ($host, $port) == 1) { + verbose ("Socket $socket connected"); + } else { + print "Unable to connect to server $host:$port!<br>"; + exit (1); + } // if + + return $socket; +} // ConnectToServer + +function SendServerAck ($server) { + $server->write ("ACK" . "\n"); +} // SendServerAck + +function GetServerAck ($server) { + while ($srvresp = $server->readLine ()) { + if ($srvresp == "ACK") { + return; + } // if + verbose ("Received $srvresp from server - expected ACK"); + } // while +} // GetServerAck + +function GetServerList ($server) { + $ecrs = array (); + + while ($srvresp = $server->readLine ()) { + if ($srvresp == "ACK") { + break; + } // if + + if (preg_match ("/ECR.*was not found/", $srvresp)) { + return; + } else { + array_push ($ecrs, $srvresp); + } // if + } // while + + return $ecrs; +} # GetServerList + +function GetServerResponse ($server) { + $fields; + + while ($srvresp = $server->readLine ()) { + if ($srvresp == "ACK") { + break; + } // if + + if (preg_match ("/ECR.*was not found/", $srvresp)) { + return; + } else { + preg_match ("/(^\w+):\s+(.*)/s", $srvresp, $matches); + $value = str_replace ("\\n", "\n", $matches [2]); + $fields {$matches [1]} = $value; + } // if + } // while + + return $fields; +} // GetServerResponse + +function SendServerCmd ($server, $command) { + $server->write ($command . "\n"); +} // SendServerCmd +?> \ No newline at end of file diff --git a/ecrc/ecrd b/ecrc/ecrd new file mode 100644 index 0000000..40e4057 --- /dev/null +++ b/ecrc/ecrd @@ -0,0 +1,564 @@ +#!/usr/bin/perl +################################################################################ +# +# File: ecrd: ECR Daemon +# Description: This script implements a daemon that handles requests for +# queries about information on ECRs contained in the Quintus +# database. In addition to lessoning the amount of time it takes +# for database opens, access to Quintus data is only available +# on certain machines. Additionally, for Perl to access this +# Informix database the Informix version of DBD would need to be +# locally installed. By calling this daemon instead clients need +# not have to install Informix and then code all the necessary +# code to access Quintus data as well as have to understand the +# structure of the database. Instead clients need only say "Give +# me what you got on ECR #". +# Author: Andrew@DeFaria.com +# Created: Tue Feb 15 09:54:59 PST 2005 +# Modified: +# Language: Perl +# +# (c) Copyright 2005, LynuxWorks, all rights reserved. +# +################################################################################ +use strict; +use warnings; + +use IO::Socket; +use Net::hostent; +use POSIX qw(setsid); +use DBI; + +my $ecrdb = "lynxmigr1"; +my $port = (!defined $ENV {ECRDPORT}) ? 1500 : $ENV {ECRDPORT}; + +# Global variables +my $DB; +my $ecrserver; +my $ecrclient; +my $sth; +my $statement; + +# Options +my $verbose; +my $debug; +my $daemon_mode; +my $quiet_mode; +my $multithreaded; +my $timeout = 10; + +# ECR translations. Note the Quintus database stores certain choice lists as +# enumerations. They I guess they are configurable. The right thing to do +# would be to figure out how to look up the definition given the number. But +# we are gonna cheat here and hard code a few important enumerations. +my @defstatus = ( + "Open", + "Closed", + "Fixed", + "Not a bug", + "Obsolete", + "Defered", + "Duplicate" +); +my @state = ( + "Reported", + "Assigned", + "Selected", + "Resolved", + "Integrated", + "Retired", + "Reviewed", + "Pending Review" +); +my @priority = ( + "Low", + "Medium", + "High", + "Critical" +); +my @severity = ( + "Low", + "Medium", + "High", + "Critical" +); + +# Pid +my $pid = $$; + +my $me = `basename $0`; +chomp $me; +my $ecrdversion = "1.3"; + +my @all_fields = ( + "productdefect", # integer + "componentdefect", # integer + "defectdefectdup", # integer + "workgroupdefect", # integer + "reporterdefect", # integer + "resolverdefect", # integer + "confirmerdefect", # integer + "buildversdefect", # integer + "rpt_versdefect", # integer + "res_versdefect", # integer + "conf_versdefect", # integer + "state", # integer + "resolverstatus", # integer + "confirmerstatus", # integer + "escstatus", # integer + "owner", # integer + "severity", # integer + "priority", # integer + "summary", # varchar(80,0) + "datereported", # datetime year to second + "dateresolved", # datetime year to second +# "description", # text +# Note: Some descriptions fields are huge containing things like +# uuencoded tar files! They are so huge that they cause this server +# to fail (not sure why - it shouldn't but it does. So this hack +# returns only the first 50K of description to avoid that problem. + "description [1,50000]", # text + "cclist", # varchar(80,0) + "dateconfirmed", # datetime year to second + "datemodified", # datetime year to second + "fix_by_date", # date + "fix_by_version", # integer + "history", # text + "likelihood", # integer + "estfixtime", # datetime year to second + "actfixtime", # datetime year to second + "resolution", # text + "businessimpact", # integer + "origin", # integer + "docimpact", # integer + "report_platform", # integer + "resolve_platform", # integer + "confirm_platform", # integer + "test_file", # varchar(64,0) + "visibility", # integer + "misc", # varchar(80,0) + "defecttype", # integer + "defstatus", # integer + "customertext", # text + "modifiedby", # varchar(20,0) + "classification", # integer + "datefixed" # datetime year to second +); + +# Forwards +sub CloseDB; +sub GetRequest; + +sub timestamp { + my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(time); + + $mday = "0$mday" if $mday < 10; + $mon = "0$mon" if $mon < 10; + $hour = "0$hour" if $hour < 10; + $min = "0$min" if $min < 10; + $year += 1900; + + return "$mon/$mday/$year $hour:$min"; +} # timestamp + +sub log_message { + print "[$pid] " . timestamp . " @_\n" if defined $verbose; +} # log_message + +sub log_error { + print STDERR "[$pid] " . timestamp . " ERROR: @_\n" +} # log_error + +sub log_warning { + print STDERR "[$pid] " . timestamp . " WARNING: @_\n" +} # log_error + +sub debug { + print "[$pid] " . timestamp . " @_\n" if defined $debug; +} # debug + +sub verbose { + print "[$pid] " . timestamp . " @_\n" if !defined $quiet_mode; +} # verbose + +sub DBError { + my $msg = shift; + my $statement = shift; + + if (!defined $DB) { + print "Catostrophic error: DB undefined!\n"; + exit 1; + } # if + + print $msg . "\nError #" . $DB->err . " " . $DB->errstr . "\n"; + print "SQL Statement: $statement\n" if defined $statement; + + exit $DB->err; +} # DBError + +sub timeout { + debug "After $timeout seconds of inactivity client timed out"; + + my $hostinfo = gethostbyaddr ($ecrclient->peeraddr); + my $host = $hostinfo->name || $ecrclient->peerhost; + debug "Closing connection to $host"; + + # Close client's connection + close $ecrclient; + + # Set up signal handlers again + $SIG{ALRM} = \&timeout; + $SIG{INT} = $SIG{QUIT} = 23234 +\&interrupt; + GetRequest; +} # timeout + +sub interrupt { + log_warning "Interrupted - closing down..."; + close $ecrserver; + verbose "Connection closed"; + CloseDB; + + exit; +} # interrupt + +sub GetClientAck { + my $client = shift; + my $clientresp; + + debug "ENTER: GetClientAck"; + alarm $timeout; + while (defined $client and defined ($clientresp = <$client>)) { + chomp $clientresp; + chop $clientresp if $clientresp =~ /\r/; + if ($clientresp eq "ACK") { + return + } # if + log_warning "Received $clientresp from client - expected ACK"; + } # while + debug "EXIT: GetClientAck"; +} # GetClientAck + +sub GetClientCmd { + my $client = shift; + my $clientresp; + + alarm $timeout; + while (defined $client and defined ($clientresp = <$client>)) { + chomp $clientresp; + return $clientresp; + } # while +} # GetClientResponse + +sub SendClientAck { + my $client = shift; + + debug "ENTER: SendClientAck"; + print $client "ACK\n"; + debug "EXIT: SendClientAck"; +} # SendClientAck + +sub SendClientResponse { + my $client = shift; + my $response = shift; + + print $client "$response\n"; +} # SendClientResponse + +sub EnterDaemonMode { + my $logfile = shift; + my $errorlog = shift; + + $logfile = "/dev/null" if $logfile eq ""; + $errorlog = "/dev/null" if $errorlog eq ""; + + # Change the current directory to / + chdir '/' + or die "$me: Error: Can't chdir to / ($!)"; + + # Turn off umask + umask 0; + + # Redirect STDIN to /dev/null + open STDIN, '/dev/null' + or die "$me: Error: Can't redirect /dev/null ($!)"; + + # Redirect STDOUT to logfile + open STDOUT, ">>$logfile" + or die "$me: Error: Can't redirect stdout to $logfile ($!)"; + + # Redirect STDERR to errorlog + open STDERR, ">>$errorlog" + or die "$me: Error: Can't redirect stderr to $errorlog ($!)"; + + # Now fork the daemon + defined (my $pid = fork) + or die "$me: Error: Can't create daemon ($!)"; + + # Now the parent exits + exit if $pid; + + # Set process to be session leader + setsid + or die "$me: Error: Can't start a new session ($!)"; +} # EnterDaemonMode + +sub OpenDB { + # Connect to database. Note this is using anonymous access (read only) + $DB = DBI->connect("DBI:Informix:$ecrdb") + or DBError "Unable to open database"; + log_message "Opened $ecrdb database"; + + # Setup our select statement with placeholders + $statement = "select "; + + # Build up the field list + my $first_time = 1; + foreach (@all_fields) { + if ($first_time) { + $first_time = 0; + $statement .= $_; + } else { + $statement .= ",$_"; + } # if + } # foreach + + # Now add the table and condition + $statement .= " from defect where pkey=?"; + + $sth = $DB->prepare ($statement) + or DBError "Unable to prepare statement", $statement; +} # OpenDB + +sub CloseDB { + $DB->disconnect () + or DBError "Unable to disconnect from database!"; + verbose "Closed $ecrdb database"; +} # CloseDB + +sub Usage { + my $msg = shift; + + print "$msg\n\n" if defined $msg; + + print "Usage: $me [ -D ] [ -v ] [ -d ] [-p ] [ -m ] [ -q ]\n\n"; + print "Where:\t-D\tEnter Daemon mode\n"; + print "\t-v\tVerbose mode (Default off)\n"; + print "\t-d\tDebug mode (Default off)\n"; + print "\t-p\tPort number to use (Default 1500)\n"; + print "\t-m\tMultithreaded (Default off)\n"; + print "\t-q\tQuiet mode (Default on)\n"; + exit 1; +} # Usage + +sub GetECRRecord { + my $ecr = shift; + + if ($ecr =~ /\D/) { + log_error "ECR $ecr is not numeric!"; + return (); + } # if + + my %fields; + my $record; + my $value; + + $sth->execute ($ecr) + or DBError "Unable to execute statement", $statement; + + my $row = $sth->fetchrow_arrayref; + + if (!defined $row) { + # @row is empty if there was no ECR by that number + log_error "ECR $ecr not found!"; + return (); + } # if + + my @rows = @{$row}; + foreach (@all_fields) { + my $value = shift @rows; + + # Transform newlines to "\n" so the field is treated as one large field + $value =~ s/\n/\\n/g if defined $value; + + # Perform some choice list field translations. Again this would be + # better done by doing database lookups to translate the enums... + $value = $defstatus [$value] if /defstatus/ and defined $value; + $value = $state [$value] if /state/ and defined $value; + $value = $priority [$value] if /priority/ and defined $value; + $value = $severity [$value] if /severity/ and defined $value; + # Fix description field back + if (/^description/) { + $_ = "description"; + } # if + $fields {$_} = $value + } # foreach + + return %fields; +} # GetECRRecord + +sub ServiceClient { + my $ecrclient = shift; + + # Service this client + my $hostinfo = gethostbyaddr ($ecrclient->peeraddr); + my $host = $hostinfo->name || $ecrclient->peerhost; + + verbose "Connect from $host"; + log_message "Waiting for command from $host"; + while () { + GetClientAck ($ecrclient); + $_ = GetClientCmd ($ecrclient); + next unless /\S/; # Skip blank requests + last if /quit|exit/i; + + if (/\*/) { + log_message "$host requests a list of all ECR #'s"; + SendClientAck ($ecrclient); + ReturnAllECRNbrs ($ecrclient); + SendClientAck ($ecrclient); + next; + } # if + + log_message "$host requests information about ECR $_"; + SendClientAck ($ecrclient); + my %fields = GetECRRecord $_; + + if (%fields) { + SendClientResponse ($ecrclient, "ecr: $_"); + while (my ($key, $value) = each (%fields)) { + $value = !defined $value ? "" : $value; + SendClientResponse ($ecrclient, "$key: $value"); + } # while + } else { + SendClientResponse ($ecrclient, "ECR $_ was not found"); + } # if + SendClientAck ($ecrclient); + } # while + + verbose "Closing connection from $host at client's request"; + close $ecrclient; +} # ServiceClient + +sub Funeral { + my $childpid = wait; + $SIG{CHLD} = \&Funeral; + log_message "Child has died" . ($? ? " with status $?" : ""); +} # Funeral + +sub GetRequest { + # Now wait for an incoming request + while ($ecrclient = $ecrserver->accept ()) { + my $hostinfo = gethostbyaddr ($ecrclient->peeraddr); + my $host = $hostinfo->name || $ecrclient->peerhost; + log_message "$host is requesting service"; + if (defined ($multithreaded)) { + my $childpid; + + log_message "Spawning child to handle request"; + + die "$me: ERROR: Can't fork: %!" unless defined ($childpid = fork ()); + + if ($childpid) { + # In parent - set up for clean up of child process + log_message "Parent produced child ($childpid)"; + $SIG{CHLD} = \&Funeral; + log_message "Parent looking for another request to service"; + } else { + # In child process - ServiceClient + $pid = $$; + debug "Child [$pid] has been born"; + ServiceClient ($ecrclient); + log_message "Child finished servicing requests"; + kill ("TERM", $$); + exit; + } # if + } else { + ServiceClient ($ecrclient); + } # if + } # while + + close ($ecrserver); +} # GetRequest + +sub ProcessRequests { + # The subroutine handles processing of requests by using a socket to + # communicate with clients. + $ecrserver = IO::Socket::INET->new ( + Proto => 'tcp', + LocalPort => $port, + Listen => SOMAXCONN, + Reuse => 1 + ); + + die "$me: Error: Could not create socket ($!)\n" unless $ecrserver; + + verbose "ECR DB Server (ecrd V$ecrdversion) accepting clients on port $port"; + + GetRequest; +} # ProcessRequests + +sub ReturnAllECRNbrs { + my $ecrclient = shift; + + my $statement = "select pkey from defect"; + + my $sth = $DB->prepare ($statement) + or DBError "Unable to prepare statement", $statement; + + $sth->execute () + or DBError "Unable to execute statement", $statement; + + log_message "Returning all ECR numbers..."; + while (my @row = $sth->fetchrow_array) { + SendClientResponse ($ecrclient, $row [0]); + } # while + + log_message "All ECR numbers returned"; +} # ReturnAllECRNbrs + +# Start main code +# Reopen STDOUT. +open STDOUT, ">-" or die "Unable to reopen STDOUT\n"; + +# Set unbuffered output +$| = 1; + +while ($ARGV [0]) { + if ($ARGV [0] eq "-D") { + $daemon_mode = 1; + } elsif ($ARGV [0] eq "-v") { + $verbose = 1; + undef ($quiet_mode); + } elsif ($ARGV [0] eq "-d") { + $debug = 1; + undef ($quiet_mode); + } elsif ($ARGV [0] eq "-m") { + $multithreaded = 1; + } elsif ($ARGV [0] eq "-q") { + $quiet_mode = 1; + undef ($verbose); + } elsif ($ARGV [0] eq "-p") { + shift @ARGV; + Usage "Must specify a port # after -p" if (!defined $ARGV [0]); + $port = $ARGV[0]; + } else { + Usage "Unknown parameter found: " . $ARGV[0]; + } # if + + shift @ARGV; +} # while + +my $tmp = (!defined $ENV {TMP}) ? "/tmp" : $ENV {TMP}; +my $ecrd_logfile = "$tmp/$me.log"; +my $ecrd_errfile = "$tmp/$me.err"; + +EnterDaemonMode ($ecrd_logfile, $ecrd_logfile) if defined ($daemon_mode); + +OpenDB; + +# Set up signal handlers +$SIG{ALRM} = \&timeout; +$SIG{INT} = $SIG{QUIT} = \&interrupt; + +ProcessRequests; diff --git a/ecrc/ecrdesc b/ecrc/ecrdesc new file mode 100644 index 0000000..9c70fb1 --- /dev/null +++ b/ecrc/ecrdesc @@ -0,0 +1,65 @@ +#!/usr/bin/perl +################################################################################ +# +# File: ecrdesc +# Description: This script will dump out the description for the ECR #(s) +# passed in. +# Author: Andrew@DeFaria.com +# Created: Fri Jan 7 15:35:13 PST 2005 +# Language: Perl +# +# (c) Copyright 2005, LynxWorks Inc., all rights reserved +# +################################################################################ +use strict; +use warnings; +use DBI; + +my $DB; + +# Called when a database error has occurred +sub DBError { + my $msg = shift; + my $statement = shift; + + print $msg . "\nError #" . $DB->err . " " . $DB->errstr . "\n"; + + if (defined $statement) { + print "SQL Statement: $statement\n"; + } # if + + exit $DB->err; +} # DBError + +# Connect to database. Note this is using anonymous access (read only) +$DB = DBI->connect("DBI:Informix:lynxmigr1") + or DBError "Unable to open database"; + +# Loop through ECR #s from the command line +foreach my $ecr (@ARGV) { + print "ECR #: $ecr\n"; + + my $statement = "select description from defect where pkey=\"$ecr\""; + my $sth = $DB->prepare ($statement) + or DBError "Unable to prepare statement", $statement; + + $sth->execute () + or DBError "Unable to execute statement", $statement; + + # Defect records are unique per pkey (AKA ECR) there for there will + # only be one entry in @row. Also the description is returned as one + # large string. + my @row = $sth->fetchrow_array; + + if (!@row) { + # @row is empty if there was no ECR by that number + print "Nothing found!\n"; + } else { + my $desc = pop @row; + print "Description:\n" . "-" x 80 . "\n" . $desc . "\n" . "-" x 80 . "\n"; + } # if +} # foreach + +$DB->disconnect; + +exit; diff --git a/etc/cq.conf b/etc/cq.conf new file mode 100644 index 0000000..18e8aad --- /dev/null +++ b/etc/cq.conf @@ -0,0 +1,30 @@ +############################################################################### +# +# File: $RCSfile: cq.conf,v $ +# Revision: $Revision: 1.5 $ +# Description: Config file for Clearquest +# Author: Andrew@ClearSCM.com +# Created: Fri Mar 11 17:58:31 PST 2011 +# Modified: $Date: 2013/01/12 18:24:50 $ +# Language: conf +# +# (c) Copyright 2011, ClearSCM, Inc., all rights reserved +# +############################################################################### + +# Common parms +CQ_DATABASE: +CQ_USERNAME: +CQ_PASSWORD: +CQ_DBSET: +CQ_MODULE: + +# Parameters for Client/Server +CQ_SERVER: +CQ_PORT: +CQ_MULTITHREADED: + +# Parameters for REST +CQ_WEBHOST: http://$CQ_SERVER +CQ_BASE_URL: $CQ_WEBHOST/cqweb/oslc +CQ_URI: /cqweb/oslc/repo/$CQ_DBSET/db/$CQ_DATABASE diff --git a/etc/doskey.mac b/etc/doskey.mac new file mode 100644 index 0000000..9cae752 --- /dev/null +++ b/etc/doskey.mac @@ -0,0 +1,57 @@ +ls=dir /w $* +ll=dir /n /-c /q /ta $* +ct=cleartool $* +grep=find "$1" $* +rm=del $* +cp=copy $* +perl=\\rtnlprod02\viewstore\PMO\CM_TOOLS\perl\bin\perl $* +lsvob=cleartool lsvob $* +llvob=cleartool lsvob -long $* +lsview=cleartool lsview $* +llview=cleartool lsview -long $* +lsregion=cleartool lsregion $* +register=cleartool register $* +unregister=cleartool unregister $* +mktag=cleartool mktag $* +rmtag=cleartool rmtag $* +setview=cleartool setview $* +setcs=cleartool setcs $* +edcs=cleartool edcs $* +catcs=cleartool catcs $* +pwv=cleartool pwv $* +startview=cleartool startview $* +endview=cleartool endview $* +killview=cleartool endview -server $* +rmtag=cleartool rmtag $* +mktag=cleartool mktag $* +mkview=cleartool mkview $* +rmview=cleartool rmview $* +lsregion=cleartool lsregion $* +describe=cleartool describe $* +vtree=cleartool lsvtree $* +unco=cleartool unco -rm $* +cdiff=cleartool diff $* +space=cleartool space $* +register=cleartool register $* +unregister=cleartool unregister $* +lslic=clearlicense -product ClearCase +rellic=clearlicense -release -product ClearCase $* +lstype=cleartool lstype $* +lltype=cleartool lstype -long $* +lsbranch=cleartoollstype brtype:$1 +llbranch=cleartool lstype -long brtype:$1 +lslabel=cleartool lstype lbtype:$1 +lllabel=cleartool lstype -long lbtype:$1 +lstrigger=cleartool lstype trtype:$1 +lltrigger=cleartool lstype -long trtype:$1 +lslock=cleartool lslock $* +mt=multitool $* +lspacket=multitool lspacket $* +llpacket=multitool lspacket -long $* +lsreplica=multitool lsreplica -vob $* +llreplica=multitool lsreplica -long -vob $* +lsepoch=multitool lsepoch -vob $* +llepoch=multitool lsepoch -long -vob $* +chepoch=multitool chepoch -vob $* +syncreplica=multitool syncreplica $* + diff --git a/etc/mail.conf b/etc/mail.conf new file mode 100644 index 0000000..0b71810 --- /dev/null +++ b/etc/mail.conf @@ -0,0 +1,15 @@ +############################################################################### +# +# File: $RCSfile: mail.conf,v $ +# Revision: $Revision: 1.2 $ +# Description: Config file for Mail.pm +# Author: Andrew@DeFaria.com +# Created: Wed Aug 1 09:16:42 MST 2007 +# Modified: $Date: 2010/12/15 23:42:33 $ +# Language: conf +# +# (c) Copyright 2007, ClearSCM, Inc., all rights reserved +# +############################################################################### +SMTPHOST: defaria.com +SMTPFROM: Andrew DeFaria \ No newline at end of file diff --git a/etc/triggers.dat b/etc/triggers.dat new file mode 100644 index 0000000..82c81d4 --- /dev/null +++ b/etc/triggers.dat @@ -0,0 +1,80 @@ +# Triggers +################################################################################ +# +# File: triggers.dat +# Description: Describes the triggers to be implemented. +# Author: Andrew@DeFaria.com +# Created: Mon Mar 15 08:48:24 PST 2004 +# Language: None +# +# (c) Copyright 2004, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +# +# Only the following keywords are currently recognized: +# +# Trigger: Introduces the trigger and gives it its name +# Description: Used for the trigger type's comment +# Type: Type of trigger (so far they're all -element -all) +# Opkinds: Operation kinds that will cause the trigger to fire +# ScriptEngine: Currently only supporting ccperl (C:\Program +# Files\Rational\ClearCase\bin\ccperl) +# Script: Script to run (triggers) +# Vobs: Can be either base, ucm, all or a list of vob tags. +# If base is specified then the trigger is applied to all +# base Clearcase vobs. If ucm is specified then the trigger +# is applied to all ucm vobs. If all is specified (or if +# Vobs is not present) then the trigger is applied to all +# vobs (base and ucm). Otherwise the value is considered +# a space separated list of vob tags (without the leading +# "\") and the trigger is applied only to those vobs. +# EndTrigger Ends this trigger definition. +# +################################################################################ +WinTriggerPath: \\vob8800sc\view\adefaria_tools\vob\adpscmtools\CCDB\triggers +LinuxTriggerPath: /view/tools_view/vob/adpscmtools/CCDB/triggers + +Trigger: CCDB_STREAM + Description: Updates CCDB when a stream is made, removed, delivered to or rebased + Type: -ucm -all + Opkinds: -postop mkstream,rmstream,deliver_complete,rebase_complete + ScriptEngine: Perl + Script: Stream.pl + Vobs: ucm +EndTrigger + +Trigger: CCDB_BASELINE + Description: Updates CCDB when baselines are made, completed or removed + Type: -ucm -all + Opkinds: -postop mkbl,mkbl_complete,rmbl + ScriptEngine: Perl + Script: Baseline.pl + Vobs: ucm +EndTrigger + +Trigger: CCDB_ACTIVITY + Description: Updates CCDB when activities are made or removed + Type: -ucm -all + Opkinds: -postop mkactivity,rmactivity,chactivity + ScriptEngine: Perl + Script: Activity.pl + Vobs: ucm +EndTrigger + +Trigger: CCDB_ELEMENT_PRE + Description: Updates CCDB when an element's version is changed + Type: -element -all + Opkinds: -preop checkin,uncheckout,rmver + ScriptEngine: Perl + Script: Element.pl + Vobs: base +EndTrigger + +Trigger: CCDB_ELEMENT_POST + Description: Updates CCDB when an element's version is changed + Type: -element -all + Opkinds: -postop checkin,checkout,lnname,rmelem + ScriptEngine: Perl + Script: Element.pl + Vobs: base +EndTrigger diff --git a/functions/common b/functions/common new file mode 100644 index 0000000..f7c69bc --- /dev/null +++ b/functions/common @@ -0,0 +1,42 @@ +#!/usr/bin/bash +################################################################################ +# +# File: common +# Description: Common functions for Korn Shell Scripts +# Author: Andrew@DeFaria.com +# Created: Tue Apr 15 14:20:02 PDT 1997 +# Modified: +# Language: Korn Shell +# +# (c) Copyright 2001, Andrew@DeFaria.com, all rights reserved +# +################################################################################ +adm_fpath=${adm_fpath:-$adm_base/functions} + +case "$(uname -s)" in + HP-UX) + export VENDOR=HP + ;; + + Sun*) + export VENDOR=Sun + ;; + + *) + export VENDOR=Unknown + ;; +esac + +modules="\ + display\ + utils\ + " + +for module in $modules; do + if [ -f "$adm_fpath/$module" ]; then + . "$adm_fpath/$module" + else + echo "Internal Error: Function Module $adm_path/$module not found!" + exit 1 + fi +done diff --git a/functions/date64 b/functions/date64 new file mode 100644 index 0000000..af7cb09 --- /dev/null +++ b/functions/date64 @@ -0,0 +1,175 @@ +#!/bin/ksh +################################################################################ +# +# File: date64 +# Description: Routines to handle the odd date arithmetic for the passwd(4) +# file. +# Author: Andrew@DeFaria.com +# Created: Thu Oct 14 14:40:31 PDT 1999 +# Language: Korn Shell +# +# (c) Copyright 2001, Andrew@DeFaria.com, all rights reserved +# +################################################################################ +# Set me to command name +me=$(basename $0) + +# Set adm_base +adm_base=${adm_base:-$HOME/adm} + +# Set adm_fpath +adm_fpath=${adm_fpath:-$adm_base/functions} + +# Source functions +. $adm_fpath/common + +# Globals +# Set up a the character map/base64 arrays for conversion +base=64 +let index=00; character[$index]="."; let base64[$index]=$base*$index +let index=01; character[$index]="/"; let base64[$index]=$base*$index +let index=02; character[$index]="0"; let base64[$index]=$base*$index +let index=03; character[$index]="1"; let base64[$index]=$base*$index +let index=04; character[$index]="2"; let base64[$index]=$base*$index +let index=05; character[$index]="3"; let base64[$index]=$base*$index +let index=06; character[$index]="4"; let base64[$index]=$base*$index +let index=07; character[$index]="5"; let base64[$index]=$base*$index +let index=08; character[$index]="6"; let base64[$index]=$base*$index +let index=09; character[$index]="7"; let base64[$index]=$base*$index +let index=10; character[$index]="8"; let base64[$index]=$base*$index +let index=11; character[$index]="9"; let base64[$index]=$base*$index +let index=12; character[$index]="A"; let base64[$index]=$base*$index +let index=13; character[$index]="B"; let base64[$index]=$base*$index +let index=14; character[$index]="C"; let base64[$index]=$base*$index +let index=15; character[$index]="D"; let base64[$index]=$base*$index +let index=16; character[$index]="E"; let base64[$index]=$base*$index +let index=17; character[$index]="F"; let base64[$index]=$base*$index +let index=18; character[$index]="G"; let base64[$index]=$base*$index +let index=19; character[$index]="H"; let base64[$index]=$base*$index +let index=20; character[$index]="I"; let base64[$index]=$base*$index +let index=21; character[$index]="J"; let base64[$index]=$base*$index +let index=22; character[$index]="K"; let base64[$index]=$base*$index +let index=23; character[$index]="L"; let base64[$index]=$base*$index +let index=24; character[$index]="M"; let base64[$index]=$base*$index +let index=25; character[$index]="N"; let base64[$index]=$base*$index +let index=26; character[$index]="O"; let base64[$index]=$base*$index +let index=27; character[$index]="P"; let base64[$index]=$base*$index +let index=28; character[$index]="Q"; let base64[$index]=$base*$index +let index=29; character[$index]="R"; let base64[$index]=$base*$index +let index=30; character[$index]="S"; let base64[$index]=$base*$index +let index=31; character[$index]="T"; let base64[$index]=$base*$index +let index=32; character[$index]="U"; let base64[$index]=$base*$index +let index=33; character[$index]="V"; let base64[$index]=$base*$index +let index=34; character[$index]="W"; let base64[$index]=$base*$index +let index=35; character[$index]="X"; let base64[$index]=$base*$index +let index=36; character[$index]="Y"; let base64[$index]=$base*$index +let index=37; character[$index]="Z"; let base64[$index]=$base*$index +let index=38; character[$index]="a"; let base64[$index]=$base*$index +let index=39; character[$index]="b"; let base64[$index]=$base*$index +let index=40; character[$index]="c"; let base64[$index]=$base*$index +let index=41; character[$index]="d"; let base64[$index]=$base*$index +let index=42; character[$index]="e"; let base64[$index]=$base*$index +let index=43; character[$index]="f"; let base64[$index]=$base*$index +let index=44; character[$index]="g"; let base64[$index]=$base*$index +let index=45; character[$index]="h"; let base64[$index]=$base*$index +let index=46; character[$index]="i"; let base64[$index]=$base*$index +let index=47; character[$index]="j"; let base64[$index]=$base*$index +let index=48; character[$index]="k"; let base64[$index]=$base*$index +let index=49; character[$index]="l"; let base64[$index]=$base*$index +let index=50; character[$index]="m"; let base64[$index]=$base*$index +let index=51; character[$index]="n"; let base64[$index]=$base*$index +let index=52; character[$index]="o"; let base64[$index]=$base*$index +let index=53; character[$index]="p"; let base64[$index]=$base*$index +let index=54; character[$index]="q"; let base64[$index]=$base*$index +let index=55; character[$index]="r"; let base64[$index]=$base*$index +let index=56; character[$index]="s"; let base64[$index]=$base*$index +let index=57; character[$index]="t"; let base64[$index]=$base*$index +let index=58; character[$index]="u"; let base64[$index]=$base*$index +let index=59; character[$index]="v"; let base64[$index]=$base*$index +let index=60; character[$index]="w"; let base64[$index]=$base*$index +let index=61; character[$index]="x"; let base64[$index]=$base*$index +let index=62; character[$index]="y"; let base64[$index]=$base*$index +let index=63; character[$index]="z"; let base64[$index]=$base*$index + +integer decimal_date=-1 +base64_date="" + +function date64_char_to_decimal { + debug "ENTER date64_char_to_decimal" + char="$1" + integer i=0 + + while [ $i -lt 64 ]; do + if [ "${character[i]}" = "$char" ]; then + break + fi + let i=i+1 + done + + if [ $i -eq 64 ]; then + print -u2 "$char not found!" + debug "RETURN -1 from date64_char_to_decimal" + return -1 + else + debug "RETURN $i from date64_char_to_decimal" + return $i + fi +} # date64_char_to_decimal + +function date64_to_decimal { + # This function accepts a 2 character "number of weeks" string as defined +in + # passwd(4) under password aging. Specifically this string represents the + # number of weeks since 1/1/1970 that the password was last changed. This + # function will convert this value to a decimal number stored in the +global + # decimal_date. + debug "ENTER date64_to_decimal ($1)" + + # Assume failure + decimal_date=-1 + + datecode="$1" + + first_char=$(print $datecode | sed 's/.$//') + second_char=$(print $datecode | sed 's/^.//') + + date64_char_to_decimal $first_char + integer units_digit=$? + if [ $units_digit -eq 255 ]; then + error "Unable to translate the first_char \"$first_char\"" 0 + debug "RETURN -1 from date64_to_decimal - invalid units digit" + return + fi + + date64_char_to_decimal $second_char + integer tens_digit=$? + if [ $tens_digit -eq 255 ]; then + error "Unable to translate the second_char \"$second_char\"" 0 + debug "RETURN -1 from date64_to_decimal - invalid tens digit" + return + fi + + let decimal_date=tens_digit*64+units_digit + debug "RETURN $decimal_date from date64_to_decimal" +} # date64_to_decimal + +function decimal_to_date64 { + # This function will convert a decimal number representing the number of + # weeks past 1/1/1970 and convert it to the odd base64 format described in + # passwd(4). + integer nbr=$1 + debug "ENTER decimal_to_date64 ($nbr)" + i=63 + + while [ $nbr -lt ${base64[i]} ]; do + let i=i-1 + done + + let nbr=nbr-base64[i] + + tens_digit=${character[i]} + units_digit=${character[nbr]} + base64_date="$units_digit$tens_digit" + debug "RETURN $base64_date from decimal_to_date64 ($nbr)" +} # decimal_to_date64 diff --git a/functions/display b/functions/display new file mode 100644 index 0000000..16c6864 --- /dev/null +++ b/functions/display @@ -0,0 +1,58 @@ +#!/bin/bash +################################################################################ +# +# File: display,v +# Revision: 1.1.1.1 +# Description: Display functions for bash scripts +# Author: Andrew@DeFaria.com +# Created: Tue Apr 15 14:20:02 PDT 1997 +# Modified: 2007/05/17 07:45:48 +# Language: bash +# +# (c) Copyright 1997-2006, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +function display { + echo -e "$@" +} # display + +function display_stderr { + echo -e "$@" 2> /dev/stderr +} # display_stderr + +function error { + declare -i errornbr=$2 + + if [ $errornbr -ne 0 ]; then + display_stderr "$me: Error: $1 (Error: $2)" + exit $errornbr + else + display_stderr "$me: Error: $1" + fi +} # error + +function warning { + declare -i warningnbr=$2 + + if [ $warningnbr -eq 0 ]; then + display_stderr "$me: Warning: $1" + else + display_stderr "$me: Warning: $1 (Warning: $2)" + fi +} # warning + +function info { + display "$me: Info: $@" +} # info + +function verbose { + if [ ! -z "$verbose" ]; then + display "$@" + fi +} # verbose + +function debug { + if [ ! -z "$debug" ]; then + display_stderr "$@" + fi +} # debug diff --git a/functions/logs b/functions/logs new file mode 100644 index 0000000..5a4e7ff --- /dev/null +++ b/functions/logs @@ -0,0 +1,31 @@ +#!/bin/ksh +################################################################################ +# +# File: logs +# Description: Functions for handling log files +# Author: Andrew@DeFaria.com +# Created: Thu Dec 9 10:05:09 PST 1999 +# Modified: +# Language: Korn Shell +# +# (c) Copyright 2001, Andrew@DeFaria.com, all rights reserved +# +################################################################################ +function roll_log { + # roll_log: This function will roll a logfile + # + # Arguments: + # $1 Directory for log file + # $2 Name of log file + # $3 Name of backup log file + # $4 What activity is being logged + logdir=$1 + logfile=$dir/$2 + backup_logfile=$dir/$3 + what=$4 + + if [ -f "$logfile" ]; then + cat $logfile >> $backup_logfile + print "$what log ($(hostname)) started on $(date)" > $logfile + fi +} # roll_log diff --git a/functions/tmpfiles b/functions/tmpfiles new file mode 100644 index 0000000..4d88874 --- /dev/null +++ b/functions/tmpfiles @@ -0,0 +1,50 @@ +#!/bin/ksh +################################################################################ +# +# File: tmpfiles +# Description: Routines for handling temp files +# Author: Andrew@DeFaria.com +# Created: Tue Apr 15 14:20:02 PDT 1997 +# Modified: +# Language: Korn Shell +# +# (c) Copyright 2001, Andrew@DeFaria.com, all rights reserved +# +################################################################################ +# This function will cleanup all temporary files used by the calling script +# providing that they are all prefixed with tmpprefix. +function cleanup { + debug "ENTER $0" + status=$? + if [ ! -z "$tmpprefix" -a $status -eq 0 ]; then + verbose "Cleaning up temp files..." + rm -f ${tmpprefix}* + else + debug "tmpprefix not set or status was not equal to 0 - no temporary +files cleaned!" + fi + + debug "EXIT $0" + exit $status +} # cleanup + +function arm_trap { + debug "ENTER $0" + if [ -z "$tmpprefix" ]; then + warning "The environment variable tmpprefix has not neen set up!\n\ +Temporary files will not be cleaned up automatically!" + else + trap 'trap cleanup EXIT ERR' EXIT + debug "Cleanup will be called on EXIT or ERR signals" + fi + + debug "EXIT $0" +} # arm_trap + +function disarm_trap { + debug "ENTER $0" + + trap 'trap - EXIT ERR' EXIT + debug "Cleanup will not be called on EXIT or ERR signals" + debug "EXIT $0" +} # disarm_trap diff --git a/functions/utils b/functions/utils new file mode 100644 index 0000000..37e5187 --- /dev/null +++ b/functions/utils @@ -0,0 +1,34 @@ +#!/bin/ksh +################################################################################ +# +# File: utils +# Description: Miscellanous utility functions for Korn Shell Scripts +# Author: Andrew@DeFaria.com +# Created: Tue Apr 15 14:20:02 PDT 1997 +# Modified: +# Language: Korn Shell +# +# (c) Copyright 2001, Andrew@DeFaria.com, all rights reserved +# +################################################################################ +if [ -x /usr/xpg4/bin/id ]; then + ID=/usr/xpg4/bin/id +else + ID=/usr/bin/id +fi + +function is_root { + if [ $($ID -u) -eq 0 ]; then + return 0 + else + return 1 + fi +} # is_root + +function is_not_root { + if [ $($ID -u) -eq 0 ]; then + return 1 + else + return 0 + fi +} # is_not_root diff --git a/lib/BinMerge.pm b/lib/BinMerge.pm new file mode 100644 index 0000000..1682a0a --- /dev/null +++ b/lib/BinMerge.pm @@ -0,0 +1,895 @@ +#!/usr/bin/perl +################################################################################ +# +# File: $RCSfile: BinMerge.pm,v $ +# Revision: $Revision: 1.4 $ +# Description: This module will perform a merge checking for any merge +# conflicts and grouping them at the end. This allows the +# majority of a large merge to happen and the user can resolve +# the conflicts at a later time. +# +# This module also assists in performing binary merges for the +# common case. With a binary merge one cannot easily merge the +# binary code. Most often it's a sitatution where the user will +# either accept the source or the destination binary file as +# a whole. In cases where there is only a 2 way merge, this +# script offers the user the choice to accept 1 binary file +# or the other or to abort this binary merge. Binary merges +# conflicts greater than 2 way are not handled. +# +# This was made into a module so that it could be easily called +# from UCMCustom.pl. There is also a corresponding bin_merge +# script which essentially calls this module +# +# Dependencies: This module depends on PerlTk. As such it must be run +# from ccperl or a Perl that has the PerlTk module +# installed. Additionally it uses the Clearcase +# cleartool command which is assumed to be in PATH. +# Author: Andrew@ClearSCM.com +# Created: Thu Nov 3 10:55:51 PST 2005 +# Modified: $Date: 2011/03/10 23:47:31 $ +# Language: perl +# +# (c) Copyright 2005, ClearSCM, Inc. all rights reserved +# +################################################################################ +package BinMerge; + +use strict; +use warnings; + +use base 'Exporter'; +use File::Spec; +use Tk; +use Tk::Dialog; +use OSDep; + +our @EXPORT = qw ( + Merge + Rebase +); + +our ($me); + +BEGIN { + # Extract relative path and basename from script name. + $0 =~ /(.*)[\/\\](.*)/; + + $me = (!defined $2) ? $0 : $2; + $me =~ s/\.pl$//; + + # Remove .pl for Perl scripts that have that extension + $me =~ s/\.pl$//; +} # BEGIN + + use Display; + use Logger; + use OSDep; + + my $version = "1.0"; + my $user = $ENV {USERNAME}; + + my $main; + my $selection_file = "$me.selection.$$"; + + sub ReadFile { + my $filename = shift; + + # Sometimes people foolishly undef $/ + local $/ = "\n"; + + open my $file, '<', $filename + or error "Unable to open $filename ($!)", 1; + + my @lines = <$file>; + + close $file; + + my @cleansed_lines; + + foreach (@lines) { + chomp; + chop if /\r/; + push @cleansed_lines, $_ if !/^#/; # Discard comment lines + } # foreach + + return @cleansed_lines; + } # ReadFile + + sub Error { + my $msg = shift; + + my $err = $main->Dialog ( + -title => "Error", + -text => $msg, + -buttons => [ "OK" ] + ); + + $err->Show; + + return; + } # Error + + sub CheckSelection { + my $list = shift; + + my @entries = $list->curselection; + + if (scalar @entries == 0) { + Error "Nothing selected!"; + return; + } # if + + my $selected = $list->get ($entries [0]); + + # Write selection out to file and exit + open my $file, '>', $selection_file + or die "Unable to open $selection_file\n"; + + print $file "$selected\n"; + + close $file; + + # Close prompt window + $main->destroy; + + return; + } # CheckSelection + + sub Help { + my $text; + + $text = "A merge conflict has been detected between two binary files. "; + $text .= "Please pick the version that you want to be the result of this "; + $text .= "merge.\n\nNote you can pick any of these versions and the result "; + $text .= "will be that that version will be considered the new version "; + $text .= "overwriting the previous version.\n\nIf this is not what you want "; + $text .= "then select the Cancel button and regenerate this binary file "; + $text .= "so that it is the result of what you want for this merge.\n\n"; + $text .= "Copyright ? 2005 - All rights reserved\n"; + $text .= "Andrew DeFaria "; + + my $desc = $main->Dialog ( + -title => "Help", + -text => $text, + -buttons => [ "OK" ] + ); + + $desc->Show; + + return; + } # Help + + sub Cancel { + $main->destroy; + + return; + } # Cancel + + sub VersionTree { + my $file = shift; + + my $cmd = "cleartool lsvtree -graphical $file"; + + if ($^O =~ /mswin|cygwin/i) { + system "start /b $cmd"; + } else { + my $pid = fork; + + return if $pid; + + system $cmd; + exit; + } # if + + return; + } # VersionTree + + # Create a ListBox widget in $parent, dynamically sizing it to the length of + # the longest entry in @list. + sub CreateList { + my ($parent, @list) = @_; + + my $list = $parent->Scrolled ("Listbox", + -scrollbars => "osoe", + -width => 70, + -height => 5, + )->pack; + + # Insert entries from @list into the new ListBox, $list + foreach (@list) { + $list->insert ("end", $_); + } # foreach + + $list->pack; + + return $list; + } # CreateList + + sub CreateButtons { + my ($parent, $list, $file) = @_; + my $one = $parent->Frame->pack (-side => "left", -pady => 2, -padx => 2); + my $two = $parent->Frame->pack (-side => "left", -pady => 2, -padx => 2); + my $three = $parent->Frame->pack (-side => "left", -pady => 2, -padx => 2); + my $four = $parent->Frame->pack (-side => "left", -pady => 2, -padx => 2); + + my $ok = $one->Button ( + -text => "OK", + -command => [ \&CheckSelection, $list ] + )->pack; + + my $cancel = $two->Button ( + -text => "Cancel", + -command => [ \&Cancel ] + )->pack; + + my $help = $three->Button ( + -text => "Help", + -command => \&Help + )->pack; + + my $vtree = $four->Button ( + -text => "Version Tree", + -command => [ \&VersionTree, $file ] + )->pack; + + return; + } # CreateButtons + + sub PromptUser { + my ($element, @versions) = @_; + + debug "ENTER: PromptUser"; + + # Create main window + $main = MainWindow->new; + + # Title window + $main->title ("Resolve merge conflict for binary element"); + + # Create the main window using containers + my $top = $main->Frame->pack (-side => "top", -fill => "x"); + my $prompt = $top->Frame->pack (-side => "left", -pady => 5, -padx => 5); + my $list = $main->Frame->pack (-side => "left"); + my $buttons = $list->Frame->pack (-side => "bottom"); + + # Label it + my $prompt_str = <<"END"; +A binary merge conflict has been detected between two versions of + +$element + +Please pick the version that you want to be the result of this merge. Note you +can pick any of these versions and the result will be that that version will be +considered the new version overwriting the previous version. If this is not what +you want then select the Cancel button here and regenerate this binary file so +that it is the result of what you want for this merge. +END + + $prompt->Message (-text => $prompt_str, -width => 500)->pack; + + my $version_list = CreateList $list, @versions; + + CreateButtons $buttons, $version_list, $element; + + # Make sure the window pops to the top + # Trying really hard... :-) + $main->update; + $main->deiconify; + $main->update; + $main->raise; + $main->update; + $main->focusForce; + $main->update; + + MainLoop; + + open my $result, '<', $selection_file + or return; + + my @lines = <$result>; + + close $result; + + unlink $selection_file; + + if (@lines) { + chomp $lines[0]; + return $lines[0]; + } else { + return; + } # if + + return; + } # PromptUser + + # The merging of directories could, in theory, unearth other elements inside + # those directories thus causing further merging. Here we keep merging + # directories until there are no directories to merge. + sub MergeDirectories { + my ($log, $path, $branch) = @_; + + my $cmds = "$me.$$.cmds"; + my $cmd = "cleartool findmerge $path -nc -type d -fversion $branch " . + "-log $cmds -print > $NULL 2>&1"; + + debug "ENTER: MergeDirectories (, $path, $branch)"; + + my @lines; + + while () { + $log->msg ("Searching for directories that need merging..."); + + debug "Performing: $cmd"; + + my $status = $log->logcmd ($cmd); + + return $status if $status != 0; + + @lines = ReadFile $cmds; + + last if scalar @lines == 0; + + $log->msg ("Performing directory merges..."); + + foreach (@lines) { + $log->log ($_); + debug "Performing: $_"; + $status = $log->logcmd ($_); + + return $status if $status != 0; + } # foreach + } # while + + $log->msg ("All directories merged."); + + # Clean up + unlink $cmds; + + debug "EXIT: MergeDirectories (, $path, $branch)"; + + return 0; + } # MergeDirectories + + # Here we'll attempt to merge file individually using -abort. This tells + # cleartool findmerge to only merge that which is can automatically merge. For + # every merge failure we'll push an entry onto @merge_conflicts. + sub MergeFiles { + my ($log, $path, $branch) = @_; + + my $cmds = "$me.$$.cmds"; + my $cmd = "cleartool findmerge $path -nc -type f -fversion $branch " . + "-log $cmds -print > $NULL 2>&1"; + + debug "ENTER: MergeFiles (, $path, $branch)"; + + $log->msg ("Merging files..."); + + $log->logcmd ($cmd); + + my @lines = ReadFile $cmds; + my @merge_conflicts; + + foreach my $file_merge_cmd (@lines) { + my %merge_conflict; + + my $file_to_merge; + + if ($file_merge_cmd =~ /cleartool findmerge (.*) -fver/) { + $file_to_merge = $1; + } # if + + # Add -abort to this variable, which use for execution. We keep + # the old variable to put in the return array. + my $file_merge_cmd_abort = "$file_merge_cmd -abort 2>&1"; + + debug "Performing $file_merge_cmd_abort"; + $log->msg ($file_merge_cmd_abort); + + # Capture the output from the merge and parse it. If there's + # just a merge conflict then "*** No Automatic Decision + # possible" and "merge: Warning: *** Aborting.." are present in + # the output. If the merge fails because of binary files then + # nothing is in the output. Either way, if Clearcase is unable + # to merge the status returned is non zero. We can then + # differentiate between resolvable merge conflicts and + # unresolvable merge conflicts (binary files). Format + # %merge_conflicts to indicate the type and push it on + # @merge_conflicts to return to the caller. + # + # Also find merges that will not work because the element is + # checked out reserved somewhere else. + my @output = `$file_merge_cmd_abort`; + my $status = $?; + + # Put output in the logfile + chomp @output; + foreach (@output) { + $log->log ($_); + } # foreach + + if ($status == 0) { + # If $status eq 0 then the merge was successful! Next merge! + $log->msg ("Auto merged $file_to_merge"); + next; + } # if + + # Check for errors + my @errors = grep {/\*\*\* /} @output; + my @reserved = grep {/is checked out reserved/} @output; + + if (scalar @reserved > 0) { + if ($reserved [0] =~ /view (\S+)\./) { + $log->err ("Unable to merge $file_to_merge because it is checked out reserved in the view $1"); + } # if + + next; + } # if + + $merge_conflict {cmd} = $file_merge_cmd; + + # Differentiate between binary merge conflicts and non binary + # merge conflicts + if (scalar @errors > 0) { + $merge_conflict {type} = "regular"; + $log->msg ("Delaying regular conflicting merge for " . $file_to_merge); + } else { + $log->msg ("Delaying binary merge for " . $file_to_merge); + $merge_conflict {type} = "binary"; + } # if + + push @merge_conflicts, \%merge_conflict; + } # foreach + + my $nbr_conflicts = scalar @merge_conflicts; + + if ($nbr_conflicts == 0) { + $log->msg ("All files merged"); + } elsif ($nbr_conflicts == 1) { + $log->msg ("$nbr_conflicts merge conflict found"); + } else { + $log->msg ("$nbr_conflicts merge conflicts found"); + } # if + + # Clean up + unlink $cmds; + + debug "EXIT: MergeFiles (, $path, $branch)"; + + return @merge_conflicts; + } # MergeFiles + + sub GetRebaseDirs { + my $log = shift; + my $baseline = shift; + + $log->msg ("Finding directories that need rebasing..."); + + my $cmd = "cleartool rebase -long -preview "; + + if (!defined $baseline) { + $cmd .= "-recommended"; + } else { + $cmd .= "-baseline $baseline"; + } # if + + $log->msg ("Performing command: $cmd"); + + my @output = `$cmd`; + chomp @output; + + my %rebase_dirs; + + return %rebase_dirs if $? != 0; + + # Now parse the files to be merged collecting information + foreach (@output) { + if (/\s*(\S*)\@\@(\S*)/) { + my $element = $1; + my $ver = $2; + + # Directories only + next if !-d $element; + + $log->msg ("Directory Element: $element Version: $ver"); + $rebase_dirs {$element} = $ver; + } # if + } # foreach + + return %rebase_dirs; + } # GetRebaseDirs + + sub GetRebaseFiles { + my $log = shift; + my $baseline = shift; + + $log->msg ("Finding files that need rebasing..."); + + my $cmd = "cleartool rebase -long -preview "; + + if (!defined $baseline) { + $cmd .= "-recommended"; + } else { + $cmd .= "-baseline $baseline"; + } # if + + $log->msg ("Performing command: $cmd"); + + my @output = `$cmd`; + + return if $? != 0; + + chomp @output; + + my %rebase_files; + + # Now parse the files to be merged collecting information + foreach (@output) { + if (/\s*(\S*)\@\@(\S*)/) { + my $element = $1; + my $ver = $2; + + # Files only + next if !-f $element; + + $log->msg ("Element: $element Version: $ver"); + $rebase_files {$element} = $ver; + } # if + } # foreach + + return %rebase_files; + } # GetRebaseFiles + + sub RebaseDirectories { + my $log = shift; + my $baseline = shift;; + + debug "ENTER: RebaseDirectories"; + + $log->msg ("Rebasing directories"); + + my $rebase_status = 0; + my %rebase_dirs; + + # Keep rebasing directories until there are no more + while (%rebase_dirs = GetRebaseDirs $log, $baseline) { + foreach my $element (keys %rebase_dirs) { + # First checkout file if necessary - ignore errors + my @output = `cleartool checkout -nc $element > $NULL 2>&1`; + + my $cmd = "cleartool merge -abort -to $element -version ${rebase_dirs {$element}} 2>&1"; + + @output = `$cmd`; + my $status = $?; + + # Put output in the logfile + chomp @output; + + foreach (@output) { + $log->log ($_); + } # foreach + + if ($status == 0) { + # If $status eq 0 then the merge was successful! Next merge! + $log->msg ("Auto merged $element"); + next; + } # if + + # Check for errors + my @errors = grep {/\*\*\* /} @output; + my @reserved = grep {/is checked out reserved/} @output; + + # TODO: This is broke! + my $file_to_merge; + if (scalar @reserved > 0) { + if ($reserved [0] =~ /view (\S+)\./) { + $log->err ("Unable to merge $file_to_merge because it is checked out reserved in the view $1"); + $rebase_status++; + } # if + + next; + } # if + } # foreach + } # while + + debug "Returning $rebase_status from RebaseDirectories"; + return $rebase_status; + } # RebaseDirectories + + sub RebaseFiles { + my ($log, $baseline, %rebase_elements) = @_; + + debug "ENTER: RebaseFiles"; + + # TODO: This is broke too + my @merge_conflicts; + + $log->msg ("Rebasing elements"); + + foreach my $element (keys %rebase_elements) { + # First checkout file if necessary - ignore errors + my @output = `cleartool checkout -nc $element > $NULL 2>&1`; + + my $cmd = "cleartool merge -abort -to $element -version ${rebase_elements {$element}} 2>&1"; + + @output = `$cmd`; + my $status = $?; + + # Put output in the logfile + chomp @output; + foreach (@output) { + $log->log ($_); + } # foreach + + if ($status == 0) { + # If $status eq 0 then the merge was successful! Next merge! + $log->msg ("Auto merged $element"); + next; + } # if + + # Check for errors + my @errors = grep {/\*\*\* /} @output; + my @reserved = grep {/is checked out reserved/} @output; + + # TODO: This is broke too + my ($file_to_merge, $merge_conflict, %merge_conflict, @merge_conflicts); + + if (scalar @reserved > 0) { + if ($reserved [0] =~ /view (\S+)\./) { + $log->err ("Unable to merge $file_to_merge because it is checked out reserved in the view $1"); + } # if + + next; + } # if + + # Differentiate between binary merge conflicts and non binary + # merge conflicts + if (scalar @errors > 0) { + $merge_conflict {type} = "regular"; + $log->msg ("Delaying regular conflicting merge for " . $element); + } else { + $log->msg ("Delaying binary merge for " . $element); + $merge_conflict {type} = "binary"; + } # if + + push @merge_conflicts, \%merge_conflict; + } # foreach + + my $nbr_conflicts = scalar @merge_conflicts; + + if ($nbr_conflicts == 0) { + $log->msg ("All files merged"); + } elsif ($nbr_conflicts == 1) { + $log->msg ("$nbr_conflicts merge conflict found"); + } else { + $log->msg ("$nbr_conflicts merge conflicts found"); + } # if + + debug "EXIT: RebaseFiles"; + + return @merge_conflicts; + } # RebaseFiles + + sub Rebase { + my ($baseline, $verbose, $debug) = @_; + + if ($verbose) { + Display::set_verbose; + Logger::set_verbose; + } # if + + set_debug if $debug; + + my $log = Logger->new ( + name => "$me.$$", + disposition => "temp", + path => $ENV{TMP} + ); + + $log->msg ("BinMerge (rebase) $version started at " . localtime); + + if (!defined $baseline) { + $log->msg ("Baseline: RECOMMENDED"); + } else { + $log->msg ("Baseline: $baseline"); + } # if + + my $rebase_status = RebaseDirectories $log, $baseline; + + my @merge_conflicts = RebaseFiles $log, $baseline; + + # more to come... + return; + } # Rebase + + sub Merge { + my ($branch, $path, $verbose, $debug) = @_; + + if ($verbose) { + Display::set_verbose; + Logger::set_verbose; + } # if + + set_debug if $debug; + + error "Must specify a branch" if !defined $branch; + $path = "." if !defined $path; + + my $log = Logger->new ( + name => "$me.$$", + disposition => "temp", + path => $ENV{TMP} + ); + + $log->msg ("BinMerge $version started at " . localtime); + my $merge_status = 0; + + $merge_status = MergeDirectories $log, $path, $branch; + + my @merge_conflicts = MergeFiles $log, $path, $branch; + + my (@binary_merge_conflicts, @text_merge_conflicts); + my $merge_conflict; + + # Separate the bin merges from the text merges. + while (@merge_conflicts) { + my %merge_conflict = %{shift @merge_conflicts}; + + if ($merge_conflict {type} eq "binary") { + # Since we can't merge binary files, change -merge to + # -print. Later we'll use the -print output to present the + # user options... + $merge_conflict {cmd} =~ s/ -merge / -print /; + push @binary_merge_conflicts, $merge_conflict {cmd}; + } else { + # For text merges we can merge but we want to merge + # graphically. + $merge_conflict {cmd} =~ s/ -merge / -gmerge /; + push @text_merge_conflicts, $merge_conflict {cmd}; + } # if + } # while; + + # Now process the text merges + foreach my $merge_conflict (@text_merge_conflicts) { + # Now try the merge so that diffmerge comes up allowing the user + # to resolve the conflicts for this element. + my $file_to_merge; + + if ($merge_conflict =~ /cleartool findmerge (.*) -fver/) { + $file_to_merge = $1; + } # if + + $file_to_merge =~ s/\\\\/\\/g; + + debug "Performing $merge_conflict"; + my $status = $log->logcmd ("$merge_conflict 2>&1"); + + if ($status != 0) { + $log->err ("$user did not resolve merge conflicts in $file_to_merge"); + $merge_status++; + } else { + $log->msg ("$user resolved conflicts in merge of $file_to_merge"); + } # if + } # foreach + + # Now process the binary ones... + foreach my $merge_conflict (@binary_merge_conflicts) { + # Now try to handle the binary merge conflicts. Best we can do + # is to present the user the with the various versions that + # could be taken as a whole along with an option to not + # merge. If they select a specific version then we simply draw a + # merge arrow. + + my @selections; + + # First let's do the merge command again capturing the output + # which has a format like: + # + # Needs Merge "firefox.exe" [to \main\adefaria_Andrew\CHECKEDOUT + # from \main\Andrew_Integration\2 base \main\adefaria_Andrew\1] + # + # From this we'll get the $from and $to to present to the user. + my $file_to_merge; + + if ($merge_conflict =~ /cleartool findmerge (.*) -fver/) { + $file_to_merge = $1; + } # if + + debug "Performing $merge_conflict"; + my @output = `$merge_conflict 2>&1`; + + my ($to, $from); + + if ($output [0] =~ /to (\S*) from (\S*)/) { + $to = $1; + $from = $2; + } # if + + push @selections, $from; + push @selections, $to; + + my $choice = PromptUser $file_to_merge, @selections; + + if (!defined $choice) { + $log->err ("$user aborted binary merge of $file_to_merge"); + next; + } # if + + chomp $choice; + # I don't know why the above doesn't remove the trailing \n so let's + # chop it off if it exists! + chop $choice if $choice =~ /\n/; + + my $cmd; + + # At this point the merge process has checked out the file in + # the current view but is unable to perform the merge because + # this is a binary file. If the user chooses the $from version + # then they are saying that the $from version should be brought + # into the current view and a merge arrow drawn from $from -> + # $to. + # + # If, however, they choose the CHECKEDOUT version then what we + # want to do is to cancel the current checkout and draw a merge + # arrow from the predecessor to $to. + if ($choice eq $from) { + # Need to copy the $from version to the checkedout version here. + debug "Copying $file_to_merge\@\@$choice to current view"; + open my $from, '<', "$file_to_merge\@\@$choice" + or error "Unable to open $file_to_merge\@\@$choice", 1; + binmode $from; + + open my $to, '>', "$file_to_merge" + or error "Unable to open $file_to_merge\@\@$to", 2; + binmode $to; + + while (<$from>) { + print $to $_; + } # while + + close $from; + close $to; + + $log->msg ("$user chose to link from $choice -> $file_to_merge" . + " in the current view"); + $cmd = "cleartool merge -to \"$file_to_merge\"" . + " -ndata \"$file_to_merge\@\@$choice\""; + } else { + # Need to cancel the checkout then determine what version + # Clearcase reverts to. WARNING: This might doesn't work + # for a snapshot view. + debug "Canceling checkout for $file_to_merge"; + @output = `cleartool unco -rm $file_to_merge 2>&1`; + + error "Unable to cancel checkout of $file_to_merge", 3 if $? != 0; + + @output = `cleartool ls -s $file_to_merge`; + + chomp $output [0]; + + if ($output [0] =~ /\@\@(.*)/) { + $choice = $1; + } # if + + debug "Drawing merge arrow from $file_to_merge\@\@$from -> $choice"; + $log->msg ("$user chose to link $file_to_merge from $from -> $choice"); + $cmd = "cleartool merge -to \"$file_to_merge\"\@\@$choice\" -ndata \"$file_to_merge\@\@$from\""; + } # if + + # Draw merge arrow + my $status = $log->logcmd ($cmd); + + error "Unable to draw merge arrow ($cmd)" if $status != 0; + + $merge_status += $status; + } # foreach + + if ($merge_status > 0) { + $log->err ("There were problems with the merge. Please review " . + $log->fullname . " for more infomation"); + } # if + + return $merge_status + } # Merge + +1; diff --git a/lib/Clearcase.pm b/lib/Clearcase.pm new file mode 100644 index 0000000..d6b9078 --- /dev/null +++ b/lib/Clearcase.pm @@ -0,0 +1,1379 @@ +=pod + +=head1 NAME $RCSfile: Clearcase.pm,v $ + +Object oriented interface to Clearcase. + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.43 $ + +=item Created + +Tue Dec 4 17:33:43 MST 2007 + +=item Modified + +$Date: 2011/11/16 18:27:37 $ + +=back + +=head1 SYNOPSIS + +Provides access to global Clearcase information in an object oriented manner as +well as an interface to cleartool. + + # Access some compile time global settings: + display "View Drive: $Clearcase::VIEW_DRIVE"; + display "Vob Tag Prefix: $Clearcase::VOBTAG_PREFIX"; + + # Access some run time global information through the default object + display "Client: $Clearcase::CC->client"; + display "Region: $Clearcase::CC->region"; + display "Registry host: $Clearcase::CC->registry_host"; + + # List all vobs using execute method of the default object"; + my ($status, @vobs) = $Clearcase::CC->execute ("lsvob -s"); + + display $_ foreach (@vobs) if $status == 0; + +=head1 DESCRIPTION + +This module, and others below the Clearcase directory, implement an object +oriented approach to Clearcase. In general Clearcase entities are made into +objects that can be manipulated easily in Perl. This module is the main or +global module. Contained herein are members and methods of a general or global +nature. Also contained here is an IPC interface to cleartool such that cleartool +runs in the background and commands are fed to it via the execute method. When +making repeated calls to cleartool this can result in a substantial savings of +time as most operating systems' fork/execute sequence is time consuming. Factors +of 8 fold improvement have been measured. + +Additionally a global variable, $CC, is implemented from this module such that +you should not need to instantiate another one, though you could. + +=head1 ROUTINES + +The following routines are exported: + +=cut + +package Clearcase; + +use strict; +use warnings; + +use base 'Exporter'; + +use Carp; + +use IPC::Open3; + +use OSDep; +use Display; + +my ($clearpid, $clearin, $clearout, $oldHandler); + +our $VIEW_DRIVE = 'M'; +our $VOB_MOUNT = 'vob'; +our $WIN_VOB_PREFIX = '\\'; +our $SFX = $ENV{CLEARCASE_XN_SFX} ? $ENV{CLEARCASE_XN_SFX} : '@@'; + +our $VOBTAG_PREFIX = ($ARCH eq 'windows' or $ARCH eq 'cygwin') + ? $WIN_VOB_PREFIX + : "/$VOB_MOUNT/"; +our $VIEWTAG_PREFIX = ($ARCH eq 'windows' or $ARCH eq 'cygwin') + ? "$VIEW_DRIVE:" + : "${SEPARATOR}view"; + +our ($CCHOME, $COUNTDB); + +our $CC; + +our @EXPORT_OK = qw ( + $CC + $CCHOME + $COUNTDB + $SFX + $VIEW_DRIVE + $VIEWTAG_PREFIX + $VOB_MOUNT + $VOBTAG_PREFIX + $WIN_VOB_PREFIX +); + +BEGIN { + # Find executables that we rely on + if ($ARCH eq 'windows' or $ARCH eq 'cygwin') { + # Should really go to the registry for this... + + # We can go to the registry pretty easy in Cygwin but I'm not sure how to do + # that in plain old Windows. Most people either have Clearcase installed on + # the C drive or commonly on the D drive on servers. So we'll look at both. + $CCHOME = 'C:\\Program Files\\Rational\\Clearcase'; + + $CCHOME = 'D:\\Program Files\\Rational\\Clearcase' + unless -d $CCHOME; + + error 'Unable to figure out where Clearcase is installed', 1 + unless -d $CCHOME; + + $COUNTDB = "$CCHOME\\etc\\utils\\countdb.exe"; + } else { + $CCHOME = '/opt/rational/clearcase'; + $COUNTDB = "$CCHOME/etc/utils/countdb"; + } # if + + #error "Unable to find countdb ($COUNTDB)", 2 + #if ! -f $COUNTDB; +} # BEGIN + +sub DESTROY { + my $exitStatus = $?; + + if ($clearpid) { + # Exit cleartool process + print $clearin "exit\n"; + + waitpid $clearpid, 0; + } # if + + local $? = $exitStatus; + + # Call old signal handler (if any) + &$oldHandler if $oldHandler; + + return; +} # DESTROY + +# Save old interrupt handler +$oldHandler = $SIG{INT}; + +# Set interrupt handler +local $SIG{INT} = \&Clearcase::DESTROY; + +sub _formatOpts { + my (%opts) = @_; + + my $opts = ''; + + foreach (keys %opts) { + $opts .= "$_ "; + $opts .= "$opts{$_} " + if $opts{$_} ne ''; + } # foreach + + return $opts; +} # _formatOpts + +sub _setComment ($) { + my ($comment) = @_; + + return !$comment ? '-nc' : '-c "' . quotameta $comment . '"'; +} # _setComment + +sub vobname ($) { + my ($tag) = @_; + +=pod + +=head2 vobname ($tag) + +Given a vob tag, return the vob name by stripping of the VOBTAG_PREFIX properly +such that you return just the unique vob name. This is tricky because Windows +uses '\' as a VOBTAG_PREFIX. With '\' in there regex's like +/$Clearcase::VOBTAG_PREFIX(.+)/ to capture the vob's name minus the +VOBTAG_PREFIX fail because Perl evaluates this as just a single '\', which +escapes the '(' of the '(.+)'! + +Parameters: + +=for html
    + +=over + +=over + +=item $tag + +Vob tag to convert + +=back + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=over + +=item $name + +The unique part of the vob name + +=back + +=back + +=for html
    + +=cut + + my $name = $tag; + + # Special code because Windows $VOBTAG prefix (a \) is such a pain! + if (substr ($tag, 0, 1) eq '\\') { + $name = substr $tag, 1; + } elsif (substr ($tag, 0, 1) eq '/') { + if ($tag =~ /${Clearcase::VOBTAG_PREFIX}(.+)/) { + $name = $1; + } # if + } # if + + return $name; +} # vobname + +sub vobtag ($) { + my ($name) = @_; + +=pod + +=head2 vobtag ($name) + +Given a vob name, add the VOBTAG_PREFIX based on the current OS. + +Parameters: + +=for html
    + +=over + +=over + +=item $name + +Vob name to convert + +=back + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=over + +=item $tag + +Vob tag + +=back + +=back + +=for html
    + +=cut + + # If the $VOBTAG_PREFIX is already there then do nothing + if (substr ($name, 0, length $VOBTAG_PREFIX) eq $VOBTAG_PREFIX) { + return $name; + } else { + return "$VOBTAG_PREFIX$name"; + } # if +} # vobtag + +sub attributes ($$;%) { + # TODO: Need to handle other options too + my ($self, $type, $name, %newAttribs) = @_; + +=pod + +=head2 attributes ($type, $name) + +Get any attributes attached to the $type:$name + +Parameters: + +=for html
    + +=over + +=over + +=item $type + +Type of object to look for attributes. For example, activity, baseline, etc. + +=item $name + +Object name to look for attributes. + +=back + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=over + +=item %attributes + +Hash of attribute name/values + +=back + +=back + +=for html
    + +=cut + + my $cmd = "describe -fmt \"%Na\" $type:$name"; + + my ($status, @output) = $CC->execute ($cmd); + + return if $status; + + my %attributes; + + if ($output[0]) { + # Parse output + my $attributes = $output[0]; + my ($name, $value); + + while ($attributes ne '') { + if ($attributes =~ /^=(\"*)(.*)/) { + if ($2 =~ /(.*?)$1(\s|$)(.*)/) { + $attributes{$name} = $1; + $attributes = $3; + } else { + $attributes{$name} = $2; + $attributes = ''; + } # if + } elsif ($attributes =~ /^(\w+)=(.*)/) { + $name = $1; + $attributes = "=$2"; + } else { + croak "Parsing error while parsing " . ref ($self) . " attributes"; + } # if + } # while + } # if + + # Set any %newAttribs + foreach (keys %newAttribs) { + # TODO: What about other options like -comment? + $cmd = "mkattr -replace -nc $_ \""; + $cmd .= quotemeta $newAttribs{$_}; + $cmd .= "\" $type:$name"; + + $CC->execute ($cmd); + + if ($CC->status) { + die "Unable to execute $cmd (Status: " + . $CC->status . ")\n" + . join ("\n", $CC->output); + } else { + $attributes{$_} = $newAttribs{$_}; + } # if + } # foreach + + return %attributes; +} # attributes + +sub status () { + my ($self) = @_; + +=pod + +=head2 status () + +Returns the status of the last executed command. + +Parameters: + +=for html
    + +=over + +=over + +=item none + +=back + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=over + +=item $status + +Status of the command last executed. + +=back + +=back + +=for html
    + +=cut + + return $self->{status}; +} # status + +sub output () { + my ($self) = @_; + +=pod + +=head2 output () + +Returns the output of the last executed command. + +Parameters: + +=for html
    + +=over + +=over + +=item none + +=back + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=over + +=item @output or $output + +If called in a list context, returns @output, otherwise returns $output. + +=back + +=back + +=for html
    + +=cut + + if (wantarray) { + return split /\n/, $self->{output}; + } else { + return $self->{output}; + } # if +} # output + +# TODO: Should implement a pipe call that essentially does a cleartool command +# to a pipe allowing the user to read from the pipe. This will help with such +# cleartool command that may give back huge output or where the user wishes to +# start processing the output as it comes instead of waiting until the cleartool +# command is completely finished. Would like to do something like execute does +# with cleartool running in the background but we need to handle the buffering +# of output sending only whole lines. + +sub execute { + my ($self, $cmd) = @_; + +=pod + +=head2 execute ($cmd) + +Sends a command to the cleartool coprocess. If not running a cleartool coprocess +is started and managed. The coprocess is implemented as a coprocess using IPC +for communication that will exist until the object is destroyed. Stdin and +stdout/stderr are therefore pipes and can be fed. The execute method feds the +input pipe and returns status and output from the output pipe. + +Using execute can speed up execution of repeative cleartool invocations +substantially. + +Parameters: + +=for html
    + +=over + +=over + +=item $cmd + +Cleartool command to execute. + +=back + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=over + +=item $status + +Status of the command last executed. + +=item @output + +Array of output lines from the cleartool command execution. + +=back + +=back + +=for html
    + +=cut + + my ($status, @output); + + # This seems to be how most people locate cleartool. On Windows (this + # includes Cygwin) we assume it's in our path. On Unix/Linux we assume it's + # installed under /opt/rational/clearcase/bin. This is needed in case we wish + # to use these Clearcase objects say in a web page where the server is often + # run as a plain user who does not have cleartool in their path. + my $cleartool; + + if ($ARCH =~ /Win/ or $ARCH eq 'cygwin') { + $cleartool = 'cleartool'; + } elsif (-x '/opt/rational/clearcase/bin/cleartool') { + $cleartool = '/opt/rational/clearcase/bin/cleartool'; + } # if + + # TODO: Need to catch SIGCHILD here in case the user does something like hit + # Ctrl-C. Such an action may interrupt the underlying cleartool process and + # kill it. But we would be unaware (i.e. $clearpid would still be set). So + # when SIGCHILD is caught we need to undef $clearpid. + if (!$clearpid) { + # Simple check to see if we can execute cleartool + @output = `$cleartool -ver 2>&1`; + + return (-1, 'Clearcase not installed') + unless $? == 0; + + $clearpid = open3 ($clearin, $clearout, $clearout, $cleartool, "-status"); + + return (-1, ('Clearcase not installed')) unless $clearpid; + } # if + + # Execute command + print $clearin "$cmd\n"; + + # Now read output from $clearout and format the lines in to an array. Also + # capture the status code to return it. + while (my $line = <$clearout>) { + if ($line !~ /(.*)Command \d+ returned status (\d+)/sm) { + push @output, $line; + } else { + push @output, $1; + $status = $2; + last; + } # if + } # while + + if (@output) { + chomp @output; + chop @output if $output[0] =~ /\r$/; + } # if + + # We're getting extra blank lines at the bottom of @output. Not sure why + # but we need to remove it + pop @output + if @output and $output[$#output] eq ''; + + $self->{status} = $status; + $self->{output} = join "\n", @output; + + return ($status, @output); +} # execute + +sub new { + my ($class) = @_; + +=pod + +=head2 new () + +Construct a new Clearcase object. Note there is already a default +Clearcase object created named $cc. You should use that unless you +have good reason to instantiate another Clearcase object. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Clearcase object + +=back + +=for html
    + +=cut + + # Attributes + my ( + $registry_host, + $version, + @regions, + ); + + my $self = bless { + registry_host => $registry_host, + version => $version, + verbose_level => 0, + vobtag_prefix => $VOBTAG_PREFIX, + viewtag_prefix => $VIEWTAG_PREFIX, + regions => \@regions, + }, $class; + + # Get list of regions + my ($status, @output); + + ($status, @regions) = $self->execute ('lsregion'); + + return $self + if $status; + + # Get hostinfo attributes + ($status, @output) = $self->execute ('hostinfo -long'); + + return $self + if $status; + + foreach (@output) { + if (/Client: (.*)/) { + $self->{client} = lc $1; + } elsif (/Product: (.*)/) { + $self->{version} = $1; + } elsif (/Operating system: (.*)/) { + $self->{os} = $1; + } elsif (/Hardware type: (.*)/) { + $self->{hardware_type} = $1; + } elsif (/Registry host: (.*)/) { + $self->{registry_host} = $1; + } elsif (/Registry region: (.*)/) { + $self->{region} = $1; + $self->{sitename} = $1; + + if ($self->{region} =~ /(\S*)(NT|UNIX)$/) { + $self->{sitename} = $1; + } # if + } elsif (/License host: (.*)/) { + $self->{license_host} = $1; + } # if + } # foreach + + return $self; +} # new + +# Member access methods... + +sub client { + my ($self) = @_; + +=pod + +=head2 client + +Returns the client + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item client + +=back + +=for html
    + +=cut + + return $self->{client}; +} # client + +sub hardware_type { + my ($self) = @_; + +=pod + +=head2 hardware_type + +Returns the hardware_type + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item hardware_type + +=back + +=for html
    + +=cut + + return $self->{hardware_type}; +} # hardware_type + +sub license_host { + my ($self) = @_; + +=pod + +=head2 license_host + +Returns the license_host + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item license_host + +=back + +=for html
    + +=cut + + return $self->{license_host}; +} # license_host + +sub os { + my ($self) = @_; + +=pod + +=head2 os + +Returns the os + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item os + +=back + +=for html
    + +=cut + + return $self->{os}; +} # os + +sub region { + my ($self) = @_; + +=pod + +=head2 region + +Returns the region + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item region + +=back + +=for html
    + +=cut + + return $self->{region}; +} # region + +sub registry_host { + my ($self) = @_; + +=pod + +=head2 registry_host + +Returns the registry_host + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item client string + +=back + +=for html
    + +=cut + + return $self->{registry_host}; +} # registry_host + +sub sitename { + my ($self) = @_; + +=pod + +=head2 sitename + +Returns the sitename + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item sitename + +=back + +=for html
    + +=cut + + return $self->{sitename}; +} # sitename + +sub version { + my ($self) = @_; + +=pod + +=head2 version + +Returns the version + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item version + +=back + +=for html
    + +=cut + + return $self->{version}; +} # version + +sub regions { + my ($self) = @_; + +=pod + +=head2 regions + +Returns an array of regions in an array context or the number of +regions in a scalar context + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item array of regions or number of regions + +=back + +=for html
    + +=cut + + if (wantarray) { + my @returnArray = sort @{$self->{regions}}; + + return @returnArray; + } else { + return scalar @{$self->{regions}}; + } # if +} # regions + +sub pwv () { + my ($self) = @_; + +=pod + +=head2 pwv + +Returns the current working view or undef if not in a view + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Current working view or undef if none + +=back + +=for html
    + +=cut + + my ($status, @output) = $self->execute ('pwv -short'); + + return if $status; + return $output[0] eq '** NONE **' ? undef : $output[0]; +} # pwv + +sub name2oid ($;$) { + my ($self, $name, $vob) = @_; + +=pod + +=head2 name2oid + +Returns the oid for a given name + +Parameters: + +=for html
    + +=over + +=item name + +The name to convert (unless filesystem object it should contain a type:) + +=item vob + +The vob the name belongs to + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item OID + +=back + +=for html
    + +=cut + + if ($vob) { + $vob = '@' . vobtag $vob; + } else { + $vob = ''; + } # if + + my ($status, @output) = $self->execute ("dump $name$vob"); + + return if $status; + + @output = grep { /^oid=/ } @output; + + if ($output[0] =~ /oid=(\S+)\s+/) { + return $1; + } else { + return; + } # if +} # name2oid + +sub oid2name ($$) { + my ($self, $oid, $vob) = @_; + +=pod + +=head2 oid2name + +Returns the object name for the given oid + +Parameters: + +=for html
    + +=over + +=item oid + +The OID to convert + +=item vob + +The vob the OID belongs to + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item String representing the OID's textual name/value + +=back + +=for html
    + +=cut + + $vob = vobtag $vob + unless $vob =~ /^vobuuid:/; + + my ($status, @output) = $self->execute ( + "describe -fmt \"%n\" oid:$oid\@$vob" + ); + + return if $status; + return $output[0]; +} # oid2name + +sub verbose_level { + my ($self) = @_; + +=pod + +=head2 verbose_level + +Returns the verbose_level + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item verbose_level + +=back + +=for html
    + +=cut + + return $self->{verbose_level}; +} # verbose_level + +sub quiet { + my ($self) = @_;; + +=pod + +=head2 quiet + +Sets verbose_level to quiet + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +=cut + + $self->{verbose_level} = 0; + + return; +} # quiet + +sub noisy { + my ($self) = @_; + +=pod + +=head2 noisy + +Sets verbose_level to noisy + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +=cut + + $self->{verbose_level} = 1; + + return; +} # noisy + +$CC = Clearcase->new; + +1; + +=pod + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +=head2 ClearSCM Perl Modules + +=for html

    Display

    + +=for html

    OSdep

    + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this module + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2007, ClearSCM, Inc. All rights reserved. + +=cut diff --git a/lib/Clearcase/Element.pm b/lib/Clearcase/Element.pm new file mode 100644 index 0000000..09de822 --- /dev/null +++ b/lib/Clearcase/Element.pm @@ -0,0 +1,1387 @@ +=pod + +=head1 NAME $RCSfile: Element.pm,v $ + +Object oriented interface to Clearcase Elements + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.18 $ + +=item Created + +Thu Dec 29 12:07:59 PST 2005 + +=item Modified + +$Date: 2011/11/16 19:46:13 $ + +=back + +=head1 SYNOPSIS + +Provides access to information about Clearcase Elements. + + my $element = new Clearcase::Element (pname => "element"); + + display "Element:\t" . $element->pname; + display "Version:\t" . $element->version; + display "Pred:\t\t" . $element->pred; + + display "Activities:"; + + if (my %activities = $element->activities) { + display "\t\t$_: $activities{$_}" foreach (keys %activities); + } else { + display "\t\tNone"; + } # if + + display "Attributes:"; + + if (my %attributes = $element->attributes) { + display "\t\t$_=$attributes{$_}" foreach (keys %attributes); + } else { + display"\t\tNone"; + } # if + + display "Hyperlinks:"; + + if (my @hyperlinks = $element->hyperlinks) { + display "\t\t$_" foreach (@hyperlinks); + } else { + display "\t\tNone"; + } # if + + display "Comments:"; + + if ($element->comments) { + display "\t\t" . $element->comments; + } else { + display "\t\tNone"; + } # if + + display "Create_date:\t" . $element->create_date; + display "User:\t\t" . $element->user; + display "Group:\t\t" . $element->group; + display "User_mode:\t" . $element->user_mode; + display "Group_mode:\t" . $element->group_mode; + display "Other_mode:\t" . $element->other_mode; + display "Mode:\t\t" . $element->mode; + + display "Labels:"; + + if (my @labels = $element->labels) { + display "\t\t$_" foreach (@labels); + } else { + display "\t\tNone"; + } # if + + display "Rule:\t\t" . $element->rule; + display "Xname:\t\t" . $element->xname; + +=head1 DESCRIPTION + +This module implements a Clearcase Element object. + +=head1 ROUTINES + +The following routines are exported: + +=cut + +package Clearcase::Element; + +use strict; +use warnings; + +use lib '..'; + +use Clearcase; + +sub collapseOverExtendedVersionPathname ($) { + my ($versionStr) = @_; + +=pod + +=head2 collapseOverExtendedVersionPathname + +This utility function will collapse an "over extended" version pathname. These +over extended pathnames can occur when we are not operating in the UCM view +from which the version was generated. Clearcase gives us enormous,technically +correct but hard to read, view/vob extended path names. Here's an example +(broken by lines for readability): + + /vob/component/branch1@@/main/branch1_Integration/1/src/main/branch1_ + /2/com/main/branch1_Integration/2/company/main/branch1_Integration/2/ + ManagerPlatform/main/branch1_Integration/2/nma/main/ + branch1_Integration/devbranch_17/1/common/main/devbranch_17/3/exception/ + main/mainline/devbranch_r17/1/Exception.java/main/mainline/1 + +We want this to read: + + element: /vob/component/src/com/company/ManagerPlatform/nma/ + common/exception/Exception.java + version: /main/mainline/1 + +Parameters: + +=for html
    + +=over + +=item $versionStr + +This is the over extended version pathname + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item %element hash + +A hash containing the element's name and version string collapsed + +=back + +=for html
    + +=cut + + return + unless $versionStr; + + $versionStr =~ s/\\/\//g; + + my ($name, $version) = split /$Clearcase::SFX/, $versionStr; + + my %element = ( + extended_name => $versionStr, + name => $name, + version => $version, + ); + + return + unless $element{version}; + + while ($element{version} =~ s/.*?\/\d+\/(.*?)\///) { + $element{name} .= "/$1"; + } # while + + $element{version} = "/$element{version}" + if $element{version} !~ /^\//; + + return %element; +} # collapseOverExtendedVersionPathname + +sub new ($) { + my ($class, $pname) = @_; + +=pod + +=head2 new + +Construct a new Clearcase Element object. + +Parameters: + +=for html
    + +=over + +=item element name + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Clearcase Element object + +=back + +=for html
    + +=cut + + my $self = bless { + pname => $pname, + }, $class; + + my ($version, $rule); + + my ($status, @output) = $Clearcase::CC->execute ("ls -d $pname"); + + return $self + if $status; + + # Sometimes ls -d puts out more than one line. Join them... + if ((join ' ', @output) =~ /^.*\@\@(\S+)\s+Rule: (.*)$/m) { + $version = $1; + $rule = $2; + } # if + + $self->{rule} = $rule; + $self->{version} = $version; + + return $self; +} # new + +sub describe () { + my ($self) = @_; + # Get information that can only be gotten with describe -long. These fields + # lack a -fmt option. + + my ($status, @output) = $Clearcase::CC->execute ( + "describe -long $self->{pname}" + ); + + return + if $status != 0; + + my $section; + + foreach (@output) { + if (/Hyperlinks:/) { + $section = 'hyperlinks'; + next; + } elsif (/Attached activities:/) { + $section = 'activities'; + next; + } # if + + if ($section) { + if ($section eq 'activities') { + if (/activity:(.*)\s+\"(.*)\"/) { + ${$self->{activities}}{$1} = $2; + } # if + } elsif ($section eq "hyperlinks") { + if (/\s+(.*)/) { + push @{$self->{hyperlinks}}, $1; + } # if + } # if + + next; + } # if + + if (/User : \S+\s*: (.*)/) { + $self->{user_mode} = $1; + } elsif (/Group: \S+\s*: (.*)/) { + $self->{group_mode} = $1; + } elsif (/Other:\s+: (.*)/) { + $self->{other_mode} = $1; + } # if + } # foreach + + # Change modes to numeric + $self->{mode} = 0; + + $self->{mode} += 400 if $self->{user_mode} =~ /r/; + $self->{mode} += 200 if $self->{user_mode} =~ /w/; + $self->{mode} += 100 if $self->{user_mode} =~ /x/; + $self->{mode} += 40 if $self->{group_mode} =~ /r/; + $self->{mode} += 20 if $self->{group_mode} =~ /w/; + $self->{mode} += 10 if $self->{group_mode} =~ /x/; + $self->{mode} += 4 if $self->{other_mode} =~ /r/; + $self->{mode} += 2 if $self->{other_mode} =~ /w/; + $self->{mode} += 1 if $self->{other_mode} =~ /x/; + + return; +} # describe + +sub activities () { + my ($self) = @_; + +=pod + +=head2 activities + +Returns a hash of activity name/value pairs + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Hash of activity name/value pairs + +=back + +=for html
    + +=cut + + $self->describe + unless $self->{activities}; + + return $self->{activities} ? %{$self->{activities}} : (); +} # activities + +sub attributes () { + my ($self) = @_; + +=pod + +=head2 attributes + +Returns a hash of attribute name/value pairs + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Hash of attribute name/value pairs + +=back + +=for html
    + +=cut + + $self->updateElementInfo + unless $self->{attributes}; + + return %{$self->{attributes}}; +} # attributes + +sub comments () { + my ($self) = @_; + +=pod + +=head2 comments + +Returns the comments associated with the current version element. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item comment + +=back + +=for html
    + +=cut + + $self->updateElementInfo + unless $self->{comments}; + + return $self->{comments}; +} # comments + +sub create_date () { + my ($self) = @_; + +=pod + +=head2 create_date + +Returns the date of creation of the element. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item create date + +=back + +=for html
    + +=cut + + $self->updateElementInfo + unless $self->{create_date}; + + return $self->{create_date}; +} # create_date + +sub group () { + my ($self) = @_; + +=pod + +=head2 group + +Returns the group of the element. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item group + +=back + +=for html
    + +=cut + + $self->updateElementInfo + unless $self->{group}; + + return $self->{group}; +} # group + +sub group_mode () { + my ($self) = @_; + +=pod + +=head2 group_mode + +Returns the group mode of the element + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item group mode + +=back + +=for html
    + +=cut + + $self->describe + unless $self->{group_mode}; + + return $self->{group_mode}; +} # group_mode + +sub hyperlinks () { + my ($self) = @_; + +=pod + +=head2 hyperlinks + +Returns a hash of hyperlink name/value pairs + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Hash of hyperlink name/value pairs + +=back + +=for html
    + +=cut + + $self->describe + unless $self->{hyperlinks}; + + return @{$self->{hyperlinks}} +} # hyperlinks + +sub labels () { + my ($self) = @_; + +=pod + +=head2 labels + +Returns an array of labels + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Array of labels + +=back + +=for html
    + +=cut + + $self->updateElementInfo + unless $self->{labels}; + + return @{$self->{labels}}; +} # labels + +sub mode () { + my ($self) = @_; + +=pod + +=head2 mode + +Returns the numeric mode representing the element's access mode + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Array of activities + +=back + +=for html
    + +=cut + + $self->describe + unless $self->{mode}; + + return $self->{mode}; +} # mode + +sub other_mode () { + my ($self) = @_; + +=pod + +=head2 other_mode + +Returns the mode for other for the element. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item A string repesenting the other mode + +=back + +=for html
    + +=cut + + $self->describe + unless $self->{other_mode}; + + return $self->{other_mode}; +} # other_mode + +sub pname () { + my ($self) = @_; + +=pod + +=head2 pname + +Returns the pname of the element. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item pname + +=back + +=for html
    + +=cut + + return $self->{pname}; +} # pname + +sub pred () { + my ($self) = @_; + +=pod + +=head2 pred + +Returns the predecessor version of this element + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Predecessor version + +=back + +=for html
    + +=cut + + $self->updateElementInfo + unless $self->{pred}; + + return $self->{pred}; +} # pred + +sub rule () { + my ($self) = @_; + +=pod + +=head2 rule + +Returns the config spec rule that selected this element's version. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item rule + +=back + +=for html
    + +=cut + + return $self->{rule}; +} # rule + +sub type () { + my ($self) = @_; + +=pod + +=head2 type + +Returns the element's type + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item element type + +=back + +=for html
    + +=cut + + $self->updateElementInfo + unless $self->{type}; + + return $self->{type}; +} # type + +sub objkind () { + my ($self) = @_; + +=pod + +=head2 objkind + +Returns the element's object kind + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item element's object kind + +=back + +=for html
    + +=cut + + $self->updateElementInfo + unless $self->{objkind}; + + return $self->{objkind}; +} # objkind + +sub oid ($) { + my ($version) = @_; + +=pod + +=head2 oid + +Returns the element's OID + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item element's OID + +=back + +=for html
    + +=cut + + $version .= $Clearcase::SFX + unless $version =~ /$Clearcase::SFX$/; + + my ($status, @output) = $Clearcase::CC->execute ('dump "' . $version . '"'); + + return + unless $status == 0; + + @output = grep {/^oid=/} @output; + + if ($output[0] =~ /oid=(.+?)\s+/) { + return $1; + } # if +} # oid + +sub user () { + my ($self) = @_; + +=pod + +=head2 user + +Returns the username of the owner of this element. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item user name + +=back + +=for html
    + +=cut + + $self->updateElementInfo + unless $self->{user}; + + return $self->{user}; +} # user + +sub user_mode () { + my ($self) = @_; + +=pod + +=head2 user_mode + +Returns the mode for the user for the element. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item A string repesenting the other mode + +=back + +=for html
    + +=cut + + $self->describe + unless $self->{user_mode}; + + return $self->{user_mode}; +} # user_mode + +sub version () { + my ($self) = @_; + +=pod + +=head2 version + +Returns this element's version + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item version + +=back + +=for html
    + +=cut + + return $self->{version}; +} # version + +sub xname () { + my ($self) = @_; + +=pod + +=head2 xname + +Returns the view extended path name (xname) of an element version. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item xname + +=back + +=for html
    + +=cut + + $self->updateElementInfo + unless $self->{xname}; + + return $self->{xname}; +} # xname + +sub mkelem (;$) { + my ($self, $comment) = @_; + +=pod + +=head2 mkelem + +Returns creates a new element + +Parameters: + +=for html
    + +=over + +=item Comment + +Creation comment. Default -nc. + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $status + +Status from cleartool + +=item @output + +Ouput from cleartool + +=back + +=for html
    + +=cut + + $comment = Clearcase::_setComment $comment; + + return $Clearcase::CC->execute ("mkelem $comment $self->{pname}"); +} # mkelem + +sub checkout (;$) { + my ($self, $comment) = @_; + +=pod + +=head2 checkout + +Checks out the element + +Parameters: + +=for html
    + +=over + +=item comment + +Checkout comment. Default -nc. + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $status + +Status from cleartool + +=item @output + +Ouput from cleartool + +=back + +=for html
    + +=cut + + $comment = Clearcase::_setComment $comment; + + return $Clearcase::CC->execute ("checkout $comment $self->{pname}"); +} # checkout + +sub checkin (;$) { + my ($self, $comment) = @_; + +=pod + +=head2 checkin + +Checks in the element + +Parameters: + +=for html
    + +=over + +=item comment + +Check in comment. Default -nc. + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $status + +Status from cleartool + +=item @output + +Ouput from cleartool + +=back + +=for html
    + +=cut + + $comment = Clearcase::_setComment $comment; + + return $Clearcase::CC->execute ("checkin $comment $self->{pname}"); +} # checkout + +sub updateElementInfo () { + my ($self) = @_; + + # Get all information that can be gotten using -fmt + my $fmt = 'Attributes:%aEndAttributes:' + . 'Comment:%cEndComment:' + . 'Create_date:%dEndCreate_date:' + . 'Group:%[group]pEndGroup:' + . 'Labels:%NlEndLabels:' + . 'Pred:%PSnEndPred:' + . 'Type:%[type]pEndType:' + . 'ObjectKind:%mEndObjectKind:' + . 'User:%[owner]pEndUser:' + . 'Xname:%XnEndXname:'; + + my ($status, @output) = + $Clearcase::CC->execute ("describe -fmt \"$fmt\" $self->{pname}"); + + return + unless $status == 0; + + # We need to make sure that fields are filled in or empty because we are using + # undef as an indication that we have not called updateElementInfo yet. + $self->{attributes} = + $self->{labels} = (); + + $self->{comments} = + $self->{create_date} = + $self->{group} = + $self->{pred} = + $self->{type} = + $self->{objkind} = + $self->{user} = + $self->{xname} = ''; + + foreach (@output) { + # This output is wrapped with parenthesis... + if (/Attributes:\((.*)\)EndAttributes:/) { + my @attributes = split ", ", $1; + my %attributes; + + foreach (@attributes) { + if (/(\w+)=(\w+)/) { + $attributes{$1}=$2; + } # if + } # foreach + + $self->{attributes} = %attributes ? \%attributes : (); + } # if + + if (/Comments:(.*)EndComments:/) { + $self->{comments} = $1; + } # if + + if (/Create_date:(.*)EndCreate_date:/) { + $self->{create_date} = $1; + } # if + + if (/Group:(.*)EndGroup:/) { + $self->{group} = $1; + } # if + + if (/Labels:(.*)EndLabels:/) { + my @labels = split " ", $1; + $self->{labels} = @labels ? \@labels : (); + } # if + + if (/Pred:(.*)EndPred:/) { + $self->{pred} = $1; + } # if + + if (/Type:(.*)EndType:/) { + $self->{type} = $1; + } # if + + if (/ObjectKind:(.*)EndObjectKind:/) { + $self->{objkind} = $1; + } # if + + if (/User:(.*)EndUser:/) { + $self->{user} = $1; + } # if + + if (/Xname:(.*)EndXname:/) { + $self->{xname} = $1; + } # if + } # foreach + + return; +} # updateElementInfo + +1; + +=head2 DEPENDENCIES + +=head3 ClearSCM Perl Modules + +=for html

    Clearcase

    + +=head2 INCOMPATABILITIES + +None + +=head2 BUGS AND LIMITATIONS + +There are no known bugs in this module. + +Please report problems to Andrew DeFaria . + +=head2 LICENSE AND COPYRIGHT + +Copyright (c) 2007, ClearSCM, Inc. All rights reserved. + +=cut diff --git a/lib/Clearcase/Server.pm b/lib/Clearcase/Server.pm new file mode 100644 index 0000000..436df28 --- /dev/null +++ b/lib/Clearcase/Server.pm @@ -0,0 +1,270 @@ +=pod + +=head1 NAME $RCSfile: Server.pm,v $ + +Object oriented interface to a Clearcase Server + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.2 $ + +=item Created + +Sat Dec 18 09:51:15 EST 2010 + +=item Modified + +$Date: 2011/01/02 04:59:36 $ + +=back + +=head2 SYNOPSIS + +Provides access to information about a Clearcase Server. +=head2 DESCRIPTION + +This module implements an object oriented interface to a Clearcase +Server. + +=head2 ROUTINES + +The following routines are exported: + +=cut + +package Clearcase::Server; + +use strict; +use warnings; + +use Clearcase; + +=pod + +=head2 new (tag) + +Construct a new Clearcase View object. Note that not all members are +initially populated because doing so would be time consuming. Such +member variables will be expanded when accessed. + +Parameters: + +=for html
    + +=over + +=item tag + +View tag to be instantiated. You can use either an object oriented call +(i.e. my $view = new Clearcase::View (tag => 'my_new_view')) or the +normal call (i.e. my $vob = new Clearcase::View ('my_new_view')). You +can also instantiate a new view by supplying a tag and then later +calling the create method. + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Clearcase View object + +=back + +=for html
    + +=cut + +sub new ($;$) { + my ($class, $name) = @_; + + my $self = bless { name => $name }, $class; + + return $self; +} # new + +sub name () { + my ($self) = @_; + + return $self->{name}; +} # name + +sub ccVer () { + my ($self) = @_; + + return $self->{ccVer}; +} # ccVer + +sub osVer () { + my ($self) = @_; + + return $self->{osVer}; +} # osVer + +sub hardware () { + my ($self) = @_; + + return $self->{hardware}; +} # hardware + +sub licenseHost () { + my ($self) = @_; + + return $self->{licenseHost}; +} # licenseHost + +sub registryHost () { + my ($self) = @_; + + return $self->{registryHost}; +} # registryHost + +sub registryRegion () { + my ($self) = @_; + + return $self->{registryRegion}; +} # registryRegion + +sub mvfsBlocksPerDirectory () { + my ($self) = @_; + + return $self->{mvfsBlocksPerDirectory}; +} # mvfsBlocksPerDirectory + +sub mvfsCleartextMnodes () { + my ($self) = @_; + + return $self->{mvfsCleartextMnodes}; +} # mvfsCleartextMnodes + +sub mvfsDirectoryNames () { + my ($self) = @_; + + return $self->{mvfsDirectoryNames}; +} # mvfsDirectoryNames + +sub mvfsFileNames () { + my ($self) = @_; + + return $self->{mvfsFileNames}; +} # mvfsFileNames + +sub mvfsFreeMnodes () { + my ($self) = @_; + + return $self->{mvfsFreeMnodes}; +} # mvfsFreeMnodes + +sub mvfsInitialMnodeTableSize () { + my ($self) = @_; + + return $self->{mvfsInitialMnodeTableSize}; +} # mvfsInitialMnodeTableSize + +sub mvfsMinCleartextMnodes () { + my ($self) = @_; + + return $self->{mvfsMinCleartextMnodes}; +} # mvfsMinCleartextMnodes + +sub mvfsMinFreeMnodes () { + my ($self) = @_; + + return $self->{mvfsMinFreeMnodes}; +} # mvfsMinFreeMnodes + +sub mvfsNamesNotFound () { + my ($self) = @_; + + return $self->{mvfsNamesNotFound}; +} # mvfsNamesNotFound + +sub mvfsRPCHandles () { + my ($self) = @_; + + return $self->{mvfsRPCHandles}; +} # mvfsRPCHandles + +sub interopRegion () { + my ($self) = @_; + + return $self->{interopRegion}; +} # interopRegion + +sub scalingFactor () { + my ($self) = @_; + + return $self->{scalingFactor}; +} # scalingFactor + +sub cleartextIdleLifetime () { + my ($self) = @_; + + return $self->{cleartextIdleLifetime}; +} # cleartextIdleLifetime + +sub vobHashTableSize () { + my ($self) = @_; + + return $self->{vobHashTableSize}; +} # vobHashTableSize + +sub cleartextHashTableSize () { + my ($self) = @_; + + return $self->{cleartextHashTableSize}; +} # cleartextHashTableSize + +sub dncHashTableSize () { + my ($self) = @_; + + return $self->{dncHashTableSize}; +} # dncHashTableSize + +sub threadHashTableSize () { + my ($self) = @_; + + return $self->{threadHashTableSize}; +} # threadHashTableSize + +sub processHashTableSize () { + my ($self) = @_; + + return $self->{processHashTableSize}; +} # processHashTableSize + +1; + +=pod + +=head2 DEPENDENCIES + +=for html

    Clearcase

    + +=head2 INCOMPATABILITIES + +None + +=head2 BUGS AND LIMITATIONS + +There are no known bugs in this module. + +Please report problems to Andrew DeFaria . + +=head2 LICENSE AND COPYRIGHT + +Copyright (c) 2007, ClearSCM, Inc. All rights reserved. + +=cut diff --git a/lib/Clearcase/UCM.pm b/lib/Clearcase/UCM.pm new file mode 100644 index 0000000..525cad6 --- /dev/null +++ b/lib/Clearcase/UCM.pm @@ -0,0 +1,168 @@ +=pod + +=head1 NAME $RCSfile: UCM.pm,v $ + +Object oriented interface to UCM Streams + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.2 $ + +=item Created + +Fri May 14 18:16:16 PDT 2010 + +=item Modified + +$Date: 2011/11/16 19:46:13 $ + +=back + +=head1 SYNOPSIS + +Provides access to information about Clearcase Elements. + + my $stream= new Clearcase::UCM::Stream ($name, $pvob); + +=head1 DESCRIPTION + +This module implements a UCM Stream object + +=head1 ROUTINES + +The following routines are exported: + +=cut + +package Clearcase::UCM; + +use strict; +use warnings; + +use Clearcase; +use Clearcase::Vob; +use Clearcase::Vobs; + +sub new ($) { + my ($class, $stream) = @_; + +=pod + +=head2 new + +Construct a new Clearcase Stream object. + +Parameters: + +=for html
    + +=over + +=item stream name + +Name of stream + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Clearcase Stream object + +=back + +=for html
    + +=cut + + return bless { + }, $class; # bless +} # new + +sub pvobs () { + my ($self) = @_; + +=pod + +=head2 pvob + +Returns the pvob of the stream + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item stream's pvob + +=back + +=for html
    + +=cut + + my @pvobs; + + my $VOBs = Clearcase::Vobs->new; + + foreach my $vobtag ($VOBs->vobs) { + my $VOB = Clearcase::Vob->new ("$Clearcase::VOBTAG_PREFIX$vobtag"); + my $attr = $VOB->vob_registry_attributes; + + if ($attr and $attr =~ /ucmvob/) { + push @pvobs, $vobtag; + } # if + } # foreach + + return @pvobs; +} # pvobs + +1; + +=head1 DEPENDENCIES + +=head2 ClearSCM Perl Modules + +=for html

    Clearcase

    + +=head1 INCOMPATABILITIES + +None + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this module. + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2007, ClearSCM, Inc. All rights reserved. + +=cut diff --git a/lib/Clearcase/UCM/Activity.pm b/lib/Clearcase/UCM/Activity.pm new file mode 100644 index 0000000..3b76770 --- /dev/null +++ b/lib/Clearcase/UCM/Activity.pm @@ -0,0 +1,862 @@ +=pod + +=head1 NAME $RCSfile: Activity.pm,v $ + +Object oriented interface to UCM Activities + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.10 $ + +=item Created + +Fri May 14 18:16:16 PDT 2010 + +=item Modified + +$Date: 2011/11/15 01:56:40 $ + +=back + +=head1 SYNOPSIS + +Provides access to information about Clearcase Activites. + + my $activity = new Clearcase::UCM::Activity ($name, $pvob); + + my @changeset = $activity->changeset; + + foreach my $element (@changeset) { + display "Element name: " . $element->pname; + display "Element verison: " . $element->version; + } # foreach + +=head1 DESCRIPTION + +This module implements a UCM Activity object + +=head1 ROUTINES + +The following routines are exported: + +=cut + +package Clearcase::UCM::Activity; + +use strict; +use warnings; + +use lib '../..'; + +use Clearcase; +use Clearcase::Element; + +# We should really inherit these from a more generic super class... +sub _processOpts (%) { + my ($self, %opts) = @_; + + my $opts; + + foreach (keys %opts) { + if ($_ eq 'cq' or $_ eq 'cqe' or $_ eq 'force' or $_ eq 'nc') { + $opts .= "-$_ "; + } elsif ($_ eq 'c' or $_ eq 'cfile') { + $opts .= "-$_ $opts{$_}"; + } # if + } # foreach + + return $opts; +} # _processOpts + +sub new ($$) { + my ($class, $activity, $pvob) = @_; + +=pod + +=head2 new + +Construct a new Clearcase Activity object. + +Parameters: + +=for html
    + +=over + +=item activity name + +Name of activity + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Clearcase Activity object + +=back + +=for html
    + +=cut + + my $self = bless { + name => $activity, + pvob => Clearcase::vobtag ($pvob), + type => $activity =~ /^(deliver|rebase)./ ? 'integration' : 'regular', + }, $class; # bless + + return $self; +} # new + +sub name () { + my ($self) = @_; + +=pod + +=head2 name + +Returns the name of the activity + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item activity's name + +=back + +=for html
    + +=cut + + return $self->{name}; +} # name + +sub pvob () { + my ($self) = @_; + +=pod + +=head2 pvob + +Returns the pvob of the activity + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item activity's pvob + +=back + +=for html
    + +=cut + + return $self->{pvob}; +} # pvob + +sub type () { + my ($self) = @_; + +=pod + +=head2 type + +Returns the type of the activity + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item activity's type + +=back + +=for html
    + +=cut + + return $self->{type}; +} # type + +sub contrib_acts () { + my ($self) = @_; + +=pod + +=head2 contrib_acts + +Returns the contributing activities + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Array of contributing activities + +=back + +=for html
    + +=cut + + $self->updateActivityInfo () unless $self->{contrib_acts}; + + return $self->{contrib_acts}; +} # crm_record + +sub crm_record_id () { + my ($self) = @_; + +=pod + +=head2 crm_record_id + +Returns the crm_record_id of the activity + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item activity's crm_record_id + +=back + +=for html
    + +=cut + + $self->updateActivityInfo () unless $self->{crm_record_id}; + + return $self->{crm_record_id}; +} # crm_record_id + +sub crm_record_type () { + my ($self) = @_; + +=pod + +=head2 crm_record_type + +Returns the crm_record_type of the activity + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item activity's crm_record_type + +=back + +=for html
    + +=cut + + $self->updateActivityInfo () unless $self->{crm_record_type}; + + return $self->{crm_record_type}; +} # crm_record_type + +sub crm_state () { + my ($self) = @_; + +=pod + +=head2 crm_state + +Returns the crm_state of the activity + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item activity's crm_state + +=back + +=for html
    + +=cut + + $self->updateActivityInfo () unless $self->{crm_state}; + + return $self->{crm_state}; +} # crm_state + +sub headline () { + my ($self) = @_; + +=pod + +=head2 headline + +Returns the headline of the activity + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item activity's headline + +=back + +=for html
    + +=cut + + $self->updateActivityInfo () unless $self->{headline}; + + return $self->{headline}; +} # headline + +sub name_resolver_view () { + my ($self) = @_; + +=pod + +=head2 name_resolver_view + +Returns the name_resolver_view of the activity + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item activity's name_resolver_view + +=back + +=for html
    + +=cut + + $self->updateActivityInfo () unless $self->{name_resolver_view}; + + return $self->{name_resolver_view}; +} # name_resolver_view + +sub stream () { + my ($self) = @_; + +=pod + +=head2 stream + +Returns the stream of the activity + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item activity's stream + +=back + +=for html
    + +=cut + + $self->updateActivityInfo () unless $self->{stream}; + + return $self->{stream}; +} # stream + +sub changeset (;$) { + my ($self, $recalc) = @_; + +=pod + +=head2 changeset + +Returns the changeset of the activity + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item An array containing Clearcase::Element objects. + +=back + +=for html
    + +=cut + + if ($self->{changeset}) { + return $self->{changeset} unless ($recalc); + } # if + + my $pvob = Clearcase::vobtag $self->{pvob}; + + my $cmd = "lsact -fmt \"%[versions]CQp\" $self->{name}\@$pvob"; + + my ($status, @output) = $Clearcase::CC->execute ($cmd); + + return ($status, @output) + if $status; + + # Need to split up change set. It's presented to us as quoted and space + # separated however the change set elements themselves can have spaces in + # them! e.g.: + # + # "/vob/foo/file name with spaces@@/main/1", "/vob/foo/file name2@@/main/2" + # + # So we'll split on '", ""'! Note that this will leave us with the first + # element with a leading '"' and the last element with a trailing '"' which + # we will have to handle. + # + # Additionally we will call collapseOverExtendedViewPathname to normalize + # the over extended pathnames to element hashes. + my (@changeset); + + @output = split /\", \"/, $output[0] + if $output[0]; + + foreach (@output) { + # Skip any cleartool warnings. We are getting warnings of the form: + # "A version in the change set of activity "63332.4" is currently + # unavailable". Probably some sort of subtle corruption that we can ignore. + # (It should be fixed but we aren't going to be doing that here!) + next if /cleartool: Warning/; + + # Strip any remaining '"'s + s/^\"//; s/\"$//; + + my %element = Clearcase::Element::collapseOverExtendedVersionPathname $_; + my $element = Clearcase::Element->new ($element{name}); + + # Sometimes $element{name} refers to a long path name we can't easily see + # in our current view. In such cases the above Clearcase::Element->new will + # return us an element where the version is missing. Since we already have + # the version information we will replace it here. + # + # The following may look odd since we use similar names against different + # Perl variables. $element->{version} means look into the $element object + # returned from new above at the member version. $element{version} says + # refer to the %element hash defined above for the version key. And finally + # $element->version says call the method version of the element object. + # So we are saying, if the version member of the element object is not + # defined (i.e. $element->version) then set it (i.e. $element->{version}) + # by using the value of the hash %element with the key version. + $element->{version} = $element{version} + unless $element->version; + + # Additionally we will set into the $element object the extended name. This + # is the long pathname that we need to use from our current context to be + # able to access the element. + #$element->setExtendedName ($_); + + push @changeset, $element; + } # foreach + + $self->{changeset} = \@changeset; + + return @changeset; +} # changeset + +sub create ($$$;$) { + my ($self, $stream, $pvob, $headline, $opts) = @_; + +=pod + +=head2 create + +Creates a new UCM Activity + +Parameters: + +=for html
    + +=over + +=item UCM Stream (required) + +UCM stream this activities is to be created on + +=item PVOB (Required) + +Project Vob + +=item headline + +Headline to associate with this activity + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $status + +Status from cleartool + +=item @output + +Ouput from cleartool + +=back + +=for html
    + +=cut + + # Fill in members + $self->{stream} = $stream; + $self->{pvob} = $pvob; + + # TODO: Should quote $headline to protect from special characters + $self->{headline} = $headline; + + # Fill in opts + $opts ||= ''; + $opts .= " -headline '$headline'" + if $headline; + + # TODO: This should call the exists function + # Return the stream name if the stream already exists + my ($status, @output) = + $Clearcase::CC->execute ('lsact -short ' . $self->{name}); + + return ($status, @output) + unless $status; + + # Need to create the stream + return $Clearcase::CC->execute + ("mkactivity $opts -in " . $stream . + "\@" . $pvob . + ' ' . $self->{name}); +} # create + +sub remove () { + my ($self) = @_; + +=pod + +=head2 remove + +Removes UCM Activity + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $status + +Status from cleartool + +=item @output + +Ouput from cleartool + +=back + +=for html
    + +=cut + + return $Clearcase::CC->execute + ('rmactivity -f ' . $self->{name} . "\@" . $self->{pvob}); +} # remove + +sub attributes (;%) { + my ($self, %newAttribs) = @_; + +=pod + +=head2 attributes + +Returns a hash of the attributes associated with an activity + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item %attributes + +Hash of attributes for this activity + +=back + +=for html
    + +=cut + + return $self->Clearcase::attributes ( + 'activity', + "$self->{name}\@" . Clearcase::vobtag ($self->{pvob}), + %newAttribs, + ); +} # attributes + +sub updateActivityInfo () { + my ($self) = @_; + + # Get all information that can be gotten using -fmt + my $fmt .= '%[crm_record_id]p=='; + $fmt .= '%[crm_record_type]p=='; + $fmt .= '%[crm_state]p=='; + $fmt .= '%[headline]p=='; + $fmt .= '%[name_resolver_view]p=='; + $fmt .= '%[stream]Xp=='; + $fmt .= '%[view]p'; + + if ($self->type eq 'integration') { + $fmt = '%[contrib_acts]CXp=='; + } # if + + $Clearcase::CC->execute ( + "lsactivity -fmt \"$fmt\" $self->{name}@" . Clearcase::vobtag ($self->{pvob}) + ); + + # Assuming this activity is an empty shell of an object that the user may + # possibly use the create method on, return our blessings... + return if $Clearcase::CC->status; + + # We need to make sure that fields are filled in or empty because we are using + # undef as an indication that we have not called updateActivityInfo yet. + my @fields = split '==', $Clearcase::CC->output; + + $self->{crm_record_id} = $fields[0]; + $self->{crm_record_type} = $fields[1]; + $self->{crm_state} = $fields[2]; + $self->{headline} = $fields[3]; + $self->{name_resolver_view} = $fields[4]; + $self->{stream} = $fields[5]; + $self->{view} = $fields[6]; + + $self->{contrib_acts} = (); + + if ($self->type eq 'integration') { + foreach (split ', ', $fields[7]) { + push @{$self->{contrib_acts}}, Clearcase::UCM::Activity->new ($_); + } # foreach + } # if + + return; +} # updateActivityInfo + +1; + +=head1 DEPENDENCIES + +=head2 ClearSCM Perl Modules + +=for html

    Clearcase

    + +=head1 INCOMPATABILITIES + +None + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this module. + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2007, ClearSCM, Inc. All rights reserved. + +=cut diff --git a/lib/Clearcase/UCM/Baseline.pm b/lib/Clearcase/UCM/Baseline.pm new file mode 100644 index 0000000..ab22f9c --- /dev/null +++ b/lib/Clearcase/UCM/Baseline.pm @@ -0,0 +1,492 @@ +=pod + +=head1 NAME $RCSfile: Baseline.pm,v $ + +Object oriented interface to UCM Streams + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.4 $ + +=item Created + +Fri May 14 18:16:16 PDT 2010 + +=item Modified + +$Date: 2011/11/15 01:59:07 $ + +=back + +=head1 SYNOPSIS + +Provides access to information about Clearcase Elements. + + my $stream= new Clearcase::UCM::Stream ($name, $pvob); + +=head1 DESCRIPTION + +This module implements a UCM Stream object + +=head1 ROUTINES + +The following routines are exported: + +=cut + +package Clearcase::UCM::Baseline; + +use strict; +use warnings; + +use Carp; + +use lib '../..'; + +use Clearcase; +use Clearcase::Element; +use Clearcase::UCM::Activity; + +sub _processOpts (%) { + my ($self, %opts) = @_; + + my $opts; + + foreach (keys %opts) { + if ($_ eq 'cq' or $_ eq 'cqe' or $_ eq 'force' or $_ eq 'nc') { + $opts .= "-$_ "; + } elsif ($_ eq 'c' or $_ eq 'cfile') { + $opts .= "-$_ $opts{$_}"; + } # if + } # foreach + + + return $opts; +} # _processOpts + +sub new ($$) { + my ($class, $baseline, $pvob) = @_; + +=pod + +=head2 new + +Construct a new Clearcase Stream object. + +Parameters: + +=for html
    + +=over + +=item stream name + +Name of stream + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Clearcase Stream object + +=back + +=for html
    + +=cut + + my $self = bless { + name => $baseline, + pvob => Clearcase::vobtag $pvob, + }, $class; # bless + + return $self; +} # new + +sub name () { + my ($self) = @_; + +=pod + +=head2 name + +Returns the name of the stream + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item stream's name + +=back + +=for html
    + +=cut + + return $self->{name}; +} # name + +sub pvob () { + my ($self) = @_; + +=pod + +=head2 pvob + +Returns the pvob of the stream + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item stream's pvob + +=back + +=for html
    + +=cut + + return $self->{pvob}; +} # pvob + +sub create ($$;$$) { + my ($self, $project, $pvob, $baseline, $opts) = @_; + +=pod + +=head2 create + +Creates a new UCM Stream Object + +Parameters: + +=for html
    + +=over + +=item UCM Project (required) + +UCM Project this stream belongs to + +=item PVOB (Required) + +Project Vob + +=item baseline + +Baseline to set this stream to + +=item opts + +Options: Additional options to use (e.g. -readonly) + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $status + +Status from cleartool + +=item @output + +Ouput from cleartool + +=back + +=for html
    + +=cut + + # Fill in object members + $self->{project} = $project; + $self->{pvob} = $pvob; + + # Fill in opts + $opts ||= ''; + $opts .= " -baseline $baseline" + if $baseline; + + $self->{readonly} = $opts =~ /-readonly/; + + # TODO: This should call the exists function + # Return the stream name if the stream already exists + my ($status, @output) = + $Clearcase::CC->execute ('lsstream -short ' . $self->{name}); + + return ($status, @output) + unless $status; + + # Need to create the stream + return $Clearcase::CC->execute + ("mkstream $opts -in " . $self->{project} . + "\@" . $self->{pvob} . + ' ' . $self->{name}); +} # create + +sub remove (\%) { + my ($self, %opts) = @_; + +=pod + +=head2 remove + +Removes UCM Baseline + +Parameters: + +=for html
    + +=over + +=item none + +=item %opts + +Options: Additional options to use (e.g. -c, -force, etc.) + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item nothing + +Remember to check status method for error, and/or output method for output. + +=back + +=for html
    + +=cut + + my $opts = $self->_processOpts (%opts); + + my $pvob = Clearcase::vobtag ($self->{pvob}); + + my ($status, @output) = $Clearcase::CC->execute + ("rmbl $opts " . $self->{name} . '@' . $pvob); + + return; +} # remove + +sub attributes () { + my ($self) = @_; + +=pod + +=head2 attributes + +Returns a hash of the attributes associated with a baseline + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item %attributes + +Hash of attributes for this baseline + +=back + +=for html
    + +=cut + + return $self->Clearcase::attributes ( + 'baseline', + "$self->{name}\@" . Clearcase::vobtag ($self->{pvob}) + ); +} # attributes + +sub diff ($;$$) { + my ($self, $type, $baseline, %opts) = @_; + +=pod + +=head2 diff + +Returns a hash of information regarding the difference between two baselines or +a baseline and the stream (AKA "top of stream"). + +Parameters: + +=for html
    + +=over + +=item [activities|versions|baselines] + +Must specify one of [activities|versions|baselines]. Information will be +returned based on this parameter. + +=item $baseline or $stream + +Specify the baseline or stream to compare to. If not specified a -predeccsor +diffbl will be done. If a stream use "stream:" otherwise use +"baseline:" or simply "". + +=item %opts + +Additional options. + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item %info + +Depending on whether activites, versions or baselines were specified, the +returned hash will be constructed with the key being the activity, version +string or baseline name as the key with additional information specified as the +value. + +=back + +=for html
    + +=cut + + unless ($type =~ /^activities$/i or + $type =~ /^versions$/i or + $type =~ /^baselines$/i) { + croak "Type must be one of activities, versions or baselines in " + . "Clearcase::UCM::Baseline::diff - not $type"; + } # unless + + my $myBaseline = "$self->{name}\@$self->{pvob}"; + + my $cmd = "diffbl -$type"; + + if ($baseline) { + if ($baseline =~ /(\S+):/) { + unless ($1 eq 'baseline' or $1 eq 'stream') { + croak "Baseline should be baseline: or stream: or " + . "just "; + } # unless + } # if + + $baseline .= "\@$self->{pvob}" unless $baseline =~ /\@/; + + $cmd .= " $myBaseline $baseline"; + } else { + $cmd .= " -predeccsor"; + } # if + + $Clearcase::CC->execute ($cmd); + + return if $Clearcase::CC->status; + + my @output = $Clearcase::CC->output; + + my %info; + + foreach (@output) { + next unless /^(\>\>|\<\<)/; + + if (/(\>\>|\<\<)\s+(\S+)\@/) { + $info{$2} = Clearcase::UCM::Activity->new ($2, $self->{pvob}); + } # if + } # foreach + + return %info; +} # diff + +1; + +=head1 DEPENDENCIES + +=head2 ClearSCM Perl Modules + +=for html

    Clearcase

    + +=head1 INCOMPATABILITIES + +None + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this module. + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2007, ClearSCM, Inc. All rights reserved. + +=cut diff --git a/lib/Clearcase/UCM/Pvob.pm b/lib/Clearcase/UCM/Pvob.pm new file mode 100644 index 0000000..db5093a --- /dev/null +++ b/lib/Clearcase/UCM/Pvob.pm @@ -0,0 +1,208 @@ +=pod + +=head1 NAME $RCSfile: Pvob.pm,v $ + +Object oriented interface to a UCM Pvob + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.1 $ + +=item Created + +Fri May 14 18:16:16 PDT 2010 + +=item Modified + +$Date: 2011/11/09 01:52:39 $ + +=back + +=head1 SYNOPSIS + +Provides access to information about a Clearcase Pvob. + + my $pvob = new Clearcase::UCM::Pvob ($name); + +=head1 DESCRIPTION + +This module implements a UCM Pvob object + +=head1 ROUTINES + +The following routines are exported: + +=cut + +package Clearcase::UCM::Pvob; + +use strict; +use warnings; + +use Clearcase; +use Clearcase::UCM::Stream; + +sub new ($) { + my ($class, $name) = @_; + +=pod + +=head2 new + +Construct a new Clearcase Pvob object. + +Parameters: + +=for html
    + +=over + +=item pvob name + +Name of pvob + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Clearcase Pvob object + +=back + +=for html
    + +=cut + + my $self = bless { + name => $name, + }, $class; # bless + + return $self; +} # new + +sub name () { + my ($self) = @_; + +=pod + +=head2 name + +Returns the name of the pvob + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item pvob's name + +=back + +=for html
    + +=cut + + return $self->{name}; +} # name + +sub streams () { + my ($self) = @_; + +=pod + +=head2 streams + +Returns an array of stream objects in the pvob + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item array of stream objects in the pvob + +=back + +=for html
    + +=cut + + my $cmd = "lsstream -short -invob $self->{name}"; + + $Clearcase::CC->execute ($cmd); + + return if $Clearcase::CC->status; + + my @streams; + + push @streams, Clearcase::UCM::Stream->new ($_, $self->{name}) + foreach ($Clearcase::CC->output); + + return @streams; +} # streams + +1; + +=head1 DEPENDENCIES + +=head2 ClearSCM Perl Modules + +=for html

    Clearcase

    + +=for html

    Clearcase::UCM::Baseline

    + +=head1 INCOMPATABILITIES + +None + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this module. + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2007, ClearSCM, Inc. All rights reserved. + +=cut diff --git a/lib/Clearcase/UCM/Stream.pm b/lib/Clearcase/UCM/Stream.pm new file mode 100644 index 0000000..43824e1 --- /dev/null +++ b/lib/Clearcase/UCM/Stream.pm @@ -0,0 +1,392 @@ +=pod + +=head1 NAME $RCSfile: Stream.pm,v $ + +Object oriented interface to UCM Streams + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.8 $ + +=item Created + +Fri May 14 18:16:16 PDT 2010 + +=item Modified + +$Date: 2011/11/15 02:00:58 $ + +=back + +=head1 SYNOPSIS + +Provides access to information about Clearcase Elements. + + my $stream= new Clearcase::UCM::Stream ($name, $pvob); + +=head1 DESCRIPTION + +This module implements a UCM Stream object + +=head1 ROUTINES + +The following routines are exported: + +=cut + +package Clearcase::UCM::Stream; + +use strict; +use warnings; + +use Clearcase; +use Clearcase::UCM::Baseline; + +sub new ($$) { + my ($class, $stream, $pvob) = @_; + +=pod + +=head2 new + +Construct a new Clearcase Stream object. + +Parameters: + +=for html
    + +=over + +=item stream name + +Name of stream + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Clearcase Stream object + +=back + +=for html
    + +=cut + + my $self = bless { + name => $stream, + pvob => Clearcase::vobtag $pvob, + }, $class; # bless + + return $self; +} # new + +sub name () { + my ($self) = @_; + +=pod + +=head2 name + +Returns the name of the stream + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item stream's name + +=back + +=for html
    + +=cut + + return $self->{name}; +} # name + +sub pvob () { + my ($self) = @_; + +=pod + +=head2 pvob + +Returns the pvob of the stream + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item stream's pvob + +=back + +=for html
    + +=cut + + return $self->{pvob}; +} # pvob + +sub create ($$;$$) { + my ($self, $project, $pvob, $baseline, $opts) = @_; + +=pod + +=head2 create + +Creates a new UCM Stream Object + +Parameters: + +=for html
    + +=over + +=item UCM Project (required) + +UCM Project this stream belongs to + +=item PVOB (Required) + +Project Vob + +=item baseline + +Baseline to set this stream to + +=item opts + +Options: Additional options to use (e.g. -readonly) + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $status + +Status from cleartool + +=item @output + +Ouput from cleartool + +=back + +=for html
    + +=cut + + # Fill in object members + $self->{project} = $project; + $self->{pvob} = $pvob; + + # Fill in opts + $opts ||= ''; + $opts .= " -baseline $baseline" + if $baseline; + + $self->{readonly} = $opts =~ /-readonly/; + + # TODO: This should call the exists function + # Return the stream name if the stream already exists + my ($status, @output) = + $Clearcase::CC->execute ('lsstream -short ' . $self->{name}); + + return ($status, @output) + unless $status; + + # Need to create the stream + return $Clearcase::CC->execute + ("mkstream $opts -in " . $self->{project} . + "\@" . $self->{pvob} . + ' ' . $self->{name}); +} # create + +sub remove () { + my ($self) = @_; + +=pod + +=head2 remove + +Removes UCM Stream + +Parameters: + +=for html
    + +=over + +=item UCM Project (required) + +UCM Project this stream belongs to + +=item PVOB (Required) + +Project Vob + +=item baseline + +Baseline to set this stream to + +=item opts + +Options: Additional options to use (e.g. -readonly) + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $status + +Status from cleartool + +=item @output + +Ouput from cleartool + +=back + +=for html
    + +=cut + + return $Clearcase::CC->execute + ('rmstream -f ' . $self->{name} . "\@" . $self->{pvob}); +} # rmStream + +sub baselines () { + my ($self) = @_; + +=pod + +=head2 baselines + +Returns baseline objects associated with the stream + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item @baselines + +An array of baseline objects for this stream + +=back + +=for html
    + +=cut + + my $cmd = "lsbl -short -stream $self->{name}\@$self->{pvob}"; + + $Clearcase::CC->execute ($cmd); + + return if $Clearcase::CC->status; + + my @baselines; + + foreach ($Clearcase::CC->output) { + my $baseline = Clearcase::UCM::Baseline->new ($_, $self->{pvob}); + + push @baselines, $baseline; + } # foreach + + return @baselines; +} # baselines + +1; + +=head1 DEPENDENCIES + +=head2 ClearSCM Perl Modules + +=for html

    Clearcase

    + +=for html

    Clearcase::UCM::Baseline

    + +=head1 INCOMPATABILITIES + +None + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this module. + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2007, ClearSCM, Inc. All rights reserved. + +=cut diff --git a/lib/Clearcase/View.pm b/lib/Clearcase/View.pm new file mode 100644 index 0000000..a3f2124 --- /dev/null +++ b/lib/Clearcase/View.pm @@ -0,0 +1,1862 @@ +=pod + +=head1 NAME $RCSfile: View.pm,v $ + +Object oriented interface to a Clearcase View + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.18 $ + +=item Created + +Thu Dec 29 12:07:59 PST 2005 + +=item Modified + +$Date: 2011/11/16 19:46:13 $ + +=back + +=head1 SYNOPSIS + +Provides access to information about a Clearcase View. Note that some +information about a view is not populated into the view object at +object instantiation. This is because members such as labels can be +very long and time consuming to acquire. When the caller request such +fields they are expanded. + + # Create View object + my $view = new Clearcase::View (tag => 'test'); + + # Access member variables... + display "View:\t\t \t" . $view->tag; + display "Accessed by:\t\t" . $view->accessed_by; + display "Accessed date:\t\t" . $view->accessed_date; + display "Access path:\t\t" . $view->access_path; + display "Active:\t\t\t" . $view->active; + + display_nolf MAGENTA . "Additional groups:\t"; + + foreach ($view->additional_groups) { + display_nolf "$_ "; + } # foreach + + display ''; + + display "Created by:\t\t" . $view->created_by; + display "Created date:\t\t" . $view->created_date; + display "CS updated by:\t\t" . $view->cs_updated_by; + display "CS updated date:\t" . $view->cs_updated_date; + display "Global path:\t\t" . $view->gpath; + display "Group:\t\t\t" . $view->group; + display "Group mode:\t\t" . $view->group_mode; + display "Host:\t\t\t" . $view->host; + display "Mode:\t\t\t" . $view->mode; + display "Modified by:\t\t" . $view->modified_by; + display "Modified date:\t\t" . $view->modified_date; + display "Other mode:\t\t" . $view->other_mode; + display "Owner:\t\t\t" . $view->owner; + display "Owner mode:\t\t" . $view->owner_mode; + display "Properties:\t\t" . $view->properties; + display "Region:\t\t\t" . $view->region; + display "Server host:\t\t" . $view->shost; + display "Text mode:\t\t" . $view->text_mode; + display "UUID:\t\t\t" . $view->uuid; + + display_nolf "Type:\t\t\t"; + + if ($view->snapshot) { + display_nolf 'snapshot'; + } else { + display_nolf 'dynamic'; + } # if + + if ($view->ucm) { + display_nolf ',ucm'; + } # if + + display ''; + + # View manipulation + my $new_view = new Clearcase::View ($ENV{USER} . '_testview'); + + $new_view->create; + + # Start new view + $new_view->start; + + # Set to view + $new_view->set; + + # Stop view + $new_view->stop; + + # Stop view server process + $new_view->kill; + + # Remove view + if ($new_view->exists) { + $new_view->remove; + } # if + +=head1 DESCRIPTION + +This module implements an object oriented interface to a Clearcase +view. + +=head1 ROUTINES + +The following routines are exported: + +=cut + +package Clearcase::View; + +use strict; +use warnings; + +use Clearcase; +use Display; + +sub new ($;$) { + my ($class, $tag, $region) = @_; + +=pod + +=head2 new (tag) + +Construct a new Clearcase View object. Note that not all members are +initially populated because doing so would be time consuming. Such +member variables will be expanded when accessed. + +Parameters: + +=for html
    + +=over + +=item tag + +View tag to be instantiated. You can use either an object oriented call +(i.e. my $view = new Clearcase::View (tag => 'my_new_view')) or the +normal call (i.e. my $vob = new Clearcase::View ('my_new_view')). You +can also instantiate a new view by supplying a tag and then later +calling the create method. + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Clearcase View object + +=back + +=for html
    + +=cut + + my $self = bless { tag => $tag }, $class; + + $self->updateViewInfo ($region); + + return $self; +} # new + +sub accessed_by () { + my ($self) = @_; + +=pod + +=head2 accessed_by + +Returns the user name of the last user to access the view. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item user name + +=back + +=for html
    + +=cut + + return $self->{accessed_by}; +} # accessed_by + +sub accessed_date () { + my ($self) = @_; + +=pod + +=head2 accessed_date + +Returns the date the view was last accessed. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item access date + +=back + +=for html
    + +=cut + + return $self->{accessed_date}; +} # accessed_date + +sub access_path () { + my ($self) = @_; + +=pod + +=head2 access_path + +Returns the access path of the view. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item access path + +=back + +=for html
    + +=cut + + return $self->{access_path}; +} # access_path + +sub active () { + my ($self) = @_; + +=pod + +=head2 active + +Returns true if the view is active + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item boolean + +=back + +=for html
    + +=cut + + return $self->{active}; +} # active + +sub additional_groups () { + my ($self) = @_; + +=pod + +=head2 additional_groups + +Returns the additional groups that have permission to access this +view. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item An array of additional groups + +=back + +=for html
    + +=cut + + if ($self->{additional_groups}) { + return @{$self->{additional_groups}}; + } else { + return (); + } # if +} # additional_groups + +sub created_by () { + my ($self) = @_; + +=pod + +=head2 created_by + +Returns the user name who created the view + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item user name + +=back + +=for html
    + +=cut + + return $self->{created_by}; +} # created_by + +sub created_date () { + my ($self) = @_; + +=pod + +=head2 created_date + +Returns the date the view was created. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item date + +=back + +=for html
    + +=cut + + return $self->{created_date}; +} # created_date + +sub cs_updated_by () { + my ($self) = @_; + +=pod + +=head2 cs_updated_date + +Returns the user name of the last user to access the view. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item date + +=back + +=for html
    + +=cut + + return $self->{cs_updated_by}; +} # cs_updated_by + +sub cs_updated_date () { + my ($self) = @_; + +=pod + +=head2 dynamic + +Returns the date the config spec for this view was updated. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item date + +=back + +=for html
    + +=cut + + return $self->{cs_updated_date}; +} # cs_updated_date + +sub dynamic () { + my ($self) = @_; + +=pod + +=head2 dynamic + +Returns true if the view is a dynamic view - false otherwise. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item boolean + +=back + +=for html
    + +=cut + + return $self->type eq 'dynamic'; +} # dynamic + +sub gpath () { + my ($self) = @_; + +=pod + +=head2 gpath + +Returns the global path to the view + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item global path + +=back + +=for html
    + +=cut + + return $self->{gpath}; +} # gpath + +sub group () { + my ($self) = @_; + +=pod + +=head2 group + +Returns the group of the user who created the view. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item group name + +=back + +=for html
    + +=cut + + return $self->{group}; +} # group + +sub group_mode () { + my ($self) = @_; + +=pod + +=head2 group_mode + +Returns the group mode of the view. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item A string representing the group mode + +=back + +=for html
    + +=cut + + return $self->{group_mode}; +} # group_mode + +sub host () { + my ($self) = @_; + +=pod + +=head2 host + +Returns the host that the view resides on + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item host + +=back + +=for html
    + +=cut + + return $self->{host}; +} # host + +sub mode () { + my ($self) = @_; + +=pod + +=head2 mode + +Returns the numeric mode representing the view's access mode + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item numeric mode + +=back + +=for html
    + +=cut + + return $self->{mode}; +} # mode + +sub modified_by () { + my ($self) = @_; + +=pod + +=head2 modified_by + +Returns the user name of the last user to modify the view. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item user name + +=back + +=for html
    + +=cut + + return $self->{modified_by}; +} # modified_by + +sub modified_date () { + my ($self) = @_; + +=pod + +=head2 modified_date + +Returns the date the view was last modified. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item date + +=back + +=for html
    + +=cut + + return $self->{modified_date}; +} # modified_date + +sub other_mode () { + my ($self) = @_; + +=pod + +=head2 other_mode + +Returns the mode for other for the view. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item A string repesenting the other mode + +=back + +=for html
    + +=cut + + return $self->{other_mode}; +} # other_mode + +sub owner () { + my ($self) = @_; + +=pod + +=head2 owner + +Returns the user name of the owner of the view. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item user name + +=back + +=for html
    + +=cut + + return $self->{owner} +} # owner + +sub owner_mode () { + my ($self) = @_; + +=pod + +=head2 owner_mode + +Returns the mode for the owner for the view. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item A string repesenting the other mode + +=back + +=for html
    + +=cut + + return $self->{owner_mode} +} # owner_mode + +sub properties () { + my ($self) = @_; + +=pod + +=head2 properties + +Returns the properties of the view. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item properties + +=back + +=for html
    + +=cut + + return $self->{properties}; +} # properties + +sub region () { + my ($self) = @_; + +=pod + +=head2 region + +Returns the region of the view + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item region + +=back + +=for html
    + +=cut + + return $self->{region}; +} # region + +sub shost () { + my ($self) = @_; + +=pod + +=head2 shost + +Returns the server host of the view + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item server host + +=back + +=for html
    + +=cut + + return $self->{shost}; +} # shost + +sub snapshot () { + my ($self) = @_; + +=pod + +=head2 snapshot + +Returns true if the view is a snapshot view - false otherwise. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item boolean + +=back + +=for html
    + +=cut + + return $self->type eq 'snapshot'; +} # snapshot + +sub webview () { + my ($self) = @_; + +=pod + +=head2 webview + +Returns true if the view is a webview - false otherwise. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item boolean + +=back + +=for html
    + +=cut + + return $self->type eq 'webview'; +} # webview + +sub tag () { + my ($self) = @_; + +=pod + +=head1 tag + +Returns the tag for this view. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item tag + +=back + +=for html
    + +=cut + + return $self->{tag}; + } # tag + +sub text_mode () { + my ($self) = @_; + +=pod + +=head2 text_mode + +Returns the text_mode of the view + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item text mode + +=back + +=for html
    + +=cut + + return $self->{text_mode}; +} # tag + +sub type () { + my ($self) = @_; + +=pod + +=head2 type + +Returns the type of the view. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item type + +=back + +=for html
    + +=cut + + return $self->{type} ? $self->{type} : 'Unknown'; +} # type + +sub ucm () { + my ($self) = @_; + +=pod + +=head2 ucm + +Returns true if the view is a UCM view. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item boolean + +=back + +=for html
    + +=cut + + return $self->{ucm}; +} # ucm + +sub uuid () { + my ($self) = @_; + +=pod + +=head2 uuid + +Returns the uuid for the view. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item uuid + +=back + +=for html
    + +=cut + + return $self->{uuid}; +} # uuid + +sub exists () { + my ($self) = @_; + +=pod + +=head3 exists + +Returns true if the view exists - false otherwise. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item boolean + +=back + +=for html
    + +=cut + + my ($status, @output) = $Clearcase::CC->execute ("lsview $self->{tag}"); + + return !$status; +} # exists + +sub create (;$$$) { + my ($self, $host, $vws, $region) = @_; + +=pod + +=head2 create + +Creates a view + +Parameters: + +=for html
    + +=over + +=item host + +Host to create the view on. Default is to use -stgloc -auto. + +=item vws + +View working storage directory to use. Default is to use -stgloc -auto. + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $status + +Status from cleartool + +=item @output + +Ouput from cleartool + +=back + +=for html
    + +=cut + + $region ||= $Clearcase::CC->region; + + if ($self->exists) { + $self->updateViewInfo ($region); + + return (0, ()) + } # if + + my ($status, @output); + + if ($host && $vws) { + ($status, @output) = + $Clearcase::CC->execute ("mkview -tag $self->{tag} -region $region " + . "-host $host -hpath $vws -gpath $vws $vws"); + } else { + # Note this requires that -stgloc's work and that using -auto is not a + # problem. + ($status, @output) = + $Clearcase::CC->execute ("mkview -tag $self->{tag} -stgloc -auto"); + } # if + + $self->updateViewInfo ($region); + + return ($status, @output); +} # create + +sub createUCM ($$) { + my ($self, $stream, $pvob, $region) = @_; + +=pod + +=head2 createUCM + +Create a UCM view + +Parameters: + +=for html
    + +=over + +=item streamName + +Name of stream to attach new view to + +=item pvob + +Name of project vob + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item status + +Integer status + +=item output + +Array of output + +=back + +=for html
    + +=cut + + $region ||= $Clearcase::CC->region; + + return (0, ()) + if $self->exists; + + # Update object members + $self->{stream} = $stream; + $self->{pvob} = $pvob; + + # Need to create the view + my ($status, @output) = + $Clearcase::CC->execute ("mkview -tag $self->{tag} -stream " + . "$self->{stream}\@$self->{pvob} -stgloc -auto"); + + return ($status, @output) + if $status; + + $self->updateViewInfo ($region); + + return ($status, @output); +} # createUCM + +sub remove () { + my ($self) = @_; + +=pod + +=head3 remove + +Removes the view. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $status + +Status from cleartool + +=item @output + +Ouput from cleartool + +=back + +=for html
    + +=cut + + return (0, ()) + unless $self->exists; + + my ($status, @output); + + if ($self->dynamic) { + ($status, @output) = $Clearcase::CC->execute ( + "rmview -force -tag $self->{tag}" + ); + } else { + error 'Removal of snapshot views not implemented yet', 1; + #($status, @output) = $Clearcase::CC->execute ( + # "rmview -force $self->{snapshot_view_pname}" + #); + } # if + + return ($status, @output); +} # remove + +sub start () { + my ($self) = @_; + +=pod + +=head2 start + +Starts the view. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $status + +Status from cleartool + +=item @output + +Ouput from cleartool + +=back + +=for html
    + +=cut + + return $Clearcase::CC->execute ("startview $self->{tag}"); +} # start + +sub stop () { + my ($self) = @_; + +=pod + +=head2 stop + +Stops the view. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $status + +Status from cleartool + +=item @output + +Ouput from cleartool + +=back + +=for html
    + +=cut + + return $Clearcase::CC->execute ("endview $self->{tag}"); +} # stop + +sub kill () { + my ($self) = @_; + +=pod + +=head2 kill + +Stops the view at the view_server process if nobody else is accessing the view. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $status + +Status from cleartool + +=item @output + +Ouput from cleartool + +=back + +=for html
    + +=cut + + return $Clearcase::CC->execute ("endview -server $self->{tag}"); +} # kill + +sub set () { + my ($self) = @_; + +=pod + +=head3 set + +Starts the view then changes directory the to view's root. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $status + +Status from cleartool + +=item @output + +Ouput from cleartool + +=back + +=for html
    + +=cut + + my ($status, @output) = $self->start; + + chdir "$Clearcase::VIEWTAG_PREFIX/$self->{tag}"; + + return ($status, @output); +} # set + +sub updateViewInfo ($$) { + my ($self, $region) = @_; + + $region ||= $Clearcase::CC->region; + + my ($status, @output) = $Clearcase::CC->execute ( + "lsview -region $region -long -properties -full $self->{tag}" + ); + + # Assuming this view is an empty shell of an object that the user may possibly + # use the create method on, return our blessings... + + # No longer assume that. Could equally be the case where the view server + # failed to respond. Carry on then...return if $status != 0; + + # Defaults + $self->{type} = 'dynamic'; + $self->{ucm} = 0; + $self->{additional_groups} = ''; + + foreach (@output) { + if (/Global path: (.*)/) { + $self->{gpath} = $1; + } elsif (/Server host: (.*)/) { + $self->{shost} = $1; + } elsif (/Region: (.*)/) { + $self->{region} = $1; + } elsif (/Active: (.*)/) { + $self->{active} = ($1 eq 'YES') ? 1 : 0; + } elsif (/View uuid: (.*)/) { + $self->{uuid} = $1; + } elsif (/View on host: (.*)/) { + $self->{host} = $1; + } elsif (/View server access path: (.*)/) { + $self->{access_path} = $1; + } elsif (/View attributes: (.*)/) { + my $view_attributes = $1; + $self->{type} = $view_attributes =~ /webview/ + ? 'webview' + : $view_attributes =~ /snapshot/ + ? 'snapshot' + : 'dynamic'; + $self->{ucm} = $view_attributes =~ /ucmview/ + ? 1 + : 0; + } elsif (/Created (\S+) by (.+)/) { + $self->{created_date} = $1; + $self->{created_by} = $2; + } elsif (/Last modified (\S+) by (.+)/) { + $self->{modified_date} = $1; + $self->{modified_by} = $2; + } elsif (/Last accessed (\S+) by (.+)/) { + $self->{accessed_date} = $1; + $self->{accessed_by} = $2; + } elsif (/Last config spec update (\S+) by (.+)/) { + $self->{cs_updated_date} = $1; + $self->{cs_updated_by} = $2; + } elsif (/Text mode: (\S+)/) { + $self->{text_mode} = $1; + } elsif (/Properties: (.*)/) { + $self->{properties} = $1; + } elsif (/Owner: (\S+)\s+: (\S+) /) { + $self->{owner} = $1; + $self->{owner_mode} = $2; + } elsif (/Group: (.+)\s+:\s+(\S+)\s+/) { + $self->{group} = $1; + $self->{group_mode} = $2; + } elsif (/Other:\s+: (\S+) /) { + $self->{other_mode} = $1; + } elsif (/Additional groups: (.*)/) { + my @additional_groups = split /\s+/, $1; + $self->{additional_groups} = \@additional_groups; + } # if + } # foreach + + # Change modes to numeric + $self->{mode} = 0; + + if ($self->{owner_mode}) { + $self->{mode} += 400 if $self->{owner_mode} =~ /r/; + $self->{mode} += 200 if $self->{owner_mode} =~ /w/; + $self->{mode} += 100 if $self->{owner_mode} =~ /x/; + $self->{mode} += 40 if $self->{group_mode} =~ /r/; + $self->{mode} += 20 if $self->{group_mode} =~ /w/; + $self->{mode} += 10 if $self->{group_mode} =~ /x/; + $self->{mode} += 4 if $self->{other_mode} =~ /r/; + $self->{mode} += 2 if $self->{other_mode} =~ /w/; + $self->{mode} += 1 if $self->{other_mode} =~ /x/; + } # if + + return; +} # updateViewInfo + +1; + +=pod + +=head2 DEPENDENCIES + +=for html

    Clearcase

    + +=head2 INCOMPATABILITIES + +None + +=head2 BUGS AND LIMITATIONS + +There are no known bugs in this module. + +Please report problems to Andrew DeFaria . + +=head2 LICENSE AND COPYRIGHT + +Copyright (c) 2007, ClearSCM, Inc. All rights reserved. + +=cut diff --git a/lib/Clearcase/Views.pm b/lib/Clearcase/Views.pm new file mode 100644 index 0000000..ef07cbd --- /dev/null +++ b/lib/Clearcase/Views.pm @@ -0,0 +1,373 @@ +=pod + +=head1 NAME $RCSfile: Views.pm,v $ + +Object oriented interface to Clearcase Views + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.12 $ + +=item Created + +Dec 29 12:07:59 PST 2005 + +=item Modified + +$Date: 2011/11/16 19:46:13 $ + +=back + +=head1 SYNOPSIS + +Provides access to information about Clearcase Views. + + my $views = new Clearcase::Views; + + my $nbr_views = $views->views; + my @view_list = $views->views; + + display "Clearcase Views\n"; + + display "Number of views:\t\t" . $nbr_views; + display "View list:\n"; + + display "\t$_" foreach (@view_list); + +=head1 DESCRIPTION + +This module implements an object oriented interface to Clearcase +views. + +=head1 ROUTINES + +The following routines are exported: + +=cut + +package Clearcase::Views; + +use strict; +use warnings; + +use Clearcase; + +sub new (;$) { + my ($class, $region) = @_; + +=pod + +=head2 new + +Construct a new Clearcase Views object. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Clearcase Views object + +=back + +=for html
    + +=cut + + $region ||= $Clearcase::CC->region; + + my ($status, @output) = + $Clearcase::CC->execute ("lsview -short -region $region"); + + $class = bless { + views => \@output, + }, $class; # bless + + return $class; +} # new + +sub views () { + my ($self) = @_; + +=pod + +=head2 views + +Return a list of view tags in an array context or the number of views in +a scalar context. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item List of views or number of views + +Array of view tags in an array context or the number of views in a scalar context. + +=back + +=for html
    + +=cut + + if (wantarray) { + return $self->{views} ? sort @{$self->{views}} : (); + } else { + return $self->{views} ? scalar @{$self->{views}} : 0; + } #if +} # views + +sub dynamic () { + my ($self) = @_; + +=pod + +=head2 dynamic + +Return the number of dynamic views + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item number of dynamic views + +Returns the number of dynamic views in the region + +=back + +=for html
    + +=cut + + $self->updateViewInfo if !defined $self->{dynamic}; + return $self->{dynamic}; +} # dynamic + +sub ucm () { + my ($self) = @_; + +=pod + +=head2 ucm + +Return the number of ucm views + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item number of ucm views + +Returns the number of ucm views in the region + +=back + +=for html
    + +=cut + + $self->updateViewInfo if !defined $self->{ucm}; + return $self->{ucm}; +} # ucm + +sub snapshot () { + my ($self) = @_; + +=pod + +=head2 snapshot + +Return the number of snapshot views + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item number of snapshot views + +Returns the number of snapshot views in the region + +=back + +=for html
    + +=cut + + $self->updateViewInfo if !defined $self->{snapshot}; + return $self->{snapshot}; +} # snapshot + +sub web () { + my ($self) = @_; + +=pod + +=head2 web + +Return the number of web views + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item number of web views + +Returns the number of web views in the region + +=back + +=for html
    + +=cut + + $self->updateViewInfo if !defined $self->{web}; + return $self->{web}; +} # web + +sub updateViewInfo ($) { + my ($self) = @_; + + my ($dynamic, $web, $ucm, $snapshot) = (0, 0, 0, 0); + + foreach ($self->views) { + my ($status, @lsview_out) = $Clearcase::CC->execute ("lsview -properties -full $_"); + + next + if $status; + + foreach (@lsview_out) { + if (/Properties/) { + $dynamic++ + if /dynamic/; + $snapshot++ + if /snapshot/ and not /webview/; + $ucm++ + if /ucmview/; + $web++ + if /webview/; + last; + } # if + } # foreach + + $self->{dynamic} = $dynamic; + $self->{web} = $web; + $self->{ucm} = $ucm; + $self->{snapshot} = $snapshot; + } # foreach + + return +} # updateViewInfo + +1; + +=head1 DEPENDENCIES + +=for html

    Clearcase

    + +=head1 INCOMPATABILITIES + +None + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this module. + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2007, ClearSCM, Inc. All rights reserved. + +=cut diff --git a/lib/Clearcase/Vob.pm b/lib/Clearcase/Vob.pm new file mode 100644 index 0000000..397c368 --- /dev/null +++ b/lib/Clearcase/Vob.pm @@ -0,0 +1,1360 @@ +=pod + +=head1 NAME $RCSfile: Vob.pm,v $ + +Object oriented interface to a Clearcase VOB + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.15 $ + +=item Created + +Thu Dec 29 12:07:59 PST 2005 + +=item Modified + +$Date: 2011/11/16 19:46:13 $ + +=back + +=head1 SYNOPSIS + +Provides access to information about a Clearcase VOB. Note that information +about the number of elements, branches, etc. that is provided by countdb are not +initially instantiated with the VOB object, rather those member variables are +expanded if and when accessed. This helps the VOB object to be more efficient. + + # Create VOB object + my $vob = new Clearcase::Vob (tag => "/vobs/test"); + + # Access member variables... + display "Tag:\t\t" . $vob->tag; + display "Global path:\t" . $vob->gpath; + display "Sever host:\t" . $vob->shost; + display "Access:\t\t" . $vob->access; + display "Mount options:\t" . $vob->mopts; + display "Region:\t\t" . $vob->region; + display "Active:\t\t" . $vob->active; + display "Replica UUID:\t" . $vob->replica_uuid; + display "Host:\t\t" . $vob->host; + display "Access path:\t" . $vob->access_path; + display "Family UUID:\t" . $vob->family_uuid; + + # This members are not initially expanded until accessed + display "Elements:\t" . $vob->elements; + display "Branches:\t" . $vob->branches; + display "Versions:\t" . $vob->versions; + display "DB Size:\t" . $vob->dbsize; + display "Adm Size:\t" . $vob->admsize; + display "CT Size:\t" . $vob->ctsize; + display "DO Size:\t" . $vob->dbsize; + display "Src Size:\t" . $vob->srcsize; + display "Size:\t\t" . $vob->size; + + # VOB manipulation + display "Umounting " . $vob->tag . "..."; + + $vob->umount; + + display "Mounting " . $vob->tag . "..."; + + $vob->mount; + +=head2 DESCRIPTION + +This module, and others below the Clearcase directory, implement an object +oriented approach to Clearcase. In general Clearcase entities are made into +objects that can be manipulated easily in Perl. This module is the main or +global module. Contained herein are members and methods of a general or global +nature. Also contained here is an IPC interface to cleartool such that cleartool +runs in the background andcommands are fed to it via the exec method. When +making repeated calls to cleartool this can result in a substantial savings of +time as most operating systems' fork/exec sequence is time consuming. Factors of +8 fold improvement have been measured. + +Additionally a global variable, $cc, is implemented from this module such that +you should not need to instantiate another one, though you could. + +=head2 ROUTINES + +The following routines are exported: + +=cut + +package Clearcase::Vob; + +use strict; +use warnings; + +use Clearcase; +use OSDep; + +sub new ($) { + my ($class, $tag) = @_; + +=pod + +=head2 new (tag) + +Construct a new Clearcase VOB object. Note that not all members are +initially populated because doing so would be time consuming. Such +member variables will be expanded when accessed. + +Parameters: + +=for html
    + +=over + +=item tag + +VOB tag to be instantiated. You can use either an object oriented call +(i.e. my $vob = new Clearcase::Vob (tag => "/vobs/test")) or the +normal call (i.e. my $vob = new Clearcase::Vob ("/vobs/test")). You +can also instantiate a new vob by supplying a tag and then later +calling the create method. + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Clearcase VOB object + +=back + +=for html
    + +=cut + + $class = bless { + tag => $tag + }, $class; + + $class->updateVobInfo; + + return $class; +} # new + +sub tag () { + my ($self) = @_; + +=pod + +=head2 tag + +Returns the VOB's tag + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item VOB's tag + +=back + +=for html
    + +=cut + + return $self->{tag}; +} # tag + +sub gpath () { + my ($self) = @_; + +=pod + +=head2 gpath + +Returns the VOB's global path + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item VOB's gpath + +=back + +=for html
    + +=cut + + return $self->{gpath}; +} # gpath + +sub shost () { + my ($self) = @_; + +=pod + +=head2 shost + +Returns the VOB's server host + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item VOB's server host + +=back + +=for html
    + +=cut + + return $self->{shost}; +} # shost + +sub access () { + my ($self) = @_; + +=pod + +=head2 access + +Returns the type of VOB access + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item access + +Returns either public for public VOBs or private for private VOBs + +=back + +=for html
    + +=cut + + return $self->{access}; +} # access + +sub mopts () { + my ($self) = @_; + +=pod + +=head2 mopts + +Returns the mount options + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item VOB's mount options + +=back + +=for html
    + +=cut + + return $self->{mopts}; +} # mopts + +sub region () { + my ($self) = @_; + +=pod + +=head3 region + +Returns the region for this VOB tag + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item region + +=back + +=for html
    + +=cut + + return $self->{region}; +} # region + +sub active () { + my ($self) = @_; + +=pod + +=head2 active + +Returns that active status (whether or not the vob is currently mounted) of the +VOB + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Returns YES for an active VOB or NO for an inactive one + +=back + +=for html
    + +=cut + + return $self->{active}; +} # active + +sub replica_uuid () { + my ($self) = @_; + +=pod + +=head2 replica_uuid + +Returns the VOBS replica_uuid + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item VOB replica_uuid + +=back + +=for html
    + +=cut + + return $self->{replica_uuid}; +} # replica_uuid + +sub host () { + my ($self) = @_; + +=pod + +=head2 host + +Returns the VOB's host + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item VOB's host + +=back + +=for html
    + +=cut + + return $self->{host}; +} # host + +sub access_path () { + my ($self) = @_; + +=pod + +=head2 access_path + +Returns the VOB's access path + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item VOB access path + +This is the path relative to the VOB's host + +=back + +=for html
    + +=cut + + return $self->{access_path}; +} # access_path + +sub family_uuid () { + my ($self) = @_; + +=pod + +=head2 family_uuid + +Returns the VOB family UUID + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item VOB family UUID + +=back + +=for html
    + +=cut + + return $self->{family_uuid}; +} # family_uuid + +sub vob_registry_attributes () { + my ($self) = @_; + +=pod + +=head2 vob_registry_attributes + +Returns the VOB Registry Attributes + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item VOB Registry Attributes + +=back + +=for html
    + +=cut + + return $self->{vob_registry_attributes}; +} # vob_registry_attributes + +sub expand_space () { + my ($self) = @_; + + my ($status, @output) = $Clearcase::CC->execute ("space -vob $self->{tag}"); + + # Initialize fields in case of command failure + $self->{dbsize} = 0; + $self->{admsize} = 0; + $self->{ctsize} = 0; + $self->{dosize} = 0; + $self->{srcsize} = 0; + $self->{size} = 0; + + foreach (@output) { + if (/(\d*\.\d).*VOB database(.*)/) { + $self->{dbsize} = $1; + } elsif (/(\d*\.\d).*administration data(.*)/) { + $self->{admsize} = $1; + } elsif (/(\d*\.\d).*cleartext pool(.*)/) { + $self->{ctsize} = $1; + } elsif (/(\d*\.\d).*derived object pool(.*)/) { + $self->{dosize} = $1; + } elsif (/(\d*\.\d).*source pool(.*)/) { + $self->{srcsize} = $1; + } elsif (/(\d*\.\d).*Subtotal(.*)/) { + $self->{size} = $1; + } # if + } # foreach + + return; +} # expand_space + +sub countdb () { + my ($self) = @_; + + # Set values to zero in case we cannot get the right values from countdb + $self->{elements} = 0; + $self->{branches} = 0; + $self->{versions} = 0; + + # Countdb needs to be done in the vob's db directory + my $cwd = `pwd`; + + chomp $cwd; + chdir "$self->{gpath}/db"; + + my $cmd = "$Clearcase::COUNTDB vob_db 2>&1"; + my @output = `$cmd`; + + if ($? != 0) { + chdir $cwd; + return; + } # if + + chomp @output; + + # Parse output + foreach (@output) { + if (/^ELEMENT\s*:\s*(\d*)/) { + $self->{elements} = $1; + } elsif (/^BRANCH\s*:\s*(\d*)/) { + $self->{branches} = $1; + } elsif (/^VERSION\s*:\s*(\d*)/) { + $self->{versions} = $1; + } # if + } # foreach + + chdir $cwd; + + return; +} # countdb + +sub elements () { + my ($self) = @_; + +=pod + +=head2 elements + +Returns the number of elements in the VOB (obtained via countdb) + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item number of elements + +=back + +=for html
    + +=cut + + $self->countdb if !$self->{elements}; + + return $self->{elements}; +} # elements + +sub branches () { + my ($self) = @_; + +=pod + +=head3 branches + +Returns the number of branch types in the vob + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item number of branch types + +=back + +=for html
    + +=cut + + $self->countdb if !$self->{branches}; + + return $self->{branches}; +} # branches + +sub versions () { + my ($self) = @_; + +=pod + +=head2 versions + +Returns the number of element versions in the VOB + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item number of element versions + +=back + +=for html
    + +=cut + + $self->countdb if !$self->{versions}; + + return $self->{versions}; +} # versions + +sub dbsize () { + my ($self) = @_; + +=pod + +=head3 dbsize + +Returns the size of the VOB's database + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item database size + +=back + +=for html
    + +=cut + + $self->expand_space if !$self->{dbsize}; + + return $self->{dbsize}; +} # dbsize + +sub admsize () { + my ($self) = @_; + +=pod + +=head2 admsize + +Returns the size of administrative data in the VOB + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item adminstrative size + +=back + +=for html
    + +=cut + + $self->expand_space if !$self->{admsize}; + + return $self->{admsize}; +} # admsize + +sub ctsize () { + my ($self) = @_; + +=pod + +=head3 ctsize + +Returns the size of the cleartext pool + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item cleartext pool size + +=back + +=for html
    + +=cut + + $self->expand_space if !$self->{ctsize}; + + return $self->{ctsize}; +} # ctsize + +sub dosize () { + my ($self) = @_; + +=pod + +=head2 dosize + +Returns the size of the derived object pool + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item derived object pool size + +=back + +=for html
    + +=cut + + $self->expand_space if !$self->{dosize}; + + return $self->{dosize}; +} # dosize + +sub srcsize () { + my ($self) = @_; + +=pod + +=head2 srcsize + +Returns the size of the source pool + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item source pool size + +=back + +=for html
    + +=cut + + $self->expand_space if !$self->{srcsize}; + + return $self->{srcsize}; +} # srcsize + +sub size () { + my ($self) = @_; + +=pod + +=head2 size + +Returns the size of the VOB + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item size + +=back + +=for html
    + +=cut + + $self->expand_space if !$self->{size}; + + return $self->{size}; +} # size + +sub mount () { + my ($self) = @_; + +=pod + +=head2 mount + +Mount the current VOB + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $status + +Status of the mount command + +=item @output + +An array of lines output from the cleartool mount command + +=back + +=for html
    + +=cut + + return 0 if $self->{active} && $self->{active} eq "YES"; + + my ($status, @output) = $Clearcase::CC->execute ("mount $self->{tag}"); + + return ($status, @output); +} # mount + +sub umount () { + my ($self) = @_; + +=pod + +=head3 umount + +Unmounts the current VOB + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $status + +Status from cleartool + +=item @output + +Ouput from cleartool + +=back + +=for html
    + +=cut + + my ($status, @output) = $Clearcase::CC->execute ("umount $self->{tag}"); + + return ($status, @output); +} # umount + +sub exists () { + my ($self) = @_; + +=pod + +=head2 exists + +Returns true or false if the VOB exists + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item boolean + +=back + +=for html
    + +=cut + + my ($status, @output) = $Clearcase::CC->execute ("lsvob $self->{tag}"); + + return !$status; +} # exists + +sub create (;$$$) { + my ($self, $host, $vbs, $comment) = @_; + +=pod + +=head2 create + +Creates a VOB. First instantiate a VOB object with a tag. Then call create. A +small subset of parameters is supported for create. + +Parameters: + +=for html
    + +=over + +=item $host (optional) + +Host to create the vob on. Default is the current host. + +=item $vbs (optional) + +VOB storage area. This is a global pathname to the VOB storage +area. Default will attempt to use -stgloc -auto. + +=item $comment (optional) + +Comment for this VOB's creation. Default is -nc + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $status + +Status from cleartool + +=item @output + +Ouput from cleartool + +=back + +=for html
    + +=cut + + return (0, ()) if $self->exists; + + $comment = Clearcase::setComment $comment; + + my ($status, @output); + + if ($host && $vbs) { + ($status, @output) = $Clearcase::CC->execute ( + "mkvob -tag $self->{tag} $comment -host $host -hpath $vbs " + . "-gpath $vbs $vbs"); + } else { + # Note this requires that -stgloc's work and that using -auto is not a + # problem. + ($status, @output) = + $Clearcase::CC->execute ("mkvob -tag $self->{tag} $comment " + . "-stgloc -auto"); + } # if + + $self->updateVobInfo; + + return ($status, @output); +} # create + +sub remove () { + my ($self) = @_; + +=pod + +=head2 remove + +Removed this VOB + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $status + +Status from cleartool + +=item @output + +Ouput from cleartool + +=back + +=for html
    + +=cut + + return $Clearcase::CC->execute ("rmvob -force $self->{gpath}"); +} # remove + +sub updateVobInfo ($$) { + my ($self) = @_; + + my ($status, @output) = $Clearcase::CC->execute ("lsvob -long $self->{tag}"); + + # Assuming this vob is an empty shell of an object that the user may possibly + # use the create method on, return our blessings... + return if $status != 0; + + foreach (@output) { + if (/Global path: (.*)/) { + $self->{gpath} = $1; + } elsif (/Server host: (.*)/) { + $self->{shost} = $1; + } elsif (/Access: (.*)/) { + $self->{access} = $1; + } elsif (/Mount options: (.*)/) { + $self->{mopts} = $1; + } elsif (/Region: (.*)/) { + $self->{region} = $1; + } elsif (/Active: (.*)/) { + $self->{active} = $1; + } elsif (/Vob tag replica uuid: (.*)/) { + $self->{replica_uuid} = $1; + } elsif (/Vob on host: (.*)/) { + $self->{host} = $1; + } elsif (/Vob server access path: (.*)/) { + $self->{access_path} = $1; + } elsif (/Vob family uuid: (.*)/) { + $self->{family_uuid} = $1; + } elsif (/Vob registry attributes: (.*)/) { + $self->{vob_registry_attributes} = $1; + } # if + } # foreach + + return; +} # getVobInfo + +1; + +=pod + +=head2 DEPENDENCIES + +=head3 ClearSCM Perl Modules + +=for html

    Clearcase

    + +=for html

    OSdep

    + +=head2 BUGS AND LIMITATIONS + +There are no known bugs in this module + +Please report problems to Andrew DeFaria . + +=head2 LICENSE AND COPYRIGHT + +Copyright (c) 2007, ClearSCM, Inc. All rights reserved. + +=cut diff --git a/lib/Clearcase/Vobs.pm b/lib/Clearcase/Vobs.pm new file mode 100644 index 0000000..2b14907 --- /dev/null +++ b/lib/Clearcase/Vobs.pm @@ -0,0 +1,315 @@ +=pod + +=head1 NAME $RCSfile: Vobs.pm,v $ + +Object oriented interface to Clearcase VOBs + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.17 $ + +=item Created + +Thu Dec 29 12:07:59 PST 2005 + +=item Modified + +$Date: 2011/11/16 19:46:13 $ + +=back + +=head1 SYNOPSIS + +Provides access to information about all Clearcase VOBs. + + # Create VOBs object + my $vobs = new Clearcase::Vobs; + + display "There are " . $vobs->vobs . " vobs to process"; + + # Iterrate through the list of vobs + foreach ($vobs->vobs) { + my $vob = new Clearcase::Vob $_; + ... + } # foreach + + # VOBs manipulation + display "Umounting all vobs"; + + $vobs->umount; + + display "Mounting all vobs"; + + $vobs->mount; + +=head1 DESCRIPTION + +This module implements a Clearcase vobs object to deal with the lists +of vobs in the current region. + +=head1 ROUTINES + +The following routines are exported: + +=cut + +package Clearcase::Vobs; + +use strict; +use warnings; + +use lib '..'; + +use Clearcase; +use Display; +use OSDep; + +sub new () { + my ($class) = @_; + +=pod + +=head2 new (tag) + +Construct a new Clearcase Vobs object. + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Clearcase VOBs object + +=back + +=for html
    + +=cut + + my ($status, @output) = $Clearcase::CC->execute ("lsvob -short"); + + return if $status; + + # Strip $VOBTAG_PREFIX + foreach (@output) { + if ($ARCH eq 'windows' or $ARCH eq 'cygwin') { + s/\\//; + } else { + s/$Clearcase::VOBTAG_PREFIX//; + } # if + } # foreach + + return bless { + vobs => \@output + }, $class; # bless +} # new + +sub vobs () { + my ($self) = @_; + +=pod + +=head3 vobs + +Return a list of VOB tags in an array context or the number of vobs in +a scalar context. + +Parameters: + +=for html
    + +=over + +=over + +=item none + +=back + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=over + +=item List of VOBs or number of VOBs + +Array of VOB tags in an array context or the number of vobs in a scalar context. + +=back + +=back + +=for html
    + +=cut + + if (wantarray) { + my @returnVobs = sort @{$self->{vobs}}; + + return @returnVobs; + } else { + return scalar @{$self->{vobs}}; + } #if +} # vobs + +sub mount () { + my ($self) = @_; + +=pod + +=head3 mount + +Mount all VOBs + +Parameters: + +=for html
    + +=over + +=over + +=item none + +=back + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=over + +=item $status + +Status from cleartool + +=item @output + +Ouput from cleartool + +=back + +=back + +=for html
    + +=cut + + my ($status, @output) = $Clearcase::CC->execute ("mount -all"); + + return $status; +} # mount + +sub umount () { + my ($self) = @_; + +=pod + +=head3 umount + +Unmounts all VOBs + +Parameters: + +=for html
    + +=over + +=over + +=item none + +=back + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=over + +=item $status + +Status from cleartool + +=item @output + +Ouput from cleartool + +=back + +=back + +=for html
    + +=cut + + my ($status, @output) = $Clearcase::CC->execute ("umount -all"); + + return $status; +} # umount + +1; + +=pod + +=head2 DEPENDENCIES + +=head3 ClearSCM Perl Modules + +=for html

    Clearcase

    + +=for html

    Display

    + +=for html

    OSdep

    + +=head2 BUGS AND LIMITATIONS + +There are no known bugs in this module + +Please report problems to Andrew DeFaria . + +=head2 LICENSE AND COPYRIGHT + +Copyright (c) 2007, ClearSCM, Inc. All rights reserved. + +=cut diff --git a/lib/Clearquest.pm b/lib/Clearquest.pm new file mode 100644 index 0000000..64e0e07 --- /dev/null +++ b/lib/Clearquest.pm @@ -0,0 +1,2713 @@ +=pod + +=head1 NAME $RCSfile: Clearquest.pm,v $ + +Object oriented interface to Clearquest. + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 2.23 $ + +=item Created + +Fri Sep 22 09:21:18 CDT 2006 + +=item Modified + +$Date: 2013/03/28 22:48:07 $ + +=back + +=head1 SYNOPSIS + +Provides access to Clearquest database in an object oriented manner. + + # Create Clearquest object + my $cq = Clearquest->new; + + # Connect to database (using all the defaults in cq.conf) + $cq->connect; + + # Connect as non standard user; + + $cq->connect (CQ_USERNAME => 'me', CQ_PASSWORD => 'mypassword'); + + # Get record (Default: all fields) + my %record = $cq->get ($recordName, $key); + + # Get record with specific field list + my %record =$cq->get ($recordName, $key, qw(field1 field2)) + + # Modify a record + my %update = ( + Description => 'This is a new description', + Active => 1, + ); + $cq->modify ($recordName, $key, 'Modify', \%update); + + # Change state using modify with an alternate action. Note the use of @ordering + my %fieldsToUpdate = ( + Project => 'Carrier', + Category => 'New Functionality', + Groups => [ 'Group1', 'Group2' ], + ); + + my @ordering qw(Project Category); + + $cq->modify ($recordName, $key, 'Open', \%fieldsToUpdate, @ordering); + + if ($cq->error) { + error "Unable to update $key to Opened state\n" + . $cq->errmsg; + } # if + +=head1 DESCRIPTION + +This module provides a simple interface to Clearquest in a Perl like fashion. +There are three modes of talking to Clearquest using this module - api, rest +and client. + +With module = 'api' you must have Clearquest installed locally and you must use +cqperl to execute your script. This mode of operation has the benefit of speed - +note that initial connection to the Clearquest database is not very speedy, but +all subsequent calls will operate at full speed. The 'api' module is free to +use. For the other modules contact ClearSCM, Inc. + +With module = 'rest' you can access Clearquest by using a RESTFull interface. +You can use any Perl which has the required CPAN modules (REST, XML::Simple - +see Clearquest::REST for a list of required CPAN modules). The REST interface is +a slower than the native api and requires the setup of Clearquest Web (cqweb) on +your network. To use the REST interface set CQ_MODULE to 'rest'. + +With module = 'client' you access Clearquest through the companion +Clearquest::Server module and the cqd.pl server script. The server process is +started on a machine that has Clearquest installed locally. It uses the api +interface for speed and can operate in a multithreaded manner, spawning +processes which open and handle requests from Clearquest::Client requests. To +use the Client interface set CQ_MODULE to 'client'. + +Other than setting CQ_MODULE to one of the three modes described above, the rest +of your script's usage of the Clearquest module should be exactly the same. + +=head1 CONFIGURATION + +This module uses GetConfig to read in a configuration file (../etc/cq.conf) +which sets default values described below. Or you can export the option name to +the env(1) to override the defaults in cq.conf. Finally you can programmatically +set the options when you call new by passing in a %parms hash. To specify the +%parms hash key remove the CQ_ portion and lc the rest. + +=for html
    + +=over + +=item CQ_SERVER + +Clearquest server to talk to. Also used for rest server (Default: From cq.conf) + +=item CQ_PORT + +Port to connect to (Default: From cq.conf) + +=item CQ_WEBHOST + +The web host to contact with leading http:// (Default: From cq.conf) + +=item CQ_DATABASE + +Name of database to connect to (Default: From cq.conf) + +=item CQ_USERNAME + +User name to connect as (Default: From cq.conf) + +=item CQ_PASSWORD + +Password for CQREST_USERNAME (Default: From cq.conf) + +=item CQ_DBSET + +Database Set name (Default: From cq.conf) + +=item CQ_MODULE + +One of 'api', 'rest' or 'client' (Default: From cq.conf) + +=back + +=head1 METHODS + +The following methods are available: + +=cut + +package Clearquest; + +use strict; +use warnings; + +use File::Basename; +use Carp; +use Time::Local; + +use GetConfig; + +# Seed options from config file +my $config = $ENV{CQ_CONF} || dirname (__FILE__) . '/../etc/cq.conf'; + +croak "Unable to find config file $config" unless -r $config; + +our %OPTS = GetConfig $config; + +my $DEFAULT_DBSET = $OPTS{CQ_DBSET}; + +our $VERSION = '$Revision: 2.23 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +# Override options if in the environment +$OPTS{CQ_DATABASE} = $ENV{CQ_DATABASE} if $ENV{CQ_DATABASE}; +$OPTS{CQ_DBSET} = $ENV{CQ_DBSET} if $ENV{CQ_DBSET}; +$OPTS{CQ_MODULE} = $ENV{CQ_MODULE} if $ENV{CQ_MODULE}; +$OPTS{CQ_PASSWORD} = $ENV{CQ_PASSWORD} if $ENV{CQ_PASSWORD}; +$OPTS{CQ_PORT} = $ENV{CQ_PORT} if $ENV{CQ_PORT}; +$OPTS{CQ_SERVER} = $ENV{CQ_SERVER} if $ENV{CQ_SERVER}; +$OPTS{CQ_USERNAME} = $ENV{CQ_USERNAME} if $ENV{CQ_USERNAME}; + +# FieldTypes ENUM +our $UNKNOWN = -1; +our $STRING = 1; +our $MULTILINE_STRING = 2; +our $INT = 3; +our $DATE_TIME = 4; +our $REFERENCE = 5; +our $REFERENCE_LIST = 6; +our $ATTACHMENT_LIST = 7; +our $ID = 8; +our $STATE = 9; +our $JOURNAL = 10; +our $DBID = 11; +our $STATETYPE = 12; +our $RECORD_TYPE = 13; + +my %FIELDS; + +my @objects; + +my $SECS_IN_MIN = 60; +my $SECS_IN_HOUR = $SECS_IN_MIN * 60; +my $SECS_IN_DAY = $SECS_IN_HOUR * 24; + +my $operatorRE = qr/ + (\w+) # field name + \s* # whitespace + ( # operators + == # double equals + |= # single equals + |!= # not equal + |<> # the other not equal + |<= # less than or equal + |>= # greater than or equal + |< # less than + |> # greater than + |like # like + |not\s+like # not like + |between # between + |not\s*between # not between + |is\s+null # is null + |is\s+not\s+null # is not null + |in # in + |not\s+in # not in + ) + \s* # whitespace + (.*) # value + /ix; + +END { + # Insure all instaniated objects have been destroyed + $_->DESTROY foreach (@objects); +} # END + +# Internal methods +sub _commitRecord ($) { + my ($self, $entity) = @_; + + $self->{errmsg} = $entity->Validate; + + if ($self->{errmsg} eq '') { + $self->{errmsg} = $entity->Commit; + $self->{error} = $self->{errmsg} eq '' ? 0 : 1; + + return $self->{errmsg}; + } else { + $self->{error} = 1; + + $entity->Revert; + + return $self->{errmsg}; + } # if +} # _commitRecord + +sub _is_leap_year ($) { + my ($year) = @_; + + return 0 if $year % 4; + return 1 if $year % 100; + return 0 if $year % 400; + + return 1; +} # _is_leap_year + +sub _dateToEpoch ($) { + my ($date) = @_; + + my $year = substr $date, 0, 4; + my $month = substr $date, 5, 2; + my $day = substr $date, 8, 2; + my $hour = substr $date, 11, 2; + my $minute = substr $date, 14, 2; + my $seconds = substr $date, 17, 2; + + my $days; + + for (my $i = 1970; $i < $year; $i++) { + $days += _is_leap_year ($i) ? 366 : 365; + } # for + + my @monthDays = ( + 0, + 31, + 59, + 90, + 120, + 151, + 181, + 212, + 243, + 273, + 304, + 334, + ); + + $days += $monthDays[$month - 1]; + + $days++ + if _is_leap_year ($year) and $month > 2; + + $days += $day - 1; + + return ($days * $SECS_IN_DAY) + + ($hour * $SECS_IN_HOUR) + + ($minute * $SECS_IN_MIN) + + $seconds; +} # _dateToEpoch + +sub _epochToDate ($) { + my ($epoch) = @_; + + my $year = 1970; + my ($month, $day, $hour, $minute, $seconds); + my $leapYearSecs = 366 * $SECS_IN_DAY; + my $yearSecs = $leapYearSecs - $SECS_IN_DAY; + + while () { + my $amount = _is_leap_year ($year) ? $leapYearSecs : $yearSecs; + + last + if $amount > $epoch; + + $epoch -= $amount; + $year++; + } # while + + my $leapYearAdjustment = _is_leap_year ($year) ? 1 : 0; + + if ($epoch >= (334 + $leapYearAdjustment) * $SECS_IN_DAY) { + $month = '12'; + $epoch -= (334 + $leapYearAdjustment) * $SECS_IN_DAY; + } elsif ($epoch >= (304 + $leapYearAdjustment) * $SECS_IN_DAY) { + $month = '11'; + $epoch -= (304 + $leapYearAdjustment) * $SECS_IN_DAY; + } elsif ($epoch >= (273 + $leapYearAdjustment) * $SECS_IN_DAY) { + $month = '10'; + $epoch -= (273 + $leapYearAdjustment) * $SECS_IN_DAY; + } elsif ($epoch >= (243 + $leapYearAdjustment) * $SECS_IN_DAY) { + $month = '09'; + $epoch -= (243 + $leapYearAdjustment) * $SECS_IN_DAY; + } elsif ($epoch >= (212 + $leapYearAdjustment) * $SECS_IN_DAY) { + $month = '08'; + $epoch -= (212 + $leapYearAdjustment) * $SECS_IN_DAY; + } elsif ($epoch >= (181 + $leapYearAdjustment) * $SECS_IN_DAY) { + $month = '07'; + $epoch -= (181 + $leapYearAdjustment) * $SECS_IN_DAY; + } elsif ($epoch >= (151 + $leapYearAdjustment) * $SECS_IN_DAY) { + $month = '06'; + $epoch -= (151 + $leapYearAdjustment) * $SECS_IN_DAY; + } elsif ($epoch >= (120 + $leapYearAdjustment) * $SECS_IN_DAY) { + $month = '05'; + $epoch -= (120 + $leapYearAdjustment) * $SECS_IN_DAY; + } elsif ($epoch >= (90 + $leapYearAdjustment) * $SECS_IN_DAY) { + $month = '04'; + $epoch -= (90 + $leapYearAdjustment) * $SECS_IN_DAY; + } elsif ($epoch >= (59 + $leapYearAdjustment) * $SECS_IN_DAY) { + $month = '03'; + $epoch -= (59 + $leapYearAdjustment) * $SECS_IN_DAY; + } elsif ($epoch >= 31 * $SECS_IN_DAY) { + $month = '02'; + $epoch -= 31 * $SECS_IN_DAY; + } else { + $month = '01'; + } # if + + $day = int (($epoch / $SECS_IN_DAY) + 1); + $epoch = $epoch % $SECS_IN_DAY; + $hour = int ($epoch / $SECS_IN_HOUR); + $epoch = $epoch % $SECS_IN_HOUR; + $minute = int ($epoch / $SECS_IN_MIN); + $seconds = $epoch % $SECS_IN_MIN; + + $day = "0$day" if $day < 10; + $hour = "0$hour" if $hour < 10; + $minute = "0$minute" if $minute < 10; + $seconds = "0$seconds" if $seconds < 10; + + return "$year-$month-$day $hour:$minute:$seconds"; +} # _pochToDate + +sub _parseCondition ($) { + my ($self, $condition) = @_; + + # Parse simple conditions only + my ($field, $operator, $value); + + if ($condition =~ $operatorRE) { + $field = $1; + $operator = $2; + $value = $3; + + if ($operator eq '==' or $operator eq '=') { + if ($value !~ /^null$/i) { + $operator = $CQPerlExt::CQ_COMP_OP_EQ; + } else { + $operator = $CQPerlExt::CQ_COMP_OP_IS_NULL; + } # if + } elsif ($operator eq '!=' or $operator eq '<>') { + if ($value !~ /^null$/i) { + $operator = $CQPerlExt::CQ_COMP_OP_NEQ; + } else { + $operator = $CQPerlExt::CQ_COMP_OP_IS_NOT_NULL; + } # if + } elsif ($operator eq '<') { + $operator = $CQPerlExt::CQ_COMP_OP_LT; + } elsif ($operator eq '>') { + $operator = $CQPerlExt::CQ_COMP_OP_GT; + } elsif ($operator eq '<=') { + $operator = $CQPerlExt::CQ_COMP_OP_LTE; + } elsif ($operator eq '>=') { + $operator = $CQPerlExt::CQ_COMP_OP_GTE; + } elsif ($operator =~ /^like$/i) { + $operator = $CQPerlExt::CQ_COMP_OP_LIKE; + } elsif ($operator =~ /^not\s+like$/i) { + $operator = $CQPerlExt::CQ_COMP_OP_NOT_LIKE; + } elsif ($operator =~ /^between$/i) { + $operator = $CQPerlExt::CQ_COMP_OP_BETWEEN; + } elsif ($operator =~ /^not\s+between$/i) { + $operator = $CQPerlExt::CQ_COMP_OP_NOT_BETWEEN; + } elsif ($operator =~ /^is\s+null$/i) { + $operator = $CQPerlExt::CQ_COMP_OP_IS_NULL; + } elsif ($operator =~ /^is\s+not\s+null$/i) { + $operator = $CQPerlExt::CQ_COMP_OP_IS_NOT_NULL; + } elsif ($operator =~ /^in$/i) { + $operator = $CQPerlExt::CQ_COMP_OP_IN; + } elsif ($operator =~ /^not\s+in$/) { + $operator = $CQPerlExt::CQ_COMP_OP_NOT_IN; + } else { + $self->_setError ("I can't understand the operator $operator"); + + $operator = undef; + + return 1; + } # if + } else { + # TODO: How to handle more complicated $condition.... + $self->_setError ("I can't understand the conditional expression " + . $condition); + + $operator = undef; + + return 1; + } # if + + # Trim quotes if any: + if ($value =~ /^\s*\'/) { + $value =~ s/^\s*\'//; + $value =~ s/\'\s*$//; + } elsif ($value =~ /^\s*\"/) { + $value =~ s/^\s*\"//; + $value =~ s/\"\s*$//; + } # if + + # Trim leading and trailing whitespace + $value =~ s/^\s+//; + $value =~ s/\s+$//; + + return ($field, $operator, $value); +} # _parseCondition + +sub _parseConditional ($$;$); +sub _parseConditional ($$;$) { + my ($self, $query, $condition, $filterOperator) = @_; + + return if $condition eq ''; + + my ($field, $operator, $value); + + if ($condition =~ /(.+?)\s+(and|or)\s+(.+)/i) { + my $leftSide = $1; + my $conjunction = lc $2; + my $rightSide = $3; + + if ($conjunction eq 'and') { + unless ($filterOperator) { + $filterOperator = $query->BuildFilterOperator ($CQPerlExt::CQ_BOOL_OP_AND); + } else { + $filterOperator = $filterOperator->BuildFilterOperator ($CQPerlExt::CQ_BOOL_OP_AND); + } # unless + } elsif ($conjunction eq 'or') { + unless ($filterOperator) { + $filterOperator = $query->BuildFilterOperator ($CQPerlExt::CQ_BOOL_OP_OR); + } else { + $filterOperator = $filterOperator->BuildFilterOperator ($CQPerlExt::CQ_BOOL_OP_OR); + } # unless + } # if + + $self->_setCondition ($self->_parseCondition ($leftSide), $filterOperator); + + $self->_parseConditional ($query, $rightSide, $filterOperator); + } else { + unless ($condition =~ $operatorRE) { + $self->_setError ("Unable to parse condition \"$condition\""); + + return; + } # unless + + $filterOperator = $query->BuildFilterOperator ($CQPerlExt::CQ_BOOL_OP_AND) + unless $filterOperator; + + $self->_setCondition ($self->_parseCondition ($condition), $filterOperator); + } # if + + # Actually clear error... + $self->_setError; + + return; +} # _parseConditional + +sub _setCondition ($$$) { + my ($self, $field, $operator, $value, $filterOperator) = @_; + + return unless $operator; + + if ($operator == $CQPerlExt::CQ_COMP_OP_IS_NULL or + $operator == $CQPerlExt::CQ_COMP_OP_IS_NOT_NULL) { + eval {$filterOperator->BuildFilter ($field, $operator, [()])}; + + if ($@) { + $self->_setError ($@); + + carp $@; + } # if + } else { + # If the operator is one of the operators that have mulitple values then we + # need to make an array of $value + if ($operator == $CQPerlExt::CQ_COMP_OP_BETWEEN or + $operator == $CQPerlExt::CQ_COMP_OP_NOT_BETWEEN or + $operator == $CQPerlExt::CQ_COMP_OP_IN or + $operator == $CQPerlExt::CQ_COMP_OP_NOT_IN) { + my @values = split /,\s*/, $value; + + eval {$filterOperator->BuildFilter ($field, $operator, \@values)}; + + if ($@) { + $self->_setError ($@); + + carp $@; + } # if + } else { + eval {$filterOperator->BuildFilter ($field, $operator, [$value])}; + + if ($@) { + $self->_setError ($@); + + carp $@; + } # if + } # if + } # if + + return; +} # _setCondition + +sub _setFields ($@) { + my ($self, $table, @fields) = @_; + + my $entityDef; + + eval {$entityDef = $self->{session}->GetEntityDef ($table)}; + + if ($@) { + $self->_setError ($@, -1); + + return; + } # if + + unless (@fields) { + # Always return dbid + push @fields, 'dbid' unless grep {$_ eq 'dbid'} @fields; + + foreach (@{$entityDef->GetFieldDefNames}) { + unless ($self->{returnSystemFields}) { + next if $entityDef->IsSystemOwnedFieldDefName ($_); + } # unless + + push @fields, $_; + } # foreach + } # unless + + return @fields; +} # _setFields + +sub _setError (;$$) { + my ($self, $errmsg, $error) = @_; + + $error ||= 0; + + if ($errmsg and $errmsg ne '') { + $error = 1; + + $self->{errmsg} = $errmsg; + } else { + $self->{errmsg} = ''; + } # if + + $self->error ($error); + + return; +} # _setError + +sub _setFieldValue ($$$$) { + my ($self, $entity, $table, $fieldName, $fieldValue) = @_; + + my $errmsg = ''; + + my $entityDef = $self->{session}->GetEntityDef ($table); + + return $errmsg if $entityDef->IsSystemOwnedFieldDefName ($fieldName); + + unless (ref $fieldValue eq 'ARRAY') { + # This is one of those rare instances where it is important to surround a + # bare variable with double quotes otherwise the CQ API will wrongly + # evaluate $fieldValue if $fieldValue is a simple number (e.g. 0, 1, etc.) + $errmsg = $entity->SetFieldValue ($fieldName, "$fieldValue") if $fieldValue; + } else { + foreach (@$fieldValue) { + $errmsg = $entity->AddFieldValue ($fieldName, $_); + + return $errmsg unless $errmsg eq ''; + } # foreach + } # unless + + return $errmsg; +} # _setFieldValues + +sub _UTCTime ($) { + my ($datetime) = @_; + + my @localtime = localtime; + my ($sec, $min, $hour, $mday, $mon, $year) = gmtime ( + _dateToEpoch ($datetime) - (timegm (@localtime) - timelocal (@localtime)) + ); + + $year += 1900; + $mon++; + + $sec = '0' . $sec if $sec < 10; + $min = '0' . $min if $min < 10; + $hour = '0' . $hour if $hour < 10; + $mon = '0' . $mon if $mon < 10; + $mday = '0' . $mday if $mday < 10; + + return "$year-$mon-${mday}T$hour:$min:${sec}Z"; +} # _UTCTime + +sub _UTC2Localtime ($) { + my ($utcdatetime) = @_; + + return unless $utcdatetime; + + # If the field does not look like a UTC time then just return it. + return $utcdatetime unless $utcdatetime =~ /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z/; + + $utcdatetime =~ s/T/ /; + $utcdatetime =~ s/Z//; + + my @localtime = localtime; + + return _epochToDate ( + _dateToEpoch ($utcdatetime) + (timegm (@localtime) - timelocal (@localtime)) + ); +} # _UTC2Localtime + +sub add ($$;@) { + my ($self, $table, $values, @ordering) = @_; + +=pod + +=head2 add ($$;@) + +Insert a new record into the database + +Parameters: + +=for html
    + +=over + +=item $table + +The name of the table to insert into + +=item $values + +Hash reference of name/value pairs for the insertion + +=item @ordering + +Array containing field names that need to be processed in order. Not all fields +mentioned in the $values hash need be mentioned here. If you have fields that +must be set in a particular order you can mention them here. So, if you're +adding the Defect record, but you need Project set before Platform, you need +only pass in an @ordering of qw(Project Platform). They will be done first, then +all of the rest of the fields in the $values hash. If you have no ordering +dependencies then you can simply omit @ordering. + +Note that the best way to determine if you have an ordering dependency try using +a Clearquest client and note the order that you set fields in. If at anytime +setting one field negates another field via action hook code then you have just +figured out that this field needs to be set before the file that just got +negated. + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $dbid + +The DBID of the newly added record or undef if error. + +=back + +=for html
    + +=cut + + $self->{errmsg} = ''; + + unless ($self->connected) { + $self->_setError ('You must connect to Clearquest before you can call add'); + + return; + } # unless + + my %values = %$values; + my $entity; + + eval {$entity = $self->{session}->BuildEntity ($table)}; + + if ($@) { + $self->_setError ("Unable to create new $table record:\n$@"); + + return; + } # if + + # First process all fields in @ordering, if specified + foreach (@ordering) { + if ($values{$_}) { + $self->{errmsg} = $self->_setFieldValue ($entity, $table, $_, $values{$_}); + } else { + $self->_setError ("$_ from the ordering array is not present in the value hash", -1); + } # if + + last unless $self->{errmsg} eq ''; + } # foreach + + return unless $self->{errmsg} eq ''; + + # Now process the rest of the values + foreach my $fieldName (keys %values) { + next if grep {$fieldName eq $_} @ordering; + + $self->{errmsg} = $self->_setFieldValue ($entity, $table, $fieldName, $values{$fieldName}); + + last unless $self->{errmsg} eq ''; + } # foreach + + $self->_setError ($self->{errmsg}); + + return unless $self->{errmsg} eq ''; + + $self->{errmsg} = $self->_commitRecord ($entity); + $self->{error} = $self->{errmsg} eq '' ? 0 : 1; + + my $dbid = $entity->GetFieldValue ('dbid')->GetValue; + + return $dbid; +} # add + +sub connect (;$$$$) { + my ($self, $username, $password, $database, $dbset) = @_; + +=pod + +=head2 connect (;$$$$) + +Connect to the Clearquest database. You can supply parameters such as username, +password, etc and they will override any passed to Clearquest::new (or those +coming from ../etc/cq.conf) + +Parameters: + +=for html
    + +=over + +=item $username + +Username to use to connect to the database + +=item $password + +Password to use to connect to the database + +=item $database + +Clearquest database to connect to + +=item $dbset + +Database set to connect to (Default: Connect to the default dbset) + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item 1 + +=back + +=for html
    + +=cut + + return unless $self->{module} eq 'api'; + + eval {require CQPerlExt}; + + croak "Unable to use Rational's CQPerlExt library - " + . "You must use cqperl to use the Clearquest API back end\n$@" if $@; + + $self->{username} = $username if $username; + $self->{password} = $password if $password; + $self->{database} = $database if $database; + $self->{dbset} = $dbset if $dbset; + + $self->{session} = CQSession::Build (); + + $self->{loggedin} = 0; + + eval { + $self->{session}->UserLogon ($self->{username}, + $self->{password}, + $self->{database}, + $self->{dbset}); + }; + + if ($@) { + chomp ($@); + + $self->_setError ($@, 1); + } else { + $self->{loggedin} = 1; + + $self->_setError ($_, 0); + } # if + + return $self->{loggedin}; +} # connect + +sub connected () { + my ($self) = @_; + +=pod + +=head2 connected () + +Returns 1 if we are currently connected to Clearquest + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item 1 if logged in - 0 if not + +=back + +=for html
    + +=cut + + return $self->{loggedin}; +} # connected + +sub connection ($) { + my ($self, $fullyQualify) = @_; + +=pod + +=head2 connection () + +Returns a connection string that describes the current connection + +Parameters: + +=for html
    + +=over + +=item $fullyQualify + +If true the connection string will be fully qualified + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $connectionStr + +A string describing the current connection. Generally +@[/]. Note that is only displayed if it is +not the default DBSet as defined in cq.conf. + +=back + +=for html
    + +=cut + + my $connectionStr = $self->username () + . '@' + . $self->database (); + + if ($fullyQualify) { + $connectionStr .= '/' . $self->dbset; + } else { + $connectionStr .= '/' . $self->dbset () unless $self->dbset eq $DEFAULT_DBSET; + } # if + + return $connectionStr; +} # connection + +sub checkErr (;$$) { + my ($self, $msg, $die) = @_; + +=pod + +=head2 checkErr (;$$) + +Checks for error in the last Clearquest method call and prints error to STDERR. +Optionally prints a user message if $msg is specified. Dies if $die is true + +Parameters: + +=for html
    + +=over + +=item $msg + +User error message + +=item $die + +Causes caller to croak if set to true + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $error + +Returns 0 for no error, non-zero if error. + +=back + +=for html
    + +=cut + + $die ||= 0; + + if ($self->{error}) { + if ($msg) { + $msg .= "\n" . $self->errmsg . "\n"; + } else { + $msg = $self->errmsg . "\n"; + } # if + + if ($die) { + croak $msg if $die; + } else { + print STDERR "$msg\n"; + + return $self->{error}; + } # if + } # if + + return 0; +} # checkErr + +sub database () { + my ($self) = @_; + +=pod + +=head2 database + +Returns the current database (or the database that would be used) + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item database + +=back + +=for html
    + +=cut + + return $self->{database}; +} # database + +sub dbset () { + my ($self) = @_; + +=pod + +=head2 dbset + +Returns the current dbset (or the dbset that would be used) + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item dbset + +=back + +=for html
    + +=cut + + return $self->{dbset}; +} # dbset + +sub dbsets () { + my ($self) = @_; + +=pod + +=head2 dbsets () + +Return the installed DBSets for this schema + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item @dbsets + +An array of dbsets + +=back + +=for html
    + +=cut + + unless ($self->connected) { + $self->_setError ('You must connect to Clearquest before you can call DBSets', '-1'); + + return; + } # unless + + return @{$self->{session}->GetInstalledDbSets}; +} # dbsets + +sub delete ($;$) { + my ($self, $table, $key) = @_; + +=pod + +=head2 delete ($;$) + +Deletes records from the database + +Parameters: + +=for html
    + +=over + +=item $table + +Table to delete records from + +=item $key + +Key of the record to delete + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $errmsg + +Error message or blank if no error + +=back + +=for html
    + +=cut + + my $entity; + + eval {$entity = $self->{session}->GetEntity ($table, $key)}; + + if ($@) { + $self->_setError ($@, 1); + + return $@; + } # if + + eval {$self->{session}->DeleteEntity ($entity, 'Delete')}; + + if ($@) { + $self->_setError ($@, 1); + + return $@; + } # if + + return ''; +} # delete + +sub DESTROY () { + my ($self) = @_; + + CQSession::Unbuild ($self->{session}) if $self->{session}; + + return; +} # DESTROY + +sub disconnect () { + my ($self) = @_; + +=pod + +=head2 disconnect () + +Disconnect from Clearquest + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item nothing + +=back + +=for html
    + +=cut + + CQSession::Unbuild ($self->{session}); + + undef $self->{session}; + + $self->{loggedin} = 0; + + return; +} # disconnect + +sub errmsg (;$) { + my ($self, $errmsg) = @_; + +=pod + +=head2 errmsg () + +Returns the last error message. Optionally sets the error message if specified. + +Parameters: + +=for html
    + +=over + +=item $errmsg + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $errmsg + +Last $errmsg + +=back + +=for html
    + +=cut + + $self->{errmsg} = $errmsg if $errmsg; + + return $self->{errmsg}; +} # errmsg + +sub error (;$) { + my ($self, $error) = @_; + +=pod + +=head2 error ($error) + +Returns the last error number. Optional set the error number if specified + +Parameters: + +=for html
    + +=over + +=item $error + +Error number to set + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $error + +Last error + +=back + +=for html
    + +=cut + + $self->{error} = $error if defined $error; + + return $self->{error}; +} # error + +sub fieldType ($$) { + my ($self, $table, $fieldName) = @_; + +=pod + +=head2 fieldType ($table, $fieldname) + +Returns the field type for the $table, $fieldname combination. + +Parameters: + +=for html
    + +=over + +=item $table + +Table to return field type from. + +=item $fieldname + +Fieldname to return the field type from. + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $fieldType + +Fieldtype enum + +=back + +=for html
    + +=cut + + return $UNKNOWN unless $self->{loggedin}; + + # If we've already computed the fieldTypes for the fields in this table then + # return the value + if ($FIELDS{$table}) { + # If we already have this fieldType just return it + if (defined $FIELDS{$table}{$fieldName}) { + return $FIELDS{$table}{$fieldName} + } else { + return $UNKNOWN + } # if + } # if + + my $entityDef = $self->{session}->GetEntityDef ($table); + + foreach (@{$entityDef->GetFieldDefNames}) { + $FIELDS{$table}{$_} = $entityDef->GetFieldDefType ($_); + } # foreach + + if (defined $FIELDS{$table}{$fieldName}) { + return $FIELDS{$table}{$fieldName} + } else { + return $UNKNOWN + } # if +} # fieldType + +sub fieldTypeName ($$) { + my ($self, $table, $fieldName) = @_; + +=pod + +=head2 fieldTypeName ($table, $fieldname) + +Returns the field type name for the $table, $fieldname combination. + +Parameters: + +=for html
    + +=over + +=item $table + +Table to return field type from. + +=item $fieldname + +Fieldname to return the field type from. + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $fieldTypeName + +Fieldtype name + +=back + +=for html
    + +=cut + + my $fieldType = $self->fieldType ($table, $fieldName); + + return $UNKNOWN unless $fieldType; + + if ($fieldType == $STRING) { + return "STRING"; + } elsif ($fieldType == $MULTILINE_STRING) { + return "MULTILINE_STRING"; + } elsif ($fieldType == $INT) { + return "INT"; + } elsif ($fieldType == $DATE_TIME) { + return "DATE_TIME"; + } elsif ($fieldType == $REFERENCE) { + return "REFERENCE" + } elsif ($fieldType == $REFERENCE_LIST) { + return "REFERENCE_LIST"; + } elsif ($fieldType == $ATTACHMENT_LIST) { + return "ATTACHMENT_LIST"; + } elsif ($fieldType == $ID) { + return "ID"; + } elsif ($fieldType == $STATE) { + return "STATE"; + } elsif ($fieldType == $JOURNAL) { + return "JOURNAL"; + } elsif ($fieldType == $DBID) { + return "DBID"; + } elsif ($fieldType == $STATETYPE) { + return "STATETYPE"; + } elsif ($fieldType == $RECORD_TYPE) { + return "RECORD_TYPE"; + } elsif ($fieldType == $UNKNOWN) { + return "UNKNOWN"; + } # if +} # fieldTypeName + +sub find ($;$@) { + my ($self, $table, $condition, @fields) = @_; + +=pod + +=head2 find ($;$@) + +Find records in $table. You can specify a $condition and which fields you wish +to retrieve. Specifying a smaller set of fields means less data transfered and +quicker retrieval so only retrieve the fields you really need. + +Parameters: + +=for html
    + +=over + +=item $table + +Name of the table to search + +=item $condition + +Condition to use. If you want all records then pass in undef. Only simple +conditions are supported. You can specify compound conditions (e.g. field1 == +'foo' and field1 == 'bar' or field2 is not null). No parenthesizing is +supported (yet). + +The following conditionals are supported + +=over + +=item Equal (==|=) + +=item Not Equal (!=|<>) + +=item Less than (<) + +=item Greater than (>) + +=item Less than or equal (<=) + +=item Greater than or equal (>=) + +=item Like + +=item Is null + +=item Is not null + +=item In + +=back + +Note that "is not null" is currently not working in the REST module (it works +in the api and thus also in the client/server model). This because the +OLSC spec V1.0 does not support it. + +As for "Like"", you'll need to specify " like '%var%'" for the +condition. + +"In" is only available in the REST interface as that's what OLSC supports. It's +syntax would be " In 'value1', 'value2', 'value3'..." + +Also conditions can be combined with (and|or) so in the api you could do "in" +as " = 'value1 or = 'value2" or = 'value3'". + +Complicated expressions with parenthesis like "(Project = 'Athena' or Project = +'Hawaii') and Category = 'Aspen'" are not supported. + +=item @fields + +An array of fieldnames to retrieve + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $result or ($result, $nbrRecs) + +Internal structure to be used with getNext. If in an array context then $nbrRecs +is also returned. + +=back + +=for html
    + +=cut + + $condition ||= ''; + + unless ($self->connected) { + $self->_setError ('You must connect to Clearquest before you can call find', '-1'); + + return; + } # unless + + my $entityDef; + + eval {$entityDef = $self->{session}->GetEntityDef ($table)}; + + if ($@) { + $self->_setError ($@, -1); + + return ($@, -1); + } # if + + @fields = $self->_setFields ($table, @fields); + + return unless @fields; + + my $query = $self->{session}->BuildQuery ($table); + + foreach (@fields) { + eval {$query->BuildField ($_)}; + + if ($@) { + $self->_setError ($@); + + carp $@; + } # if + } # foreach + + $self->_parseConditional ($query, $condition); + + return if $self->error; + + my $result = $self->{session}->BuildResultSet ($query); + my $nbrRecs = $result->ExecuteAndCountRecords; + + $self->_setError; + + my %resultSet = ( + result => $result + ); + + if (wantarray) { + return (\%resultSet, $nbrRecs); + } else { + return \%resultSet + } # if +} # find + +sub findIDs ($) { + my ($str) = @_; + +=pod + +=head2 findIDs ($) + +Given a $str or a reference to an array of strings, this function returns a list +of Clearquest IDs found in the $str. If called in a scalar context this function +returns a comma separated string of IDs found. Note that duplicate IDs are +eliminated. Also, the lists of IDs may refer to different Clearquest databases. + +Parameters: + +=for html
    + +=over + +=item $str + +String or reference to an array of strings to search + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item @IDs or $strIDs + +Either an array of CQ IDs or a comma separated list of CQ IDs. + +=back + +=for html
    + +=cut + + $str = join ' ', @$str if ref $str eq 'ARRAY'; + + my @IDs = $str =~ /([A-Za-z]\w{1,4}\d{8})/gs; + + my %IDs; + + map { $IDs{$_} = 1; } @IDs; + + if (wantarray) { + return keys %IDs; + } else { + return join ',', keys %IDs; + } # if +} # findIDs + +sub get ($$;@) { + my ($self, $table, $id, @fields) = @_; + +=pod + +=head2 get ($$) + +Return a record that you have the id or key of. + +Parameters: + +=for html
    + +=over + +=item $table + +The $table to get the record from + +=item $id + +The $id or key to use to retrieve the record + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item %record + +Hash of name/value pairs for all the fields in $table + +=back + +=for html
    + +=cut + + unless ($self->connected) { + $self->_setError ('You must connect to Clearquest before you can call get', '-1'); + + return; + } # unless + + @fields = $self->_setFields ($table, @fields); + + return unless @fields; + + my $entity; + + eval {$entity = $self->{session}->GetEntity ($table, $id)}; + + if ($@) { + $self->_setError ($@); + + return; + } # if + + my %record; + + foreach (@fields) { + my $fieldType = $entity->GetFieldValue ($_)->GetType; + + if ($fieldType == $CQPerlExt::CQ_REFERENCE_LIST) { + $record{$_} = $entity->GetFieldValue ($_)->GetValueAsList; + } else { + $record{$_} = $entity->GetFieldValue ($_)->GetValue; + $record{$_} ||= '' if $self->{emptyStringForUndef}; + + # Fix any UTC dates + if ($fieldType == $CQPerlExt::CQ_DATE_TIME) { + $record{$_} = _UTC2Localtime ($record{$_}); + } # if + } # if + } # foreach + + $self->_setError; + + return %record; +} # get + +sub getDBID ($$;@) { + my ($self, $table, $dbid, @fields) = @_; + +=pod + +=head2 getDBID ($$;@) + +Return a record that you have the dbid + +Parameters: + +=for html
    + +=over + +=item $table + +The $table to get the record from + +=item $dbid + +The $dbid to use to retrieve the record + +=item @fields + +Array of field names to retrieve (Default: All fields) + +Note: Avoid getting all fields for large records. It will be slow and bloat your +script's memory usage. + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item %record + +Hash of name/value pairs for all the fields in $table + +=back + +=for html
    + +=cut + + unless ($self->connected) { + $self->_setError ('You must connect to Clearquest before you can call getDBID', '-1'); + + return; + } # unless + + @fields = $self->_setFields ($table, @fields); + + return if @fields; + + my $entity; + + eval {$entity = $self->{session}->GetEntityByDbId ($table, $dbid)}; + + if ($@) { + $self->_setError ($@); + + return; + } # if + + my %record; + + foreach (@fields) { + my $fieldType = $entity->GetFieldValue ($_)->GetType; + + if ($fieldType == $CQPerlExt::CQ_REFERENCE_LIST) { + $record{$_} = $entity->GetFieldValue ($_)->GetValueAsList; + } else { + $record{$_} = $entity->GetFieldValue ($_)->GetValue; + $record{$_} ||= '' if $self->{emptyStringForUndef}; + + # Fix any UTC dates + if ($fieldType == $CQPerlExt::CQ_DATE_TIME) { + $record{$_} = _UTC2Localtime ($record{$_}); + } # if + } # if + } # foreach + + $self->_setError; + + return %record; +} # getDBID + +sub getDynamicList ($) { + my ($self, $list) = @_; + +=pod + +=head2 getDynamicList ($) + +Return the entries of a dynamic list + +Parameters: + +=for html
    + +=over + +=item $list + +The name of the dynamic list + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item @entries + +An array of entries from the dynamic list + +=back + +=for html
    + +=cut + + return () unless $self->connected; + + return @{$self->{session}->GetListMembers ($list)}; +} # getDynamicList + +sub getNext ($) { + my ($self, $result) = @_; + +=pod + +=head2 getNext ($) + +Return the next record that qualifies from a preceeding call to the find method. + +Parameters: + +=for html
    + +=over + +=item $result + +The $result returned from find. + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item %record + +Hash of name/value pairs for the @fields specified to find. + +=back + +=for html
    + +=cut + + unless ($self->connected) { + $self->_setError ('You must connect to Clearquest before you can call getNext', '-1'); + + return; + } # unless + +# Here we need to do special processing to gather up reference list fields, if +# any. If we have a reference list field in the field list then Clearquest +# returns multiple records - one for each entry in the reference list. Thus if +# you were getting say the key field of a record and a reference list field like +# say Projects, you might see: +# +# Key Value Projects +# --------- -------- +# key1 Athena +# key1 Apollo +# key1 Gemini +# +# Things get combinatoric when multiple reference list fields are involved. Our +# strategy here is to keep gathering all fields that change into arrays assuming +# they are reference fields as long as the dbid field has not changed. +my %record; + +while () { + unless ($result->{lastDBID}) { + # Move to the first record + last unless $result->{result}->MoveNext == $CQPerlExt::CQ_SUCCESS; + } elsif ($result->{lastDBID} == $result->{thisDBID}) { + # If the dbid is the same then we have at least one reference list field + # in the request so we need to move to the next record + last unless $result->{result}->MoveNext == $CQPerlExt::CQ_SUCCESS; + } else { + # If lastDBID != thisDBID then set lastDBID to thisDBID so we can process + # this group + $result->{lastDBID} = $result->{thisDBID}; + + delete $result->{lastRecord}; + } # unless + + my $nbrColumns = $result->{result}->GetNumberOfColumns; + + my $column = 1; + + # Format %record + while ($column <= $nbrColumns) { + my $value = $result->{result}->GetColumnValue ($column); + + $value ||= '' if $self->{emptyStringForUndef}; + + # Fix any UTC dates - _UTC2Localtime will only modify data if the data + # matches a UTC datetime. + $value = _UTC2Localtime ($value); + + $record{$result->{result}->GetColumnLabel ($column++)} = $value; + } # while + + %{$result->{lastRecord}} = %record unless $result->{lastRecord}; + + # Store this record's DBID + $result->{thisDBID} = $record{dbid}; + + if ($result->{lastDBID}) { + if ($result->{thisDBID} == $result->{lastDBID}) { + # Since the dbid's are the same, we have at least one reference list field + # and we need to compare all fields + foreach my $field (keys %record) { + # If the field is blank then skip it + next if $record{$field} eq ''; + + # Here we check the field in %lastRecord to see if it was a reference + # list with more than one entry. + if (ref \$result->{lastRecord}{$field} eq 'ARRAY') { + # Check to see if this entry is already in the list of current entries + next if grep {/^$record{$field}$/} @{$result->{lastRecord}{$field}}; + } # if + + # This checks to see if the current field is a scalar and we have a new + # value, then the scalar needs to be changed to an array + if (ref \$result->{lastRecord}{$field} eq 'SCALAR') { + # If the field is the same value then no change, no array. We do next + # to start processing the next field + next if $result->{lastRecord}{$field} eq $record{$field}; + + # Changed $lastRecord{$_} to a reference to an ARRAY + $result->{lastRecord}{$field} = [$result->{lastRecord}{$field}, $record{$field}]; + } else { + # Push the value only if it does not already exists in the array + push @{$result->{lastRecord}{$field}}, $record{$field} + unless grep {/^$record{$field}$/} @{$result->{lastRecord}{$field}}; + } # if + } # foreach + + # Transfer %lastRecord -> %record + %record = %{$result->{lastRecord}}; + } else { + %record = %{$result->{lastRecord}}; + + last; + } # if + } # if + + # The $lastDBID is now $thisDBID + $result->{lastDBID} = $result->{thisDBID}; + + # Update %lastRecord + %{$result->{lastRecord}} = %record; +} # while + + $self->_setError; + + return %record; +} # getNext + +sub id2db ($) { + my ($ID) = @_; + +=pod + +=head2 id2db ($) + +This function returns the database name given an ID. + +Parameters: + +=for html
    + +=over + +=item $ID + +The ID to extract the database name from + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $database + +Returns the name of the database the ID is part of or undef if not found. + +=back + +=for html
    + +=cut + + if ($ID =~ /([A-Za-z]\w{1,4})\d{8}/) { + return $1; + } else { + return; + } # if +} # id2db + +sub key ($$) { + my ($self, $table, $dbid) = @_; + +=pod + +=head2 key ($$) + +Return the key of the record given a $dbid + +Parameters: + +=for html
    + +=over + +=item $table + +Name of the table to lookup + +=item $dbid + +Database ID of the record to retrieve + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item key + +=back + +=for html
    + +=cut + + unless ($self->connected) { + $self->_setError ('You must connect to Clearquest before you can call key', '-1'); + + return; + } # unless + + my $entity; + + eval {$entity = $self->{session}->GetEntityByDbId ($table, $dbid)}; + + return $entity->GetDisplayName; +} # key + +sub modify ($$$$;@) { + my ($self, $table, $key, $action, $values, @ordering) = @_; + +=pod + +=head2 modify ($$$$;@) + +Update record(s) + +Parameters: + +=for html
    + +=over + +=item $table + +The $table to get the record from + +=item $key + +The $key identifying the record to modify + +=item $action + +Action to perform the modification under. Default is 'Modify'. + +=item $values + +Hash reference containing name/value that have the new values for the fields + +=item @ordering + +Array containing field names that need to be processed in order. Not all fields +mentioned in the $values hash need be mentioned here. If you have fields that +must be set in a particular order you can mention them here. So, if you're +modifying the Defect record, but you need Project set before Platform, you need +only pass in an @ordering of qw(Project Platform). They will be done first, then +all of the rest of the fields in the $values hash. If you have no ordering +dependencies then you can simply omit @ordering. + +Note that the best way to determine if you have an ordering dependency try using +a Clearquest client and note the order that you set fields in. If at anytime +setting one field negates another field via action hook code then you have just +figured out that this field needs to be set before the file that just got +negated. + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $errmsg + +The $errmsg, if any, when performing the update (empty string for success) + +=back + +=for html
    + +=cut + + unless ($self->connected) { + $self->_setError ('You must connect to Clearquest before you can call modify', '-1'); + + return $self->{errmsg}; + } # unless + + my %record = $self->get ($table, $key, qw(dbid)); + + return $self->modifyDBID ($table, $record{dbid}, $action, $values, @ordering); +} # modify + +sub modifyDBID ($$$$;@) { + my ($self, $table, $dbid, $action, $values, @ordering) = @_; + +=pod + +=head2 modifyDBID ($$$%) + +Update a unique record (by DBID) + +Parameters: + +=for html
    + +=over + +=item $table + +The $table to get the record from + +=item $dbid + +The $dbid of the record to update. Note that the find method always includes the +dbid of a record in the hash that it returns. + +=item $action + +Action to perform the modification under. Default is 'Modify'. + +=item %update + +Hash containing name/value that have the new values for the fields + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $errmsg + +The $errmsg, if any, when performing the update (empty string for success) + +=back + +=for html
    + +=cut + $action ||= 'Modify'; + + my %values = %$values; + + my $entity; + + eval {$entity = $self->{session}->GetEntityByDbId ($table, $dbid)}; + + if ($@) { + $self->_setError ($@); + + return; + } # if + + eval {$entity->EditEntity ($action)}; + + if ($@) { + $self->_setError ($@); + + return $@; + } # if + + # First process all fields in @ordering, if specified + foreach (@ordering) { + if ($values{$_}) { + $self->{errmsg} = $self->_setFieldValue ($table, $_, $values{$_}); + } else { + $self->_setError ("$_ from the ordering array is not present in the value hash", -1); + } # if + + last unless $self->{errmsg} eq ''; + } # foreach + + return $self->{errmsg} unless $self->{errmsg} eq ''; + + # Now process the rest of the values + foreach my $fieldName (keys %values) { + next if grep {$fieldName eq $_} @ordering; + + $self->{errmsg} = $self->_setFieldValue ($entity, $table, $fieldName, $values{$fieldName}); + + last unless $self->{errmsg} eq ''; + } # foreach + + $self->_setError ($self->{errmsg}); + + return $self->{errmsg} unless $self->{errmsg} eq ''; + + $self->{errmsg} = $self->_commitRecord ($entity); + $self->{error} = $self->{errmsg} eq '' ? 0 : 1; + + return $self->{errmsg}; +} # modifyDBID + +sub module () { + my ($self) = @_; + +=pod + +=head2 module + +Returns the current back end module we are using + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item module + +=back + +=for html
    + +=cut + + return $self->{module}; +} # module + +sub new (;%) { + my ($class, %parms) = @_; + +=pod + +=head2 new () + +Construct a new Clearquest object. + +Parameters: + +Below are the key values for the %parms hash. + +=for html
    + +=over + +=item CQ_SERVER + +Webhost for REST module + +=item CQ_USERNAME + +Username to use to connect to the database + +=item CQ_PASSWORD + +Password to use to connect to the database + +=item CQ_DATABASE + +Clearquest database to connect to + +=item CQ_DBSET + +Database set to connect to + +=item CQ_MODULE + +One of 'rest', 'api' or 'client' (Default: From cq.conf). This determines which +backend module will be used. + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Clearquest object + +=back + +=for html
    + +=cut + + $parms{CQ_DATABASE} ||= $OPTS{CQ_DATABASE}; + $parms{CQ_USERNAME} ||= $OPTS{CQ_USERNAME}; + $parms{CQ_PASSWORD} ||= $OPTS{CQ_PASSWORD}; + $parms{CQ_DBSET} ||= $OPTS{CQ_DBSET}; + + my $self = bless { + server => $parms{CQ_SERVER}, + port => $parms{CQ_PORT}, + database => $parms{CQ_DATABASE}, + dbset => $parms{CQ_DBSET}, + username => $parms{CQ_USERNAME}, + password => $parms{CQ_PASSWORD}, + emptyStringForUndef => 0, + returnSystemFields => 0, + }, $class; + + my $module = delete $parms{CQ_MODULE}; + + $module ||= $OPTS{CQ_MODULE}; + + $module = lc $module; + + if ($module eq 'rest') { + require Clearquest::REST; + + $self->{webhost} = $parms{CQ_WEBHOST} || $OPTS{CQ_WEBHOST}; + + $self = Clearquest::REST->new ($self); + } elsif ($module eq 'client') { + require Clearquest::Client; + + $self->{server} = $parms{CQ_SERVER} || $OPTS{CQ_SERVER}; + $self->{port} = $parms{CQ_PORT} || $OPTS{CQ_PORT}; + + $self = Clearquest::Client->new ($self); + } elsif ($module ne 'api') { + croak "Unknown interface requested - $module"; + } # if + + $self->{module} = $module; + + # Save reference to instaniated instance of this object to insure that global + # variables are properly disposed of + push @objects, $self; + + return $self; +} # new + +sub server () { + my ($self) = @_; + +=pod + +=head2 server + +Returns the current server if applicable + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $server + +For api this will return ''. For REST and client/server this will return the +server name that we are talking to. + +=back + +=for html
    + +=cut + + return $self->{server}; +} # server + +sub setOpts (%) { + my ($self, %opts) = @_; + +=pod + +=head2 setOpts + +Set options for operating + +Parameters: + +=for html
    + +=over + +=item %opts + +=back + +Options to set. The only options currently supported are emptyStringForUndef +and returnSystemFields. If set emptyStringForUndef will return empty strings for +empty fields instead of undef. Default: Empty fields are represented with undef. + +System-owned fields are used internally by IBM Rational ClearQuest to maintain +information about the database. You should never modify system fields directly +as it could corrupt the database. If returnSystemFields is set then system +fields will be returned. Default: System fields will not be returned unless +explicitly stated in the @fields parameter. This means that if you do not +specify any fields in @fields, all fields will be returned except system fields, +unless you set returnSystemFields via this method or you explicitly mention the +system field in your @fields parameter. + +=for html
    + +Returns: + +=for html
    + +=over + +=item Nothing + +=back + +=for html
    + +=cut + + $self->{emptyStringForUndef} = $opts{emptyStringForUndef} + if $opts{emptyStringForUndef}; + $self->{returnSystemFields} = $opts{returnSystemFields} + if $opts{returnSystemFields}; +} # setOpts + +sub getOpt ($) { + my ($self, $option) = @_; + +=pod + +=head2 getOpt + +Get option + +Parameters: + +=for html
    + +=over + +=item $option + +=back + +Option to retrieve. If non-existant then undef is returned. + +=for html
    + +Returns: + +=for html
    + +=over + +=item $option or undef if option doesn't exist + +=back + +=for html
    + +=cut + + my @validOpts = qw (emptyStringForUndef returnSystemFields); + + if (grep {$option eq $_} @validOpts) { + return $self->{$option}; + } else { + return; + } # if +} # getOpt + +sub username () { + my ($self) = @_; + +=pod + +=head2 username + +Returns the current username (or the username that would be used) + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item username + +=back + +=for html
    + +=cut + + return $self->{username}; +} # username + +sub webhost () { + my ($self) = @_; + + return $self->{webhost}; +} # webhost + +1; + +=pod + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +=head2 ClearSCM Perl Modules + +=for html

    GetConfig

    + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this module + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2007, ClearSCM, Inc. All rights reserved. + +=cut diff --git a/lib/Clearquest/Admin.pm b/lib/Clearquest/Admin.pm new file mode 100644 index 0000000..980a594 --- /dev/null +++ b/lib/Clearquest/Admin.pm @@ -0,0 +1,499 @@ +=pod + +=head1 NAME $RCSfile: Admin.pm,v $ + +Clearquest Admin - Provide access Clearquest AdminSession objects + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.3 $ + +=item Created + +Wed Apr 18 09:59:47 PDT 2012 + +=item Modified + +$Date: 2012/11/09 06:53:11 $ + +=back + +=head1 SYNOPSIS + +Provides an interface to the Clearquest AdminSession objects. These are for +dealing with objects in the schema, not the user database. + +=head1 DESCRIPTION + +The Admin object allows you to create a session object associated with a schema +repository. This allows you to retrieve and modify information in a schema +repository. You must log into the Admin object as an admin user. + +Functions are available to deal with users, groups, databases and schemas. + +Note: Admin object needs to be filled out with more functions over time... + +=head1 ROUTINES + +The following methods are available: + +=cut + +package Clearquest::Admin; + +use strict; +use warnings; + +use Carp; +use File::Basename; +use FindBin; + +use DateUtils; +use Display; +use GetConfig; + +use Clearquest; + +# Seed options from config file +my $config = $ENV{CQD_CONF} || dirname (__FILE__) . '/../../etc/cqdservice.conf'; + +croak "Unable to find config file $config" unless -r $config; + +our %OPTS = GetConfig $config; + +our $VERSION = '$Revision: 1.3 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +# Override options if in the environment +$OPTS{CQD_HOST} = $ENV{CQD_HOST} + if $ENV{CQD_HOST}; +$OPTS{CQD_PORT} = $ENV{CQD_PORT} + if $ENV{CQD_PORT}; +$OPTS{CQD_MULTITHREADED} = $ENV{CQD_MULTITHREADED} + if defined $ENV{CQD_MULTITHREADED}; +$OPTS{CQD_DATABASE} = $ENV{CQD_DATABASE} + if $ENV{CQD_DATABASE}; +$OPTS{CQD_USERNAME} = $ENV{CQD_USERNAME} + if $ENV{CQD_USERNAME}; +$OPTS{CQD_PASSWORD} = $ENV{CQD_PASSWORD} + if $ENV{CQD_PASSWORD}; +$OPTS{CQD_DBSET} = $ENV{CQD_DBSET} + if $ENV{CQD_DBSET}; + +sub getUser ($) { + my ($self, $loginname) = @_; + +=pod + +=head2 getUser ($) + +Returns a user object for the specified user or undef. + +Parameters: + +=for html
    + +=over + +=item $username + +The $loginname to retrieve the user object for + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item User object + +A user object + +=back + +=for html
    + +=cut + + return $self->{session}->GetUser ($loginname); +} # getNext + +sub userActive ($) { + my ($self, $loginname) = @_; + +=pod + +=head2 userActive ($) + +Returns a true if user is active + +Parameters: + +=for html
    + +=over + +=item $username + +The $loginname to see if active + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item 1 if true, 0 if false + +=back + +=for html
    + +=cut + + my $user = $self->getUser ($loginname); + + if ($user) { + return $user->GetActive; + } else { + return 0; + } # if +} # userActive + +sub userActivate ($) { + my ($self, $loginname) = @_; + +=pod + +=head2 userActivate ($) + +Activates a user if they were inactive + +Parameters: + +=for html
    + +=over + +=item $username + +The $loginname to activate + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item nothing + +=back + +=for html
    + +=cut + + unless ($self->activeUser ($logname)) { + my $user = $self->getUser ($loginname); + + if ($user) { + $user->SetUser (1); + } # if + } # unless +} # userActive + +sub userActivate ($) { + my ($self, $loginname) = @_; + +=pod + +=head2 userInactivate ($) + +Inactivates a user if they were active + +Parameters: + +=for html
    + +=over + +=item $username + +The $loginname to inactivate + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item nothing + +=back + +=for html
    + +=cut + + if ($self->activeUser ($logname)) { + my $user = $self->getUser ($loginname); + + if ($user) { + $user->SetUser (0); + } # if + } # unless +} # userInactive + +sub new () { + my ($class, $username, $password, $dbset) = @_; + + my $self = bless {}, $class; + + if (ref $username eq 'HASH') { + my %parms = %$username; + + $self->{username} = $parms{username}; + $self->{password} = $parms{password}; + $self->{dbset} = $parms{dbset}; + } else { + $self->{username} = $username; + $self->{password} = $password; + $self->{dbset} = $dbset; + } # if + + return $self; +} # new + +sub connect (;$$$) { + +=pod + +=head2 connect (;$$$) + +Connect to the Clearquest schema database. You can supply parameters such as +username, password, etc and they will override any passed to +Clearquest::Admin::new (or those coming from ../etc/cq.conf) + +Parameters: + +=for html
    + +=over + +=item $username + +Username to use to connect to the schema database + +=item $password + +Password to use to connect to the schema database + +=item $dbset + +Database set to connect to (Default: Connect to the default dbset) + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item 1 + +=back + +=for html
    + +=cut + + my ($self, $username, $password, $dbset) = @_; + + $self->{username} = $username if $username; + $self->{password} = $password if $password; + $self->{database} = $database if $database; + $self->{dbset} = $dbset if $dbset; + + $self->{session} = CQAdminSession::Build; + + # TODO: Should handle failures better + $self->{session}->($self->{username}, + $self->{password}, + $self->{dbset}); + $self->{loggedin} = 1; + + return $self->{loggedin}; +} # connect + +sub connected () { + my ($self) = @_; + +=pod + +=head2 connected () + +Returns 1 if we are currently connected to a Clearquest Admin Schema Database + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item 1 if logged in - 0 if not + +=back + +=for html
    + +=cut + + return $self->{loggedin}; +} # connected + +sub disconnect () { + my ($self) = @_; + +=pod + +=head2 disconnect () + +Disconnect from Clearquest Admin Schema Database + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item nothing + +=back + +=for html
    + +=cut + + CQAdminSession::Unbuild ($self->{session}); + + undef $self->{session}; + + $self->{loggedin} = 0; + + return; +} # disconnect + + + +1; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + DateUtils + Display + GetConfig + +=end man + +=begin html + +
    +DateUtils
    +Display
    +GetConf
    +
    + +=end html + +=head1 SEE ALSO + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this module. + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2011, ClearSCM, Inc. All rights reserved. + +=cut diff --git a/lib/Clearquest/Client.pm b/lib/Clearquest/Client.pm new file mode 100644 index 0000000..479e97f --- /dev/null +++ b/lib/Clearquest/Client.pm @@ -0,0 +1,504 @@ +=pod + +=head1 NAME $RCSfile: Client.pm,v $ + +Clearquest client - Provide access to a running Clearquest server + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 2.8 $ + +=item Created + +Monday, October 10, 2011 5:02:07 PM PDT + +=item Modified + +$Date: 2013/05/30 15:43:28 $ + +=back + +=head1 SYNOPSIS + +Provides an interface to a running Clearquest Server over the network. This +means that you can use any Perl you like, not just cqperl, and you don't need +to have Clearquest installed locally. In fact you can run from say Linux and +talk to the Clearquest Server running on Windows. + +=head1 DESCRIPTION + +The server allows both read and write access to a Clearquest database as defined +in cqdservice.conf file. Note the username/password must be of a user who can +write to the Clearquest database for write access to succeed. + +A hash is passed into to the execute method, which the client should use to talk +to the server, that describes relatively simple protocol to tell the server what +action to perform. In both the read case and the read/write case a field named +id should be defined that has a value of "=" (e.g. +"defect=BUGDB00034429"). + +For the read case the rest of the keys are the names of the fields to retrieve +with values that are undef'ed. For read/write, the rest of hash contains name +value pairs of fields to set and their values. + +Execute returns a status and a hash of name value pairs for the read case and an +array of lines for any error messages for the read/write case. + +=head1 ROUTINES + +The following methods are available: + +=cut + +package Clearquest::Client; + +use strict; +use warnings; + +use Carp; +use File::Basename; +use FindBin; +use IO::Socket; +use Net::hostent; +use POSIX ":sys_wait_h"; +use Data::Dumper; + +use Clearquest; + +use parent 'Clearquest'; + +$Data::Dumper::Indent = 0; + +our $VERSION = '$Revision: 2.8 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +=pod + +=head1 Options + +Options are keep in the cq.conf file in the etc directory. They specify the +default options listed below. Or you can export the option name to the env(1) to +override the defaults in cq.conf. Finally you can programmatically set the +options when you call new by passing in a %parms hash. The items below are the +key values for the hash. + +=for html
    + +=over + +=item CQ_SERVER + +The CQ Server host to connect to + +=item CQ_PORT + +Port number to contact the server at (Default: From cq.conf) + +=item CQ_USERNAME + +User name to connect as (Default: From cq.conf) + +=item CQ_PASSWORD + +Password for CQ_USERNAME + +=item CQ_DATABASE + +Name of database to connect to (Default: From cq.conf) + +=item CQ_DBSET + +Database Set name (Default: From cq.conf) + +=back + +=cut + +sub _parseCmd ($) { + my ($self, $cmd) = @_; +} # _parseCmd + +sub _request ($;@) { + my ($self, $call, @parms) = @_; + + my $server = $self->{socket}; + + my $request = $call; + + $request .= ' '; + $request .= Dumper \@parms; + $request .= "\n"; + + # Send request + print $server $request; + + # Get response + my ($response, $status, @output); + + while (defined ($response = <$server>)) { + if ($response =~ /Clearquest::Server Status: (-*\d+)/) { + $status = $1; + last; + } # if + + chomp $response; chop $response if $response =~ /\r$/; + + push @output, $response; + } # while + + unless (@output) { + push @output, 'Unknown or unhandled error'; + + $status = -1; + } # unless + + $self->_setError (join ("\n", @output), $status) if $status; + + return ($status, @output); +} # _request + +sub add ($$;@) { + my ($self, $table, $values, @ordering) = @_; + + my @parms; + + push @parms, $table, Dumper ($values), @ordering; + + $self->_request ('add', @parms); + + return $self->errmsg; +} # add + +sub connect (;$$$$) { + my ($self, $username, $password, $database, $dbset) = @_; + + return $self->connectToServer; +} # connect + +sub connectToServer (;$$) { + my ($self, $server, $port) = @_; + + $self->{socket} = IO::Socket::INET->new ( + Proto => 'tcp', + PeerAddr => $self->{server}, + PeerPort => $self->{port}, + ); + + unless ($self->{socket}) { + $self->_setError ($!, 1); + + return; + } # unless + + $self->{socket}->autoflush; + + # Now tell the server what database we wish to use + my ($status, @output) = $self->_request ( + 'open', + $self->{database}, + $self->{username}, + $self->{password}, + $self->{dbset}, + ); + + $self->{loggedin} = $status == 0; + + $self->_setError (@output, $status); + + return $self->connected; +} # connectToServer + +sub dbsets () { + my ($self) = @_; + + my ($status, @output) = $self->_request ('dbsets'); + + return @output; +} # dbsets + +sub delete ($$) { + my ($self, $table, $key) = @_; + + my @parms; + + push @parms, $table; + push @parms, $key; + + my ($status, @output) = $self->_request ('delete', @parms); + + return $self->errmsg; +} # delete + +sub DESTROY () { + my ($self) = @_; + + $self->disconnectFromServer; +} # DESTROY + +sub disconnect () { + my ($self) = @_; + + $self->disconnectFromServer; + + $self->{loggedin} = 0; + + return; +} # disconnect + +sub disconnectFromServer () { + my ($self) = @_; + + if ($self->{socket}) { + $self->_request ('end'); + + close $self->{socket}; + + undef $self->{socket}; + } # if + + return; +} # disconnectFromServer + +sub find ($;$@) { + my ($self, $table, $condition, @fields) = @_; + + $condition ||= ''; + + # TODO: Need to return nbrrecs + my ($status, @output) = $self->_request ('find', $table, $condition, @fields); + + if ($self->error) { + return (undef, $self->errmsg); + } else { + return ($status, $output[1]); + } # if +} # find + +sub get ($$@) { + my ($self, $table, $key, @fields) = @_; + + my %record; + + $self->_setError ('', 0); + + my ($status, @output) = $self->_request ('get', $table, $key, @fields); + + return if $status; + + foreach (@output) { + my ($field, $value) = split /\@\@/; + + $value =~ s/ /\n/g; + + if ($record{$field}) { + if (ref $record{$field} ne 'ARRAY') { + my $valueOne = $record{$field}; + + $record{$field} = (); + + push @{$record{$field}}, $valueOne, $value; + } else { + push @{$record{$field}}, $value; + } # if + } else { + $record{$field} = $value; + } # if + } # foreach + + return %record; +} # get + +sub getDBID ($$@) { + my ($self, $table, $dbid, @fields) = @_; + + my %record = (); + + my ($status, @output) = $self->_request ('getDBID', $table, $dbid, @fields); + + return ($status, %record) if $status; + + foreach (@output) { + my ($field, $value) = split /\@\@/; + + $value =~ s/ /\n/g; + + if ($record{$field}) { + if (ref $record{$field} ne 'ARRAY') { + my $valueOne = $record{$field}; + + $record{$field} = (); + + push @{$record{$field}}, $valueOne, $value; + } else { + push @{$record{$field}}, $value; + } # if + } else { + $record{$field} = $value; + } # if + } # foreach + + return %record; +} # getDBID + +sub getDynamicList ($) { + my ($self, $list) = @_; + + my ($status, @output) = $self->_request ('getDynamicList', $list); + + return @output; +} # getDynamicList + +sub getNext ($) { + my ($self, $result) = @_; + + my ($status, @output) = $self->_request ('getNext', ()); + + return if $status; + + my %record; + + foreach (@output) { + my ($field, $value) = split /\@\@/; + + $value =~ s/ /\n/g; + + if ($record{$field}) { + if (ref $record{$field} ne 'ARRAY') { + push @{$record{$field}}, $record{$field}, $value; + } else { + push @{$record{$field}}, $value; + } # if + } else { + $record{$field} = $value; + } # if + } # foreach + + return %record; +} # getNext + +sub key ($$) { + my $self = shift; + + my ($status, @output) = $self->_request ('key', @_); + + return $output[0]; +} # key + +sub modify ($$$$;@) { + my ($self, $table, $key, $action, $values, @ordering) = @_; + + $action ||= 'Modify'; + + my @parms; + + push @parms, $table, $key, $action, Dumper ($values), @ordering; + + $self->_request ('modify', @parms); + + return $self->errmsg; +} # modify + +sub modifyDBID ($$$$;@) { + my ($self, $table, $dbid, $action, $values, @ordering) = @_; + + my @parms; + + push @parms, $table, $dbid, $action, Dumper ($values), @ordering; + + $self->_request ('modifyDBID', @parms); + + return $self->errmsg; +} # modifyDBID + +sub port () { + my ($self) = @_; + + return $self->{port}; +} # port + +sub new () { + my ($class, $self) = @_; + + $$self{server} ||= $Clearquest::OPTS{CQ_SERVER}; + $$self{port} ||= $Clearquest::OPTS{CQ_PORT}; + + bless $self, $class; +} # new + +sub shutdown () { + my ($self) = @_; + + if ($self->{socket}) { + $self->_request ('shutdown'); + } # if +} # shutdown + +1; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + DateUtils + Display + GetConfig + +=end man + +=begin html + +
    +DateUtils
    +Display
    +GetConf
    +
    + +=end html + +=head1 SEE ALSO + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this module. + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2011, ClearSCM, Inc. All rights reserved. + +=cut diff --git a/lib/Clearquest/DBService.pm b/lib/Clearquest/DBService.pm new file mode 100644 index 0000000..0ca5300 --- /dev/null +++ b/lib/Clearquest/DBService.pm @@ -0,0 +1,633 @@ +=pod + +=head1 NAME $RCSfile: DBService.pm,v $ + +DB Service - Provide access to Clearquest database + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.2 $ + +=item Created + +Monday, October 10, 2011 5:02:07 PM PDT + +=item Modified + +$Date: 2011/12/31 02:13:37 $ + +=back + +=head1 SYNOPSIS + +Provides an interface to the Clearquest database over the network. + +This library implements both the daemon portion of the server and the client +API. + +=head1 DESCRIPTION + +The server allows both read and write access to a Clearquest database as defined +in cqdservice.conf file. Note the username/password must be of a user who can +write to the Clearquest database for write access to succeed. + +A hash is passed into to the execute method, which the client should use to talk +to the server, that describes relatively simple protocol to tell the server what +action to perform. In both the read case and the read/write case a field named +id should be defined that has a value of "=" (e.g. +"defect=BUGDB00034429"). + +For the read case the rest of the keys are the names of the fields to retrieve +with values that are undef'ed. For read/write, the rest of hash contains name +value pairs of fields to set and their values. + +Execute returns a status and a hash of name value pairs for the read case and an +array of lines for any error messages for the read/write case. + +=head1 ROUTINES + +The following methods are available: + +=cut + +package Clearquest::DBService; + +use strict; +use warnings; + +use Carp; +use File::Basename; +use FindBin; +use IO::Socket; +use Net::hostent; +use POSIX ":sys_wait_h"; + +use DateUtils; +use Display; +use GetConfig; + +# Seed options from config file +my $config = $ENV{CQD_CONF} || dirname (__FILE__) . '/../../etc/cqdservice.conf'; + +croak "Unable to find config file $config" unless -r $config; + +our %OPTS = GetConfig $config; + +our $VERSION = '$Revision: 1.2 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +# Override options if in the environment +$OPTS{CQD_HOST} = $ENV{CQD_HOST} + if $ENV{CQD_HOST}; +$OPTS{CQD_PORT} = $ENV{CQD_PORT} + if $ENV{CQD_PORT}; +$OPTS{CQD_MULTITHREADED} = $ENV{CQD_MULTITHREADED} + if defined $ENV{CQD_MULTITHREADED}; +$OPTS{CQD_DATABASE} = $ENV{CQD_DATABASE} + if $ENV{CQD_DATABASE}; +$OPTS{CQD_USERNAME} = $ENV{CQD_USERNAME} + if $ENV{CQD_USERNAME}; +$OPTS{CQD_PASSWORD} = $ENV{CQD_PASSWORD} + if $ENV{CQD_PASSWORD}; +$OPTS{CQD_DBSET} = $ENV{CQD_DBSET} + if $ENV{CQD_DBSET}; + +sub new () { + my ($class) = @_; + + my $cqdservice = bless {}, $class; + + $cqdservice->{multithreaded} = $OPTS{CQD_MULTITHREADED}; + + return $cqdservice; +} # new + +sub _tag ($) { + my ($self, $msg) = @_; + + my $tag = YMDHMS; + $tag .= ' '; + $tag .= $self->{pid} ? "[$self->{pid}] " : ''; + + return "$tag$msg"; +} # _tag + +sub _verbose ($) { + my ($self, $msg) = @_; + + verbose $self->_tag ($msg); + + return; +} # _verbose + +sub _debug ($) { + my ($self, $msg) = @_; + + debug $self->_tag ($msg); + + return; +} # _debug + +sub _log ($) { + my ($self, $msg) = @_; + + display $self->_tag ($msg); + + return; +} # log + +sub _funeral () { + debug 'Entered _funeral'; + + while (my $childpid = waitpid (-1, WNOHANG) > 0) { + my $status = $?; + + debug "childpid: $childpid - status: $status"; + + if ($childpid != -1) { + local $SIG{CHLD} = \&_funeral; + + my $msg = 'Child has died'; + $msg .= $status ? " with status $status" : ''; + + verbose "[$childpid] $msg" + if $status; + } else { + debug "All children reaped"; + } # if + } # while + + return; +} # _funeral + +sub _endServer () { + display "CQDService V$VERSION shutdown at " . localtime; + + # Kill process group + kill 'TERM', -$$; + + # Wait for all children to die + while (wait != -1) { + # do nothing + } # while + + # Now that we are alone, we can simply exit + exit; +} # _endServer + +sub _restartServer () { + # Not sure what to do on a restart server + display 'Entered _restartServer'; + + return; +} # _restartServer + +sub setMultithreaded ($) { + my ($self, $value) = @_; + + my $oldValue = $self->{multithreaded}; + + $self->{multithreaded} = $value; + + return $oldValue; +} # setMultithreaded + +sub getMultithreaded () { + my ($self) = @_; + + return $self->{multithreaded}; +} # getMultithreaded + +sub connectToServer (;$$) { + my ($self, $host, $port) = @_; + + $host ||= $OPTS{CQD_HOST}; + $port ||= $OPTS{CQD_PORT}; + + $self->{socket} = IO::Socket::INET->new ( + Proto => 'tcp', + PeerAddr => $host, + PeerPort => $port, + ); + + return unless $self->{socket}; + + $self->{socket}->autoflush; + + $self->{host} = $host; + $self->{port} = $port; + + return $self->{socket} ? 1 : 0; +} # connectToServer + +sub disconnectFromServer () { + my ($self) = @_; + + if ($self->{socket}) { + close $self->{socket}; + + undef $self->{socket}; + } # if + + return; +} # disconnectFromServer + +# TODO: This function should not be internal and it should be overridable +sub _serviceClient ($$) { + my ($self, $host, $client) = @_; + + $self->_verbose ("Serving requests from $host"); + + # Set autoflush for client + $client->autoflush + if $client; + + # Input is simple and consists of the following: + # + # = + # = + # += + # ... + # end + # + # Notes: can be . Also a += means append this fieldvalue to + # the existing value for the field. + + # First get record line + my $line = <$client>; + + if ($line) { + chomp $line; chop $line if $line =~ /\r$/; + } else { + $self->_verbose ("Host $host went away!"); + + close $client; + + return; + } # if + + if ($line =~ /stopserver/i) { + if ($self->{server}) { + $self->_verbose ("$host requested to stop server [$self->{server}]"); + + # Send server hangup signal + kill 'HUP', $self->{server}; + } else { + $self->_verbose ('Shutting down server'); + + print $client "CQDService Status: 0\n"; + + exit; + } # if + } # if + + my ($record, $id) = split /=/, $line; + + unless ($id) { + $self->_verbose ('Garbled record line - rejected request'); + + close $client; + + return; + } # unless + + $self->_verbose ("Client wishes to deal with $id"); + + my $scope; + + if ($id =~ /_(\S+)/) { + $scope = $1; + } # if + + $self->_debug ("$host wants $record:$id"); + + my ($read, %fields); + + # Now read name/value pairs + while () { + # Read command from client + $line = <$client>; + + if ($line) { + chomp $line; chop $line if $line =~ /\r$/; + } else { + $self->_verbose ("Host $host went away!"); + + close $client; + + return; + } # if + + last if $line =~ /^end$/i; + + # Collect name/values. Note if only names are requested then we will instead + # return data. + my ($name, $value) = split /=/, $line; + + if ($value) { + # Transform %0A's back to \n + $value =~ s/\%0A/\n/g; + + $self->_verbose ("Will set $name to $value"); + } else { + $read = 1; + $self->_verbose ("Will retrieve $name"); + } # if + + $fields{$name} = $value; + } # while + + # Get record + my $entity; + + $self->_verbose ("Getting $record:$id"); + + eval { $entity = $self->{session}->GetEntity ($record, $id) }; + + unless ($entity) { + print $client "Unable to GetEntity $record:$id\n"; + + close $client; + + return; + } # unless + + if ($read) { + print $client "$_@@" . $entity->GetFieldValue ($_)->GetValue . "\n" + foreach (keys %fields); + print $client "CQD Status: 0\n"; + + close $client; + + return; + } # if + + # Edit record + $self->_verbose ("Editing $id"); + + $entity->EditEntity ('Backend'); + + my $status; + + foreach my $fieldName (keys %fields) { + if ($fieldName =~ /(.+)\*$/) { + my $newValue = delete $fields{$fieldName}; + + $fieldName = $1; + + $fields{$fieldName} = $entity->GetFieldValue ($fieldName)->GetValue + . $newValue; + } # if + + $self->_verbose ("Setting $fieldName to $fields{$fieldName}"); + + $status = $entity->SetFieldValue ($fieldName, $fields{$fieldName}); + + if ($status ne '') { + $self->_verbose ($status); + + print $client "$status\n"; + print $client "CQD Status: 1\n"; + + close $client; + + return; + } # if + } # foreach + + $self->_verbose ("Validating $id"); + + $status = $entity->Validate; + + if ($status eq '') { + $self->_verbose ('Committing'); + $entity->Commit; + + print $client "Successfully updated $id\n"; + print $client "CQD Status: 0\n"; + } else { + $self->_verbose ('Reverting changes'); + $entity->Revert; + print $client "$status\n"; + print $client "CQD Status: 1\n"; + } # if + + close $client; + + $self->_verbose ("Serviced requests from $host"); + + return; +} # _serviceClient + +sub execute (%) { + my ($self, %request) = @_; + + $self->connectToServer or croak 'Unable to connect to CQD Service'; + + return (-1, 'Unable to talk to server') + unless $self->{socket}; + + my ($status, @output) = (-1, ()); + + my $server = $self->{socket}; + + my $id = delete $request{id}; + + print $server "$id\n"; + + my $read; + + foreach (keys %request) { + if ($request{$_}) { + print $server "$_=$request{$_}\n"; + } else { + $read = 1; + print $server "$_\n"; + } # if + } # foreach + + print $server "end\n"; + + my ($response, %output); + + while (defined ($response = <$server>)) { + if ($response =~ /CQD Status: (-*\d+)/) { + $status = $1; + last; + } # if + + if ($read) { + chomp $response; chop $response if $response =~ /\r$/; + + my ($field, $value) = split /\@\@/, $response; + + $output{$field} = $value; + } else { + push @output, $response; + } # if + } # while + + chomp @output unless $read; + + $self->disconnectFromServer; + + if ($status != 0 or $read == 0) { + return ($status, @output); + } else { + return ($status, %output); + } # if +} # execute + +sub startServer (;$$$$$) { + + require 'Clearquest.pm'; + + my ($self, $port, $username, $password, $db, $dbset) = @_; + + $port ||= $OPTS{CQD_PORT}; + $username ||= $OPTS{CQD_USERNAME}; + $password ||= $OPTS{CQD_PASSWORD}; + $db ||= $OPTS{CQD_DATABASE}; + $dbset ||= $OPTS{CQD_DBSET}; + + # Create new socket to communicate to clients with + $self->{socket} = IO::Socket::INET->new( + Proto => 'tcp', + LocalPort => $port, + Listen => SOMAXCONN, + Reuse => 1 + ); + + error "Could not create socket - $!", 1 + unless $self->{socket}; + + # Connect to Clearquest database + $self->{session} = CQSession::Build (); + + verbose "Connecting to $username\@$db"; + + $self->{session}->UserLogon ($username, $password, $db, $dbset); + + # Announce ourselves + $self->_log ("CQD V$VERSION accepting clients at " . localtime); + + # Now wait for an incoming request + LOOP: + my $client; + + while ($client = $self->{socket}->accept) { + my $hostinfo = gethostbyaddr $client->peeraddr; + my $host = $hostinfo ? $hostinfo->name : $client->peerhost; + + $self->_verbose ("$host is requesting service"); + + if ($self->getMultithreaded) { + $self->{server} = $$; + + my $childpid; + + $self->_debug ("Spawning child to handle request"); + + error "Can't fork: $!" + unless defined ($childpid = fork); + + if ($childpid) { + $self->{pid} = $$; + + $SIG{CHLD} = \&_funeral; + $SIG{HUP} = \&_endServer; + $SIG{USR2} = \&_restartServer; + + $self->_debug ("Parent produced child [$childpid]"); + } else { + # In child process - ServiceClient + $self->{pid} = $$; + + $self->_debug ("Calling _serviceClient"); + $self->_serviceClient ($host, $client); + $self->_debug ("Returned from _serviceClient - exiting..."); + + exit; + } # if + } else { + $self->_serviceClient ($host, $client); + } # if + } # while + + # This works but I really don't like it. The parent should have looped back to + # the while statement thus waiting for the next client. But it doesn't seem to + # do that. Instead, when multithreaded, the child exits above and then the + # parent breaks out of the while loop. I'm not sure why this is happening. + # This goto fixes this up but it's sooooo ugly! + goto LOOP; +} # startServer + +1; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + DateUtils + Display + GetConfig + +=end man + +=begin html + +
    +DateUtils
    +Display
    +GetConf
    +
    + +=end html + +=head1 SEE ALSO + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this module. + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2011, ClearSCM, Inc. All rights reserved. + +=cut diff --git a/lib/Clearquest/LDAP.pm b/lib/Clearquest/LDAP.pm new file mode 100644 index 0000000..beab857 --- /dev/null +++ b/lib/Clearquest/LDAP.pm @@ -0,0 +1,168 @@ +#!/usr/bin/perl +################################################################################ +# +# File: $RCSfile: LDAP.pm,v $ +# Revision: $Revision: 1.3 $ +# Description: The Clearquest LDAP Perl Module. +# Author: Andrew@ClearSCM.com +# Created: Fri Sep 22 09:21:18 CDT 2006 +# Modified: $Date: 2011/01/09 01:04:33 $ +# Language: perl +# +# (c) Copyright 2006, ClearSCM, Inc. all rights reserved +# +################################################################################ +use strict; +use warnings; + +package LDAP; + use base "Exporter"; + + use Carp; + use OSDep; + + my @MapFields = ( + "CQ_EMAIL", + "CQ_FULLNAME", + "CQ_LOGIN_NAME", + "CQ_MISC_INFO", + "CQ_PHONE", + ); + + my @ScopeFields = ( + "sub", + "one", + "base", + ); + + my @EXPORT = qw ( + MapFields + ScopeFields + Validate + GetSettings + ); + + sub MapFields { + return @MapFields; + } # MAPFields + + sub ScopeFields { + return @ScopeFields; + } # ScopeFields + + sub Validate { + my ( + $server, + $port, + $base, + $search_filter, + $account_attribute, + $search_for, + ) = @_; + + eval { require Net::LDAP }; + + if ($@) { + return $FALSE, "Unable to load Net::LDAP. LDAP validation not possible."; + } # if + + my $ldap = Net::LDAP->new ($server, + timeout => 2, + port => $port + ); + + return $FALSE, "Unable to connect to $server:$port" if !$ldap; + + if (!$ldap->bind (version => 3)) { + return $FALSE, "Unable to bind to $server:$port"; + } # if + + my @attribute = ($account_attribute); + my $key = $search_filter; + $key =~ s/\%login\%/$search_for/; + + my $result = $ldap->search (base => $base, + scope => "sub", + filter => $key, + attrs => @attribute, + ); + + $ldap->unbind; + + my $entry = $result->entry; + + if ($entry) { + my $value = $entry->get_value ($account_attribute); + return $TRUE, "Matched $key to LDAP"; + } else { + return $FALSE, "Unable to find entry ($key)"; + } # if + } # Validate + + sub GetSettings { + my $dbset = shift; + my $admin_username = shift; + my $admin_passwords = shift; + + my %LDAPSettings; + + my $cmd = "installutil getldapinit $dbset $admin_username $admin_passwords"; + + my @output = `$cmd`; + + carp "Unable to execute $cmd" if $?; + + foreach (@output) { + chomp; chop if /\r/; + + next if /^\*|^$/; + + if (/Exit code (\d*)/) { + $? = $1; + next; + } # if + + $LDAPSettings {ldapinit} .= "$_\n"; + } # foreach + + $cmd = "installutil getldapsearch $dbset $admin_username $admin_passwords"; + + @output = `$cmd`; + + croak "Unable to execute $cmd" if $?; + + foreach (@output) { + chomp; chop if /\r/; + + next if /^\*|^$/; + + if (/Exit code (\d*)/) { + $? = $1; + next; + } # if + + $LDAPSettings {ldapsearch} .= "$_\n"; + } # foreach + + $cmd = "installutil getcqldapmap $dbset $admin_username $admin_passwords"; + + @output = `$cmd`; + + croak "Unable to execute $cmd" if $?; + + foreach (@output) { + chomp; chop if /\r/; + + next if /^\*|^$/; + + if (/Exit code (\d*)/) { + $? = $1; + next; + } # if + + $LDAPSettings {cqldapmap} .= "$_\n"; + } # foreach + + return %LDAPSettings; + } # GetSettings +1; diff --git a/lib/Clearquest/REST.pm b/lib/Clearquest/REST.pm new file mode 100644 index 0000000..46d11a5 --- /dev/null +++ b/lib/Clearquest/REST.pm @@ -0,0 +1,2172 @@ +=pod + +=head1 NAME $RCSfile: REST.pm,v $ + +Clearquest REST client - Provide access to Clearquest via the REST interface + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 2.16 $ + +=item Created + +Wed May 30 11:43:41 PDT 2011 + +=item Modified + +$Date: 2013/03/26 02:24:01 $ + +=back + +=head1 SYNOPSIS + +Provides a RESTful interface to Clearquest + +=head1 DESCRIPTION + +This module implements a simple interface to Clearquest. The backend uses REST +however this module hides all of the ugly details of the REST implementation. +Since REST is used, however, this module can be used by any normal Perl. See +Perl Modules below of a list of Perl modules required. + +This module is object oriented so you need to instantiate an object. Be careful +to make sure that you properly disconect from this object (See disconnect +method). + +The methods exported are simple: add, delete, get, modify... In most cases you +simply need to supply the table name and a hash of name value pairs to perform +actions. Record hashes representing name/value parts for the fields in the +records are returned to you. + +Here's an example of use: + + use Clearquest; + + my $cq; + + END { + $cq->disconnect if $cq; + } # END + + $cq = Clearquest->new (CQ_MODULE => 'rest'); + + $cq->connect; + + my %record = $cq->get ('Project', 'Athena'); + + my %update = ( + Deprecated => 1, + Projects => 'Island', '21331', 'Hera' ], + ); + + $cq->modify ('VersionInfo', '1.0', 'Modify', \%update); + + if ($cq->error) { + die "Unable to modify record\n" . $cq->errmsg; + } + +=head2 NOTES + +Multiline text strings are limited to only 2000 characters by default. In order +to expand this you need to change the cqrest.properties file in: + +C:\Program Files (x86)\IBM\RationalSDLC\common\CM\profiles\cmprofile\installedApps\dfltCell\TeamEAR.ear\cqweb.war\WEB-INF\classes + +on the web server. Multiline text strings can theoretically grow to 2 gig, +however when set even as small as 10 meg REST messes up! + +=head1 METHODS + +The following methods are available: + +=cut + +package Clearquest::REST; + +use strict; +use warnings; + +use File::Basename; +use Carp; + +use CGI qw (escapeHTML); +use Encode; +use LWP::UserAgent; +use HTTP::Cookies; +use MIME::Base64; +use REST::Client; +use XML::Simple; + +use Clearquest; +use GetConfig; +use Utils; + +use parent 'Clearquest'; + +our $VERSION = '$Revision: 2.16 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +=pod + +=head1 Options + +Options are keep in the cq.conf file in the etc directory. They specify the +default options listed below. Or you can export the option name to the env(1) to +override the defaults in cq.conf. Finally you can programmatically set the +options when you call new by passing in a %parms hash. The items below are the +key values for the hash. + +=for html
    + +=over + +=item CQ_SERVER + +The web host to contact with leading http:// + +=item CQ_USERNAME + +User name to connect as (Default: From cq.conf) + +=item CQ_PASSWORD + +Password for CQ_USERNAME + +=item CQ_DATABASE + +Name of database to connect to (Default: From cq.conf) + +=item CQ_DBSET + +Database Set name (Default: From cq.conf) + +=back + +=cut + +our (%RECORDS, %FIELDS); + +# FieldTypes ENUM +my $UNKNOWN = -1; +my $STRING = 0; +my $MULTILINE_STRING = 1; +my $REFERENCE = 2; +my $REFERENCE_LIST = 3; +my $JOURNAL = 4; +my $ATTACHMENT_LIST = 5; +my $INT = 6; +my $DATE_TIME = 7; +my $DBID = 8; +my $RECORD_TYPE = 9; + +sub _callREST ($$$;%) { + my ($self, $type, $url, $body, %parms) = @_; + + # Set error and errmsg to no error + $self->error (0); + $self->{errmsg} = ''; + + # Upshift the call type as the calls are actually like 'GET' and not 'get' + $type = uc $type; + + # We only support these call types + croak "Unknown call type \"$type\"" + unless $type eq 'GET' or + $type eq 'POST' or + $type eq 'PATCH' or + $type eq 'OPTIONS' or + $type eq 'PUT' or + $type eq 'DELETE' or + $type eq 'HEAD'; + + # If the caller did not give us authorization then use the login member we + # already have in the object + unless ($parms{Authorization}) { + $parms{$_} = $self->{login}{$_} foreach (keys %{$self->{login}}); + } # unless + + # We need to use OSLC 2.0 for the conditional "is not null". So if we see a + # "oslc.where" in the URL then add OSLC-Core-Version => '2.0' to %parms. + if ($url =~ /oslc.where/) { + $parms{'OSLC-Core-Version'} = '2.0'; + } # if + + # Remove the host portion if any + $url =~ s/^http.*$self->{server}//; + + # Call the REST call (Different calls have different numbers of parameters) + if ($type eq 'GET' or + $type eq 'DELETE' or + $type eq 'OPTIONS' or + $type eq 'HEAD') { + $self->{rest}->$type ($url, \%parms); + } else { + $self->{rest}->$type ($url, $body, \%parms); + } # if + + return $self->error; +} # _callREST + +sub _getRecordName ($) { + my ($self, $query) = @_; + + $self->_callREST ('get', $query); + + if ($self->error) { + $self->errmsg ("Unable to get record name for $query"); + + return; + } # if + + my %record = %{XMLin ($self->{rest}->responseContent)}; + + return $record{element}{name}; +} # _getRecordName + +sub _getAttachmentList ($$) { + my ($self, $result, $fields) = @_; + + croak ((caller(0))[3] . ' is not implemented'); + + return; +} # _getAttachmentList + +sub _getInternalID ($$) { + my ($self, $table, $key) = @_; + + my $query = "/cqweb/oslc/repo/$self->{dbset}/db/$self->{database}/record/?rcm.type=$table&"; + + $query .= "rcm.name=$key"; + + $self->_callREST ('get', $query); + + unless ($self->error) { + my %result = %{XMLin ($self->{rest}->responseContent)}; + + return $result{entry}{id}; + } else { + $self->errmsg ("Record not found (Table: $table, Key: \"$key\")"); + + return $self->errmsg; + } # unless +} # _getInternalID + +sub _getRecord ($$@) { + my ($self, $table, $url, @fields) = @_; + + $self->{fields} = [$self->_setFields ($table, @fields)]; + + $self->_callREST ('get', $url); + + return if $self->error; + + # Now parse the results + my %result = %{XMLin ($self->{rest}->responseContent)}; + + if ($result{entry}{content}{$table}) { + return $self->_parseFields ($table, %{$result{entry}{content}{$table}}); + } elsif (ref \%result eq 'HASH') { + # The if test above will create an empty $result{entry}{content}. We need + # to delete that + delete $result{entry}; + + return $self->_parseFields ($table, %result); + } else { + return; + } # if +} # _getRecord + +sub _getRecordID ($) { + my ($self, $table) = @_; + + $self->records; + + return $RECORDS{$table}; +} # _getRecordID + +sub _getRecordURL ($$;@) { + my ($self, $table, $url, @fields) = @_; + + $self->{fields} = [$self->_setFields ($table, @fields)]; + + $self->error ($self->_callREST ('get', $url)); + + return if $self->error; + + return $self->_parseFields ($table, %{XMLin ($self->{rest}->responseContent)}); +} # _getRecordURL + +sub _getReferenceList ($$) { + my ($self, $url, $field) = @_; + + $self->error ($self->_callREST ('get', $url)); + + return if $self->error; + + my %result = %{XMLin ($self->{rest}->responseContent)}; + + my @values; + + # Need to find the field array here... + foreach my $key (keys %result) { + if (ref $result{$key} eq 'ARRAY') { + foreach (@{$result{$key}}) { + push @values, $$_{'oslc_cm:label'}; + } # foreach + + last; + } elsif (ref $result{$key} eq 'HASH' and $result{$key}{'oslc_cm:label'}) { + push @values, $result{$key}{'oslc_cm:label'}; + } # if + } # foreach + + return @values; +} # _getReferenceList + +sub _parseCondition ($$) { + my ($self, $table, $condition) = @_; + + # Parse simple conditions only + my ($field, $operator, $value); + + if ($condition =~ /(\w+)\s*(==|=|!=|<>|<=|>=|<|>|in|is\s+null|is\s+not\s+null)\s*(.*)/i) { + $field = $1; + $operator = $2; + $value = $3; + + if ($operator eq '==') { + $operator = '='; + } elsif ($operator eq '<>') { + $operator = '!='; + } elsif ($operator =~ /is\s+null/i) { + return "$field in [\"\"]"; + } elsif ($operator =~ /is\s+not\s+null/i) { + return "$field in [*]"; + } elsif ($operator =~ /in/i) { + return "$field in [$value]" + } # if + } # if + + if ($operator eq '=' and $value =~ /^null$/i) { + return "$field in [\"\"]"; + } elsif ($operator eq '!=' and $value =~ /^null$/i) { + return "$field in [*]"; + } # if + + # Trim quotes if any: + if ($value =~ /^\s*\'/) { + $value =~ s/^\s*\'//; + $value =~ s/\'\s*$//; + } elsif ($value =~ /^\s*\"/) { + $value =~ s/^\s*\"//; + $value =~ s/\"\s*$//; + } # if + + # Trim leading and trailing whitespace + $value =~ s/^\s+//; + $value =~ s/\s+$//; + + # Convert datetimes to Zulu + if ($self->fieldType ($table, $field) == $DATE_TIME and + $value !~ /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z/) { + $value = Clearquest::_UTCTime ($value); + } # if + + return "$field $operator \"$value\""; +} # _parseCondition + +sub _parseConditional ($$) { + my ($self, $table, $condition) = @_; + + return 'oslc_cm.query=' unless $condition; + + my $parsedConditional; + + # Special case when the condition is ultra simple + if ($condition !~ /(\w+)\s*(==|=|!=|<>|<|>|<=|>=|in|is\s+null|is\s+not\s+null)\s*(.*)/i) { + return "rcm.name=$condition"; + } # if + + # TODO: This section needs improvement to handle more complex conditionals + while () { + if ($condition =~ /(.+?)\s+(and|or)\s+(.+)/i) { + my $leftSide = $self->_parseCondition ($table, $1); + + $parsedConditional .= "$leftSide $2 "; + $condition = $3; + } else { + $parsedConditional .= $self->_parseCondition ($table, $condition); + + last; + } # if + } # while + + # TODO: How would this work if we have a condition like 'f1 = "value" and + # f2 is not null'? + if ($parsedConditional =~ /in \[\*\]/) { + return "oslc.where=$parsedConditional"; + } else { + return "oslc_cm.query=$parsedConditional"; + } # if +} # _parseConditional + +sub _parseFields ($%) { + my ($self, $table, %record) = @_; + + foreach my $field (keys %record) { + if ($field =~ /:/ or + $field eq 'xmlns' or + grep {/^$field$/} @{$self->{fields}} == 0) { + delete $record{$field}; + + next; + } # if + + my $fieldType = $self->fieldType ($table, $field); + + if (ref $record{$field} eq 'HASH') { + if ($fieldType == $REFERENCE) { + $record{$field} = $record{$field}{'oslc_cm:label'}; + } elsif ($fieldType == $REFERENCE_LIST) { + my @values = $self->_getReferenceList ($record{$field}{'oslc_cm:collref'}, $field); + + $record{$field} = \@values; + } elsif ($fieldType == $ATTACHMENT_LIST) { + my @attachments = $self->_getAttachmentList ($record{$field}{'oslc_cm:collref'}, $field); + + $record{$field} = \@attachments; + } elsif ($fieldType == $RECORD_TYPE) { + $record{$field} = $record{$field}{'oslc_cm:label'}; + } elsif (!%{$record{$field}}) { + $record{$field} = undef; + } # if + } # if + + $record{$field} ||= '' if $self->{emptyStringForUndef}; + + if ($fieldType == $DATE_TIME) { + $record{$field} = Clearquest::_UTC2Localtime $record{$field}; + } # if + } # foreach + + return %record; +} # _parseFields + +sub _parseRecordDesc ($) { + my ($self, $table) = @_; + + # Need to get fieldType info + my $recordID = $self->_getRecordID ($table); + + return unless $recordID; + + my $url = "$self->{uri}/record-type/$recordID"; + + $self->_callREST ('get', $url); + + return if $self->error; + + my %result = %{XMLin ($self->{rest}->responseContent)}; + + # Reach in deep for field definitions + my %fields = %{$result{element}{complexType}{choice}{element}}; + + foreach (keys %fields) { + if ($fields{$_}{type} and $fields{$_}{type} eq 'cqf:reference') { + $FIELDS{$table}{$_}{FieldType} = $REFERENCE; + $FIELDS{$table}{$_}{References} = $self->_getRecordName ($fields{$_}{'cq:refURI'}); + } elsif ($fields{$_}{type} and $fields{$_}{type} eq 'cqf:multilineString') { + $FIELDS{$table}{$_}{FieldType} = $MULTILINE_STRING; + } elsif ($fields{$_}{simpleType}) { + if ($fields{$_}{simpleType}{restriction}{base}) { + if ($fields{$_}{simpleType}{restriction}{base} eq 'string') { + $FIELDS{$table}{$_}{FieldType} = $STRING; + } elsif ($fields{$_}{simpleType}{union}{simpleType}[0]{restriction}{base} eq 'string') { + $FIELDS{$table}{$_}{FieldType} = $STRING; + } else { + $FIELDS{$table}{$_}{FieldType} = $UNKNOWN; + } # if + } elsif ($fields{$_}{simpleType}{union}{simpleType}[0]{restriction}{base} eq 'string') { + $FIELDS{$table}{$_}{FieldType} = $STRING; + } elsif ($fields{$_}{simpleType}{union}{simpleType}[0]{restriction}{base} eq 'cqf:integer') { + $FIELDS{$table}{$_}{FieldType} = $INT; + } else { + $FIELDS{$table}{$_} = $UNKNOWN; + } # if + } elsif ($fields{$_}{complexType} and $fields{$_}{'cq:refURI'}) { + $FIELDS{$table}{$_}{FieldType} = $REFERENCE_LIST; + $FIELDS{$table}{$_}{References} = $self->_getRecordName ($fields{$_}{'cq:refURI'}); + } elsif ($fields{$_}{complexType} and + $fields{Symptoms}{complexType}{sequence}{element}{simpleType}{union}{simpleType}[1]{restriction}{base} eq 'string') { + $FIELDS{$table}{$_}{FieldType} = $MULTILINE_STRING; + } elsif ($fields{$_}{type} and $fields{$_}{type} eq 'cqf:journal') { + $FIELDS{$table}{$_}{FieldType} = $JOURNAL; + } elsif ($fields{$_}{type} and $fields{$_}{type} eq 'cqf:attachmentList') { + $FIELDS{$table}{$_}{FieldType} = $ATTACHMENT_LIST; + } elsif ($fields{$_}{type} and $fields{$_}{type} eq 'cqf:integer') { + $FIELDS{$table}{$_}{FieldType} = $INT; + } elsif ($fields{$_}{type} and $fields{$_}{type} eq 'cqf:dateTime') { + $FIELDS{$table}{$_}{FieldType} = $DATE_TIME; + } elsif ($fields{$_}{type} and $fields{$_}{type} eq 'cqf:recordType') { + $FIELDS{$table}{$_}{FieldType} = $RECORD_TYPE; + } else { + $FIELDS{$table}{$_}{FieldType} = $UNKNOWN; + } # if + + if ($fields{$_}{'cq:systemOwned'} and $fields{$_}{'cq:systemOwned'} eq 'true') { + $FIELDS{$table}{$_}{SystemField} = 1; + } else { + $FIELDS{$table}{$_}{SystemField} = 0; + } # if + } # foreach + + return; +} # _parseRecordDesc + +sub _isSystemField ($$) { + my ($self, $table, $fieldName) = @_; + + if ($FIELDS{$table}) { + # If we already have this fieldType just return it + if (defined $FIELDS{$table}{$fieldName}) { + return $FIELDS{$table}{$fieldName}{SystemField}; + } else { + return 0; + } # if + } # if + + $self->_parseRecordDesc ($table); + + if (defined $FIELDS{$table}{$fieldName}) { + return $FIELDS{$table}{$fieldName}{SystemField}; + } else { + return 0; + } # if +} # _isSystemField + +sub _setFields ($@) { + my ($self, $table, @fields) = @_; + + # Cause %FIELDS to be expanded for $table + $self->_parseRecordDesc ($table); + + unless (@fields) { + foreach ($self->fields ($table)) { + unless ($self->{returnSystemFields}) { + next if $FIELDS{$table}{$_}{SystemField} + } # unless + + push @fields, $_; + } # foreach + } # unless + + push @fields, 'dbid' unless grep { /dbid/ } @fields; + + return @fields; +} # _setFields + +sub _setFieldValue ($$$) { + my ($self, $table, $fieldName, $fieldValue) = @_; + + return if $self->_isSystemField ($table, $fieldName); + + my $xml .= "<$fieldName>"; + + my $fieldType = $self->fieldType ($table, $fieldName); + + if ($fieldType == $STRING or + $fieldType == $MULTILINE_STRING or + $fieldType == $INT or + $fieldType == $DATE_TIME) { + # Fix MULTILINE_STRINGs + if ($fieldType == $MULTILINE_STRING and ref $fieldValue eq 'ARRAY') { + chomp @{$fieldName}; + + $fieldValue= join "\n", @$fieldValue; + } # if + + $xml .= escapeHTML $fieldValue; + } elsif ($fieldType == $REFERENCE) { + my $tableReferenced = $self->fieldReference ($table, $fieldName); + + if ($tableReferenced) { + $xml .= $self->_getInternalID ($tableReferenced, $fieldValue); + } else { + $self->error (600); + $self->errmsg ("Could not determine reference for $fieldName"); + + return; + } # if + } elsif ($fieldType == $REFERENCE_LIST) { + # We'll allow either an array reference or a single value, which we will + # turn into an array + my @values; + + @values = ref $fieldValue eq 'ARRAY' ? @$fieldValue + : ($fieldValue); + + my $tableReferenced = $self->fieldReference ($table, $fieldName); + + unless ($tableReferenced) { + $self->error (600); + $self->errmsg ("Could not determine reference for $fieldName"); + + return; + } # if + + foreach (@values) { + my $internalID = $self->_getInternalID ($tableReferenced, $_); + + if ($internalID) { + $xml .= "\n"; + } else { + $self->error (600); + $self->errmsg ("Could not find a valid/active $tableReferenced with a key of \"$_\""); + + return + } # if + } # foreach + } else { + croak "Unable to handle field $fieldName fieldType: " . $self->fieldTypeName ($table, $fieldName); + } # if + + $xml .= "\n"; + + return $xml; +} # _setFieldValue + +sub _startXML ($) { + my ($table) = @_; + + my $xml = << "XML"; + +<$table + xmlns="http://www.ibm.com/xmlns/prod/rational/clearquest/1.0/" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:dc="http://purl.org/dc/terms/" + xmlns:oslc_cm="http://open-services.net/xmlns/cm/1.0/"> +XML + + return $xml +} # _startXML + +sub add ($$;@) { + my ($self, $table, $record, @ordering) = @_; + +=pod + +=head2 add ($table, %record) + +Adds a %record to $table. + +Parameters: + +=for html
    + +=over + +=item $table + +Table to add a record to (e.g. 'Defect') + +=item $values + +Hash reference of name/value pairs for the insertion + +=item @ordering + +Array containing field names that need to be processed in order. Not all fields +mentioned in the $values hash need be mentioned here. If you have fields that +must be set in a particular order you can mention them here. So, if you're +adding the Defect record, but you need Project set before Platform, you need +only pass in an @ordering of qw(Project Platform). They will be done first, then +all of the rest of the fields in the $values hash. If you have no ordering +dependencies then you can simply omit @ordering. + +Note that the best way to determine if you have an ordering dependency try using +a Clearquest client and note the order that you set fields in. If at anytime +setting one field negates another field via action hook code then you have just +figured out that this field needs to be set before the file that just got +negated. + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $errmsg + +Error message (if any) + +=back + +=for html
    + +=cut + + my %record = %$record; + my $xml = _startXML $table; + my $uri = $self->{uri} . '/record'; + + # First process all fields in the @ordering, if specified + $xml .= $self->_setFieldValue ($table, $_, $record{$_}) foreach (@ordering); + + foreach my $field (keys %record) { + next if InArray $field, @ordering; + + $xml .= $self->_setFieldValue ($table, $field, $record{$field}); + } # foreach + + $xml .= ""; + + $self->_callREST ('post', $uri, $xml); + + # Get the DBID of the newly created record + if ($self->{rest}{_res}{_headers}{location} =~ /-(\d+)$/) { + return $1; + } else { + return; + } # if +} # add + +sub connect (;$$$$) { + my ($self, $username, $password, $database, $dbset) = @_; + +=pod + +=head2 connect (;$$$$) + +This method doesn't really connect but is included to be similar to the +Clearquest::connect method. It does set any of the username, password, +database and/or dbset members + +Parameters: + +=for html
    + +=over + +=item $username + +Username to use to connect to the database + +=item $password + +Password to use to connect to the database + +=item $database + +Clearquest database to connect to + +=item $dbset + +Database set to connect to (Default: Connect to the default dbset) + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item 1 + +=back + +=for html
    + +=cut + + if (ref $username eq 'HASH') { + my %opts = %$username; + + $self->{username} = delete $opts{CQ_USERNAME}; + $self->{password} = delete $opts{CQ_PASSWORD}; + $self->{database} = delete $opts{CQ_DATABASE}; + $self->{dbset} = delete $opts{CQ_DBSET}; + } else { + $self->{username} = $username if $username; + $self->{password} = $password if $password; + $self->{database} = $database if $database; + $self->{dbset} = $dbset if $dbset; + } # if + + # Set URI in case anything changed + $self->{uri} = "/cqweb/oslc/repo/$self->{dbset}/db/$self->{database}"; + $self->{loggedin} = 1; + + return 1; +} # connect + +sub connected () { + my ($self) = @_; + +=pod + +=head2 connected () + +Returns 1 if we are currently connected to Clearquest + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item 1 if logged in - 0 if not + +=back + +=for html
    + +=cut + + return $self->{loggedin}; +} # connected + +sub database () { + my ($self) = @_; + +=pod + +=head2 database + +Returns the current database (or the database that would be used) + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item database + +=back + +=for html
    + +=cut + + return $self->{database}; +} # database + +sub dbset () { + my ($self) = @_; + +=pod + +=head2 dbset + +Returns the current dbset (or the dbset that would be used) + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item dbset + +=back + +=for html
    + +=cut + + return $self->{dbset}; +} # dbset + +sub dbsets () { + croak ((caller(0))[3] . ' is not implemented'); +} # dbsets + +sub delete ($$) { + my ($self, $table, $key) = @_; + +=pod + +=head2 delete ($table, $key) + +Deletes a %record from $table. + +Parameters: + +=for html
    + +=over + +=item $table + +Table from which to delete a record from (e.g. 'Defect') + +=item $key + +Key of the record to delete + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $errmsg + +Error message (if any) + +=back + +=for html
    + +=cut + + my $query = $self->_getInternalID ($table, $key); + + # Need to remove $self->{server} from beginning of $query + $query =~ s/^http.*$self->{server}//; + + $self->_callREST ('delete', $query); + + return $self->errmsg; +} # delete + +sub DESTROY () { + my ($self) = @_; + + # Attempt to delete session if we still have a rest object. Note that during + # global destruction (like when you die or exit), the ordering of destruction + # is unpredictable so we might not succeed. + return unless $self->{rest}; + + # Delete session - ignore error as there's really nothing we can do if this + # fails. + $self->_callREST ('delete', '/cqweb/oslc/session/'); + + croak "Unable to release REST session in destructor" if $self->error; + + return; +} # DESTROY + +sub disconnect () { + my ($self) = @_; + +=pod + +=head2 disconnect () + +Disconnects from REST. Note you should take care to call disconnect or use undef +to undefine your instantiated Clearquest::REST object. If your script dies or +exits without disconnecting you may cause web sessions to remain. You might try +something like: + + use Clearquest::REST; + + my $cq = Clearquest::REST->new; + + END { + $cq->disconnect if $cq; + } # END + +Parameters: + +=for html
    + +=over + +=item nothing + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $error + +Error number (if any) + +=back + +=for html
    + +=cut + + return unless $self->{rest}; + + $self->_callREST ('delete', '/cqweb/oslc/session/'); + + return $self->error; +} # disconnect + +sub errmsg (;$) { + my ($self, $errmsg) = @_; + +=pod + +=head2 errmsg ($errmsg) + +Returns the last error message. Optionally sets the error message if specified. + +Parameters: + +=for html
    + +=over + +=item $errmsg + +Error message to set + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $errmsg + +Last error message + +=back + +=for html
    + +=cut + + if ($errmsg) { + $self->{errmsg} = $errmsg; + } else { + # User defined errors are in the 600 series. If we have a user defined + # error and the caller did not supply us an errmsg to set then they want + # the user defined error we set so just return that. + if ($self->{responseCode} >= 600) { + return $self->{errmsg}; + } else { + my $response = $self->response; + + if ($response and $response ne '') { + my %xml = %{XMLin ($self->response)}; + + if ($xml{Error}{message}) { + $self->{errmsg} = $xml{Error}{message}; + } elsif (ref $xml{message} ne 'HASH' and $xml{message}) { + $self->{errmsg} = $xml{message}; + } else { + $self->{errmsg} = 'Unknown error'; + } # if + } else { + $self->{errmsg} = ''; + } # if + } # if + } # if + + return $self->{errmsg}; +} # errmsg + +sub error (;$) { + my ($self, $error) = @_; + +=pod + +=head2 error ($error) + +Returns the last error number. Optional set the error number if specified + +Parameters: + +=for html
    + +=over + +=item $error + +Error number to set + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $error + +Last error + +=back + +=for html
    + +=cut + + + if (defined $error) { + $self->{responseCode} = $error; + } else { + # If the user has not yet called any underlying REST functionality yet (for + # example, they could have called the find method but have not asked for the + # $nbrRecs) then we cannot call $self->{rest}->responseCode because the + # REST::Client object has not been instantiated yet. So we'll return no + # error. + if ($self->{rest}{_res}) { + $self->{responseCode} = $self->{rest}->responseCode; + } else { + $self->{responseCode} = 0; + } # if + } # if + + return 0 if $self->{responseCode} >= 200 and $self->{responseCode} < 300; + return $self->{responseCode}; +} # error + +sub fields ($) { + my ($self, $table) = @_; + +=pod + +=head2 fields ($table) + +Returns an array of the fields in a table + +Parameters: + +=for html
    + +=over + +=item $table + +Table to return field info from. + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item @fields + +Array of the fields names for $table + +=back + +=for html
    + +=cut + + my $recordID = $self->_getRecordID ($table); + + return unless $recordID; + + my $url = "$self->{uri}/record-type/$recordID"; + + $self->_callREST ('get', $url); + + return if $self->error; + + my %result = %{XMLin ($self->{rest}->responseContent)}; + + my @fields = keys %{$result{element}{complexType}{choice}{element}}; + + return @fields; +} # fields + +sub fieldType ($$) { + my ($self, $table, $fieldName) = @_; + +=pod + +=head2 fieldType ($table, $fieldname) + +Returns the field type for the $table, $fieldname combination. + +Parameters: + +=for html
    + +=over + +=item $table + +Table to return field type from. + +=item $fieldname + +Fieldname to return the field type from. + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $fieldType + +Fieldtype enum + +=back + +=for html
    + +=cut + + # If we've already computed the fieldTypes for the fields in this table then + # return the value + if ($FIELDS{$table}) { + # If we already have this fieldType just return it + if (defined $FIELDS{$table}{$fieldName}) { + return $FIELDS{$table}{$fieldName}{FieldType}; + } else { + return $UNKNOWN + } # if + } # if + + $self->_parseRecordDesc ($table); + + if (defined $FIELDS{$table}{$fieldName}) { + return $FIELDS{$table}{$fieldName}{FieldType}; + } else { + return $UNKNOWN + } # if +} # fieldType + +sub fieldReference ($$) { + my ($self, $table, $fieldName) = @_; + +=pod + +=head2 fieldReference ($table, $fieldname) + +Returns the name of the table this reference or reference list field references +or undef if this is not a reference or reference list field. + +Parameters: + +=for html
    + +=over + +=item $table + +Table to return field reference from. + +=item $fieldname + +Fieldname to return the field type from. + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $fieldType + +Name of table this reference or reference list field references or undef if +this is not a reference or reference list field. + +=back + +=for html
    + +=cut + + # If we've already computed the fieldTypes for the fields in this table then + # return the value + return $FIELDS{$table}{$fieldName}{References} if $FIELDS{$table}; + + $self->_parseRecordDesc ($table); + + return $FIELDS{$table}{$fieldName}{References}; +} # fieldReference + +sub find ($;$@) { + my ($self, $table, $condition, @fields) = @_; + +=pod + +=head2 find ($;$@) + +Find records in $table. You can specify a $condition and which fields you wish +to retrieve. Specifying a smaller set of fields means less data transfered and +quicker retrieval so only retrieve the fields you really need. + +Parameters: + +=for html
    + +=over + +=item $table + +Name of the table to search + +=item $condition + +Condition to use. If you want all records then pass in undef. Only simple +conditions are supported. You can specify compound conditions (e.g. field1 == +'foo' and field1 == 'bar' or field2 is not null). No parenthesizing is +supported (yet). + +=item @fields + +An array of fieldnames to retrieve + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $result or ($result, $nbrRecs) + +Internal structure to be used with getNext. If in an array context then $nbrRecs +is also returned. + +=back + +=for html
    + +=cut + + $self->{url} = "$self->{uri}/record/?rcm.type=$table&" + . $self->_parseConditional ($table, $condition); + + @fields = $self->_setFields ($table, @fields); + + # Remove dbid for find + @fields = grep { $_ ne 'dbid' } @fields; + + if (@fields) { + $self->{url} .= "&oslc_cm.properties="; + $self->{url} .= join ',', @fields; + } # if + + # Save some fields for getNext + $self->{fields} = \@fields; + $self->{table} = $table; + + $self->{url} .= "&oslc_cm.pageSize=1"; + + return $self->{url} unless wantarray; + + # If the user wants an array then he wants ($reesult, $nbrRecs) and so we need + # to go out and get that info. + $self->_callREST ('get', $self->{url}); + + return (undef, 0) if $self->error; + + # Now parse the results + my %result = %{XMLin ($self->{rest}->responseContent)}; + + return ($self->{url}, $result{'oslc_cm:totalCount'}{content}); +} # find + +sub get ($$;@) { + my ($self, $table, $key, @fields) = @_; + +=pod + +=head2 get ($table, $key, @fields) + +Retrieve records from $table matching $key. Note $key can be a condition (e.g. +Project = 'Athena'). Return back @fields. If @fields is not specified then all +fields are returned. + +Warning: Some Clearquest records are large. It's always better and faster to +return only the fields that you need. + +Parameters: + +=for html
    + +=over + +=item $table + +Table to get records from (e.g. 'Defect') + +=item $key + +Key to use to get the record. Key is the field that is designated to be the key +for the record. + +=item @fields + +An array of field names to return. It's usually better to specify only those +fields that you need. + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item %record + +An hash representing the qualifying record. + +=back + +=for html
    + +=cut + + my $url = "$self->{uri}/record/?rcm.type=$table&rcm.name=$key"; + + if (@fields) { + $url .= "&oslc_cm.properties="; + $url .= 'dbid,' unless grep { /dbid/i } @fields; + $url .= join ',', @fields; + } # if + + return $self->_getRecord ($table, $url, @fields); +} # get + +sub getDBID ($$;@) { + my ($self, $table, $dbid, @fields) = @_; + +=pod + +=head2 get ($table, $key, @fields) + +Retrieve records from $table matching $key. Note $key can be a condition (e.g. +Project = 'Athena'). Return back @fields. If @fields is not specified then all +fields are returned. + +Warning: Some Clearquest records are large. It's always better and faster to +return only the fields that you need. + +Parameters: + +=for html
    + +=over + +=item $table + +Table to get records from (e.g. 'Defect') + +=item $key + +Key to use to get the record. Key is the field that is designated to be the key +for the record. + +=item @fields + +An array of field names to return. It's usually better to specify only those +fields that you need. + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item %record + +An hash representing the qualifying record. + +=back + +=for html
    + +=cut + + my $url = "$self->{uri}/record/"; + $url .= $self->_getRecordID ($table); + $url .= '-'; + $url .= $dbid; + + if (@fields) { + $url .= "?oslc_cm.properties="; + $url .= 'dbid,' unless grep { /dbid/i } @fields; + $url .= join ',', @fields; + } # if + + return $self->_getRecord ($table, $url); +} # getDBID + +sub getDynamicList () { + croak ((caller(0))[3] . ' is not implemented'); +} # getDynamicList + +sub getNext ($) { + my ($self, $result) = @_; + +=pod + +=head2 getNext ($) + +Return the next record that qualifies from a preceeding call to the find method. + +Parameters: + +=for html
    + +=over + +=item $result + +The $result returned from find. + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item %record + +Hash of name/value pairs for the @fields specified to find. + +=back + +=for html
    + +=cut + + return unless $self->{url}; + + my $url = $self->{url}; + + $self->_callREST ('get', $url); + + return if $self->error; + + # Now parse the results + my %result = %{XMLin ($self->{rest}->responseContent)}; + + # Get the next link + undef $self->{url}; + + if (ref $result{link} eq 'ARRAY') { + foreach (@{$result{link}}) { + if ($$_{rel} eq 'next') { + ($self->{url}) = ($$_{href} =~ /^http.*$self->{server}(.*)/); + + last; + } # if + } # foreach + } # if + + my %record; + + if (ref $result{entry}{content}{$self->{table}} eq 'HASH') { + %record = $self->_parseFields ($self->{table}, %{$result{entry}{content}{$self->{table}}}); + } elsif (ref $result{entry} eq 'HASH') { + if ($result{entry}{id}) { + %record = $self->_getRecordURL ($self->{table}, $result{entry}{id}, @{$self->{fields}}); + } # if + } # if + + # Get dbid + if ($result{entry}{link}{href} =~ /-(\d+)$/) { + $record{dbid} = $1; + } # if + + return %record; +} # getNext + +sub key ($$) { + my ($self, $table, $dbid) = @_; + +=pod + +=head2 key ($$) + +Return the key of the record given a $dbid + +NOTE: Not supported in REST implementation. + +Parameters: + +=for html
    + +=over + +=item $table + +Name of the table to lookup + +=item $dbid + +Database ID of the record to retrieve + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item key + +=back + +=for html
    + +=cut + + croak "The method key is not support in the REST interface"; +} # key + +sub modify ($$$$;@) { + my ($self, $table, $key, $action, $values, @ordering) = @_; + +=pod + +=head2 modify ($table, $key, $action, $values, @ordering) + +Updates records from $table matching $key. + +Parameters: + +=for html
    + +=over + +=item $table + +Table to modify records (e.g. 'Defect') + +=item $key + +The $key of the record to modify. + +=item $action + +Action to use for modification (Default: Modify). You can use this to change +state for stateful records. + +=item $values + +Hash reference containing name/value that have the new values for the fields + +=item @ordering + +Array containing field names that need to be processed in order. Not all fields +mentioned in the $values hash need be mentioned here. If you have fields that +must be set in a particular order you can mention them here. So, if you're +modifying the Defect record, but you need Project set before Platform, you need +only pass in an @ordering of qw(Project Platform). They will be done first, then +all of the rest of the fields in the $values hash. If you have no ordering +dependencies then you can simply omit @ordering. + +Note that the best way to determine if you have an ordering dependency try using +a Clearquest client and note the order that you set fields in. If at anytime +setting one field negates another field via action hook code then you have just +figured out that this field needs to be set before the file that just got +negated. + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $errmsg + +Error message (if any) + +=back + +=for html
    + +=cut + + my %values = %$values; + my $xml = _startXML $table; + + $action ||= 'Modify'; + + my $query = $self->_getInternalID ($table, $key); + + # Remove host portion + $query =~ s/^http.*$self->{server}//; + + # Add on action + $query .= "?rcm.action=$action"; + + # First process all fields in the @ordering, if specified + $xml .= $self->_setFieldValue ($table, $_, $values{$_}) foreach (@ordering); + + foreach my $field (keys %values) { + next if InArray $field, @ordering; + + $xml .= $self->_setFieldValue ($table, $field, $values{$field}); + } # foreach + + $xml .= ""; + + $self->_callREST ('put', $query, $xml); + + return $self->errmsg; +} # modify + +sub modifyDBID ($$$$;@) { + my ($self, $table, $dbid, $action, $values, @ordering) = @_; + +=pod + +=head2 modifyDBID ($table, $dbid, $action, %update) + +Updates records from $table matching $dbid. + +Parameters: + +=for html
    + +=over + +=item $table + +Table to modify records (e.g. 'Defect') + +=item $dbid + +The $dbid of the record to modify. + +=item $action + +Action to use for modification (Default: Modify). You can use this to change +state for stateful records. + +=item $values + +Hash reference containing name/value that have the new values for the fields + +=item @ordering + +Array containing field names that need to be processed in order. Not all fields +mentioned in the $values hash need be mentioned here. If you have fields that +must be set in a particular order you can mention them here. So, if you're +modifying the Defect record, but you need Project set before Platform, you need +only pass in an @ordering of qw(Project Platform). They will be done first, then +all of the rest of the fields in the $values hash. If you have no ordering +dependencies then you can simply omit @ordering. + +Note that the best way to determine if you have an ordering dependency try using +a Clearquest client and note the order that you set fields in. If at anytime +setting one field negates another field via action hook code then you have just +figured out that this field needs to be set before the file that just got +negated. + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $errmsg + +Error message (if any) + +=back + +=for html
    + +=cut + + my %values = %$values; + my $xml = _startXML $table; + + $action ||= 'Modify'; + + my $query = "$self->{uri}/record/"; + $query .= $self->_getRecordID ($table); + $query .= '-'; + $query .= $dbid; + + # Add on action + $query .= "?rcm.action=$action"; + + # First process all fields in the @ordering, if specified + $xml .= $self->_setFieldValue ($table, $_, $values{$_}) foreach (@ordering); + + foreach my $field (keys %values) { + next if InArray $field, @ordering; + + $xml .= $self->_setFieldValue ($table, $field, $values{$field}); + } # foreach + + $xml .= ""; + + $self->_callREST ('put', $query, $xml); + + return $self->errmsg; +} # modifyDBID + +sub new (;%) { + my ($class, $self) = @_; + +=pod + +=head2 new (%parms) + +Instantiate a new REST object. You can override the standard options by passing +them in as a hash in %parms. + +Parameters: + +=for html
    + +=over + +=item %parms + +Hash of overriding options + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item REST object + +=back + +=for html
    + +=cut + + $self->{server} ||= $Clearquest::OPTS{CQ_SERVER}; + + $$self{base_url} = "$self->{server}/cqweb/oslc", + $$self{uri} = "/cqweb/oslc/repo/$self->{dbset}/db/$self->{database}", + $$self{login} = { +# 'OSLC-Core-Version' => '2.0', + Accept => 'application/xml', + Authorization => 'Basic ' + . encode_base64 "$self->{username}:$self->{password}", + }; + + bless $self, $class; + + # We create this UserAgent and Cookie Jar so we can set cookies to be + # remembered and passed back and forth automatically. By doing this we re-use + # the JSESSIONID cookie we allows us to reuse our login and to dispose of the + # login session properly when we are destroyed. + my $userAgent = LWP::UserAgent->new; + + # Set the cookie jar to use in-memory cookie management, cookies can be + # persisted to disk, see HTTP::Cookies for more info. + $userAgent->cookie_jar (HTTP::Cookies->new); + + $self->{rest} = REST::Client->new ( + host => $self->{server}, + timeout => 15, + follow => 1, + useragent => $userAgent, + ); + + return $self; +} # new + +sub records () { + my ($self) = @_; + +=pod + +=head2 records () + +Returns a hash of all records and their record numbers + +Parameters: + +=for html
    + +=over + +=item nothing + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item %records + +Hash of records and their record numbers + +=back + +=for html
    + +=cut + + return if %RECORDS; + + my $url = "$self->{uri}/record-type/"; + + $self->_callREST ('get', $url); + + unless ($self->error) { + my %result = %{XMLin ($self->{rest}->responseContent)}; + + foreach my $uri (keys %{$result{entry}}) { + my ($recordID) = ($uri =~ /\/(\d+)/); + + $RECORDS{$result{entry}{$uri}{title}} = $recordID; + } # foreach + } # unless + + return %RECORDS; +} # records + +sub response () { + my ($self) = @_; + +=pod + +=head2 response () + +Returns the response content + +Parameters: + +=for html
    + +=over + +=item nothing + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $respondContent + +Response content from the last REST call + +=back + +=for html
    + +=cut + + return $self->{rest}->responseContent; +} # response + +sub username () { + my ($self) = @_; + +=pod + +=head2 username + +Returns the current username (or the username that would be used) + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item username + +=back + +=for html
    + +=cut + + return $self->{username}; +} # username + +1; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +L + +L + +L + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + GetConfig + +=end man + +=begin html + +
    +GetConf
    +
    + +=end html + +=head1 SEE ALSO + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this module. + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2012, ClearSCM, Inc. All rights reserved. + +=cut \ No newline at end of file diff --git a/lib/Clearquest/Server.pm b/lib/Clearquest/Server.pm new file mode 100644 index 0000000..559e007 --- /dev/null +++ b/lib/Clearquest/Server.pm @@ -0,0 +1,672 @@ +=pod + +=head1 NAME $RCSfile: Server.pm,v $ + +Clearquest Server - Provide access to Clearquest database + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 2.6 $ + +=item Created + +Monday, October 10, 2011 5:02:07 PM PDT + +=item Modified + +$Date: 2013/03/14 23:13:33 $ + +=back + +=head1 SYNOPSIS + +Provides an interface to the Clearquest database over the network. + +This library implements both the daemon portion of the server and the client +API. + +=head1 DESCRIPTION + +The server allows both read and write access to a Clearquest database as defined +in cq.conf file. Note the username/password must be of a user who can write to +the Clearquest database for write access to succeed. + +A hash is passed into to the execute method, which the client should use to talk +to the server, that describes relatively simple protocol to tell the server what +action to perform. In both the read case and the read/write case a field named +id should be defined that has a value of "=" (e.g. +"defect=BUGDB00034429"). + +For the read case the rest of the keys are the names of the fields to retrieve +with values that are undef'ed. For read/write, the rest of hash contains name +value pairs of fields to set and their values. + +Execute returns a status and a hash of name value pairs for the read case and an +array of lines for any error messages for the read/write case. + +=head1 ROUTINES + +The following methods are available: + +=cut + +package Clearquest::Server; + +use strict; +use warnings; + +use Carp; +use File::Basename; +use FindBin; +use IO::Socket; +use Net::hostent; +use POSIX qw(:sys_wait_h :signal_h); + +use DateUtils; +use Display; +use GetConfig; + +use Clearquest; + +# We cannot use parent here because CQPerl is used by the server. As such cqperl +# doesn't have parent.pm! +our @ISA = 'Clearquest'; + +our $VERSION = '$Revision: 2.6 $'; + ($VERSION) = ($VERSION =~ /\$Revision: (.*) /); + +sub new (;%) { + my ($class, %parms) = @_; + + my $self; + + $parms{CQ_DATABASE} ||= $Clearquest::OPTS{CQ_DATABASE}; + $parms{CQ_USERNAME} ||= $Clearquest::OPTS{CQ_USERNAME}; + $parms{CQ_PASSWORD} ||= $Clearquest::OPTS{CQ_PASSWORD}; + $parms{CQ_DBSET} ||= $Clearquest::OPTS{CQ_DBSET}; + $parms{CQ_SERVER} ||= $Clearquest::OPTS{CQ_SERVER}; + $parms{CQ_PORT} ||= $Clearquest::OPTS{CQ_PORT}; + + $parms{CQ_MULTITHREADED} = $Clearquest::OPTS{CQ_MULTITHREADED} + unless defined $parms{CQ_MULTITHREADED}; + + # The server always uses the standard Clearquest API + $parms{CQ_MODULE} = 'api'; + + # Set data members + $self->{username} = $parms{CQ_USERNAME}; + $self->{password} = $parms{CQ_PASSWORD}; + $self->{database} = $parms{CQ_DATABASE}; + $self->{dbset} = $parms{CQ_DBSET}; + $self->{server} = $parms{CQ_SERVER}; + $self->{port} = $parms{CQ_PORT}; + $self->{module} = $parms{CQ_MODULE}; + $self->{multithreaded} = $parms{CQ_MULTITHREADED}; + + return bless $self, $class; +} # new + +sub _tag ($) { + my ($self, $msg) = @_; + + my $tag = YMDHMS; + $tag .= ' '; + $tag .= $self->{pid} ? '[' . abs ($self->{pid}) . '] ' : ''; + + return "$tag$msg"; +} # _tag + +sub _verbose ($) { + my ($self, $msg) = @_; + + verbose $self->_tag ($msg); + + return; +} # _verbose + +sub _debug ($) { + my ($self, $msg) = @_; + + debug $self->_tag ($msg); + + return; +} # _debug + +sub _log ($) { + my ($self, $msg) = @_; + + display $self->_tag ($msg); + + return; +} # log + +sub _funeral () { + debug "Entered _funeral"; + + while (my $childpid = waitpid (-1, WNOHANG) > 0) { + my $status = $?; + + if ($childpid != -1) { + local $SIG{CHLD} = \&_funeral; + + my $msg = 'Child has died'; + $msg .= $status ? " with status $status" : ''; + + verbose "[$childpid] $msg" + if $status; + } # if + } # while + + return; +} # _funeral + +sub _endServer () { + display "Clearquest::Server V$VERSION shutdown at " . localtime; + + # Kill process group + kill 'TERM', -$$; + + # Wait for all children to die + while (wait != -1) { + # do nothing + } # while + + # Now that we are alone, we can simply exit + exit; +} # _endServer + +sub _restartServer () { + # Not sure what to do on a restart server + display 'Entered _restartServer'; + + return; +} # _restartServer + +sub _printStatus ($) { + my ($self, $client) = @_; + + my $status = $self->{clearquest}->error; + + $status ||= 0; + + $self->_debug ("Printing status: " . __PACKAGE__ . " Status: $status"); + + print $client __PACKAGE__ . " Status: $status\n"; + + $self->_debug ("After print"); + + return; +} # printStatus + +sub _connectToClearquest ($$$$) { + my ($self, $database, $username, $password, $dbset) = @_; + + my %parms; + + $parms{CQ_DATABASE} = $database; + $parms{CQ_USERNAME} = $username; + $parms{CQ_PASSWORD} = $password; + $parms{CQ_DBSET} = $dbset; + + # The server always uses the standard Clearquest API + $parms{CQ_MODULE} = 'api'; + + # Connect to Clearquest database + $self->{clearquest} = Clearquest->new (%parms); + + $self->_verbose ("Connecting to " + . "$parms{CQ_USERNAME}\@$parms{CQ_DATABASE}/$parms{CQ_DBSET}" + . " for $self->{clientname}"); + + $self->{loggedin} = $self->{clearquest}->connect; + + return $self->{loggedin}; +} # _connectToClearquest + +sub _processCommand ($$@) { + my ($self, $client, $call, @parms) = @_; + + $self->_debug ("Client wishes to execute $call"); + + if ($call eq 'end') { + $self->_verbose ("Serviced requests from $self->{clientname}"); + + close $client; + + $self->disconnectFromClient; + + return 1; + } elsif ($call eq 'open') { + debug "connectToClearquest"; + unless ($self->_connectToClearquest (@parms)) { + debug "Error: " . $self->{clearquest}->errmsg; + print $client $self->{clearquest}->errmsg . "\n"; + } else { + debug "Success!"; + print $client 'Connected to ' + . $self->username () . '@' + . $self->database () . '/' + . $self->dbset () . "\n"; + } # if + + debug "Calling _printStatus"; + $self->_printStatus ($client); + } elsif ($call eq 'get') { + my %record = $self->{clearquest}->get (@parms); + + unless ($self->{clearquest}->error) { + foreach my $field (keys %record) { + # TODO: Need to handle field types better... + if (ref $record{$field} eq 'ARRAY') { + foreach (@{$record{$field}}) { + # Change \n's to + s/\r\n/ /gm; + + print $client "$field\@\@$_\n"; + } # foreach + } else { + # Change \n's to + $record{$field} =~ s/\r\n/ /gm; + + print $client "$field\@\@$record{$field}\n"; + } # if + } # foreach + } else { + print $client $self->{clearquest}->errmsg . "\n"; + } # unless + + $self->_printStatus ($client); + } elsif ($call eq 'find') { + my ($result, $nbrRecs) = $self->{clearquest}->find (@parms); + + if ($self->{clearquest}->error != 0) { + print $client $self->{clearquest}->errmsg . "\n"; + } else { + # Store away $result so we can use it later + $self->{result} = $result; + + print $client "$result\n$nbrRecs\n"; + } # if + + $self->_printStatus ($client); + } elsif ($call eq 'getnext') { + my %record = $self->{clearquest}->getNext ($self->{result}); + + unless ($self->{clearquest}->error) { + foreach my $field (keys %record) { + # TODO: Need to handle field types better... + if (ref $record{$field} eq 'ARRAY') { + foreach (@{$record{$field}}) { + # Change \n's to + s/\r\n/ /gm; + + print $client "$field\@\@$_\n"; + } # foreach + } else { + # Change \n's to + $record{$field} =~ s/\r\n/ /gm; + + print $client "$field\@\@$record{$field}\n"; + } # if + } # foreach + } else { + print $client $self->{clearquest}->errmsg . "\n"; + } # unless + + $self->_printStatus ($client); + } elsif ($call eq 'getdynamiclist') { + # TODO Better error handling/testing + my @entry = $self->{clearquest}->getDynamicList (@parms); + + print $client "$_\n" foreach @entry; + + $self->_printStatus ($client); + } elsif ($call eq 'dbsets') { + # TODO Better error handling/testing + print $client "$_\n" foreach ($self->{clearquest}->DBSets); + + $self->_printStatus ($client); + } elsif ($call eq 'key') { + # TODO Better error handling/testing + print $client $self->{clearquest}->key (@parms) . "\n"; + + $self->_printStatus ($client); + } elsif ($call eq 'modify' or $call eq 'modifyDBID') { + my $table = shift @parms; + my $key = shift @parms; + my $action = shift @parms; + + # Need to turn off strict for eval here... + my ($values, @ordering); + no strict; + eval $parms[0]; + + $values = $VAR1; + use strict; + + @ordering = @{$parms[1]} if ref $parms[1] eq 'ARRAY'; + + my $errmsg; + + if ($call eq 'modify') { + $errmsg = $self->{clearquest}->modify ($table, $key, $action, $values, @ordering); + } elsif ($call eq 'modifyDBID') { + $errmsg = $self->{clearquest}->modifyDBID ($table, $key, $action, $values, @ordering); + } # if + + print $client "$errmsg\n" if $errmsg ne ''; + + $self->_printStatus ($client); + } elsif ($call eq 'add') { + my $dbid = $self->{clearquest}->add (@parms); + + if ($self->{clearquest}->error) { + print $client 'ERROR: ' . $self->{clearquest}->errmsg () . "\n"; + } # if + + $self->_printStatus ($client); + } elsif ($call eq 'delete') { + $self->{clearquest}->delete (@parms); + + if ($self->{clearquest}->error) { + print $client 'ERROR: ' . $self->{clearquest}->errmsg () . "\n"; + } # if + + $self->_printStatus ($client); + } else { + $self->{clearquest}->{errnbr} = -1; + $self->{clearquest}->{errmsg} = "Unknown call $call"; + + print $client $self->{clearquest}->errmsg . "\n"; + + $self->_printStatus ($client); + } # if + + return; +} # _processCommand + +sub _serviceClient ($) { + my ($self, $client) = @_; + + $self->_verbose ("Servicing requests from $self->{clientname}"); + + # Set autoflush for client + $client->autoflush if $client; + + my $line; + + $self->_debug ("Reading request from client"); + + while ($line = <$client>) { + $self->_debug ("Request read: $line"); + + if ($line) { + chomp $line; chop $line if $line =~ /\r$/; + } else { + $self->_verbose ("Host $self->{clientname} went away!"); + + close $client; + + return; + } # if + + if ($line =~ /^shutdown/i) { + if ($self->{server}) { + $self->_verbose ("$self->{clientname} requested to shutdown the server"); + + print $client __PACKAGE__ . " Status: 0\n"; + } # if + + # TODO: This is not working because getppid is not implemented on Windows! + #kill HUP => getppid; + + exit 1; + } # if + + # Parse command line + my ($call, @parms); + + if ($line =~ /^\s*(\S+)\s+(.*)/) { + $call = lc $1; + + no strict; + eval $2; + + @parms = @$VAR1; + use strict; + + my $i = 0; + + foreach (@parms) { + if (/^\$VAR1/) { + no strict; + eval; + + $parms[$i++] = $VAR1; + use strict; + } else { + $i++; + } # if + } # foreach + } elsif ($line =~ /^\s*(\S+)/) { + $call = lc $1; + @parms = (); + } else { + my $errmsg = "Garbled command line: '$line'"; + + if ($self->{clearquest}) { + $self->{clearquest}->{errnbr} = -1; + $self->{clearquest}->{errmsg} = $errmsg; + + print $client $self->{clearquest}->errmsg . "\n"; + } else { + print "$errmsg\n"; + } # if + + $self->_printStatus ($client); + + return; + } # if + + $self->_debug ("Processing command $call @parms"); + + last if $self->_processCommand ($client, $call, @parms); + } # while + + return; +} # _serviceClient + +sub multithreaded (;$) { + my ($self, $newValue) = @_; + + my $oldValue = $self->{multithreaded}; + + $self->{multithreaded} = $newValue if $newValue; + + return $oldValue +} # multithreaded + +sub disconnectFromClient () { + my ($self) = @_; + + # Destroy Clearquest object so we disconnect from Clearquest. + undef $self->{clearquest}; + + $self->_verbose ("Disconnected from client $self->{clientname}") + if $self->{clientname}; + + undef $self->{clientname}; + + return; +} # disconnectFromClient + +sub DESTROY () { + my ($self) = @_; + + $self->disconnectFromClient; + + if ($self->{socket}) { + close $self->{socket}; + + undef $self->{socket}; + } # if +} # DESTROY + +sub startServer () { + my ($self) = @_; + + # Create new socket to communicate to clients with + $self->{socket} = IO::Socket::INET->new ( + Proto => 'tcp', + LocalPort => $self->{port}, + Listen => SOMAXCONN, + ReuseAddr => 1, + ); + + error "Could not create socket - $!", 1 + unless $self->{socket}; + + # Announce ourselves + $self->_log (__PACKAGE__ . " V$VERSION accepting clients at " . localtime); + + $SIG{HUP} = \&_endServer; + + # Now wait for an incoming request + my $client; + + LOOP: while () { + $client = $self->{socket}->accept; + + if ($? == -1) { + if ($!{EINTR}) { + next; + } else { + error "Accept called failed (Error: $?) - $!", 1; + } # if + } # if + + my $hostinfo = gethostbyaddr $client->peeraddr; + + $self->{clientname} = $hostinfo ? $hostinfo->name : $client->peerhost; + + $self->_verbose ("$self->{clientname} is requesting service"); + + if ($self->multithreaded) { + $self->{pid} = $$; + + my $childpid; + + $self->_debug ("Spawning child to handle request"); + + error "Can't fork: $!" + unless defined ($childpid = fork); + + if ($childpid) { + $self->{pid} = $$; + + # Signal handling sucks under Windows. For example, we cannot catch + # SIGCHLD when using the ActiveState based cqperl when running on + # Windows. If there will be a zombie apocalypse it will start on + # Windows! ;-) + unless ($^O =~ /win/i) { + my $sigset = POSIX::SigSet->new (&POSIX::SIGCHLD); + my $sigaction = POSIX::SigAction->new (\&_funeral, $sigset, &POSIX::SA_RESTART); + } # unless + + $self->_debug ("Parent produced child [$childpid]"); + } else { + # In child process - ServiceClient + $self->{pid} = $$; + + # Now exec the caller but set STDIN to be the socket. Also pass + # -serviceClient to the caller which will need to handle that and call + # _serviceClient. + $self->_debug ("Client: $client"); + open STDIN, '+<&', $client + or croak "Unable to dup client"; + + my $cmd = "cqperl \"$FindBin::Bin/$FindBin::Script -serviceClient=$self->{clientname} -verbose -debug"; + + $self->_debug ("Execing: $cmd"); + + exec 'cqperl', "\"$FindBin::Bin/$FindBin::Script\"", "-serviceClient=$self->{clientname}", '-verbose', '-debug' + or croak "Unable to exec $cmd"; + } # if + } else { + $self->_serviceClient ($client); + } # if + } # while + + # On Windows we can't catch SIGCHLD so we need to loop around. Ugly! + goto LOOP if $^O =~ /win/i; +} # startServer + +1; + +=pod + +=head1 CONFIGURATION AND ENVIRONMENT + +DEBUG: If set then $debug is set to this level. + +VERBOSE: If set then $verbose is set to this level. + +TRACE: If set then $trace is set to this level. + +=head1 DEPENDENCIES + +=head2 Perl Modules + +L + +L + +L + +L + +L + +L + +=head2 ClearSCM Perl Modules + +=begin man + + DateUtils + Display + GetConfig + +=end man + +=begin html + +
    +DateUtils
    +Display
    +GetConf
    +
    + +=end html + +=head1 SEE ALSO + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this module. + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2011, ClearSCM, Inc. All rights reserved. + +=cut diff --git a/lib/CmdLine.pm b/lib/CmdLine.pm new file mode 100644 index 0000000..828a3d0 --- /dev/null +++ b/lib/CmdLine.pm @@ -0,0 +1,1490 @@ +=pod + +=head1 NAME $RCSfile: CmdLine.pm,v $ + +Library to implement generic command line interface + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.13 $ + +=item Created + +Fri May 13 15:23:37 PDT 2011 + +=item Modified + +$Date: 2011/12/23 01:02:49 $ + +=back + +=head1 SYNOPSIS + +Provides an interface to a command line utilizing Term::ReadLine and +Term::ReadLine::Gnu. Note, the latter is not part of Perl core and +must be downloaded from CPAN. Without Term::ReadLine::Gnu a lot of +functionality doesn't work. + +CmdLine uses a hash to describe what your valid commands are along +with help and longer help, i.e. description strings. If you do not +define your commands then no command name completion nor help will be +available. + + use FindBin; + use CmdLine; + + my %cmds = ( + list => ( + help => 'help []' + description => 'This is a longer description +of the list command', + ), + execute => ( + help => 'execute ', + description => 'Longer description of the execute command', + ), + ); + + # Create a new cmdline: + my $cmdline = CmdLine->new ($FindBin::Script, %cmds); + + while (my $cmd = $cmdline->get) { + ... + } # while + +=head1 DESCRIPTION + +This module implements a command line stack using Term::ReadLine and +Term::ReadLine::Gnu. If Term::ReadLine::Gnu is not installed then many +of the functions do not work. Command completion if commands are +defined with a hash as shown above. + +=head1 DEFAULT COMMANDS + +The for a list of the builtin commands see %builtin_cmds below + +Additionally ! will re-exeucte a comand from history and ! +will execute { + help => 'history [ ]', + description => 'Displays cmd history. You can specify where to and where to . +Default is to list only the last screen full lines of history +(as denoted by $LINES).' + }, + + help => { + help => 'help []', + description => 'Displays help.', + }, + + savehist => { + help => 'savehist [ ]', + description => 'Saves a section of the history to a file. You can specify where to +and where to . Default is to save all of the history to +the specified file.', + }, + + get => { + help => 'get ', + description => 'Gets a variable.', + }, + + set => { + help => 'set =', + description => 'Sets a variable. Note that expression can be any valid expression.', + }, + + vars => { + help => 'vars', + description => 'Displays all known variables.', + }, + + source => { + help => 'source ', + description => 'Run commands from a file.', + }, + + color => { + help => 'color []', + description => 'Turn on|off color. With no options displays status of color.', + }, + + trace => { + help => 'trace []', + description => 'Turn on|off tracing. With no options displays status of trace.', + }, +); + +sub _cmdCompletion ($$) { + my ($text, $state) = @_; + + return unless %_cmds; + + $_pos = 0 unless $state; + + my @cmds = keys %_cmds; + + for (; $_pos < @cmds;) { + return $cmds[$_pos - 1] + if $cmds[$_pos++] =~ /^$text/i; + } # for + + return; +}# _cmdCompletion + +sub _complete ($$$$) { + my ($text, $line, $start, $end) = @_; + + return $_cmdline->completion_matches ($text, \&CmdLine::_cmdCompletion); +} # _complete + +sub _gethelp () { + my ($self) = @_; + + return unless %_cmds; + + my $line = $_cmdline->{line_buffer}; + + # Trim + $line =~ s/^\s+//; + $line =~ s/\s+$//; + + display ''; + + # Sometimes we are called by ReadLine's callback and can't pass $self + if (ref $self eq 'CmdLine') { + $self->help ($line); + } else { + $CmdLine::cmdline->help ($line); + } # if + + $_cmdline->on_new_line; +} # _gethelp + +sub _interpolate ($) { + my ($self, $str) = @_; + + # Skip interpolation for the perl command (Note this is raid specific) + return $str + if $str =~ /^\s*perl\s*/i; + + while ($str =~ /\$/) { + if ($str =~ /\$(\w+)/) { + my $varname = $1; + + if (defined $self->{vars}{$varname}) { + if ($self->{vars}{$varname} =~ / /) { + $str =~ s/\$$varname/\'$self->{vars}{$varname}\'/; + } else { + $str =~ s/\$$varname/$self->{vars}{$varname}/; + } # if + } else { + $str =~ s/\$$varname//; + } # if + } # if + } # while + + return $str; +} # _interpolate + +sub _builtinCmds ($) { + my ($self, $line) = @_; + + unless (defined $line) { + display ''; + return 'exit'; + } # unless + + my ($cmd, $result); + + # Short circut "psuedo" commands of ! and ! + if ($line =~ /^\s*!\s*(\d+)/) { + $line = $self->history ('redo', $1); + } elsif ($line =~ /^\s*!\s*(\S+)\s*(.*)/) { + if ($2) { + system "$1 $2"; + } else { + system $1; + } # if + + #$_cmdline->remove_history ($_cmdline->where_history); + + return; + } # if + + if ($line =~ /^\s*(\S+)/) { + $cmd = $1; + } # if + + return + unless $cmd; + + my @parms; + + # Search for matches of partial commands + my $foundCmd; + + foreach (keys %builtin_cmds) { + if ($_ eq $cmd) { + # Exact match - honor it + $foundCmd = $cmd; + last; + } elsif (/^$cmd/) { + # Command matched partially + unless ($foundCmd) { + # Found first instance of a match + $foundCmd = $_; + } else { + # Found second instance of a match - $cmd is not unique + undef $foundCmd; + last; + } # unless + } # if + } # foreach + + # If we found a command, substitute it into line + if ($foundCmd) { + $line =~ s/^\s*$cmd\s*/$foundCmd /; + $cmd = $foundCmd; + } # if + + if ($builtin_cmds{$cmd}) { + if ($line =~ /^\s*help\s*(.*)/i) { + if ($1 =~ /(.+)$/) { + $self->help ($1); + } else { + $self->help; + } # if + } elsif ($line =~ /^\s*history\s*(.*)/i) { + if ($1 =~ /(\d+)\s+(\d+)\s*$/) { + $self->history ('list', $1, $2); + } elsif ($1 =~ /^\s*$/) { + $self->history ('list'); + } else { + error "Invalid usage"; + $self->help ('history'); + } # if + } elsif ($line =~ /^\s*savehist\s*(.*)/i) { + if ($1 =~ /(\S+)\s+(\d+)\s+(\d+)\s*$/) { + $self->history ('save', $1, $2, $3); + } else { + error 'Invalid usage'; + $self->help ('savehist'); + } # if + } elsif ($line =~ /^\s*get\s*(.*)/i) { + if ($1 =~ (/^\$*(\S+)\s*$/)) { + my $value = $self->_get ($1); + + if ($value) { + display "$1 = $value"; + } else { + error "$1 is not set"; + } # if + } else { + error 'Invalid usage'; + $self->help ('get'); + } # if + } elsif ($line =~ /^\s*set\s*(.*)/i) { + if ($1 =~ /^\$*(\S+)\s*=\s*(.*)/) { + $self->_set ($1, $2) + } else { + error 'Invalid usage'; + $self->help ('set'); + } # if + } elsif ($line =~ /^\s*source\s+(\S+)/i) { + $result = $self->source ($1); + } elsif ($line =~ /^\s*vars\s*/) { + $self->vars ($line); + } elsif ($line =~ /^\s*color\s*(.*)/i) { + if ($1 =~ /(1|on)/i) { + $opts{color} = 1; + delete $ENV{ANSI_COLORS_DISABLED} + if $ENV{ANSI_COLORS_DISABLED}; + } elsif ($1 =~ /(0|off)/i) { + $opts{trace} = 0; + $ENV{ANSI_COLORS_DISABLED} = 1; + } elsif ($1 =~ /\s*$/) { + if ($ENV{ANSI_COLORS_DISABLED}) { + display 'Color is currently off'; + } else { + display 'Color is currently on'; + } # if + } else { + error 'Invalid usage'; + $self->help ('color'); + } # if + } elsif ($line =~ /^\s*trace\s*(.*)/i) { + if ($1 =~ /(1|on)/i) { + $opts{trace} = 1; + } elsif ($1 =~ /(0|off)/i) { + $opts{trace} = 0; + } elsif ($1 =~ /\s*$/) { + if ($opts{trace}) { + display 'Trace is currently on'; + } else { + display 'Trace is currently off'; + } # if + } else { + error 'Invalid usage'; + $self->help ('trace'); + } # if + } # if + } # if + + return ($cmd, $line, $result); +} # _builtinCmds + +sub _interrupt () { + # Announce that we have hit an interrupt + print color ('yellow') . "\n" . color ('reset'); + + # Free up all of the line state info + $_cmdline->free_line_state; + + # Allow readline to clean up + $_cmdline->cleanup_after_signal; + + # Redisplay prompt on a new line + $_cmdline->on_new_line; + $_cmdline->{line_buffer} = ''; + $_cmdline->redisplay; + + return; +} # _interrupt + +sub _displayMatches ($$$) { + my ($matches, $numMatches, $maxLength) = @_; + + # Work on a copy... (Otherwise we were getting "Attempt to free unreferenced + # scalar" internal errors from perl) + my @Matches; + + push @Matches, $_ foreach (@$matches); + + my $match = shift @Matches; + + if ($match =~/^\s*(.*) /) { + $match = $1; + } elsif ($match =~ /^\s*(\S+)$/) { + $match = ''; + } # if + + my %newMatches; + + foreach (@Matches) { + # Get next word + s/^$match//; + + if (/(\w+)/) { + $newMatches{$1} = $1; + } # if + } # foreach + + my @newMatches = sort keys %newMatches; + + unshift @newMatches, $match; + + $_cmdline->display_match_list (\@newMatches); + $_cmdline->on_new_line; + $_cmdline->redisplay; + + return; +} # _displayMatches + +sub new (;$$%) { + my ($class, $histfile, $eval, %cmds) = @_; + +=pod + +=head2 new () + +Construct a new CmdLine object. Note there is already a default +CmdLine object created named $cmdline. You should use that unless you +have good reason to instantiate another CmdLine object. + +Parameters: + +=for html
    + +=over + +=item $histfile + +Set to a file name where to write the history file. If not defined no +history is kept. + +=item %cmds + +A hash describing the valid commands and their help/description +strings. + + my %cmds = ( + 'list' => { + help => 'List all known commands', + description => 'This is a longer description + of the list command', + }, + 'help' => { + help => 'This is a help command', + description => 'help + Longer description of help', + }, + ); + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item CmdLine object + +=back + +=for html
    + +=cut + + my $self = bless { + histfile => $histfile, + }, $class; + + my $me = get_me; + + $histfile ||= ".${me}_hist"; + + error "Creating bogus .${me}_hist file!" + if $me eq '-'; + + unless (-f $histfile) { + open my $hist, '>', $histfile + or error "Unable to open $histfile for writing - $!", 1; + + close $hist; + } # unless + + # Instantiate a commandline + $_cmdline = Term::ReadLine->new ($me); + + # Store the function pointer of what to call when sourcing a file or + # evaluating an expression. + if ($eval) { + if (ref $eval eq 'CODE') { + $self->{eval} = $eval; + } else { + error "Invalid function pointer\nUsage: CmdLine->new ($histfile, $eval, %cmds)", 1; + } # if + } # if + + # Default prompt is "$me:" + $self->{prompt} = "$me:"; + + # Set commands + $self->set_cmds (%cmds); + + # Set some ornamentation + $_cmdline->ornaments ('s,e,u,') unless $Config{cppflags} =~ /win32/i; + + # Read in history + $self->set_histfile ($histfile); + + # Generator function for completion matches + $_attribs = $_cmdline->Attribs; + + $_attribs->{attempted_completion_function} = \&CmdLine::_complete; + $_attribs->{completion_display_matches_hook} = \&CmdLine::_displayMatches; + $_attribs->{completer_word_break_characters} =~ s/ // + if $_attribs->{completer_word_break_characters}; + + # The following functionality requires Term::ReadLine::Gnu + if ($_haveGnu) { + # Bind a key to display completion + $_cmdline->add_defun ('help-on-command', \&CmdLine::_gethelp, ord ("\cl")); + + # Save a handy copy of RL_PROMPT_[START|END]_IGNORE + $self->{ignstart} = $_cmdline->RL_PROMPT_START_IGNORE; + $self->{ignstop} = $_cmdline->RL_PROMPT_END_IGNORE; + } # if + + if ($Config{cppflags} =~ /win32/i) { + $opts{trace} = 0; + $ENV{ANSI_COLORS_DISABLED} = 1; + } # if + + return $self; +} # new + +sub get () { + my ($self) = @_; + +=pod + +=head2 get + +Retrieves a command line + +Parameters: + +=for html
    + +=over + +=item None + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $cmds + +=back + +=for html
    + +=cut + + my ($cmd, $line, $result); + + do { + # Substitute cmdnbr into prompt if we find a '\#' + my $prompt = $self->{prompt}; + + $prompt =~ s/\\\#/$self->{cmdnbr}/g; + + use POSIX; + + # Term::ReadLine::Gnu restarts whatever system call it is using, such that + # once we ctrl C, we don't get back to Perl until the user presses enter, + # finally whereupon we get our signal handler called. We use sigaction + # instead to use the old perl unsafe signal handling, but only in this read + # routine. Sure, sigaction poses race conditions, but you'd either be at a + # prompt or executing whatever command your prompt prompted for. The user + # has said "Abort that!" with his ctrl-C and we're attempting to honor that. + + # Damn Windows can't do any of this + my $oldaction; + + if ($Config{cppflags} !~ /win32/i) { + my $sigset = POSIX::SigSet->new; + my $sigaction = POSIX::SigAction->new (\&_interrupt, $sigset, 0); + + $oldaction = POSIX::SigAction->new; + + # Set up our unsafe signal handler + POSIX::sigaction (&POSIX::SIGINT, $sigaction, $oldaction); + } # if + + $line = $_cmdline->readline ($prompt); + + # Restore the old signal handler + if ($Config{cppflags} !~ /win32/i) { + POSIX::sigaction (&POSIX::SIGINT, $oldaction); + } # if + + $line = $self->_interpolate ($line) + if $line; + + $self->{cmdnbr}++ + unless $self->{sourcing}; + + ($cmd, $line, $result) = $self->_builtinCmds ($line); + + $line = '' + unless $cmd; + } while ($cmd and $builtin_cmds{$cmd}); + + return ($line, $result); +} # get + +sub set_cmds (%) { + my ($self, %cmds) = @_; + +=pod + +=head2 set_cmds + +Sets the cmds + +Parameters: + +=for html
    + +=over + +=item %cmds + +New commands to use + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Nothing + +=back + +=for html
    + +=cut + + %_cmds = %cmds; + + # Add in builtins + foreach (keys %builtin_cmds) { + $_cmds{$_}{help} = $builtin_cmds{$_}{help}; + $_cmds{$_}{description} = $builtin_cmds{$_}{description}; + } # foreach + + return; +} # set_cmds + +sub set_prompt ($) { + my ($self, $prompt) = @_; + +=pod + +=head2 set_prompt + +Sets the prompt + +Parameters: + +=for html
    + +=over + +=item $new_prompt + +New commands to use + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $old_prompt + +=back + +=for html
    + +=cut + + my $return = $self->{prompt}; + + $self->{prompt} = $prompt; + + return $return; +} # set_prompt + +sub set_histfile ($) { + my ($self, $histfile) = @_; + +=pod + +=head2 set_histfile + +Sets the histfile + +Parameters: + +=for html
    + +=over + +=item $histfile + +New commands to use + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Nothing + +=back + +=for html
    + +=cut + + if ($histfile and -f $histfile) { + $self->{histfile} = $histfile; + + if ($_haveGnu) { + # Clear old history (if any); + $_cmdline->clear_history; + + # Now read histfile + $_cmdline->ReadHistory ($histfile); + } # if + + # Determine the number of lines in the history file + open my $hist, '<', $histfile; + + # Set cmdnbr + for (<$hist>) {} + $self->{cmdnbr} = $. + 1; + + close $hist; + } # if + + return; +} # set_histfile + +sub set_eval (;\&) { + my ($self, $eval) = @_; + +=pod + +=head2 set_eval + +Sets the eval function pointer + +Parameters: + +=for html
    + +=over + +=item [\&function] + +Function to set eval to. This function will be called with the command +line as the only paramter and it should return a result. + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item \&old_eval + +=back + +=for html
    + +=cut + + my $returnEval = $self->{eval}; + + $self->{eval} = $eval; + + return $returnEval; +} # set_eval + +sub help (;$) { + my ($self, $cmd) = @_; + +=pod + +=head2 help [] + +Displays help + +Note that the user does not need to explicitly call help - CmdLine's +get method will already sense that the builtin help command was +invoked and handle it. This method is provided if the caller wishes to +call this internally for some reason. + +Parameters: + +=for html
    + +=over + +=item $cmd + +Optional command help + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Nothing + +=back + +=for html
    + +=cut + + my @help; + + $cmd ||= ''; + $cmd =~ s/^\s+//; + $cmd =~ s/\s+$//; + + if ($cmd =~ /^\s*(.+)/) { + my ($searchStr, $helpFound); + + $searchStr = $1; + + foreach (sort keys %_cmds) { + if (/$searchStr/i) { + $helpFound = 1; + + my $cmdcolor = $builtin_cmds{$_} ? color ('cyan') : color ('magenta'); + my $boldOn = $builtin_cmds{$_} ? color ('white on_cyan') : color ('white on_magenta'); + my $boldOff = color ('reset') . $cmdcolor; + + my $cmd = "$cmdcolor$_"; + $cmd =~ s/($searchStr)/$boldOn$1$boldOff/g; + $cmd .= " $_cmds{$_}{parms}" if $_cmds{$_}{parms}; + $cmd .= color ('reset'); + $cmd .= " - $_cmds{$_}{help}" if $_cmds{$_}{help}; + + push @help, $cmd; + + if ($_cmds{$_}{description}) { + push @help, " $_" + foreach (split /\n/, $_cmds{$_}{description}); + } # if + } # if + } # foreach + + unless ($helpFound) { + display "I don't know about $cmd"; + + return; + } # if + } else { + foreach (sort keys %_cmds) { + my $cmdcolor = $builtin_cmds{$_} ? color ('cyan') : color ('magenta'); + + my $cmd = "$cmdcolor$_"; + $cmd .= " $_cmds{$_}{parms}" if $_cmds{$_}{parms}; + $cmd .= color ('reset'); + $cmd .= " - $_cmds{$_}{help}" if $_cmds{$_}{help}; + + push @help, $cmd; + + if ($_cmds{$_}{description}) { + push @help, " $_" + foreach (split /\n/, $_cmds{$_}{description}); + } # if + } # foreach + } # if + + $self->handleOutput ($cmd, @help); + + return; +} # help + +sub history (;$) { + my ($self, $action) = @_; + +=pod + +=head2 history [] [ ] + +This method lists, saves or executes (redo) a command from the history +stack. can be one of 'list', 'save' or 'redo'. If listing +history one can specify the optional and parameters. If +saving then must be specified and optionally and +. If redoing a command then only or the command number +should be specified. + +Note that the user does not need to explicitly call history - +CmdLine's get method will already sense that the builtin history +command was invoked and handle it. This method is provided if the +caller wishes to call this internally for some reason. + +Parameters: + +=for html
    + +=over + +=item $action + +One of 'list', 'save' or 'redo' + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Nothing + +=back + +=for html
    + +=cut + + if ($Config{cppflags} =~ /win32/i) { + warning 'The history command does not work on Windows (sorry)'; + + return; + } # if + + my ($file, $start, $end); + + if ($action eq 'list') { + $start = $_[2]; + $end = $_[3]; + } elsif ($action eq 'save') { + $file = $_[2]; + $start = $_[3]; + $end = $_[4]; + } elsif ($action eq 'redo') { + $_cmdline->remove_history ($_cmdline->where_history); + + my $nbr = $_[2]; + my $line = $_cmdline->history_get ($nbr); + + $_cmdline->add_history ($line); + display $line; + + my ($cmd, $result) = $self->_builtinCmds ($line); + + if ($builtin_cmds{$cmd}) { + return; + } else { + return $line; + } # if + } else { + error "Unknown action $action in history"; + return; + } # if + + my $current = $_cmdline->where_history; + + my $lines = ($ENV{LINES} ? $ENV{LINES} : 24) - 2; + + $start = $current - $lines + unless defined $start; + $start = 1 + if $start < 1; + $end = $current + unless defined $end; + $end = 1 + if $end < 1; + + if ($start > $end) { + error "Start ($start) is > end ($end)"; + help ('history'); + } else { + my $savefile; + + if ($action eq 'save') { + unless ($file) { + error "Usage: savehist [ ]"; + return; + } # unless + + if (-f $file) { + display_nolf "Overwrite $file (yN)? "; + + my $response = ; + + unless ($response =~ /(y|yes)/i) { + display "Not overwritten"; + return; + } # unless + } # if + + my $success = open $savefile, '>', $file; + + unless ($success) { + error "Unable to open history file $file - $!"; + return; + } # unless + } # if + + for (my $pos = $start; $pos <= $end; $pos++) { + my $histline = $_cmdline->history_get ($pos); + + last unless $histline; + + if ($action eq 'list') { + display "$pos) $histline"; + } else { + print $savefile "$histline\n"; + } # if + } # for + + close $savefile + if $action eq 'save'; + } # if + + return; +} # history + +sub _get ($$) { + my ($self, $name) = @_; + +=pod + +=head2 _get ($name) + +This method gets a variable to a value stored in the CmdLine +object. + +Parameters: + +=for html
    + +=over + +=item $name + +Name of the variable + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $value + +=back + +=for html
    + +=cut + + return $self->{vars}{$name} +} # _get + +sub _set ($$) { + my ($self, $name, $value) = @_; + +=pod + +=head2 _set ($name, $value) + +This method sets a variable to a value stored in the CmdLine +object. Note $value will be evaluated if eval is set. + +Parameters: + +=for html
    + +=over + +=item $name + +Name of the variable + +=item $value + +Value of the variable + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $oldvalue + +=back + +=for html
    + +=cut + + my $returnValue = $self->{vars}{$name}; + + if (defined $value) { + $value = $self->_interpolate ($value); + + # Do not call eval if we are setting result - otherwise we recurse + # infinitely. + unless ($name eq 'result') { + no strict; + $value = $self->{eval} ($value) + if $self->{eval}; + use strict; + } # unless + + $self->{vars}{$name} = $value; + } else { + delete $self->{vars}{$name}; + } # if + + return $returnValue; +} # _set + +sub vars ($) { + my ($self, $cmd) = @_; + +=pod + +=head2 vars ($name) + +This method will print out all known variables + +Parameters: + +=for html
    + +=over + +=item none + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Nothing + +=back + +=for html
    + +=cut + + my @output; + + push @output, "$_ = $self->{vars}{$_}" + foreach (keys %{$self->{vars}}); + + $self->handleOutput ($cmd, @output); +} # vars + +sub handleOutput ($@) { + my ($self, $line, @output) = @_; + +=pod + +=head2 handleOutput ($line, @output) + +This method will handle outputing the array @output. It also handles redirection +(currently only output redirection) and piping + +Parameters: + +=for html
    + +=over + +=item $line + +The command line used to produce @output. This method parses out redirection +(i.e. > and >>) and piping (|) from $cmd + +=item @output + +The output produced by the command to redirect or pipe. (Note this isn't true +piping in that command must run first and produce all of @output before we are +called. Need to look into how to use Perl's pipe command here). + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Nothing + +=back + +=for html
    + +=cut + + my ($outToFile, $pipeToCmd); + + # Handle piping and redirection + if ($line =~ /(.*)\>{2}\s*(.*)/) { + $line = $1; + $outToFile = ">$2"; + } elsif ($line =~ /(.*)\>{1}\s*(.*)/) { + $line = $1; + $outToFile = $2; + } elsif ($line =~ /(.*?)\|\s*(.*)/) { + $line = $1; + $pipeToCmd = $2; + } # if + + # Store @output + $self->{output} = \@output; + + if ($pipeToCmd) { + my $pipe; + + local $SIG{PIPE} = 'IGNORE'; + + open $pipe, "|$pipeToCmd" + or undef $pipe; + + # TODO: Not handling the output here. Need open2 and then recursively call + # handleOutput. + if ($pipe) { + print $pipe "$_\n" + foreach (@output); + + close $pipe + or error "Unable to close pipe for $pipeToCmd - $!"; + } else { + error "Unable to open pipe for $pipeToCmd - $!"; + } # if + } else { + unless ($outToFile) { + PageOutput @output; + } else { + open my $output, ">$outToFile"; + + if ($output) { + print $output "$_\n" + foreach (@output); + + close $output; + + undef $outToFile; + } else { + error "Unable to open $outToFile for writing - $!" + } # if + } # unless + } # if + + return; +} # handleOutput + +sub source ($) { + my ($self, $file) = @_; + +=pod + +=head2 source + +This method opens a file and sources it's content by executing each +line. Note that the user must have set $self->{eval} to a function +pointer. The function will be called with one parameter - the command +line to execute. The function will return the result from the +execution of the final command. + +Note that the user does not need to explicitly call source - +CmdLine's get method will already sense that the builtin source +command was invoked and handle it. This method is provided if the +caller wishes to call this internally for some reason. + +Parameters: + +=for html
    + +=over + +=item $file + +Filename to source + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Returns the result of the last command executed + +=back + +=for html
    + +=cut + + unless (-f $file) { + error "Unable to open file $file - $!"; + return; + } # unless + + open my $source, '<', $file; + + my $result; + + $self->{sourcing} = 1; + + my $i = 0; + + while (<$source>) { + chomp; + + $i++; + + my $prompt = $self->{prompt}; + + $prompt =~ s/\\\#/$file:$i/; + + display "$prompt$_" if $CmdLine::opts{trace}; + + next if /^\s*($|\#)/; + + $_ = $self->_interpolate ($_); + + # Check to see if it's a builtin + my ($cmd, $line, $result) = $self->_builtinCmds ($_); + + next if $builtin_cmds{$cmd}; + + no strict; + $result = $self->{eval} ($line); + use strict; + + if (defined $result) { + if (ref \$result eq 'SCALAR') { + PageOutput (split /\n/, $result); + } else { + display "Sorry but I cannot display structured results"; + } # if + } # if + } # while + + $self->{sourcing} = 0; + + close $source; + + return $result; +} # source + +sub DESTROY { + my ($self) = @_; + + $_cmdline->WriteHistory ($self->{histfile}) + if $_cmdline and $_haveGnu; +} # DESTROY + +our $cmdline = CmdLine->new; + +1; + +=pod + +=head1 DEPENDENCIES + +=head2 Perl Modules + +=head2 ClearSCM Perl Modules + +=for html

    Display

    + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this module + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +Copyright (c) 2007, ClearSCM, Inc. All rights reserved. + +=cut diff --git a/lib/DateUtils.pm b/lib/DateUtils.pm new file mode 100644 index 0000000..b8c1f49 --- /dev/null +++ b/lib/DateUtils.pm @@ -0,0 +1,1293 @@ +=pod + +=head1 NAME $RCSfile: DateUtils.pm,v $ + +Simple date/time utilities + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.32 $ + +=item Created + +Thu Jan 5 11:06:49 PST 2006 + +=item Modified + +$Date: 2013/02/21 05:01:17 $ + +=back + +=head1 SYNOPSIS + +Simple date and time utilities for often used date/time functionality. + + my $ymd = YMD; + my $ymdhm = YMDHM; + my $timestamp = timestamp; + +=head1 DESCRIPTION + +Often you just want to simply and quickly get date or date and time in +a YMD or YMDHM format. Note the YMDHM format defined here is YMD\@H:M +and is not well suited for a filename. The timestamp routine returns +YMD_HM. + +=head1 ROUTINES + +The following routines are exported: + +=cut + +package DateUtils; + +use strict; +use warnings; + +use base 'Exporter'; + +use Carp; +use Time::Local; + +use Display; +use Utils; + +our @EXPORT = qw ( + Add + Age + Compare + DateToEpoch + EpochToDate + FormatDate + FormatTime + MDY + SQLDatetime2UnixDatetime + SubtractDays + Today2SQLDatetime + UnixDatetime2SQLDatetime + UTCTime + YMD + YMDHM + YMDHMS + timestamp + ymdhms +); + +my @months = ( + 31, # January + 28, # February + 31, # March + 30, # April + 31, # May + 30, # June + 31, # July + 31, # August + 30, # September + 31, # October + 30, # November + 31 # Descember +); + +my $SECS_IN_MIN = 60; +my $SECS_IN_HOUR = $SECS_IN_MIN * 60; +my $SECS_IN_DAY = $SECS_IN_HOUR * 24; + +# Forwards +sub Today2SQLDatetime (); +sub DateToEpoch ($); +sub EpochToDate ($); + +sub ymdhms { + my ($time) = @_; + + $time ||= time; + + my ( + $sec, + $min, + $hour, + $mday, + $mon, + $year, + $wday, + $yday, + $isdst + ) = localtime ($time); + + # Adjust month + $mon++; + + # Adjust year + $year += 1900; + + # Zero preface month, day, hour and minute + $mon = '0' . $mon if $mon < 10; + $mday = '0' . $mday if $mday < 10; + $hour = '0' . $hour if $hour < 10; + $min = '0' . $min if $min < 10; + $sec = '0' . $sec if $sec < 10; + + return $year, $mon, $mday, $hour, $min, $sec; +} # ymdhms + +sub julian ($$$) { + my ($year, $month, $day) = @_; + + my $days = 0; + my $m = 1; + + foreach (@months) { + last if $m >= $month; + $m++; + $days += $_; + } # foreach + + return $days + $day; +} # julian + +sub _is_leap_year ($) { + my ($year) = @_; + + return 0 if $year % 4; + return 1 if $year % 100; + return 0 if $year % 400; + + return 1; +} # _is_leap_year + +sub Add ($%) { + my ($datetime, %parms) = @_; + +=pod + +=head2 Add ($datetime, %parms) + +Add to a datetime + +Parameters: + +=for html
    + +=over + +=item $datetime + +Datetime in SQLDatetime format to manipulate. + +=item %parms + +Hash of parms. Acceptable values are of the following format: + + seconds => $seconds + minutes => $minutes + hours => $hours + days => $days + month => $month + +Note that month will simply increment the month number, adjusting for overflow +of year if appropriate. Therefore a date of 2/28/2001 would increase by 1 month +to yield 3/28/2001. And, unfortunately, an increase of 1 month to 1/30/2011 +would incorrectly yeild 2/30/2011! + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item New datetime + +=back + +=for html
    + +=cut + + my @validKeys = ( + 'seconds', + 'minutes', + 'hours', + 'days', + 'months', + ); + + foreach (keys %parms) { + unless (InArray ($_, @validKeys)) { + croak "Invalid key in DateUtils::Add: $_"; + } # unless + } # foreach + + my $epochTime = DateToEpoch $datetime; + + my $amount = 0; + + $parms{seconds} ||= 0; + $parms{minutes} ||= 0; + $parms{hours} ||= 0; + $parms{days} ||= 0; + + $amount += $parms{days} * $SECS_IN_DAY; + $amount += $parms{hours} * $SECS_IN_HOUR; + $amount += $parms{minutes} * $SECS_IN_MIN; + $amount += $parms{seconds}; + + $epochTime += $amount; + + $datetime = EpochToDate $epochTime; + + if ($parms{month}) { + my $years = $parms{month} / 12; + my $months = $parms{month} % 12; + + my $month = substr $datetime, 5, 2; + + $years += ($month + $months) / 12; + substr ($datetime, 5, 2) = ($month + $months) % 12; + + substr ($datetime, 0, 4) = substr ($datetime, 0, 4) + $years; + } # if + + return $datetime; +} # Add + +sub Age ($) { + my ($timestamp) = @_; + +=pod + +=head2 Age ($timestamp) + +Determines how old something is given a timestamp + +Parameters: + +=for html
    + +=over + +=item $timestamp: + +Timestamp to age from (Assumed to be earlier than today) + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Number of days between $timestamp and today + +=back + +=for html
    + +=cut + + my $today = Today2SQLDatetime; + my $today_year = substr $today, 0, 4; + my $month = substr $today, 5, 2; + my $day = substr $today, 8, 2; + my $today_days = julian $today_year, $month, $day; + + my $timestamp_year = substr $timestamp, 0, 4; + $month = substr $timestamp, 5, 2; + $day = substr $timestamp, 8, 2; + my $timestamp_days = julian $timestamp_year, $month, $day; + + if ($timestamp_year > $today_year or + ($timestamp_days > $today_days and $timestamp_year == $today_year)) { + return; + } else { + my $leap_days = 0; + + for (my $i = $timestamp_year; $i < $today_year; $i++) { + + $leap_days++ if $i % 4 == 0; + } # for + + $today_days += 365 * ($today_year - $timestamp_year) + $leap_days; + return $today_days - $timestamp_days; + } # if +} # Age + +sub Compare ($$) { + my ($date1, $date2) = @_; + +=pod + +=head2 Compare ($date2, $date2) + +Compares two datetimes returning -1 if $date1 < $date2, 0 if equal or 1 if +$date1 > $date2 + +Parameters: + +=for html
    + +=over + +=item $date1 + +Date 1 to compare + +=item $date2 + +Date 2 to compare + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item -1 if $date1 < $date2, 0 if equal or 1 if $date1 > $date2 + +=back + +=for html
    + +=cut + + return DateToEpoch ($date1) <=> DateToEpoch ($date2); +} # Compare + +sub DateToEpoch ($) { + my ($date) = @_; + +=pod + +=head2 DateToEpoch ($datetime) + +Converts a datetime to epoch + +Parameters: + +=for html
    + +=over + +=item $datetime + +Datetime to convert to an epoch + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $epoch + +=back + +=for html
    + +=cut + + my $year = substr $date, 0, 4; + my $month = substr $date, 5, 2; + my $day = substr $date, 8, 2; + my $hour = substr $date, 11, 2; + my $minute = substr $date, 14, 2; + my $seconds = substr $date, 17, 2; + + my $days; + + for (my $i = 1970; $i < $year; $i++) { + $days += _is_leap_year ($i) ? 366 : 365; + } # for + + my @monthDays = ( + 0, + 31, + 59, + 90, + 120, + 151, + 181, + 212, + 243, + 273, + 304, + 334, + ); + + $days += $monthDays[$month - 1]; + + $days++ + if _is_leap_year ($year) and $month > 2; + + $days += $day - 1; + + return ($days * $SECS_IN_DAY) + + ($hour * $SECS_IN_HOUR) + + ($minute * $SECS_IN_MIN) + + $seconds; +} # DateToEpoch + +sub EpochToDate ($) { + my ($epoch) = @_; + +=pod + +=head2 EpochToDate ($epoch) + +Converts an epoch to a datetime + +Parameters: + +=for html
    + +=over + +=item $epoch + +Epoch to convert to a datetime + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $datetime + +=back + +=for html
    + +=cut + + my $year = 1970; + my ($month, $day, $hour, $minute, $seconds); + my $leapYearSecs = 366 * $SECS_IN_DAY; + my $yearSecs = $leapYearSecs - $SECS_IN_DAY; + + while () { + my $amount = _is_leap_year ($year) ? $leapYearSecs : $yearSecs; + + last + if $amount > $epoch; + + $epoch -= $amount; + $year++; + } # while + + my $leapYearAdjustment = _is_leap_year ($year) ? 1 : 0; + + if ($epoch >= (334 + $leapYearAdjustment) * $SECS_IN_DAY) { + $month = '12'; + $epoch -= (334 + $leapYearAdjustment) * $SECS_IN_DAY; + } elsif ($epoch >= (304 + $leapYearAdjustment) * $SECS_IN_DAY) { + $month = '11'; + $epoch -= (304 + $leapYearAdjustment) * $SECS_IN_DAY; + } elsif ($epoch >= (273 + $leapYearAdjustment) * $SECS_IN_DAY) { + $month = '10'; + $epoch -= (273 + $leapYearAdjustment) * $SECS_IN_DAY; + } elsif ($epoch >= (243 + $leapYearAdjustment) * $SECS_IN_DAY) { + $month = '09'; + $epoch -= (243 + $leapYearAdjustment) * $SECS_IN_DAY; + } elsif ($epoch >= (212 + $leapYearAdjustment) * $SECS_IN_DAY) { + $month = '08'; + $epoch -= (212 + $leapYearAdjustment) * $SECS_IN_DAY; + } elsif ($epoch >= (181 + $leapYearAdjustment) * $SECS_IN_DAY) { + $month = '07'; + $epoch -= (181 + $leapYearAdjustment) * $SECS_IN_DAY; + } elsif ($epoch >= (151 + $leapYearAdjustment) * $SECS_IN_DAY) { + $month = '06'; + $epoch -= (151 + $leapYearAdjustment) * $SECS_IN_DAY; + } elsif ($epoch >= (120 + $leapYearAdjustment) * $SECS_IN_DAY) { + $month = '05'; + $epoch -= (120 + $leapYearAdjustment) * $SECS_IN_DAY; + } elsif ($epoch >= (90 + $leapYearAdjustment) * $SECS_IN_DAY) { + $month = '04'; + $epoch -= (90 + $leapYearAdjustment) * $SECS_IN_DAY; + } elsif ($epoch >= (59 + $leapYearAdjustment) * $SECS_IN_DAY) { + $month = '03'; + $epoch -= (59 + $leapYearAdjustment) * $SECS_IN_DAY; + } elsif ($epoch >= 31 * $SECS_IN_DAY) { + $month = '02'; + $epoch -= 31 * $SECS_IN_DAY; + } else { + $month = '01'; + } # if + + $day = int (($epoch / $SECS_IN_DAY) + 1); + $epoch = $epoch % $SECS_IN_DAY; + $hour = int ($epoch / $SECS_IN_HOUR); + $epoch = $epoch % $SECS_IN_HOUR; + $minute = int ($epoch / $SECS_IN_MIN); + $seconds = $epoch % $SECS_IN_MIN; + + $day = "0$day" if $day < 10; + $hour = "0$hour" if $hour < 10; + $minute = "0$minute" if $minute < 10; + $seconds = "0$seconds" if $seconds < 10; + + return "$year-$month-$day $hour:$minute:$seconds"; +} # EpochToDate + +sub UTCTime ($) { + my ($datetime) = @_; + +=pod + +=head2 UTCTime ($epoch) + +Converts an epoch to UTC Time + +Parameters: + +=for html
    + +=over + +=item $epoch + +Epoch to convert to a datetime + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item $datetime + +=back + +=for html
    + +=cut + + my @localtime = localtime; + my ($sec, $min, $hour, $mday, $mon, $year) = gmtime ( + DateToEpoch ($datetime) - (timegm (@localtime) - timelocal (@localtime)) + ); + + $year += 1900; + $mon++; + + $sec = '0' . $sec if $sec < 10; + $min = '0' . $min if $min < 10; + $hour = '0' . $hour if $hour < 10; + $mon = '0' . $mon if $mon < 10; + $mday = '0' . $mday if $mday < 10; + + return "$year-$mon-${mday}T$hour:$min:${sec}Z"; +} # UTCTime + +sub UTC2Localtime ($) { + my ($utcdatetime) = @_; + + # If the field does not look like a UTC time then just return it. + return $utcdatetime unless $utcdatetime =~ /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z/; + + $utcdatetime =~ s/T/ /; + $utcdatetime =~ s/Z//; + + my @localtime = localtime; + + return EpochToDate ( + DateToEpoch ($utcdatetime) + (timegm (@localtime) - timelocal (@localtime)) + ); +} # UTC2Localtime + +sub FormatDate ($) { + my ($date) = @_; + +=pod + +=head2 FormatDate ($date) + +Formats date + +Parameters: + +=for html
    + +=over + +=item $date: + +Date in YYYYMMDD + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Returns a date in MM/DD/YYYY format + +=back + +=for html
    + +=cut + + return substr ($date, 4, 2) + . "/" + . substr ($date, 6, 2) + . "/" + . substr ($date, 0, 4); +} # FormatDate + +sub FormatTime ($) { + my ($time) = @_; + +=pod + +=head2 FormatTime ($time) + +Formats Time + +Parameters: + +=for html
    + +=over + +=item $time: + +Time in in HH:MM format (24 hour format) + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Time in HH:MM [Am|Pm] format + +=back + +=for html
    + +=cut + + my $hours = substr $time, 0, 2; + my $minutes = substr $time, 3, 2; + my $AmPm = $hours > 12 ? "Pm" : "Am"; + + $hours = $hours - 12 if $hours > 12; + + return "$hours:$minutes $AmPm"; +} # FormatTime + +sub MDY (;$) { + my ($time) = @_; + +=pod + +=head2 MDY ($time) + +Returns MM/DD/YYYY for $time + +Parameters: + +=for html
    + +=over + +=item $time: + +Time in Unix time format (Default: current time) + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Date in MM/DD/YYYY + +=back + +=for html
    + +=cut + + my ($year, $mon, $mday) = ymdhms $time; + + return "$mon/$mday/$year"; +} # MDY + +sub SQLDatetime2UnixDatetime ($) { + my ($sqldatetime) = @_; + +=pod + +=head2 SQLDatetime2UnixDatetime ($sqldatetime) + +Converts an SQL formatted date to a Unix (localtime) formatted date) + +Parameters: + +=for html
    + +=over + +=item $sqldatetime: + +Date and time stamp in SQL format + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Returns a Unix formated date and time (a la localtime) + +=back + +=for html
    + +=cut + + my %months = ( + "01" => "Jan", + "02" => "Feb", + "03" => "Mar", + "04" => "Apr", + "05" => "May", + "06" => "Jun", + "07" => "Jul", + "08" => "Aug", + "09" => "Sep", + "10" => "Oct", + "11" => "Nov", + "12" => "Dec" + ); + + my $year = substr $sqldatetime, 0, 4; + my $month = substr $sqldatetime, 5, 2; + my $day = substr $sqldatetime, 8, 2; + my $time = FormatTime (substr $sqldatetime, 11); + + return $months{$month} . " $day, $year \@ $time"; +} # SQLDatetime2UnixDatetime + +sub SubtractDays ($$) { + my ($timestamp, $nbr_of_days) = @_; + +=pod + +=head2 SubtractDays ($timestamp, $nbr_of_days) + +Subtracts $nbr_of_days from $timestamp + +Parameters: + +=for html
    + +=over + +=item $timestamp: + +Timestamp to subtract days from + +=back + +=over + +=item $nbr_of_days: + +=back + +Number of days to subtract from $timestamp + +=for html
    + +Returns: + +=for html
    + +=over + +=item SQL format date $nbr_of_days ago + +=back + +=for html
    + +=cut + + my $year = substr $timestamp, 0, 4; + my $month = substr $timestamp, 5, 2; + my $day = substr $timestamp, 8, 2; + + # Convert to Julian + my $days = julian $year, $month, $day; + + # Subtract $nbr_of_days + $days -= $nbr_of_days; + + # Compute $days_in_year + my $days_in_year; + + # Adjust if crossing year boundary + if ($days <= 0) { + $year--; + $days_in_year = (($year % 4) == 0) ? 366 : 365; + $days = $days_in_year + $days; + } else { + $days_in_year = (($year % 4) == 0) ? 366 : 365; + } # if + + # Convert back + $month = 0; + + while ($days > 28) { + # If remaining days is less than the current month then last + last if ($days <= $months[$month]); + + # Subtract off the number of days in this month + $days -= $months[$month++]; + } # while + + # Prefix month with 0 if necessary + $month++; + if ($month < 10) { + $month = "0" . $month; + } # if + + # Prefix days with 0 if necessary + if ($days == 0) { + $days = "01"; + } elsif ($days < 10) { + $days = "0" . $days; + } # if + + return $year . "-" . $month . "-" . $days . substr $timestamp, 10; +} # SubtractDays + +sub Today2SQLDatetime () { + +=pod + +=head2 Today2SQLDatetime ($datetime) + +Returns today's date in an SQL format + +Parameters: + +=for html
    + +=over + +=item None + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item SQL formated time stamp for today + +=back + +=for html
    + +=cut + + return UnixDatetime2SQLDatetime (scalar (localtime)); +} # Today2SQLDatetime + +sub UnixDatetime2SQLDatetime ($) { + my ($datetime) = @_; + +=pod + +=head2 UnixDatetime2SQLDatetime ($datetime) + +Converts a Unix (localtime) date/time stamp to an SQL formatted +date/time stamp + +Parameters: + +=for html
    + +=over + +=item $datetime: + +Unix formated date time stamp + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item SQL formated time stamp + +=back + +=for html
    + +=cut + + my $orig_datetime = $datetime; + my %months = ( + Jan => '01', + Feb => '02', + Mar => '03', + Apr => '04', + May => '05', + Jun => '06', + Jul => '07', + Aug => '08', + Sep => '09', + Oct => '10', + Nov => '11', + Dec => '12', + ); + + # Some mailers neglect to put the leading day of the week field in. + # Check for this and compensate. + my $dow = substr $datetime, 0, 3; + + if ($dow ne 'Mon' and + $dow ne 'Tue' and + $dow ne 'Wed' and + $dow ne 'Thu' and + $dow ne 'Fri' and + $dow ne 'Sat' and + $dow ne 'Sun') { + $datetime = 'XXX, ' . $datetime; + } # if + + # Some mailers have day before month. We need to correct this + my $day = substr $datetime, 5, 2; + + if ($day =~ /\d /) { + $day = '0' . (substr $day, 0, 1); + $datetime = (substr $datetime, 0, 5) . $day . (substr $datetime, 6); + } # if + + if ($day !~ /\d\d/) { + $day = substr $datetime, 8, 2; + } # if + + # Check for 1 digit date + if ((substr $day, 0, 1) eq ' ') { + $day = '0' . (substr $day, 1, 1); + $datetime = (substr $datetime, 0, 8) . $day . (substr $datetime, 10); + } elsif ((substr $day, 1, 1) eq ' ') { + $day = '0' . (substr $day, 0, 1); + $datetime = (substr $datetime, 0, 8) . $day . (substr $datetime, 9); + } # if + + my $year = substr $datetime, 20, 4; + + if ($year !~ /\d\d\d\d/) { + $year = substr $datetime, 12, 4; + if ($year !~ /\d\d\d\d/) { + $year = substr $datetime, 12, 2; + } #if + } # if + + # Check for 2 digit year. Argh! + if (length $year == 2 or (substr $year, 2, 1) eq ' ') { + $year = '20' . (substr $year, 0, 2); + $datetime = (substr $datetime, 0, 12) . '20' . (substr $datetime, 12); + } # if + + my $month_name = substr $datetime, 4, 3; + + unless ($months{$month_name}) { + $month_name = substr $datetime, 8, 3; + } # unless + + my $month = $months{$month_name}; + my $time = substr $datetime, 11, 8; + + if ($time !~ /\d\d:\d\d:\d\d/) { + $time = substr $datetime, 17, 8 + } # if + + unless ($year) { + warning "Year undefined for $orig_datetime\nReturning today's date"; + return Today2SQLDatetime; + } # unless + + unless ($month) { + warning "Month undefined for $orig_datetime\nReturning today's date"; + return Today2SQLDatetime; + } # unless + + unless ($day) { + warning "Day undefined for $orig_datetime\nReturning today's date"; + return Today2SQLDatetime; + } # unless + + unless ($time) { + warning "Time undefined for $orig_datetime\nReturning today's date"; + return Today2SQLDatetime; + } # unless + + return "$year-$month-$day $time"; +} # UnixDatetime2SQLDatetime + +sub YMD (;$) { + my ($time) = @_; + +=pod + +=head2 YMD ($time) + +Returns the YMD in a format of YYYYMMDD + +Parameters: + +=for html
    + +=over + +=item $time: + +Time to convert to YYYYMMDD (Default: Current time) + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Date in YYYYMMDD format + +=back + +=for html
    + +=cut + + my ($year, $mon, $mday) = ymdhms $time; + + return "$year$mon$mday"; +} # YMD + +sub YMDHM (;$) { + my ($time) = @_; + +=pod + +=head2 YMDHM ($time) + +Returns the YMD in a format of YYYYMMDD@HH:MM + +Parameters: + +=for html
    + +=over + +=item $time: + +Time to convert to YYYYMMDD@HH:MM (Default: Current time) + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Date in YYYYMMDD@HH:MM format + +=back + +=for html
    + +=cut + + my ($year, $mon, $mday, $hour, $min) = ymdhms $time; + + return "$year$mon$mday\@$hour:$min"; +} # YMDHM + +sub YMDHMS (;$) { + my ($time) = @_; + +=pod + +=head2 YMDHMS ($time) + +Returns the YMD in a format of YYYYMMDD@HH:MM:SS + +Parameters: + +=for html
    + +=over + +=item $time: + +Time to convert to YYYYMMDD@HH:MM:SS (Default: Current time) + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Date in YYYYMMDD@HH:MM:SS format + +=back + +=for html
    + +=cut + + my ($year, $mon, $mday, $hour, $min, $sec) = ymdhms $time; + + return "$year$mon$mday\@$hour:$min:$sec"; +} # YMDHMS + +sub timestamp (;$) { + my ($time) = @_; + +=pod + +=head2 timestamp ($time) + +Returns the YMD in a format of YYYYMMDD_HHMM + +Parameters: + +=for html
    + +=over + +=item $time: + +Time to convert to YYYYMMDD_HHMMSS (Default: Current time) + +=for html
    + +Returns: + +=for html
    + +=over + +=item Date in YYYYMMDD_HHMMSS format + +=back + +=for html
    + +=cut + + my ($year, $mon, $mday, $hour, $min, $sec) = ymdhms $time; + + return "$year$mon${mday}_$hour$min$sec"; +} # timestamp + +1; + +=head2 DEPENDENCIES + +=head3 Perl Modules + +=for html

    Display

    + +=head1 INCOMPATABILITIES + +None yet... + +=head1 BUGS AND LIMITATIONS + +There are no known bugs in this module. + +Please report problems to Andrew DeFaria . + +=head1 LICENSE AND COPYRIGHT + +This Perl Module is freely available; you can redistribute it and/or +modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version. + +This Perl Module is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License (L) for more +details. + +You should have received a copy of the GNU General Public License +along with this Perl Module; if not, write to the Free Software Foundation, +Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +reserved. + +=cut diff --git a/lib/Display.pm b/lib/Display.pm new file mode 100644 index 0000000..f73a54b --- /dev/null +++ b/lib/Display.pm @@ -0,0 +1,1264 @@ +=pod + +=head1 NAME $RCSfile: Display.pm,v $ + +Simple and consistant display routines for Perl + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.45 $ + +=item Created + +Fri Mar 12 10:17:44 PST 2004 + +=item Modified + +$Date: 2013/05/30 15:48:06 $ + +=back + +=head1 SYNOPSIS + +This module seeks to make writing output simpler and more consistant. Messages +are classified as display (informational - always displayed), verbose (written +only if $verbose is set) and debug (written only if $debug is set). There are +also routines for error(s) and warning(s) which support optional parameters for +error number and warning number. If error number is specified then the process +is also terminated. + + display "message"; + verbose "$n records processed"; + verbose2 "Processing record #$recno"; + warning "Unable to find record", 1; + debug "Reached here..."; + error "Can't continue", 2; + +=head2 DESCRIPTION + +This module implements several routines to provide and easy and +consistant interface to writing output in Perl. Perl has lots of ways +to do such things but these methods seek to be self explainitory and +to provide convenient parameters and defaults so as to make coding +easier. + +There are also some other routines, i.e. get_debug, that will return +$debug in case you want to execute other Perl code only when +debugging: + + if (get_debug) { + foreach (@output_line) { + debug $_; + } # foreach + } # if + +By default these routines write lines complete with the terminating +"\n". I find that this is most often what you are doing. There are +corresponding _nolf versions for display and verbose in case +you wish to not terminate lines. Or use the new say function. + +Also, routines like display support a file handle parameter if you +wish to say display into a file - Default STDOUT. + +Both version and debug support levels and have convienence functions: +verbose1, debug2. Three levels of conienence functions are supplied +although an unlimited amount can be supported directly through +verbose/debug. See documentaton for those functions for details. + +=head1 ROUTINES + +The following routines are exported: + +=cut + +package Display; + +use strict; +use warnings; + +use base 'Exporter'; + +use FindBin; +use File::Spec; +use Term::ANSIColor qw(color); +use Carp; +use Config; + +our @EXPORT = qw ( + debug debug1 debug2 debug3 + display + display_err + display_error + display_nolf + error + get_debug + get_me + get_trace + get_verbose + say + set_debug + set_me + set_trace + set_verbose + trace + trace_enter + trace_exit + verbose verbose1 verbose2 verbose3 + verbose_nolf + warning +); + +my ($me, $verbose, $debug, $trace); + +BEGIN { + $me = $FindBin::Script; + $me =~ s/\.pl$//; + + $verbose = $ENV{VERBOSE}; + $debug = $ENV{DEBUG}; + $trace = $ENV{TRACE}; +} # BEGIN + +sub display_err ($;$$); + +sub debug ($;$$$) { + my ($msg, $handle, $nolinefeed, $level) = @_; + +=pod + +=head2 debug[1-3] ($msg, $handle, $nolinefeed, $level) + +Write $msg to $handle (default STDERR) with a "\n" unless $nolinefeed +is defined. Messages are written only if written if $debug is set and +=< $level. $level defaults to 1. + +debug1, debug2 and debug3 are setup as convienence functions that are +equivalent to calling debug with $level set to 1, 2 or 3 respectively + +Parameters: + +=for html
    + +=over + +=item $msg: + +Message to display + +=item $handle: + +File handle to display to (Default: STDERR) + +=item $nolinefeed: + +If defined no linefeed is displayed at the end of the message. + +=item $level + +If defined, if $level =< $debug then the debug message is displayed. + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Nothing + +=back + +=for html
    + +=cut + + return + unless $debug; + + return + if $debug == 0; + + $level ||= 1; + $msg ||= ''; + + if (($handle and -t $handle) or (-t *STDERR)) { + $msg = color ('cyan') + . $me + . color ('reset') + . ": " + . color ('magenta') + . "DEBUG" + . color ('reset') + . ": $msg"; + } else { + $msg = "$me: DEBUG: $msg"; + } # if + + display_err $msg, $handle, $nolinefeed if $debug and $level <= $debug; + + return; +} # debug + +sub debug1 ($;$$) { + my ($msg, $handle, $nolinefeed) = @_; + + debug $msg, $handle, $nolinefeed, 1; + + return; +} # debug1 + +sub debug2 ($;$$) { + my ($msg, $handle, $nolinefeed) = @_; + + debug $msg, $handle, $nolinefeed, 2; + + return; +} # debug1 + +sub debug3 ($;$$) { + my ($msg, $handle, $nolinefeed) = @_; + + debug $msg, $handle, $nolinefeed, 2; + + return; +} # debug1 + +sub display (;$$$) { + my ($msg, $handle, $nolinefeed) = @_; + +=pod + +=head2 display ($msg, $handle, $nolinefeed) + +Write $msg to $handle (default STDOUT) with a "\n" unless $nolinefeed +is defined. + +Parameters: + +=for html
    + +=over + +=item $msg: + +Message to display + +=item $handle: + +File handle to display to (Default: STDOUT) + +=item $nolinefeed: + +If defined no linefeed is displayed at the end of the message. + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Nothing + +=back + +=for html
    + +=cut + + $msg ||= ''; + $handle = *STDOUT unless $handle; + + print $handle $msg; + print $handle "\n" unless $nolinefeed; + + return; +} # display + +sub display_err ($;$$) { + my ($msg, $handle, $nolinefeed) = @_; + +=pod + +=head2 display_err ($msg, $handle, $nolinefeed) + +Displays $msg to STDERR + +Parameters: + +=for html
    + +=over + +=item $msg: + +Message to display + +=item $handle: + +File handle to display to (Default: STDOUT) + +=item $nolinefeed: + +If defined no linefeed is displayed at the end of the message. + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Nothing + +=back + +=for html
    + +=cut + + $msg ||= ''; + $handle = *STDERR if !$handle; + + print $handle $msg; + print $handle "\n" if !$nolinefeed; + + return; +} # display_err + +sub display_error ($;$$$) { + my ($msg, $errno, $handle, $nolinefeed) = @_; + +=pod + +=head2 display_error ($msg, $errno, $handle, $nolinefeed) + +Displays colorized $msg to STDERR + +Parameters: + +=for html
    + +=over + +=item $msg: + +Message to display + +=item $errno + +Error no to display (if any) + +=item $handle: + +File handle to display to (Default: STDOUT) + +=item $nolinefeed: + +If defined no linefeed is displayed at the end of the message. + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Nothing + +=back + +=for html
    + +=cut + + $msg ||= ''; + + unless ($errno) { + if (($handle and -t $handle) or (-t *STDERR) and ($Config{perl} ne 'ratlperl')) { + $msg = color ('cyan') + . $me + . color ('reset') + . ": " + . color ('red') + . "ERROR" + . color ('reset') + . ": $msg"; + } else { + $msg = "$me: ERROR: $msg"; + } # if + } else { + if (($handle and -t $handle) or (-t *STDERR) and ($Config{perl} ne 'ratlperl')) { + $msg = color ('cyan') + . $me + . color ('reset') + . ": " + . color ('red') + . "ERROR #$errno" + . color ('reset') + . ": $msg"; + } else { + $msg = "$me: ERROR #$errno: $msg"; + } # if + } # if + + display_err $msg, $handle, $nolinefeed; + + return; +} # display_error + +sub display_nolf ($;$) { + my ($msg, $handle) = @_; + +=pod + +=head2 display_nolf ($msg, $handle) + +Equivalent of display ($msg, $handle, "nolf"). + +Parameters: + +=for html
    + +=over + +=item $msg: + +Message to display + +=item $handle: + +File handle to display to (Default: STDOUT) + +=back + +=for html
    + +Returns: + +=for html
    + +=over + +=item Nothing + +=back + +=for html
    + +=cut + + display $msg, $handle, "nolf"; + + return; +} # display_nolf + +sub error ($;$$$) { + my ($msg, $errno, $handle, $nolinefeed) = @_; + +=pod + +=head2 error ($msg, $errno, $handle, $nolinefeed) + +Write $msg to $handle (default STDERR) with a "\n" unless $nolinefeed +is defined. Preface message with " + + +
    +

    MAPS +Reports

    +

    +
    +
    + +

    Reports

    +
      +
    • Returned messages by domain
    • +
    • Recent Activity
      +
    • +
    • Space Usage
    • +
    +
    + +
    + + diff --git a/maps/SignupForm.html b/maps/SignupForm.html new file mode 100644 index 0000000..a45401e --- /dev/null +++ b/maps/SignupForm.html @@ -0,0 +1,159 @@ + + + + + MAPS: Signup + + + + + + + +
    +

    MAPS +Spam Elimination System

    +

    Sign up for MAPS

    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Username:Specify a username to log into MAPS
    +
    Full name:Specify your full name
    +
    Email Address:Your email address is used if you are a Tag + & Forward user. This is the email address that MAPS  + will forward your email to after it tags it. This email address is + also used in case you forget your password so that we can email you + your password.
    Password:Choose a password greater than 6 characters
    +
    Repeat Password:Re-enter your password so we can be sure + you typed it correctly
    +
    MAPSPOP User: Yes + NoMAPSPOP users need to download MAPSPOP. + See Using MAPSPOP + for more information.
    +
    Keep history for:
    +
    + +  days This specifies how many days of history + that MAPS will keep before discarding returned messages.
    +
    Dates in Stats Page
    +
    + + This specifies how many days are displayed + in the MAPS Stats Page.
    +
    Entries per page:
    +
    + + This specifies how many entries are + displayed per page in the online MAPS Reports.
    +
    Tag & Forward:Yes + NoTag and Forward means that MAPS + will not filter or save any email for you. Instead it will simply + add an X-MAPS header to your email indicating what MAPS would have + done with the email. This allows you to filter your email in your + local email client.
    +
    + +

    +
    + + + + + + diff --git a/maps/adm/index.html b/maps/adm/index.html new file mode 100755 index 0000000..8b29a80 --- /dev/null +++ b/maps/adm/index.html @@ -0,0 +1,93 @@ + + + + MAPS: Administration + + + + + +
    +

    MAPS +Administration

    +
    +
    +
    +
    Welcome Andrew
    + + +
    +

    Today's Activity

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Processed3n/a
    Returned3100.0%
    Nulllist0 0.0%
    Whitelist0 0.0%
    Blacklist0 0.0%
    Error0 0.0%
    Mailloop0n/a
    Registered0n/a
    +
    +
    +
      +
    • Show Users
    • +
    • Delete a User
    • +
    • Space
    • +
    + +
    + + diff --git a/maps/bin/MAPS.pm b/maps/bin/MAPS.pm new file mode 100644 index 0000000..824a2cb --- /dev/null +++ b/maps/bin/MAPS.pm @@ -0,0 +1,792 @@ +#!/usr/bin/perl +################################################################################# +# +# File: $RCSfile: MAPS.pm,v $ +# Revision: $Revision: 1.1 $ +# Description: Main module for Mail Authentication and Permission System (MAPS) +# Author: Andrew@DeFaria.com +# Created: Fri Nov 29 14:17:21 2002 +# Modified: $Date: 2013/06/12 14:05:47 $ +# Language: perl +# +# (c) Copyright 2000-2006, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +package MAPS; + +use strict; + +use FindBin; + +use MAPSDB; +use MAPSLog; +use MAPSFile; +use MAPSUtil; +use MIME::Entity; + +use vars qw (@ISA @EXPORT); +use Exporter; + +@ISA = qw (Exporter); + +@EXPORT = qw ( + Add2Blacklist + Add2Nulllist + Add2Whitelist + AddEmail + AddList + AddUser + AddUserOptions + Blacklist + CleanEmail + CleanLog + CleanList + CountMsg + Decrypt + DeleteEmail + DeleteList + DeleteLog + Encrypt + FindEmail + FindList + FindLog + FindUser + ForwardMsg + GetContext + GetEmail + GetList + GetLog + GetUser + GetUserOptions + ListLog + ListUsers + Login + Nulllist + OnBlacklist + OnNulllist + OnWhitelist + OptimizeDB + ReadMsg + ResequenceList + ReturnList + ReturnListEntry + ReturnMsg + ReturnMessages + ReturnSenders + SaveMsg + SearchEmails + SetContext + Space + UpdateList + UpdateUser + UpdateUserOptions + UserExists + Whitelist +); + +my $mapsbase = "$FindBin::Bin/.."; + +# Forwards +sub Add2Blacklist; +sub Add2Nulllist; +sub Add2Whitelist; +sub AddEmail; +sub AddList; +sub AddUser; +sub AddUserOptions; +sub Blacklist; +sub CleanEmail; +sub CleanLog; +sub CountMsg; +sub Decrypt; +sub DeleteEmail; +sub DeleteList; +sub DeleteLog; +sub Encrypt; +sub FindEmail; +sub FindList; +sub FindLog; +sub FindUser; +sub ForwardMsg; +sub GetContext; +sub GetEmail; +sub GetList; +sub GetLog; +sub GetUser; +sub GetUserOptions; +sub Login; +sub Nulllist; +sub OnBlacklist; +sub OnNulllist; +sub OnWhitelist; +sub OptimizeDB; +sub ReadMsg; +sub ResequenceList; +sub ReturnList; +sub ReturnListEntry; +sub ReturnMsg; +sub ReturnMessages; +sub ReturnSenders; +sub SaveMsg; +sub SearchEmails; +sub SendMsg; +sub SetContext; +sub Space; +sub UpdateList; +sub UpdateUser; +sub UpdateUserOptions; +sub UserExists; +sub Whitelist; + +BEGIN { + my $MAPS_username = "mapsadmin"; + my $MAPS_password = "mapsadmin"; + + OpenDB $MAPS_username, $MAPS_password; +} # BEGIN + +END { + CloseDB; +} # END + +sub Add2Blacklist { + # Add2Blacklist will add an entry to the blacklist + my ($sender, $userid, $comment) = @_; + + # First SetContext to the userid whose black list we are adding to + MAPSDB::SetContext $userid; + + # Add to black list + AddList "black", $sender, 0, $comment; + + # Log that we black listed the sender + Info "Added $sender to " . ucfirst $userid . "'s black list"; + + # Delete old emails + my $count = DeleteEmail $sender; + + # Log out many emails we managed to remove + Info "Removed $count emails from $sender" +} # Add2Blacklist + +sub Add2Nulllist ($$;$) { + # Add2Nulllist will add an entry to the nulllist + my ($sender, $userid, $comment) = @_; + + # First SetContext to the userid whose null list we are adding to + MAPSDB::SetContext $userid; + + # Add to null list + AddList "null", $sender, 0, $comment; + + # Log that we null listed the sender + Info "Added $sender to " . ucfirst $userid . "'s null list"; + + # Delete old emails + my $count = DeleteEmail $sender; + + # Log out many emails we managed to remove + Info "Removed $count emails from $sender" +} # Add2Nulllist + +sub Add2Whitelist ($$;$) { + # Add2Whitelist will add an entry to the whitelist + my ($sender, $userid, $comment) = @_; + + # First SetContext to the userid whose white list we are adding to + MAPSDB::SetContext $userid; + + # Add to white list + AddList 'white', $sender, 0, $comment; + + # Log that we registered a user + Logmsg "registered", $sender, "Registered new sender"; + + # Check to see if there are any old messages to deliver + my $handle = FindEmail $sender; + + my ($dbsender, $subject, $timestamp, $message); + + # Deliver old emails + my $messages = 0; + my $return_status = 0; + + while (($userid, $dbsender, $subject, $timestamp, $message) = GetEmail $handle) { + last + unless $userid; + + $return_status = Whitelist $sender, $message; + + last + if $return_status; + + $messages++; + } # while + + # Done with $handle + $handle->finish; + + # Return if we has a problem delivering email + return $return_status + if $return_status; + + # Remove delivered messages. + DeleteEmail $sender; + + return $messages; +} # Add2Whitelist + +sub AddEmail ($$$) { + my ($sender, $subject, $data) = @_; + + MAPSDB::AddEmail $sender, $subject, $data; +} # AddEmail + +sub AddList ($$$;$) { + my ($listtype, $pattern, $sequence, $comment) = @_; + + MAPSDB::AddList $listtype, $pattern, $sequence, $comment, CountMsg $pattern; +} # AddList + +sub AddUser ($$$$) { + my ($userid, $realname, $email, $password) = @_; + + return MAPSDB::AddUser $userid, $realname, $email, $password; +} # AddUser + +sub AddUserOptions ($%) { + my ($userid, %options) = @_; + + my $status; + + foreach (keys (%options)) { + $status = MAPSDB::AddUserOption $userid, $_, $options{$_}; + last if $status ne 0; + } # foreach + + return $status; +} # AddUserOptions + +sub Blacklist ($$$@) { + # Blacklist will send a message back to the $sender telling them that + # they've been blacklisted. Currently we save a copy of the message. + # In the future we should just disregard the message. + my ($sender, $sequence, $hit_count, @msg) = @_; + + # Check to see if this sender has already emailed us. + my $msg_count = CountMsg $sender; + + if ($msg_count lt 5) { + # Bounce email + SendMsg ($sender, "Your email has been discarded by MAPS", "$mapsbase/blacklist.html", @msg); + Logmsg "blacklist", $sender, "Sent blacklist reply"; + } else { + Logmsg "mailloop", $sender, "Mail loop encountered"; + } # if + + RecordHit "black", $sequence, ++$hit_count if $sequence; +} # Blacklist + +sub CleanEmail ($) { + my ($timestamp) = @_; + + MAPSDB::CleanEmail $timestamp; +} # CleanEmail + +sub CleanLog ($) { + my ($timestamp) = @_; + + MAPSDB::CleanLog $timestamp; +} # CleanLog + +sub CleanList ($;$) { + my ($timestamp, $listtype) = @_; + + MAPSDB::CleanList $timestamp, $listtype; +} # CleanList + +sub CountMsg ($) { + my ($sender) = @_; + + return MAPSDB::CountMsg $sender; +} # CountMsg + +sub Decrypt ($$) { + my ($password, $userid) = @_; + + return MAPSDB::Decrypt $password, shift; +} # Decrypt + +sub DeleteEmail ($) { + my ($sender) = @_; + + return MAPSDB::DeleteEmail $sender; +} # DeleteEmail + +sub DeleteList ($$) { + my ($type, $sequence) = @_; + + return MAPSDB::DeleteList $type, $sequence; +} # DeleteList + +sub DeleteLog ($) { + my ($sender) = @_; + + return MAPSDB::DeleteLog $sender; +} # DeleteLog + +sub Encrypt ($$) { + my ($password, $userid) = @_; + + return MAPSDB::Encrypt $password, $userid; +} # Encrypt + +sub FindEmail (;$) { + my ($sender) = @_; + + return MAPSDB::FindEmail $sender; +} # FindEmail + +sub FindList ($;$) { + my ($type, $sender) = @_; + + return MAPSDB::FindList $type, $sender; +} # FindList + +sub FindLog ($) { + my ($how_many) = @_; + + my $start_at = 0; + my $end_at = MAPSDB::countlog (); + + if ($how_many < 0) { + $start_at = $end_at - abs ($how_many); + $start_at = 0 if ($start_at < 0); + } # if + + return MAPSDB::FindLog $start_at, $end_at; +} # FindLog + +sub FindUser (;$) { + my ($userid) = @_; + + return MAPSDB::FindUser $userid +} # FindUser + +sub GetContext () { + return MAPSDB::GetContext (); +} # GetContext + +sub GetEmail ($) { + my ($handle) = @_; + + return MAPSDB::GetEmail $handle; +} # GetEmail + +sub GetList ($) { + my ($handle) = @_; + + return MAPSDB::GetList $handle; +} # GetList + +sub GetLog ($) { + my ($handle) = @_; + + return MAPSDB::GetLog $handle; +} # GetLog + +sub GetUser ($) { + my ($handle) = @_; + + return MAPSDB::GetUser $handle; +} # GetUser + +sub GetUserOptions ($) { + my ($userid) = @_; + + return MAPSDB::GetUserOptions $userid; +} # GetUserOptions + +sub Login ($$) { + my ($userid, $password) = @_; + + $password = Encrypt $password, $userid; + + # Check if user exists + my $dbpassword = UserExists $userid; + + # Return -1 if user doesn't exist + return -1 if !$dbpassword; + + # Return -2 if password does not match + if ($password eq $dbpassword) { + MAPSDB::SetContext $userid; + return 0 + } else { + return -2 + } # if +} # Login + +sub Nulllist ($;$$) { + # Nulllist will simply discard the message. + my ($sender, $sequence, $hit_count) = @_; + + RecordHit "null", $sequence, ++$hit_count if $sequence; + + # Discard Message + Logmsg "nulllist", $sender, "Discarded message"; +} # Nulllist + +sub OnBlacklist ($) { + my ($sender) = @_; + + return CheckOnList "black", $sender; +} # CheckOnBlacklist + +sub OnNulllist ($) { + my ($sender) = @_; + + return CheckOnList "null", $sender; +} # CheckOnNulllist + +sub OnWhitelist { + my ($sender, $userid) = @_; + + if (defined $userid) { + MAPSDB::SetContext $userid; + } # if + + return CheckOnList "white", $sender; +} # OnWhitelist + +sub OptimizeDB () { + return MAPSDB::OptimizeDB (); +} # OptimizeDB + +sub ReadMsg ($) { + # Reads an email message file from $input. Returns sender, subject, + # date and data, which is a copy of the entire message. + my ($input) = @_; + + my $sender = ""; + my $sender_long = ""; + my $envelope_sender = ""; + my $reply_to = ""; + my $subject = ""; + my $data = ""; + my @data; + + # Find first message's "From " line indicating start of message + while (<$input>) { + chomp; + last if /^From /; + } # while + + # If we hit eof here then the message was garbled. Return indication of this + if (eof $input) { + $data = "Garbled message - unable to find From line"; + return $sender, $sender_long, $reply_to, $subject, $data; + } # if + + if (/From (\S*)/) { + $envelope_sender = $1; + $sender_long = $envelope_sender; + } # if + + push @data, $_ if /^From /; + + while (<$input>) { + chomp; + push @data, $_; + + # Blank line indicates start of message body + last if ($_ eq "" || $_ eq "\r"); + + # Extract sender's address + if (/^from: .*/i) { + $_ = substr ($_, 6); + $sender_long = $_; + if (/<(\S*)@(\S*)>/) { + $sender = lc ("$1\@$2"); + } elsif (/(\S*)@(\S*)\ /) { + $sender = lc ("$1\@$2"); + } elsif (/(\S*)@(\S*)/) { + $sender = lc ("$1\@$2"); + } # if + } elsif (/^subject: .*/i) { + $subject = substr ($_, 9); + } elsif (/^reply-to: .*/i) { + $_ = substr ($_, 10); + if (/<(\S*)@(\S*)>/) { + $reply_to = lc ("$1\@$2"); + } elsif (/(\S*)@(\S*)\ /) { + $reply_to = lc ("$1\@$2"); + } elsif (/(\S*)@(\S*)/) { + $reply_to = lc ("$1\@$2"); + } # if + } else { + next; + } # if + } # while + + # Read message body + while (<$input>) { + chomp; + + last if (/^From /); + push @data, $_; + } # while + + # Set file pointer back by length of the line just read + seek ($input, -length () - 1, 1) if !eof $input; + + # Sanitize email addresses + $envelope_sender =~ s/\//g; + $envelope_sender =~ s/\"//g; + $envelope_sender =~ s/\'//g; + $sender =~ s/\//g; + $sender =~ s/\"//g; + $sender =~ s/\'//g; + $reply_to =~ s/\//g; + $reply_to =~ s/\"//g; + $reply_to =~ s/\'//g; + + # Now let's pack the @data array to a scalar + foreach (@data) { + $data = $data . $_ . "\n"; + } # foreach + + # Determine best addresses + $sender = $envelope_sender if $sender eq ""; + $reply_to = $sender if $reply_to eq ""; + + return $sender, $sender_long, $reply_to, $subject, $data; +} # ReadMsg + +sub ResequenceList ($$) { + my ($userid, $type) = @_; + + return MAPSDB::ResequenceList $userid, $type; +} # ResequenceList + +sub ReturnMessages ($$) { + my ($userid, $sender) = @_; + + return MAPSDB::ReturnMessages $userid, $sender; +} # ReturnMessages + +sub ReturnSenders ($$$;$$) { + my ($userid, $type, $next, $lines, $date) = @_; + + return MAPSDB::ReturnSenders $userid, $type, $next, $lines, $date; +} # ReturnSenders + +sub ReturnList ($$$) { + my ($type, $start_at, $lines) = @_; + + return MAPSDB::ReturnList $type, $start_at, $lines; +} # ReturnList + +sub ReturnListEntry ($$) { + my ($type, $sequence) = @_; + + return MAPSDB::ReturnListEntry $type, $sequence; +} # ReturnList + +# Added reply_to. Previously we passed reply_to into here as sender. This +# caused a problem in that we were filtering as per sender but logging it +# as reply_to. We only need reply_to for SendMsg so as to honor reply_to +# so we now pass in both sender and reply_to +sub ReturnMsg ($$$$) { + # ReturnMsg will send back to the $sender the register message. + # Messages are saved to be delivered when the $sender registers. + my ($sender, $reply_to, $subject, $data) = @_; + + # Check to see if this sender has already emailed us. + my $msg_count = CountMsg $sender; + + if ($msg_count < 5) { + # Return register message + my @msg; + foreach (split /\n/,$data) { + push @msg, "$_\n"; + } # foreach + SendMsg $reply_to, + "Your email has been returned by MAPS", + "$mapsbase/register.html", + GetContext, + @msg + if $msg_count eq 0; + Logmsg "returned", $sender, "Sent register reply"; + # Save message + SaveMsg $sender, $subject, $data; + } else { + Add2Nulllist $sender, GetContext, "Auto Null List - Mail loop"; + Logmsg "mailloop", $sender, "Mail loop encountered"; + } # if +} # ReturnMsg + +sub SaveMsg ($$$) { + my ($sender, $subject, $data) = @_; + + AddEmail $sender, $subject, $data; +} # SaveMsg + +sub SearchEmails ($$) { + my ($userid, $searchfield) = @_; + + return MAPSDB::SearchEmails $userid, $searchfield; +} # SearchEmails + +sub ForwardMsg ($$$) { + my ($sender, $subject, $data) = @_; + + my @lines = split /\n/, $data; + + while ($_ = shift @lines) { + last if ($_ eq "" || $_ eq "\r"); + } # while + + my $to = "renn.leech\@compassbank.com"; + + my $msg = MIME::Entity->build ( + From => $sender, + To => $to, + Subject => $subject, + Type => "text/html", + Data => \@lines, + ); + + # Send it + open MAIL, "| /usr/lib/sendmail -t -oi -oem" + or die "ForwardMsg: Unable to open pipe to sendmail $!"; + $msg->print(\*MAIL); + close MAIL; +} # ForwardMsg + +sub SendMsg ($$$$@) { + # SendMsg will send the message contained in $msgfile. + my ($sender, $subject, $msgfile, $userid, @spammsg) = @_; + + my @lines; + + # Open return message template file + open RETURN_MSG_FILE, "$msgfile" + or die "Unable to open return msg file ($msgfile): $!\n"; + + # Read return message template file and print it to $msg_body + while () { + if (/\$userid/) { + # Replace userid + s/\$userid/$userid/; + } # if + if (/\$sender/) { + # Replace sender + s/\$sender/$sender/; + } #if + push @lines, $_; + } # while + + # Close RETURN_MSG_FILE + close RETURN_MSG_FILE; + + # Create the message, and set up the mail headers: + my $msg = MIME::Entity->build ( + From => "MAPS\@DeFaria.com", + To => $sender, + Subject => $subject, + Type => "text/html", + Data => \@lines + ); + + # Need to obtain the spam message here... + $msg->attach ( + Type => "message", + Disposition => "attachment", + Data => \@spammsg + ); + + # Send it + open MAIL, "| /usr/lib/sendmail -t -oi -oem" + or die "SendMsg: Unable to open pipe to sendmail $!"; + $msg->print(\*MAIL); + close MAIL; +} # SendMsg + +sub SetContext ($) { + my ($new_user) = @_; + + return MAPSDB::SetContext $new_user; +} # SetContext + +sub Space ($) { + my ($userid) = @_; + + return MAPSDB::Space $userid; +} # Space + +sub UpdateList ($$$$$$) { + my ($userid, $type, $pattern, $domain, $comment, $sequence) = @_; + + return MAPSDB::UpdateList $userid, $type, $pattern, $domain, $comment, $sequence; +} # UpdateList + +sub UpdateUser ($$$$) { + my ($userid, $fullname, $email, $password) = @_; + + return MAPSDB::UpdateUser $userid, $fullname, $email, $password; +} # UpdateUser + +sub UpdateUserOptions ($@) { + my ($userid, %options) = @_; + + my $status; + + foreach (keys (%options)) { + $status = MAPSDB::UpdateUserOption $userid, $_, $options{$_}; + last if $status ne 0; + } + + return $status; +} # UpdateUserOptions + +sub UserExists ($) { + my ($userid) = @_; + + return MAPSDB::UserExists $userid +} # UserExists + +sub Whitelist ($$;$$) { + # Whitelist will deliver the message. + my ($sender, $data, $sequence, $hit_count) = @_; + + my $userid = GetContext; + + # Dump message into a file + open MESSAGE, ">/tmp/MAPSMessage.$$" + or Error "Unable to open message file (/tmp/MAPSMessage.$$): $!\n", return -1; + + print MESSAGE $data; + + close MESSAGE; + + # Now call MAPSDeliver + my $status = system "$FindBin::Bin/MAPSDeliver $userid /tmp/MAPSMessage.$$"; + + unlink "/tmp/MAPSMessage.$$"; + + if ($status eq 0) { + Logmsg "whitelist", $sender, "Delivered message"; + } else { + Error "Unable to deliver message - is MAPSDeliver setgid? - $!"; + } # if + + RecordHit "white", $sequence, ++$hit_count if $sequence; + + return $status; +} # Whitelist + +1; diff --git a/maps/bin/MAPSDB.pm b/maps/bin/MAPSDB.pm new file mode 100644 index 0000000..74b1302 --- /dev/null +++ b/maps/bin/MAPSDB.pm @@ -0,0 +1,1504 @@ +#!/usr/bin/perl +################################################################################# +# +# File: $RCSfile: MAPSDB.pm,v $ +# Revision: $Revision: 1.1 $ +# Description: MAPS Database routines +# Author: Andrew@DeFaria.com +# Created: Fri Nov 29 14:17:21 2002 +# Modified: $Date: 2013/06/12 14:05:47 $ +# Language: perl +# +# (c) Copyright 2000-2006, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +package MAPSDB; + +use strict; +use vars qw (@ISA @EXPORT); +use DBI; + +use MAPSUtil; + +@ISA = qw (Exporter); + +# Globals +my $userid = $ENV{MAPS_USERNAME} ? $ENV{MAPS_USERNAME} : $ENV{USER}; +my %useropts; +my $DB; + +@EXPORT = qw ( + AddLog + CheckOnList + CloseDB + DBError + OpenDB + RecordHit +); + +# Forwards +sub AddEmail; +sub AddList; +sub AddLog; +sub AddUser; +sub AddUserOption; +sub CheckOnList; +sub CleanEmail; +sub CleanLog; +sub CleanList; +sub CloseDB; +sub CountMsg; +sub DBError; +sub Decrypt; +sub DeleteEmail; +sub DeleteList; +sub Encrypt; +sub FindEmail; +sub FindList; +sub FindLog; +sub FindUser; +sub GetContext; +sub GetEmail; +sub GetList; +sub GetLog; +sub GetNextSequenceNo; +sub GetUser; +sub GetUserInfo; +sub GetUserOptions; +sub OpenDB; +sub OptimizeDB; +sub ResequenceList; +sub ReturnEmails; +sub ReturnList; +sub ReturnListEntry; +sub SetContext; +sub Space; +sub UpdateList; +sub UpdateUser; +sub UpdateUserOption; +sub UserExists; +sub count; +sub countlog; + +sub AddEmail ($$$) { + my ($sender, $subject, $data) = @_; + + # "Sanitize" some fields so that characters that are illegal to SQL are escaped + $sender = 'Unknown' + if (!defined $sender || $sender eq ''); + $sender = $DB->quote ($sender); + $subject = $DB->quote ($subject); + $data = $DB->quote ($data); + + my $timestamp = UnixDatetime2SQLDatetime (scalar (localtime)); + my $statement = "insert into email values (\"$userid\", $sender, $subject, \"$timestamp\", $data)"; + + $DB->do ($statement) + or DBError 'AddEmail: Unable to do statement', $statement; +} # AddEmail + +sub AddList ($$$;$$) { + my ($listtype, $pattern, $sequence, $comment, $hitcount) = @_; + + $hitcount ||= 0; + + my ($user, $domain) = split /\@/, $pattern; + + if (!$domain || $domain eq '') { + $domain = 'NULL'; + $pattern = $DB->quote ($user); + } else { + $domain = "'$domain'"; + if ($user eq '') { + $pattern = 'NULL'; + } else { + $pattern = $DB->quote ($user); + } # if + } # if + + if (!$comment || $comment eq '') { + $comment = 'NULL'; + } else { + $comment = $DB->quote ($comment); + } # if + + # Get next sequence # + if ($sequence eq 0) { + $sequence = GetNextSequenceNo $userid, $listtype; + } # if + + my $timestamp = UnixDatetime2SQLDatetime (scalar (localtime)); + + my $statement = "insert into list values (\"$userid\", \"$listtype\", $pattern, $domain, $comment, $sequence, $hitcount, \"$timestamp\")"; + + $DB->do ($statement) + or DBError 'AddList: Unable to do statement', $statement; +} # AddList + +sub AddLog ($$$) { + my ($type, $sender, $msg) = @_; + + my $timestamp = UnixDatetime2SQLDatetime (scalar (localtime)); + my $statement; + + # Use quote to protect ourselves + $msg = $DB->quote ($msg); + + if ($sender eq '') { + $statement = "insert into log values (\"$userid\", \"$timestamp\", null, \"$type\", $msg)"; + } else { + $statement = "insert into log values (\"$userid\", \"$timestamp\", \"$sender\", \"$type\", $msg)"; + } # if + + $DB->do ($statement) + or DBError 'AddLog: Unable to do statement', $statement; +} # AddLog + +sub AddUser ($$$$) { + my ($userid, $realname, $email, $password) = @_; + + $password = Encrypt $password, $userid; + + if (UserExists $userid) { + return 1; + } else { + my $statement = "insert into user values ('$userid', '$realname', '$email', '$password')"; + + $DB->do ($statement) + or DBError 'AddUser: Unable to do statement', $statement; + } # if + + return 0; +} # AddUser + +sub AddUserOption ($$$) { + my ($userid, $name, $value) = @_; + + if (!UserExists $userid) { + return 1; + } # if + + my $statement = "insert into useropts values ('$userid', '$name', '$value')"; + + $DB->do ($statement) + or DBError 'AddUserOption: Unable to do statement', $statement; + + return 0; +} # AddUserOption + +sub RecordHit ($$$) { + my ($listtype, $sequence, $hit_count) = @_; + + my $current_date = UnixDatetime2SQLDatetime (scalar (localtime)); + + my $statement = "update list set hit_count=$hit_count, last_hit='$current_date' where userid='$userid' and type='$listtype' and sequence=$sequence"; + + $DB->do ($statement) + or DBError 'AddList: Unable to do statement', $statement; +} # RecordHit + +sub CheckOnList ($$) { + # CheckOnList will check to see if the $sender is on the $listfile. + # Return 1 if found 0 if not. + my ($listtype, $sender) = @_; + + my $status = 0; + my $rule; + + my $statement = "select pattern, domain, comment, sequence, hit_count from list where userid = '$userid' and type = '$listtype'"; + + my $sth = $DB->prepare ($statement) + or DBError 'CheckOnList: Unable to prepare statement', $statement; + + $sth->execute + or DBError 'CheckOnList: Unable to execute statement', $statement; + + while (my @row = $sth->fetchrow_array) { + last if !@row; + + my $hit_count = pop (@row); + my $sequence = pop (@row); + my $comment = pop (@row); + my $domain = pop (@row); + my $pattern = pop (@row); + my $email_on_file; + + unless ($domain) { + $email_on_file = $pattern; + } else { + unless ($pattern) { + $email_on_file = '@' . $domain; + } else { + $email_on_file = $pattern . '@' . $domain; + } # if + } # unless + + # Escape some special characters + $email_on_file =~ s/\@/\\@/; + $email_on_file =~ s/^\*/.\*/; + + # We want to terminate the search string with a "$" iff there's an + # "@" in there. This is because some "email_on_file" may have no + # domain (e.g. "mailer-daemon" with no domain). In that case we + # don't want to terminate the search string with a "$" rather we + # wish to terminate it with an "@". But in the case of say + # "@ti.com" if we don't terminate the search string with "$" then + # "@ti.com" would also match "@tixcom.com"! + my $search_for = $email_on_file =~ /\@/ + ? "$email_on_file\$" + : !defined $domain + ? "$email_on_file\@" + : $email_on_file; + + if ($sender =~ /$search_for/i) { + $rule = "Matching rule: ($listtype:$sequence) \"$email_on_file\""; + $rule .= " - $comment" if $comment and $comment ne ''; + $status = 1; + + RecordHit $listtype, $sequence, ++$hit_count; + + last; + } # if + } # while + + $sth->finish; + + return ($status, $rule); +} # CheckOnList + +sub CleanEmail ($) { + my ($timestamp) = @_; + + # First see if anything needs to be deleted + my $count = 0; + + my $statement = "select count(*) from email where userid = '$userid' and timestamp < '$timestamp'"; + + # Prepare statement + my $sth = $DB->prepare ($statement) + or DBError 'CleanEmail: Unable to prepare statement', $statement; + + # Execute statement + $sth->execute + or DBError 'CleanEmail: Unable to execute statement', $statement; + + # Get return value, which should be how many entries were deleted + my @row = $sth->fetchrow_array; + + # Done with $sth + $sth->finish; + + # Retrieve returned value + unless ($row[0]) { + $count = 0 + } else { + $count = $row[0]; + } # unless + + # Just return if there's nothing to delete + return $count if ($count eq 0); + + # Delete emails for userid whose older than $timestamp + $statement = "delete from email where userid = '$userid' and timestamp < '$timestamp'"; + + # Prepare statement + $sth = $DB->prepare ($statement) + or DBError 'CleanEmail: Unable to prepare statement', $statement; + + # Execute statement + $sth->execute + or DBError 'CleanEmail: Unable to execute statement', $statement; + + return $count; +} # CleanEmail + +sub CleanLog ($) { + my ($timestamp) = @_; + + # First see if anything needs to be deleted + my $count = 0; + + my $statement = "select count(*) from log where userid = '$userid' and timestamp < '$timestamp'"; + + # Prepare statement + my $sth = $DB->prepare ($statement) + or DBError $DB, 'CleanLog: Unable to prepare statement', $statement; + + # Execute statement + $sth->execute + or DBError 'CleanLog: Unable to execute statement', $statement; + + # Get return value, which should be how many entries were deleted + my @row = $sth->fetchrow_array; + + # Done with $sth + $sth->finish; + + # Retrieve returned value + unless ($row[0]) { + $count = 0 + } else { + $count = $row[0]; + } # unless + + # Just return if there's nothing to delete + return $count if ($count eq 0); + + # Delete log entries for userid whose older than $timestamp + $statement = "delete from log where userid = '$userid' and timestamp < '$timestamp'"; + + # Prepare statement + $sth = $DB->prepare ($statement) + or DBError 'CleanLog: Unable to prepare statement', $statement; + + # Execute statement + $sth->execute + or DBError 'CleanLog: Unable to execute statement', $statement; + + return $count; +} # CleanLog + +sub CleanList ($;$) { + my ($timestamp, $listtype) = @_; + + $listtype = 'null' if !$listtype; + + # First see if anything needs to be deleted + my $count = 0; + + my $statement = "select count(*) from list where userid = '$userid' and type = '$listtype' and last_hit < '$timestamp'"; + + # Prepare statement + my $sth = $DB->prepare ($statement) + or DBError $DB, 'CleanList: Unable to prepare statement', $statement; + + # Execute statement + $sth->execute + or DBError 'CleanList: Unable to execute statement', $statement; + + # Get return value, which should be how many entries were deleted + my @row = $sth->fetchrow_array; + + # Done with $sth + $sth->finish; + + # Retrieve returned value + $count = $row[0] ? $row[0] : 0; + + # Just return if there's nothing to delete + return $count if ($count eq 0); + + # Get data for these entries + $statement = "select type, sequence, hit_count from list where userid = '$userid' and type = '$listtype' and last_hit < '$timestamp'"; + + # Prepare statement + $sth = $DB->prepare ($statement) + or DBError 'CleanList: Unable to prepare statement', $statement; + + # Execute statement + $sth->execute + or DBError 'CleanList: Unable to execute statement', $statement; + + $count = 0; + + while (my @row = $sth->fetchrow_array) { + last if !@row; + + my $hit_count = pop (@row); + my $sequence = pop (@row); + my $listtype = pop (@row); + + if ($hit_count == 0) { + $count++; + + $statement = "delete from list where userid='$userid' and type='$listtype' and sequence=$sequence"; + $DB->do ($statement) + or DBError 'CleanList: Unable to execute statement', $statement; + } else { + # Age entry: Sometimes entries are initially very popular and + # the $hit_count gets very high quickly. Then the domain is + # abandoned and no activity happens. One case recently observed + # was for phentermine.com. The $hit_count initially soared to + # 1920 within a few weeks. Then it all stopped as of + # 07/13/2007. Obvisously this domain was shutdown. With the + # previous aging algorithm of simply subtracting 1 this + # phentermine.com entry would hang around for over 5 years! + # + # So the tack here is to age the entry by dividing it's + # $hit_count in half. Sucessive halfing then will quickly age + # the entry down to size. However we don't want to age small + # $hit_count's too quickly, therefore once their numbers drop to + # < 30 we revert to the old method of subtracting 1. + if ($hit_count < 30) { + $hit_count--; + } else { + $hit_count = $hit_count / 2; + } # if + + $statement = "update list set hit_count=$hit_count where userid='$userid' and type='$listtype' and sequence=$sequence;"; + $DB->do ($statement) + or DBError 'CleanList: Unable to execute statement', $statement; + } # if + } # while + + ResequenceList $userid, $listtype if $count > 0; + + return $count; +} # CleanList + +sub CloseDB () { + $DB->disconnect; +} # CloseDB + +sub CountMsg ($) { + my ($sender) = @_; + + return count ('email', "userid = '$userid' and sender like '%$sender%'"); +} # CountMsg + +sub DBError ($$) { + my ($msg, $statement) = @_; + + print 'MAPSDB::' . $msg . "\nError #" . $DB->err . ' ' . $DB->errstr . "\n"; + + if ($statement) { + print "SQL Statement: $statement\n"; + } # if + + exit $DB->err; +} # DBError + +sub Decrypt ($$) { + my ($password, $userid) = @_; + + my $statement = "select decode('$password','$userid')"; + + my $sth = $DB->prepare ($statement) + or DBError 'Decrypt: Unable to prepare statement', $statement; + + $sth->execute + or DBError 'Decrypt: Unable to execute statement', $statement; + + # Get return value, which should be the encoded password + my @row = $sth->fetchrow_array; + + # Done with $sth + $sth->finish; + + return $row[0] +} # Decrypt + +sub DeleteEmail ($) { + my $sender = shift; + + my ($username, $domain) = split /@/, $sender; + my $condition; + + if ($username eq '') { + $condition = "userid = '$userid' and sender like '%\@$domain'"; + } else { + $condition = "userid = '$userid' and sender = '$sender'"; + } # if + + # First see if anything needs to be deleted + my $count = count ('email', $condition); + + # Just return if there's nothing to delete + return $count if ($count eq 0); + + my $statement = 'delete from email where ' . $condition; + + $DB->do ($statement) + or DBError 'DeleteEmail: Unable to execute statement', $statement; + + return $count; +} # DeleteEmail + +sub DeleteList ($$) { + my ($type, $sequence) = @_; + + # First see if anything needs to be deleted + my $count = count ('list', "userid = '$userid' and type = '$type' and sequence = '$sequence'"); + + # Just return if there's nothing to delete + return $count if ($count eq 0); + + my $statement = "delete from list where userid = '$userid' and type = '$type' and sequence = '$sequence'"; + + $DB->do ($statement) + or DBError 'DeleteList: Unable to execute statement', $statement; + + return $count; +} # DeleteList + +sub DeleteLog ($) { + my ($sender) = @_; + + my ($username, $domain) = split /@/, $sender; + my $condition; + + if ($username eq '') { + $condition = "userid = '$userid' and sender like '%\@$domain'"; + } else { + $condition = "userid = '$userid' and sender = '$sender'"; + } # if + + # First see if anything needs to be deleted + my $count = count ('log', $condition); + + # Just return if there's nothing to delete + return $count if ($count eq 0); + + my $statement = 'delete from log where ' . $condition; + + $DB->do ($statement) + or DBError 'DeleteLog: Unable to execute statement', $statement; + + return $count; +} # DeleteLog + +sub Encrypt ($$) { + my ($password, $userid) = @_; + + my $statement = "select encode('$password','$userid')"; + + my $sth = $DB->prepare ($statement) + or DBError 'Encrypt: Unable to prepare statement', $statement; + + $sth->execute + or DBError 'Encrypt: Unable to execute statement', $statement; + + # Get return value, which should be the encoded password + my @row = $sth->fetchrow_array; + + # Done with $sth + $sth->finish; + + return $row[0] +} # Encrypt + +sub FindEmail (;$) { + my ($sender) = @_; + + my $statement; + + if (!defined $sender || $sender eq '') { + $statement = "select * from email where userid = '$userid'"; + } else { + $statement = "select * from email where userid = '$userid' and sender = '$sender'"; + } # if + + my $sth = $DB->prepare ($statement) + or DBError 'FindEmail: Unable to prepare statement', $statement; + + $sth->execute + or DBError 'FindEmail: Unable to execute statement', $statement; + + return $sth; +} # FindEmail + +sub FindList ($;$) { + my ($type, $sender) = @_; + + my $statement; + + unless ($sender) { + $statement = "select * from list where userid = '$userid' and type = '$type'"; + } else { + my ($pattern, $domain) = split /\@/, $sender; + $statement = "select * from list where userid = '$userid' and type = '$type' " . + "and pattern = '$pattern' and domain = '$domain'"; + } # unless + + # Prepare statement + my $sth = $DB->prepare ($statement) + or DBError 'FindList: Unable to prepare statement', $statement; + + # Execute statement + $sth->execute + or DBError 'FindList: Unable to execute statement', $statement; + + # Get return value, which should be how many entries were deleted + return $sth; +} # FindList + +sub FindLog ($$) { + my ($start_at, $end_at) = @_; + + my $statement = "select * from log where userid = '$userid' order by timestamp limit $start_at, $end_at"; + + # Prepare statement + my $sth = $DB->prepare ($statement) + or DBError 'FindLog: Unable to prepare statement', $statement; + + # Execute statement + $sth->execute + or DBError 'FindLog: Unable to execute statement', $statement; + + # Get return value, which should be how many entries were deleted + return $sth; +} # FindLog + +sub FindUser (;$) { + my ($userid) = @_; + + my $statement; + + if (!defined $userid || $userid eq '') { + $statement = 'select * from user'; + } else { + $statement = "select * from user where userid = '$userid'"; + } # if + + my $sth = $DB->prepare ($statement) + or DBError 'FindUser: Unable to prepare statement', $statement; + + $sth->execute + or DBError 'FindUser: Unable to execute statement', $statement; + + return $sth; +} # FindUser + +sub GetContext () { + return $userid; +} # GetContext + +sub GetEmail ($) { + my ($sth) = @_; + + my @email; + + if (@email = $sth->fetchrow_array) { + my $message = pop @email; + my $timestamp = pop @email; + my $subject = pop @email; + my $sender = pop @email; + my $userid = pop @email; + return $userid, $sender, $subject, $timestamp, $message; + } else { + return undef; + } # if +} # GetEmail + +sub GetList ($) { + my ($sth) = @_; + + my @list; + + if (@list = $sth->fetchrow_array) { + my $last_hit = pop @list; + my $hit_count = pop @list; + my $sequence = pop @list; + my $comment = pop @list; + my $domain = pop @list; + my $pattern = pop @list; + my $type = pop @list; + my $userid = pop @list; + return $userid, $type, $pattern, $domain, $comment, $sequence, $hit_count, $last_hit; + } else { + return undef; + } # if +} # GetList + +sub GetLog ($) { + my ($sth) = @_; + + my @log; + + if (@log = $sth->fetchrow_array) { + my $message = pop @log; + my $type = pop @log; + my $sender = pop @log; + my $timestamp = pop @log; + my $userid = pop @log; + return $userid, $timestamp, $sender, $type, $message; + } else { + return undef; + } # if +} # GetLog + +sub GetNextSequenceNo ($$) { + my ($userid, $listtype) = @_; + + my $count = count ('list', "userid = '$userid' and type = '$listtype'"); + + return $count + 1; +} # GetNextSequenceNo + +sub GetUser ($) { + my ($sth) = @_; + + my @user; + + if (@user = $sth->fetchrow_array) { + my $password = pop @user; + my $email = pop @user; + my $name = pop @user; + my $userid = pop @user; + return ($userid, $name, $email, $password); + } else { + return undef; + } # if +} # GetUser + +sub GetUserInfo ($) { + my ($userid) = @_; + + my $statement = "select name, email from user where userid='$userid'"; + + my $sth = $DB->prepare ($statement) + or DBError 'GetUserInfo: Unable to prepare statement', $statement; + + $sth->execute + or DBError 'GetUserInfo: Unable to execute statement', $statement; + + my @userinfo = $sth->fetchrow_array; + my $user_email = lc (pop @userinfo); + my $username = lc (pop @userinfo); + + $sth->finish; + + return ($username, $user_email); +} # GetUserInfo + +sub GetUserOptions ($) { + my ($userid) = @_; + + my $statement = "select * from useropts where userid = '$userid'"; + + my $sth = $DB->prepare ($statement) + or DBError 'GetUserOptions: Unable to prepare statement', $statement; + + $sth->execute + or DBError 'GetUserOptions: Unable to execute statement', $statement; + + my @useropts; + + # Empty hash + %useropts = (); + + while (@useropts = $sth->fetchrow_array) { + my $value = pop @useropts; + my $name = pop @useropts; + pop @useropts; + $useropts{$name} = $value; + } # while + + $sth->finish; + + return %useropts; +} # GetUserOptions + +sub GetRows ($) { + my ($statement) = @_; + + my $sth = $DB->prepare ($statement) + or DBError 'Unable to prepare statement' , $statement; + + $sth->execute + or DBError 'Unable to execute statement' , $statement; + + my @array; + + while (my @row = $sth->fetchrow_array) { + foreach (@row) { + push @array, $_; + } # foreach + } # while + + return @array; +} # GetRows + +sub OpenDB ($$) { + my ($username, $password) = @_; + + my $dbname = 'MAPS'; + my $dbdriver = 'mysql'; + my $dbserver = $ENV{MAPS_SERVER} ? $ENV{MAPS_SERVER} : 'jupiter'; + + if (!$DB || $DB eq '') { + $dbserver='localhost'; + $DB = DBI->connect("DBI:$dbdriver:$dbname:$dbserver", $username, $password, {PrintError => 0}) + or die "Couldn't connect to $dbname database as $username\n" . $DBI::errstr; + } # if + + return $DB; +} # OpenDB + +sub OptimizeDB () { + my $statement = 'lock tables email read, list read, log read, user read, useropts read'; + my $sth = $DB->prepare ($statement) + or DBError 'OptimizeDB: Unable to prepare statement', $statement; + + $sth->execute + or DBError 'OptimizeDB: Unable to execute statement', $statement; + + $statement = 'check table email, list, log, user, useropts'; + $sth = $DB->prepare ($statement) + or DBError 'OptimizeDB: Unable to prepare statement', $statement; + + $sth->execute + or DBError 'OptimizeDB: Unable to execute statement', $statement; + + $statement = 'unlock tables'; + $sth = $DB->prepare ($statement) + or DBError 'OptimizeDB: Unable to prepare statement', $statement; + + $sth->execute + or DBError 'OptimizeDB: Unable to execute statement', $statement; + + $statement = 'optimize table email, list, log, user, useropts'; + $sth = $DB->prepare ($statement) + or DBError 'OptimizeDB: Unable to prepare statement', $statement; + + $sth->execute + or DBError 'OptimizeDB: Unable to execute statement', $statement; +} # OptimizeDB + +sub ResequenceList ($$) { + my ($userid, $type) = @_; + + if ($type ne 'white' && $type ne 'black' && $type ne 'null') { + return 1; + } # if + + if (!UserExists $userid) { + return 2; + } # if + + my $statement = "select sequence from list where userid = '$userid' ". + " and type = '$type' order by sequence"; + + my $sth = $DB->prepare ($statement) + or DBError 'ResequenceList: Unable to prepare statement', $statement; + + $sth->execute + or DBError 'ResequenceList: Unable to execute statement', $statement; + + my $sequence = 1; + + while (my @row = $sth->fetchrow_array) { + last if !@row; + my $old_sequence = pop (@row); + + if ($old_sequence != $sequence) { + my $update_statement = "update list set sequence = $sequence " . + "where userid = '$userid' and " . + "type = '$type' and sequence = $old_sequence"; + $DB->do ($update_statement) + or DBError 'ResequenceList: Unable to do statement', $statement; + } # if + + $sequence++; + } # while + + return 0; +} # ResequenceList + +# This subroutine returns an array of senders in reverse chronological +# order based on time timestamp from the log table of when we returned +# their message. The complication here is that a single sender may +# send multiple times in a single day. So if spammer@foo.com sends +# spam @ 1 second after midnight and then again at 2 Pm there will be +# at least two records in the log table saying that we returned his +# email. Getting records sorted by timestamp desc will have +# spammer@foo.com listed twice. But we want him listed only once, as +# the first entry in the returned array. Plus we may be called +# repeatedly with different $start_at's. Therefore we need to process +# the whole list of returns for today, eliminate duplicate entries for +# a single sender then slice the resulting array. +sub ReturnSenders ($$$;$$) { + my ($userid, $type, $start_at, $nbr_emails, $date) = @_; + + $start_at ||= 0; + + my $dateCond = ''; + + if ($date) { + my $sod = $date . ' 00:00:00'; + my $eod = $date . ' 23:59:59'; + + $dateCond = "and timestamp > '$sod' and timestamp < '$eod'"; + } # if + + my $statement = <prepare ($statement) + or DBError 'ReturnSenders: Unable to prepare statement', $statement; + + $sth->execute + or DBError 'ReturnSenders: Unable to execute statement', $statement; + + # Watch the distinction between senders (plural) and sender (singular) + my (%senders, %sendersByTimestamp); + + # Run through the results and add to %senders by sender key. This + # results in a hash that has the sender in it and the first + # timestamp value. Since we already sorted timestamp desc by the + # above select statement, and we've narrowed it down to only log + # message that occurred for the given $date, we will have a hash + # containing 1 sender and the latest timestamp for the day. + while (my $senderRef = $sth->fetchrow_hashref) { + my %sender = %{$senderRef}; + + $senders{$sender{sender}} = $sender{timestamp} + unless $senders{$sender{sender}}; + } # while + + $sth->finish; + + # Make a hash whose keys are the timestamp (so we can later sort on + # them). + while (my ($key, $value) = each %senders) { + $sendersByTimestamp{$value} = $key; + } # while + + my @senders; + + # Sort by timestamp desc and push on to the @senders array + push @senders, $sendersByTimestamp{$_} + foreach (sort { $b cmp $a } keys %sendersByTimestamp); + + # Finally slice for the given range + my $end_at = $start_at + $nbr_emails - 1; + + $end_at = (@senders - 1) + if $end_at > @senders; + + return (@senders) [$start_at .. $end_at]; +} # ReturnSenders + +sub ReturnMessages ($$) { + my ($userid, $sender) = @_; + + # Note, the left(timestamp,16) chops off the seconds and the group + # by effectively squashes two emails received in the same minute to + # just one. We get a lot of double emails within the same minute. I + # think it's a result of the mailer configuration and it attempting + # to resend the message, not that it's the spammer sending just two + # emails in under a minute then going away. This will mean we will + # see fewer emails listed (essentially dups within one minute are + # squashed) yet they still will count towards the number of hits + # before we autonullist. We should squash these upon receipt, not + # upon report. Maybe latter... + my $statement = <prepare ($statement) + or DBError 'ReturnMessages: Unable to prepare statement', $statement; + + $sth->execute + or DBError 'ReturnMessages: Unable to execute statement', $statement; + + my @messages; + + while (my @row = $sth->fetchrow_array) { + my $date = pop @row; + my $subject = pop @row; + + push @messages, [$subject, $date]; + } # while + + $sth->finish; + + return @messages; +} # ReturnMessages + +sub ReturnEmails ($$$;$$) { + my ($userid, $type, $start_at, $nbr_emails, $date) = @_; + + $start_at ||= 0; + + my $statement; + + if ($date) { + my $sod = $date . ' 00:00:00'; + my $eod = $date . ' 23:59:59'; + + if ($type eq 'returned') { + $statement = < '$sod' and + log.timestamp < '$eod' and + log.type = '$type' +group by + log.sender +limit + $start_at, $nbr_emails +END + } else { + $statement = < '$sod' and + timestamp < '$eod' and + type = '$type' +group by + sender +limit + $start_at, $nbr_emails +END + } # if + } else { + if ($type eq 'returned') { + $statement = <prepare ($statement) + or DBError 'ReturnEmails: Unable to prepare statement', $statement; + + $sth->execute + or DBError 'ReturnEmails: Unable to execute statement', $statement; + + my @emails; + + while (my $sender = $sth->fetchrow_array) { + my $earliestDate; + + # Get emails for this sender. Format an array of subjects and timestamps. + my @messages; + + $statement = "select timestamp, subject from email where userid = '$userid' " . + "and sender = '$sender'"; + + my $sth2 = $DB->prepare ($statement) + or DBError 'ReturnEmails: Unable to prepare statement', $statement; + + $sth2->execute + or DBError 'ReturnEmails: Unable to execute statement', $statement; + + while (my @row = $sth2->fetchrow_array) { + my $subject = pop @row; + my $date = pop @row; + + if ($earliestDate) { + my $earliestDateShort = substr $earliestDate, 0, 10; + my $dateShort = substr $date, 0, 10; + + if ($earliestDateShort eq $dateShort and + $earliestDate > $date) { + $earliestDate = $date + if $earliestDateShort eq $dateShort; + } # if + } else { + $earliestDate = $date; + } # if + + push @messages, [$subject, $date]; + } # while + + # Done with sth2 + $sth2->finish; + + $earliestDate ||= ''; + + unless ($type eq 'returned') { + push @emails, [$earliestDate, [$sender, @messages]]; + } else { + push @emails, [$earliestDate, [$sender, @messages]] + if @messages > 0; + } # unless + } # while + + # Done with $sth + $sth->finish; + + return @emails; +} # ReturnEmails + +sub ReturnList ($$$) { + my ($type, $start_at, $lines) = @_; + + $lines ||= 10; + + my $statement; + + if ($start_at) { + $statement = "select * from list where userid = '$userid' " . + "and type = '$type' order by sequence " . + "limit $start_at, $lines"; + } else { + $statement = "select * from list where userid = '$userid' " . + "and type = '$type' order by sequence"; + } # if + + my $sth = $DB->prepare ($statement) + or DBError 'ReturnList: Unable to prepare statement', $statement; + + $sth->execute + or DBError 'ReturnList: Unable to execute statement', $statement; + + my @list; + my $i = 0; + + while (my @row = $sth->fetchrow_array) { + last if $i++ > $lines; + + my %list; + + $list {last_hit} = pop @row; + $list {hit_count} = pop @row; + $list {sequence} = pop @row; + $list {comment} = pop @row; + $list {domain} = pop @row; + $list {pattern} = pop @row; + $list {type} = pop @row; + $list {userid} = pop @row; + push @list, \%list; + } # for + + return @list; +} # ReturnList + +sub ReturnListEntry ($$) { + my ($type, $sequence) = @_; + + my $statement = "select * from list where userid = '$userid' " . + "and type = '$type' and sequence = '$sequence'"; + + my $sth = $DB->prepare ($statement) + or DBError 'ReturnListEntry: Unable to prepare statement', $statement; + + $sth->execute + or DBError 'ReturnListEntry: Unable to execute statement', $statement; + + my %list; + my @row = $sth->fetchrow_array; + + $list {sequence} = pop @row; + $list {comment} = pop @row; + $list {domain} = pop @row; + $list {pattern} = pop @row; + $list {type} = pop @row; + $list {userid} = pop @row; + + return %list; +} # ReturnListEntry + +sub UpdateList ($$$$$$) { + my ($userid, $type, $pattern, $domain, $comment, $sequence) = @_; + + if (!$pattern || $pattern eq '') { + $pattern = 'NULL'; + } else { + $pattern = "'" . quotemeta ($pattern) . "'"; + } # if + + if (!$domain || $domain eq '') { + $domain = 'NULL'; + } else { + $domain = "'" . quotemeta ($domain) . "'"; + } # if + + if (!$comment || $comment eq '') { + $comment = 'NULL'; + } else { + $comment = "'" . quotemeta ($comment) . "'"; + } # if + + my $statement = + 'update list set ' . + "pattern = $pattern, domain = $domain, comment = $comment " . + "where userid = '$userid' and type = '$type' and sequence = $sequence"; + + $DB->do ($statement) + or DBError 'UpdateList: Unable to do statement', $statement; + + return 0; +} # UpdateList + +sub SearchEmails ($$) { + my ($userid, $searchfield) = @_; + + my @emails; + + my $statement = + "select sender, subject, timestamp from email where userid = '$userid' and ( + sender like '%$searchfield%' or subject like '%$searchfield%') + order by timestamp desc"; + + my $sth = $DB->prepare ($statement) + or DBError 'SearchEmails: Unable to prepare statement', $statement; + + $sth->execute + or DBError 'SearchEmails: Unable to execute statement', $statement; + + while (my @row = $sth->fetchrow_array) { + my $date = pop @row; + my $subject = pop @row; + my $sender = pop @row; + + push @emails, [$sender, $subject, $date]; + } # while + + $sth->finish; + + return @emails; +} # SearchEmails + +sub SetContext ($) { + my ($to_user) = @_; + + my $old_user = $userid; + + if (UserExists $to_user) { + $userid = $to_user; + GetUserOptions $userid; + return GetUserInfo $userid; + } else { + return 0; + } # if +} # SetContext + +sub Space ($) { + my ($userid) = @_; + + my $total_space = 0; + my %msg_space; + + my $statement = "select * from email where userid = '$userid'"; + my $sth = $DB->prepare ($statement) + or DBError 'Unable to prepare statement', $statement; + + $sth->execute + or DBError 'Unable to execute statement', $statement; + + while (my @row = $sth->fetchrow_array) { + last if !@row; + my $data = pop @row; + my $timestamp = pop @row; + my $subject = pop @row; + my $sender = pop @row; + my $user = pop @row; + + my $msg_space = + length ($userid) + + length ($sender) + + length ($subject) + + length ($timestamp) + + length ($data); + + $total_space += $msg_space; + $msg_space{$sender} += $msg_space; + } # while + + $sth->finish; + + return wantarray ? %msg_space : $total_space; +} # Space + +sub UpdateUser ($$$$) { + my ($userid, $fullname, $email, $password) = @_; + + if (!UserExists $userid) { + return 1; + } # if + + my $statement; + + if (!defined $password || $password eq '') { + $statement = "update user set userid='$userid', name='$fullname', email='$email' where userid='$userid'"; + } else { + $password = Encrypt $password, $userid; + $statement = "update user set userid='$userid', name='$fullname', email='$email', password='$password' where userid='$userid'"; + } # if + + $DB->do ($statement) + or DBError 'UpdateUser: Unable to do statement', $statement; + + return 0; +} # UpdateUser + +sub UpdateUserOption ($$$) { + my ($userid, $name, $value) = @_; + + if (!UserExists $userid) { + return 1; + } # if + + my $statement = "update useropts set value='$value' where userid='$userid' and name='$name'"; + + $DB->do ($statement) + or DBError 'UpdateUserOption: Unable to do statement', $statement; + + return 0; +} # UpdateUserOptions + +sub UserExists ($) { + my ($userid) = @_; + + return 0 + unless $userid; + + my $statement = "select userid, password from user where userid = '$userid'"; + + my $sth = $DB->prepare ($statement) + or DBError 'UserExists: Unable to prepare statement', $statement; + + $sth->execute + or DBError 'UserExists: Unable to execute statement', $statement; + + my @userdata = $sth->fetchrow_array; + + $sth->finish; + + return 0 if scalar (@userdata) == 0; + + my $dbpassword = pop @userdata; + my $dbuserid = pop @userdata; + + if ($dbuserid ne $userid) { + return 0; + } else { + return $dbpassword; + } # if +} # UserExists + +sub count ($$) { + my ($table, $condition) = @_; + + my $statement; + + if ($condition) { + $statement = "select count(*) from $table where $condition"; + } else { + $statement = "select count(*) from $table"; + } # if + + my $sth = $DB->prepare ($statement) + or DBError 'count: Unable to prepare statement', $statement; + + $sth->execute + or DBError 'count: Unable to execute statement', $statement; + + # Get return value, which should be how many message there are + my @row = $sth->fetchrow_array; + + # Done with $sth + $sth->finish; + + my $count; + + # Retrieve returned value + unless ($row[0]) { + $count = 0 + } else { + $count = $row[0]; + } # unless + + return $count +} # count + +sub count_distinct ($$$) { + my ($table, $column, $condition) = @_; + + my $statement; + + if ($condition) { + $statement = "select count(distinct $column) from $table where $condition"; + } else { + $statement = "select count(distinct $column) from $table"; + } # if + + my $sth = $DB->prepare ($statement) + or DBError 'count: Unable to prepare statement', $statement; + + $sth->execute + or DBError 'count: Unable to execute statement', $statement; + + # Get return value, which should be how many message there are + my @row = $sth->fetchrow_array; + + # Done with $sth + $sth->finish; + + # Retrieve returned value + unless ($row[0]) { + return 0; + } else { + return $row[0]; + } # unless +} # count_distinct + +sub countlog (;$$) { + my ($additional_condition, $type) = @_; + + $type ||= ''; + + my $condition; + + $condition = "userid=\'$userid\' "; + + $condition .= "and $additional_condition" + if $additional_condition; + + return count_distinct ('log', 'sender', $condition); +} # countlog + +1; diff --git a/maps/bin/MAPSDB.sql b/maps/bin/MAPSDB.sql new file mode 100644 index 0000000..0a2a3fc --- /dev/null +++ b/maps/bin/MAPSDB.sql @@ -0,0 +1,96 @@ +------------------------------------------------------------------------------- +-- +-- File: $RCSFile$ +-- Revision: $Revision: 1.1 $ +-- Description: This file creates the MAPS database. +-- Author: Andrew@DeFaria.com +-- Created: Tue May 13 13:28:18 PDT 2003 +-- Modified: $Date: 2013/06/12 14:05:47 $ +-- Language: SQL +-- +-- Copyright (c) 2000-2006, Andrew@DeFaria.com, all rights reserved +-- +------------------------------------------------------------------------------- +-- Warning: The following line will delete the old database! +drop database if exists MAPS; + +-- Create a new database +create database MAPS; + +-- Now let's focus on this new database +use MAPS; + +-- user: Valid users and their passwords are contained here +create table user ( + userid varchar (128) not null, + name tinytext not null, + email varchar (128) not null, + password tinytext not null, + primary key (userid) +) type=innodb; -- user + +-- useropts: User's options are stored here +create table useropts ( + userid varchar (128) not null, + name tinytext, + value varchar (128), + key user_index (userid), + foreign key (userid) references user (userid) on delete cascade +) type=innodb; -- useropts + +-- email: Table that holds the email +create table email ( + userid varchar (128) not null, + sender varchar (128) not null, + subject varchar (255), + timestamp datetime, + data longblob, + key user_index (userid), + foreign key (userid) references user (userid) on delete cascade, + key sender_index (sender) +) type=innodb; -- email + +-- whitelist: Table holds the users' whitelists +create table list ( + userid varchar (128) not null, + type enum ("white", "black", "null") not null, + pattern varchar (128), + domain varchar (128), + comment varchar (128), + sequence smallint, + hit_count integer, + last_hit datetime, + key user_index (userid), + key user_listtype (userid, type), + unique (userid, type, sequence), + foreign key (userid) references user (userid) on delete cascade +) type=innodb; -- list + +-- log: Table to hold log information +create table log ( + userid varchar (128) not null, + timestamp datetime, + sender varchar (128), + type enum ( + "blacklist", + "debug", + "error", + "info", + "mailloop", + "nulllist", + "registered", + "returned", + "whitelist" + ) not null, + message varchar (255) not null, + key user_index (userid), + foreign key (userid) references user (userid) on delete cascade +) type=innodb; -- log + +-- Create users +--grant all privileges +-- on MAPS.* to mapsadmin@"%" identified by "mapsadmin"; +--grant select +-- on MAPS.* to mapsreader@"%" identified by "reader"; +--grant insert, select, update, delete +-- on MAPS.* to mapswriter@"%" identified by "writer"; diff --git a/maps/bin/MAPSDeliver b/maps/bin/MAPSDeliver new file mode 100755 index 0000000..954847b --- /dev/null +++ b/maps/bin/MAPSDeliver @@ -0,0 +1,93 @@ +#!/usr/bin/perl +################################################################################ +# +# File: $RCSfile: MAPSDeliver,v $ +# Revision: $Revision: 1.1 $ +# Description: This script simply delivers the mail. It is separated out so +# it can be the only portion that is setgid to the group mail +# for the purposes of being able to deliver the mail to the users +# maildrop +# Author: Andrew@DeFaria.com +# Created: Fri Nov 29 14:17:21 2002 +# Modified: $Date: 2013/06/12 14:05:47 $ +# Language: perl +# +# (c) Copyright 2000-2006, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +use strict; +use warnings; + +use English; +use FindBin; + +# Untaint $FindBin::Bin +my $lib; + +BEGIN { + if ($FindBin::Bin =~ /^(.*)$/) { + $lib = $1; + } # if +} # BEGIN + +use lib $lib; + +use MAPSFile; +use MAPSDB; +use MAPSLog; + +sub DeliverMail ($$) { + my ($userid, $msgfileName) = @_; + + # Switch to group mail + $EGID = getgrnam "mail"; + + # Untaint $userid + if ($userid =~ /^([-\@\w.]+)$/) { + $userid = $1; + } # if + + # Open maildrop file + open my $maildrop, '>>', "/var/mail/$userid" + or return "Unable to open maildrop file (/var/mail/$userid): $!"; + + # Open msgfile + open my $msgfile, '<', $msgfileName + or return "Unable to open msgfile ($msgfileName): $!"; + + # Lock file + Lock $maildrop; + + # Write msgfile -> $maildrop + print $maildrop $_ + while (<$msgfile>); + + # Unlock the file + Unlock $maildrop; + + # Close files + close $maildrop; + close $msgfile; + + return; +} # DeliverMail + +# Main +die 'User id not specified' unless $ARGV [0]; +die 'Msgfile not specified' unless $ARGV [1]; + +my $userid = shift @ARGV; +my $msgfile = shift @ARGV; + +my $err = DeliverMail $userid, $msgfile; + +if ($err) { + OpenDB 'mapsadmin', 'mapsadmin'; + + MAPSDB::SetContext $userid; + + Error $err; +} # if + +exit 1 if $err; +exit 0; diff --git a/maps/bin/MAPSFile.pm b/maps/bin/MAPSFile.pm new file mode 100644 index 0000000..f9fc4c6 --- /dev/null +++ b/maps/bin/MAPSFile.pm @@ -0,0 +1,43 @@ +#!/usr/bin/perl +################################################################################ +# +# File: $RCSfile: MAPSFile.pm,v $ +# Revision: $Revision: 1.1 $ +# Description: File manipulation routines for MAPS. +# Author: Andrew@DeFaria.com +# Created: Fri Nov 29 14:17:21 2002 +# Modified: $Date: 2013/06/12 14:05:47 $ +# Language: perl +# +# (c) Copyright 2000-2006, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +package MAPSFile; + +use strict; +use vars qw (@ISA @EXPORT); + +use Fcntl ':flock'; # import LOCK_* constants + +use Exporter; +@ISA = qw (Exporter); + +@EXPORT = qw ( + Lock + Unlock +); + +sub Lock { + my $file = shift; + + flock ($file, LOCK_EX); + # and, in case someone appended while we were waiting... + seek ($file, 0, 2); +} # lock + +sub Unlock { + my $file = shift; + flock ($file,LOCK_UN); +} # unlock + +1; diff --git a/maps/bin/MAPSLog.pm b/maps/bin/MAPSLog.pm new file mode 100644 index 0000000..d52587b --- /dev/null +++ b/maps/bin/MAPSLog.pm @@ -0,0 +1,115 @@ +#!/usr/bin/perl +################################################################################# +# +# File: $RCSfile: MAPSLog.pm,v $ +# Revision: $Revision: 1.1 $ +# Description: MAPS routines for logging. +# Author: Andrew@DeFaria.com +# Created: Fri Nov 29 14:17:21 2002 +# Modified: $Date: 2013/06/12 14:05:47 $ +# Language: perl +# +# (c) Copyright 2000-2006, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +package MAPSLog; + +use strict; + +use FindBin; + +use lib $FindBin::Bin; + +use MAPSDB; +use MAPSUtil; +use vars qw (@ISA @EXPORT); +use Exporter; + +@ISA = qw (Exporter); + +@EXPORT = qw ( + Debug + Error + GetStats + Info + Logmsg + countlog + getstats + @Types +); + +our @Types = ( + 'returned', + 'whitelist', + 'blacklist', + 'registered', + 'mailloop', + 'nulllist' +); + +sub countlog (;$$) { + my ($condition, $type) = @_; + + return MAPSDB::countlog $condition, $type; +} # countlog + +sub nbr_msgs ($) { + my ($sender) = @_; + + return MAPSDB::FindEmail $sender; +} # nbr_msgs + +sub GetStats (;$$) { + my ($nbr_days, $date) = @_; + + $nbr_days ||= 1; + $date ||= Today2SQLDatetime + + my %dates; + + while ($nbr_days > 0) { + my $ymd = substr $date, 0, 10; + my $sod = $ymd . ' 00:00:00'; + my $eod = $ymd . ' 23:59:59'; + + my %stats; + + foreach (@Types) { + my $condition = "log.type=\'$_\' and (log.timestamp > \'$sod\' and log.timestamp < \'$eod\')"; + $stats{$_} = countlog $condition, $_; + } # foreach + + $dates{$ymd} = \%stats; + + $date = SubtractDays $date, 1; + $nbr_days--; + } # while + + return %dates +} # GetStats + +sub Logmsg ($$$) { + my ($type, $sender, $msg) = @_; + + AddLog $type, $sender, $msg; +} # logmsg + +sub Debug ($) { + my ($msg) = @_; + + Logmsg 'debug', '', $msg; +} # Debug + +sub Error ($) { + my ($msg) = @_; + + Logmsg 'error', '', $msg; +} # Error + +sub Info ($) { + my ($msg) = @_; + + Logmsg 'info', '', $msg; +} # info + +1; diff --git a/maps/bin/MAPSUtil.pm b/maps/bin/MAPSUtil.pm new file mode 100644 index 0000000..0d234d2 --- /dev/null +++ b/maps/bin/MAPSUtil.pm @@ -0,0 +1,265 @@ +#!/usr/bin/perl +################################################################################ +# +# File: $RCSfile: MAPSUtil.pm,v $ +# Revision: $Revision: 1.1 $ +# Description: MAPS Utilities +# Author: Andrew@DeFaria.com +# Created: Fri Nov 29 14:17:21 2002 +# Modified: $Date: 2013/06/12 14:05:47 $ +# Language: perl +# +# (c) Copyright 2000-2006, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +package MAPSUtil; + +use strict; +use vars qw (@ISA @EXPORT); + +@ISA = qw (Exporter); + +@EXPORT = qw ( + FormatDate + FormatTime + SQLDatetime2UnixDatetime + SubtractDays + Today2SQLDatetime + UnixDatetime2SQLDatetime +); + +# Forwards +sub FormatDate; +sub FormatTime; +sub SQLDatetime2UnixDatetime; +sub SubtractDays; +sub Today2SQLDatetime; +sub UnixDatetime2SQLDatetime; + +sub FormatDate { + my $date = shift; + + return substr ($date, 5, 2) . + "/" . + substr ($date, 8, 2) . + "/" . + substr ($date, 0, 4); +} # FormatDate + +sub FormatTime { + my $time = shift; + + my $hours = substr $time, 0, 2; + my $minutes = substr $time, 3, 2; + my $AmPm = $hours > 12 ? "Pm" : "Am"; + + $hours = $hours - 12 if $hours > 12; + + return "$hours:$minutes $AmPm"; +} # FormatTime + +sub SQLDatetime2UnixDatetime { + my $sqldatetime = shift; + + my %months = ( + "01" => "Jan", + "02" => "Feb", + "03" => "Mar", + "04" => "Apr", + "05" => "May", + "06" => "Jun", + "07" => "Jul", + "08" => "Aug", + "09" => "Sep", + "10" => "Oct", + "11" => "Nov", + "12" => "Dec" + ); + + my $year = substr $sqldatetime, 0, 4; + my $month = substr $sqldatetime, 5, 2; + my $day = substr $sqldatetime, 8, 2; + my $time = FormatTime (substr $sqldatetime, 11); + + return $months {$month} . " $day, $year \@ $time"; +} # SQLDatetime2UnixDatetime + +sub SubtractDays { + my $timestamp = shift; + my $nbr_of_days = shift; + + my @months = ( + 31, # January + 28, # February + 31, # March + 30, # April + 31, # May + 30, # June + 31, # July + 31, # August + 30, # September + 31, # October + 30, # November + 31 # Descember + ); + + my $year = substr $timestamp, 0, 4; + my $month = substr $timestamp, 5, 2; + my $day = substr $timestamp, 8, 2; + + # Convert to Julian + my $days = 0; + my $m = 1; + + foreach (@months) { + last if $m >= $month; + $m++; + $days += $_; + } # foreach + + # Subtract $nbr_of_days + $days += $day - $nbr_of_days; + + # Compute $days_in_year + my $days_in_year; + + # Adjust if crossing year boundary + if ($days <= 0) { + $year--; + $days_in_year = (($year % 4) eq 0) ? 366 : 365; + $days = $days_in_year + $days; + } else { + $days_in_year = (($year % 4) eq 0) ? 366 : 365; + } # if + + # Convert back + $month = 0; + + while ($days > 28) { + # If remaining days is less than the current month then last + last if ($days <= $months[$month]); + + # Subtract off the number of days in this month + $days -= $months[$month++]; + } # while + + # Prefix month with 0 if necessary + $month++; + if ($month < 10) { + $month = "0" . $month; + } # if + + # Prefix days with 0 if necessary + if ($days eq 0) { + $days = "01"; + } elsif ($days < 10) { + $days = "0" . $days; + } # if + + return $year . "-" . $month . "-" . $days . substr $timestamp, 10; +} # SubtractDays + +sub Today2SQLDatetime { + return UnixDatetime2SQLDatetime (scalar (localtime)); +} # Today2SQLDatetime + +sub UnixDatetime2SQLDatetime { + my $datetime = shift; + + my $orig_datetime = $datetime; + my %months = ( + "Jan" => "01", + "Feb" => "02", + "Mar" => "03", + "Apr" => "04", + "May" => "05", + "Jun" => "06", + "Jul" => "07", + "Aug" => "08", + "Sep" => "09", + "Oct" => "10", + "Nov" => "11", + "Dec" => "12" + ); + + # Some mailers neglect to put the leading day of the week field in. + # Check for this and compensate. + my $dow = substr $datetime, 0, 3; + + if ($dow ne "Mon" && + $dow ne "Tue" && + $dow ne "Wed" && + $dow ne "Thu" && + $dow ne "Fri" && + $dow ne "Sat" && + $dow ne "Sun") { + $datetime = "XXX, " . $datetime; + } # if + + # Some mailers have day before month. We need to correct this + my $day = substr $datetime, 5, 2; + + if ($day =~ /\d /) { + $day = "0" . (substr $day, 0, 1); + $datetime = (substr $datetime, 0, 5) . $day . (substr $datetime, 6); + } # if + + if ($day !~ /\d\d/) { + $day = substr $datetime, 8, 2; + } # if + + # Check for 1 digit date + if ((substr $day, 0, 1) eq " ") { + $day = "0" . (substr $day, 1, 1); + $datetime = (substr $datetime, 0, 8) . $day . (substr $datetime, 10); + } # if + + my $year = substr $datetime, 20, 4; + + if ($year !~ /\d\d\d\d/) { + $year = substr $datetime, 12, 4; + if ($year !~ /\d\d\d\d/) { + $year = substr $datetime, 12, 2; + } #if + } # if + + # Check for 2 digit year. Argh! + if (length $year == 2 or (substr $year, 2, 1) eq " ") { + $year = "20" . (substr $year, 0, 2); + $datetime = (substr $datetime, 0, 12) . "20" . (substr $datetime, 12); + } # if + + my $month_name = substr $datetime, 4, 3; + + if (!defined $months {$month_name}) { + $month_name = substr $datetime, 8, 3; + } # if + my $month = $months {$month_name}; + + my $time = substr $datetime, 11, 8; + + if ($time !~ /\d\d:\d\d:\d\d/) { + $time = substr $datetime, 17, 8 + } # if + + if (!defined $year) { + print "WARNING: Year undefined for $orig_datetime\nReturning today's date\n"; + return Today2SQLDatetime; + } # if + if (!defined $month) { + print "Month undefined for $orig_datetime\nReturning today's date\n"; + return Today2SQLDatetime; + } # if + if (!defined $day) { + print "Day undefined for $orig_datetime\nReturning today's date\n"; + return Today2SQLDatetime; + } # if + if (!defined $time) { + print "Time undefined for $orig_datetime\nReturning today's date\n"; + return Today2SQLDatetime; + } # if + + return "$year-$month-$day $time"; +} # UnixDatetime2SQLDatetime + +1; diff --git a/maps/bin/MAPSWeb.pm b/maps/bin/MAPSWeb.pm new file mode 100644 index 0000000..dc3d881 --- /dev/null +++ b/maps/bin/MAPSWeb.pm @@ -0,0 +1,338 @@ +################################################################################# +# +# File: $RCSfile: MAPSWeb.pm,v $ +# Revision: $Revision: 1.1 $ +# Description: Routines for generating portions of MAPSWeb +# Author: Andrew@DeFaria.com +# Created: Fri Nov 29 14:17:21 2002 +# Modified: $Date: 2013/06/12 14:05:47 $ +# Language: perl +# +# (c) Copyright 2000-2006, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +package MAPSWeb; + +use strict; + +use FindBin; + +use lib $FindBin::Bin; + +use MAPS; +use MAPSLog; +use MAPSUtil; + +use CGI qw (:standard *table start_Tr end_Tr start_div end_div); +use vars qw (@ISA @EXPORT); + +use Exporter; + +@ISA = qw (Exporter); + +@EXPORT = qw ( + Debug + DisplayError + Footing + Heading + NavigationBar +); + +sub getquickstats { + my $date = shift; + + my %dates = GetStats (1, $date); + + foreach (@MAPSLog::Types) { + $dates{$date}{processed} += $dates{$date}{$_}; + } # foreach + + return %dates; +} # getquickstats + +sub displayquickstats { + # Quick stats are today only. + my $today = Today2SQLDatetime; + my $time = substr $today, 11; + my $date = substr $today, 0, 10; + my %dates = getquickstats $date; + + print start_div {-class => 'quickstats'}; + print h4 {-class => 'header', + -align => 'center'}, + 'Today\'s Activity'; + print p {-align => 'center'}, + b ('as of ' . FormatTime ($time)); + print start_table { + -align => 'center', + -border => 0, + -cellspacing => 0, + -cellpadding => 2}; + print start_Tr {-align => 'right'}; + print + td {-class => 'smalllabel', + -align => 'right'}, + 'Processed'; + print + td {-class => 'smallnumber', + -align => 'right'}, + $dates{$date}{'processed'}; + print + td {-class => 'smallnumber', + -align => 'right'}, + 'n/a'; + print end_Tr; + + foreach (@MAPSLog::Types) { + print start_Tr {-align => 'right'}; + + my $value = $dates{$date}{$_}; + my $percent; + if ($_ eq 'mailloop' || $_ eq 'registered') { + $percent = 'n/a'; + } else { + $percent = $dates{$date}{processed} == 0 ? + 0 : $dates{$date}{$_} / $dates{$date}{processed} * 100; + $percent = sprintf '%5.1f%s', $percent, '%'; + } # if + my $stat = $value == 0 ? + 0 : a {-href => "detail.cgi?type=$_;date=$date"}, $value; + print + td {-class => 'smalllabel'}, ucfirst ($_); + print + td {-class => 'smallnumber'}, $stat; + print + td {-class => 'smallnumber'}, $percent; + print end_Tr; + } # foreach + print end_table; + print end_div; +} # displayquickstats + +sub Footing (;$) { + my ($table_name) = @_; + + # General footing (copyright). Note we calculate the current year + # so that the copyright automatically extends itself. + my $year = substr ((scalar (localtime)), 20, 4); + + print start_div {-class => "copyright"}; + print "Copyright © 2001-$year - All rights reserved"; + print br ( + a ({-href => 'http://defaria.com'}, + 'Andrew DeFaria'), + a ({-href => 'mailto:Andrew@DeFaria.com'}, + '<Andrew@DeFaria.com>')); + print end_div; + + print end_div; # This div ends "content" which was started in Heading + print "" + if $table_name; + print end_html; +} # Footing + +sub Debug ($) { + my ($msg) = @_; + + print br, font ({ -class => 'error' }, 'DEBUG: '), $msg; +} # Debug + +sub DisplayError ($) { + my ($errmsg) = @_; + + print h3 ({-class => 'error', + -align => 'center'}, 'ERROR: ' . $errmsg); + + Footing; + + exit 1; +} # DisplayError + +# This subroutine puts out the header for web pages. It is called by +# various cgi scripts thus has a few parameters. +sub Heading ($$$$;$$@) { + my ($action, # One of getcookie, setcookie, unsetcookie + $userid, # User id (if setting a cookie) + $title, # Title string + $h1, # H1 header + $h2, # H2 header (optional) + $table_name, # Name of table in page, if any + @scripts) = @_; # Array of JavaScript scripts to include + + my @java_scripts; + my $cookie; + + # Since CheckAddress appears on all pages (well except for the login + # page) include it by default along with MAPSUtils.js + push @java_scripts, [ + {-language => 'JavaScript1.2', + -src => '/maps/JavaScript/MAPSUtils.js'}, + {-language => 'JavaScript1.2', + -src => '/maps/JavaScript/CheckAddress.js'} + ]; + + # Add on any additional JavaScripts that the caller wants. Note the + # odd single element array of hashes but that's what CGI requires! + # Build up scripts from array + foreach (@scripts) { + push @{$java_scripts[0]}, + {-language => 'JavaScript1.2', + -src => "/maps/JavaScript/$_"} + } # foreach + + # Since Heading is called from various scripts we sometimes need to + # set a cookie, other times delete a cookie but most times return the + # cookie. + if ($action eq 'getcookie') { + # Get userid from cookie + $userid = cookie ('MAPSUser'); + } elsif ($action eq 'setcookie') { + $cookie = cookie ( + -name => 'MAPSUser', + -value => $userid, + -expires => '+1y', + -path => '/maps' + ); + } elsif ($action eq 'unsetcookie') { + $cookie = cookie ( + -name => 'MAPSUser', + -value => '', + -expires => '-1d', + -path => '/maps' + ); + } # if + + print + header (-title => "MAPS: $title", + -cookie => $cookie); + + if (defined $table_name) { + print + start_html (-title => "MAPS: $title", + -author => 'Andrew\@DeFaria.com', + -style => {-src => '/maps/css/MAPSStyle.css'}, + -onResize => "AdjustTableWidth (\"$table_name\");", + -head => [ + Link ({-rel => 'icon', + -href => '/maps/MAPS.png', + -type => 'image/png'}), + Link ({-rel => 'shortcut icon', + -href => '/maps/favicon.ico'}) + ], + -script => @java_scripts); + } else { + print + start_html (-title => "MAPS: $title", + -author => 'Andrew\@DeFaria.com', + -style => {-src => '/maps/css/MAPSStyle.css'}, + -head => [ + Link ({-rel => 'icon', + -href => '/maps/MAPS.png', + -type => 'image/png'}), + Link ({-rel => 'shortcut icon', + -href => '/maps/favicon.ico'})], + -script => @java_scripts); + } # if + + print start_div {class => 'heading'}; + print h2 {-align => 'center', + -class => 'header'}, + font ({-class => 'standout'}, 'MAPS'), + $h1; + + if (defined $h2 && $h2 ne '') { + print h3 {-align => 'center', + -class => 'header'}, + $h2; + } # if + print end_div; + + # Start body content + print start_div {-class => 'content'}; + + return $userid +} # Heading + +sub NavigationBar { + my $userid = shift; + + print start_div {-id => 'leftbar'}; + + if (!defined $userid) { + print div ({-class => 'username'}, 'Welcome to MAPS'); + print div ({-class => 'menu'}, + (a {-href => '/maps/doc/'}, + 'What is MAPS?
    '), + (a {-href => '/maps/doc/SPAM.html'}, + 'What is SPAM?
    '), + (a {-href => '/maps/doc/Requirements.html'}, + 'Requirements
    '), + (a {-href => '/maps/SignupForm.html'}, + 'Signup
    '), + (a {-href => '/maps/doc/Using.html'}, + 'Using MAPS
    '), + (a {-href => '/maps/doc/'}, + 'Help
    '), + ); + } else { + print div ({-class => 'username'}, 'Welcome '. ucfirst $userid); + print div ({-class => 'menu'}, + (a {-href => '/maps/'}, + 'MAPS Home
    '), + (a {-href => '/maps/bin/stats.cgi'}, + 'Statistics
    '), + (a {-href => '/maps/bin/editprofile.cgi'}, + 'Edit Profile
    '), + (a {-href => '/maps/php/Reports.php'}, + 'Reports
    '), + (a {-href => '/maps/php/list.php?type=white'}, + 'White List
    '), + (a {-href => '/maps/php/list.php?type=black'}, + 'Black List
    '), + (a {-href => '/maps/php/list.php?type=null'}, + 'Null List
    '), + (a {-href => '/maps/doc/'}, + 'Help
    '), + (a {-href => '/maps/adm/'}, + 'MAPS Admin
    '), + (a {-href => '/maps/?logout=yes'}, + 'Logout'), + ); + print start_div {-class => 'search'}; + print start_form {-method => 'get', + -action => '/maps/bin/search.cgi', + -name => 'search'}; + print 'Search Sender/Subject', + textfield {-class => 'searchfield', + -id => 'searchfield', + -name => 'str', + -size => 20, + -maxlength => 255, + -value => '', + -onclick => "document.search.str.value = '';"}; + print end_form; + print end_div; + + displayquickstats; + + print start_div {-class => 'search'}; + print start_form {-method => 'post', + -action => 'javascript://', + -name => 'address', + -onsubmit => 'checkaddress(this);'}; + print 'Check Email Address', + textfield {-class => 'searchfield', + -id => 'searchfield', + -name => 'email', + -size => 20, + -maxlength => 255, + -value => '', + -onclick => "document.address.email.value = '';"}; + print end_form; + print end_div; + } # if + + print end_div; +} # NavigationBar + +1; diff --git a/maps/bin/Search.gif b/maps/bin/Search.gif new file mode 100644 index 0000000000000000000000000000000000000000..2d8caca33f0cba22eec30277aad48ac0466b7a5e GIT binary patch literal 948 zcmZ?wbhEHb3}6ss_|Cwfsp$286pV(zNDTqSpDc_FK%fI6KzYJ}V 'error'}, "The email address $sender is already on ${Userid}'s $type list"; + } else { + Add2Blacklist $sender, $userid, $comment; + print br "The email address, $sender, has been added to ${Userid}'s $type list"; + + # Now remove this entry from the other lists (if present) + foreach my $otherlist ('white', 'null') { + my $sth = FindList $otherlist, $sender; + my ($sequence, $count); + + ($_, $_, $_, $_, $_, $sequence) = GetList $sth; + + if ($sequence) { + $count = DeleteList $otherlist, $sequence; + print br "Removed $sender from ${Userid}'s " . ucfirst $otherlist . ' list' + if $count > 0; + + ResequenceList $userid, $otherlist; + } # if + } # foreach + } # if + + $nextseq++; + } # while +} # Add2List + +# Main +$userid = Heading ( + 'getcookie', + '', + 'Add to Black List', + 'Add to Black List', +); + +$Userid = ucfirst $userid; + +SetContext $userid; + +NavigationBar $userid; + +Add2List; + +print start_form { + -method => 'post', + -action => 'processaction.cgi', + -name => 'list' +}; + +print '

    ', + hidden ({-name => 'type', + -default => $type}), + submit ({-name => 'action', + -value => 'Add New Entry'}), + '
    '; + +Footing; + +exit; diff --git a/maps/bin/add2nulllist.cgi b/maps/bin/add2nulllist.cgi new file mode 100755 index 0000000..7fcf668 --- /dev/null +++ b/maps/bin/add2nulllist.cgi @@ -0,0 +1,110 @@ +#!/usr/bin/perl +################################################################################ +# +# File: $RCSfile: add2nulllist.cgi,v $ +# Revision: $Revision: 1.1 $ +# Description: Add an email address to the nulllist +# Author: Andrew@DeFaria.com +# Created: Mon Jan 16 20:25:32 PST 2006 +# Modified: $Date: 2013/06/12 14:05:47 $ +# Language: Perl +# +# (c) Copyright 2000-2006, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +use strict; +use warnings; + +use FindBin; +$0 = $FindBin::Script; + +use lib $FindBin::Bin; + +use MAPS; +use MAPSLog; +use MAPSWeb; + +use CGI qw/:standard *table/; +use CGI::Carp 'fatalsToBrowser'; + +my $userid; +my $Userid; +my $type = 'null'; + +sub Add2List { + my $sender = ''; + my $nextseq = MAPSDB::GetNextSequenceNo $userid, $type; + + while () { + my $pattern = param "pattern$nextseq"; + my $domain = param "domain$nextseq"; + my $comment = param "comment$nextseq"; + + last if ((!defined $pattern || $pattern eq '') && + (!defined $domain || $domain eq '')); + + $sender = lc "$pattern\@$domain"; + + my ($status, $rule) = OnNulllist $sender; + + if ($status != 0) { + print br {-class => 'error'}, "The email address $sender is already on ${Userid}'s $type list"; + } else { + Add2Nulllist $sender, $userid, $comment; + + print br "The email address, $sender, has been added to ${Userid}'s $type list"; + + # Now remove this entry from the other lists (if present) + foreach my $otherlist ('white', 'black') { + my $sth = FindList $otherlist, $sender; + my ($sequence, $count); + + ($_, $_, $_, $_, $_, $sequence) = GetList $sth; + + if ($sequence) { + $count = DeleteList $otherlist, $sequence; + + print br "Removed $sender from ${Userid}'s " . ucfirst $otherlist . ' list' + if $count > 0; + + ResequenceList $userid, $otherlist; + } # if + } # foreach + } # if + + $nextseq++; + } # while +} # Add2List + +# Main +$userid = Heading ( + 'getcookie', + '', + 'Add to Null List', + 'Add to Null List', +); + +SetContext $userid; + +NavigationBar $userid; + +$Userid = ucfirst $userid; + +Add2List; + +print start_form { + -method => 'post', + -action => 'processaction.cgi', + -name => 'list' +}; + +print '

    ', + hidden ({-name => 'type', + -default => $type}), + submit ({-name => 'action', + -value => 'Add New Entry'}), + '
    '; + +Footing; + +exit; diff --git a/maps/bin/add2nulllist.pl b/maps/bin/add2nulllist.pl new file mode 100755 index 0000000..21755b3 --- /dev/null +++ b/maps/bin/add2nulllist.pl @@ -0,0 +1,96 @@ +#!/usr/bin/perl +use strict; +use warnings; + +use FindBin; + +use lib $FindBin::Bin, '/opt/clearscm/lib'; + +use MAPS; +use MAPSLog; +use MAPSWeb; +use Display; + +# Highly specialized! +my $userid = $ENV{USER}; +my $Userid; +my $type = "null"; + +sub GetItems { + my $filename = shift; + + my @items; + + open FILE, $filename + or error "Unable to open $filename - $!", 1; + + while () { + my @fields = split; + my %item; + + my @address = split /\@/, $fields [0]; + + $item {pattern} = $address [0]; + $item {domain} = $address [1]; + $item {comment} = $fields [1] ? $fields [1] : ""; + + push @items, \%item; + } # while + + return @items; +} # GetItems + +sub Add2List { + my @items = @_; + + my $sender = ""; + my $nextseq = MAPSDB::GetNextSequenceNo $userid, $type; + + foreach (@items) { + my %item = %{$_}; + + my $pattern = $item {pattern}; + my $domain = $item {domain}; + my $comment = $item {comment}; + + display_nolf "Adding $pattern\@$domain ($comment) to null list ($nextseq)..."; + last if ((!defined $pattern || $pattern eq "") && + (!defined $domain || $domain eq "")); + $sender = lc ("$pattern\@$domain"); + + if (OnNulllist $sender) { + display " Already on list"; + } else { + Add2Nulllist $sender, $userid, $comment; + display " done"; + + # Now remove this entry from the other lists (if present) + foreach my $otherlist ("white", "black") { + my $sth = FindList $otherlist, $sender; + my ($sequence, $count); + ($_, $_, $_, $_, $_, $sequence) = GetList $sth; + if (defined $sequence) { + $count = DeleteList $otherlist, $sequence; + } # if + } # foreach + } # if + $nextseq++; + } # while +} # Add2List + +# Main +my $filename; + +if ($ARGV [0]) { + $filename = $ARGV [0]; +} else { + error "Must specify a filename of addresses to null list", 1; +} # if + +SetContext $userid; + +$Userid = ucfirst $userid; + +Add2List (GetItems $filename); + +exit; diff --git a/maps/bin/add2whitelist.cgi b/maps/bin/add2whitelist.cgi new file mode 100755 index 0000000..cb0eb26 --- /dev/null +++ b/maps/bin/add2whitelist.cgi @@ -0,0 +1,122 @@ +#!/usr/bin/perl +################################################################################ +# +# File: $RCSfile: add2whitelist.cgi,v $ +# Revision: $Revision: 1.1 $ +# Description: Add an email address to the blacklist +# Author: Andrew@DeFaria.com +# Created: Mon Jan 16 20:25:32 PST 2006 +# Modified: $Date: 2013/06/12 14:05:47 $ +# Language: Perl +# +# (C) Copyright 2000-2006, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +use strict; +use warnings; + +use FindBin; +$0 = $FindBin::Script; + +use lib $FindBin::Bin; + +use MAPS; +use MAPSLog; +use MAPSWeb; + +use CGI qw/:standard *table/; +use CGI::Carp 'fatalsToBrowser'; + +my $userid; +my $Userid; +my $type = 'white'; + +sub Add2List { + my $sender = ''; + my $nextseq = MAPSDB::GetNextSequenceNo $userid, $type; + + while () { + my $pattern = param "pattern$nextseq"; + my $domain = param "domain$nextseq"; + my $comment = param "comment$nextseq"; + + last if ((!defined $pattern || $pattern eq '') && + (!defined $domain || $domain eq '')); + + $sender = lc "$pattern\@$domain"; + + my ($status, $rule) = OnWhitelist $sender, $userid; + + if ($status != 0) { + print br {-class => 'error'}, "The email address $sender is already on ${Userid}'s $type list"; + } else { + my $messages = Add2Whitelist $sender, $userid, $comment; + + print br "The email address, $sender, has been added to ${Userid}'s $type list"; + if ($messages > 0) { + if ($messages == 1) { + print br 'Your previous message has been delivered'; + } else { + print br "Your previous $messages messages have been delivered"; + } # if + } elsif ($messages == -1) { + print br {-class => 'error'}, 'Unable to deliver message'; + } else { + print br 'Unable to find any old messages but future messages will now be delivered.'; + } # if + + # Now remove this entry from the other lists (if present) + foreach my $otherlist ('black', 'null') { + my $sth = FindList $otherlist, $sender; + my ($sequence, $count); + + ($_, $_, $_, $_, $_, $sequence) = GetList $sth; + + if ($sequence) { + $count = DeleteList $otherlist, $sequence; + print br "Removed $sender from ${Userid}'s " . ucfirst $otherlist . ' list' + if $count > 0; + + ResequenceList $userid, $otherlist; + } # if + } # foreach + } # if + + $nextseq++; + } # while +} # Add2List + +# Main +$userid = Heading ( + 'getcookie', + '', + 'Add to White List', + 'Add to White List', +); + +$userid ||= $ENV{USER}; + +$Userid = ucfirst $userid; + +SetContext $userid; + +NavigationBar $userid; + +Add2List; + +print start_form { + -method => 'post', + -action => 'processaction.cgi', + -name => 'list' +}; + +print '

    ', + hidden ({-name => 'type', + -default => $type}), + submit ({-name => 'action', + -value => 'Add New Entry'}), + '
    '; + +Footing; + +exit; diff --git a/maps/bin/checkaddress b/maps/bin/checkaddress new file mode 100755 index 0000000..19a0df5 --- /dev/null +++ b/maps/bin/checkaddress @@ -0,0 +1,67 @@ +#!/usr/bin/perl +################################################################################ +# +# File: $RCSfile: checkaddress,v $ +# Revision: $Revision: 1.1 $ +# Description: Check an email address +# Author: Andrew@DeFaria.com +# Created: Mon Jan 16 20:25:32 PST 2006 +# Modified: $Date: 2013/06/12 14:05:47 $ +# Language: perl +# +# (c) Copyright 2000-2006, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +use strict; +use warnings; + +use FindBin; + +use lib $FindBin::Bin, '/opt/clearscm/lib'; + +use MAPS; +use Display; + +error ("Must specify an email address to check", 1) + if !$ARGV[0] or $ARGV[0] eq ""; + +foreach (@ARGV) { + my $sender = lc $_; + + my ($status, $rule); + + my $username = lc $ENV{USER}; + + my ($user, $domain) = $sender =~ /(.+)\@(.+)/; + + unless ($user and $domain) { + error "Illegal email address $sender"; + + next; + } # unless + + if ($domain eq "defaria.com" and $user ne $username) { + display "Nulllist - $sender is from this domain but is not from $username"; + next; + } # if + + ($status, $rule) = OnNulllist $sender; + + if ($status) { + display "Sender $sender would be nulllist'ed\n$rule"; + } else { + ($status, $rule) = OnBlacklist $sender; + + if ($status) { + display "Sender $sender would be blacklist'ed\n$rule"; + } else { + ($status, $rule) = OnWhitelist $sender; + + if ($status) { + display "Sender $sender would be whitelist'ed\n$rule"; + } else { + display "Sender $sender would be returned"; + } # if + } # if + } # if +} # foreach diff --git a/maps/bin/checkaddress.cgi b/maps/bin/checkaddress.cgi new file mode 100755 index 0000000..34ec004 --- /dev/null +++ b/maps/bin/checkaddress.cgi @@ -0,0 +1,96 @@ +#!/usr/bin/perl +################################################################################ +# +# File: $RCSfile: checkaddress.cgi,v $ +# Revision: $Revision: 1.1 $ +# Description: Check an email address +# Author: Andrew@DeFaria.com +# Created: Mon Jan 16 20:25:32 PST 2006 +# Modified: $Date: 2013/06/12 14:05:47 $ +# Language: perl +# +# (c) Copyright 2000-2006, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +use strict; +use warnings; + +use FindBin; +$0 = $FindBin::Script; + +use lib $FindBin::Bin; + +use MAPS; + +use CGI qw (:standard); + +# Get MAPSUser from cookie +my $userid; + +if (param "user") { + $userid = param "user"; +} else { + $userid = cookie ("MAPSUser"); +} # if + +my $sender = param ("sender"); + +sub Heading { + print + header (-title => "MAPS: Check Address"), + start_html (-title => "MAPS: Check Address", + -author => "Andrew\@DeFaria.com"); + print h3 {-align => "center", + -class => "header"}, + "MAPS: Checking address $sender"; +} # Heading + +sub Body { + my ($status, $rule); + + ($status, $rule) = OnNulllist $sender; + if ($status) { + print div {-align => "center"}, + font {-color => "grey"}, + "Messages from", b ($sender), "will be", b ("discarded"), br, hr; + print $rule; + } else { + ($status, $rule) = OnBlacklist $sender; + if ($status) { + print div {-align => "center"}, + font {-color => "black"}, + "Messages from", b ($sender), "will be", b ("blacklisted"), br, hr; + print $rule; + } else { + ($status, $rule) = OnWhitelist $sender; + if ($status) { + print div {-align => "center"}, + font {-color => "green"}, + "Messages from", b ($sender), "will be", b ("delivered"), br, hr; + print $rule; + } else { + print div {-align => "center"}, + font {-color => "red"}, + "Messages from", b ($sender), "will be", b ("returned"); + } # if + } # if + } # if + + print br div {-align => "center"}, + submit (-name => "submit", + -value => "Close", + -onClick => "window.close (self)"); +} # Body + +sub Footing { + print end_html; +} # Footing + +# Main +SetContext $userid; +Heading; +Body; +Footing; + +exit; + diff --git a/maps/bin/detail.cgi b/maps/bin/detail.cgi new file mode 100755 index 0000000..de7fd29 --- /dev/null +++ b/maps/bin/detail.cgi @@ -0,0 +1,313 @@ +#!/usr/bin/perl +################################################################################# +# File: $RCSfile: detail.cgi,v $ +# Revision: $Revision: 1.1 $ +# Description: Displays list of email addresses based on report type. +# Author: Andrew@DeFaria.com +# Created: Fri Nov 29 14:17:21 2002 +# Modified: $Date: 2013/06/12 14:05:47 $ +# Language: perl +# +# (c) Copyright 2000-2006, Andrew@DeFaria.com, all rights reserved. +# +################################################################################use strict; +use warnings; + +use MIME::Words qw(:all); +use FindBin; +$0 = $FindBin::Script; + +use lib $FindBin::Bin; + +use MAPS; +use MAPSLog; +use MAPSUtil; +use MAPSWeb; +use CGI qw (:standard *table start_td end_td start_Tr end_Tr start_div end_div); +use CGI::Carp 'fatalsToBrowser'; + +my $type = param ('type'); +my $next = param ('next'); +my $lines = param ('lines'); +my $date = param ('date'); + +$date ||= ''; + +my $userid; +my $current; +my $last; +my $prev; +my $total; +my $table_name = 'detail'; + +my %types = ( + 'blacklist' => [ + 'Blacklist report', + 'The following blacklisted users attempted to email you' + ], + 'whitelist' => [ + 'Delivered report', + 'Delivered email from the following users' + ], + 'nulllist' => [ + 'Discarded report', + 'Discarded messages from the following users' + ], + 'error' => [ + 'Error report', + 'Errors detected' + ], + 'mailloop' => [ + 'MailLoop report', + 'Automatically detected mail loops from the following users' + ], + 'registered' => [ + 'Registered report', + 'The following users have recently registered' + ], + 'returned' => [ + 'Returned report', + 'Sent Register reply to the following users' + ] +); + +sub MakeButtons { + my $type = shift; + + my $prev_button = $prev >= 0 ? + a ({-href => "detail.cgi?type=$type;date=$date;next=$prev"}, + 'Previous') : ''; + my $next_button = ($next + $lines) < $total ? + a {-href => "detail.cgi?type=$type;date=$date;next=" . ($next + $lines)}, + 'Next' : ''; + + my $buttons = $prev_button; + + if ($type eq 'whitelist') { + $buttons = $buttons . + submit ({-name => 'action', + -value => 'Blacklist Marked', + -onClick => 'return CheckAtLeast1Checked (document.detail);'}) . + submit ({-name => 'action', + -value => 'Nulllist Marked', + -onClick => 'return CheckAtLeast1Checked (document.detail);'}) . + submit ({-name => 'action', + -value => 'Reset Marks', + -onClick => 'return ClearAll (document.detail);'}); + } elsif ($type eq 'blacklist') { + $buttons = $buttons . + submit ({-name => 'action', + -value => 'Whitelist Marked', + -onClick => 'return CheckAtLeast1Checked (document.detail);'}) . + submit ({-name => 'action', + -value => 'Nulllist Marked', + -onClick => 'return CheckAtLeast1Checked (document.detail);'}) . + submit ({-name => 'action', + -value => 'Reset Marks', + -onClick => 'return ClearAll (document.detail);'}); + } elsif ($type eq 'nulllist') { + $buttons = $buttons . + submit ({-name => 'action', + -value => 'Whitelist Marked', + -onClick => 'return CheckAtLeast1Checked (document.detail);'}) . + submit ({-name => 'action', + -value => 'Blacklist Marked', + -onClick => 'return CheckAtLeast1Checked (document.detail);'}) . + submit ({-name => 'action', + -value => 'Reset Marks', + -onClick => 'return ClearAll (document.detail);'}); + } else { + $buttons = $buttons . + submit ({-name => 'action', + -value => 'Whitelist Marked', + -onClick => 'return CheckAtLeast1Checked (document.detail);'}) . + submit ({-name => 'action', + -value => 'Blacklist Marked', + -onClick => 'return CheckAtLeast1Checked (document.detail);'}) . + submit ({-name => 'action', + -value => 'Nulllist Marked', + -onClick => 'return CheckAtLeast1Checked (document.detail);'}) . + submit ({-name => 'action', + -value => 'Reset Marks', + -onClick => 'return ClearAll (document.detail);'}); + } # if + + return $buttons . $next_button; +} # MakeButtons + +sub PrintTable { + my ($type) = @_; + + my $current = $next + 1; + + print div {-align => 'center'}, b ( + '(' . $current . '-' . $last . ' of ' . $total . ')'); + print start_form { + -method => 'post', + -action => 'processaction.cgi', + -name => 'detail' + }; + print start_table ({-align => 'center', + -id => $table_name, + -border => 0, + -cellspacing => 0, + -cellpadding => 0, + -width => '100%'}) . "\n"; + + my $buttons = MakeButtons $type; + + print start_div {-class => 'toolbar'}; + print + Tr [ + td {-class => 'tablebordertopleft', + -valign => 'middle'}, + td {-class => 'tablebordertopright', + -valign => 'middle', + -align => 'center'}, $buttons, + ]; + print end_div; + + foreach my $sender (ReturnSenders $userid, $type, $next, $lines, $date) { + my @msgs = ReturnMessages $userid, $sender; + + $next++; + print + start_Tr {-valign => 'middle'}; + print + td {-class => 'tableborder'}, small ($next, + checkbox {-name => "action$next", + -label => ''}), + hidden ({-name => "email$next", + -default => $sender}); + print + start_td {-align => 'left'}; + print + start_table {-class => 'tablerightdata', + -cellpadding => 2, + -callspacing => 0, + -border => 0, + -width => '100%', + -bgcolor => '#d4d0c8'}; + print + td {-class => 'tablelabel', + -valign => 'middle', + -width => '40'}, 'Sender:', + td {-class => 'sender', + -valign => 'middle'}, + a {-href => "mailto:$sender"}, $sender; + print + end_table; + + my $messages = 1; + + foreach (@msgs) { + my $msg_date = pop @{$_}; + my $subject = pop @{$_}; + + if ($date eq substr ($msg_date, 0, 10)) { + $msg_date = b font {-color => 'green'}, SQLDatetime2UnixDatetime $msg_date; + } else { + $msg_date = SQLDatetime2UnixDatetime $msg_date; + } # if + + $subject = $subject eq '' ? '<Unspecified>' : $subject; + $subject = decode_mimewords ($subject); + $subject =~ s/\>/>/g; + $subject =~ s/\ 'tablerightdata', + -cellpadding => 2, + -cellspacing => 2, + -border => 0, + -width => '100%'}; + my $msg_nbr = $messages; + print + Tr [ + td {-class => 'msgnbr', + -valign => 'middle', + -rowspan => 2, + -width => '2%'}, $messages++, + td {-class => 'tablelabel', + -valign => 'middle', + -width => '45'}, 'Subject:', + td {-class => 'subject', + -valign => 'middle', + -bgcolor => '#ffffff'}, + a {-href => "display.cgi?sender=$sender;msg_nbr=$msg_nbr"}, $subject, + td {-class => 'date', + -width => '130', + -valign => 'middle'}, $msg_date + ]; + print end_table; + } # foreach + print end_td; + print end_Tr; + } # foreach + + print start_div {-class => 'toolbar'}; + print + Tr [ + td {-class => 'tableborderbottomleft', + -valign => 'middle'}, + td {-class => 'tableborderbottomright', + -valign => 'middle'}, + $buttons + ]; + print end_div; + print end_table; + print end_form; +} # PrintTable + +# Main +my @scripts = ('ListActions.js'); + +my $heading_date =$date ne '' ? ' on ' . FormatDate ($date) : ''; + +$userid = Heading ( + 'getcookie', + '', + (ucfirst ($type) . ' Report'), + $types {$type} [0], + $types {$type} [1] . $heading_date, + $table_name, + @scripts +); + +$userid ||= $ENV{USER}; + +SetContext $userid; +NavigationBar $userid; + +unless ($lines) { + my %options = GetUserOptions $userid; + $lines = $options{'Page'}; +} # unless + +if ($date eq '') { + $condition .= "userid = '$userid' and type = '$type'"; +} else { + my $sod = $date . ' 00:00:00'; + my $eod = $date . ' 23:59:59'; + + $condition .= "userid = '$userid' and type = '$type' " + . "and timestamp > '$sod' and timestamp < '$eod' "; +} # if + +$total = MAPSDB::count_distinct ('log', 'sender', $condition); + +$next ||= 0; + +$last = $next + $lines < $total ? $next + $lines : $total; + +if (($next - $lines) > 0) { + $prev = $next - $lines; +} else { + $prev = $next eq 0 ? -1 : 0; +} # if + +PrintTable $type; + +Footing $table_name; + +exit; diff --git a/maps/bin/display.cgi b/maps/bin/display.cgi new file mode 100755 index 0000000..21f47b4 --- /dev/null +++ b/maps/bin/display.cgi @@ -0,0 +1,191 @@ +#!/usr/bin/perl +################################################################################ +# +# File: $RCSfile: display.cgi,v $ +# Revision: $Revision: 1.1 $ +# Description: Displays an email message +# Author: Andrew@DeFaria.com +# Created: Fri Nov 29 14:17:21 2002 +# Modified: $Date: 2013/06/12 14:05:47 $ +# Language: perl +# +# (c) Copyright 2000-2006, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +use strict; +use warnings; + +use FindBin; +$0 = $FindBin::Script; + +use lib $FindBin::Bin; + +use MAPS; +use MAPSWeb; + +use CGI qw/:standard *table/; +use CGI::Carp "fatalsToBrowser"; + +use MIME::Parser; +use MIME::Base64; +use MIME::Words qw(:all); + +my $userid = cookie ("MAPSUser"); +my $sender = param ("sender"); +my $msg_nbr = param ("msg_nbr"); +my $table_name = "message"; + +sub ParseEmail (@) { + my (@header) = @_; + + my %header; + + # First output the header information. Note we'll skip uninteresting stuff + foreach (@header) { + last if ($_ eq "" || $_ eq "\cM"); + + # Escape "<" and ">" + s/\/\>\;/; + + if (/^from:\s*(.*)/i) { + $header{From} = $1; + } elsif (/^subject:\s*(.*)/i) { + $header{Subject} = $1; + } elsif (/^date:\s*(.*)/i) { + $header{date} = $1; + } elsif (/^To:\s*(.*)/i) { + $header{to} = $1; + } # if + } # while + + return %header; +} # ParseEmail + +sub Body ($) { + my ($count) = @_; + + $count ||= 1; + + my $handle = FindEmail $sender; + + my ($userid, $sender, $subject, $timestamp, $message); + + # Need to handle multiple messages + for (my $i = 0; $i < $count; $i++) { + ($userid, $sender, $subject, $timestamp, $message) = GetEmail $handle; + } # for + + my $parser = new MIME::Parser; + + $parser->output_to_core (1); + + my $entity = $parser->parse_data ($message); + + my %header = ParseEmail @{($entity->header)[0]}; + + print p . "\n"; + print start_table ({-align => "center", + -id => $table_name, + -border => 0, + -cellspacing => 0, + -cellpadding => 0, + -width => "100%"}); + print start_table ({-align => "center", + -bgcolor => "#d4d0c8", + -border => 0, + -cellspacing => 2, + -cellpadding => 2, + -width => "100%"}) . "\n"; + print "
    \n"; + print start_table ({-align => "center", + -border => 0, + -cellspacing => 0, + -cellpadding => 2, + -bgcolor => "#ece9d8", + -width => "100%"}) . "\n"; + + foreach (keys (%header)) { + my $str = decode_mimewords ($header{$_}); + + print Tr ([ + th ({-align => "right", + -bgcolor => "#ece9d8", + -width => "8%"}, "$_:") . "\n" . + td ({-bgcolor => "white"}, $str) + ]); + } # if + + print end_table; + print "
    \n"; + print start_table ({-align => "center", + -border => 0, + -cellspacing => 0, + -cellpadding => 2, + -bgcolor => "white", + -width => "100%"}) . "\n"; + print "
    \n"; + + my @parts = $entity->parts; + + if (scalar @parts == 0) { + print '
    ';
    +    $entity->print_body;
    +    print '
    '; + } else { + foreach my $part ($entity->parts) { + # We assume here that if this part is multipart/alternative then + # there exists at least one part that is text/html and we favor + # that (since we're outputing to a web page anyway... + if ($part->mime_type eq 'multipart/alternative') { + foreach my $subpart ($part->parts) { + if ($subpart->mime_type eq 'text/html') { + $subpart->print_body; + last; + } elsif ($subpart->mime_type eq 'multipart/related') { + # This is stupid - multipart/related? When it's really just HTML?!? + $subpart->print_body; + last; + } # if + } # foreach + } else { + if ($part->mime_type =~ /text/) { + print '
    ';
    +	  $part->print_body;
    +	  print '
    '; + } # if + } # if + } # foreach + } # if + + print "
    + + + + + + +
    + + + + + + + + + + +
    Warning
    +
    Care should be taken when using regular + expressions as you can easily black list email messages you do not want +to have blacklisted!
    +
    +
    +
    + +
    + + + + + + + +
    + + + + + + + + + + + + + +
    Full name
    Email address
    +
    +
    + +
    +
    +
    +
    + + diff --git a/maps/doc/add2nulllist.html b/maps/doc/add2nulllist.html new file mode 100644 index 0000000..55e8b55 --- /dev/null +++ b/maps/doc/add2nulllist.html @@ -0,0 +1,99 @@ + + + + MAPS Add to Black List + + + + + + + +

    MAPS: Add to Null List
    +

    + This screen allows you to add to your null list. Note that regular expressions + can be used so you can modify the Email address below to be any +portion thereof or specify a regular expression. Here are some examples. +Given an email address of Spammer@spamdomain.com you can:
    + +
      +
    • Specify just the domain (e.g. @spamdomain.com). This will effectively + discard all email from everybody at that domain.
    • +
    • Specify just the username portion (e.g. Spammer@). This will effectively + discard all email from anybody using the username of spammer (note that +email addresses are not case sensitive) from any domain
    • +
    • Use regular expression characters to further refine your filter. + For example, "^spammer.*@" means any email from an address that starts +(^) with the word "spammer", has any number of characters after (.*), then +an "@" sign will be discarded.
    • + +
    + + + + + + + + +
    + + + + + + + + + + +
    Warning
    +
    Care should be taken when using regular + expressions as you can easily lose email messages you do not want to have +discarded!
    +
    +
    +
    + +
    + + + + + + + +
    + + + + + + + + + + + + + +
    Full name
    Email address
    +
    +
    + +
    +
    +
    +
    + + diff --git a/maps/doc/detail.html b/maps/doc/detail.html new file mode 100644 index 0000000..9a608b0 --- /dev/null +++ b/maps/doc/detail.html @@ -0,0 +1,524 @@ + + + + Returned Report for Andrew@DeFaria.com + + + + + +

    Returned Report for Andrew@DeFaria.com

    + + + + + + + + +
    Note: Only unique email addresses are reported
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Count
    +
    Email Address
    +
    # of Msgs
    +
    Message
    +
    Add to
    +
    Add to
    +
    Add to
    +
    1
    +
    -lee@yahoo.com
    +
    1
    +
    Display
    +
    White + list
    +
    Black + List
    +
    Null + List
    +
    2
    +
    1yeq7jft@excite.com
    +
    1
    +
    Display
    +
    White + list
    +
    Black + List
    +
    Null + List
    +
    3
    +
    235558@wdell.com
    +
    2
    +
    Display
    +
    White + list
    +
    Black + List
    +
    Null + List
    +
    4
    +
    3popmewpdl@freemail.com.au
    +
    N/A
    +
    N/A
    +
    White + list
    +
    Black + List
    +
    Null + List
    +
    5
    +
    4319jackie762r@excite.com +
    +
    2
    +
    Display
    +
    White + list
    +
    Black + List
    +
    Null + List
    +
    6
    +
    4yourliferate@businessvvorld.com
    +
    4
    +
    Display
    +
    White + list
    +
    Black + List
    +
    Null + List
    +
    7
    +
    a10566@aol.com
    +
    1
    +
    Display
    +
    White + list
    +
    Black + List
    +
    Null + List
    +
    8
    +
    a14c17@aol.com
    +
    1
    +
    Display
    +
    White + list
    +
    Black + List
    +
    Null + List
    +
    9
    +
    abaddon57@i-france.com
    +
    1
    +
    Display
    +
    White + list
    +
    Black + List
    +
    Null + List
    +
    10
    +
    abaris@jippii.fi
    +
    3
    +
    Display
    +
    White + list
    +
    Black + List
    +
    Null + List
    +
    11
    +
    adsdssf_@zzn.com
    +
    1
    +
    Display
    +
    White + list
    +
    Black + List
    +
    Null + List
    +
    12
    +
    aduderstad3220@myself.com
    +
    N/A
    +
    N/A
    +
    White + list
    +
    Black + List
    +
    Null + List
    +
    13
    +
    afk@crosswinds.net
    +
    5
    +
    Display
    +
    White + list
    +
    Black + List
    +
    Null + List
    +
    14
    +
    afrank@utopia.com
    +
    1
    +
    Display
    +
    White + list
    +
    Black + List
    +
    Null + List
    +
    15
    +
    aishazentz@london.com
    +
    1
    +
    Display
    +
    White + list
    +
    Black + List
    +
    Null + List
    +
    16
    +
    aj@san.rr.com
    +
    1
    +
    Display
    +
    White + list
    +
    Black + List
    +
    Null + List
    +
    17
    +
    akafu@excite.com
    +
    1
    +
    Display
    +
    White + list
    +
    Black + List
    +
    Null + List
    +
    18
    +
    al39@hotmail.com
    +
    1
    +
    Display
    +
    White + list
    +
    Black + List
    +
    Null + List
    +
    19
    +
    aldarupazmiy1@mail.com
    +
    1
    +
    Display
    +
    White + list
    +
    Black + List
    +
    Null + List
    +
    20
    +
    alesinettie@ief.hr
    +
    1
    +
    Display
    +
    White + list
    +
    Black + List
    +
    Null + List
    +
    +
    +
    +
    +
    +
    + + diff --git a/maps/doc/index.php b/maps/doc/index.php new file mode 100644 index 0000000..4e8e151 --- /dev/null +++ b/maps/doc/index.php @@ -0,0 +1,88 @@ + + + + + MAPS: Help + + + + +
    +

    + MAPS Spam Elimination System!

    +
    + +
    + + +

    What is MAPS?

    + +

    MAPS - which the observant might notice is SPAM spelt backwards - + works on a simple principal that is commonly used with Instant + Messenger (IM) clients such as AOL's Instant Messenger or + Microsoft's Messenger. That is that with most IM clients you need to + get the permission of the person you want to message before you can + send them an instant message. MAPS considers all email spam and + returns it to the sender unless the email is from somebody on your + white list.

    + +

    Now white lists are not new but maintaining a white list is a + bother. So MAPS automates the maintaining of the that white list by + putting the responsibility of maintaining it on the people who wish + to email you. MAPS also seeks to make it easy for real people, not + spammers, to request permission to email you. Here's how it + works....

    + +

    Email that is delivered to you is passed through a filter (maps + filter) which processes your email like so:

    + +
      + +
    1. Extract senders email address - no sender address (and no + envelope address)? Discard the email
    2. + +
    3. Check to see if the sender is on your null list - if so + discard the email
    4. + +
    5. Check to see if the sender is on your black list - if so + return a message telling the sender that s/he is blocked from + emailing you.
    6. + +
    7. Check to see if the sender is on your white list - if so + deliver the mail
    8. + +
    9. Otherwise send the sender a message with a link for them to + quickly register. Also, save their email so it can be delivered + when they register
    10. + +
    + +

    As you can see this algorithm will greatly reduce your + spam. Also, it's easy for real people to register. Spammers + typically do not read any email returning to them so they never + register!

    + +

    Other topics

    + + + + + +
    + + diff --git a/maps/doc/maps.css b/maps/doc/maps.css new file mode 100644 index 0000000..2fa3011 --- /dev/null +++ b/maps/doc/maps.css @@ -0,0 +1,290 @@ +/************************************************************************/ +/* File: MAPSStyle.css */ +/* Description: Cascading Style Sheet definitions for MAPS */ +/* Author: Andrew@DeFaria.com */ +/* Created: Mon Nov 3 21:55:05 PST 2003 */ +/* Language: Cascading Style Sheet */ +/* */ +/* (c) Copyright 2003, Andrew@DeFaria.com, all rights reserved. */ +/************************************************************************/ +body { + background-color: #fff; + background-image: url(/maps/images/Pattern1.gif); + background-repeat: repeat-y; + color: #666; + font-family: trebuchet MS, trebuchet, verdana, arial, sans-serif; + font-size: 12px; + margin: 0px; +} + +.heading { + margin-left: 140px; + margin-top: 5px; + padding: 5px; +} + +.leftbar { + height: auto; + left: 2px; + position: absolute; + top: 5px; + width: 135px; +} + +.username { + color: white; + font-family: veranda, arial; + font-style: bold; + font-size: 14px; + margin-left: 2px; + margin-bottom: 5px; + text-align: center; + width: 125px; +} + +.menu { + background-color: #579; + background-image: url(/maps/images/worldnew.gif); + border: 2px groove black; + font-family: verdana, geneva, arial, helvetica, sans-serif; + font-size: 14px; + font-weight: bold; + line-height: 150%; + margin: 2px; + padding: 2px; + width: 122px; +} + +.intromenu { + background-color: #579; + border: 2px groove black; + font-family: verdana, geneva, arial, helvetica, sans-serif; + font-size: 14px; + font-weight: bold; + line-height: 150%; + margin: 2px; + padding: 2px; + width: 125px; +} + +.search { + background: #4682b4; + border: 2px groove black; + color: white; + font-family: veranda, arial; + font-style: bold; + margin-left: 2px; + margin-right: 5px; + text-align: center; + width: 125px; +} + +.quickstats { + background-color: #ffffcc; + border: 2px groove #336699; + color: black; + font-size: 10px; + line-height: 10px; + margin: 2px; + padding: 2; + width: 125px; +} + +.content { + background: #fff; + border: 0.1px solid #fff; + color: #666; + font-family: trebuchet MS, trebuchet, verdana, arial, sans-serif; + margin-left: 140px; + padding: 5px; + width: auto; +} + +.copyright { + border-bottom: 1px dotted #ccc; + border-top: 1px dotted #ccc; + color: #666; + font-family: verdana, arial, sans-serif; + font-size: 10px; + margin-top: 5px; + text-align: center; + width: auto; +} + +.label { + color: #993333; + font-weight: bold; + font-size: 14px; +} + +.smalllabel { + color: #993333; + font-weight: bold; + font-size: 12px; +} + +.smallnumber { + color: black; + font-size: 12px; +} + +.header { + color: #000099; + font-weight: bold; +} + +.standout { + color: red +} + +.error { + color: red; + font-style: bold; +} + +.dim { + color: #999999; +} + +.dimsmall { + color: #999999; + font-size: 0.9em; + font-family: Times; +} + +.highlite { + color: #000099; + font-weight: bold; +} + +.inputfield { + background: #ece9d8; + color: Black; + font-family: Veranda, + Times; + font-size: 12px; + padding-top: 0px; + padding-bottom: 0px; +} + +.note { + background: #339999; + color: White; + font-weight: bold; +} + +.notetext { + color: #333; + font-size: 10px; + font-weight: italic; +} + +# Headers +h1, h2, h3, h4, h5 { + color: #000099; +} + +h1 { + font-size: 18pt; +} + +h2 { + font-size: 14pt; +} + +h3 { + font-size: 10pt; +} + +h4 { + font-size: 8pt; +} + +# Global anchor effects +a { + background: transparent; + text-decoration: none; +} + +a:link { + color: #0000ee; +} + +a:visited { + color: #cc33cc; +} + +a:hover { + color: White; + background: #0054e3; +} + +a:active { + color: #ff0000; +} + +# Special anchor effects +.sender { + font-weight: bold +} + +.sender a:link { + color: Red; +} + +.sender a:visited { + color: #0054e3; +} + +.sender a:hover { + color: White; + background: Red; +} + +.sender a:active { + color: Yellow; +} + +# Menu anchors +.menu { + font-weight: bold; +} + +.menu a:link { + color: Red; +} + +.menu a:visited { + color: white; +} + +.menu a:hover { + color: White; + background: Red; +} + +.menu a:active { + color: Yellow; +} + +# Intromenu anchors +.intromenu { + font-weight: bold; +} + +.intromenu a:link { + color: Red; +} + +.intromenu a:visited { + color: white; +} + +.intromenu a:hover { + color: White; + background: Red; +} + +.intromenu a:active { + color: Yellow; +} diff --git a/maps/doc/world.gif b/maps/doc/world.gif new file mode 100644 index 0000000000000000000000000000000000000000..d4e1a939e7ba0889342ac5aa8ab7aa11540a7aa6 GIT binary patch literal 94570 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 literal 0 HcmV?d00001 diff --git a/maps/forward b/maps/forward new file mode 100755 index 0000000..cde95c0 --- /dev/null +++ b/maps/forward @@ -0,0 +1 @@ +"|/usr/local/maps/bin/maps -u andrew" diff --git a/maps/images/Pattern1.gif b/maps/images/Pattern1.gif new file mode 100644 index 0000000000000000000000000000000000000000..98017ec31285f70ba8159261952c2b7c7a49b4cf GIT binary patch literal 175 zcmV;g08sx&Nk%w1VT=GM0FeR!05fI)05kvp{{R30A^8LW=mP=(EC2ui0E_@A06+qO zgpaAq?GK}zwAzca-n{z{hT=$;=82~2%C_zc$MQ_q_KoNI&iDQg3<`(DqVb4KDwoWr zvlEC)r_`$Tip^@b+^+XKeHOvwviXcoi;3yB`wjQ5hw-}oj-Ctg{Jy`c7xXtsSQiLX dn5bxW__O#3snzHcS!o$H*>k!1n8{NB06P_JS9JgY literal 0 HcmV?d00001 diff --git a/maps/images/next.gif b/maps/images/next.gif new file mode 100644 index 0000000000000000000000000000000000000000..8c8cee9161c716b6fe2b217a1a750db487eff56c GIT binary patch literal 683 zcmZ?wbhEHblw%NKc$UR*=+L192M$6&rd-v|$w{KrYM#jU35BKcZbMD-^ zn>TN6+O)~U#N^hkTf27cTE2Yw`t|FN9zD8y_wKZ`wEX=1DO09gxNxDnyW8B{{NTZZ zJ9q9pe*E~6BS+4hIWu+Y)ZEhk5wdU|>V1qGp@p~c0;8#iw3?d`pE=~8-n`o)VEh9mazj^cK6DLm0oH_dwO~j5)x8VQ`_3wf`fxSJUrCY)PQbgpbRMf zWMO1r$YRg|83T$F2KN6ASxwC?t!?cs;^HzLy?v2<96mupebbutxF&M&aS7>7?_-eT z<_I_NmStpQSlGtEsmpEcZ?DO~#>%#4U9+~Zu1x~>JmsC7!klbU>lg(&Oi*?P|D75qtqmkW? zIU5TZPBAz$W^MT3#K6pjK7dsXn3E<&S3T^A9UVP;_UvQFjvYLB(80lB?b@|PMMX|dPV?u_ zH!(5E$jGRvskwOZ;?${AXU&>*^5n_o%a@mwlw7`idC{UpQ>IKgeE4uuQj(F8(UT`n z=FOWI5fL$M+B9EZ-%Xn~-MxD^G&FSo{{0#n8fVX*RaaNf&(FVi@80?I=N~?NsH3B! ztE=1C*ccNN^WedQ6)RR~YHBWAxNzmlm6tAE%F4>RaN)xJ`}gO}nUkEH+||{!V8Md8 zxVXy7%IfOs{{H@^rl#QF;M1p1M@2ZS5VMT?LGSO5GC* z7??R#LlY);h=iLkaE8TXi_UCeljG-iW{b~DGE-Ypz@}orZpXnJDB$GB8YD1rv$>WQ z6T2IWtEjlHM1XI%h#UtyJJWS0O9NgJeFYyKF~JT7MT_*{Xnq!!91ac^UPVqGSs^ix zHc>8tC@v#LCLW1`M=TA^%$p3P1)IHk-CIRmegryowu$mc$jmfgY?;ZbcByB=g|y?+ z3i?7*FCJiaX5@>wqBHS;Qd>7q;=LDFA{i!4SJ#u7aM3V%`Ng>z$5Ob04jjC=Jm2a_ z)8?iVY%8o}S!QTuFtxAtpIo52Wq~)>V##AtzZxFBna!HT#mky-TUl?>+C~NjYXD5N BAEp2R literal 0 HcmV?d00001 diff --git a/maps/images/world.gif b/maps/images/world.gif new file mode 100644 index 0000000000000000000000000000000000000000..c6004113abf4d327cdf0eb223a75f30b1efb8717 GIT binary patch literal 89866 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

    |O_saJKfQ2tsSYvJlD(V_R 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 t?#(9OI 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 SchCFY`~LU94?gV!0suQP$9zrz literal 0 HcmV?d00001 diff --git a/maps/images/world.jpg b/maps/images/world.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5f7524c39c7483dc390df39222e4b7b0d624e94d GIT binary patch literal 26424 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 literal 0 HcmV?d00001 diff --git a/maps/index.php b/maps/index.php new file mode 100755 index 0000000..6b9707e --- /dev/null +++ b/maps/index.php @@ -0,0 +1,95 @@ + + + + + + MAPS: Mail Authorization and Permission System + + + + +

    +

    + MAPS Mail Authorization and Permission System

    +

    Spam Elimination System

    +
    + +
    + + +

    MAPS is a system for totally eliminating spam from your life. It + seeks to minimize the amount of intervention and thus the amount of + time you spent dealing with unsolicited emails by requiring that all + email are solicited. MAPS provides a convenient way to manage your + spam and to allow those you wish to receive email from to be able to + email you without hassle.

    + +

    To learn more about MAPS select an option from the menu on the + left. Be sure to read Using MAPS + to familiarize yourself with how the MAPS system works and to + configure your email client. Then signup for an account (it's free!) + and login and enjoy spam free email!

    + +
    + + + + + + + + + + + + + + + + + + "; + } // if + ?> + + + + +
    Username: + + +
    Password: + + +
    +
    $errormsg
    + Forgot your password? +
    +
    + + + +
    + + diff --git a/maps/next.gif b/maps/next.gif new file mode 100644 index 0000000000000000000000000000000000000000..8c8cee9161c716b6fe2b217a1a750db487eff56c GIT binary patch literal 683 zcmZ?wbhEHblw%NKc$UR*=+L192M$6&rd-v|$w{KrYM#jU35BKcZbMD-^ zn>TN6+O)~U#N^hkTf27cTE2Yw`t|FN9zD8y_wKZ`wEX=1DO09gxNxDnyW8B{{NTZZ zJ9q9pe*E~6BS+4hIWu+Y)ZEhk5wdU|>V1qGp@p~c0;8#iw3?d`pE=~8-n`o)VEh9mazj^cK6DLm0oH_dwO~j5)x8VQ`_3wf`fxSJUrCY)PQbgpbRMf zWMO1r$YRg|83T$F2KN6ASxwC?t!?cs;^HzLy?v2<96mupebbutxF&M&aS7>7?_-eT z<_I_NmStpQSlGtEsmpEcZ?DO~#>%#4U9+~Zu1x~>JmsC7!klbU>lg(&Oi*?P|D75qtqmkW? zIU5TZPBAz$W^MT3#K6pjK7dsXn3E<&S3T^A + + + + MAPS: Forgot Password + + + + +
    +

    + MAPS Password Retrieval

    +
    + +
    + + +

    Password Retrieval

    + +

    So you forgot your password! Hey it happens. Give us your + username and select Send Me My Password and we will email you + your password.

    + +
    + +
    + + + +

    + +
    + +
    + + + +
    + + diff --git a/maps/php/ListDomains.php b/maps/php/ListDomains.php new file mode 100755 index 0000000..8922bb3 --- /dev/null +++ b/maps/php/ListDomains.php @@ -0,0 +1,49 @@ + + + + + + MAPS: Returned Messages by Domain + + + + +
    +

    MAPS +Returned Messages by Domain

    +
    +
    + +
    + +
    + +
    + + diff --git a/maps/php/MAPS.php b/maps/php/MAPS.php new file mode 100755 index 0000000..18b9abe --- /dev/null +++ b/maps/php/MAPS.php @@ -0,0 +1,519 @@ + 0) { + $ymd = substr ($date, 0, 10); + $sod = $ymd . " 00:00:00"; + $eod = $ymd . " 23:59:59"; + + foreach ($Types as $type) { + $condition = "type=\"$type\" and (timestamp > \"$sod\" and timestamp < \"$eod\")"; + $stats[$type] = countlog ($condition); + } # foreach + + $dates[$ymd] = &$stats; + + $date = SubtractDays ($date, 1); + $nbr_days--; + } # while + + return $dates; +} # GetStats + +function displayquickstats () { + $today = substr (Today2SQLDatetime (), 0, 10); + $dates = getquickstats ($today); + $current_time = date ("g:i a"); + + // Start quickstats + print "
    "; + print "

    Today's Activity

    "; + print "

    as of $current_time

    "; + + $processed = $dates[$today]["processed"]; + $returned = $dates[$today]["returned"]; + $returned_pct = $processed == 0 ? 0 : + number_format ($returned / $processed * 100, 1, ".", ""); + $whitelist = $dates[$today]["whitelist"]; + $whitelist_pct = $processed == 0 ? 0 : + number_format ($whitelist / $processed * 100, 1, ".", ""); + $blacklist = $dates[$today]["blacklist"]; + $blacklist_pct = $processed == 0 ? 0 : + number_format ($blacklist / $processed * 100, 1, ".", ""); + $registered = $dates[$today]["registered"]; + $mailloop = $dates[$today]["mailloop"]; + $nulllist = $dates[$today]["nulllist"]; + $nulllist_pct = $processed == 0 ? 0 : + number_format ($nulllist / $processed * 100, 1, ".", ""); + + $returned_link = $returned == 0 ? 0 : + "$returned"; + $whitelist_link = $whitelist == 0 ? 0 : + "$whitelist"; + $blacklist_link = $blacklist == 0 ? 0 : + "$blacklist"; + $registered_link = $registered == 0 ? 0 : + "$registered"; + $mailloop_link = $mailloop == 0 ? 0 : + "$mailloop"; + $nulllist_link = $nulllist == 0 ? 0 : + "$nulllist"; + +print << + + Processed + $processed + n/a + + + Returned + $returned_link + $returned_pct% + + + Whitelist + $whitelist_link + $whitelist_pct% + + + Blacklist + $blacklist_link + $blacklist_pct% + + + Registered + $registered_link + n/a + + + Mailloop + $mailloop_link + n/a + + + Nulllist + $nulllist_link + $nulllist_pct% + + +
    +EOT; +} // displayquickstats + +function getquickstats ($date) { + global $Types; + + $dates = GetStats (1, $date); + + foreach ($Types as $type) { + if (isset ($dates [$date]["processed"])) { + $dates [$date]["processed"] += $dates [$date][$type]; + } else { + $dates [$date]["processed"] = $dates [$date][$type]; + } // if + } # foreach + + return $dates; +} // getquickstats + +function NavigationBar ($userid) { + print "
    "; + + if (!isset ($userid) || $userid == "") { + print <<Welcome to MAPS
    + +END; + } else { + $Userid = ucfirst ($userid); + print <<Welcome $Userid + +END; + print << +
    + Search Sender/Subject + +
    + +END; + + displayquickstats (); + + print << +
    + Check Email Address + +
    + +END; + } // if + + print ""; +} # NavigationBar + +function GetUserLines () { + global $userid; + + $lines = 10; + + $statement = "select value from useropts where userid=\"$userid\" and name=\"Page\""; + + $result = mysql_query ($statement) + or DBError ("GetUserLines: Unable to execute query: ", $statement); + + $row = mysql_fetch_array ($result); + + if (isset ($row["value"])) { + $lines = $row["value"]; + } // if + + return $lines; +} // GetUserLines + +function DisplayList ($type, $next, $lines) { + global $userid; + global $total; + global $last; + + $statement="select * from list where userid=\"$userid\" and type=\"$type\" order by sequence limit $next, $lines"; + + $result = mysql_query ($statement) + or DBError ("DisplayList: Unable to execute query: ", $statement); + + for ($i = 0; $i < $lines; $i++) { + $row = mysql_fetch_array ($result); + + if (!isset ($row ["sequence"])) { + break; + } // if + + $sequence = $row ["sequence"]; + $username = $row ["pattern"] == "" ? " " : $row ["pattern"]; + $domain = $row ["domain"] == "" ? " " : $row ["domain"]; + $hit_count = $row ["hit_count"] == "" ? " " : $row ["hit_count"]; + $last_hit = $row ["last_hit"] == "" ? " " : $row ["last_hit"]; + $comments = $row ["comment"] == "" ? " " : $row ["comment"]; + + // Remove time from last hit + $last_hit = substr ($last_hit, 0, (strlen ($last_hit) - strpos ($last_hit, " ")) + 1); + + // Reformat last_hit + $last_hit = substr ($last_hit, 5, 2) . "/" . + substr ($last_hit, 8, 2) . "/" . + substr ($last_hit, 0, 4); + $leftclass = ($i == $lines || $sequence == $total || $sequence == $last) ? + "tablebottomleft" : "tableleftdata"; + $dataclass = ($i == $lines || $sequence == $total || $sequence == $last) ? + "tablebottomdata" : "tabledata"; + $rightclass = ($i == $lines || $sequence == $total || $sequence == $last) ? + "tablebottomright" : "tablerightdata"; + + print "" . $sequence . ""; + print "\n"; + print "" . $username . ""; + print "@"; + print "" . $domain . ""; + print "" . $hit_count . ""; + print "" . $last_hit . ""; + print "" . $comments . ""; + print ""; + } // for +} // DisplayList + +function MAPSHeader () { + print << + + + + + + + + +END; +} // MAPSHeader + +function ListDomains ($top = 10) { + global $userid; + + // Generate a list of the top 10 spammers by domain + $statement = "select count(sender) as nbr, "; + // Must extract domain from sender... + $statement = $statement . "substring(sender, locate(\"@\",sender, 1)+1) as domain "; + // From email for the current userid... + $statement = $statement . "from email where userid=\"$userid\" "; + // Group things by domain but order them descending on nbr... + $statement = $statement . "group by domain order by nbr desc"; + + // Do the query + $result = mysql_query ($statement) + or DBError ("ListDomains: Unable to execute query: ", $statement); + + print << + + Mark + Ranking + Domain + Returns + +END; + + // Get results + for ($i = 0; $i < $top; $i++) { + $row = mysql_fetch_array ($result); + $domain = $row["domain"]; + $nbr = $row["nbr"]; + + print ""; + $ranking = $i + 1; + if ($i < $top - 1) { + print "\n"; + print "" . $ranking . ""; + print "$domain"; + print ""; + print "$nbr"; + } else { + print "\n"; + print "" . $ranking . ""; + print "$domain"; + print ""; + print "$nbr"; + } // if + print ""; + } // for + + print << + + + + +END; +} // ListDomains + +function Space () { + global $userid; + + // Tally up space used by $userid + $space = 0; + + $statement = "select * from email where userid = \"$userid\""; + + $result = mysql_query ($statement) + or DBError ("Space: Unable to execute query: ", $statement); + + while ($row = mysql_fetch_array ($result)) { + $msg_space = + strlen ($row["userid"]) + + strlen ($row["sender"]) + + strlen ($row["subject"]) + + strlen ($row["timestamp"]) + + strlen ($row["data"]); + $space = $space + $msg_space; + } // while + + return $space; +} // Space +?> diff --git a/maps/php/Reports.php b/maps/php/Reports.php new file mode 100755 index 0000000..8c14a87 --- /dev/null +++ b/maps/php/Reports.php @@ -0,0 +1,47 @@ + + + + + + MAPS: Reports + + + +
    +

    MAPS +Reports

    +
    +
    + + +

    Reports

    + + + +
    + + diff --git a/maps/php/Space.php b/maps/php/Space.php new file mode 100755 index 0000000..9c4bd02 --- /dev/null +++ b/maps/php/Space.php @@ -0,0 +1,55 @@ + + + + + + MAPS: Space Usage + + + +
    +

    MAPS +Space Usage for

    +
    +
    + $one_meg) { + $space = number_format ($space / $one_meg, 1); + print "$Userid is using up $space Megabytes of space in the database"; + } elseif ($space > 0) { + $space = number_format ($space / 1024, 0); + print "$Userid is using up $space Kbytes of space in the database"; + } else { + print "$Userid is using up no space in the database"; + } // if + + copyright (2001); + ?> +
    + + diff --git a/maps/php/emailpassword.php b/maps/php/emailpassword.php new file mode 100755 index 0000000..50be758 --- /dev/null +++ b/maps/php/emailpassword.php @@ -0,0 +1,93 @@ + + + Your MAPS Password + + +

    Your MAPS Password is $decoded_password

    + +

    Click here to login to MAPS. + + +"; + +/* To send HTML mail, you can set the Content-type header. */ +$headers = "MIME-Version: 1.0\r\n"; +$headers .= "Content-type: text/html; charset=iso-8859-1\r\n"; + +/* additional headers */ +$headers .= "To: $email\r\n"; +$headers .= "From: MAPS \r\n"; + +/* and now mail it */ +$mailed = mail($to, $subject, $message, $headers); +?> + + + + MAPS: Password Retrieval + + + + +

    +

    + MAPS Password Retrieval

    +
    + +
    + + +

    Your password has been emailed to

    + + + + + diff --git a/maps/php/list.php b/maps/php/list.php new file mode 100755 index 0000000..4050d33 --- /dev/null +++ b/maps/php/list.php @@ -0,0 +1,127 @@ + + + + + MAPS: Manage <?php echo "$Type"?> List + + 0) { + $prev = $next - $lines; +} else { + $prev = $next == 0 ? -1 : 0; +} // if + +$total = CountList ($type); +$last = $next + $lines < $total ? $next + $lines : $total; +$last_page = floor ($total / $lines); +$this_page = $next / $lines + 1; +?> + + + +
    +

    + MAPS Manage List

    +
    + +
    + +
    +
    + $message"; + } // if + $current = $next + 1; + print ""; + print ""; + print "Page: "; + print " of $last_page"; + ?> +
    +
    + = 0 ? + "Previous" : ""; + $next_button = ($next + $lines) < $total ? + "Previous" : ""; + print $prev_button; + ?> + + + + + +
    +
    + + + + + + + + + + + + + +
    SeqMarkUsername@DomainHit CountLast HitComments
    +
    +
    + + + + +
    + + + + + diff --git a/maps/php/main.php b/maps/php/main.php new file mode 100755 index 0000000..9313485 --- /dev/null +++ b/maps/php/main.php @@ -0,0 +1,86 @@ + + + + + + MAPS: Home + + + + +
    +

    + MAPS Spam Elimination

    +
    + +
    + + +

    Welcome to MAPS!

    + +

    This is the main or home page of MAPS. To the left you see a menu + of choices that you can use to explore MAPS functionality. Statistics gives you a view of the + spam that MAPS has been trapping for you in tabular format. You can + use Edit Profile to change + your profile information or to change your password.

    + +

    MAPS also offers a series of web based Reports to analyze your mail flow. You + can manage your White, + Black and Null lists although MAPS + seeks to put that responsibility on those who wish to email you. You + can use this to pre-register somebody or to black or null list + somebody. You can also import/export your lists through these + pages.

    + +

    MAPS Administration is to + administer MAPS itself and is only available to MAPS + Administrators.

    + +

    Also on the left you will see Today's Activity which + quickly shows you what mail MAPS processed today for you.

    + + + +
    + + diff --git a/maps/previous.gif b/maps/previous.gif new file mode 100644 index 0000000000000000000000000000000000000000..e06d49d160d2bd494fe590818b8dff4658505e96 GIT binary patch literal 685 zcmZ?wbhEHblw%NKcvir09UVP;_UvQFjvYLB(80lB?b@|PMMX|dPV?u_ zH!(5E$jGRvskwOZ;?${AXU&>*^5n_o%a@mwlw7`idC{UpQ>IKgeE4uuQj(F8(UT`n z=FOWI5fL$M+B9EZ-%Xn~-MxD^G&FSo{{0#n8fVX*RaaNf&(FVi@80?I=N~?NsH3B! ztE=1C*ccNN^WedQ6)RR~YHBWAxNzmlm6tAE%F4>RaN)xJ`}gO}nUkEH+||{!V8Md8 zxVXy7%IfOs{{H@^rl#QF;M1p1M@2ZS5VMT?LGSO5GC* z7??R#LlY);h=iLkaE8TXi_UCeljG-iW{b~DGE-Ypz@}orZpXnJDB$GB8YD1rv$>WQ z6T2IWtEjlHM1XI%h#UtyJJWS0O9NgJeFYyKF~JT7MT_*{Xnq!!91ac^UPVqGSs^ix zHc>8tC@v#LCLW1`M=TA^%$p3P1)IHk-CIRmegryowu$mc$jmfgY?;ZbcByB=g|y?+ z3i?7*FCJiaX5@>wqBHS;Qd>7q;=LDFA{i!4SJ#u7aM3V%`Ng>z$5Ob04jjC=Jm2a_ z)8?iVY%8o}S!QTuFtxAtpIo52Wq~)>V##AtzZxFBna!HT#mky-TUl?>+C~NjYXD5N BAEp2R literal 0 HcmV?d00001 diff --git a/maps/register.html b/maps/register.html new file mode 100644 index 0000000..44c1bf7 --- /dev/null +++ b/maps/register.html @@ -0,0 +1,62 @@ + + + + + + MAPS Registration + + + + +

    Mail Authorization and +Permission System

    + +
    + +To register and send your previous +email click here + +

    For your convience you email message is attached to this +message. Note that if you register your previous emails will be +delivered therefore you do not need to resend the message that is +attached to this one. +

    + +

    My email address is protected by Mail Authorization and Permission +System (MAPS). You must register to email me. Registration is +quick and easy and you need only register once to be added to my +white list, thereafter you should have no problems emailing +me. This is not unlike the authorization procedure in many instant +messaging clients.

    + +

    This registration process is instantaneous however I reserve the +right to remove you from my white list should you abuse this +privilege.

    + +
    + +

    Special note to solicitors: +Please remove my email address from any and all lists that you +maintain or sell. I do not wish to receive any solicitations via +email. I do not give you permission to sell or otherwise use my email +address for any purpose whatsoever. I reserve the right to add you to +my white list as well as to put you on my black list. If +I do wish to receive email from you I will add you to my list +myself. If you register for permission to email me then I will most +likely move you to my black list.

    + +
    + + + + + diff --git a/maps/world.gif b/maps/world.gif new file mode 100644 index 0000000000000000000000000000000000000000..d4e1a939e7ba0889342ac5aa8ab7aa11540a7aa6 GIT binary patch literal 94570 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 /dev/null; then + eval $(dircolors -b ~/.rc/dircolors) + fi + fi + + if [ $ARCH = "sun" ]; then + # Ugh! --color doesn't work on braindead SUN + alias ls="ls -F" + else + alias ls="ls -F --color=auto" + fi +fi + +alias ll="ls -la" +alias whence="type -p" +alias mroe=more + +if [ $ARCH = "cygwin" ]; then + alias host=nslookup +fi + +export LANG=C + +if [ $(type -p vim) ]; then + alias vi=vim +fi + +if [ $(type -p ncftp) ]; then + alias ftp=ncftp + alias ftpput=ncftpput + alias ftpget=ncftpget +fi + +#alias grep="grep -d skip" + +if [ "$TERM" = "hpterm" -o \ + "$TERM" = "hp" -o \ + "$TERM" = "dtterm" -o \ + "$TERM" = "sun-color" -o \ + "$TERM" = "vt100" -o \ + "$TERM" = "vt220" -o \ + "$TERM" = "xterm" -o \ + "$TERM" = "cygwin" ]; then + alias cd=mycd + alias pushd=mypushd + alias popd=mypopd +fi + +# Miscellaneous: +if [ -x $(type -p less) ]; then + export LESS=eiXP"?f%f :[stdin] .?pt(%pt\%):?bt(%bt bytes):-.." + alias more="less -sr" + export PAGER="less -sr" +else + export MORE=-s + export PAGER=more +fi + +export PS4='${0##*/} line $LINENO:' + +set_title +set_prompt + +if [ "$TERM" = "dtterm" ]; then + export TERM=vt100 + export DTTERM=True +fi + +# Set mail +export MAIL=/var/mail/$USER + +# Perl Environment +export PERLCRITIC=~/.rc/perlcriticrc +export PERLTIDY=~/.rc/perltidyrc + +# CDPATH +export CDPATH="." + +alias vbs="cscript //nologo" + +# Set PATH +if [ -f ~/.rc/set_path ]; then + source ~/.rc/set_path +else + echo "Warning: ~/.rc/set_path does not exist!" +fi + +# Color man pages with yellow keywords +export LESS_TERMCAP_md=$'\e[1;33m' + +# Client specific customizations +for script in $(\ls ~/.rc/client_scripts); do + # This is not working as long as ACLs are not supported from the remote + # NetApp. This happens at some clients where the home directory is on a + # Netapp and they do not support NTFS ACLs properly. We cannot determine + # if the script is executable. + #if [ ! -d ~/.rc/client_scripts/$script ]; then + if [ -x ~/.rc/client_scripts/$script -a \ + ! -d ~/.rc/client_scripts/$script ]; then + source ~/.rc/client_scripts/$script + fi +done + +# Set display to local +export DISPLAY=${DISPLAY:-:0} + +# Now go home (in case we were not autmatically cd'ed there) +if [ $(id -u) -ne 0 ]; then + cd +fi diff --git a/rc/clearcase b/rc/clearcase new file mode 100644 index 0000000..d2d8b0c --- /dev/null +++ b/rc/clearcase @@ -0,0 +1,4129 @@ +#!/bin/bash +################################################################################ +# +# File: $RCSfile: clearcase,v $ +# Revision: $Revision: 1.33 $ +# Description: This script set up some useful environment variables and aliases +# for Clearcase execution. File should be sourced (e.g . +# clearcase) +# Author: Andrew@DeFaria.com +# Created: Wed Jun 5 21:08:03 PDT 1996 +# Modified: $Date: 2011/10/24 18:07:05 $ +# Language: bash +# +# (c) Copyright 2000-2011, ClearSCM, Inc., all rights reserved. +# +################################################################################ +# Set ARCH, architecture of the machine +KERNEL=$(uname -s) + +if [[ $KERNEL = CYGWIN* ]]; then + export ARCH=cygwin + vobtagPrefix='\\\\\\\' +elif [ $KERNEL = "Linux" ]; then + export ARCH=linux +elif [ $KERNEL = "SunOS" ]; then + export ARCH=sun +elif [ $KERNEL = "FreeBSD" ]; then + export ARCH=$KERNEL +else + export ARCH='' + echo "Warning: Unknown architecture ($KERNEL)" +fi + +if [ $ARCH = 'cygwin' ]; then + export CCHOME=$(cygpath -u "$(regtool get '/machine/SOFTWARE/Rational Software/RSINSTALLDIR' 2>/dev/null)"/Clearcase 2>/dev/null); +else + export CCHOME="/opt/rational/clearcase" +fi + +if [ ! -d "$CCHOME" ]; then + unset CCHOME +# exit +fi + +# Source in clearcase.conf +if [ -f ~/.rc/clearcase.conf ]; then + source ~/.rc/clearcase.conf +else + echo "WARNING: Could not find ~/.rc/clearcase.conf - functionality will be limted" +fi + +export CLEARTOOL="$CCHOME/bin/cleartool" +export CLEARCASE_BLD_HOST_TYPE="unix" + +if [ -f "$CCHOME/etc/utils/creds" ]; then + if [ $ARCH = 'cygwin' ]; then + alias creds=$(cygpath "$CCHOME/etc/utils/creds") + else + alias creds="$CCHOME/etc/utils/creds" + fi +fi + +if [ -x "$CLEARTOOL" ]; then + export RGY="$CCHOME/var/atria/rgy" + export LOGS="$CCHOME/var/log" +fi + +function scm { + scmstatus=0 + + if [ -x "$CLEARTOOL" ]; then + # Cleartool suddenly started appending ^M's (I think as of 7.1) + if [ $ARCH = 'cygwin' ]; then + # Need to set pipefail to pick up the exit code from cleartool + # otherwise we get the exit code from tr which is usually 0 + set -o pipefail + "$CLEARTOOL" "$@" | tr -d "\015" + scmstatus=$? + set +o pipefail + else + "$CLEARTOOL" "$@" + scmstatus=$? + fi + else + scmstatus=1 + fi + + return $scmstatus +} # scm + +function ct { + # Setview is special - there is no setview on Windows. But we approximate it + # with the function setview + if [ "$1" = "setview" ]; then + shift + setview "$@" + else + scm "$@" + fi + + return $scmstatus +} # ct + +function scmsystem { + if [ "$(scm pwv)" != '' ]; then + return 1 + elif [ -f CVS/Root ]; then + return 2 + else + return 0 + fi +} # scmsystem + +function ci { + if [ $# = 0 ]; then + echo "ci: Error: No files specified" + else + scm ci "$@" + fi +} # ci + +function co { + if [ $# = 0 ]; then + echo "co: Error: No files specified" + else + scm co "$@" + fi +} # co + +function unco { + if [ $# = 0 ]; then + echo "unco: Error: No files specified" + else + scm unco "$@" + fi +} # unco + +function lslock { + scm lslock "$@" +} # lslock + +function lllock { + scm lslock -long "$@" +} # lllock + +# View related functions +function setview { + if [ $ARCH = 'cygwin' ]; then + if [[ $1 = -* ]]; then + echo "The setview command with options is not supported on Windows" + return + fi + + # Save off where we are + back=$PWD + + # Start the view + scm startview "$@" + + if [ $? != 0 ]; then + return $? + fi + + # Setup $VOBTAG_PREFIX + mount -f -o binary M:/$1 $LINUX_VOBTAG_PREFIX + + # Start a bash shell + bash + + # Remove $LINUX_VOBTAG_PREFIX mount (Ignore errors) + umount $LINUX_VOBTAG_PREFIX 2> /dev/null + + # Chdir back to were we started + cd $back + else + scm setview "$@" + fi + + set_title + set_prompt +} # setview + +function startview { + scm startview "$@" + + mycd /view/$1 +} # startview + +function endview { + scm endview "$@" +} # endview + +function killview { + scm endview -server "$@" +} # killview + +function mkview { + scm mkview "$@" +} # mkview + +function makeview { + if [ $# != 1 ]; then + echo "Usage: $FUNCNAME " + return 1 + fi + + stream=$1 + + # Check to see if the view already exists + scm lsview -short ${USER}_$stream > /dev/null 2>&1 + + if [ $? = 0 ]; then + echo "${USER}_$stream already exists!" + else + mkview -tag ${USER}_$stream -stream $stream@$pvob -stgloc -auto + + if [ $? != 0 ]; then + echo "Couldn't find that stream. Perhaps it's one of these?" + let i=${#stream}-4 + searchFor=${stream:1:$i} + scm lsstream -short -invob $pvob | grep $searchFor | $PAGER + return 0 + fi + fi + + setview ${USER}_$stream +} # makeview + +function rmview { + scm rmview "$@" +} # rmview + +function lsview { + if [ $# = 0 ]; then + scm lsview -s | $PAGER + elif [ $# = 1 ]; then + scm lsview -s | grep "$1" + else + scm lsview "$@" + fi +} # lsview + +function myviews { + # Well they asked for my "views"... + if [ $(whence fortune) ]; then + fortune + echo + fi + + # List my views and their associated activities + for view in $(scm lsview -s 2>&1 | grep $USER); do + headline=$(scm lsact -cact -fmt "%[headline]p" -view $view 2> /dev/null) + + if [ "$headline" = "" ]; then + headline="" + fi + + echo -e "$view\t$headline" + done +} # myviews + +function llview { + if [ $# = 0 ]; then + scm lsview -long | $PAGER + else + scm lsview -long "$@" + fi +} # llview + +function lsviews { + if [ $# = 0 ]; then + scm lsview -short + else + scm lsview -short | grep $1 + fi +} # lsviews + +# Vob related functions +function lsvob { + if [ $# = 0 ]; then + scm lsvob | $PAGER + elif [ $# = 1 ]; then + scm lsvob | grep "$1" + else + scm lsvob "$@" + fi +} # lsvob + +function llvob { + if [ $# = 0 ]; then + scm lsvob -long | $PAGER + else + scm lsvob -long "$@" + fi +} # llvob + +function lsvobs { + if [ $# = 0 ]; then + scm lsvob -short + else + scm lsvob -short | grep $1 + fi +} # lsvobs + +# Config spec functions +function setcs { + scm setcs "$@" +} # setcs + +function edcs { + scm edcs "$@" +} # edcs + +function catcs { + scm catcs "$@" +} # catcs + +function pwv { + scm pwv -s "$@" +} # pwv + +function rmtag { + scm rmtag "$@" +} # rmtag + +function mktag { + scm mktag "$@" +} # mktag + +function describe { + scm describe "$@" +} # describe + +function desc { + scm describe "$@" +} # describe + +function oid2name { + if [ $# == 2 ]; then + oid=$1 + projvob=$2 + elif [ $# == 1 ]; then + oid=$1 + projvob=$pvob + else + echo "Usage: oid2name: []" + + return + fi + + scm describe oid:$oid@$projvob 2> /dev/null | head -1 +} # oid2name + +function name2oid { + if [ $# == 3 ]; then + type=$1 + name=$2 + projvob=$3 + elif [ $# == 2 ]; then + type=$1 + name=$2 + projvob=$pvob + else + echo "Usage: name2oid: []" + + return + fi + + scm dump $type:$name@$projvob 2> /dev/null | grep oid= +} # name2oid + +# Action functions +function vtree { + if [ $# != 1 ]; then + echo "vtree: Error: Must specify an element to view its version tree" + else + if [ $ARCH = 'cygwin' ]; then + scm lsvtree -g "$@" + else + xlsvtree "$@" + fi + fi +} # vtree + +function merge { + scm merge "$@" +} # merge + +function findmerge { + scm findmerge "$@" +} # findmerge + +function cdiff { + scmsystem; scmtype=$? + + if [ $# = 1 ]; then + if [ $scmtype = 1 ]; then + scm diff -graphical -pred $1 + elif [ $scmtype = 2 ]; then + cvs diff $1 + fi + else + if [ $scmtype = 1 ]; then + scm diff -graphical "$@" + elif [ $scmtype = 2 ]; then + cvs diff "$@" + fi + fi +} # cdiff + +function ctdiff { + if [ $# = 1 ]; then + scm xdiff -vstack -pred $1 + else + scm xdiff -vstack $@ + fi +} # ctdiff + +# Administrative functions +function space { + scm space "$@" +} # space + +function register { + scm register "$@" +} # register + +function unregister { + scm unregister "$@" +} # unregister + +# Information functions +function hostinfo { + scm hostinfo "$@" +} # hostinfo + +function lstrig { + if [ $# = 2 ]; then + trig_name=$1 + vob=$2 + scm lstype trtype:$trig_name@$vob + else + scm lstype -kind trtype "$@" | $PAGER + fi +} # lstrig + +function lltrig { + if [ $# = 1 ]; then + scm lstype -long trtype:$1 + else + scm lstype -long -kind trtype "$@"| $PAGER + fi +} # lltrig + +function lsbr { + scm lstype -brtype +} # lsbr + +function lslab { + scm lstype -lbtype +} # lslab + +# UCM oriented functions +function lsstream { + if [ $# = 0 ]; then + # If we are in a view context then try to list the stream for our view + if [[ $(pwv) == "** NONE **" ]]; then + lsstreams | $PAGER + else + scm lsstream | $PAGER + fi + else + # Check to see if a pvob was specified + if [[ $@ == *@* ]]; then + scm lsstream "$@" + else + scm lsstream -invob $pvob | grep $1 | $PAGER + fi + fi +} # lsstream + +function llstream { + if [ $# = 0 ]; then + scm lsstream -l | $PAGER + else + scm lsstream -l "$@" + fi +} # llstream + +function lsstreams { + if [ $# = 0 ]; then + scm lsstream -short -invob $pvob + else + scm lsstream -short "$@" + fi +} # lstreams + +function lsregions () { + if [ $# -gt 0 ]; then + scm lsregion -short | grep ^$1 + else + scm lsregion -short + fi +} # lsregions + +function rebase { + scm rebase "$@" +} # rebase + +function deliver { + scm deliver "$@" +} # deliver + +function lsbl { + scm lsbl "$@" +} # lsbl + +function lsll { + scm lsbl -lon "$@" +} # llbl + +function lsproj { + if [ $# = 0 ]; then + scm lsproject | $PAGER + else + scm lsproject "$@" + fi +} # lsproj + +function llproj { + if [ $# = 0 ]; then + scm lsproject -l | $PAGER + else + scm lsproject -l "$@" + fi +} # llproj + +function lsfolder { + scm lsfolder "$@" +} # lsfolder + +function llfolder { + scm lsfolder -long "$@" +} # llfolder + +function lsstgloc { + if [ $# = 0 ]; then + scm lsstgloc | $PAGER + else + scm lsstgloc "$@" + fi +} # lsstgloc + +function llstgloc { + if [ $# = 0 ]; then + scm lsstgloc -l | $PAGER + else + scm lsstgloc -l "$@" + fi +} # llstgloc + +function lsact { + if [ $# = 0 ]; then + scm lsactivity | $PAGER + else + scm lsactivity "$@" + fi +} # lsact + +function llact { + if [ $# = 0 ]; then + scm lsactivity -l | $PAGER + else + scm lsactivity -l "$@" + fi +} # llact + +function setact { + if [ $# = 0 ]; then + echo "What? Am I suppose to guess?!?" + exit 1 + else + scm setactivity "$@" + fi +} # setact + +function clist { + scmsystem; scmtype=$? + + if [ $scmtype = 1 ]; then + scm lsco -short -cview -all -me + elif [ $scmtype = 2 ]; then + cvs -Q status | grep ^File | grep -v Up-to-date + fi +} # clist + +function ciwork { + scm ci -cq $(clist) +} # ciwork + +# Bash completion for Clearcase commands. T + +# Add "," to COMP_BREAKWORDS +COMP_WORDBREAKS=\ \"\'\>\<\=\;\|\&\(\:\, + +# All Clearcase commands +_scm_cmds="\ +annotate \ +catcr \ +catcs \ +cd \ +chactivity \ +chbl \ +checkin \ +checkout \ +checkvob \ +chevent \ +chflevel \ +chfolder \ +chmaster \ +chpool \ +chproject \ +chstream \ +chtype \ +chview \ +ci \ +co \ +cptype \ +deliver \ +describe \ +diff \ +diffbl \ +diffcr \ +dospace \ +edcs \ +endview \ +file \ +find \ +findmerge \ +get \ +getcache \ +getlog \ +help \ +hostinfo \ +ln \ +lock \ +ls \ +lsactivity \ +lsbl \ +lscheckout \ +lscheckout \ +lsclients \ +lscomp \ +lsdo \ +lsfolder \ +lshistory \ +lslock \ +lsmaster \ +lspool \ +lsprivate \ +lsproject \ +lsregion \ +lsreplica \ +lssite \ +lsstgloc \ +lsstream \ +lstype \ +lstype \ +lsview \ +lsvob \ +lsvtree \ +man \ +merge \ +mkactivity \ +mkattr \ +mkattype \ +mkbl \ +mkbranch \ +mkbrtype \ +mkcomp \ +mkdir \ +mkelem \ +mkeltype \ +mkfolder \ +mkhlink \ +mkhltype \ +mklabel \ +mklbtype \ +mkpool \ +mkproject \ +mkregion \ +mkstgloc \ +mkstream \ +mktag \ +mktrigger \ +mktrtype \ +mkview \ +mkvob \ +mount \ +move \ +protect \ +protectvob \ +pwd \ +pwv \ +quit \ +rebase \ +recoverview \ +reformatview \ +reformatvob \ +register \ +relocate \ +rename \ +reqmaster \ +reserve \ +rmactivity \ +rmattr \ +rmbl \ +rmbranch \ +rmcomp \ +rmdo \ +rmelem \ +rmfolder \ +rmhlink \ +rmlabel \ +rmmerge \ +rmname \ +rmpool \ +rmproject \ +rmregion \ +rmstgloc \ +rmstream \ +rmtag \ +rmtrigger \ +rmtype \ +rmver \ +rmview \ +rmvob \ +schedule \ +setactivity \ +setcache \ +setcs \ +setplevel \ +setsite \ +setview \ +space \ +startview \ +umount \ +uncheckout \ +unlock \ +unregister \ +unreserve \ +update \ +winkin \ +" +# Kinds +_kinds="\ +attype \ +brtype \ +eltype \ +hltype \ +lbtype \ +trtype \ +" +# Type selectors +_type_selectors="\ +attype: \ +brtype: \ +eltype: \ +hltype: \ +lbtype: \ +trtype: \ +" +# These options are common to all scm commands. So far -help is the only one +_global_opts="\ +-help \ +" +# Comment opts. These options are common for commands that request the user +# specify a comment +_comment_opts="\ +-c \ +-cfile \ +-cq \ +-cqe \ +-nc \ +" +# Individual command options +_annotate_opts="\ +$_global_opts \ +-all \ +-fmt \ +-force \ +-long \ +-nco \ +-ndata \ +-nheader \ +-out \ +-rm \ +-rmfmt \ +-short \ +" +_catcr_opts="\ +$_global_opts \ +-check \ +-ci \ +-critical \ +-element \ +-flat \ +-follow \ +-long \ +-makefile \ +-name \ +-nxname \ +-recurse \ +-scripts \ +-select \ +-short \ +-type \ +-union \ +-view \ +-wd \ +-zero \ +" +_chbl_opts="\ +$_global_opts \ +$_comment_opts \ +-full \ +-incremental \ +-level \ +-nrecurse \ +" +_checkout_opts="\ +$_global_opts \ +$_comment_opts \ +-branch \ +-ndata \ +-nmaster \ +-nquery \ +-nwarn \ +-out \ +-ptime \ +-query \ +-reserved \ +-unreserved \ +-usehijack \ +-version \ +" +_chflevel_opts="\ +$_global_opts \ +-auto \ +-family \ +-force \ +-master \ +-override \ +-replica \ +" +_chpool_opts="\ +$_global_opts \ +$_comment_opts \ +-force \ +" +_chtype_opts="\ +$_global_opts \ +$_comment_opts \ +-force \ +-pname \ +" +_diff_opts="\ +$_global_opts \ +-graphical \ +-hstack \ +-options \ +-predecessor \ +-serial \ +-tiny \ +" +_diffbl_opts="\ +$_global_opts \ +-activities \ +-baselines \ +-first \ +-graphical \ +-nmerge \ +-nrecurse \ +-predecessor \ +-versions \ +" +_diffcr_opts="\ +$_global_opts \ +-ci \ +-critical \ +-element \ +-follow \ +-long \ +-name \ +-nxname \ +-recurse \ +-select \ +-short \ +-type \ +-view \ +-wd \ +" +_edcs_opts="\ +$_global_opts \ +-ctime \ +-overwrite \ +-ptime \ +-rename \ +-tag \ +" +_deliver_opts="\ +$_global_opts \ +-abort \ +-activities \ +-baseline \ +-cact \ +-cancel \ +-complete \ +-force \ +-gmerge \ +-graphical \ +-long \ +-ok \ +-preview \ +-qall \ +-qntrivial \ +-query \ +-reset \ +-resume \ +-serial \ +-short \ +-status \ +-stream \ +-target \ +-to \ +" +_diff_opts="\ +$_global_opts \ +-columns \ +-diff \ +-graphical \ +-hstack \ +-options \ +-predecessor \ +-serial \ +-tiny \ +-vstack \ +" +_diffbl_opts="\ +$_global_opts \ +-activities \ +-baselines \ +-elements \ +-first \ +-graphical \ +-nmerge \ +-nrecurse \ +-predecessor \ +-versions \ +" +_diffcr="\ +$_global_opts \ +-ci \ +-critical \ +-element \ +-flat \ +-follow \ +-long \ +-name \ +-nxname \ +-recurse \ +-select \ +-short \ +-type \ +-view \ +-wd \ +" +_dospace_opts="\ +$_global_opts \ +-all \ +-before \ +-dump \ +-generate \ +-pool \ +-references \ +-region \ +-scrub \ +-since \ +-size \ +-top \ +-update \ +" +_find_opts="\ +$_global_opts \ +-all \ +-avobs \ +-branch \ +-cview \ +-depth \ +-directory \ +-element \ +-exec \ +-follow \ +-group \ +-kind \ +-name \ +-nrecurse \ +-nvisible \ +-nxname \ +-ok \ +-type \ +-user \ +-version \ +-visible \ +" +_mkelem_opts="\ +$_global_opts \ +-c \ +-cfile \ +-ci \ +-cq \ +-cqe \ +-eltype \ +-master \ +-mkpath \ +-nc \ +-nco \ +-nwarn \ +" +_findmerge_opts="\ +$_global_opts \ +$_comment_opts \ +-abort \ +-all \ +-avobs \ +-blank \ +-depth \ +-directory \ +-element \ +-exec \ +-fbtag \ +-fcsets \ +-flatest \ +-follow \ +-ftag \ +-graphical \ +-group \ +-log \ +-long \ +-name \ +-nback \ +-nrecurse \ +-nxname \ +-nzero \ +-ok \ +-okgmerge \ +-okmerge \ +-qall \ +-qntrivial \ +-query \ +-serial \ +-short \ +-type \ +-unreserved \ +-user \ +-visible \ +-whynot \ +" +_get_opts="\ +$_global_opts \ +-to \ +" +_getcache_opts="\ +$_global_opts \ +-all \ +-host \ +-mvfs \ +-persistent \ +-reset \ +-short \ +-site \ +-view \ +" +_ln_opts="\ +$_global_opts \ +$_comment_opts \ +-force \ +-nco \ +-slink \ +" +_ls_opts="\ +$_global_opts \ +-directory \ +-long \ +-nxname \ +-recurse \ +-short \ +-view_only \ +-visibile \ +-vob_only \ +" +_lscheckout_opts="\ +$_global_opts \ +-all \ +-areplicas \ +-avobs \ +-brtype \ +-cview \ +-directory \ +-fmt \ +-graphical \ +-long \ +-me \ +-recurse \ +-short \ +-user \ +" +_lsdo_opts="\ +$_global_opts \ +-fmt \ +-long \ +-me \ +-nshareable_dos \ +-recurse \ +-short \ +-sname \ +-stime \ +-zero \ +" +_lshistory_opts="\ +$_global_opts \ +-all \ +-avobs \ +-branch \ +-directory \ +-eventid \ +-fmt \ +-graphical \ +-last \ +-local \ +-long \ +-me \ +-minor \ +-nco \ +-nopreferences \ +-pname \ +-recurse \ +-short \ +-since \ +-user \ +" +_lslock_opts="\ +$_global_opts \ +-all \ +-fmt \ +-local \ +-long \ +-obsolete \ +-pname \ +-short \ +" +_lsmaster_opts="\ +$_global_opts \ +-all \ +-fmt \ +-inreplicas \ +-kind \ +-view \ +" +_lsreplica_opts="\ +$_global_opts \ +-all \ +-fmt \ +-inreplicas \ +-invob \ +-long \ +-short \ +-siblings \ +" +_lssite_opts="\ +$_global_opts \ +-inquire \ +-setting-name \ +" +_lsstream_opts="\ +$_global_opts \ +-ancestor \ +-cview \ +-depth \ +-fmt \ +-in \ +-invob \ +-long \ +-obsolete \ +-recurse \ +-short \ +-template \ +-tree \ +-view \ +" +_lsvtree_opts="\ +$_global_opts \ +-all \ +-branch \ +-graphical \ +-merge \ +-nco \ +-nmerge \ +-nrecurse \ +-obsolete \ +-short \ +" +_merge_opts="\ +$_global_opts \ +-abort \ +-base \ +-c \ +-cfile \ +-columns \ +-cq \ +-cqe \ +-delete \ +-diff \ +-graphical \ +-insert \ +-narrows \ +-nc \ +-ndata \ +-options \ +-out \ +-qall \ +-qntrivial \ +-query \ +-replace \ +-serial \ +-tiny \ +-to \ +-version \ +" +_mkattr_opts="\ +$_global_opts \ +$_comment_opts \ +-ci \ +-config \ +-default \ +-name \ +-pname \ +-replace \ +-select \ +-type \ +-version \ +" +_mkattype_opts="\ +$_global_opts \ +$_comment_opts \ +-acquire \ +-default +-enum \ +-ge \ +-global \ +-gt \ +-le +-lt +-ordinary \ +-replace \ +-shared \ +-vpbranch \ +-vpelement \ +-vpversion \ +-vtype \ +" +_mkbranch_opts="\ +$_global_opts \ +$_comment_opts \ +-nco \ +-nwarn \ +-ptime \ +-version \ +" +_mkbrtype_opts="\ +$_global_opts \ +-acquire \ +-global \ +-ordinary \ +-pbranch \ +-replace \ +" +_mkdir_opts="\ +$_global_opts \ +$_comment_opts \ +-master \ +-nco \ +" +_mkelem_opts="\ +$_global_opts \ +$_comment_opts \ +-ci \ +-eltype \ +-master \ +-mkpath \ +-nco \ +-nwarn \ +-ptime \ +" +_mkeltype_opts="\ +$_global_opts \ +$_comment_opts \ +-acquire \ +-global \ +-manager \ +-mergetype \ +-ordinary \ +-ptime \ +-replace \ +-supertype \ +" +_mkhlink_opts="\ +$_global_opts \ +$_comment_opts \ +-acquire \ +-fpname \ +-ftext \ +-tpname \ +-ttext \ +-unidir \ +" +_mkhltype_opts="\ +$_global_opts \ +$_comment_opts \ +-acquire \ +-global \ +-ordinary \ +-replace \ +-shared \ +" +_mklabel_opts="\ +$_global_opts \ +$_comment_opts \ +-ci \ +-config \ +-follow \ +-name \ +-nc \ +-recurse \ +-replace \ +-select \ +-type \ +-version \ +" +_mklbtype_opts="\ +$_global_opts \ +$_comment_opts \ +-acquire \ +-global \ +-ordinary \ +-pbranch \ +-replace \ +-shared \ +" +_mkpool_opts="\ +$_global_opts \ +$_comment_opts \ +-age \ +-alert \ +-cleartext \ +-ln \ +-size \ +-source \ +-update \ +" +_mktrigger_opts="\ +$_global_opts \ +$_comment_opts \ +-force \ +-nattach \ +-ninherit \ +-recurse \ +" +_move_opts="\ +$_global_opts \ +$_comment_opts \ +" +_protect_opts="\ +$_global_opts \ +$_comment_opts \ +-chgrp \ +-chmod \ +-chown \ +-directory \ +-file \ +-pname \ +-recurse \ +" +_protectvob_opts="\ +$_global_opts \ +-add_group \ +-chgrp \ +-chown \ +-delete_group \ +-force \ +-nremote_admin \ +-remote_admin \ +" +_pwv_opts="\ +-root \ +-setview \ +-short \ +-wdview \ +" +_relocate_opts="\ +$_global_opts \ +-force \ +-log \ +-qall \ +-update \ +" +_rename_opts="\ +$_global_opts \ +$_comment_opts \ +-acquire \ +" +_reqmaster_opts="\ +$_global_opts \ +$_comment_opts \ +-acl \ +-allow \ +-deny \ +-disable \ +-edit \ +-enable \ +-get \ +-instances \ +-list \ +-set \ +" +_reserve_opts="\ +$_global_opts \ +$_comment_opts \ +-cact \ +" +_rmbranch_opts="\ +$_global_opts \ +$_comment_opts \ +-force \ +" +_rmdo_opts="\ +$_global_opts \ +-all \ +-before \ +-since \ +-zero \ +" +_rmelem_opts="\ +$_global_opts \ +$_comment_opts \ +-force \ +" +_rmmerge_opts="\ +$_global_opts \ +$_comment_opts \ +" +_rmname_opts="\ +$_global_opts \ +$_comment_opts \ +-force \ +" +_rmpool_opts="\ +$_global_opts \ +$_comment_opts \ +" +_rmstream_opts="\ +$_global_opts \ +$_comment_opts \ +" +_rmver_opts="\ +$_global_opts \ +$_comment_opts \ +-data \ +-force \ +-version \ +-vrange \ +-xattr \ +-xbranch \ +-xhlink \ +-xlabel \ +" +_schedule_opts="\ +$_global_opts \ +-acl \ +-delete \ +-edit \ +-force \ +-get \ +-host \ +-job \ +-run \ +-schedule \ +-set \ +-status \ +-tasks \ +-wait \ +" +_setactivity_opts="\ +$_global_opts \ +$_comment_opts \ +-none \ +-view \ +" +_setcache_opts="\ +$_global_opts \ +-acache \ +-cachesize \ +-cto \ +-cview \ +-cvpfreemax \ +-cvpfreemin \ +-default \ +-dirdnc \ +-dncache \ +-host \ +-mnmax \ +-mvfs \ +-nacache \ +-ncto \ +-ndncachez \ +-noentdnc \ +-nrlcache \ +-nrvcache \ +-password \ +-persistent \ +-persistent_only \ +-readdir_blocks \ +-regdnc \ +-rlcache \ +-rpchandles \ +-rvcache \ +-scalefactor \ +-site \ +-view \ +-vobfreemax \ +-vobfreemin \ +-zone \ +" +_setsite_opts="\ +$_global_opts \ +-password \ +" +_uncheckout_opts="\ +$_global_opts \ +-cact \ +-keep \ +-rm \ +-unco \ +" +_unregister_opts="\ +$_global_opts \ +-uuid \ +-view \ +-vob \ +" +_unreserve_opts="\ +$_global_opts \ +$_comment_opts \ +-cact \ +-view \ +" +_update_opts="\ +$_global_opts \ +-add_loadrules \ +-ctime \ +-force \ +-graphical \ +-log \ +-noverwrite \ +-overwrite \ +-print \ +-ptime \ +-rename \ +" +_winkin_opts="\ +$_global_opts \ +-adirs \ +-adirs \ +-ci \ +-noverwrite \ +-out \ +-print \ +-recurse \ +-select \ +-siblings \ +" + +_object_selectors="\ +activity: \ +attype: \ +baseline: \ +brtype: \ +component: \ +eltype: \ +folder: \ +hlink: \ +hltype: \ +lbtype: \ +oid: \ +pool: \ +project: \ +replica: \ +stream: \ +trtype: \ +vob: \ +" + +_operation_types="\ +chactivity \ +chbl \ +checkin \ +checkout \ +chevent \ +chfolder \ +chmaster \ +chproject \ +chstream \ +chtype \ +deliver_cancel \ +deliver_complete \ +deliver_start \ +lnname \ +lock \ +mkactivity \ +mkattr \ +mkbl \ +mkbl_complete \ +mkbranch \ +mkcomp \ +mkelem \ +mkfolder \ +mkhlink \ +mklabel \ +mkproject \ +mkslink \ +mkstream \ +mktrigger \ +protect \ +rebase_cancel \ +rebase_complete \ +rebase_start \ +reserve \ +rmactivity \ +rmattr \ +rmbranch \ +rmcl \ +rmcomp \ +rmelem \ +rmfolder \ +rmhlink \ +rmlabel \ +rmname \ +rmproject \ +rmstream \ +rmtrigger \ +rmver \ +setactivity \ +setactivity_none \ +setplevel \ +uncheckout \ +unlock \ +unreserve \ +" + +if [ $ARCH = 'cygwin' ]; then + _arch_policies="\ +POLICY_WIN_INT_SNAP \ +POLICY_WIN_DEV_SNAP \ +" +else + _arch_policies="\ +POLICY_UNIX_INT_SNAP \ +POLICY_UNIX_DEV_SNAP \ +" +fi + +_policies="\ +$arch_policies \ +POLICY_CHPROJECT_UNRESTRICED \ +POLICY_DELIVER_REQUIRE_REBASE \ +POLICY_DELIVER_NCO_DEVSTR \ +POLICY_DELIVER_NOCO_SELACTS \ +POLICY_REBASE_CO \ +POLICY_INTRAPROJECT_DELIVER_FOUNDATION_CHANGES \ +POLICY_INTRAPROJECT_DELIVER_ALLOW_MISSING_TGTCOMPS \ +POLICY_INTERPROJECT_DELIVER \ +POLICY_INTERPROJECT_DELIVER_FOUNDATION_CHANGES \ +POLICY_INTERPROJECT_DELIVER_REQUIRE_TGTCOMP_VISIBIITY \ +POLICY_INTERPROJECT_DELIVER_ALLOW_NONMOD_TGTCOMPS \ +" + +# Helper functions +function set_pvob () { + if [ $# -eq 1 ]; then + pvob=$1 + fi +} # set_pvob + +function set_dvob () { + if [ $# -eq 1 ]; then + dvob=$1 + fi +} # set_dvob + +# Listing type functions. These functions produce a list of some object and are +# used by the various completion functions. +function _attypes () { + if [ $# -gt 0 ]; then + scm lstype -kind attype -invob $pvob -fmt "%n " | grep ^$1 + else + scm lstype -kind attype -invob $pvob -fmt "%n " + fi +} # _attypes + +function _activities () { + if [ $# -gt 0 ]; then + scm lsactivity -short -invob $pvob | grep ^$1 | sed "s:$:@$vobtagPrefix$pvob:" + else + scm lsactivity -short -invob $pvob | sed "s:$:@$vobtagPrefix$pvob:" + fi +} # _activities + +function _baselines () { + stream=$(scm lsstream -short 2> /dev/null) + + if [ -z $stream ]; then + return; + fi + + if [ $# -ge 0 ]; then + scm lsbl -stream $stream@$pvob -short | grep ^$1 | sed "s:$:@$vobtagPrefix$pvob:" + else + scm lsbl -stream $stream@$pvob -short | sed "s:$:@$vobtagPrefix$pvob:" + fi +} # _baselines + +function _brtypes () { + if [ $# -gt 0 ]; then + scm lstype -kind brtype -invob $pvob -fmt "%n\n" | grep ^$1 + else + scm lstype -kind brtype -invob $pvob -fmt "%n\n" + fi +} # _brtypes + +function _cchosts () { + registryHost=$(scm hostinfo -long | grep "Registry host:" | awk '{print $NF}') + + scm lsclients -short -host $registryHost | tr [:upper:] [:lower:] +} # _cchosts + +function _components () { + if [ $# -gt 0 ]; then + scm lscomp -short -invob $pvob | grep ^$1 | sed "s:$:@$vobtagPrefix$pvob:" + else + scm lscomp -short -invob $pvob | sed "s:$:@$vobtagPrefix$pvob:" + fi +} # _components + +function _eltypes () { + if [ $# -gt 0 ]; then + scm lstype -kind eltype -invob $pvob -fmt "%n " | grep ^$1 + else + scm lstype -kind eltype -invob $pvob -fmt "%n " + fi +} # _eltypes + +function _folders () { + if [ $# -gt 0 ]; then + scm lsfolder -short -invob $pvob | grep ^$1 | sed "s:$:@$vobtagPrefix$pvob:" + else + scm lsfolder -short -invob $pvob | sed "s:$:@$vobtagPrefix$pvob:" + fi +} # _folders + +function _hltypes () { + if [ $# -gt 0 ]; then + scm lstype -kind hltype -invob $pvob -fmt "%n " | grep ^$1 + else + scm lstype -kind hltype -invob $pvob -fmt "%n " + fi +} # _hltypes + +function _lbtypes () { + if [ $# -gt 0 ]; then + scm lstype -kind lbtype -invob $pvob -fmt "%n@$vobtagPrefix$pvob " | grep ^$1 + else + scm lstype -kind lbtype -invob $pvob -fmt "%n@$vobtagPrefix$pvob " + fi +} # _lbtypes + +function _projects () { + if [ $# -gt 0 ]; then + scm lsproject -short -invob $pvob | grep ^$1 | sed "s:$:@$vobtagPrefix$pvob:" + else + scm lsproject -short -invob $pvob | sed "s:$:@$vobtagPrefix$pvob:" + fi +} # _projects + +function _streams () { + if [ $# -gt 0 ]; then + scm lsstream -short -invob $pvob | grep ^$1 | sed "s:$:@$vobtagPrefix$pvob:" + else + scm lsstream -short -invob $pvob | sed "s:$:@$vobtagPrefix$pvob:" + fi +} # _streams + +function _stglocs () { + if [ $# -gt 0 ]; then + scm lsstgloc -short | grep ^$1 + else + scm lsstgloc -short + fi +} # _stglocs + +function _views () { + if [ $# -gt 0 ]; then + scm lsview -short | grep ^$1; + else + scm lsview -short; + fi +} # _views + +function _vobs () { + # We simply must rid ourselves of these bloody backlashes!!! And yet Clearcase + # insists on retaining them. Let's strip them off, do our thing, then put them + # back to backslashes when we output stuff. + if [ $ARCH = 'cygwin' ]; then + if [ $# -eq 0 ]; then + scm lsvob -short | sed 's:\\:\\\\\\\\:' + else + # Convert to foward slashes + vob=$(echo $1 | sed 's:\\::g') + + scm lsvob -short | sed 's:\\::' | grep ^$vob | sed 's:^:\\\\\\\\:' + fi + else + if [ $# -eq 0 ]; then + scm lsvob -short + else + scm lsvob -short | grep ^$1 + fi + fi +} # _vobs + +function _trtypes () { + if [ $# -gt 0 ]; then + scm lstype -kind trtype -invob $pvob -fmt "%n " | grep ^$1 + else + scm lstype -kind trtype -invob $pvob -fmt "%n " + fi +} # _trtypes + +# Completion functions for individual commands where the completion may change +# depending on the operation. For example, typing "scm catcs -tag " should +# now complete based on view tags whereas "scm catcs -" should just complete +# based on options (which are only -help and -tag at this point for this +# particular command). If a command does not have a "completer function" then +# it will only provide for options completion and will be handled by the general +# _scm completion function. +function _catcs () { + local cur prev + local opts="\ +$_global_opts \ +-tag \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -tag ]]; then + COMPREPLY=($(compgen -W "$(lsviews)" -- $cur)) + fi +} # _catcs + +function _chactivity () { + local cur prev + local opts="\ +$_global_opts \ +$_comment_opts \ +-headline \ +-fcset \ +-tcset \ +-view \ +-cqaction \ +-cact \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -fcset || $prev == -tcset ]]; then + COMPREPLY=($(compgen -W "$(_activities $cur)")) + elif [[ $prev == -view ]]; then + COMPREPLY=($(compgen -W "$(lsviews)" -- $cur)) + fi +} # _chactivity + +function _checkin () { + local cur prev prev2 + local opts="\ +$_global_opts \ +$_comment_opts \ +-cact \ +-cr \ +-from \ +-identical \ +-keep \ +-ptime \ +-rm \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + prev2="${COMP_WORDS[COMP_CWORD-2]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev2 == activity && $prev == : ]]; then + COMPREPLY=($(compgen -W "$(_activities $cur)")) + fi +} # _checkin + +function _checkvob () { + local cur prev + local opts="\ +$_global_opts \ +-acquire \ +-activity \ +-cleartext \ +-component \ +-crm \ +-data \ +-debris \ +-derived \ +-fix \ +-force \ +-from \ +-global \ +-hlinks \ +-hltype \ +-ignore \ +-lock \ +-log \ +-pname \ +-pool \ +-protections \ +-setup \ +-source \ +-to \ +-trssize \ +-ucm \ +-unlock \ +-verbose \ +-view \ +-vob \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -view ]]; then + COMPREPLY=($(compgen -W "$(lsviews)" -- $cur)) + fi +} # _checkvob + +function _chevent () { + local cur prev + local opts="\ +$_global_opts \ +$_comment_opts \ +-append \ +-event \ +-insert \ +-invob \ +-pname \ +-replace \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -invob ]]; then + COMPREPLY=($(compgen -W "$(_vobs $cur)")) + elif [[ $cur == * ]]; then + COMPREPLY=($(compgen -W "$_object_selectors" -- $cur)) + fi +} # _chevent + +function _chfolder () { + local cur prev + local opts="\ +$_global_opts \ +$_comment_opts \ +-to \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -to ]]; then + COMPREPLY=($(compgen -W "$(_folders $cur)")) + elif [[ $cur == * ]]; then + COMPREPLY=($(compgen -W "$(_folders $cur)")) + fi +} # _chfolder + +function _chmaster () { + local cur prev + local opts="\ +$_global_opts \ +$_comment_opts \ +-all \ +-default \ +-long \ +-obsolete_replica \ +-override \ +-pname \ +-stream \ +-view \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -stream ]]; then + COMPREPLY=($(compgen -W "$(_streams $cur)")) + elif [[ $cur == -view ]]; then + COMPREPLY=($(compgen -W "$(lsviews)" -- $cur)) + fi +} # _chmaster + +function _chproject () { + local cur prev + local opts="\ +$_global_opts \ +$_comment_opts \ +-amodcomp \ +-blname \ +-connection \ +-crmenable \ +-custom \ +-dmodcomp \ +-ncrmenable \ +-npolicy \ +-policy \ +-rebase \ +-spolicy \ +-template \ +-to \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -amodcomp || + $prev == -dmodcomp ]]; then + COMPREPLY=($(compgen -W "$(_components $cur)")) + elif [[ $prev == -to ]]; then + COMPREPLY=($(compgen -W "$(_folders $cur)")) + elif [[ $prev == -policy || + $prev == -npolicy || + $prev == -spolicy ]]; then + COMPREPLY=($(compgen -W "$_policies" -- $cur)) + elif [[ $cur == * ]]; then + COMPREPLY=($(compgen -W "$(_projects $cur)")) + fi +} # _chproject + +function _chstream () { + local cur prev + local opts="\ +$_global_opts \ +$_comment_opts \ +-amodcomp \ +-cview \ +-default \ +-dmodcomp \ +-generate \ +-npolicy \ +-nrecommended \ +-ntarget \ +-policy \ +-recommended \ +-target \ +-template \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -amodcomp || + $prev == -dmodcomp ]]; then + COMPREPLY=($(compgen -W "$(_components $cur)")) + elif [[ $prev == -policy || + $prev == -npolicy ]]; then + COMPREPLY=($(compgen -W "$_policies" -- $cur)) + elif [[ $prev == , || $prev == -recommended || $cur == , ]]; then + if [[ $cur == , ]]; then + cur='' + fi + COMPREPLY=($(compgen -W "$(_baselines $cur)")) + elif [[ $prev == -target || $cur == * ]]; then + COMPREPLY=($(compgen -W "$(_streams $cur)")) + fi +} # _chstream + +function _chview () { + local cur prev + local opts="\ +$_global_opts \ +-cachesize \ +-cview \ +-force \ +-nshareable \ +-readonly \ +-readwrite \ +-shareable \ +-stream \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -stream ]]; then + COMPREPLY=($(compgen -W "$(_streams $cur)")) + elif [[ $prev == * ]]; then + COMPREPLY=($(compgen -W "$(lsviews)" -- $cur)) + fi +} # _chview + +function _deliver () { + local cur prev + local opts="\ +$_global_opts \ +-activities \ +-cact \ +-complete \ +-force \ +-gmerge \ +-graphical \ +-query \ +-reset \ +-serial \ +-stream \ +-target \ +-to \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -stream || $prev == -target ]]; then + COMPREPLY=($(compgen -W "$(_streams $cur)")) + elif [[ $prev == -to ]]; then + COMPREPLY=($(compgen -W "$(lsviews)" -- $cur)) + elif [[ $prev == -activities ]]; then + COMPREPLY=($(compgen -W "$(_activities $cur)")) + fi +} # _deliver + +function _describe () { + local cur prev + local opts="\ +$_global_opts \ +-aattr \ +-ahlink \ +-alabel \ +-aliases \ +-all \ +-cact \ +-cview \ +-graphical \ +-ihlink \ +-local \ +-long \ +-pname \ +-predecessor \ +-type \ +-version \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $cur == * ]]; then + COMPREPLY=($(compgen -W "$_object_selectors" -- $cur)) + fi +} # _describe + +function _dospace () { + local cur prev + local opts="\ +$_global_opts \ +-before \ +-pool \ +-references \ +-region \ +-scrub \ +-since \ +-size \ +-top \ +-update \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -region ]]; then + COMPREPLY=($(compgen -W "$(lsregions)" -- $cur)) + fi +} # _dospace + +function _edcs () { + local cur prev + local opts="\ +$_global_opts \ +-ctime \ +-overwrite \ +-ptime \ +-rename \ +-tag \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -tag ]]; then + COMPREPLY=($(compgen -W "$(lsviews)" -- $cur)) + fi +} # _edcs + +function _endview () { + local cur prev + local opts="\ +$_global_opts \ +-server \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == * ]]; then + COMPREPLY=($(compgen -W "$(lsviews)" -- $cur)) + fi +} # _endview + +function _file () { + local cur prev + local opts="\ +$_global_opts \ +-all \ +-invob \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -invob ]]; then + COMPREPLY=($(compgen -W "$(_vobs $cur)")) + fi +} # _file + +function _getlog () { + local cur prev + local opts="\ +$_global_opts \ +-all \ +-around \ +-cview \ +-full \ +-graphical \ +-host \ +-inquire \ +-last \ +-since \ +-tag \ +-vob \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -tag || $prev == -target ]]; then + COMPREPLY=($(compgen -W "$(lsviews)" -- $cur)) + elif [[ $prev == -vob ]]; then + COMPREPLY=($(compgen -W "$(_vobs $cur)")) + elif [[ $prev == -host ]]; then + COMPREPLY=($(compgen -W "$(_cchosts)" -- $cur)) + fi +} # _getlog + +function _hostinfo () { + local cur prev + local opts="\ +$_global_opts \ +-full \ +-long \ +-properties \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $cur == * ]]; then + COMPREPLY=($(compgen -W "$(_cchosts)" -- $cur)) + fi +} # _hostinfo + +function _lsactivity () { + local cur prev + local opts="\ +$_global_opts \ +-ancestor \ +-cact \ +-contrib \ +-cview \ +-depth \ +-fmt \ +-fmt \ +-in \ +-invob \ +-long \ +-me \ +-obsolete \ +-recurse \ +-short \ +-user \ +-view \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -invob ]]; then + COMPREPLY=($(compgen -W "$(_vobs $cur)")) + elif [[ $prev == -in ]]; then + COMPREPLY=($(compgen -W "$(_streams $cur)")) + elif [[ $prev == -view ]]; then + COMPREPLY=($(compgen -W "$(lsviews)" -- $cur)) + elif [[ $cur == * ]]; then + COMPREPLY=($(compgen -W "$(_activities $cur)")) + fi +} # _lsactivity + +function _lsbl () { + local cur prev + local opts="\ +$_global_opts \ +-component \ +-cview \ +-fmt \ +-gtlevel \ +-level \ +-long \ +-long \ +-ltlevel \ +-obsolete \ +-recurse \ +-short \ +-stream \ +-tree \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -component ]]; then + COMPREPLY=($(compgen -W "$(_components $cur)")) + elif [[ $prev == -stream ]]; then + COMPREPLY=($(compgen -W "$(_streams $cur)")) + fi +} # _lsbl + +function _lsclients () { + local cur prev + local opts="\ +$_global_opts \ +-host \ +-type \ +-short \ +-long \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -host ]]; then + COMPREPLY=($(compgen -W "$(_cchosts $cur)")) + fi +} # _lsclients + +function _lscomp () { + local cur prev + local opts="\ +$_global_opts \ +-fmt \ +-invob \ +-long \ +-obsolete \ +-short \ +-tree \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -invob ]]; then + COMPREPLY=($(compgen -W "$(_vobs $cur)")) + elif [[ $cur == * ]]; then + COMPREPLY=($(compgen -W "$(_components $cur)")) + fi +} # _lscomp + +function _lsfolder () { + local cur prev + local opts="\ +$_global_opts \ +-ancestor \ +-cview \ +-depth \ +-fmt \ +-in \ +-invob \ +-obsolete \ +-recurse \ +-short \ +-tree \ +-view \ +" + local lopts="$opts -long" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + if [[ $prev == -llfolder ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + else + COMPREPLY=($(compgen -W "$lopts" -- $cur)) + fi + elif [[ $prev == -invob ]]; then + COMPREPLY=($(compgen -W "$(_vobs $cur)")) + elif [[ $prev == -view ]]; then + COMPREPLY=($(compgen -W "$(lsviews)" -- $cur)) + elif [[ $prev == -in || $cur == * ]]; then + COMPREPLY=($(compgen -W "$(_folders $cur)")) + fi +} # _lsfolder + +function _lspool () { + local cur prev + local opts="\ +$_global_opts \ +-fmt \ +-invob \ +-long \ +-obsolete \ +-short \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -invob ]]; then + COMPREPLY=($(compgen -W "$(_vobs $cur)")) + fi +} # _lspool + +function _lsprivate () { + local cur prev + local opts="\ +$_global_opts \ +-age \ +-co \ +-do \ +-invob \ +-long \ +-other \ +-short \ +-size \ +-tag \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -tag ]]; then + COMPREPLY=($(compgen -W "$(lsviews)" -- $cur)) + elif [[ $prev == -invob ]]; then + COMPREPLY=($(compgen -W "$(_vobs $cur)")) + fi +} # _lsprivate + +function _lsproject () { + local cur prev + local opts="\ +$_global_opts \ +-ancestor \ +-custom \ +-cview \ +-depth \ +-fmt \ +-in \ +-invob \ +-long \ +-obsolete \ +-recurse \ +-short \ +-template \ +-tree \ +-view \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -view ]]; then + COMPREPLY=($(compgen -W "$(lsviews)" -- $cur)) + elif [[ $prev == -invob ]]; then + COMPREPLY=($(compgen -W "$(_vobs $cur)")) + elif [[ $prev == -in || $cur == * ]]; then + COMPREPLY=($(compgen -W "$(_folders $cur)")) + fi +} # _lsproject + +function _lsregion () { + local cur prev + local opts="\ +$_global_opts \ +-long \ +-short \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == * ]]; then + regions=$(scm lsregion -short | grep ^$cur) + COMPREPLY=($(compgen -W "$regions" -- $cur)) + fi +} # _lsregion + +function _lsstgloc () { + local cur prev + local opts="\ +$_global_opts \ +-host \ +-long \ +-region \ +-short \ +-storage \ +-view \ +-vob \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -region ]]; then + COMPREPLY=($(compgen -W "$(_regions $cur)" -- $cur)) + elif [[ $prev == -storage ]]; then + COMPREPLY=($(compgen -W "$(_stglocs $cur)")) + elif [[ $prev == -host ]]; then + _cchosts + fi +} # _lsstgloc + +function _lsstream () { + local cur prev + local opts="\ +$_global_opts \ +-ancestor \ +-cview \ +-depth \ +-fmt \ +-in \ +-invob \ +-obsolete \ +-recurse \ +-short \ +-template \ +-tree \ +-view \ +" + local lopts="$opts -long" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + if [[ $prev == llstream ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + else + COMPREPLY=($(compgen -W "$lopts" -- $cur)) + fi + elif [[ $prev == -in ]]; then + COMPREPLY=($(compgen -W "$(_projects $cur)")) + elif [[ $prev == -invob ]]; then + COMPREPLY=($(compgen -W "$(_vobs $cur)")) + elif [[ $prev == -view ]]; then + COMPREPLY=($(compgen -W "$(lsviews)")) + elif [[ $cur == * ]]; then + COMPREPLY=($(compgen -W "$(_streams $cur)")) + fi +} # _lsstream + +function _lstype () { + local cur prev + local opts="\ +$_global_opts \ +-fmt \ +-graphical \ +-invob \ +-kind \ +-local \ +-long \ +-nostatus \ +-obsolete \ +-short \ +-unsorted \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -kind ]]; then + COMPREPLY=($(compgen -W "$_kinds" -- $cur)) + elif [[ $prev == -invob ]]; then + COMPREPLY=($(compgen -W "$(_vobs $cur)")) + elif [[ $cur == * ]]; then + _type_selector + fi +} # _lstype + +function _lsview () { + local cur prev + local opts="\ +$_global_opts \ +-age \ +-cview \ +-full \ +-host \ +-properties \ +-quick \ +-region \ +-short \ +-storage \ +-text \ +-uuid \ +" + local lopts="$opts -long" + + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + if [[ $prev == llview ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + else + COMPREPLY=($(compgen -W "$lopts" -- $cur)) + fi + elif [[ $prev == -region ]]; then + COMPREPLY=($(compgen -W "$(_regions $cur)" -- $cur)) + elif [[ $prev == -host ]]; then + COMPREPLY=($(compgen -W "$(_cchosts $cur)")) + elif [[ $cur == * ]]; then + COMPREPLY=($(compgen -W "$(lsviews)" -- $cur)) + fi +} # _lsview + +function _lsvob () { + local cur prev + local opts="\ +$_global_opts \ +-family \ +-graphical \ +-host \ +-quick \ +-region \ +-short \ +-storage \ +-uuid \ +" + lopts="$opts -long" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + if [[ $prev == llvob ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + else + COMPREPLY=($(compgen -W "$lopts" -- $cur)) + fi + elif [[ $prev == -region ]]; then + COMPREPLY=($(compgen -W "$(_regions $cur)")) + elif [[ $prev == -host ]]; then + COMPREPLY=($(compgen -W "$(_cchosts $cur)")) + elif [[ $cur == * ]]; then + COMPREPLY=($(compgen -W "$(_vobs $cur)")) + fi +} # _lsvob + +function _mkactivity () { + local cur prev + local opts="\ +$_global_opts \ +-force \ +-headline \ +-in \ +-nset \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -in ]]; then + COMPREPLY=($(compgen -W "$(_streams $cur)")) + fi +} # _mkactivity + +function _mkbl () { + local cur prev + local opts="\ +$_global_opts \ +-activities \ +-adepends \ +-all \ +-component \ +-ddepends \ +-full \ +-identical \ +-import \ +-incremental \ +-nact \ +-nlabel \ +-view \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -activities ]]; then + COMPREPLY=($(compgen -W "$(_activities $cur)")) + elif [[ $prev == -component || + $prev == -adepends_on || + $prev == -ddepends_on ]]; then + COMPREPLY=($(compgen -W "$(_components $cur)")) + elif [[ $prev == -clone ]]; then + COMPREPLY=($(compgen -W "$(_baselines $cur)")) + elif [[ $prev == -view ]]; then + COMPREPLY=($(compgen -W "$(lsviews)" -- $cur)) + fi +} # _mkbl + +function _mkcomp () { + local cur prev + local opts="\ +$_global_opts \ +-nroot \ +-root \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $cur == * ]]; then + COMPREPLY=($(compgen -W "$(_components $cur)")) + fi +} # _mkcomp + +function _mkfolder () { + local cur prev + local opts="\ +$_global_opts \ +-in \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -in || + $cur == * ]]; then + COMPREPLY=($(compgen -W "$(_folders $cur)")) + fi +} # _mkfolder + +function _mkproject () { + local cur prev + local opts="\ +$_global_opts \ +-blname \ +-connection \ +-crmenable \ +-custom \ +-in \ +-modcomp \ +-model \ +-npolicy \ +-policy \ +-spolicy \ +-template \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -modcomp ]]; then + COMPREPLY=($(compgen -W "$(_components $cur)")) + elif [[ $prev == -in ]]; then + COMPREPLY=($(compgen -W "$(_folders $cur)")) + elif [[ $prev == -policy || + $prev == -npolicy || + $prev == -spolicy ]]; then + COMPREPLY=($(compgen -W "$_policies" -- $cur)) + fi +} # _mkproject + +function _mkregion () { + local cur prev + local opts="\ +$_global_opts \ +-replace \ +-tag \ +-tcomment \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -tag ]]; then + COMPREPLY=($(compgen -W "$(_regions $cur)" -- $cur)) + fi +} # _mkregion + +function _mkstgloc () { + local cur prev + local opts="\ +$_global_opts \ +-comment \ +-force \ +-gpath \ +-host \ +-hpath \ +-ngpath \ +-region \ +-view \ +-vob \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -region ]]; then + COMPREPLY=($(compgen -W "$(_regions $cur)" -- $cur)) + elif [[ $prev == -host ]]; then + COMPREPLY=($(compgen -W "$(_cchosts $cur)")) + fi +} # _mkstgloc + +function _mkstream () { + local cur prev + local opts="\ +$_global_opts \ +-amodcomp \ +-baseline \ +-in \ +-integration \ +-nc \ +-npolicy \ +-policy \ +-readonly \ +-target \ +-template \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -amodcomp ]]; then + COMPREPLY=($(compgen -W "$(_components $cur)")) + elif [[ $prev == -baseline ]]; then + COMPREPLY=($(compgen -W "$(_baselines $cur)")) + elif [[ $prev == -in ]]; then + COMPREPLY=($(compgen -W "$(_projects $cur)")) + elif [[ $prev == -policy || + $prev == -npolicy ]]; then + COMPREPLY=($(compgen -W "$(_policies $cur)" -- $cur)) + fi +} # _mkstream + +function _mktag () { + local cur prev + local opts="\ +$_global_opts \ +-gpath \ +-host \ +-nstart \ +-options \ +-password \ +-public \ +-region \ +-replace \ +-tag \ +-tcomment \ +-view \ +-vob \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + prev2="$(COMP_WORDS[COMP_CWORD-2])" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev2 == -view && $prev == "-tag" ]]; then + COMPREPLY=($(compgen -W "$(lsviews)" -- $cur)) + elif [[ $prev2 == -vob && $prev == "-tag" ]]; then + COMPREPLY=($(compgen -W "$(_vobs $cur)")) + elif [[ $prev == -region ]]; then + COMPREPLY=($(compgen -W "$(_regions $cur)" -- $cur)) + elif [[ $prev == -host ]]; then + COMPREPLY=($(compgen -W "$(_cchosts $cur)")) + fi +} # _mktag + +function _mktrtype () { + local cur prev + local opts="\ +$_global_opts \ +-activity \ +-all \ +-attype \ +-baseline \ +-brtype \ +-component \ +-eltype \ +-execunix \ +-execwin \ +-folder \ +-hltype \ +-lbtype \ +-mkattr \ +-mkhlink \ +-mklabel \ +-nc \ +-nusers \ +-postop \ +-print \ +-project \ +-replace \ +-stream \ +-trtype \ +-type \ +-ucmobject \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -preop || + $prev == -postopt ]]; then + COMPREPLY=($(compgen -W "$_operation_types" -- $cur)) + elif [[ $prev == -project ]]; then + COMPREPLY=($(compgen -W "$(_projects $cur)")) + elif [[ $prev == -stream ]]; then + COMPREPLY=($(compgen -W "$(_streams $cur)")) + elif [[ $prev == -component ]]; then + COMPREPLY=($(compgen -W "$(_components $cur)")) + elif [[ $prev == -folder ]]; then + COMPREPLY=($(compgen -W "$(_folders $cur)")) + elif [[ $prev == -activity ]]; then + COMPREPLY=($(compgen -W "$(_activites $cur)" -- $cur)) + elif [[ $prev == -baseline ]]; then + COMPREPLY=($(compgen -W "$(_baselines $cur)")) + elif [[ $prev == -attype ]]; then + COMPREPLY=($(compgen -W "$(_attypes $cur)")) + elif [[ $prev == -brtype ]]; then + COMPREPLY=($(compgen -W "$(_brtypes $cur)")) + elif [[ $prev == -eltype ]]; then + COMPREPLY=($(compgen -W "$(_eltypes $cur)")) + elif [[ $prev == -hltype ]]; then + COMPREPLY=($(compgen -W "$(_hltypes $cur)")) + elif [[ $prev == -lbtype ]]; then + COMPREPLY=($(compgen -W "$(_lbtypes $cur)")) + elif [[ $prev == -trtypes ]]; then + COMPREPLY=($(compgen -W "$(_trtypes $cur)")) + fi +} # _mktrtype + +function _mkview () { + local cur prev + local opts="\ +$_global_opts \ +-auto \ +-cachesize \ +-colocated \ +-gpath \ +-host \ +-hpath \ +-nshareable \ +-ptime \ +-region \ +-shareable \ +-snapshot \ +-stgloc \ +-stream \ +-tag \ +-tcomment \ +-tmode \ +-vws \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -tag ]]; then + COMPREPLY=($(compgen -W "$(lsviews)" -- $cur)) + elif [[ $prev == -region ]]; then + COMPREPLY=($(compgen -W "$(_regions $cur)" -- $cur)) + elif [[ $prev == -stgloc ]]; then + COMPREPLY=($(compgen -W "$(_stglocs $cur)")) + elif [[ $prev == -host ]]; then + COMPREPLY=($(compgen -W "$(_cchosts $cur)")) + fi +} # _mkview + +function _mkvob () { + local cur prev + local opts="\ +$_global_opts \ +-auto \ +-gpath \ +-host \ +-hpath \ +-nc \ +-nremote \ +-options \ +-password \ +-public \ +-region \ +-stgloc \ +-tag \ +-tcomment \ +-ucmproject \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -tag ]]; then + COMPREPLY=($(compgen -W "$(_vobs $cur)")) + elif [[ $prev == -region ]]; then + COMPREPLY=($(compgen -W "$(_regions $cur)" -- $cur)) + elif [[ $prev == -stgloc ]]; then + COMPREPLY=($(compgen -W "$(_stglocs $cur)")) + elif [[ $prev == -host ]]; then + COMPREPLY=($(compgen -W "$(_cchosts $cur)")) + fi +} # _mkvob + +function _mount () { + local cur prev + local opts="\ +$_global_opts \ +-all \ +-options \ +-persistent \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $cur == * ]]; then + COMPREPLY=($(compgen -W "$(_vobs $cur)")) + fi +} # _mount + +function _rebase () { + local cur prev + local opts="\ +$_global_opts \ +-abort \ +-baseline \ +-cancel \ +-complete \ +-dbaseline \ +-force \ +-gmerge \ +-graphical \ +-long \ +-ok \ +-preview \ +-qall \ +-qntrivial \ +-query \ +-recommended \ +-resume \ +-serial \ +-short \ +-status \ +-stream \ +-view \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -view ]]; then + COMPREPLY=($(compgen -W "$(lsviews)" -- $cur)) + elif [[ $prev == -stream ]]; then + COMPREPLY=($(compgen -W "$(_streams $cur)")) + elif [[ $prev == -baseline || + $prev == -dbaseline ]]; then + COMPREPLY=($(compgen -W "$(_baselines $cur)")) + fi +} # _rebase + +function _recoverview () { + local cur prev + local opts="\ +$_global_opts \ +-directory \ +-force \ +_synchronize \ +-tag \ +-vob \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -tag ]]; then + COMPREPLY=($(compgen -W "$(lsviews)" -- $cur)) + elif [[ $prev == -vob ]]; then + COMPREPLY=($(compgen -W "$(_vobs $cur)")) + fi +} # _recoverview + +function _reformatview () { + local cur prev + local opts="\ +$_global_opts \ +-dump \ +-load \ +-tag \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -tag ]]; then + COMPREPLY=($(compgen -W "$(lsviews)" -- $cur)) + fi +} # _reformatview + +function _reformatvob () { + local cur prev + local opts="\ +$_global_opts \ +-dump \ +-force \ +-host \ +-hpath \ +-load \ +-rm \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -host ]]; then + COMPREPLY=($(compgen -W "$(_cchosts $cur)")) + fi +} # _reformatvob + +function _register () { + local cur prev + local opts="\ +$_global_opts \ +-host \ +-hpath \ +-replace \ +-ucmproject \ +-view \ +-vob \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -host ]]; then + COMPREPLY=($(compgen -W "$(_cchosts $cur)")) + fi +} # _register + +function _rmactivity () { + local cur prev + local opts="\ +$_global_opts \ +$_comment_opts \ +-force \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $cur == * ]]; then + COMPREPLY=($(compgen -W "$(_activities $cur)")) + fi +} # _rmactivity + +function _rmattr () { + local cur prev + local opts="\ +$_global_opts \ +$_comment_opts \ +-recurse \ +-version \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $cur == * ]]; then + COMPREPLY=($(compgen -W "$(_attypes $cur)")) + fi +} # _rmattr + +function _rmbl () { + local cur prev + local opts="\ +$_global_opts \ +$_comment_opts \ +-force\ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $cur == * ]]; then + COMPREPLY=($(compgen -W "$(_baselines $cur)")) + fi +} # _rmbl + +function _rmcomp () { + local cur prev + local opts="\ +$_global_opts \ +$_comment_opts \ +-force\ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $cur == * ]]; then + COMPREPLY=($(compgen -W "$(_components $cur)")) + fi +} # _rmcomp + +function _rmfolder () { + local cur prev + local opts="\ +$_global_opts \ +$_comment_opts \ +-force\ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $cur == * ]]; then + COMPREPLY=($(compgen -W "$(_folders $cur)")) + fi +} # _rmfolder + +function _rmhlink () { + local cur prev + local opts="\ +$_global_opts \ +$_comment_opts \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $cur == * ]]; then + COMPREPLY=($(compgen -W "$(_hltypes $cur)")) + fi +} # _rmhlink + +function _rmlabel () { + local cur prev + local opts="\ +$_global_opts \ +$_comment_opts \ +-follow \ +-recurse \ +-version \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $cur == * ]]; then + COMPREPLY=($(compgen -W "$(_lbtypes $cur)")) + fi +} # _rmlabel + +function _rmproject () { + local cur prev + local opts="\ +$_global_opts \ +$_comment_opts \ +-force \ +-template \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $cur == * ]]; then + COMPREPLY=($(compgen -W "$(_projects $cur)")) + fi +} # _rmproject + +function _rmregion () { + local cur prev + local opts="\ +$_global_opts \ +-password \ +-rmall \ +-tag \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -tag ]]; then + COMPREPLY=($(compgen -W "$(_regions $cur)" -- $cur)) + fi +} # _rmregion + +function _rmstgloc () { + local cur prev + local opts="\ +$_global_opts \ +-all \ +-region \ +-storage \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -region ]]; then + COMPREPLY=($(compgen -W "$(_regions $cur)" -- $cur)) + elif [[ $cur == * ]]; then + COMPREPLY=($(compgen -W "$(_stglocs $cur)")) + fi +} # _rmstgloc + +function _rmstream () { + local cur prev + local opts="\ +$_global_opts \ +$_comment_opts \ +-force \ +-template \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $cur == * ]]; then + COMPREPLY=($(compgen -W "$(_streams $cur)")) + fi +} # _rmstream + +function _rmtag () { + local cur prev + local opts="\ +$_global_opts \ +-all \ +-region \ +-view \ +-vob \ +-password \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -view ]]; then + COMPREPLY=($(compgen -W "$(lsviews)" -- $cur)) + elif [[ $prev == -vob ]]; then + COMPREPLY=($(compgen -W "$(_vobs $cur)")) + elif [[ $prev == -region ]]; then + COMPREPLY=($(compgen -W "$(_regions $cur)" -- $cur)) + fi +} # _rmtag + +function _rmtrigger () { + local cur prev + local opts="\ +$_global_opts \ +$_comment_opts \ +-nattach \ +-ninherit \ +-recurse \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $cur == * ]]; then + COMPREPLY=($(compgen -W "$(_trtypes $cur)")) + fi +} # _rmtrigger + +function _rmtype () { + local cur prev + local opts="\ +$_global_opts \ +$_comment_opts \ +-force \ +-ignore \ +-rmall \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $cur == * ]]; then + _type_selectors + fi +} # _rmtype + +function _rmview () { + local cur prev + local opts="\ +$_global_opts \ +$_comment_opts \ +-all \ +-avobs \ +-force \ +-tag \ +-vob \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -tag ]]; then + COMPREPLY=($(compgen -W "$(lsviews)" -- $cur)) + elif [[ $prev == -vob ]]; then + COMPREPLY=($(compgen -W "$(_vobs $cur)")) + fi +} # _rmview + +function _rmvob () { + local cur prev + local opts="\ +$_global_opts \ +-force \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $cur == * ]]; then + COMPREPLY=($(compgen -W "$(_vobs $cur)")) + fi +} # _rmvob + +function _setcs () { + local cur prev + local opts="\ +$_global_opts \ +-ctime \ +-current \ +-default \ +-force \ +-overwrite \ +-pname \ +-ptime \ +-rename \ +-stream \ +-tag \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $cur == -tag ]]; then + COMPREPLY=($(compgen -W "$(lsviews)" -- $cur)) + fi +} # _setcs + +function _setplevel () { + local cur prev + local opts="\ +$_global_opts \ +$_comment_opts \ +-default \ +-invob \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -invob ]]; then + COMPREPLY=($(compgen -W "$(_vobs $cur)")) + fi +} # _setplevel + +function _setview() { + local cur prev + local opts="\ +$_global_opts \ +-login \ +-exec \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* && $ARCH != cygwin ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $cur == * ]]; then + COMPREPLY=($(compgen -W "$(_views $cur)")) + fi +} # _setview + +function _space () { + local cur prev + local opts="\ +$_global_opts \ +-all \ +-directory \ +-generate \ +-host \ +-region \ +-scrub \ +-update \ +-view \ +-vob \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $prev == -view ]]; then + COMPREPLY=($(compgen -W "$(lsviews)" -- $cur)) + elif [[ $prev == -vob || $cur == * ]]; then + COMPREPLY=($(compgen -W "$(_vobs $cur)")) + fi +} # _space + +function _startview () { + local cur prev + local opts="\ +$_global_opts \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $cur == * ]]; then + COMPREPLY=($(compgen -W "$(lsviews)" -- $cur)) + fi +} # _startview + +function _umount () { + local cur prev + local opts="\ +$_global_opts \ +-all +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $cur == * ]]; then + COMPREPLY=($(compgen -W "$(_vobs $cur)")) + fi +} # _umount + +function _unlock () { + local cur prev + local opts="\ +$_global_opts \ +$_comment_opts \ +-pname \ +-version \ +" + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $cur == -* ]]; then + COMPREPLY=($(compgen -W "$opts" -- $cur)) + elif [[ $cur == * ]]; then + COMPREPLY=($(compgen -W "$(_object_selector $cur)" -- $cur)) + fi +} # _unlock + +function _scm () { + local cur prev + + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + cmd="${COMP_WORDS[0]}" + subcmd="${COMP_WORDS[1]}" + + if [[ $cmd == scm || $cmd == ct ]]; then + if [[ $COMP_CWORD == 1 ]]; then + COMPREPLY=($(compgen -W "$_scm_cmds" -- $cur)) + else + case "$subcmd" in + annotate) + COMPREPLY=($(compgen -W "$_annotate_opts" -- $cur));; + catcr) + COMPREPLY=($(compgen -W "$_catcr_opts" -- $cur));; + catcs) + _catcs;; + chactivity) + _chactivity;; + chbl) + COMPREPLY=($(compgen -W "$_chbl_opts" -- $cur));; + checkin|ci) + _checkin;; + checkout|co) + COMPREPLY=($(compgen -W "$_checkout_opts" -- $cur));; + checkvob) + _checkvob;; + chevent) + _chevent;; + chflevel) + COMPREPLY=($(compgen -W "$_chflevel_opts" -- $cur));; + chfolder) + _chfolder;; + chmaster) + _chmaster;; + chpool) + COMPREPLY=($(compgen -W "$_chpool_opts" -- $cur));; + chproject) + _chproject;; + chstream) + _chstream;; + chtype) + COMPREPLY=($(compgen -W "$_chtype_opts" -- $cur));; + chview) + _chview;; + deliver) + _deliver;; + describe|desc) + _describe;; + diff) + COMPREPLY=($(compgen -W "$_diff_opts" -- $cur));; + diffbl) + COMPREPLY=($(compgen -W "$_diffbl_opts" -- $cur));; + diffcr) + COMPREPLY=($(compgen -W "$_diffcr_opts" -- $cur));; + dospace) + _dospace;; + edcs) + _edcs;; + endview) + _endview;; + file) + _file;; + find) + COMPREPLY=($(compgen -W "$_find_opts" -- $cur));; + findmerge) + COMPREPLY=($(compgen -W "$_findmerge_opts" -- $cur));; + get) + COMPREPLY=($(compgen -W "$_get_opts" -- $cur));; + getcache) + COMPREPLY=($(compgen -W "$_getcache_opts" -- $cur));; + getlog) + _getlog;; + help) + COMPREPLY=($(compgen -W "$_scm_cmds" -- $cur));; + hostinfo) + _hostinfo;; + ln) + COMPREPLY=($(compgen -W "$_ln_opts" -- $cur));; + ls) + COMPREPLY=($(compgen -W "$_ls_opts" -- $cur));; + lsactivity) + _lsactivity;; + lsbl) + _lsbl;; + lscheckout|lsco) + COMPREPLY=($(compgen -W "$_lscheckout_opts" -- $cur));; + lsclients) + lsclients;; + lscomp) + _lscomp;; + lsdo) + COMPREPLY=($(compgen -W "$_lsdo_opts" -- $cur));; + lsfolder) + _lsfolder;; + lshistory) + COMPREPLY=($(compgen -W "$_lshistory_opts" -- $cur));; + lslock) + COMPREPLY=($(compgen -W "$_lslock_opts" -- $cur));; + lsmaster) + COMPREPLY=($(compgen -W "$_lsmaster_opts" -- $cur));; + lspool) + _lspool;; + lsprivate) + _lsprivate;; + lsproject) + _lsproject;; + lsregion) + _lsregion;; + lsreplica) + COMPREPLY=($(compgen -W "$_lsreplica_opts" -- $cur));; + lssite) + COMPREPLY=($(compgen -W "$_lssite_opts" -- $cur));; + lsstgloc) + _lsstgloc;; + lsstream) + _lsstream;; + lstype) + _lstype;; + lsview) + _lsview;; + lsvob) + _lsvob;; + lsvtree|vtree) + COMPREPLY=($(compgen -W "$_lsvtree_opts" -- $cur));; + man) + COMPREPLY=($(compgen -W "$_scm_cmds" -- $cur));; + merge) + COMPREPLY=($(compgen -W "$_merge_opts" -- $cur));; + mkactivity) + _mkactivity;; + mkattr) + COMPREPLY=($(compgen -W "$_mkattr_opts" -- $cur));; + mkattype) + COMPREPLY=($(compgen -W "$_mkattype_opts" -- $cur));; + mkbl) + _mkbl;; + mkbranch) + COMPREPLY=($(compgen -W "$_mkbranch_opts" -- $cur));; + mkbrtype) + COMPREPLY=($(compgen -W "$_mkbrtype_opts" -- $cur));; + mkcomp) + _mkcomp;; + mkdir) + COMPREPLY=($(compgen -W "$_mkdir_opts" -- $cur));; + mkelem) + COMPREPLY=($(compgen -W "$_mkelem_opts" -- $cur));; + mkeltype) + COMPREPLY=($(compgen -W "$_mkeltype_opts" -- $cur));; + mkfolder) + _mkfolder;; + mkhlink) + COMPREPLY=($(compgen -W "$_mkhlink_opts" -- $cur));; + mkhltype) + COMPREPLY=($(compgen -W "$_mkhltype_opts" -- $cur));; + mklabel) + COMPREPLY=($(compgen -W "$_mklabel_opts" -- $cur));; + mklbtype) + COMPREPLY=($(compgen -W "$_mklbtype_opts" -- $cur));; + mkpool) + COMPREPLY=($(compgen -W "$_mkpool_opts" -- $cur));; + mkproject) + _mkproject;; + mkregion) + _mkregion;; + mkstgloc) + _mkstgloc;; + mkstream) + _mkstream;; + mktag) + _mktag;; + mktrigger) + COMPREPLY=($(compgen -W "$_mktrigger_opts" -- $cur));; + mktrtype) + _mktrtype;; + mkview) + _mkview;; + mkvob) + _mkvob;; + mount) + _mount;; + move) + COMPREPLY=($(compgen -W "$_move_opts" -- $cur));; + protect) + COMPREPLY=($(compgen -W "$_protect_opts" -- $cur));; + protectvob) + COMPREPLY=($(compgen -W "$_protectvob_opts" -- $cur));; + pwd) + COMPREPLY=($(compgen -W "$_pwd_opts" -- $cur));; + pwv) + COMPREPLY=($(compgen -W "$_pwv_opts" -- $cur));; + rebase) + _rebase;; + recoverview) + _recoverview;; + reformatview) + _reformatview;; + reformatvob) + _reformatvob;; + register) + _register;; + relocate) + COMPREPLY=($(compgen -W "$_relocate_opts" -- $cur));; + rename) + COMPREPLY=($(compgen -W "$_rename_opts" -- $cur));; + reqmaster) + COMPREPLY=($(compgen -W "$_reqmaster_opts" -- $cur));; + reserve) + COMPREPLY=($(compgen -W "$_reserve_opts" -- $cur));; + rmactivity) + _rmactivity;; + rmattr) + _rmattr;; + rmbl) + _rmbl;; + rmbranch) + COMPREPLY=($(compgen -W "$_rmbranch_opts" -- $cur));; + rmcomp) + _rmcomp;; + rmdo) + COMPREPLY=($(compgen -W "$_rmdo_opts" -- $cur));; + rmelem) + COMPREPLY=($(compgen -W "$_rmelem_opts" -- $cur));; + rmfolder) + _rmfolder;; + rmhlink) + _rmhlink;; + rmlabel) + _rmlabel;; + rmmerge) + COMPREPLY=($(compgen -W "$_rmmerge_opts" -- $cur));; + rmname) + COMPREPLY=($(compgen -W "$_rmname_opts" -- $cur));; + rmpool) + COMPREPLY=($(compgen -W "$_rmpool_opts" -- $cur));; + rmproject) + _rmproject;; + rmregion) + _rmregion;; + rmstgloc) + _rmstgloc;; + rmstream) + _rmstream;; + rmtag) + _rmtag;; + rmtrigger) + _rmtrigger;; + rmtype) + _rmtype;; + rmver) + COMPREPLY=($(compgen -W "$_rmver_opts" -- $cur));; + rmview) + _rmview;; + rmvob) + _rmvob;; + schedule) + COMPREPLY=($(compgen -W "$_schedule_opts" -- $cur));; + setcache) + COMPREPLY=($(compgen -W "$_setcache_opts" -- $cur));; + setcs) + _setcs;; + setview) + _setview;; + setplevel) + _setplevel;; + setsite) + COMPREPLY=($(compgen -W "$_setsite_opts" -- $cur));; + space) + _space;; + startview) + _startview;; + umount) + _umount;; + uncheckout|unco) + COMPREPLY=($(compgen -W "$_uncheckout_opts" -- $cur));; + unlock) + _unlock;; + unregister) + COMPREPLY=($(compgen -W "$_unregister_opts" -- $cur));; + unreserve) + COMPREPLY=($(compgen -W "$_unreserve_opts" -- $cur));; + update) + COMPREPLY=($(compgen -W "$_update_opts" -- $cur));; + winkin) + COMPREPLY=($(compgen -W "$_winkin_opts" -- $cur));; + esac + fi + fi +} # _scm + +# TODO: These functions aren't working very well yet. +function _type_selector () { + local cur prev prev2 + + cur="${COMP_WORDS[COMP_CWORD]}" + + if (($COMP_CWORD - 1 >= 0)); then + prev="${COMP_WORDS[COMP_CWORD-1]}" + fi + + if (($COMP_CWORD - 2 >= 0)); then + prev2="${COMP_WORDS[COMP_CWORD-2]}" + fi + + if [[ $prev == : ]]; then + COMPREPLY=($(compgen -W "$(ct lstype -kind $prev2 -short -invob $dvob | grep ^$cur)" -- $cur)) + elif [[ $cur == : ]]; then + COMPREPLY=($(compgen -W "$(ct lstype -kind $prev -short -invob $dvob)")) + elif [[ $cur == * ]]; then + COMPREPLY=($(compgen -W "$_type_selectors" -- $cur)) + fi +} # _type_selector + +function _object_selector () { + local cur prev prev2 + + COMPREPLY=() + + cur="${COMP_WORDS[COMP_CWORD]}" + + if (($COMP_CWORD - 1 >= 0)); then + prev="${COMP_WORDS[COMP_CWORD-1]}" + fi + + if (($COMP_CWORD - 2 >= 0)); then + prev2="${COMP_WORDS[COMP_CWORD-2]}" + fi + + if [[ $prev == activity || ($prev == : && $prev2 == activity) ]]; then + if [[ $cur == : ]]; then + COMPREPLY=($(compgen -W "$(_activities)" -- "")) + else + echo "cur = $cur" + COMPREPLY=($(compgen -W "$(_activities $cur)")) + echo "COMPREPLY = ${COMPREPLY[*]}" + fi + elif [[ $prev == lbtype || ($prev == : && $prev2 == lbtype) ]]; then + if [[ $cur == : ]]; then + COMPREPLY=($(compgen -W "$(_lbtypes)" -- "")) + echo "COMPREPLY = ${COMPREPLY[*]}" + else + echo "cur = $cur" + COMPREPLY=($(compgen -W "$(_lbtypes $cur)")) + echo "COMPREPLY = ${COMPREPLY[*]}" + fi + elif [[ $cur == * ]] ; then + COMPREPLY=($(compgen -W "$_object_selectors" -- $cur)) + fi +} # _object_selector + +complete -o default -F _scm scm ct + +complete -F _catcs catcs +complete -F _checkin ci +complete -F _deliver deliver +complete -F _endview endview +complete -F _lsactivity lsact +complete -F _lsbl lsbl +complete -F _lsproject lsproj +complete -F _lsfolder lsfolder llfolder +complete -F _lsstgloc lsstgloc +complete -F _lsstream lsstream llstream +complete -F _lsview lsview llview +complete -F _lsvob lsvob llvob +complete -F _merge merge +complete -F _mktag mktag +complete -F _mkview mkview +complete -F _rebase rebase +complete -F _rmtag rmtag +complete -F _rmview rmview +complete -F _setactivity setact +complete -F _setcs setcs +complete -F _setview setview +complete -F _startview startview +complete -F _space space +complete -F _register register +complete -F _uncheckout unco +complete -F _unregister unregister + +complete -F _object_selector -o nospace lstype +complete -F _object_selector -o nospace lltype +complete -F _object_selector -o nospace lslock +complete -F _object_selector -o nospace lllock diff --git a/rc/clearcase.conf b/rc/clearcase.conf new file mode 100644 index 0000000..aafc4cc --- /dev/null +++ b/rc/clearcase.conf @@ -0,0 +1,21 @@ +# Clearcase configuration: This file is sourced by ~/.rc/clearcase so you +# can set some variables if you like to represent site defaults + +# The vobtag prefix in use at this site +if [ $ARCH = 'cygwin' ]; then + export VOBTAG_PREFIX=\\ +else + export VOBTAG_PREFIX=/vob/ +fi + +# While for most commands we use $VOBTAG_PREFIX even when in Cygwin because +# from a command line we are often calling cleartool (through the scm function) +# We do want the setview function to mount vobs in the Linux way so we export +# this variable for that purpose. +export LINUX_VOBTAG_PREFIX=/vob + +# The default pvob +export pvob=${VOBTAG_PREFIX}9200_projects + +# The default vob +export dvob="${VOBTAG_PREFIX}9200" \ No newline at end of file diff --git a/rc/clearcase_profile b/rc/clearcase_profile new file mode 100644 index 0000000..c318103 --- /dev/null +++ b/rc/clearcase_profile @@ -0,0 +1,17 @@ +################################################################################ +# +# File: $RCSfile: clearcase_profile,v $ +# Revision: $Revision: 1.2 $ +# Description: Clearcase profile +# Author: Andrew@ClearSCM.com +# Created: Fri Jun 27 17:45:37 MST 2008 +# Modified: $Date: 2010/04/27 03:27:21 $ +# Language: .clearcase_profile +# +# (c) Copywrite 2000-2009, Andrew@ClearSCM.com, all rights reserved. +# +################################################################################ +checkout -nc +checkin -nc +mkelem -nc +unco -rm diff --git a/rc/client_scripts/Broadcom b/rc/client_scripts/Broadcom new file mode 100644 index 0000000..2326591 --- /dev/null +++ b/rc/client_scripts/Broadcom @@ -0,0 +1,33 @@ +#!/bin/bash +################################################################################ +# +# File: $RCSfile: Broadcom,v $ +# Revision: $Revision: 1.1 $ +# Description: Client specific start up for Broadcom +# Author: Andrew@DeFaria.com +# Created: Wed Jan 18 14:09:31 PST 2012 +# Modified: $Date: 2013/03/26 20:52:09 $ +# Language: bash +# +# (c) Copyright 2012, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +append_to_path /projects/mob_tools/scripts +append_to_path /projects/mob_tools/bin +append_to_path /tools/ecloud/commander/bin +append_to_path /usr/brcm/ba/bin +append_to_path /projects/mob_tools/scripts +append_to_path /tools/bin +append_to_path /opt/Perforce + +if [ -f "/cygdrive/c/Program Files/Perforce/p4.exe" ]; then + append_to_path "/cygdrive/c/Program Files/Perforce" +elif [ -f "/cygdrive/c/Program Files (x86)/Perforce/p4.exe" ]; then + append_to_path "/cygdrive/c/Program Files (x86)/Perforce" +fi + +alias baperl=/usr/brcm/ba/bin/perl + +export CDPATH=$CDPATH:/mcsi:/mob_tools + +alias repo=/projects/mob_tools/bin/repo diff --git a/rc/client_scripts/GD b/rc/client_scripts/GD new file mode 100644 index 0000000..fb2993c --- /dev/null +++ b/rc/client_scripts/GD @@ -0,0 +1,54 @@ +#!/bin/bash +################################################################################ +# +# File: $RCSfile: GD,v $ +# Revision: $Revision: 1.1 $ +# Description: Client specific start up for General Dynamics +# Author: Andrew@DeFaria.com +# Created: Mon Aug 20 17:35:01 2001 +# Modified: $Date: 2010/04/09 05:36:46 $ +# Language: bash +# +# (c) Copyright 2000-2005, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +umask 002 + +export SITE_PERLLIB=/cleartrig/ent/SNSD/muos/ccadm_tools/vobs/ranccadm/scripts/lib +export PATH=/cleartrig/ent/SNSD/muos/ccadm_tools/vobs/ranccadm/scripts/clearcase:$PATH + +export http_proxy=webgate0.gddsi.com:8080 +export ftp_proxy=webgate0.gddsi.com + +export QTDIR="/usr/local/Trolltech/Qt-4.2.2" +export QMAKESPEC="$QTDIR/mkspecs/solaris-cc" +export ORACLE="SID rancq" +export ORACLE_HOME="/usr/local/oracle/product/9.2" + +export CCASE_MAKE_COMPAT=gnu + +export CQ_HOME=/opt/rational/clearquest +export CQ_HELP_BROWSER=firefox +export CQ_PERLLIB=/opt/rational/common/lib/perl5/5.6.1/sun4-solaris-multi:/opt/rational/common/lib/perl5/5.6.1:/opt/rational/common/lib/perl5/site_perl/5.6.1/sun4-solaris-multi:/opt/rational/common/lib/perl5/site_perl/5.6.1:/opt/rational/common/lib/perl5/site_perl + +export TZ="US/Arizona" + +alias xv=/prj/Synopsis/gccsparcOS5/ccss/utils/xv/xv + +export RSU_LICENSE_MAP="/prj/muosran/config/Rational_License_Map" + +export LM_LICENSE_FILE="flex2:1850@flex2:15280@ranadm2:19353@ranadm2:19355@ranadm2:2468@ranadm2:1717@flex2:1711@bartlett:1711@flex3:27000@ranadm2:28000@ranadm2:5270@flex2" + +alias xemacs="ssh muosbldforge2 xemacs" + +export EDITOR="ssh muosbldforge2 xemacs" + +if [ $(uname) = "SunOS" ]; then + export QTDIR=/usr/local/Trolltech/Qt-4.2.2 + export ORACLE_HOME="/usr/local/oracle/product/9.2" + export CQ_HOME=/opt/rational/clearquest/ +elif [ $(uname) = "Linux" ]; then + export QTDIR=/usr/local/Trolltech/Qt-4.2.3 + export ORACLE_HOME="/usr/local/oracle/product/10.2.0" + export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/X11R6/lib +fi \ No newline at end of file diff --git a/rc/client_scripts/GE b/rc/client_scripts/GE new file mode 100644 index 0000000..1250906 --- /dev/null +++ b/rc/client_scripts/GE @@ -0,0 +1,15 @@ +#!/bin/bash +################################################################################ +# +# File: $RCSfile: GE,v $ +# Revision: $Revision: 1.1 $ +# Description: Client specific start up for General Electric +# Author: Andrew@DeFaria.com +# Created: Mon Aug 20 17:35:01 2001 +# Modified: $Date: 2010/05/31 21:20:53 $ +# Language: bash +# +# (c) Copyright 2010, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +export CDPATH="$CDPATH:/vobs/ce_gdn_src:/vobs/scm_tools_src" diff --git a/rc/dircolors b/rc/dircolors new file mode 100644 index 0000000..96f4800 --- /dev/null +++ b/rc/dircolors @@ -0,0 +1,107 @@ +# Configuration file for dircolors, a utility to help you set the +# LS_COLORS environment variable used by GNU ls with the --color option. + +# The keywords COLOR, OPTIONS, and EIGHTBIT (honored by the +# slackware version of dircolors) are recognized but ignored. + +# Below, there should be one TERM entry for each termtype that is colorizable +TERM linux +TERM linux-c +TERM mach-color +TERM console +TERM con132x25 +TERM con132x30 +TERM con132x43 +TERM con132x60 +TERM con80x25 +TERM con80x28 +TERM con80x30 +TERM con80x43 +TERM con80x50 +TERM con80x60 +TERM dtterm +TERM xterm +TERM xterm-color +TERM xterm-debian +TERM rxvt +TERM screen +TERM screen-w +TERM vt100 +TERM Eterm + +# Below are the color init strings for the basic file types. A color init +# string consists of one or more of the following numeric codes: +# Attribute codes: +# 00=none 01=bold 04=underscore 05=blink 07=reverse 08=concealed +# Text color codes: +# 30=black 31=red 32=green 33=yellow 34=blue 35=magenta 36=cyan 37=white +# Background color codes: +# 40=black 41=red 42=green 43=yellow 44=blue 45=magenta 46=cyan 47=white +NORMAL 00 # global default, although everything should be something. +FILE 00 # normal file +DIR 01;33 # directory +LINK 01;36 # symbolic link. (If you set this to 'target' instead of a + # numerical value, the color is as for the file pointed to.) +FIFO 40;33 # pipe +SOCK 01;35 # socket +DOOR 01;35 # door +BLK 40;33;01 # block device driver +CHR 40;33;01 # character device driver +ORPHAN 40;31;01 # symlink to nonexistent file + +# This is for files with execute permission: +EXEC 01;32 + +# List any file extensions like '.gz' or '.tar' that you would like ls +# to colorize below. Put the extension, a space, and the color init string. +# (and any comments you want to add after a '#') + +# If you use DOS-style suffixes, you may want to uncomment the following: +#.cmd 01;32 # executables (bright green) +#.exe 01;32 +#.com 01;32 +#.btm 01;32 +#.bat 01;32 + +.tar 01;31 # archives or compressed (bright red) +.tgz 01;31 +.arj 01;31 +.taz 01;31 +.lzh 01;31 +.zip 01;31 +.z 01;31 +.Z 01;31 +.gz 01;31 +.bz2 01;31 +.deb 01;31 +.rpm 01;31 +.jar 01;31 + +# image formats +.jpg 01;35 +.jpeg 01;35 +.gif 01;35 +.bmp 01;35 +.pbm 01;35 +.pgm 01;35 +.ppm 01;35 +.tga 01;35 +.xbm 01;35 +.xpm 01;35 +.tif 01;35 +.tiff 01;35 +.png 01;35 +.mov 01;35 +.mpg 01;35 +.mpeg 01;35 +.avi 01;35 +.fli 01;35 +.gl 01;35 +.dl 01;35 +.xcf 01;35 +.xwd 01;35 + +# audio formats +.ogg 01;35 +.mp3 01;35 +.wav 01;35 diff --git a/rc/functions b/rc/functions new file mode 100644 index 0000000..fd373a2 --- /dev/null +++ b/rc/functions @@ -0,0 +1,252 @@ +#!/bin/bash +############################################################################### +# +# File: $RCSfile: functions,v $ +# Revision: $Revision: 1.20 $ +# Description: Common bash functions +# Author: Andrew@DeFaria.com +# Created: Thu Jun 6 08:31:57 PDT 1996 +# Modified: $Date: 2013/03/26 20:38:23 $ +# Language: bash +# +# (c) Copyright 2000-2005, Andrew@DeFaria.com, all rights reserved. +# +############################################################################### +ESC=$(echo "\033") +CR=$(echo "\015") + +view_name= + +# Function to set the title bar. Works on the terminal emulators listed. +function title_bar { + prefix="$@" + # Change $HOME -> ~ + if [ "${PWD#$HOME*}" != "$PWD" ]; then + current_dir="~${PWD#$HOME*}" + elif [ "$PWD" = "$HOME" ]; then + current_dir=~ + else + current_dir="$PWD" + fi + + # Remove view name + current_dir="${current_dir#/view/$view_name*}" + current_dir="${current_dir#/sview/$view_name*}" + + # Add CVS/Root if there is one + if [ -f "CVS/Root" ]; then + current_dir="$current_dir ($(cat CVS/Root | tr -d $CR))" + fi + + if [ "$TERM" = "hpterm" -o \ + "$TERM" = "hp" -o \ + "$TERM" = "2392A" ]; then + string=$(echo "${SYSNAME##*:}:$@") + echo -n "${ESC}&f0k${#string}D$string" + elif [ "$TERM" = "dtterm" -o \ + "$TERM" = "vt221" ]; then + string=$(echo "${SYSNAME##*:}:$@") + echo -n "${ESC}]2;$string\007" + elif [ "$TERM" = "cygwin" -o "$TERM" = "vt100" -o "$TERM" = "xterm" ]; then + PS1="\[\e]0;$prefix$current_dir\a\e[01;33m\]$SYSNAME:\[\e[0m\]" + fi +} # title_bar + +# Function to set the icon name. Works on the terminal emulators listed. +function icon_name { + if [ "$TERM" = "hpterm" -o \ + "$TERM" = "hp" -o \ + "$TERM" = "2392A" ]; then + string=$(echo "$1") + echo -n "${ESC}&f-1k${#string}D$string" + elif [ "$TERM" = "dtterm" -o \ + "$TERM" = "vt100" -a "$DTTERM" = "True" ]; then + # Note setting icon_name on vt100 overwrites the title bar so skip it + echo -n "${ESC}]1;$@\007" + fi +} # icon_name + +# Sets both the title bar and the icon name. +function title { + title_bar "$@" + icon_name "${SYSNAME##*:}" +} # title + +# Sets title bar to machine name and path. Will include a view name if in a +# view and a string to indicate that you are root. +function set_title { + if [ $($id -u) -eq 0 ]; then + ROOT="Wizard " + else + ROOT= + fi + + view_name=$(scm pwv -short 2> /dev/null); + + if [ $? -ne 0 -o -z "$view_name" ]; then + view_name='*NONE*' + fi + + if [[ $view_name = *NONE* ]]; then + view_name="" + title_bar "$ROOT" + else + title_bar "${ROOT}View: $view_name: " + fi + + icon_name "${SYSNAME##*:}" +} # set_title + +# Sets prompt on terminals listed. +function set_prompt { + if [ $($id -u) -eq 0 ]; then + if [ "$TERM" = "hpterm" -o \ + "$TERM" = "hp" -o \ + "$TERM" = "2392A" -o \ + "$TERM" = "dtterm" -o \ + ! -z "$DTTERM" ]; then + ROOT="${RED}Wizard$NORMAL " + elif [ "$TERM" = "vt100" -o \ + "$TERM" = "xterm" -o \ + "$TERM" = "vt220" ]; then + ROOT="${BOLD}${BLINK}Wizard$NORMAL " + fi + else + ROOT="" + fi + + if [ "$TERM" = "vt100" -o \ + "$TERM" = "xterm" -o \ + "$TERM" = "vt220" ]; then + PS1="$ROOT$BOLD$SYSNAME:$NORMAL" + else + PS1="$ROOT$SYSNAME:" + fi + + set_title +} # set_prompt + +# Function to override the normal cd command, setting title and prompt. +function mycd { + if [ -z "$1" ]; then + \cd ~ + else + \cd "$1" + fi + set_title + set_prompt +} # mycd +export mycd + +# Functions to override the normal push/popd commands, setting title and prompt. +function mypushd { + if [ -z "$1" ]; then + \pushd > /dev/null + else + \pushd "$1" > /dev/null + fi + set_title + set_prompt +} # mypushd + +function mypopd { + if [ -z "$1" ]; then + cd - > /dev/null + else + \popd "$1" > /dev/null + fi + set_title + set_prompt +} # mypopd + +# Function to override rlogin. Note that it fixes up the title and prompt +# upon return. +function rlogin { + /usr/bin/rlogin "$@" + set_title + set_prompt +} # rlogin + +# Function to override rsh. Note that it fixes up the title and prompt +# upon return. +function rsh { + /usr/bin/rsh "$@" + set_title + set_prompt +} # rsh + +# Function to override ssh. Note that it fixes up the title and prompt +# upon return. +function ssh { + /usr/bin/ssh "$@" + set_title + set_prompt +} # ssh + +function sj { + if [ $ARCH = "FreeBSD" ]; then + psopts="-aux" + else + psopts="-ef" + fi + + if [ $# = 0 ]; then + ps $psopts | $PAGER + else + for str; do + ps $psopts | grep "$str" | grep -v "grep $str" | grep -v "grep -d skip" + done + fi +} # sj + +function start_imap { + # Starts an ssh tunnel for IMAP + ssh -C -L 143:defaria.com:143 andrew@defaria.com +} # start_imap + +function cmdline { + # Displays the command line from the /proc filesystem (if present) + + me=$0; + + if [ $# -ne 1 ]; then + error "Usage: cmdline " + return 1 + fi + + pid=$1; + + if [ ! -d "/proc" ]; then + error "This OS has no /proc filesystem" + return 1 + fi + + if [ ! -d "/proc/$pid" ]; then + error "PID $pid does not exist" + return 1 + fi + + if [ ! -f "/proc/$pid/cmdline" ]; then + error "PID $pid has no cmdline!" + return 1 + fi + + cat /proc/$pid/cmdline | tr -c [:print:] " " + display +} # cmdline + +function user { + if [ $# -gt 0 ]; then + ypcat passwd | grep -i $@ + else + ypcat passwd | $PAGER + fi +} # user + +function group { + if [ $# -gt 0 ]; then + ypcat group | grep -i $@ + else + ypcat group | $PAGER + fi +} # group diff --git a/rc/inputrc b/rc/inputrc new file mode 100644 index 0000000..d94c326 --- /dev/null +++ b/rc/inputrc @@ -0,0 +1,56 @@ +# .inputrc is used by GNU's readline routine +# +# This tells filename completion to be case insensitive +set completion-ignore-case on + +# Home +"\e[7~": beginning-of-line +"\e[h~": beginning-of-line + +# End +"\e[8~": end-of-line + +# Delete +"\e[3~": delete-char + +# Insert +"\e[2~": paste-from-clipboard + +# Control left +"\eOd": backward-word +"\e[1;5D": backward-word + +# Control right +"\eOc": forward-word +"\e[1;5C": forward-word + +# Function keys (From Randhuall R Schulz ) +# F1 - F5 (Console) +"\M-[[A" "F1" +"\M-[[B" "F2" +"\M-[[C" "2>&1 &" +"\M-[[D" "F4" +"\M-[[E" "F5" + +# F1 - F3 (RXVT) +"\M-[11~" "F1" +"\M-[12~" "F2" +"\M-[13~" "2>&1 &" +"\M-[14~" "F4" +"\M-[15~" "F5" + +# Both Console and RXVT +"\M-[17~" "F6" +"\M-[18~" "F7" +"\M-[19~" "exit\C-M" +"\M-[20~" "F9" +"\M-[21~" "F10" +"\M-[23~" "F11" +"\M-[24~" "F12" + +# Horizontal scroll... +#set horizontal-scroll-mode on + +# The following tells bash to show all filenames if ambiguous instead of +# beeping first +#set show-all-if-ambiguous on diff --git a/rc/logout b/rc/logout new file mode 100644 index 0000000..413f429 --- /dev/null +++ b/rc/logout @@ -0,0 +1,17 @@ +#/usr/bin/env bash +################################################################################ +# +# File: $RCSfile: logout,v $ +# Revision: $Revision: 1.4 $ +# Description: Script to run at logout +# Author: Andrew@DeFaria.com +# Created: Thu Jun 6 08:31:57 PDT 1996 +# Modified: $Date: 2006/07/24 05:37:40 $ +# Language: bash +# +# (c) Copyright 2000-2005, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +if [ -x "$(type -p fortune)" ]; then + fortune -sw +fi diff --git a/rc/multisite b/rc/multisite new file mode 100644 index 0000000..2c9a8c2 --- /dev/null +++ b/rc/multisite @@ -0,0 +1,87 @@ +#!/bin/bash +################################################################################ +# +# File: $RCSfile: multisite,v $ +# Revision: $Revision: 1.6 $ +# Description: This script set up some useful environment variables and aliases +# for MultiSite execution. File should be sourced (e.g . +# multisite) +# Author: Andrew@DeFaria.com +# Created: Wed Jun 5 21:08:03 PDT 1996 +# Modified: $Date: 2011/03/07 22:11:23 $ +# Language: bash +# +# (c) Copyright 2000-2005, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +if [ $ARCH = "cygwin" ]; then + export MSHOME=$(cygpath -u "$(regtool get '/machine/SOFTWARE/Rational Software/RSINSTALLDIR' 2>/dev/null)" 2>/dev/null) +else + export MSHOME=/opt/rational/clearcase/ +fi + +if [ ! -d "$MSHOME" ]; then + unset MSHOME + return +fi + +export MULTITOOL="${MSHOME}bin/multitool" + +if [ -x "$MULTITOOL" ]; then + export CLEARCASE_BLD_HOST_TYPE=Windows + export SLOGS=$LOGS/sync_logs + + if [ $ARCH = "cygwin" ]; then + export SB="${MSHOME}var/shipping/ms_ship" + else + export SB="/var/adm/rational/clearcase/shipping/ms_ship" + fi +fi + +function mt { + if [ -x "$MULTITOOL" ]; then + "$MULTITOOL" "$@" + else + echo "MultiSite is not installed on this system!" + fi +} # mt + +function lspacket { + "$MULTITOOL" lspacket "$@" +} # lspacket + +function llpacket { + "$MULTITOOL" lspacket -long "$@" +} # llpacket + +function lsreplica { + "$MULTITOOL" lsreplica -invob "$@" +} # lsreplica + +function llreplica { + "$MULTITOOL" lsreplica -long -invob "$@" +} # llreplica + +function lsepoch { + "$MULTITOOL" lsepoch -invob "$@" +} # lsepoch + +function llepoch { + "$MULTITOOL" lsepoch -long -invob "$@" +} # llepoch + +function chepoch { + "$MULTITOOL" chepoch -invob "$@" +} # chepoch + +function shipping_server { + $MSHOME/etc/shipping_server "$@" +} # shipping_server + +function mkorder { + $MSHOME/etc/mkorder "$@" +} # mkorder + +function syncreplica { + "$MULTITOOL" syncreplica "$@" +} # syncreplica diff --git a/rc/perlcriticrc b/rc/perlcriticrc new file mode 100644 index 0000000..75fe45b --- /dev/null +++ b/rc/perlcriticrc @@ -0,0 +1,52 @@ +################################################################################ +# +# File: $RCSfile: perlcriticrc,v $ +# Revision: $Revision: 1.3 $ +# Description: Perlcritic defaults +# Author: Andrew@DeFaria.com +# Created: Fri Jan 23 11:08:55 MST 2009 +# Modified: $Date: 2011/01/09 01:04:56 $ +# Language: perltidy +# +# (c) Copyright 2000-2009, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +severity = harsh + +# We think these are really important, so always load them +[TestingAndDebugging::RequireUseStrict] +severity = 5 + +[TestingAndDebugging::RequireUseWarnings] +severity = 5 + +# We like function prototypes +[-Subroutines::ProhibitSubroutinePrototypes] + +# Not every regex needs to be fully explained +[RegularExpressions::RequireExtendedFormatting] +minimum_regex_length_to_complain_about = 20 + +# Backticks only in non void contexts +[InputOutput::ProhibitBacktickOperators] +only_in_void_context = 1 + +# Reading from STDIN should be OK +[-InputOutput::ProhibitExplicitStdin] + +# Cascading elsif's are not that difficult to understand. Switch is not +# that much easier. And switch is not available without a CPAN module install +# which is not always available +[ControlStructures::ProhibitCascadingIfElse] +max_elsif = 99 + +# In multipocess situations you don't want to localize %SIG or you can get +# defunct children. +[Variables::RequireLocalizedPunctuationVars] +allow = %SIG + +# Actually I find reading regex's is not that hard. Perl programmers should be +# able to do it. Besides it's not the character count that makes a regex +# complicated - it's more it's complication than the number of characters. +[RegularExpressions::RequireExtendedFormatting] +minimum_regex_length_to_complain_about = 50 \ No newline at end of file diff --git a/rc/perldb b/rc/perldb new file mode 100644 index 0000000..54c75b2 --- /dev/null +++ b/rc/perldb @@ -0,0 +1,2 @@ +parse_options ('windowSize=20'); +parse_options ('HistFile=.perldb.hist'); diff --git a/rc/perltidyrc b/rc/perltidyrc new file mode 100644 index 0000000..be3ca71 --- /dev/null +++ b/rc/perltidyrc @@ -0,0 +1,28 @@ +################################################################################ +# +# File: $RCSfile: perltidyrc,v $ +# Revision: $Revision: 1.1 $ +# Description: Perltidy defaults +# Author: Andrew@DeFaria.com +# Created: Fri Jan 23 11:08:55 MST 2009 +# Modified: $Date: 2010/04/09 05:40:01 $ +# Language: perltidy +# +# (c) Copyright 2000-2009, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +--indent-columns=2 +--output-line-ending=unix +--noline-up-parentheses +--nooutdent-labels +--paren-tightness=2 +--square-bracket-tightness=2 +--brace-tightness=2 +--block-brace-tightness=2 +--nospace-for-semicolon +--space-keyword-paren +--space-function-paren +--closing-side-comments +--cuddled-else +--opening-token-right +--stack-opening-tokens diff --git a/rc/set_colors b/rc/set_colors new file mode 100644 index 0000000..28c7a69 --- /dev/null +++ b/rc/set_colors @@ -0,0 +1,66 @@ +#!/bin/bash +################################################################################ +# +# File: $RCSfile: set_colors,v $ +# Revision: $Revision: 1.3 $ +# Description: Set color variables +# Author: Andrew@DeFaria.com +# Created: Thu Jun 6 08:31:57 PDT 1996 +# Modified: $Date: 2010/04/12 15:57:33 $ +# Language: bash +# +# (c) Copyright 2000-2005, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +export esc=$(echo "\033") + +if [ "$TERM" = "vt100" -o \ + "$TERM" = "vt220" ]; then + export NORMAL="$esc[0m" + export BOLD="$esc[1m" + export UNDERLINE="$esc[4m" + export BLINK="$esc[5m" + export INVERSE="$esc[7m" +elif [ "$TERM" = "dtterm" -o "$TERM" = "vt100" -o "$TERM" = "xterm" -o -z DTTERM ]; then + export NORMAL="$esc[39m" + export RED="$esc[31m" + export B_RED=$RED + export GREEN="$esc[32m" + export B_GREEN=$GREEN + export YELLOW="$esc[33m" + export B_YELLOW=$YELLOW + export BLUE="$esc[34m" + export B_BLUE=$BLUE + export MAGENTA="$esc[35m" + export B_MAGENTA=$MAGENTA + export AQUA="$esc[36m" + export B_AQUA=$AQUA + export WHITE="$esc[36m" + export B_WHITE=$WHITE +elif [ "$TERM" = "hp" -o "$TERM" = "hpterm" ]; then + export NORMAL="$esc&d@$esc&v0S" + export RED="$esc&v1S" + export GREEN="$esc&v2S" + export YELLOW="$esc&v3S" + export BLUE="$esc&v4S" + export PURPLE="$esc&v5S" + export AQUA="$esc&v6S" + export HB_NORMAL="$esc&v0S$esc&dK" + export B_NORMAL="$esc&v0S$esc&dB" + export HB_RED="$esc&v1S$esc&dK" + export B_RED="$esc&v1S$esc&dB" + export HB_GREEN="$esc&v2S$esc&dK" + export B_GREEN="$esc&v2S$esc&dB" + export HB_YELLOW="$esc&v3S$esc&dK" + export B_YELLOW="$esc&v3S$esc&dB" + export HB_BLUE="$esc&v4S$esc&dK" + export B_BLUE="$esc&v4S$esc&dB" + export PURPLE="$esc&v5S" + export HB_PURPLE="$esc&v5S$esc&dK" + export B_PURPLE="$esc&v5S$esc&dB" + export HB_AQUA="$esc&v6S$esc&dK" + export B_AQUA="$esc&v6S$esc&dB" + export INVERSE="$esc&v7S" + export HB_INVERSE="$esc&v7S$esc&dK" + export B_INVERSE="$esc&v7S$esc&dB" +fi diff --git a/rc/set_path b/rc/set_path new file mode 100644 index 0000000..8eaf9da --- /dev/null +++ b/rc/set_path @@ -0,0 +1,137 @@ +#!/bin/bash +################################################################################ +# +# File: $RCSfile: set_path,v $ +# Revision: $Revision: 1.8 $ +# Description: Sets the path from scratch +# Author: Andrew@DeFaria.com +# Created: Thu Jun 6 08:31:57 PDT 1996 +# Modified: $Date: 2012/09/20 18:10:28 $ +# Language: bash +# +# (c) Copyright 2000-2005, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +function append_to_path { + component="$1" + + if [ -d "$component" ]; then + if [ -z "$PATH" ]; then + PATH="$component" + else + PATH="$PATH:$component" + fi + fi +} # append_to_path + +function append_to_manpath { + component="$1" + + if [ -d "$component" ]; then + if [ -z "$MANPATH" ]; then + MANPATH="$component" + else + MANPATH="$MANPATH:$component" + fi + fi +} # append_to_manpath + +if [ -x /app/manpath ]; then + OLDIFS=$IFS + IFS=: + for manpath in $(/app/manpath); do + manpath_dirs="$manpath_dirs $manpath" + done + IFS=$OLDIFS +fi + +# Set up PATH +path_dirs= + +if [ -f /etc/PATH ]; then + OLDIFS=$IFS + IFS=: + for path in $(cat /etc/PATH); do + path_dirs="$path_dirs $path" + done + IFS=$OLDIFS +fi + +if [ "$SYSTEMROOT" ]; then + systemroot=$(cygpath -u $SYSTEMROOT) +fi + +path_dirs="$path_dirs\ + .\ + "$HOME/bin"\ + $adm_base/bin\ + $adm_base/cc\ + $adm_base/cq\ + $adm_base/cvsbin\ + /opt/Rational/Clearcase/bin\ + /opt/Rational/ClearQuest\ + /opt/Rational/Common\ + /bin\ + /sbin\ + /usr/local/mysql/bin\ + /usr/local/maps/bin\ + /usr/afsws/bin\ + /usr/afsws\ + /usr/bin\ + /usr/X11R6/bin\ + /usr/bin/X11\ + /usr/local/ddts/bin\ + /usr/local/bin\ + /usr/dt/bin\ + /usr/openwin/bin\ + /opt/rational/clearcase/bin\ + /opt/ibm/rationalsdlc/clearcase/bin\ + /opt/ibm/rationalsdlc/clearcase/etc\ + /opt/ibm/rationalsdlc/clearquest/bin\ + /opt/ibm/rationalsdlc/clearquest\ + /opt/ibm/rationalsdlc/common\ + /usr/sbin\ + /usr/ccs/bin\ + /usr/seos/bin\ + /usr/ucb\ + /opt/ssh/bin\ + /tools/bin\ + $systemroot/System32\ + $systemroot\ +" + +manpath_dirs="\ + /usr/share/man\ + /usr/dt/man\ + /usr/dt/man/man1\ + /usr/cns/man\ + /usr/local/packages/ccperl/ccperl5.001m/man\ + /usr/local/packages/atria/current/man\ + /usr/local/packages/emacs/man\ + /usr/seos/man\ + /opt/ssh/man\ + /opt/medusa/share/man\ + /usr/afsws/man\ +" + +PATH= +for component in $path_dirs; do + append_to_path "$component" +done + +# Set up MANPATH +if [ -f /etc/MANPATH ]; then + MANPATH=$(cat /etc/MANPATH) +fi + +for component in $manpath_dirs; do + append_to_manpath "$component" +done + +# Set up SHLIB_PATH +if [ "hp-ux" = "10" ]; then + export SHLIB_PATH=$(cat /etc/SHLIB_PATH) + export SHLIB_PATH=$SHLIB_PATH:$M_LROOT/bin + export LD_LIBRARY_PATH=$SHLIB_PATH:$M_LROOT/bin +fi + diff --git a/rc/setup_rc b/rc/setup_rc new file mode 100755 index 0000000..d57a67c --- /dev/null +++ b/rc/setup_rc @@ -0,0 +1,64 @@ +#!/bin/bash +################################################################################ +# +# File: $RCSfile: setup_rc,v $ +# Revision: $Revision: 1.6 $ +# Description: This script sets up my rc scripts +# Author: Andrew@DeFaria.com +# Created: Thu Feb 16 07:34:32 PST 2006 +# Modified: $Date: 2011/12/14 22:28:59 $ +# Language: bash +# +# (c) Copyright 2006, Andrew@DeFaria.com, all rights reserved +# +################################################################################ +function ReplaceFile { + dest=$1 + source=$2 + + if [ -f "$dest" ]; then + if [ -h "$dest" ]; then + return + else + echo "Saving your old $dest as $dest.save..." + mv "$dest" "$dest.save" + fi + fi + + if [ ! -h "$dest" ]; then + ln -s "$source" "$dest" + fi +} # ReplaceFile + +function ReplaceDir { + dest=$1 + source=$2 + + if [ -d "$dest" ]; then + if [ -h "$dest" ]; then + return + else + echo "Saving your old $dest as $dest.save..." + mv "$dest" "$dest.save" + fi + fi + + if [ ! -h "$dest" ]; then + ln -s "$source" "$dest" + fi +} # ReplaceDir + +if [ ! -d $HOME/.rc ]; then + echo "No $HOME/.rc directory found" + exit 1 +fi + +ReplaceFile "$HOME/.Xdefaults" "$HOME/.rc/Xdefaults" +ReplaceFile "$HOME/.bash_login" "$HOME/.rc/bash_login" +ReplaceFile "$HOME/.bashrc" "$HOME/.rc/bash_login" +ReplaceFile "$HOME/.inputrc" "$HOME/.rc/inputrc" +ReplaceFile "$HOME/.vimrc" "$HOME/.rc/vimrc" +ReplaceDir "$HOME/.xemacs" "$HOME/.rc/xemacs" +ReplaceFile "$HOME/.ssh/config" "$HOME/.rc/sshconfig" +ReplaceFile "$HOME/.perlcriticrc" "$HOME/.rc/perlcriticrc" +ReplaceFile "$HOME/.perltidyrc" "$HOME/.rc/perltidyrc" diff --git a/rc/signatures b/rc/signatures new file mode 100644 index 0000000..0c8ee9d --- /dev/null +++ b/rc/signatures @@ -0,0 +1,1483 @@ +Andrew DeFaria +$ +11th commandment - Covet not thy neighbor's Pentium. +% +2 + 2 = 5 for extremely large values of 2. +% +2400 Baud makes you want to get out and push!! +% +24 hours in a day...24 beers in a case...coincidence? +% +3 kinds of people: those who can count & those who can't. +% +42.7 percent of all statistics are made up on the spot. +% +5 days a week my body is a temple. The other two, it's an amusement park. +% +"640K ought to be enough for anybody." - Bill Gates, 1981 +% +640K ought to be enough RAM for anybody. - Bill Gates, 1981 +% +99 percent of lawyers give the rest a bad name. +% +A balanced diet is a cookie in each hand. +% +A bartender is just a pharmacist with a limited inventory. +% +A bird in the hand makes it difficult to blow your nose. +% +(A)bort, (R)etry, (G)et a beer? +% +(A)bort, (R)etry, (T)ake down entire network? +% +A budget is just a method of worrying before you spend money, as well as afterward. +% +A bus station is where a bus stops. A train station is where a train stops. On my desk, I have a work station... Go figure! +% +Access denied--nah nah na nah nah! +% +A Clean House Is A Sign Of A Misspent Life +% +A clear conscience is usually the sign of a bad memory. +% +A closed mouth gathers no feet. +% +A common mistake people make when trying to design something completely foolproof is to underestimate the ingenuity of complete fools. - Douglas Adams +% +A computer's attention span is as long as it's power cord. +% +A conclusion is simply the place where you got tired of thinking. +% +A conscience is what hurts when all your other parts feel so good. +% +A cubicle is just a padded cell without a door. +% +A day without sunshine is like night. +% +Adults are just kids who owe money. +% +Advice - Do not use a hatchet to remove a fly from your forehead. +% +A dyslexic man walks into a bra... +% +A flashlight is a case for holding dead batteries. +% +A fool and his money are soon partying. +% +A fool-proof method for sculpting an elephant: First, get a huge block of marble; then chip away everything that doesn't look like an elephant. +% +A friend of mine is into Voodoo Acupuncture. You don't have to go. You'll just be walking down the street, and...........ooooohhhhhh, that's much better... +% +After eating, do amphibians have to wait one hour before getting out of the water? +% +A good friend will come and bail you out of jail but a true friend will be sitting next to you saying, "Dang, that was fun." +% +A good scapegoat is almost as good as a solution. +% +A husband is someone who takes out the trash and gives the impression he just cleaned the whole house. +% +A lady came up to me on the street, pointed at my suede jacket and said, "Don't you know a cow was murdered for that jacket?" I said "I didn't know there were any witnesses. Now I'll have to kill you too." +% +Alcohol and calculus don't mix. Never drink and derive. +% +All computers wait at the same speed. +% +All generalizations are false +% +All I ask is a chance to prove that money can't make me happy. +% +All I ask is that you treat me no differently than you would the King. +% +All I want in life is a warm bed, and unlimited power. +% +All of us could take a lesson from the weather. It pays no attention to criticism. +% +All that glitters has a high refractive index. +% +All things being equal, fat people use more soap. +% +All those who believe in psychokinesis raise my hand. +% +All wiyht. Rho sritched mg kegtops awound? +% +Always borrow money from pessimists. They don't expect to be paid back. +% +Always proofread carefully to see if you any words out. +% +Always remember to pillage BEFORE you burn. +% +Always remember you're unique, just like everyone else. +% +Always try to be modest. And be damn proud of it! +% +A mainframe: The biggest PC peripheral available. +% +Ambition is a poor excuse for not having enough sense to be lazy. +% +Ambition is the last refuge of a failure. +% +Ambivalent? Well, yes and no. +% +A Messy Kitchen Is A Happy Kitchen And This Kitchen Is Delirious +% +A musicologist is a man who can read music but can't hear it. - Sir Thomas Beecham (1879 - 1961) +% +Analyzing humor is like dissecting a frog. Few people are interested and the frog dies of it. - E. B. White +% +An American is a person who isn't afraid to criticize the President but is always polite to traffic cops. +% +And don't start a sentence with a conjunction. +% +And when I get real, real bored, I like to drive downtown and get a great parking spot, then sit in my car and count how many people ask me if I'm leaving. +% +And whose cruel idea was it to put an S in the word Lisp? +% +An error? Impossible! My modem is error correcting. +% +Animal testing is a bad idea - they get nervous and give the wrong answers. +% +An ounce of practice is worth more than tons of preaching. - Mohandas Gandhi +% +An oyster is a fish built like a nut. +% +Any clod can have the facts, but having opinions is an art. - Charles McCabe +% +Anything worth fighting for is worth fighting dirty for. +% +A PBS mind in an MTV world. +% +A penny saved is ridiculous. +% +A preposition must never be used to end a sentence with. +% +Are the kids on the Barney Show just too damn happy? +% +Are there seeing eye humans for blind dogs? +% +Artificial intelligence is no match for natural stupidity +% +As a computer, I find your faith in technology amusing. +% +ASCII stupid question, get a stupid ANSI! +% +A shark is the only fish that can blink with both eyes. +% +As I always say "I never repeat myself" +% +A singles bar is a place people go to in hopes of meeting the sort of person who wouldn't be caught dead in a singles bar. +% +Ask not for whom the bell tolls. Let the machine get it. +% +Ask people why they have deer heads on their walls and they tell you it's because they're such beautiful animals.I think my wife is beautiful, but I only have photographs of her on the wall. +% +As long as there are tests, there will be prayer in public schools. +% +A synonym is a word you use when you can't spell the word you first thought of. - Burt Bacharach +% +Atheism is a non-prophet organization. +% +A truly wise man never plays leapfrog with a unicorn. +% +Avoid unnecessary, unessential and needless repetition and redundancy. +% +A waist is a terrible thing to mind. +% +Back off man. I'm a scientist. +% +Backup not found: (A)bort (R)etry (P)anic +% +Backups? Backups? We don't need no stinking backups! +% +Bad breath is better than no breath. +% +Bad command. Bad, bad command! Sit! Stay! Staaay.. +% +Beauty is in the eye of the beer holder. +% +Be different. Conform. +% +Beer: It's not just for breakfast anymore. +% +Be nice to your kids. They'll choose your nursing home. +% +Better living through denial. +% +Better to understand a little than to misunderstand a lot. +% +Black holes really suck. +% +Borrow money from pessimists-they don't expect it back. +% +BREAKFAST.COM Halted...Cereal Port Not Responding +% +Budget: A method for going broke methodically. +% +BUFFERS=20 FILES=15 2nd down, 4th quarter, 5 yards to go! +% +Bureaucracy: a method of turning energy into solid waste +% +Buy a Pentium 586/90 so you can reboot faster. +% +By the time you make ends meet, they move the ends. +% +By the turn of this century, we will live in a paperless society. - Roger Smith, chairman of General Motors, 1986 +% +Cannot find REALITY.SYS. Universe halted. +% +Can vegetarians eat animal crackers? +% +Can you be a closet claustrophobic? +% +Can you buy anything specific at a general store? +% +Can you sentence a homeless man to house arrest? +% +Car service: If it ain't broke, we'll break it. +% +C:\> Bad command or file name! Go stand in the corner. +% +C:\DOS C:\DOS\RUN RUN\DOS\RUN +% +Change is inevitable, except from a vending machine. +% +Chaos, panic, & disorder -- my work here is done. +% +Clones are people two. +% +Clothes make the man. Naked people have little or no influence on society. - Mark Twain +% +COFFEE.EXE Missing - Insert Cup and Press Any Key +% +Collaboration, n.: A literary partnership based on the false assumption that the other person can spell. +% +Computers are not intelligent. They only think they are. +% +Computers are useless. They can only give you answers. - Pablo Picasso +% +Computers make very fast, very accurate mistakes. +% +Confidence is the feeling you have before you really understand the problem. +% +CONGRESS.SYS Corrupted: Re-boot Washington D.C (Y/n)? +% +Consciousness: That annoying time between naps. +% +Consider, the Bible was written by the same people who said the Earth was flat. +% +Copywight 1994 Elmer Fudd. All wights wesewved. +% +C program run. C program crash. C programmer quit. +% +"Criminal Lawyer" is a redundancy. +% +C:\WINDOWS C:\WINDOWS\GO C:\PC\CRAWL +% +Daddy, why doesn't this magnet pick up this floppy disk? +% +Dain bramaged. +% +DEFINITION: Computer - A device designed to speed and automate errors. +% +Demons are a Ghouls best Friend. +% +Department of Redundancy Department +% +Despite the cost of living, have you noticed how it remains so popular? +% +Did anyone see my lost carrier? +% +Did ya hear? They took the word gullible out of the dictionary! +% +Did you ever notice when you blow in a dog's face he gets mad at you? But when you take him in a car he sticks his head out the window. +% +Diplomacy is the art of saying "Nice doggie!" - Until you can find a rock. +% +Diplomacy - the art of letting someone have your way. +% +Disco is to music what Etch-A-Sketch is to art +% +Disinformation is not as good as datinformation. +% +Disk Full - Press F1 to belch. +% +Do cemetery workers prefer the graveyard shift? +% +Does fuzzy logic tickle? +% +Does it bother you that doctors call what they do a practice? +% +Doesn't "expecting the unexpected" make the unexpected the expected. +% +Does your train of thought have a caboose? +% +Do fish get cramps after eating? +% +Do hungry crows have ravenous appetites? +% +Do illiterate people get the full effect of alphabet soup? +% +Do infants enjoy infancy as much as adults enjoy adultery? +% +Do Lipton Tea employees take coffee breaks? +% +Do not meddle in the affairs of dragons, for you are crunchy and taste good toasted. +% +Don't be accommodating, be honest. I honestly don't have much more time for anything else. +% +Don't be so open-minded your brains fall out. +% +Don't bother me. I'm living happily ever after. +% +Don't drink and drive... You might hit a bump and spill your drink. +% +Don't look back, they might be gaining on you. +% +Don't make no sense that common sense don't make no sense no more. - John Prine +% +Don't take life too seriously, you won't get out alive. +% +Don't tell anyone, but duct tape is The Force. It has a dark side, and a light side, and it binds the Universe together. +% +Don't use a big word where a diminutive one will suffice. +% +Don't you hate when your hand falls asleep and you know it will be up all night. +% +DOS Tip #17: Add DEVICE=FNGRCROS.SYS to CONFIG.SYS +% +Double your drive space - delete Windows! +% +Do unto others, then run like hell. +% +Do witches run spell checkers? +% +Do you think that when they asked George Washington for ID that he just whipped out a quarter? +% +Dumb Question Department: Been swimming. Smart Answer: No, I was out walking my pet fish! +% +DUMBWAITER: one who asks if the kids would care to order dessert. +% +Dyslexics of the world, UNTIE! +% +Eagles may soar, but weasels don't get sucked into jet engines. +% +Earth First! We'll strip mine the other planets later. +% +E-mail returned to sender -- insufficient voltage. +% +Energizer Bunny arrested, charged with battery. +% +Enter any 11-digit prime number to continue... +% +E Pluribus Modem +% +Error: Keyboard not attached. Press F1 to continue ... +% +Error, no keyboard - press F1 to continue. +% +Error reading FAT record: Try the SKINNY one? (Y/N) +% +Ethernet (n): something used to catch the etherbunny +% +Even a mosquito doesn't get a slap on the back until it starts to work. +% +Ever notice how fast Windows runs? Neither did I. +% +Ever notice how irons have a setting for permanent press? I don't get it... +% +Ever notice that anyone going slower than you is an idiot, but anyone going faster is a maniac? +% +Ever notice when you blow in a dog's face he gets mad at you, but when you take him in a car he sticks his head out the window? +% +Ever stop to think, and forget to start again? +% +Ever wonder what the speed of lightning would be if it didn't zigzag? +% +Everybody is somebody else's weirdo. +% +Everybody repeat after me ...We are all individuals. +% +...Every morning is the dawn of a new error... +% +Everyone has a photographic memory, some just don't have any film. +% +Everyone has the right to be stupid, but your abusing the privilege. +% +Everyone hates me because I'm paranoid. +% +Everything should be made as simple as possible, but no simpler. +% +Everywhere is walking distance if you have the time. +% +Excuse me for butting in, but I'm interrupt-driven. +% +Experience is something you don't get until just after you need it. +% +FATAL ERROR! SYSTEM HALTED! - Press any key to do nothing. +% +Fear has its use but cowardice has none. - Mohandas Gandhi +% +... File not found. Should I fake it? (Y/N) +% +Five out of four people have trouble with fractions. +% +For every action, there is an equal and opposite criticism. +% +For my birthday I got a humidifier and a de-humidifier...I put them in the same room and let them fight it out... +% +For people who like peace and quiet: a phoneless cord. +% +For Sale: Parachute. Only used once, never opened, small stain. +% +Friction can be a real drag. +% +Friends help you move. Real friends help you move bodies. +% +Friends may come and go, but enemies accumulate. +% +Funny, I don't remember being absent minded. +% +Get your facts first, and then you can distort them as much as you please. - Mark Twain +% +Give a man a fish and he'll eat for a day, teach a man to phish and he'll suck your bank account dry +% +Give a person a fish and you feed them for a day; teach that person to use the Internet and they won't bother you for weeks. +% +Give me ambiguity or give me something else. +% +Go ahead and take risks....just be sure that everything will turn out OK. +% +Going to church does not make you a Christian any more than standing in a garage makes you a car. +% +Good health is merely the slowest possible rate at which one can die. +% +Good judgment comes from bad experience... Which comes from bad judgment +% +Great art is as irrational as great music. It is mad with its own loveliness. - George Jean Nathan +% +Half the people you know are below average. +% +Hang up and drive. +% +Happiness is merely the remission of pain. +% +Hard work has a future payoff - Laziness pays off now. +% +Have you ever imagined a world with no hypothetical situations? +% +Have you noticed since everyone has a camcorder these days no one talks about seeing UFOs like they used to? +% +Headline: Bear takes over Disneyland in Pooh D'Etat! +% +Help! I'm modeming... and I can't hang up!!! +% +Help Wanted: Telepath. You know where to apply. +% +Hermits have no peer pressure. +% +He's not dead, he's electroencephalographically challenged. +% +He who laughs last thinks slowest! +% +Hidden DOS secret: add BUGS=OFF to your CONFIG.SYS +% +Hit any user to continue. +% +Home computers are being called upon to perform many new functions, including the consumption of homework formerly eaten by the dog. - Doug Larson +% +Honesty is the best policy, but insanity is a better defense. +% +How can you tell when the blue cheese goes bad? +% +How come abbreviated is such a long word? +% +How come a slight tax increase costs you two hundred dollars and a substantial tax cut saves you thirty cents? +% +How come you don't ever hear about gruntled employees? And who has been dissing them anyhow? +% +How do you tell when you run out of invisible ink? +% +How good bad music and bad reasons sound when we march against an enemy. - Friedrich Nietzsche +% +How many of you believe in telekinesis? Raise my hands. . . . +% +How much deeper would oceans be if sponges didn't live there? +% +Humor is a rubber sword - it allows you to make a point without drawing blood. - Mary Hirsch +% +I always take life with a grain of salt, plus a slice of lemon and a shot of tequila. +% +I always wanted to be a procrastinator but never got around to it +% +I always wanted to be somebody, but I should have been more specific. +% +I am in shape. Round is a shape! +% +I broke a mirror in my house. I'm supposed to get seven years of bad luck, but my lawyer thinks he can get me five. +% +I can please only one person per day. Today is not your day. And tomorrow isn't looking good either. +% +I can see clearly now, the brain is gone... +% +I can't remember if I'm the good twin or the evil one. +% +I'd explain it to you, but your brain would explode. +% +I didn't climb to the top of the food chain to be a vegetarian. +% +I don't get even, I get odder. +% +I don't have a license to kill. I have a learner's permit. +% +I don't have a solution but I admire the problem. +% +I don't have to take this abuse from you -- I've got hundreds of people waiting to abuse me. +% +I don't suffer from insanity. I enjoy every minute of it. +% +If 7-11 stores are open 24 hours/7-days a week, why do they have locks on the front door? +% +If a book about failures does not sell, is it a success? +% +If a cow laughed, would milk come out her nose? +% +If all the world is a stage, where is the audience sitting? +% +If a man says something in the woods and there are no women there, is he still wrong? +% +If a mime is arrested do they tell him he has the right to talk? +% +If a mute swears does his mother wash his hands with soap? +% +If an orange is orange, why isn't a lime called a green or a lemon called a yellow? +% +If a pig loses its voice, is it disgruntled? +% +If at first you don't succeed, destroy all evidence that you tried. +% +If at first you don't succeed, skydiving is not for you. +% +If at first you DO succeed, try not to look astonished! +% +If a turtle doesn't have a shell, is he homeless or naked? +% +If bankers can count, how come they have eight windows and only four tellers? +% +If croutons are stale bread, why do they come in airtight packages? +% +If debugging is the process of removing bugs, then programming must be the process of putting them in. +% +If electricity comes from electrons, where does morality come from? +% +If Fed Ex and UPS were to merge, would they call it Fed UP? +% +If God wanted me to touch my toes, he would have put them on my knees. +% +If ignorance is bliss, you must be orgasmic. +% +If I melted dry ice, could I swim in it and not get wet? +% +If I only had a little humility, I'd be perfect. - Ted Turner +% +If it ain't broke fix it anyway! If it's broke fix it and make it worse! +% +If it's true that we are here to help others, then what exactly are the others here for? +% +If it's zero degrees outside today and it's supposed to be twice as cold tomorrow, how cold is it going to be? +% +If it's zero degrees outside today and it's supposed to be twice as cold tomorrow, how cold is it going to be? +% +If I want your opinion, I'll ask you to fill out the necessary forms. +% +If knees were backwards, what would chairs look like? +% +If lawyers are disbarred and clergymen defrocked, doesn't it follow that electricians can be delighted, musicians denoted, cowboys deranged, models deposed, tree surgeons debarked, and dry cleaners depressed? +% +If mother always knows best...What happens when two mothers disagree? +% +If olive oil comes from olives, where does baby oil come from? +% +If one synchronized swimmer drowns, do the rest have to drown too? +% +If people from Poland are called Poles, why aren't people from Holland called Holes? +% +If quitters never win, and winners never quit, what fool came up with, "Quit while you're ahead"? +% +If the odds are a million to one against something occurring, chances are 50-50 it will. +% +If the professor on Gilligan's Island can make a radio out of a coconut, why can't he fix a hole in a boat? +% +If there is a god, he will understand why I don't believe in him. +% +If there's one thing I can't stand, it's intolerance. +% +If the world was a logical place, men would ride horses side-saddle. +% +If things get any worse, I'll have to ask you to stop helping me. +% +If toast always lands butter-side-down, and a cats always land on their feet, what would happen if you strapped a piece of toast on the back of a cat & dropped it? +% +If vegetarians eat vegetables, what do humanitarians eat? +% +If you believe in telekinesis, raise my hand. +% +If you blow into a dog's face, it will drive it crazy. Why is it when you take them for a ride in a car, they stick their head out of the window? +% +If you can read this, I can slam on my brakes and sue you. +% +If you can smile when things go wrong, you have someone in mind to blame. +% +If you can survive death, you can probably survive anything. +% +If you can't be kind, at least have the decency to be vague. +% +If you drink, don't park. Accidents cause people. +% +If you had everything, where would you keep it? +% +If you have a difficult task, give it to a lazy person; they'll find an easier way to do it. +% +If you mixed vodka with orange juice and Milk Of Magnesia, would you get a Philip's Screwdriver? +% +If you must choose between two evils, pick the one you've never tried before. +% +If you're cross-eyed and have dyslexia, can you read all right? +% +If you're living on the edge, make sure you're wearing your seat belt. +% +If you're sending someone some Styrofoam, what do you pack it in? +% +If you take an Oriental person and spin him around several times, does he become disoriented? +% +If you think nobody cares about you, try missing a couple of payments. +% +If you think that there is good in everybody, you haven't met everybody. +% +If you were driving your car at the speed of light, and you turned on your headlights. Would anything happen? +% +I got a new shadow. I had to get rid of the other one -- it wasn't doing what I was doing. +% +I have no choice but to believe in free will. - Randy Wayne White +% +I have not failed, I've just found 10,000 ways that won't work. - Thomas Edison +% +I have seen the truth and it makes no sense. +% +I have six locks on my door all in a row. When I go out, I lock every other one. I figure no matter how long somebody stands there picking the locks, they are always locking three. +% +I hit the CTRL key but I'm still not in control! +% +I intend to live forever - so far, so good +% +I just got a physical and asked the doctor, "How do I stand?" He said, "That's what puzzles me!" +% +I just got skylights put in my place. The people who live above me are furious! +% +I know how I want to die...shot at the age of 108 by a jealous husband! +% +I know you believe you understand what you think I said, but I'm not sure you realize that what you heard is not what I meant. +% +I know you may think you know what I said, but I'm not sure that you realize that what you think I said is not really what I meant. +% +I like kids, but I don't think I could eat a whole one. +% +I love deadlines. I especially like the whooshing sound they make as they go flying by. +% +I love to go shopping. I love to freak out salespeople. They ask me if they can help me, and I say, "Have you got anything I'd like?" Then they ask me what size I need, and I say, "Extra medium." +% +Imagination is more important than knowledge. - Albert Einstein +% +I'm all in favor of keeping dangerous weapons out of the hands of fools. Let's start with typewriters. - Solomon Short +% +I'm a psychic amnesiac. I know in advance what I'm going to forget. +% +I'm a tagline virus, please copy me to your signature file +% +I'm desperately trying to figure out why Kamikaze pilots wore helmets. +% +I'm not a complete idiot, some parts are missing! +% +I'm not into working out. My philosophy is no pain, no pain. +% +I'm not schizophrenic, and neither am I. +% +I'm not tense, just terribly, terribly alert. +% +Indecision is the key to flexibility. +% +Individualists of the world, UNITE! +% +In love the paradox occurs that two beings become one and yet remain two. - Erich Fromm +% +In my house, on the ceilings I have paintings of the rooms above...so I never have to go upstairs. +% +In some cultures what I do would be considered normal. +% +Instead of talking to your plants, if you yelled a them would they still grow, only to be troubled and insecure? +% +I(nternal) R(evenue) S(ervice): We've got what it takes to take what you've got. +% +In the 60's, people took acid to make the world appear weird. Now the world is weird and people take Prozac to make it appear normal. +% +Introducing LITE - the new way to spell LIGHT with 20% fewer letters! +% +I once wanted to become an atheist but I gave up...They have no holidays +% +I played a blank tape on full volume. The mime who lived next door complained. So I shot him with a gun with a silencer. +% +I put contact lenses in my dog's eyes. They had little pictures of cats on them. Then I took one out and he ran around in circles. +% +I put instant coffee in my microwave oven and almost went back in time. +% +I said "NO" to drugs, but they didn't listen. +% +I see no virtue in outliving my ability to have fun. +% +Is French kissing in France just called kissing? +% +Is it my imagination, or do buffalo wings taste like chicken? +% +Is it time for your medication or mine? +% +Is it true that cannibals don't eat clowns because they taste funny? +% +I spilled spot remover on my dog and now he's gone. +% +Is there another word for synonym? +% +I swear by my life and my love of it, that I will never live for the sake of another man, nor ask another man to live for mine +% +It doesn't matter what temperature a room is, it's always room temperature. +% +I think there is a world market for maybe five computers. - Thomas J. Watson, chairman of IBM, 1943 +% +I thought about how mothers feed their babies with little tiny spoons and forks so I wonder what Chinese mothers use. Toothpicks? +% +It IS as bad as you think, and they ARE out to get you. +% +It is bad luck to be superstitious. +% +It may be that your sole purpose in life is simply to serve as a warning to others. +% +I took an IQ test and the results were negative. +% +I tried sniffing Coke once, but the ice cubes froze the end of my nose. +% +I tried to backup my hard drive but I couldn't figure out how to put it in reverse +% +It's a small world, but I wouldn't want to paint it... +% +It's lonely at the top, but you eat better. +% +It's not an optical illusion. It just looks like one. +% +It's not hard to meet expenses, they're everywhere. +% +It's not the pace of life that concerns me, it's the sudden stop at the end. +% +I used to be a bartender at the Betty Ford Clinic. +% +I used to be clueless about math, but I turned that around 360 degrees. +% +I used to be schizophrenic, but we're all right now. +% +I used to have a handle on life, then it broke. +% +I used to have an open mind but my brains kept falling out. +% +I used to work at a factory where they made hydrants; but you couldn't park anywhere near the place. +% +I used up all my sick days, so now I'm calling in dead. +% +I've always wanted to be somebody, but I should have been more specific. +% +I've had amnesia for as long as I can remember. +% +I've taken a vow of poverty -- to annoy me, send money +% +I've writing a book. I've got the page numbers done. +% +I wake up every morning at nine and grab for the morning paper. Then I look at the obituary page. If my name is not on it, I get up. - Benjamin Franklin +% +I want to die in my sleep like my grandfather did, not screaming and yelling like the passengers in his car. +% +I want to die peacefully in my sleep like my grandfather... Not screaming and yelling like the passengers in his car. +% +I was born by Cesarean section, but you really can't tell...except that when I leave my house, I always go out the window... +% +I was hitchhiking the other day, and a hearse stopped. I said, "No thanks - I'm not going that far." +% +I was self-employed for two years, and boy was my boss a turkey! :-) +% +I was once walking through the forest alone. A tree fell right in front of me -- and I didn't hear it. +% +I was pulled over for speeding today. The officer said, "Don't you know the speed limit is 55 miles an hour?" I replied, "Yes, but I wasn't going to be out that long. +% +I was simply furnishing a home. I love music ... and I don't think a $130,000 indoor-outdoor stereo system is extravagant. - Leona Helmsley +% +I was thinking about how people seem to read the Bible a whole lot more as they get older, then it dawned on me . .they were cramming for their finals.. +% +I was trying to daydream, but my mind kept wandering. +% +I went for a walk last night and my kids asked me how long I'd be gone. I said, "The whole time." +% +I went to a bookstore and asked the saleswoman where the Self Help section was, she said if she told me it would defeat the purpose +% +I went to a general store, but they wouldn't let me buy anything specific. +% +I went to a restaurant that serves "breakfast at any time". So I ordered French Toast during the Renaissance. +% +I wonder how much deeper would the ocean be without sponges. +% +I won't rise to the occasion, but I'll slide over to it. +% +I wrote a song, but I can't read music. Every time I hear a new song on the radio I think "Hey, maybe I wrote that." +% +Just because I have a short attention span doesn't mean I ... um ... er ... uh ... +% +Just because you're paranoid doesn't mean they're not out to get you. +% +Just before someone gets nervous, do they experience cocoons in their stomach? +% +Just for today, I will not sit in my living room all day in my underwear. Instead, I will move my computer into the bedroom. +% +Just remember one thing in life - no matter where you go - there you are. +% +Just what part of "NO" didn't you understand? +% +Just what the hell was the best thing BEFORE sliced bread? +% +Keep honking while I reload. +% +Last night I played a blank tape at full blast. The mime next door went nuts. +% +Law of Probability Dispersal: Whatever it is that hits the fan will not be evenly distributed. +% +Lead me not into temptation (I can find the way myself). +% +Life is a salad bar and I just keep banging my head on the sneeze guard. +% +Living on Earth may be expensive, but it includes an annual trip around the sun. +% +Look out for #1. Don't step in #2 either. +% +Lottery: A tax on people who are bad at math. +% +Love is always bestowed as a gift - freely, willingly and without expectation. We don't love to be loved; we love to love. - Leo Buscaglia +% +Love is like war: easy to begin but very hard to stop. - H. L. Mencken +% +Love is the triumph of imagination over intelligence. - H. L. Mencken +% +Madness takes its toll. Please have exact change. +% +Make it idiot proof and someone will make a better idiot. +% +Making music should not be left to the professionals. - Michelle Shocked +% +Maybe some lonesome picker will find some healing in my songs. - John Stewart +% +Mental Floss prevents Moral Decay. +% +Middle Age is when actions creak louder than words +% +"More hay, Trigger?" "No thanks, Roy, I'm stuffed!" +% +Music expresses that which cannot be said and on which it is impossible to be silent. - Victor Hugo +% +Music is essentially useless, as life is. - George Santayana +% +Music is the art which is most nigh to tears and memory. - Oscar Wilde +% +Music, I suppose, will be the thing that sustains me when I'm too old for sex, and not quite ready to meet God. - Dolly Parton +% +My friend has a baby. I'm writing down all the noises he makes so later I can ask him what he meant. +% +My software never has bugs. It just develops random features. +% +My wife keeps complaining I never listen to her ...or something like that. +% +Never argue with a fool; he will soon beat you with his experience. +% +Never raise your hands to your kids. It leaves your groin unprotected. +% +Never wrestle with a pig. You both get dirty and the pig likes it. +% +NEWS FLASH! This just in from the Department of Redundancy Department ... +% +No one ever says, "It's only a game", when their team is winning. +% +Nostalgia isn't what it used to be. +% +Nothing is foolproof to a sufficiently talented fool. +% +Nothing says poor craftsmanship more than wrinkled duct tape. +% +Not one shred of evidence supports the notion that life is serious. +% +Now they show you how detergents take out bloodstains. I think if you've got a T-shirt with a bloodstains all over it, maybe laundry isn't your biggest problem. Maybe you should get rid of the body before you do the wash. +% +Okay, who put a "stop payment" on my reality check? +% +Old age is when you still have something on the ball but you are just too tired to bounce it. +% +Old dog still learning - please don't shoot yet +% +Old is when an "All-Nighter" means not getting up to pee +% +Old quarterbacks never die, they just fade back and pass +% +One nice thing about egoists: They don't talk about other people. +% +One of the great tragedies of life is the murder of a beautiful theory by a gang of brutal facts. - Benjamin Franklin +% +One reason most people play golf is to wear clothes they wouldn't be caught dead in otherwise. +% +One-seventh of your life is spent on Monday. +% +One tequila, two tequila, three tequila, floor. +% +One time a cop pulled me over for running a stop sign. He said "Didn't you see the stop sign." I said "Yeah, but I don't believe everything I read." +% +Only in America are there handicap parking places in front of a skating rink. +% +Only in America can a pizza get to your house faster than an ambulance. +% +On the keyboard of life, always keep one finger on the escape key. +% +Oooo, baby, it's a big old goofy world. - John Prine +% +Oops. My brain just hit a bad sector. +% +Out of my mind. Back in five minutes. +% +Part of the inhumanity of the computer is that, once it is competently programmed and working smoothly, it is completely honest. - Isaac Asimov +% +Patience has its limits. Take it too far, and it's cowardice. - George Jackson +% +Pentiums melt in your PC, not in your hand. +% +Photons have mass? I didn't even know they were Catholic. +% +Plan to be spontaneous tomorrow. +% +Please, Lord, let me prove that winning the lottery won't spoil me. +% +Prediction is very difficult, especially about the future. - Niels Bohr +% +Press any key... no, no, no, NOT THAT ONE! +% +Press any key to continue or any other key to quit... +% +Press CTRL-ALT-DEL to continue ... +% +Programmer - A red-eyed, mumbling mammal capable of conversing with inanimate objects. +% +Programmers don't die, they just GOSUB without RETURN. +% +Proofread carefully to see if you any words out. +% +Puritanism: The haunting fear that someone, somewhere may be happy. +% +Question: Why do people always seem to find things in the last place that they look? Answer: Because most people stop looking after they find it! +% +RAM disk is *not* an installation procedure. +% +Read my chips: No new upgrades! +% +Reality is a crutch for people who can't handle drugs. +% +REALITY.SYS corrupted: Reboot universe? (Y/N/Q) +% +Real programmers don't document. If it was hard to write, it should be to understand. +% +Right now I'm having vu ja de--deja vu and amnesia at the same time. I could have sworn I forgot this before! +% +Save the whales! Trade them for valuable prizes. +% +Seen it all, done it all, can't remember most of it. +% +SENILE.COM found . . . Out Of Memory . . . +% +Shell to DOS... Come in DOS, do you copy? Shell to DOS... +% +Shin: A device for finding furniture in the dark. +% +Show me a man with both feet firmly on the ground, and I'll show you a man who can't get his pants off. +% +Sign seen in a bar: "Those drinking to forget please pay in advance" +% +Since light travels faster than sound, isn't that why some people appear bright until you hear them speak? +% +Smash forehead on keyboard to continue..... +% +Someone who thinks logically is a nice contrast to the real world. +% +Some people are like Slinkies . . not really good for anything, but you still can't help but smile when you see one tumble down the stairs. +% +Some people are only alive because it is illegal to shoot them. +% +Some people just don't know how to drive, I call these people "Everybody But Me." +% +Some people say "life is short". What?? Life is the longest damn thing anyone ever does!! What can you do that's longer? +% +Sometimes I think it's a shame when I get feelin' better when I'm feelin' no pain. - Gordon Lightfoot +% +Sometimes too much drink is not enough. +% +Southern DOS: Y'all reckon? (Yep/Nope) +% +So what's the speed of dark? +% +Sped up my XT; ran it on 220v! Works greO?_~" +% +Stop repeat offenders. Don't re-elect them! +% +Suburbia: where they tear out the trees & then name streets after them. +% +Success always occurs in private, and failure in full view. +% +Suicidal twin kills sister by mistake! +% +Suicide is the most sincere form of self-criticism. +% +Sure you can trust the government! Just ask an Indian! +% +Take my advice; I don't use it anyway. +% +Taxation with representation isn't so hot, either! +% +Tell a man that there are 400 billion stars and he'll believe you. Say a bench has wet paint and he has to touch it. +% +That's a hell of an ambition, to be mellow. It's like wanting to be senile. - Randy Newman +% +That's the beer that made Mel Famie walk us. +% +The 2 most common elements in the universe are hydrogen and stupidity. +% +The beatings will continue until morale improves. +% +The careful application of terror is also a form of communication. +% +The chance that you'll forget something is directly proportional to ... to ... uh ... +% +The colder the X-ray table, the more of your body is required on it. +% +The cost of living hasn't affected its popularity. +% +The Definition of an Upgrade: Take old bugs out, put new ones in. +% +The easiest way to find something lost around the house is to buy a replacement. +% +The facts, although interesting, are irrelevant. +% +The gene pool could use a little chlorine. +% +The hilarious thing about self-important self-righteous people is that they are so easily baited. +% +<-------- The information went data way --------> +% +The more you complain, the longer God makes you live. +% +The more you run over a dead cat, the flatter it gets. +% +The name is Baud......, James Baud. +% +The nice thing about Standards is there are so many to choose from. - Michael Santovec +% +The obituaries in the newspaper prove beyond a shadow of a doubt that people die in alphabetical order. +% +The only difference between a grave and a rut is the depth. +% +The other day I was playing poker with Tarot cards. I got a full house and four people died. +% +The problem with the gene pool is that there is no lifeguard. +% +There are 3 kinds of people in this world. Those who can count and those who can't. +% +There cannot be a crisis today; my schedule is already full. +% +There is absolutely no substitute for a genuine lack of preparation. +% +There is always one more imbecile than you counted on. +% +There is no reason anyone would want a computer in their home. - Ken Olson, president, chairman and founder of Digital Equipment Corp., 1977 +% +There is one thing I would break up over and that is if she caught me with another woman. I wouldn't stand for that. - Steve Martin +% +There's a fine line between fishing and standing on the shore looking like an idiot. +% +There's nothing more annoying than Stravinsky or the Sex Pistols being drowned out by "You've got mail!" +% +There's no trick to being a humorist when you have the whole government working for you. - Will Rogers +% +There's only two things that money can't buy and that's true love and home grown tomatoes. - Guy Clark +% +There's too much blood in my caffeine system. +% +The secret of the universe is @*&^^^ NO CARRIER +% +The trouble with doing something right the first time is that nobody appreciates how difficult it was. +% +The trouble with life is, that you're halfway through it before you realize that it's a "do it yourself" thing. +% +They show you how detergents take out bloodstains. I think if you've got a T-shirt with bloodstains all over it, maybe your laundry isn't your biggest problem. +% +Things are more like they are today than they ever were before. +% +Think "honk" if you're telepathic. +% +This is as bad as it can get, but don't bet on it. +% +Those are my principles. If you don't like them I have others. +% +Time is nature's way of keeping everything from happening all at once. +% +Today I dialed a wrong number....The other side said, "Hello?" and I said, "Hello, could I speak to Joey?" They said," Uh, I don't think so...He's only two months old." I said, "I'll wait..." +% +Today I met with a subliminal advertising executive for just a second. +% +Too many clicks spoil the browse +% +Too many freaks, not enough circuses. +% +Try not to let your mind wander. it's too small and fragile to be out by itself. +% +Two cannibals are eating a clown. One says to the other: "Does this taste funny to you?" +% +Ultimate office automation: networked coffee. +% +Unable to close TROUSER.ZIP! - Replace floppy and retry (Y/N) +% +Very funny Scotty - now beam down my clothes. +% +Violence is the last refuge of the incompetent. - Isaac Asimov +% +We are born naked, wet and hungry. Then things get worse. +% +We don't like their sound, and guitar music is on the way out. -Decca Recording Co. rejecting the Beatles, 1962. +% +We have enough youth. How about a fountain of smart? +% +We win justice quickest by rendering justice to the other party. - Mohandas Gandhi +% +What do people in China call their good plates? +% +What do you do when you see an endangered animal that eats only endangered plants? +% +Whatever happened to Preparations A through G? +% +What hair color do they put on the driver's licenses of bald men? +% +What happened to the first 6 ups? +% +What happens if you get scared half-to-death twice? +% +What has four legs and an arm? A happy pit bull. +% +What if there were no hypothetical questions? +% +What is a "free" gift ? Aren't all gifts free? +% +What is the speed of dark? +% +What's another word for synonym? +% +What's another word for thesaurus? +% +What's so great about sliced bread? Isn't the bread slicer really more impressive? +% +When cheese gets it's picture taken, what does it say? +% +Whenever I feel the need to exercise, I lie down till the feeling goes away. +% +When everything's coming your way, you're in the wrong lane. +% +When God is amazed, does he say: "Oh my Me!"? +% +When it rains, why don't sheep shrink? +% +When it's your lie, you can tell it any way you want +% +When someone asks you, "A penny for your thoughts?" and you put your two cents in, what happens to the other penny? +% +When something is "new and improved!". Which is it? If it's new, then there has never been anything before it. If it's an improvement, then there must have been something before it. +% +When there's a will, I want to be in it. +% +When you do a good deed, get a receipt - In case heaven is like the IRS. +% +When you open a new bag of cotton balls, are you supposed to throw the top one away? +% +When your pet bird sees you reading the newspaper, does he wonder why you're just sitting there, staring at carpeting? +% +Where do forest rangers go to get away from it all? +% +Whitewater is over when the First Lady sings. +% +Who's General Failure & why's he reading my disk? +% +Who so loves believes the impossible. - Elizabeth Barrett Browning +% +Who was the first person to look at a cow and say, "I think I'll squeeze these dangly things here and drink whatever comes out"? +% +Why are a wise man and a wise guy opposites? +% +Why are builders afraid to have a 13th floor but book publishers aren't afraid to have Chapter 11? +% +Why are cigarettes sold in gas stations when you can't smoke there? +% +Why are there 5 syllables in the word monosyllabic? +% +Why are there interstate highways in Hawaii? +% +Why are there tags on blow-dryers that say Do Not Use In The Shower? Is this really a problem? +% +Why are they called apartments, when they're all stuck together? +% +Why are they called stairs inside but steps outside? +% +Why can't women put on mascara with their mouth closed? +% +Why can we shop in a store but we can't store in a shop? +% +Why do ballet dancers always dance on their toes? Wouldn't it be easier to just hire taller dancers? +% +Why do banks charge you a non-sufficient funds fee on money they already know you don't have? +% +Why do croutons come in airtight packages? It's just stale bread to begin with. +% +Why does a cowboy have two spurs? If one side of the horse goes, so does the other. +% +Why does mineral water that has trickled through mountains for centuries have a "use by" date? +% +Why doesn't DOS ever say "EXCELLENT command or filename!" +% +Why doesn't the glue stick to the inside of the bottle? +% +Why does your gynecologist leave the room when you get undressed? +% +Why don't they just make mouse-flavored cat food? +% +Why do overlook and oversee mean opposite things? +% +Why do people ask "Can I ask you a question?".... Didn't really give me a choice there, did ya sunshine? +% +Why do people ask "Has the bus come yet"? If the bus came would I be standing here! +% +Why do people leave cars worth tens of thousands of dollars in the driveway and leave useless things and junk in boxes in the garage? +% +Why do people point to their wrist when asking for the time, but not to their crotch when they ask where the toilet is? +% +Why do people say "did you see that" when watching a movie at the theater? No, I paid $12 to come to the cinema and stare at the floor! +% +Why do people say "Oh you just want to have your cake and eat it too". Damn right! What good is a cake if you can't eat it? +% +Why do psychics have to ask you for your name? +% +Why do the Alphabet song and Twinkle, Twinkle Little Star have the same tune? +% +Why do they call it the Department of Interior when they are in charge of everything outdoors? +% +Why do they put pictures of criminals up in the Post Office? What are we supposed to do, write to them? Why don't they just put their pictures on the postage stamps so the mailmen could look for them while they delivered the mail? +% +Why do they report power outages on TV? +% +Why do they sterilize needles for lethal injections? +% +Why do toasters always have a setting that burns the toast to a horrible crisp no one would eat? +% +Why do we buy hot dogs in packages of ten and buns in packages of eight? +% +Why do we drive on parkways and park on driveways? +% +Why do we play in recitals and recite in plays? +% +Why do we say something is out of whack? What's a whack? +% +Why do women wear evening gowns to nightclubs? Shouldn't they be wearing night gowns? +% +Why do you always turn down your radio when looking for an address? +% +Why do you need a driver's license to buy alcohol when you can't drink and drive? +% +Why is "abbreviation" such a long word? +% +Why is a man who invests all your money called a broker? +% +Why is a person who plays the piano called a pianist, but a person who drives a race car not called a racist? +% +Why is it called Alcoholics Anonymous when the first thing you do is stand up and say, "My name is Bob, and I am an alcoholic"? +% +Why is it that anybody going slower than you is an idiot, and anyone going faster is a maniac. +% +Why is it that the guy who comes up behind you while you're waiting for an elevator presses the already lit button as though he has some magical powers that you don't? +% +Why is it that when you transport something by car, it is called a shipment, but when you transport something by ship, it is called cargo? +% +Why isn't 11 pronounced "onety one"? +% +Why isn't the word phonetic spelled the way is sounds? +% +Why is the alphabet in that order? Is it because of that song? +% +Why is there a light in the fridge and not in the freezer? +% +Why is there only one Monopolies commission? +% +Will the information superhighway have any rest stops? +% +Windows: Just another pane in the glass. +% +Would a fly without wings be called a walk? +% +Yes, I guess, they oughtta name a drink after you. - John Prine +% +Yesterday I parked my car in a tow-away zone...when I came back the entire area was missing... +% +You cannot achieve the impossible without attempting the absurd. +% +You can't fall off the floor. +% +You can't have everything...Where would you put it? +% +You can't tell which way the train went by looking at the track. +% +You can't trust dogs to watch your food. +% +You have the right to remain silent. Anything you say will be misquoted, then used against you. +% +You have to stay in shape. My mother started walking five miles a day when she was 60. She's 97 now and we have no idea where she is. +% +You know how it is when you're walking up the stairs, and you get to the top, and you think there's one more step? I'm like that all the time. +% +You know how most packages say "Open here"? What if it said, "Open somewhere else?" +% +You must be Daddy's little pumpkin, I can tell by the way you roll. - John Prine +% +You never really learn to swear until you learn to drive. +% +Young at heart. Slightly older in other places. +% +You're just jealous because the voices only talk to ME. +% +Is it good if a vacuum really sucks? +% +Why is the third hand on the watch called the second hand? +% +If a word is misspelled in the dictionary, how would we ever know? +% +If Webster wrote the first dictionary, where did he find the words? +% +Why does "fat chance" and "slim chance" mean the same thing? +% +Why do "tug" boats push their barges? +% +Why do we sing "Take me out to the ball game" when we are already there? +% +Why is it called "after dark" when it really is "after light"? +% +If work is so terrific, why do they have to pay you to do it? +% +If all the world is a stage, where is the audience sitting? +% +If you are cross-eyed and have dyslexia, can you read all right? +% +Why do you press harder on the buttons of a remote control when you know the batteries are dead? +% +Why do we put suits in garment bags and garments in a suitcase? +% +Why do we wash bath towels? Aren't we clean when we use them? +% +Ever wonder about those people who spend $2.00 apiece on those little bottles of Evian water? Try spelling Evian backwards +% +Isn't making a smoking section in a restaurant like making a peeing section in a swimming pool? +% +If 4 out of 5 people SUFFER from diarrhea...does that mean that one enjoys it? +% +Can god conceive of something that he can't create or destroy? If so then he's not all powerful. If not then he's not all-knowing. +% +Calling atheism a religion is like calling baldness a hair color. +% +The only stupid questions are the ones you didn't Google first! +% +Don't go around saying the world owes you a living. The world owes you nothing. It was here first. +% +Prejudices are what fools use for reason. +% +The true triumph of reason is that it enables us to get along with those who do not possess it. +% +It is hard to free fools from the chains they revere. +% +Anyone who has the power to make you believe absurdities has the power to make you commit injustices. +% +One of the penalties for refusing to participate in politics is that you end up being governed by your inferiors. +% +No one ever teaches well who wants to teach, or governs well who wants to govern. +% +Courage is knowing what not to fear. +% +The best argument against democracy is a five-minute conversation with the average voter. +% +It has been said that democracy is the worst form of government except all the others that have been tried. +% +Think of how stupid the average person is, and realize half of them are stupider than that. +% +I still say a church steeple with a lightning rod on top shows a lack of confidence. +% +What can be asserted without proof can be dismissed without proof. +% +The fact that a believer is happier than a skeptic is no more to the point than the fact that a drunken man is happier than a sober one. +% +Accept that some days you're the pigeon, and some days you're the statue. +% +Always keep your words soft and sweet, just in case you have to eat them. +% +Always wear stuff that will make you look good if you die in the middle of it. +% +If you lend someone $20 and never see that person again, it was probably worth it. +% +It may be that your sole purpose in life is simply to be kind to others. +% +Never put both feet in your mouth at the same time, because then you won't have a leg to stand on. +% +Nobody cares if you can't dance well. just get up and dance. +% +Since it's the early worm that gets eaten by the bird, sleep late. +% +The second mouse gets the cheese. +% +Birthdays are good for you. the more you have, the longer you live. +% +You may be only one person in the world, but you may also be the world to one person. +% +Some mistakes are too much fun to only make once. +% +We could learn a lot from crayons... Some are sharp, some are pretty and some are dull. Some have weird names, and all are different colors, but they all have to live in the same box. +% +A truly happy person is one who can enjoy the scenery on a detour. +% +The journey of a thousand miles begins with a broken fan belt and leaky tire. +% +It's always darkest before dawn. So if you're going to steal your neighbor's newspaper, that's the time to do it. +% +Don't be irreplaceable. If you can't be replaced, you can't be promoted. +% +Never test the depth of the water with both feet. +% +Before you criticize someone, you should walk a mile in their shoes. That way, when you criticize them, you're a mile away and you have their shoes. +% +Give a man a fish and he will eat for a day. Teach him how to fish, and he will sit in a boat and drink beer all day. +% +If you tell the truth, you don't have to remember anything. +% +Everyone seems normal until you get to know them. +% +There are two theories to arguing with women. Neither one works. +% +Never miss a good chance to shut up. +% +Never, under any circumstances, take a sleeping pill and a laxative on the same night. +% +The box said to install Windows XP/Vista or better - so I installed Linux! +% + diff --git a/rc/signatures.clearscm b/rc/signatures.clearscm new file mode 100644 index 0000000..21aee0d --- /dev/null +++ b/rc/signatures.clearscm @@ -0,0 +1,91 @@ + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    PresidentClearSCM, +Inc.
    1250 +West Grove Parkway #1178
    The power to see +clearly...Tempe, +Arizona 85283-4449
    Professional SCM +ConsultantsPhone: +480-220-7526
    http://ClearSCM.com
    +
    Info@ClearSCM.com
    +
    +
    diff --git a/rc/sshconfig b/rc/sshconfig new file mode 100644 index 0000000..3daa4f0 --- /dev/null +++ b/rc/sshconfig @@ -0,0 +1,3 @@ +ForwardX11 yes +ForwardX11Trusted yes +StrictHostKeyChecking no diff --git a/rc/system b/rc/system new file mode 100644 index 0000000..88d1921 --- /dev/null +++ b/rc/system @@ -0,0 +1,22 @@ +#!/bin/bash +################################################################################ +# +# File: $RCSfile: system,v $ +# Revision: $Revision: 1.6 $ +# Description: System specific settings +# Author: Andrew@DeFaria.com +# Created: Mon Aug 20 17:35:01 2001 +# Modified: $Date: 2010/06/11 20:42:23 $ +# Language: bash +# +# (c) Copyright 2000-2005, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +# This system's name +export SYSNAME=$(uname -n) + +# Strip domains +SYSNAME=${SYSNAME%%\.*} + +# Set to initial cap +SYSNAME=$(echo ${SYSNAME:0:1} | tr [:lower:] [:upper:])$(echo ${SYSNAME:1} | tr [:upper:] [:lower:]) diff --git a/rc/toprc b/rc/toprc new file mode 100644 index 0000000..0fe95b3 --- /dev/null +++ b/rc/toprc @@ -0,0 +1,14 @@ +RCfile for "top with windows" # shameless braggin' +Id:a, Mode_altscr=0, Mode_irixps=1, Delay_time=3.000, Curwin=0 +Def fieldscur=AEHIOQTWKNMbcdfgjplrsuvyzX + winflags=64824, sortindx=10, maxtasks=0 + summclr=6, msgsclr=1, headclr=2, taskclr=3 +Job fieldscur=ABcefgjlrstuvyzMKNHIWOPQDX + winflags=62777, sortindx=0, maxtasks=0 + summclr=6, msgsclr=6, headclr=7, taskclr=6 +Mem fieldscur=ANOPQRSTUVbcdefgjlmyzWHIKX + winflags=62777, sortindx=13, maxtasks=0 + summclr=5, msgsclr=5, headclr=4, taskclr=5 +Usr fieldscur=ABDECGfhijlopqrstuvyzMKNWX + winflags=62777, sortindx=4, maxtasks=0 + summclr=3, msgsclr=3, headclr=2, taskclr=3 diff --git a/rc/vimrc b/rc/vimrc new file mode 100644 index 0000000..188debf --- /dev/null +++ b/rc/vimrc @@ -0,0 +1,8 @@ +set background=light +set autoindent +set autowrite +syntax enable +set nocompatible +set backspace=indent,eol,start +colorscheme evening +map!   diff --git a/rc/vueprofile b/rc/vueprofile new file mode 100644 index 0000000..9ecf7f2 --- /dev/null +++ b/rc/vueprofile @@ -0,0 +1,2 @@ +export VUE=true +. $HOME/.profile \ No newline at end of file diff --git a/rc/xemacs/clearcase.el b/rc/xemacs/clearcase.el new file mode 100644 index 0000000..f5e1fcc --- /dev/null +++ b/rc/xemacs/clearcase.el @@ -0,0 +1,7970 @@ +;;; clearcase.el --- ClearCase/Emacs integration. + +;; Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 Kevin Esler + +;; Author: Kevin Esler +;; Maintainer: Kevin Esler +;; Keywords: clearcase tools +;; Web home: http://members.verizon.net/~vze24fr2/EmacsClearCase + +;; This file is not part of GNU Emacs. +;; +;; This program is free software; you can redistribute it and/or modify it under +;; the terms of the GNU General Public License as published by the Free Software +;; Foundation; either version 2, or (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, but WITHOUT +;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +;; FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +;; details. + +;; You should have received a copy of the GNU General Public License along with +;; GNU Emacs; see the file COPYING. If not, write to the Free Software +;; Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +;;{{{ Introduction + +;; This is a ClearCase/Emacs integration. +;; +;; +;; How to use +;; ========== +;; +;; 0. Make sure you're using Gnu Emacs-20.4 or later or a recent XEmacs. +;; In general it seems to work better in Gnu Emacs than in XEmacs, +;; although many XEmacs users have no problems at all with it. +;; +;; 1. Make sure that you DON'T load old versions of vc-hooks.el which contain +;; incompatible versions of the tq package (functions tq-enqueue and +;; friends). In particular, Bill Sommerfeld's VC/CC integration has this +;; problem. +;; +;; 2. Copy the files (or at least the clearcase.elc file) to a directory +;; on your emacs-load-path. +;; +;; 3. Insert this in your emacs startup file: (load "clearcase") +;; +;; When you begin editing in any view-context, a ClearCase menu will appear +;; and ClearCase Minor Mode will be activated for you. +;; +;; Summary of features +;; =================== +;; +;; Keybindings compatible with Emacs' VC (where it makes sense) +;; Richer interface than VC +;; Works on NT and Unix +;; Context sensitive menu (Emacs knows the ClearCase-status of files) +;; Snapshot view support: update, version comparisons +;; Can use Emacs Ediff for version comparison display +;; Dired Mode: +;; - en masse checkin/out etc +;; - enhanced display +;; - browse version tree +;; Completion of viewnames, version strings +;; Auto starting of views referenced as /view/TAG/.. (or \\view\TAG\...) +;; Emacs for editing comments, config specs +;; Standard ClearCase GUI tools launchable from Emacs menu +;; - version tree browser +;; - project browser +;; - UCM deliver +;; - UCM rebase +;; Operations directly available from Emacs menu/keymap: +;; create-activity +;; set-activity +;; mkelem, +;; checkout +;; checkin, +;; unco, +;; describe +;; list history +;; edit config spec +;; mkbrtype +;; snapshot view update: file, directory, view +;; version comparisons using ediff, diff or GUI +;; find checkouts +;; annotate version +;; et al. +;; +;; Acknowledgements +;; ================ +;; +;; The help of the following is gratefully acknowledged: +;; +;; XEmacs support and other bugfixes: +;; +;; Rod Whitby +;; Adrian Aichner +;; +;; This was a result of examining earlier versions of VC and VC/ClearCase +;; integrations and borrowing freely therefrom. Accordingly, the following +;; are ackowledged as contributors: +;; +;; VC/ClearCase integration authors: +;; +;; Bill Sommerfeld +;; Rod Whitby +;; Andrew Markebo +;; Andy Eskilsson +;; Paul Smith +;; John Kohl +;; Chris Felaco +;; +;; VC authors: +;; +;; Eric S. Raymond +;; Andre Spiegel +;; Sebastian Kremer +;; Richard Stallman +;; Per Cederqvist +;; ttn@netcom.com +;; Andre Spiegel +;; Jonathan Stigelman +;; Steve Baur +;; +;; Other Contributors: +;; +;; Alastair Rankine +;; Andrew Maguire +;; Barnaby Dalton +;; Christian Savard +;; David O'Shea +;; Dee Zsombor +;; Gabor Zoka +;; Jason Rumney +;; Jeff Phillips +;; Justin Vallon +;; Mark Collins +;; Patrik Madison +;; Ram Bhamidipaty +;; Reinhard Hahn +;; Richard Kim +;; Richard Y. Kim +;; Simon Graham +;; Stephen Leake +;; Steven E. Harris +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;;}}} + +;;{{{ Version info + +(defconst clearcase-version-stamp "ClearCase-version: ") +(defconst clearcase-version (substring clearcase-version-stamp 19)) + +(defun clearcase-maintainer-address () + ;; Avoid spam. + ;; + (concat "kevin.esler.1989" + "@" + "alum.bu.edu")) + +(defun clearcase-submit-bug-report () + "Submit via mail a bug report on ClearCase Mode" + (interactive) + (and (y-or-n-p "Do you really want to submit a report on ClearCase Mode ? ") + (reporter-submit-bug-report + (clearcase-maintainer-address) + (concat "clearcase.el " clearcase-version) + '( + system-type + system-configuration + emacs-version + clearcase-clearcase-version-installed + clearcase-cleartool-path + clearcase-lt + clearcase-v3 + clearcase-v4 + clearcase-v5 + clearcase-v6 + clearcase-servers-online + clearcase-disable-tq + clearcase-on-cygwin + clearcase-setview-root + clearcase-suppress-vc-within-mvfs + shell-file-name + w32-quote-process-args + )))) + +;;}}} + +;;{{{ Macros + +(defmacro clearcase-when-debugging (&rest forms) + (list 'if 'clearcase-debug (cons 'progn forms))) + +(defmacro clearcase-with-tempfile (filename-var &rest forms) + `(let ((,filename-var (clearcase-utl-tempfile-name))) + (unwind-protect + ,@forms + + ;; Cleanup. + ;; + (if (file-exists-p ,filename-var) + (delete-file ,filename-var))))) + +;;}}} + +;;{{{ Portability + +(defvar clearcase-xemacs-p (string-match "XEmacs" emacs-version)) + +(defvar clearcase-on-mswindows (memq system-type + '(windows-nt ms-windows cygwin cygwin32))) + +(defvar clearcase-on-cygwin (memq system-type '(cygwin cygwin32))) + +(defvar clearcase-sink-file-name + (cond + (clearcase-on-cygwin "/dev/null") + (clearcase-on-mswindows "NUL") + (t "/dev/null"))) + +(defun clearcase-view-mode-quit (buf) + "Exit from View mode, restoring the previous window configuration." + (progn + (cond ((frame-property (selected-frame) 'clearcase-view-window-config) + (set-window-configuration + (frame-property (selected-frame) 'clearcase-view-window-config)) + (set-frame-property (selected-frame) 'clearcase-view-window-config nil)) + ((not (one-window-p)) + (delete-window))) + (kill-buffer buf))) + +(defun clearcase-view-mode (arg &optional camefrom) + (if clearcase-xemacs-p + (let* ((winconfig (current-window-configuration)) + (was-one-window (one-window-p)) + (buffer-name (buffer-name (current-buffer))) + (clearcase-view-not-visible + (not (and (windows-of-buffer buffer-name) ;shortcut + (memq (selected-frame) + (mapcar 'window-frame + (windows-of-buffer buffer-name))))))) + (when clearcase-view-not-visible + (set-frame-property (selected-frame) + 'clearcase-view-window-config winconfig)) + (view-mode camefrom 'clearcase-view-mode-quit) + (setq buffer-read-only nil)) + (view-mode arg))) + +(defun clearcase-port-view-buffer-other-window (buffer) + (if clearcase-xemacs-p + (switch-to-buffer-other-window buffer) + (view-buffer-other-window buffer nil 'kill-buffer))) + +(defun clearcase-dired-sort-by-date () + (if (fboundp 'dired-sort-by-date) + (dired-sort-by-date))) + +;; Copied from emacs-20 +;; +(if (not (fboundp 'subst-char-in-string)) + (defun subst-char-in-string (fromchar tochar string &optional inplace) + "Replace FROMCHAR with TOCHAR in STRING each time it occurs. +Unless optional argument INPLACE is non-nil, return a new string." + (let ((i (length string)) + (newstr (if inplace string (copy-sequence string)))) + (while (> i 0) + (setq i (1- i)) + (if (eq (aref newstr i) fromchar) + (aset newstr i tochar))) + newstr))) + +;;}}} + +;;{{{ Require calls + +;; nyi: we also use these at the moment: +;; -view +;; -ediff +;; -view +;; -dired-sort + +(require 'cl) +(require 'comint) +(require 'dired) +(require 'easymenu) +(require 'executable) +(require 'reporter) +(require 'ring) +(or clearcase-xemacs-p + (require 'timer)) + +;; NT Emacs - doesn't use tq. +;; +(if (not clearcase-on-mswindows) + (require 'tq)) + +;;}}} + +;;{{{ Debugging facilities + +;; Setting this to true will enable some debug code. +;; +(defvar clearcase-debug nil) + +(defun clearcase-trace (string) + (clearcase-when-debugging + (let ((trace-buf (get-buffer "*clearcase-trace*"))) + (if trace-buf + (save-excursion + (set-buffer trace-buf) + (goto-char (point-max)) + (insert string "\n")))))) + +(defun clearcase-enable-tracing () + (interactive) + (setq clearcase-debug t) + (get-buffer-create "*clearcase-trace*")) + +(defun clearcase-disable-tracing () + (interactive) + (setq clearcase-debug nil)) + +(defun clearcase-dump () + (interactive) + (clearcase-utl-populate-and-view-buffer + "*clearcase-dump*" + nil + (function (lambda () + (clearcase-fprop-dump-to-current-buffer) + (clearcase-vprop-dump-to-current-buffer))))) + +(defun clearcase-flush-caches () + (interactive) + (clearcase-fprop-clear-all-properties) + (clearcase-vprop-clear-all-properties)) + +;;}}} + +;;{{{ Customizable variables + +(eval-and-compile + (condition-case nil + (require 'custom) + (error nil)) + (if (and (featurep 'custom) + (fboundp 'custom-declare-variable)) + nil ;; We've got what we needed + ;; We have the old custom-library, hack around it! + (defmacro defgroup (&rest args) + nil) + (defmacro defcustom (var value doc &rest args) + (` (defvar (, var) (, value) (, doc)))) + (defmacro defface (face value doc &rest stuff) + `(make-face ,face)) + (defmacro custom-declare-variable (symbol value doc &rest args) + (list 'defvar (eval symbol) value doc)))) + +(defgroup clearcase () "ClearCase Options" :group 'tools :prefix "clearcase") + +(defcustom clearcase-keep-uncheckouts t + "When true, the contents of an undone checkout will be kept in a file +with a \".keep\" suffix. Otherwise it will be removed." + :group 'clearcase + :type 'boolean) + +(defcustom clearcase-keep-unhijacks t + "When true, the contents of an undone hijack will be kept in a file +with a \".keep\" suffix. Otherwise it will be removed." + :group 'clearcase + :type 'boolean) + +;; nyi: We could also allow a value of 'prompt here +;; +(defcustom clearcase-set-to-new-activity t + "*If this variable is non-nil when a new activity is created, that activity +will be set as the current activity for the view, otherwise no change is made +to the view's current activity setting." + :group 'clearcase + :type 'boolean) + +(defcustom clearcase-prompt-for-activity-names t + "*If this variable is non-nil the user will be prompted for activity names. +Otherwise, activity names will be generated automatically and will typically +have the form \"activity011112.155233\". If the name entered is empty sucn an +internal name will also be generated." + :group 'clearcase + :type 'boolean) + +(defcustom clearcase-make-backup-files nil + "*If non-nil, backups of ClearCase files are made as with other files. +If nil (the default), files under ClearCase control don't get backups." + :group 'clearcase + :type 'boolean) + +(defcustom clearcase-complete-viewtags t + "*If non-nil, completion on viewtags is enabled. For sites with thousands of view +this should be set to nil." + :group 'clearcase + :type 'boolean) + +(defcustom clearcase-minimise-menus nil + "*If non-nil, menus will hide rather than grey-out inapplicable choices." + :group 'clearcase + :type 'boolean) + +(defcustom clearcase-auto-dired-mode t + "*If non-nil, automatically enter `clearcase-dired-mode' in dired-mode +for directories in ClearCase." + :group 'clearcase + :type 'boolean) + +(defcustom clearcase-dired-highlight t + "If non-nil, highlight reserved files in clearcase-dired buffers." + :group 'clearcase + :type 'boolean) + +(defcustom clearcase-dired-show-view t + "If non-nil, show the view tag in dired buffers." + :group 'clearcase + :type 'boolean) + +(defcustom clearcase-verify-pre-mkelem-dir-checkout nil + "*If non-nil, prompt before checking out the containing directory +before creating a new ClearCase element." + :group 'clearcase + :type 'boolean) + +(defcustom clearcase-diff-on-checkin nil + "Display diff on checkin to help you compose the checkin comment." + :group 'clearcase + :type 'boolean) + +;; General customization + +(defcustom clearcase-suppress-confirm nil + "If non-nil, treat user as expert; suppress yes-no prompts on some things." + :group 'clearcase + :type 'boolean) + +(defcustom clearcase-initial-mkelem-comment nil + "Prompt for initial comment when an element is created." + :group 'clearcase + :type 'boolean) + +(defcustom clearcase-command-messages nil + "Display run messages from back-end commands." + :group 'clearcase + :type 'boolean) + +(defcustom clearcase-checkin-arguments + ;; For backwards compatibility with old name for this variable: + ;; + (if (and (boundp 'clearcase-checkin-switches) + (not (null clearcase-checkin-switches))) + (list clearcase-checkin-switches) + nil) + "A list of extra arguments passed to the checkin command." + :group 'clearcase + :type '(repeat (string :tag "Argument"))) + +(defcustom clearcase-checkin-on-mkelem nil + "If t, file will be checked-in when first created as an element." + :group 'clearcase + :type 'boolean) + +(defcustom clearcase-suppress-checkout-comments nil + "Suppress prompts for checkout comments for those version control +systems which use them." + :group 'clearcase + :type 'boolean) + +(defcustom clearcase-checkout-arguments + ;; For backwards compatibility with old name for this variable: + ;; + (if (and (boundp 'clearcase-checkout-arguments) + (not (null clearcase-checkout-arguments))) + (list clearcase-checkout-arguments) + nil) + "A list of extra arguments passed to the checkout command." + :group 'clearcase + :type '(repeat (string :tag "Argument"))) + +(defcustom clearcase-directory-exclusion-list '("lost+found") + "Directory names ignored by functions that recursively walk file trees." + :group 'clearcase + :type '(repeat (string :tag "Subdirectory"))) + +(defcustom clearcase-use-normal-diff nil + "If non-nil, use normal diff instead of cleardiff." + :group 'clearcase + :type 'boolean) + +(defcustom clearcase-normal-diff-program "diff" + "*Program to use for generating the differential of the two files +when `clearcase-use-normal-diff' is t." + :group 'clearcase + :type 'string) + +(defcustom clearcase-normal-diff-arguments + (if (and (boundp 'clearcase-normal-diff-switches) + (not (null clearcase-normal-diff-switches))) + (list clearcase-normal-diff-switches) + (list "-u")) + "A list of extra arguments passed to `clearcase-normal-diff-program' +when `clearcase-use-normal-diff' is t. Usage of the -u switch is +recommended to produce unified diffs, when your +`clearcase-normal-diff-program' supports it." + :group 'clearcase + :type '(repeat (string :tag "Argument"))) + +(defcustom clearcase-vxpath-glue "@@" + "The string used to construct version-extended pathnames." + :group 'clearcase + :type 'string) + +(defcustom clearcase-viewroot (if clearcase-on-mswindows + "//view" + "/view") + "The ClearCase viewroot directory." + :group 'clearcase + :type 'file) + +(defcustom clearcase-viewroot-drive "m:" + "The ClearCase viewroot drive letter for Windows." + :group 'clearcase + :type 'string) + +(defcustom clearcase-suppress-vc-within-mvfs t + "Suppresses VC activity within the MVFS." + :group 'clearcase + :type 'boolean) + +(defcustom clearcase-hide-rebase-activities t + "Hide rebase activities from activity selection list." + :group 'clearcase + :type 'boolean) + +(defcustom clearcase-rebase-id-regexp "^rebase\\." + "The regexp used to detect rebase actvities." + :group 'clearcase + :type 'string) + +;;}}} + +;;{{{ Global variables + +;; Initialize clearcase-pname-sep-regexp according to +;; directory-sep-char. +(defvar clearcase-pname-sep-regexp + (format "[%s/]" + (char-to-string directory-sep-char))) + +(defvar clearcase-non-pname-sep-regexp + (format "[^%s/]" + (char-to-string directory-sep-char))) + +;; Matches any viewtag (without the trailing "/"). +;; +(defvar clearcase-viewtag-regexp + (concat "^" + clearcase-viewroot + clearcase-pname-sep-regexp + "\\(" + clearcase-non-pname-sep-regexp "*" + "\\)" + "$" + )) + +;; Matches ANY viewroot-relative path +;; +(defvar clearcase-vrpath-regexp + (concat "^" + clearcase-viewroot + clearcase-pname-sep-regexp + "\\(" + clearcase-non-pname-sep-regexp "*" + "\\)" + )) + +;;}}} + +;;{{{ Minor Mode: ClearCase + +;; For ClearCase Minor Mode +;; +(defvar clearcase-mode nil) +(set-default 'clearcase-mode nil) +(make-variable-buffer-local 'clearcase-mode) +(put 'clearcase-mode 'permanent-local t) + +;; Tell Emacs about this new kind of minor mode +;; +(if (not (assoc 'clearcase-mode minor-mode-alist)) + (setq minor-mode-alist (cons '(clearcase-mode clearcase-mode) + minor-mode-alist))) + +;; For now we override the bindings for VC Minor Mode with ClearCase Minor Mode +;; bindings. +;; +(defvar clearcase-mode-map (make-sparse-keymap)) +(defvar clearcase-prefix-map (make-sparse-keymap)) +(define-key clearcase-mode-map "\C-xv" clearcase-prefix-map) +(define-key clearcase-mode-map "\C-x\C-q" 'clearcase-toggle-read-only) + +(define-key clearcase-prefix-map "b" 'clearcase-browse-vtree-current-buffer) +(define-key clearcase-prefix-map "c" 'clearcase-uncheckout-current-buffer) +(define-key clearcase-prefix-map "e" 'clearcase-edcs-edit) +(define-key clearcase-prefix-map "g" 'clearcase-annotate-current-buffer) +(define-key clearcase-prefix-map "i" 'clearcase-mkelem-current-buffer) +(define-key clearcase-prefix-map "l" 'clearcase-list-history-current-buffer) +(define-key clearcase-prefix-map "m" 'clearcase-mkbrtype) +(define-key clearcase-prefix-map "u" 'clearcase-uncheckout-current-buffer) +(define-key clearcase-prefix-map "v" 'clearcase-next-action-current-buffer) +(define-key clearcase-prefix-map "w" 'clearcase-what-rule-current-buffer) +(define-key clearcase-prefix-map "=" 'clearcase-diff-pred-current-buffer) +(define-key clearcase-prefix-map "?" 'clearcase-describe-current-buffer) +(define-key clearcase-prefix-map "~" 'clearcase-version-other-window) + +;; To avoid confusion, we prevent VC Mode from being active at all by +;; undefining its keybindings for which ClearCase Mode doesn't yet have an +;; analogue. +;; +(define-key clearcase-prefix-map "a" 'undefined) ;; vc-update-change-log +(define-key clearcase-prefix-map "d" 'undefined) ;; vc-directory +(define-key clearcase-prefix-map "h" 'undefined) ;; vc-insert-headers +(define-key clearcase-prefix-map "m" 'undefined) ;; vc-merge +(define-key clearcase-prefix-map "r" 'undefined) ;; vc-retrieve-snapshot +(define-key clearcase-prefix-map "s" 'undefined) ;; vc-create-snapshot +(define-key clearcase-prefix-map "t" 'undefined) ;; vc-dired-toggle-terse-mode + +;; Associate the map and the minor mode +;; +(or (not (boundp 'minor-mode-map-alist)) + (assq 'clearcase-mode (symbol-value 'minor-mode-map-alist)) + (setq minor-mode-map-alist + (cons (cons 'clearcase-mode clearcase-mode-map) + minor-mode-map-alist))) + +(defun clearcase-mode (&optional arg) + "ClearCase Minor Mode" + + (interactive "P") + + ;; Behave like a proper minor-mode. + ;; + (setq clearcase-mode + (if (interactive-p) + (if (null arg) + (not clearcase-mode) + + ;; Check if the numeric arg is positive. + ;; + (> (prefix-numeric-value arg) 0)) + + ;; else + ;; Use the car if it's a list. + ;; + (if (consp arg) + (setq arg (car arg))) + (if (symbolp arg) + (if (null arg) + (not clearcase-mode) ;; toggle mode switch + (not (eq '- arg))) ;; True if symbol is not '- + + ;; else + ;; assume it's a number and check that. + ;; + (> arg 0)))) + + (if clearcase-mode + (easy-menu-add clearcase-menu 'clearcase-mode-map)) + ) + +;;}}} + +;;{{{ Minor Mode: ClearCase Dired + +;;{{{ Reformatting the Dired buffer + +;; Create a face for highlighting checked out files in clearcase-dired. +;; +(if (not (memq 'clearcase-dired-checkedout-face (face-list))) + (progn + (make-face 'clearcase-dired-checkedout-face) + (set-face-foreground 'clearcase-dired-checkedout-face "red"))) + +(defun clearcase-dired-insert-viewtag () + (save-excursion + (progn + (goto-char (point-min)) + + ;; Only do this if the buffer is not currently narrowed + ;; + (if (= 1 (point)) + (let ((viewtag (clearcase-fprop-viewtag (file-truename default-directory)))) + (if viewtag + (progn + (forward-line 1) + (let ((buffer-read-only nil)) + (insert (format " [ClearCase View: %s]\n" viewtag)))))))))) + +(defun clearcase-dired-reformat-buffer () + "Reformats the current dired buffer." + (let* ((checkout-list nil) + (modified-file-info nil) + (hijack-list nil) + (directory default-directory) + subdir + fullpath) + + ;; Iterate over each line in the buffer. + ;; + ;; Important notes: + ;; 1. In general, a Dired buffer can contain listings for several + ;; directories. We pass though from top to bottom and adjust + ;; subdir as we go. + ;; 2. Since this is called from dired-after-reading-hook, it can get + ;; called on a single-line buffer. In this case there is no subdir, + ;; and no checkout-list. We need to call clearcase-fprop-checked-out + ;; to test for a checkout. + ;; + (save-excursion + (goto-char (point-min)) + (while (not (eobp)) + (cond + + ;; Case 1: Look for directory markers + ;; + ((setq subdir (dired-get-subdir)) + + ;; We're at a subdirectory line in the dired buffer. + ;; Go and list all checkouts and hijacks in this subdirectory. + ;; + (setq modified-file-info (clearcase-dired-list-modified-files subdir)) + (setq checkout-list (nth 0 modified-file-info)) + (setq hijack-list (nth 1 modified-file-info)) + + ;; If no checkouts are found, we don't need to check each file, and + ;; it's very slow. The checkout-list should contain something so it + ;; doesn't attempt to do this. + ;; + (if (null checkout-list) + (setq checkout-list '(nil))) + (if (null hijack-list) + (setq hijack-list '(nil))) + (message "Reformatting %s..." subdir)) + + ;; Case 2: Look for files (the safest way to get the filename). + ;; + ((setq fullpath (dired-get-filename nil t)) + + ;; Expand it to get rid of . and .. entries. + ;; + (setq fullpath (expand-file-name fullpath)) + + (setq fullpath (clearcase-path-canonicalise-slashes fullpath)) + + ;; Only modify directory listings of the correct format. + ;; We replace the GID field with a checkout indicator. + ;; + (if (looking-at + ;; (1) (2) (3) (4) + ;; -rw-rw-rw- 1 esler 5 28 Feb 2 16:02 foo.el + "..\\([drwxlts-]+ \\) *\\([0-9]+\\) \\([^ ]+\\) *\\([^ ]+ *\\) +[0-9]+\\( [^ 0-9]+ [0-9 ][0-9] .*\\)") + + (let* ((replacement-begin (match-beginning 4)) + (replacement-end (match-end 4)) + + (replacement-length (- replacement-end replacement-begin)) + (checkout-replacement-text (format "CHECKOUT")) + (hijack-replacement-text (format "HIJACK")) + (is-checkout (if checkout-list + (member fullpath checkout-list) + (clearcase-fprop-checked-out fullpath))) + (is-hijack (if hijack-list + (member fullpath hijack-list) + (clearcase-fprop-hijacked fullpath)))) + + ;; Highlight the line if the file is checked-out. + ;; + (if is-checkout + (progn + ;; Replace the GID field with CHECKOUT. + ;; + (let ((buffer-read-only nil)) + + ;; Pad with replacement text with trailing spaces if necessary. + ;; + (if (>= replacement-length (length checkout-replacement-text)) + (setq checkout-replacement-text + (concat checkout-replacement-text + (make-string (- replacement-length (length checkout-replacement-text)) + 32)))) + (goto-char replacement-begin) + (delete-char replacement-length) + (insert (substring checkout-replacement-text 0 replacement-length))) + + ;; Highlight the checked out files. + ;; + (if (fboundp 'put-text-property) + (let ((buffer-read-only nil)) + (put-text-property replacement-begin replacement-end + 'face 'clearcase-dired-checkedout-face))) + ) + ) + + (if is-hijack + (progn + ;; Replace the GID field with CHECKOUT. + ;; + (let ((buffer-read-only nil)) + + ;; Pad with replacement text with trailing spaces if necessary. + ;; + (if (>= replacement-length (length hijack-replacement-text)) + (setq hijack-replacement-text + (concat hijack-replacement-text + (make-string (- replacement-length (length hijack-replacement-text)) + 32)))) + (goto-char replacement-begin) + (delete-char replacement-length) + (insert (substring hijack-replacement-text 0 replacement-length))) + + ;; Highlight the checked out files. + ;; + (if (fboundp 'put-text-property) + (let ((buffer-read-only nil)) + (put-text-property replacement-begin replacement-end + 'face 'clearcase-dired-checkedout-face))) + ) + ) + + )))) + (forward-line 1)))) + (message "Reformatting...Done")) + + +(defun clearcase-path-follow-if-vob-slink (path) + (if (clearcase-fprop-file-is-vob-slink-p path) + + ;; It's a slink so follow it. + ;; + (let ((slink-text (clearcase-fprop-vob-slink-text path))) + (if (file-name-absolute-p slink-text) + slink-text + (concat (file-name-directory path) slink-text))) + + ;; Not an slink. + ;; + path)) + +;;{{{ Searching for modified files + +;;{{{ Old code + +;; (defun clearcase-dired-list-checkouts (directory) +;; "Returns a list of files checked-out to the current view in DIRECTORY." + +;; ;; Don't bother looking for checkouts in +;; ;; - a history-mode branch-qua-directory +;; ;; - a view-private directory +;; ;; +;; ;; NYI: For now don't run lsco in root of a snapshot because it gives errors. +;; ;; We need to make this smarter. +;; ;; +;; ;; NYI: For a pathname which is a slink to a dir, despite the fact that +;; ;; clearcase-fprop-file-is-version-p returns true, lsco fails on it, +;; ;; with "not an element". Sheesh, surely lsco ought to follow links ? +;; ;; Solution: catch the error and check if the dir is a slink then follow +;; ;; the link and retry the lsco on the target. +;; ;; +;; ;; For now just ignore the error. +;; ;; +;; (if (and (not (clearcase-vxpath-p directory)) +;; (not (eq 'view-private-object (clearcase-fprop-mtype directory))) +;; (clearcase-fprop-file-is-version-p directory)) + + +;; (let* ((ignore (message "Listing ClearCase checkouts...")) + +;; (true-dir-path (file-truename directory)) + +;; ;; Give the directory as an argument so all names will be +;; ;; fullpaths. For some reason ClearCase adds an extra slash if you +;; ;; leave the trailing slash on the directory, so we need to remove +;; ;; it. +;; ;; +;; (native-dir-path (clearcase-path-native (directory-file-name true-dir-path))) + +;; (followed-dir-path (clearcase-path-follow-if-vob-slink native-dir-path)) + +;; ;; Form the command: +;; ;; +;; (cmd (list +;; "lsco" "-cview" "-fmt" +;; (if clearcase-on-mswindows +;; "%n\\n" +;; "'%n\\n'") + +;; followed-dir-path)) + +;; ;; Capture the output: +;; ;; +;; (string (clearcase-path-canonicalise-slashes +;; (apply 'clearcase-ct-cleartool-cmd cmd))) + +;; ;; Split the output at the newlines: +;; ;; +;; (checkout-list (clearcase-utl-split-string-at-char string ?\n))) + +;; ;; Add entries for "." and ".." if they're checked-out. +;; ;; +;; (let* ((entry ".") +;; (path (expand-file-name (concat (file-name-as-directory true-dir-path) +;; entry)))) +;; (if (clearcase-fprop-checked-out path) +;; (setq checkout-list (cons path checkout-list)))) +;; (let* ((entry "..") +;; (path (expand-file-name (concat (file-name-as-directory true-dir-path) +;; entry)))) +;; (if (clearcase-fprop-checked-out path) +;; (setq checkout-list (cons path checkout-list)))) + +;; ;; If DIRECTORY is a vob-slink, checkout list will contain pathnames +;; ;; relative to the vob-slink target rather than to DIRECTORY. Convert +;; ;; them back here. We're making it appear that lsco works on +;; ;; slinks-to-dirs. +;; ;; +;; (if (clearcase-fprop-file-is-vob-slink-p true-dir-path) +;; (let ((re (regexp-quote (file-name-as-directory followed-dir-path)))) +;; (setq checkout-list +;; (mapcar +;; (function +;; (lambda (path) +;; (replace-regexp-in-string re true-dir-path path))) +;; checkout-list)))) + +;; (message "Listing ClearCase checkouts...done") + +;; ;; Return the result. +;; ;; +;; checkout-list) +;; )) + +;; ;; I had believed that this implementation below OUGHT to be faster, having +;; ;; read the code in "ct+lsco". It seemed that "lsco -cview" hit the VOB and +;; ;; listed all checkouts on all elements in the directory, and then filtered by +;; ;; view. I thought it would probably be quicker to run "ct ls -vob_only" and +;; ;; keep the lines that have "[eclipsed by checkout]". However this code +;; ;; actually seemed to run slower. Leave the code here for now so I can test +;; ;; further. +;; ;; +;; (defun clearcase-dired-list-checkouts-experimental (directory) +;; "Returns a list of files checked-out to the current view in DIRECTORY." + +;; ;; Don't bother looking for checkouts in a history-mode listing +;; ;; nor in view-private directories. +;; ;; +;; (if (and (not (clearcase-vxpath-p directory)) +;; (not (eq 'view-private-object (clearcase-fprop-mtype directory)))) + +;; (let* ((ignore (message "Listing ClearCase checkouts...")) + +;; (true-directory (file-truename directory)) + +;; ;; Move temporarily to the directory: +;; ;; +;; (default-directory true-directory) + +;; ;; Form the command: +;; ;; +;; (cmd (list "ls" "-vob_only")) + +;; ;; Capture the output: +;; ;; +;; (string (clearcase-path-canonicalise-slashes +;; (apply 'clearcase-ct-cleartool-cmd cmd))) + +;; ;; Split the output at the newlines: +;; ;; +;; (line-list (clearcase-utl-split-string-at-char string ?\n)) + +;; (checkout-list nil)) + +;; ;; Look for lines of the form: +;; ;; FILENAME@@ [eclipsed by checkout] +;; ;; +;; (mapcar (function +;; (lambda (line) +;; (if (string-match "^\\([^ @]+\\)@@ +\\[eclipsed by checkout\\].*" line) +;; (setq checkout-list (cons (concat +;; ;; Add back directory name to get +;; ;; full pathname. +;; ;; +;; default-directory +;; (substring line +;; (match-beginning 1) +;; (match-end 1))) +;; checkout-list))))) +;; line-list) + +;; ;; Add entries for "." and ".." if they're checked-out. +;; ;; +;; (let* ((entry ".") +;; (path (expand-file-name (concat true-directory entry)))) +;; (if (clearcase-fprop-checked-out path) +;; (setq checkout-list (cons path checkout-list)))) +;; (let* ((entry "..") +;; (path (expand-file-name (concat true-directory entry)))) +;; (if (clearcase-fprop-checked-out path) +;; (setq checkout-list (cons path checkout-list)))) + +;; (message "Listing ClearCase checkouts...done") + +;; ;; Return the result. +;; ;; +;; checkout-list))) + +;; (defun clearcase-dired-list-hijacks (directory) +;; "Returns a list of files hijacked to the current view in DIRECTORY." + +;; ;; Don't bother looking for hijacks in; +;; ;; - a history-mode listing +;; ;; - a in view-private directory +;; ;; - a dynamic view +;; ;; +;; (let* ((true-directory (file-truename directory)) +;; (viewtag (clearcase-fprop-viewtag true-directory))) + +;; (if (and viewtag +;; (not (clearcase-vxpath-p directory)) +;; (not (eq 'view-private-object (clearcase-fprop-mtype directory))) +;; (clearcase-file-would-be-in-snapshot-p true-directory)) + +;; (let* ((ignore (message "Listing ClearCase hijacks...")) + +;; (true-directory (file-truename directory)) + +;; ;; Form the command: +;; ;; +;; (cmd (list +;; "ls" + +;; ;; Give the directory as an argument so all names will be +;; ;; fullpaths. For some reason ClearCase adds an extra slash +;; ;; if you leave the trailing slash on the directory, so we +;; ;; need to remove it. +;; ;; +;; (clearcase-path-native (directory-file-name true-directory)))) + +;; ;; Capture the output: +;; ;; +;; (string (clearcase-path-canonicalise-slashes +;; (apply 'clearcase-ct-cleartool-cmd cmd))) + +;; ;; Split the output at the newlines: +;; ;; +;; (line-list (clearcase-utl-split-string-at-char string ?\n)) + +;; (hijack-list nil)) + +;; (mapcar (function +;; (lambda (line) +;; (if (string-match "^\\([^ @]+\\)@@[^ ]+ \\[hijacked\\].*" line) +;; (setq hijack-list (cons (substring line +;; (match-beginning 1) +;; (match-end 1)) +;; hijack-list))))) +;; line-list) + +;; (message "Listing ClearCase hijacks...done") + +;; ;; Return the result. +;; ;; +;; hijack-list)))) + +;;}}} + +(defun clearcase-dired-list-modified-files (directory) + "Returns a pair of lists of files (checkouts . hijacks) to the current view in DIRECTORY." + + ;; Don't bother looking for hijacks in; + ;; - a history-mode listing + ;; - a in view-private directory + ;; - a dynamic view + ;; + (let* ((true-directory (file-truename directory)) + (viewtag (clearcase-fprop-viewtag true-directory)) + (snapshot (clearcase-file-would-be-in-snapshot-p true-directory)) + (result '(() ()))) + + (if (and viewtag + (not (clearcase-vxpath-p directory)) + (not (eq 'view-private-object (clearcase-fprop-mtype directory)))) + + (let* ((ignore (message "Listing ClearCase modified files...")) + + (true-directory (file-truename directory)) + + ;; Form the command: + ;; + (cmd (list + "ls" + + ;; Give the directory as an argument so all names will be + ;; fullpaths. For some reason ClearCase adds an extra slash + ;; if you leave the trailing slash on the directory, so we + ;; need to remove it. + ;; + (clearcase-path-native (directory-file-name true-directory)))) + + ;; Capture the output: + ;; + (string (clearcase-path-canonicalise-slashes + (apply 'clearcase-ct-cleartool-cmd cmd))) + + ;; Split the output at the newlines: + ;; + (line-list (clearcase-utl-split-string-at-char string ?\n)) + + (hijack-list nil) + (checkout-list nil)) + + (mapcar (function + (lambda (line) + (if (string-match "^\\([^ @]+\\)@@[^ ]+ \\[hijacked\\].*" line) + (setq hijack-list (cons (substring line + (match-beginning 1) + (match-end 1)) + hijack-list))) + (if (string-match "^\\([^ @]+\\)@@.+CHECKEDOUT from .*" line) + (setq checkout-list (cons (substring line + (match-beginning 1) + (match-end 1)) + checkout-list))))) + line-list) + + (message "Listing ClearCase modified files...done") + + ;; Return the result. + ;; + (setq result (list checkout-list hijack-list)))) + result)) + +;;}}} + +;;}}} + +;; For ClearCase Dired Minor Mode +;; +(defvar clearcase-dired-mode nil) +(set-default 'clearcase-dired-mode nil) +(make-variable-buffer-local 'clearcase-dired-mode) + +;; Tell Emacs about this new kind of minor mode +;; +(if (not (assoc 'clearcase-dired-mode minor-mode-alist)) + (setq minor-mode-alist (cons '(clearcase-dired-mode clearcase-dired-mode) + minor-mode-alist))) + +;; For now we override the bindings for VC Minor Mode with ClearCase Dired +;; Minor Mode bindings. +;; +(defvar clearcase-dired-mode-map (make-sparse-keymap)) +(defvar clearcase-dired-prefix-map (make-sparse-keymap)) +(define-key clearcase-dired-mode-map "\C-xv" clearcase-dired-prefix-map) + +(define-key clearcase-dired-prefix-map "b" 'clearcase-browse-vtree-dired-file) +(define-key clearcase-dired-prefix-map "c" 'clearcase-uncheckout-dired-files) +(define-key clearcase-dired-prefix-map "e" 'clearcase-edcs-edit) +(define-key clearcase-dired-prefix-map "i" 'clearcase-mkelem-dired-files) +(define-key clearcase-dired-prefix-map "g" 'clearcase-annotate-dired-file) +(define-key clearcase-dired-prefix-map "l" 'clearcase-list-history-dired-file) +(define-key clearcase-dired-prefix-map "m" 'clearcase-mkbrtype) +(define-key clearcase-dired-prefix-map "u" 'clearcase-uncheckout-dired-files) +(define-key clearcase-dired-prefix-map "v" 'clearcase-next-action-dired-files) +(define-key clearcase-dired-prefix-map "w" 'clearcase-what-rule-dired-file) +(define-key clearcase-dired-prefix-map "=" 'clearcase-diff-pred-dired-file) +(define-key clearcase-dired-prefix-map "~" 'clearcase-version-other-window) +(define-key clearcase-dired-prefix-map "?" 'clearcase-describe-dired-file) + +;; To avoid confusion, we prevent VC Mode from being active at all by +;; undefining its keybindings for which ClearCase Mode doesn't yet have an +;; analogue. +;; +(define-key clearcase-dired-prefix-map "a" 'undefined) ;; vc-update-change-log +(define-key clearcase-dired-prefix-map "d" 'undefined) ;; vc-directory +(define-key clearcase-dired-prefix-map "h" 'undefined) ;; vc-insert-headers +(define-key clearcase-dired-prefix-map "m" 'undefined) ;; vc-merge +(define-key clearcase-dired-prefix-map "r" 'undefined) ;; vc-retrieve-snapshot +(define-key clearcase-dired-prefix-map "s" 'undefined) ;; vc-create-snapshot +(define-key clearcase-dired-prefix-map "t" 'undefined) ;; vc-dired-toggle-terse-mode + +;; Associate the map and the minor mode +;; +(or (not (boundp 'minor-mode-map-alist)) + (assq 'clearcase-dired-mode (symbol-value 'minor-mode-map-alist)) + (setq minor-mode-map-alist + (cons (cons 'clearcase-dired-mode clearcase-dired-mode-map) + minor-mode-map-alist))) + +(defun clearcase-dired-mode (&optional arg) + "The augmented Dired minor mode used in ClearCase directory buffers. +All Dired commands operate normally. Users with checked-out files +are listed in place of the file's owner and group. Keystrokes bound to +ClearCase Mode commands will execute as though they had been called +on a buffer attached to the file named in the current Dired buffer line." + + (interactive "P") + + ;; Behave like a proper minor-mode. + ;; + (setq clearcase-dired-mode + (if (interactive-p) + (if (null arg) + (not clearcase-dired-mode) + + ;; Check if the numeric arg is positive. + ;; + (> (prefix-numeric-value arg) 0)) + + ;; else + ;; Use the car if it's a list. + ;; + (if (consp arg) + (setq arg (car arg))) + + (if (symbolp arg) + (if (null arg) + (not clearcase-dired-mode) ;; toggle mode switch + (not (eq '- arg))) ;; True if symbol is not '- + + ;; else + ;; assume it's a number and check that. + ;; + (> arg 0)))) + + (if (not (eq major-mode 'dired-mode)) + (setq clearcase-dired-mode nil)) + + (if (and clearcase-dired-mode clearcase-dired-highlight) + (clearcase-dired-reformat-buffer)) + + (if clearcase-dired-mode + (easy-menu-add clearcase-dired-menu 'clearcase-dired-mode-map)) + ) + +;;}}} + +;;{{{ Major Mode: for editing comments. + +;; The major mode function. +;; +(defun clearcase-comment-mode () + "Major mode for editing comments for ClearCase. + +These bindings are added to the global keymap when you enter this mode: + +\\[clearcase-next-action-current-buffer] perform next logical version-control operation on current file +\\[clearcase-mkelem-current-buffer] mkelem the current file +\\[clearcase-toggle-read-only] like next-action, but won't create elements +\\[clearcase-list-history-current-buffer] display change history of current file +\\[clearcase-uncheckout-current-buffer] cancel checkout in buffer +\\[clearcase-diff-pred-current-buffer] show diffs between file versions +\\[clearcase-version-other-window] visit old version in another window + +While you are entering a comment for a version, the following +additional bindings will be in effect. + +\\[clearcase-comment-finish] proceed with check in, ending comment + +Whenever you do a checkin, your comment is added to a ring of +saved comments. These can be recalled as follows: + +\\[clearcase-comment-next] replace region with next message in comment ring +\\[clearcase-comment-previous] replace region with previous message in comment ring +\\[clearcase-comment-search-reverse] search backward for regexp in the comment ring +\\[clearcase-comment-search-forward] search backward for regexp in the comment ring + +Entry to the clearcase-comment-mode calls the value of text-mode-hook, then +the value of clearcase-comment-mode-hook. + +Global user options: + clearcase-initial-mkelem-comment If non-nil, require user to enter a change + comment upon first checkin of the file. + + clearcase-suppress-confirm Suppresses some confirmation prompts, + notably for reversions. + + clearcase-command-messages If non-nil, display run messages from the + actual version-control utilities (this is + intended primarily for people hacking clearcase.el + itself). +" + (interactive) + + ;; Major modes are supposed to just (kill-all-local-variables) + ;; but we rely on clearcase-parent-buffer already having been set + ;; + ;;(let ((parent clearcase-parent-buffer)) + ;; (kill-all-local-variables) + ;; (set (make-local-variable 'clearcase-parent-buffer) parent)) + + (setq major-mode 'clearcase-comment-mode) + (setq mode-name "ClearCase/Comment") + + (set-syntax-table text-mode-syntax-table) + (use-local-map clearcase-comment-mode-map) + (setq local-abbrev-table text-mode-abbrev-table) + + (make-local-variable 'clearcase-comment-operands) + (make-local-variable 'clearcase-comment-ring-index) + + (set-buffer-modified-p nil) + (setq buffer-file-name nil) + (run-hooks 'text-mode-hook 'clearcase-comment-mode-hook)) + +;; The keymap. +;; +(defvar clearcase-comment-mode-map nil) +(if clearcase-comment-mode-map + nil + (setq clearcase-comment-mode-map (make-sparse-keymap)) + (define-key clearcase-comment-mode-map "\M-n" 'clearcase-comment-next) + (define-key clearcase-comment-mode-map "\M-p" 'clearcase-comment-previous) + (define-key clearcase-comment-mode-map "\M-r" 'clearcase-comment-search-reverse) + (define-key clearcase-comment-mode-map "\M-s" 'clearcase-comment-search-forward) + (define-key clearcase-comment-mode-map "\C-c\C-c" 'clearcase-comment-finish) + (define-key clearcase-comment-mode-map "\C-x\C-s" 'clearcase-comment-save) + (define-key clearcase-comment-mode-map "\C-x\C-q" 'clearcase-comment-num-num-error)) + +;; Constants. +;; +(defconst clearcase-comment-maximum-ring-size 32 + "Maximum number of saved comments in the comment ring.") + +;; Variables. +;; +(defvar clearcase-comment-entry-mode nil) +(defvar clearcase-comment-operation nil) +(defvar clearcase-comment-operands) +(defvar clearcase-comment-ring nil) +(defvar clearcase-comment-ring-index nil) +(defvar clearcase-comment-last-match nil) +(defvar clearcase-comment-window-config nil) + +;; In several contexts, this is a local variable that points to the buffer for +;; which it was made (either a file, or a ClearCase dired buffer). +;; +(defvar clearcase-parent-buffer nil) +(defvar clearcase-parent-buffer-name nil) + +;;{{{ Commands and functions + +(defun clearcase-comment-start-entry (uniquifier + prompt + continuation + operands + &optional parent-buffer comment-seed) + + "Accept a comment by popping up a clearcase-comment-mode buffer +with a name derived from UNIQUIFIER, and emitting PROMPT in the minibuffer. +Set the continuation on close to CONTINUATION, which should be apply-ed to a list +formed by appending OPERANDS and the comment-string. + +Optional 5th argument specifies a PARENT-BUFFER to return to when the operation +is complete. + +Optional 6th argument specifies a COMMENT-SEED to insert in the comment buffer for +the user to edit." + + (let ((comment-buffer (get-buffer-create (format "*clearcase-comment-%s*" uniquifier))) + (old-window-config (current-window-configuration)) + (parent (or parent-buffer + (current-buffer)))) + (pop-to-buffer comment-buffer) + + ;; Record in buffer-local variables information sufficient to restore + ;; window context. + ;; + (set (make-local-variable 'clearcase-comment-window-config) old-window-config) + (set (make-local-variable 'clearcase-parent-buffer) parent) + + (clearcase-comment-mode) + (setq clearcase-comment-operation continuation) + (setq clearcase-comment-operands operands) + (if comment-seed + (insert comment-seed)) + (message "%s Type C-c C-c when done." prompt))) + + +(defun clearcase-comment-cleanup () + ;; Make sure it ends with newline + ;; + (goto-char (point-max)) + (if (not (bolp)) + (newline)) + + ;; Remove useless whitespace. + ;; + (goto-char (point-min)) + (while (re-search-forward "[ \t]+$" nil t) + (replace-match "")) + + ;; Remove trailing newlines, whitespace. + ;; + (goto-char (point-max)) + (skip-chars-backward " \n\t") + (delete-region (point) (point-max))) + +(defun clearcase-comment-finish () + "Complete the operation implied by the current comment." + (interactive) + + ;;Clean and record the comment in the ring. + ;; + (let ((comment-buffer (current-buffer))) + (clearcase-comment-cleanup) + + (if (null clearcase-comment-ring) + (setq clearcase-comment-ring (make-ring clearcase-comment-maximum-ring-size))) + (ring-insert clearcase-comment-ring (buffer-string)) + + ;; Perform the operation on the operands. + ;; + (if clearcase-comment-operation + (save-excursion + (apply clearcase-comment-operation + (append clearcase-comment-operands (list (buffer-string))))) + (error "No comment operation is pending")) + + ;; Return to "parent" buffer of this operation. + ;; Remove comment window. + ;; + (let ((old-window-config clearcase-comment-window-config)) + (pop-to-buffer clearcase-parent-buffer) + (delete-windows-on comment-buffer) + (kill-buffer comment-buffer) + (if old-window-config (set-window-configuration old-window-config))))) + +(defun clearcase-comment-save-comment-for-buffer (comment buffer) + (save-excursion + (set-buffer buffer) + (let ((file (buffer-file-name))) + (if (clearcase-fprop-checked-out file) + (progn + (clearcase-ct-do-cleartool-command "chevent" + file + comment + (list "-replace")) + (clearcase-fprop-set-comment file comment)) + (error "Can't change comment of checked-in version with this interface"))))) + +(defun clearcase-comment-save () + "Save the currently entered comment" + (interactive) + (let ((comment-string (buffer-string)) + (parent-buffer clearcase-parent-buffer)) + (if (not (buffer-modified-p)) + (message "(No changes need to be saved)") + (progn + (save-excursion + (set-buffer parent-buffer) + (clearcase-comment-save-comment-for-buffer comment-string parent-buffer)) + + (set-buffer-modified-p nil))))) + +(defun clearcase-comment-num-num-error () + (interactive) + (message "Perhaps you wanted to type C-c C-c instead?")) + +;; Code for the comment ring. +;; +(defun clearcase-comment-next (arg) + "Cycle forwards through comment history." + (interactive "*p") + (clearcase-comment-previous (- arg))) + +(defun clearcase-comment-previous (arg) + "Cycle backwards through comment history." + (interactive "*p") + (let ((len (ring-length clearcase-comment-ring))) + (cond ((or (not len) (<= len 0)) + (message "Empty comment ring") + (ding)) + (t + (erase-buffer) + + ;; Initialize the index on the first use of this command so that the + ;; first M-p gets index 0, and the first M-n gets index -1. + ;; + (if (null clearcase-comment-ring-index) + (setq clearcase-comment-ring-index + (if (> arg 0) -1 + (if (< arg 0) 1 0)))) + (setq clearcase-comment-ring-index + (mod (+ clearcase-comment-ring-index arg) len)) + (message "%d" (1+ clearcase-comment-ring-index)) + (insert (ring-ref clearcase-comment-ring clearcase-comment-ring-index)))))) + +(defun clearcase-comment-search-forward (str) + "Searches forwards through comment history for substring match." + (interactive "sComment substring: ") + (if (string= str "") + (setq str clearcase-comment-last-match) + (setq clearcase-comment-last-match str)) + (if (null clearcase-comment-ring-index) + (setq clearcase-comment-ring-index 0)) + (let ((str (regexp-quote str)) + (n clearcase-comment-ring-index)) + (while (and (>= n 0) (not (string-match str (ring-ref clearcase-comment-ring n)))) + (setq n (- n 1))) + (cond ((>= n 0) + (clearcase-comment-next (- n clearcase-comment-ring-index))) + (t (error "Not found"))))) + +(defun clearcase-comment-search-reverse (str) + "Searches backwards through comment history for substring match." + (interactive "sComment substring: ") + (if (string= str "") + (setq str clearcase-comment-last-match) + (setq clearcase-comment-last-match str)) + (if (null clearcase-comment-ring-index) + (setq clearcase-comment-ring-index -1)) + (let ((str (regexp-quote str)) + (len (ring-length clearcase-comment-ring)) + (n (1+ clearcase-comment-ring-index))) + (while (and (< n len) + (not (string-match str (ring-ref clearcase-comment-ring n)))) + (setq n (+ n 1))) + (cond ((< n len) + (clearcase-comment-previous (- n clearcase-comment-ring-index))) + (t (error "Not found"))))) + +;;}}} + +;;}}} + +;;{{{ Major Mode: for editing config-specs. + +;; The major mode function. +;; +(defun clearcase-edcs-mode () + (interactive) + (set-syntax-table text-mode-syntax-table) + (use-local-map clearcase-edcs-mode-map) + (setq major-mode 'clearcase-edcs-mode) + (setq mode-name "ClearCase/edcs") + (make-variable-buffer-local 'clearcase-parent-buffer) + (set-buffer-modified-p nil) + (setq buffer-file-name nil) + (run-hooks 'text-mode-hook 'clearcase-edcs-mode-hook)) + +;; The keymap. +;; +(defvar clearcase-edcs-mode-map nil) +(if clearcase-edcs-mode-map + nil + (setq clearcase-edcs-mode-map (make-sparse-keymap)) + (define-key clearcase-edcs-mode-map "\C-c\C-c" 'clearcase-edcs-finish) + (define-key clearcase-edcs-mode-map "\C-x\C-s" 'clearcase-edcs-save)) + +;; Variables. +;; +(defvar clearcase-edcs-tag-name nil + "Name of view tag which is currently being edited") + +(defvar clearcase-edcs-tag-history () + "History of view tags used in clearcase-edcs-edit") + +;;{{{ Commands + +(defun clearcase-edcs-edit (tag-name) + "Edit a ClearCase configuration specification" + + (interactive + (let ((vxname (clearcase-fprop-viewtag default-directory))) + (if clearcase-complete-viewtags + (list (directory-file-name + (completing-read "View Tag: " + (clearcase-viewtag-all-viewtags-obarray) + nil + ;;'fascist + nil + vxname + 'clearcase-edcs-tag-history))) + (read-string "View Tag: ")))) + + (let ((start (current-buffer)) + (buffer-name (format "*clearcase-config-spec-%s*" tag-name))) + (kill-buffer (get-buffer-create buffer-name)) + (pop-to-buffer (get-buffer-create buffer-name)) + (auto-save-mode auto-save-default) + (erase-buffer) + (insert (clearcase-ct-cleartool-cmd "catcs" "-tag" tag-name)) + (goto-char (point-min)) + (re-search-forward "^[^#\n]" nil 'end) + (beginning-of-line) + (clearcase-edcs-mode) + (setq clearcase-parent-buffer start) + (make-local-variable 'clearcase-edcs-tag-name) + (setq clearcase-edcs-tag-name tag-name))) + +(defun clearcase-edcs-save () + (interactive) + (if (not (buffer-modified-p)) + (message "Configuration not changed since last saved") + + (message "Setting configuration for %s..." clearcase-edcs-tag-name) + (clearcase-with-tempfile + cspec-text + (write-region (point-min) (point-max) cspec-text nil 'dont-mention-it) + (let ((ret (clearcase-ct-cleartool-cmd "setcs" + "-tag" + clearcase-edcs-tag-name + (clearcase-path-native cspec-text)))) + + ;; nyi: we could be smarter and retain viewtag info and perhaps some + ;; other info. For now invalidate all cached file property info. + ;; + (clearcase-fprop-clear-all-properties) + + (set-buffer-modified-p nil) + (message "Setting configuration for %s...done" + clearcase-edcs-tag-name))))) + +(defun clearcase-edcs-finish () + (interactive) + (let ((old-buffer (current-buffer))) + (clearcase-edcs-save) + (bury-buffer nil) + (kill-buffer old-buffer))) + +;;}}} + +;;}}} + +;;{{{ View browser + +;; nyi: Just an idea now. +;; Be able to present a selection of views at various times +;; - show me current file in other view +;; - top-level browse operation + +;; clearcase-viewtag-started-viewtags gives us the dynamic views that are mounted. + +;; How to find local snapshots ? + +;; How to find drive-letter mount points for view on NT ? +;; - parse "subst" output + +;;}}} + +;;{{{ Commands + +;;{{{ Hijack/unhijack + +(defun clearcase-hijack-current-buffer () + "Hijack the file in the current buffer." + (interactive) + (clearcase-hijack buffer-file-name)) + +(defun clearcase-hijack-dired-files () + "Hijack the selected files." + (interactive) + (clearcase-hijack-seq (dired-get-marked-files))) + +(defun clearcase-unhijack-current-buffer () + "Unhijack the file in the current buffer." + (interactive) + (clearcase-unhijack buffer-file-name)) + +(defun clearcase-unhijack-dired-files () + "Hijack the selected files." + (interactive) + (clearcase-unhijack-seq (dired-get-marked-files))) + +;;}}} + +;;{{{ Annotate + +(defun clearcase-annotate-file (file) + (let ((relative-name (file-relative-name file))) + (message "Annotating %s ..." relative-name) + (clearcase-with-tempfile + annotation-file + (clearcase-ct-do-cleartool-command "annotate" + file + 'unused + (list "-nco" + "-out" + annotation-file)) + (clearcase-utl-populate-and-view-buffer + "*clearcase-annotate*" + nil + (function + (lambda () + (insert-file-contents annotation-file))))) + (message "Annotating %s ...done" relative-name))) + +(defun clearcase-annotate-current-buffer () + (interactive) + (clearcase-annotate-file buffer-file-name)) + +(defun clearcase-annotate-dired-file () + "Annotate the selected file." + (interactive) + (clearcase-annotate-file (dired-get-filename))) + +;;}}} + +;;{{{ nyi: Find checkouts + +;; NYI: Enhance this: +;; - group by: +;; - activity name +;; - checkout comment +;; - permit unco/checkin +;; +(defun clearcase-find-checkouts-in-current-view () + "Find the checkouts in all vobs in the current view." + (interactive) + (let ((viewtag (clearcase-fprop-viewtag default-directory)) + (dir default-directory)) + (if viewtag + (let* ((ignore (message "Finding checkouts...")) + (text (clearcase-ct-blocking-call "lsco" + "-cview" + "-avobs" + "-short"))) + (if (zerop (length text)) + (message "No checkouts found") + (progn + (message "Finding checkouts...done") + + (clearcase-utl-populate-and-view-buffer + "*clearcase*" + (list text) + (function (lambda (s) + (insert s)))))))))) + +;;}}} + +;;{{{ UCM operations + +;;{{{ Make activity + +(defun clearcase-read-new-activity-name () + "Read the name of a new activity from the minibuffer. +Return nil if the empty string is entered." + + ;; nyi: Probably should check that the activity doesn't already exist. + ;; + (let ((entered-name (read-string "Activity name (optional): " ))) + (if (not (zerop (length entered-name))) + entered-name + nil))) + +(defun clearcase-read-mkact-args () + "Read the name and headline arguments for clearcase-ucm-mkact-current-dir +from the minibuffer." + + (let ((name nil) + (headline "")) + (if clearcase-prompt-for-activity-names + (setq name (clearcase-read-new-activity-name))) + (setq headline (read-string "Activity headline: " )) + (list name headline))) + +(defun clearcase-make-internally-named-activity (stream-name comment-file) + "Make a new activity in STREAM-NAME with creation comment in COMMENT-FILE, +and use an internally-generated name for the activity." + + (let ((ret + (if clearcase-set-to-new-activity + (clearcase-ct-blocking-call "mkact" + "-cfile" (clearcase-path-native comment-file) + "-in" stream-name + "-force") + (clearcase-ct-blocking-call "mkact" + "-nset" + "-cfile" (clearcase-path-native comment-file) + "-in" stream-name + "-nset" + "-force")))) + (if (string-match "Created activity \"\\([^\"]+\\)\"" ret) + (substring ret (match-beginning 1) (match-end 1)) + (error "Failed to create activity: %s" ret)))) + +(defun clearcase-ucm-mkact-current-dir (name headline &optional comment) + + "Make an activity with NAME and HEADLINE and optional COMMENT, in the stream +associated with the view associated with the current directory." + + (interactive (clearcase-read-mkact-args)) + (let* ((viewtag (clearcase-fprop-viewtag default-directory)) + (stream (clearcase-vprop-stream viewtag)) + (pvob (clearcase-vprop-pvob viewtag))) + (if (not (clearcase-vprop-ucm viewtag)) + (error "View %s is not a UCM view" viewtag)) + (if (null stream) + (error "View %s has no stream" viewtag)) + (if (null stream) + (error "View %s has no PVOB" viewtag)) + + (if (null comment) + ;; If no comment supplied, go and get one.. + ;; + (progn + (clearcase-comment-start-entry (format "new-activity-%d" (random)) + "Enter comment for new activity." + 'clearcase-ucm-mkact-current-dir + (list name headline))) + ;; ...else do the operation. + ;; + (message "Making activity...") + (clearcase-with-tempfile + comment-file + (write-region comment nil comment-file nil 'noprint) + (let ((qualified-stream (format "%s@%s" stream pvob))) + (if (stringp name) + (if clearcase-set-to-new-activity + (clearcase-ct-blocking-call "mkact" + "-cfile" (clearcase-path-native comment-file) + "-headline" headline + "-in" qualified-stream + "-force" + name) + (clearcase-ct-blocking-call "mkact" + "-nset" + "-cfile" (clearcase-path-native comment-file) + "-headline" headline + "-in" qualified-stream + "-force" + name)) + (progn + ;; If no name was provided we do the creation in two steps: + ;; mkact -force + ;; chact -headline + ;; to make sure we get preferred internally generated activity + ;; name of the form "activityNNN.MMM" rather than some horrible + ;; concoction based on the headline. + ;; + (let ((name (clearcase-make-internally-named-activity qualified-stream comment-file))) + (clearcase-ct-blocking-call "chact" + "-headline" headline + name)))))) + + ;; Flush the activities for this view so they'll get refreshed when needed. + ;; + (clearcase-vprop-flush-activities viewtag) + + (message "Making activity...done")))) + +;;}}} + +;;{{{ Set activity + +(defun clearcase-ucm-filter-out-rebases (activities) + (if (not clearcase-hide-rebase-activities) + activities + (clearcase-utl-list-filter + (function + (lambda (activity) + (let ((id (car activity))) + (not (string-match clearcase-rebase-id-regexp id))))) + activities))) + +(defun clearcase-ucm-set-activity-current-dir () + (interactive) + (let* ((viewtag (clearcase-fprop-viewtag default-directory))) + (if (not (clearcase-vprop-ucm viewtag)) + (error "View %s is not a UCM view" viewtag)) + ;; Filter out the rebases here if the user doesn't want to see them. + ;; + (let ((activities (clearcase-ucm-filter-out-rebases (clearcase-vprop-activities viewtag)))) + (if (null activities) + (error "View %s has no activities" viewtag)) + (clearcase-ucm-make-selection-window (format "*clearcase-activity-select-%s*" viewtag) + (mapconcat + (function + (lambda (activity) + (let ((id (car activity)) + (title (cdr activity))) + (format "%s\t%s" id title)))) + activities + "\n") + 'clearcase-ucm-activity-selection-interpreter + 'clearcase-ucm-set-activity + (list viewtag))))) + +(defun clearcase-ucm-activity-selection-interpreter () + "Extract the activity name from the buffer at point" + (if (looking-at "^\\(.*\\)\t") + (let ((activity-name (buffer-substring (match-beginning 1) + (match-end 1)))) + activity-name) + (error "No activity on this line"))) + +(defun clearcase-ucm-set-activity-none-current-dir () + (interactive) + (let* ((viewtag (clearcase-fprop-viewtag default-directory))) + (if (not (clearcase-vprop-ucm viewtag)) + (error "View %s is not a UCM view" viewtag)) + (clearcase-ucm-set-activity viewtag nil))) + +(defun clearcase-ucm-set-activity (viewtag activity-name) + (if activity-name + ;; Set an activity + ;; + (progn + (message "Setting activity...") + (let ((qualified-activity-name (if (string-match "@" activity-name) + activity-name + (concat activity-name "@" (clearcase-vprop-pvob viewtag))))) + (clearcase-ct-blocking-call "setactivity" "-nc" "-view" + viewtag + (if qualified-activity-name + qualified-activity-name + "-none"))) + ;; Update cache + ;; + (clearcase-vprop-set-current-activity viewtag activity-name) + (message "Setting activity...done")) + + ;; Set NO activity + ;; + (message "Unsetting activity...") + (clearcase-ct-blocking-call "setactivity" + "-nc" + "-view" viewtag + "-none") + ;; Update cache + ;; + (clearcase-vprop-set-current-activity viewtag nil) + (message "Unsetting activity...done"))) + +;;}}} + +;;{{{ Show current activity + +(defun clearcase-ucm-describe-current-activity () + (interactive) + (let* ((viewtag (clearcase-fprop-viewtag default-directory))) + (if (not viewtag) + (error "Not in a view")) + (if (not (clearcase-vprop-ucm viewtag)) + (error "View %s is not a UCM view" viewtag)) + (let ((pvob (clearcase-vprop-pvob viewtag)) + (current-activity (clearcase-vprop-current-activity viewtag))) + (if (not current-activity) + (message "No activity set") + (let ((text (clearcase-ct-blocking-call "desc" + (concat "activity:" + current-activity + "@" + pvob)))) + (if (not (zerop (length text))) + (clearcase-utl-populate-and-view-buffer + "*clearcase*" + (list text) + (function (lambda (s) + (insert s)))))))))) +;;}}} + +;;}}} + +;;{{{ Next-action + +(defun clearcase-next-action-current-buffer () + "Do the next logical operation on the current file. +Operations include mkelem, checkout, checkin, uncheckout" + (interactive) + (clearcase-next-action buffer-file-name)) + +(defun clearcase-next-action-dired-files () + "Do the next logical operation on the marked files. +Operations include mkelem, checkout, checkin, uncheckout. +If all the files are not in an equivalent state, an error is raised." + + (interactive) + (clearcase-next-action-seq (dired-get-marked-files))) + +(defun clearcase-next-action (file) + (let ((action (clearcase-compute-next-action file))) + (cond + + ((eq action 'mkelem) + (clearcase-commented-mkelem file)) + + ((eq action 'checkout) + (clearcase-commented-checkout file)) + + ((eq action 'uncheckout) + (if (yes-or-no-p "Checked-out file appears unchanged. Cancel checkout ? ") + (clearcase-uncheckout file))) + + ((eq action 'illegal-checkin) + (error "This file is checked out by someone else: %s" (clearcase-fprop-user file))) + + ((eq action 'checkin) + (clearcase-commented-checkin file)) + + (t + (error "Can't compute suitable next ClearCase action for file %s" file))))) + +(defun clearcase-next-action-seq (files) + "Do the next logical operation on the sequence of FILES." + + ;; Check they're all in the same state. + ;; + (let ((actions (mapcar (function clearcase-compute-next-action) files))) + (if (not (clearcase-utl-elts-are-eq actions)) + (error "Marked files are not all in the same state")) + (let ((action (car actions))) + (cond + + ((eq action 'mkelem) + (clearcase-commented-mkelem-seq files)) + + ((eq action 'checkout) + (clearcase-commented-checkout-seq files)) + + ((eq action 'uncheckout) + (if (yes-or-no-p "Checked-out files appears unchanged. Cancel checkouts ? ") + (clearcase-uncheckout-seq files))) + + ((eq action 'illegal-checkin) + (error "These files are checked out by someone else; will no checkin")) + + ((eq action 'checkin) + (clearcase-commented-checkin-seq files)) + + (t + (error "Can't compute suitable next ClearCase action for marked files")))))) + +(defun clearcase-compute-next-action (file) + "Compute the next logical action on FILE." + + (cond + ;; nyi: other cases to consider later: + ;; + ;; - file is unreserved + ;; - file is not mastered + + ;; Case 1: it is not yet an element + ;; ==> mkelem + ;; + ((clearcase-file-ok-to-mkelem file) + 'mkelem) + + ;; Case 2: file is not checked out + ;; ==> checkout + ;; + ((clearcase-file-ok-to-checkout file) + 'checkout) + + ;; Case 3: file is checked-out but not modified in buffer or disk + ;; ==> offer to uncheckout + ;; + ((and (clearcase-file-ok-to-uncheckout file) + (not (file-directory-p file)) + (not (buffer-modified-p)) + (not (clearcase-file-appears-modified-since-checkout-p file))) + 'uncheckout) + + ;; Case 4: file is checked-out but by somebody else using this view. + ;; ==> refuse to checkin + ;; + ;; This is not reliable on some Windows installations where a user is known + ;; as "esler" on Unix and the ClearCase server, and "ESLER" on the Windows + ;; client. + ;; + ((and (not clearcase-on-mswindows) + (clearcase-fprop-checked-out file) + (not (string= (user-login-name) + (clearcase-fprop-user file)))) + 'illegal-checkin) + + ;; Case 5: user has checked-out the file + ;; ==> check it in + ;; + ((clearcase-file-ok-to-checkin file) + 'checkin) + + (t + nil))) + +;;}}} + +;;{{{ Mkelem + +(defun clearcase-mkelem-current-buffer () + "Make the current file into a ClearCase element." + (interactive) + + ;; Watch out for new buffers of size 0: the corresponding file + ;; does not exist yet, even though buffer-modified-p is nil. + ;; + (if (and (not (buffer-modified-p)) + (zerop (buffer-size)) + (not (file-exists-p buffer-file-name))) + (set-buffer-modified-p t)) + + (clearcase-commented-mkelem buffer-file-name)) + +(defun clearcase-mkelem-dired-files () + "Make the selected files into ClearCase elements." + (interactive) + (clearcase-commented-mkelem-seq (dired-get-marked-files))) + +;;}}} + +;;{{{ Checkin + +(defun clearcase-checkin-current-buffer () + "Checkin the file in the current buffer." + (interactive) + + ;; Watch out for new buffers of size 0: the corresponding file + ;; does not exist yet, even though buffer-modified-p is nil. + ;; + (if (and (not (buffer-modified-p)) + (zerop (buffer-size)) + (not (file-exists-p buffer-file-name))) + (set-buffer-modified-p t)) + + (clearcase-commented-checkin buffer-file-name)) + +(defun clearcase-checkin-dired-files () + "Checkin the selected files." + (interactive) + (clearcase-commented-checkin-seq (dired-get-marked-files))) + +(defun clearcase-dired-checkin-current-dir () + (interactive) + (clearcase-commented-checkin (dired-current-directory))) + +;;}}} + +;;{{{ Edit checkout comment + +(defun clearcase-edit-checkout-comment-current-buffer () + "Edit the clearcase comment for the checked-out file in the current buffer." + (interactive) + (clearcase-edit-checkout-comment buffer-file-name)) + +(defun clearcase-edit-checkout-comment-dired-file () + "Checkin the selected file." + (interactive) + (clearcase-edit-checkout-comment (dired-get-filename))) + +(defun clearcase-edit-checkout-comment (file &optional comment) + "Edit comment for FILE by popping up a buffer to accept one. If COMMENT +is specified, save it." + (if (null comment) + ;; If no comment supplied, go and get one... + ;; + (clearcase-comment-start-entry (file-name-nondirectory file) + "Edit the file's check-out comment." + 'clearcase-edit-checkout-comment + (list buffer-file-name) + (find-file-noselect file) + (clearcase-fprop-comment file)) + ;; We have a comment, save it + (clearcase-comment-save-comment-for-buffer comment clearcase-parent-buffer))) + +;;}}} + +;;{{{ Checkout + +(defun clearcase-checkout-current-buffer () + "Checkout the file in the current buffer." + (interactive) + (clearcase-commented-checkout buffer-file-name)) + +(defun clearcase-checkout-dired-files () + "Checkout the selected files." + (interactive) + (clearcase-commented-checkout-seq (dired-get-marked-files))) + +(defun clearcase-dired-checkout-current-dir () + (interactive) + (clearcase-commented-checkout (dired-current-directory))) + +;;}}} + +;;{{{ Uncheckout + +(defun clearcase-uncheckout-current-buffer () + "Uncheckout the file in the current buffer." + (interactive) + (clearcase-uncheckout buffer-file-name)) + +(defun clearcase-uncheckout-dired-files () + "Uncheckout the selected files." + (interactive) + (clearcase-uncheckout-seq (dired-get-marked-files))) + +(defun clearcase-dired-uncheckout-current-dir () + (interactive) + (clearcase-uncheckout (dired-current-directory))) + +;;}}} + +;;{{{ Mkbrtype + +(defun clearcase-mkbrtype (typename) + (interactive "sBranch type name: ") + (clearcase-commented-mkbrtype typename)) + +;;}}} + +;;{{{ Describe + +(defun clearcase-describe-current-buffer () + "Give a ClearCase description of the file in the current buffer." + (interactive) + (clearcase-describe buffer-file-name)) + +(defun clearcase-describe-dired-file () + "Describe the selected files." + (interactive) + (clearcase-describe (dired-get-filename))) + +;;}}} + +;;{{{ What-rule + +(defun clearcase-what-rule-current-buffer () + (interactive) + (clearcase-what-rule buffer-file-name)) + +(defun clearcase-what-rule-dired-file () + (interactive) + (clearcase-what-rule (dired-get-filename))) + +;;}}} + +;;{{{ List history + +(defun clearcase-list-history-current-buffer () + "List the change history of the current buffer in a window." + (interactive) + (clearcase-list-history buffer-file-name)) + +(defun clearcase-list-history-dired-file () + "List the change history of the current file." + (interactive) + (clearcase-list-history (dired-get-filename))) + +;;}}} + +;;{{{ Ediff + +(defun clearcase-ediff-pred-current-buffer () + "Use Ediff to compare a version in the current buffer against its predecessor." + (interactive) + (clearcase-ediff-file-with-version buffer-file-name + (clearcase-fprop-predecessor-version buffer-file-name))) + +(defun clearcase-ediff-pred-dired-file () + "Use Ediff to compare the selected version against its predecessor." + (interactive) + (let ((truename (clearcase-fprop-truename (dired-get-filename)))) + (clearcase-ediff-file-with-version truename + (clearcase-fprop-predecessor-version truename)))) + +(defun clearcase-ediff-branch-base-current-buffer() + "Use Ediff to compare a version in the current buffer +against the base of its branch." + (interactive) + (clearcase-ediff-file-with-version buffer-file-name + (clearcase-vxpath-version-of-branch-base buffer-file-name))) + +(defun clearcase-ediff-branch-base-dired-file() + "Use Ediff to compare the selected version against the base of its branch." + (interactive) + (let ((truename (clearcase-fprop-truename (dired-get-filename)))) + (clearcase-ediff-file-with-version truename + (clearcase-vxpath-version-of-branch-base truename)))) + +(defun clearcase-ediff-named-version-current-buffer (version) + ;; nyi: if we're in history-mode, probably should just use + ;; (read-file-name) + ;; + (interactive (list (clearcase-read-version-name "Version for comparison: " + buffer-file-name))) + (clearcase-ediff-file-with-version buffer-file-name version)) + +(defun clearcase-ediff-named-version-dired-file (version) + ;; nyi: if we're in history-mode, probably should just use + ;; (read-file-name) + ;; + (interactive (list (clearcase-read-version-name "Version for comparison: " + (dired-get-filename)))) + (clearcase-ediff-file-with-version (clearcase-fprop-truename (dired-get-filename)) + version)) + +(defun clearcase-ediff-file-with-version (truename other-version) + (let ((other-vxpath (clearcase-vxpath-cons-vxpath (clearcase-vxpath-element-part truename) + other-version))) + (if (clearcase-file-is-in-mvfs-p truename) + (ediff-files other-vxpath truename) + (ediff-buffers (clearcase-vxpath-get-version-in-buffer other-vxpath) + (find-file-noselect truename t))))) + +;;}}} + +;;{{{ GUI diff + +(defun clearcase-gui-diff-pred-current-buffer () + "Use GUI to compare a version in the current buffer against its predecessor." + (interactive) + (clearcase-gui-diff-file-with-version buffer-file-name + (clearcase-fprop-predecessor-version buffer-file-name))) + +(defun clearcase-gui-diff-pred-dired-file () + "Use GUI to compare the selected version against its predecessor." + (interactive) + (let ((truename (clearcase-fprop-truename (dired-get-filename)))) + (clearcase-gui-diff-file-with-version truename + (clearcase-fprop-predecessor-version truename)))) + +(defun clearcase-gui-diff-branch-base-current-buffer() + "Use GUI to compare a version in the current buffer +against the base of its branch." + (interactive) + (clearcase-gui-diff-file-with-version buffer-file-name + (clearcase-vxpath-version-of-branch-base buffer-file-name))) + +(defun clearcase-gui-diff-branch-base-dired-file() + "Use GUI to compare the selected version against the base of its branch." + (interactive) + (let ((truename (clearcase-fprop-truename (dired-get-filename)))) + (clearcase-gui-diff-file-with-version truename + (clearcase-vxpath-version-of-branch-base truename)))) + +(defun clearcase-gui-diff-named-version-current-buffer (version) + ;; nyi: if we're in history-mode, probably should just use + ;; (read-file-name) + ;; + (interactive (list (clearcase-read-version-name "Version for comparison: " + buffer-file-name))) + (clearcase-gui-diff-file-with-version buffer-file-name version)) + +(defun clearcase-gui-diff-named-version-dired-file (version) + ;; nyi: if we're in history-mode, probably should just use + ;; (read-file-name) + ;; + (interactive (list (clearcase-read-version-name "Version for comparison: " + (dired-get-filename)))) + (clearcase-gui-diff-file-with-version (clearcase-fprop-truename (dired-get-filename)) + version)) + +(defun clearcase-gui-diff-file-with-version (truename other-version) + (let* ((other-vxpath (clearcase-vxpath-cons-vxpath (clearcase-vxpath-element-part truename) + other-version)) + (other-file (if (clearcase-file-is-in-mvfs-p truename) + other-vxpath + (clearcase-vxpath-get-version-in-temp-file other-vxpath))) + (gui-name (if clearcase-on-mswindows + "cleardiffmrg" + "xcleardiff"))) + (start-process "Diff" + nil + gui-name + (clearcase-path-native other-file) + (clearcase-path-native truename)))) + +;;}}} + +;;{{{ Diff + +(defun clearcase-diff-pred-current-buffer () + "Use Diff to compare a version in the current buffer against its predecessor." + (interactive) + (clearcase-diff-file-with-version buffer-file-name + (clearcase-fprop-predecessor-version buffer-file-name))) + +(defun clearcase-diff-pred-dired-file () + "Use Diff to compare the selected version against its predecessor." + (interactive) + (let ((truename (clearcase-fprop-truename (dired-get-filename)))) + (clearcase-diff-file-with-version truename + (clearcase-fprop-predecessor-version truename)))) + +(defun clearcase-diff-branch-base-current-buffer() + "Use Diff to compare a version in the current buffer +against the base of its branch." + (interactive) + (clearcase-diff-file-with-version buffer-file-name + (clearcase-vxpath-version-of-branch-base buffer-file-name))) + +(defun clearcase-diff-branch-base-dired-file() + "Use Diff to compare the selected version against the base of its branch." + (interactive) + (let ((truename (clearcase-fprop-truename (dired-get-filename)))) + (clearcase-diff-file-with-version truename + (clearcase-vxpath-version-of-branch-base truename)))) + +(defun clearcase-diff-named-version-current-buffer (version) + ;; nyi: if we're in history-mode, probably should just use + ;; (read-file-name) + ;; + (interactive (list (clearcase-read-version-name "Version for comparison: " + buffer-file-name))) + (clearcase-diff-file-with-version buffer-file-name version)) + +(defun clearcase-diff-named-version-dired-file (version) + ;; nyi: if we're in history-mode, probably should just use + ;; (read-file-name) + ;; + (interactive (list (clearcase-read-version-name "Version for comparison: " + (dired-get-filename)))) + (clearcase-diff-file-with-version (clearcase-fprop-truename (dired-get-filename)) + version)) + +(defun clearcase-diff-file-with-version (truename other-version) + (let ((other-vxpath (clearcase-vxpath-cons-vxpath (clearcase-vxpath-element-part truename) + other-version))) + (if (clearcase-file-is-in-mvfs-p truename) + (clearcase-diff-files other-vxpath truename) + (clearcase-diff-files (clearcase-vxpath-get-version-in-temp-file other-vxpath) + truename)))) + +;;}}} + +;;{{{ Browse vtree + +(defun clearcase-version-other-window (version) + (interactive + (list + (clearcase-read-version-name (format "Version of %s to visit: " + (file-name-nondirectory buffer-file-name)) + buffer-file-name))) + (find-file-other-window (clearcase-vxpath-cons-vxpath + (clearcase-vxpath-element-part buffer-file-name) + version))) + +(defun clearcase-browse-vtree-current-buffer () + (interactive) + (clearcase-browse-vtree buffer-file-name)) + +(defun clearcase-browse-vtree-dired-file () + (interactive) + (clearcase-browse-vtree (dired-get-filename))) + +;;}}} + +;;{{{ GUI vtree + +(defun clearcase-gui-vtree-browser-current-buffer () + (interactive) + (clearcase-gui-vtree-browser buffer-file-name)) + +(defun clearcase-gui-vtree-browser-dired-file () + (interactive) + (clearcase-gui-vtree-browser (dired-get-filename))) + +(defun clearcase-gui-vtree-browser (file) + (let ((gui-name (if clearcase-on-mswindows + "clearvtree" + "xlsvtree"))) + (start-process-shell-command "Vtree_browser" + nil + gui-name + (clearcase-path-native file)))) + +;;}}} + +;;{{{ Other GUIs + +(defun clearcase-gui-clearexplorer () + (interactive) + (start-process-shell-command "ClearExplorer" + nil + "clearexplorer" + ".")) + +(defun clearcase-gui-rebase () + (interactive) + (start-process-shell-command "Rebase" + nil + "clearmrgman" + (if clearcase-on-mswindows + "/rebase" + "-rebase"))) + +(defun clearcase-gui-deliver () + (interactive) + (start-process-shell-command "Deliver" + nil + "clearmrgman" + (if clearcase-on-mswindows + "/deliver" + "-deliver"))) + +(defun clearcase-gui-merge-manager () + (interactive) + (start-process-shell-command "Merge_manager" + nil + "clearmrgman")) + +(defun clearcase-gui-project-explorer () + (interactive) + (start-process-shell-command "Project_explorer" + nil + "clearprojexp")) + +(defun clearcase-gui-snapshot-view-updater () + (interactive) + (start-process-shell-command "View_updater" + nil + "clearviewupdate")) + +;;}}} + +;;{{{ Update snapshot + +;; In a file buffer: +;; - update current-file +;; - update directory +;; In dired: +;; - update dir +;; - update marked files +;; - update file + +;; We allow several simultaneous updates, but only one per view. + +(defun clearcase-update-view () + (interactive) + (clearcase-update (clearcase-fprop-viewtag default-directory))) + +(defun clearcase-update-default-directory () + (interactive) + (clearcase-update (clearcase-fprop-viewtag default-directory) + default-directory)) + +(defun clearcase-update-current-buffer () + (interactive) + (clearcase-update (clearcase-fprop-viewtag default-directory) + buffer-file-name)) + +(defun clearcase-update-dired-files () + (interactive) + (apply (function clearcase-update) + (cons (clearcase-fprop-viewtag default-directory) + (dired-get-marked-files)))) + + +;;}}} + +;;}}} + +;;{{{ Functions + +;;{{{ Basic ClearCase operations + +;;{{{ Update snapshot view + +;;{{{ Asynchronous post-processing of update + +(defvar clearcase-post-update-timer nil) +(defvar clearcase-post-update-work-queue nil) + +(defun clearcase-post-update-schedule-work (buffer) + (clearcase-trace "entering clearcase-post-update-schedule-work") + ;; Add to the work queue. + ;; + (setq clearcase-post-update-work-queue (cons buffer + clearcase-post-update-work-queue)) + ;; Create the timer if necessary. + ;; + (if (null clearcase-post-update-timer) + (if clearcase-xemacs-p + ;; Xemacs + ;; + (setq clearcase-post-update-timer + (run-with-idle-timer 2 t 'clearcase-post-update-timer-function)) + ;; FSF Emacs + ;; + (progn + (setq clearcase-post-update-timer (timer-create)) + (timer-set-function clearcase-post-update-timer 'clearcase-post-update-timer-function) + (timer-set-idle-time clearcase-post-update-timer 2) + (timer-activate-when-idle clearcase-post-update-timer))) + (clearcase-trace "clearcase-post-update-schedule-work: post-update timer found to be non-null"))) + + +(defun clearcase-post-update-timer-function () + (clearcase-trace "Entering clearcase-post-update-timer-function") + ;; For (each update-process buffer in the work queue) + ;; if (its process has successfully terminated) + ;; do the post-processing for this update + ;; remove it from the work queue + ;; + (clearcase-trace (format "Queue before: %s" clearcase-post-update-work-queue)) + (setq clearcase-post-update-work-queue + + (clearcase-utl-list-filter + (function clearcase-post-update-check-process-buffer) + clearcase-post-update-work-queue)) + + (clearcase-trace (format "Queue after: %s" clearcase-post-update-work-queue)) + ;; If the work queue is now empty cancel the timer. + ;; + (if (null clearcase-post-update-work-queue) + (progn + (cancel-timer clearcase-post-update-timer) + (setq clearcase-post-update-timer nil)))) + +(defun clearcase-post-update-check-process-buffer (buffer) + (clearcase-trace "Entering clearcase-post-update-check-process-buffer") + + ;; return t for those buffers that should remain in the work queue + + ;; if it has terminated successfully + ;; go sync buffers on the files that were updated + + ;; We want to field errors here and when they occurm return nil to avoid a + ;; loop + ;; + ;;(condition-case nil + + ;; protected form + (let ((proc (get-buffer-process buffer))) + (if proc + ;; Process still exists so keep this on the work queue. + ;; + (progn + (clearcase-trace "Update process still exists") + t) + + ;; Process no longer there, cleaned up by comint code. + ;; + + ;; Sync any buffers that need it. + ;; + (clearcase-trace "Update process finished") + (clearcase-sync-after-scopes-updated (with-current-buffer buffer + ;; Evaluate buffer-local variable. + ;; + clearcase-update-buffer-scopes)) + + ;; Remove from work queue + ;; + nil)) + + ;; Error occurred, make sure we return nil to remove the buffer from the + ;; work queue, or a loop could develop. + ;; + ;;(error nil) + ) + +(defun clearcase-sync-after-scopes-updated (scopes) + (clearcase-trace "Entering clearcase-sync-after-scopes-updated") + + ;; nyi: reduce scopes to minimal set of disjoint scopes + + ;; Use dynamic binding here since we don't have lexical binding. + ;; + (let ((clearcase-dynbound-updated-scopes scopes)) + + ;; For all buffers... + ;; + (mapcar + (function + (lambda (buffer) + (let ((visited-file (buffer-file-name buffer))) + (if visited-file + (if (clearcase-path-file-in-any-scopes visited-file + clearcase-dynbound-updated-scopes) + ;; This buffer visits a file within an updated scope. + ;; Sync it from disk if it needs it. + ;; + (clearcase-sync-from-disk-if-needed visited-file)) + + ;; Buffer is not visiting a file. If it is a dired-mode buffer + ;; under one of the scopes, revert it. + ;; + (with-current-buffer buffer + (if (eq 'dired-mode major-mode) + (if (clearcase-path-file-in-any-scopes default-directory + clearcase-dynbound-updated-scopes) + (dired-revert nil t)))))))) + (buffer-list)))) + +;;}}} + +;; Silence compiler complaints about free variable. +;; +(defvar clearcase-update-buffer-viewtag nil) + +(defun clearcase-update (viewtag &rest files) + "Run a cleartool+update process in VIEWTAG +if there isn't one already running in that view. +Other arguments FILES indicate files to update" + + ;; Check that there is no update process running in that view. + ;; + (if (apply (function clearcase-utl-or-func) + (mapcar (function (lambda (proc) + (if (not (eq 'exit (process-status proc))) + (let ((buf (process-buffer proc))) + (and buf + (assq 'clearcase-update-buffer-viewtag + (buffer-local-variables buf)) + (save-excursion + (set-buffer buf) + (equal viewtag + clearcase-update-buffer-viewtag))))))) + (process-list))) + (error "There is already an update running in view %s" viewtag)) + + ;; All clear so: + ;; - create a process in a buffer + ;; - rename the buffer to be of the form *clearcase-update* + ;; - mark it as one of ours by setting clearcase-update-buffer-viewtag + ;; + (pop-to-buffer (apply (function make-comint) + (append (list "*clearcase-update-temp-name*" + clearcase-cleartool-path + nil + "update") + files)) + t) ;; other window + (rename-buffer "*clearcase-update*" t) + + ;; Store in this buffer what view was being updated and what files. + ;; + (set (make-local-variable 'clearcase-update-buffer-viewtag) viewtag) + (set (make-local-variable 'clearcase-update-buffer-scopes) files) + + ;; nyi: schedule post-update buffer syncing + (clearcase-post-update-schedule-work (current-buffer))) + +;;}}} + +;;{{{ Hijack + +(defun clearcase-file-ok-to-hijack (file) + + "Test if FILE is suitable for hijack." + + (and + + ;; If it is writeable already, no need to offer a hijack operation, even + ;; though, according to ClearCase, it may not yet be hijacked. + ;; + ;;(not (file-writable-p file)) + + (not (clearcase-fprop-hijacked file)) + (clearcase-file-is-in-view-p file) + (not (clearcase-file-is-in-mvfs-p file)) + (eq 'version (clearcase-fprop-mtype file)) + (not (clearcase-fprop-checked-out file)))) + +(defun clearcase-hijack-seq (files) + (unwind-protect + (progn + (message "Hijacking...") + (mapcar + (function + (lambda (file) + (if (not (file-directory-p file)) + (clearcase-hijack file)))) + files)) + ;; Unwind + ;; + (message "Hijacking...done"))) + +(defun clearcase-hijack (file) + + ;; cases + ;; - buffer/files modtimes are equal + ;; - file more recent + ;; ==> revert + ;; - buffer more recent + ;; ==> make file writeable; save buffer ? + ;; + ;; Post-conditions: + ;; - file is hijacked wrt. CC + ;; - buffer is in sync with disk contents, modtime and writeability + ;; except if the user refused to save + ;; + (if (not (file-writable-p file)) + ;; Make it writeable. + ;; + (clearcase-utl-make-writeable file)) + + ;; Attempt to modify the modtime of the file on disk, otherwise ClearCase + ;; won't actually deem it hijacked. This will silently fail if there is no + ;; "touch" command command available. + ;; + (clearcase-utl-touch-file file) + + ;; Sync up any buffers. + ;; + (clearcase-sync-from-disk file t)) + +;;}}} + +;;{{{ Unhijack + +(defun clearcase-file-ok-to-unhijack (file) + "Test if FILE is suitable for unhijack." + (clearcase-fprop-hijacked file)) + +(defun clearcase-unhijack (file) + (clearcase-unhijack-seq (list file))) + +(defun cleartool-unhijack-parse-for-kept-files (ret snapshot-view-root) + ;; Look for occurrences of: + ;; Loading "source\emacs\.emacs.el" (296690 bytes). + ;; (renaming original hijacked object to ".emacs.el.keep.10"). + ;; + (let ((start 0) + (kept-files nil)) + (while (string-match + "^Loading \"\\([^\"]+\\)\"[^\n]+\n(renaming original hijacked object to \"\\([^\"]+\\)\")\\.\n" + ret + start) + (let* ((elt-path (substring ret (match-beginning 1) (match-end 1))) + (abs-elt-path (concat (if snapshot-view-root + snapshot-view-root + "/") + elt-path)) + (abs-elt-dir (file-name-directory abs-elt-path )) + (kept-file-rel (concat abs-elt-dir + (substring ret (match-beginning 2) (match-end 2)))) + + ;; This is necessary on Windows to get an absolute path, i.e. one + ;; with a drive letter. Note: probably only correct if + ;; unhijacking files in a single snapshot view, mounted on a + ;; drive-letter. + ;; + (kept-file (expand-file-name kept-file-rel))) + (setq kept-files (cons kept-file kept-files))) + (setq start (match-end 0))) + kept-files)) + +(defun clearcase-utl-files-in-same-view-p (files) + (if (< (length files) 2) + t + (let ((v0 (clearcase-fprop-viewtag (nth 0 files))) + (v1 (clearcase-fprop-viewtag (nth 1 files)))) + (if (or (not (stringp v0)) + (not (stringp v1)) + (not (string= v0 v1))) + nil + (clearcase-utl-files-in-same-view-p (cdr files)))))) + +(defun clearcase-unhijack-seq (files) + + ;; Check: there are no directories involved. + ;; + (mapcar + (function + (lambda (file) + (if (file-directory-p file) + (error "Cannot unhijack a directory")))) + files) + + ;; Check: all files are in the same snapshot view. + ;; + ;; (Why ? The output from ct+update only has view-root-relative paths + ;; and we need to obtain absolute paths of renamed-aside hijacks if we are to + ;; dired-relist them.) + ;; + ;; Alternative: partition the set, with each partition containing elements in + ;; the same view. + ;; + (if (not (clearcase-utl-files-in-same-view-p files)) + (error "Can't unhijack files in different views in the same operation")) + + ;; Run the scoped workspace update synchronously. + ;; + (unwind-protect + (progn + (message "Unhijacking...") + (let* ((ret (apply (function clearcase-ct-blocking-call) + (append (list "update" + (if clearcase-keep-unhijacks + "-rename" + "-overwrite") + "-log" clearcase-sink-file-name) + files))) + (snapshot-view-root (clearcase-file-snapshot-root (car files))) + + ;; Scan for renamed-aside files. + ;; + (kept-files (if clearcase-keep-unhijacks + (cleartool-unhijack-parse-for-kept-files ret + snapshot-view-root) + nil))) + + ;; Do post-update synchronisation. + ;; + (mapcar + (function clearcase-sync-after-file-updated-from-vob) + files) + + ;; Update any dired buffers as to the existence of the kept files. + ;; + (if clearcase-keep-unhijacks + (mapcar (function + (lambda (file) + (dired-relist-file file))) + kept-files)))) + ;; unwind + ;; + (message "Unhijacking...done"))) + +;;}}} + +;;{{{ Mkelem + +(defun clearcase-file-ok-to-mkelem (file) + "Test if FILE is okay to mkelem." + (let ((mtype (clearcase-fprop-mtype file))) + (and (not (file-directory-p file)) + (and (or (equal 'view-private-object mtype) + (equal 'derived-object mtype)) + (not (clearcase-fprop-hijacked file)) + (not (clearcase-file-covers-element-p file)))))) + +(defun clearcase-assert-file-ok-to-mkelem (file) + "Raise an exception if FILE is not suitable for mkelem." + (if (not (clearcase-file-ok-to-mkelem file)) + (error "%s cannot be made into an element" file))) + +(defun clearcase-commented-mkelem (file &optional okay-to-checkout-dir-first comment) + "Create a new element from FILE. If OKAY-TO-CHECKOUT-DIR-FIRST is non-nil, +the containing directory will be checked out if necessary. +If COMMENT is non-nil, it will be used, otherwise the user will be prompted +to enter one." + + ;; Pre-condition + ;; + (clearcase-assert-file-ok-to-mkelem file) + + (let ((containing-dir (file-name-directory file))) + + ;; Pre-condition + ;; + (if (not (eq 'directory-version (clearcase-fprop-mtype containing-dir))) + (error "Parent directory of %s is not a ClearCase versioned directory." + file)) + + ;; Determine if we'll need to checkout the parent directory first. + ;; + (let ((dir-checkout-needed (not (clearcase-fprop-checked-out containing-dir)))) + (if dir-checkout-needed + (progn + ;; Parent dir will need to be checked out. Get permission if + ;; appropriate. + ;; + (if (null okay-to-checkout-dir-first) + (setq okay-to-checkout-dir-first + (or (null clearcase-verify-pre-mkelem-dir-checkout) + (y-or-n-p (format "Checkout directory %s " containing-dir))))) + (if (null okay-to-checkout-dir-first) + (error "Can't make an element unless directory is checked-out.")))) + + (if (null comment) + ;; If no comment supplied, go and get one... + ;; + (clearcase-comment-start-entry (file-name-nondirectory file) + "Enter initial comment for the new element." + 'clearcase-commented-mkelem + (list file okay-to-checkout-dir-first) + (find-file-noselect file) + clearcase-initial-mkelem-comment) + + ;; ...otherwise perform the operation. + ;; + + ;; We may need to checkout the directory. + ;; + (if dir-checkout-needed + (clearcase-commented-checkout containing-dir comment)) + + (clearcase-fprop-unstore-properties file) + + (message "Making element %s..." file) + + (save-excursion + ;; Sync the buffer to disk. + ;; + (let ((buffer-on-file (find-buffer-visiting file))) + (if buffer-on-file + (progn + (set-buffer buffer-on-file) + (clearcase-sync-to-disk)))) + + (clearcase-ct-do-cleartool-command "mkelem" + file + comment + (if clearcase-checkin-on-mkelem + (list "-ci"))) + (message "Making element %s...done" file) + + ;; Resync. + ;; + (clearcase-sync-from-disk file t)))))) + +(defun clearcase-commented-mkelem-seq (files &optional comment) + "Mkelem a sequence of FILES. If COMMENT is supplied it will be +used, otherwise the user will be prompted to enter one." + + (mapcar + (function clearcase-assert-file-ok-to-mkelem) + files) + + (if (null comment) + ;; No comment supplied, go and get one... + ;; + (clearcase-comment-start-entry "mkelem" + "Enter comment for elements' creation" + 'clearcase-commented-mkelem-seq + (list files)) + ;; ...otherwise operate. + ;; + (mapcar + (function + (lambda (file) + (clearcase-commented-mkelem file nil comment))) + files))) + +;;}}} + +;;{{{ Checkin + +(defun clearcase-file-ok-to-checkin (file) + "Test if FILE is suitable for checkin." + (let ((me (user-login-name))) + (equal me (clearcase-fprop-owner-of-checkout file)))) + +(defun clearcase-assert-file-ok-to-checkin (file) + "Raise an exception if FILE is not suitable for checkin." + (if (not (clearcase-file-ok-to-checkin file)) + (error "You cannot checkin %s" file))) + +(defun clearcase-commented-checkin (file &optional comment) + "Check-in FILE with COMMENT. If the comment is omitted, +a buffer is popped up to accept one." + + (clearcase-assert-file-ok-to-checkin file) + + (if (null comment) + ;; If no comment supplied, go and get one.. + ;; + (progn + (clearcase-comment-start-entry (file-name-nondirectory file) + "Enter a checkin comment." + 'clearcase-commented-checkin + (list file) + (find-file-noselect file) + (clearcase-fprop-comment file)) + + ;; Also display a diff, if that is the custom: + ;; + (if (and (not (file-directory-p file)) + clearcase-diff-on-checkin) + (save-excursion + (let ((tmp-buffer (current-buffer))) + (message "Running diff...") + (clearcase-diff-file-with-version file + (clearcase-fprop-predecessor-version file)) + (message "Running diff...done") + (set-buffer "*clearcase*") + (if (get-buffer "*clearcase-diff*") + (kill-buffer "*clearcase-diff*")) + (rename-buffer "*clearcase-diff*") + (pop-to-buffer tmp-buffer))))) + + ;; ...otherwise perform the operation. + ;; + (message "Checking in %s..." file) + (save-excursion + ;; Sync the buffer to disk, and get local value of clearcase-checkin-arguments + ;; + (let ((buffer-on-file (find-buffer-visiting file))) + (if buffer-on-file + (progn + (set-buffer buffer-on-file) + (clearcase-sync-to-disk)))) + (clearcase-ct-do-cleartool-command "ci" + file + comment + clearcase-checkin-arguments)) + (message "Checking in %s...done" file) + + ;; Resync. + ;; + (clearcase-sync-from-disk file t))) + +(defun clearcase-commented-checkin-seq (files &optional comment) + "Checkin a sequence of FILES. If COMMENT is supplied it will be +used, otherwise the user will be prompted to enter one." + + ;; Check they're all in the right state to be checked-in. + ;; + (mapcar + (function clearcase-assert-file-ok-to-checkin) + files) + + (if (null comment) + ;; No comment supplied, go and get one... + ;; + (clearcase-comment-start-entry "checkin" + "Enter checkin comment." + 'clearcase-commented-checkin-seq + (list files)) + ;; ...otherwise operate. + ;; + (mapcar + (function + (lambda (file) + (clearcase-commented-checkin file comment))) + files))) + +;;}}} + +;;{{{ Checkout + +(defun clearcase-file-ok-to-checkout (file) + "Test if FILE is suitable for checkout." + (let ((mtype (clearcase-fprop-mtype file))) + (and (or (eq 'version mtype) + (eq 'directory-version mtype) + (clearcase-fprop-hijacked file)) + (not (clearcase-fprop-checked-out file))))) + +(defun clearcase-assert-file-ok-to-checkout (file) + "Raise an exception if FILE is not suitable for checkout." + (if (not (clearcase-file-ok-to-checkout file)) + (error "You cannot checkout %s" file))) + +;; nyi: Offer to setact if appropriate + +(defun clearcase-commented-checkout (file &optional comment) + "Check-out FILE with COMMENT. If the comment is omitted, +a buffer is popped up to accept one." + + (clearcase-assert-file-ok-to-checkout file) + + (if (and (null comment) + (not clearcase-suppress-checkout-comments)) + ;; If no comment supplied, go and get one... + ;; + (clearcase-comment-start-entry (file-name-nondirectory file) + "Enter a checkout comment." + 'clearcase-commented-checkout + (list file) + (find-file-noselect file)) + + ;; ...otherwise perform the operation. + ;; + (message "Checking out %s..." file) + ;; Change buffers to get local value of clearcase-checkin-arguments. + ;; + (save-excursion + (set-buffer (or (find-buffer-visiting file) + (current-buffer))) + (clearcase-ct-do-cleartool-command "co" + file + comment + clearcase-checkout-arguments)) + (message "Checking out %s...done" file) + + ;; Resync. + ;; + (clearcase-sync-from-disk file t))) + + +(defun clearcase-commented-checkout-seq (files &optional comment) + "Checkout a sequence of FILES. If COMMENT is supplied it will be +used, otherwise the user will be prompted to enter one." + + (mapcar + (function clearcase-assert-file-ok-to-checkout) + files) + + (if (and (null comment) + (not clearcase-suppress-checkout-comments)) + ;; No comment supplied, go and get one... + ;; + (clearcase-comment-start-entry "checkout" + "Enter a checkout comment." + 'clearcase-commented-checkout-seq + (list files)) + ;; ...otherwise operate. + ;; + (mapcar + (function + (lambda (file) + (clearcase-commented-checkout file comment))) + files))) + +;;}}} + +;;{{{ Uncheckout + +(defun clearcase-file-ok-to-uncheckout (file) + "Test if FILE is suitable for uncheckout." + (equal (user-login-name) + (clearcase-fprop-owner-of-checkout file))) + +(defun clearcase-assert-file-ok-to-uncheckout (file) + "Raise an exception if FILE is not suitable for uncheckout." + (if (not (clearcase-file-ok-to-uncheckout file)) + (error "You cannot uncheckout %s" file))) + +(defun cleartool-unco-parse-for-kept-file (ret) + ;;Private version of "foo" saved in "foo.keep.1" + (if (string-match "^Private version of .* saved in \"\\([^\"]+\\)\"\\.$" ret) + (substring ret (match-beginning 1) (match-end 1)) + nil)) + +(defun clearcase-uncheckout (file) + "Uncheckout FILE." + + (clearcase-assert-file-ok-to-uncheckout file) + + ;; If it has changed since checkout, insist the user confirm. + ;; + (if (and (not (file-directory-p file)) + (clearcase-file-appears-modified-since-checkout-p file) + (not clearcase-suppress-confirm) + (not (yes-or-no-p (format "Really discard changes to %s ?" file)))) + (message "Uncheckout of %s cancelled" file) + + ;; Go ahead and unco. + ;; + (message "Cancelling checkout of %s..." file) + ;; nyi: + ;; - Prompt for -keep or -rm + ;; - offer to remove /0 branches + ;; + (let* ((ret (clearcase-ct-blocking-call "unco" + (if clearcase-keep-uncheckouts + "-keep" + "-rm") + file)) + ;; Discover the name of the saved. + ;; + (kept-file (if clearcase-keep-uncheckouts + (cleartool-unco-parse-for-kept-file ret) + nil))) + + (if kept-file + (message "Checkout of %s cancelled (saved in %s)" + (file-name-nondirectory kept-file) + file) + (message "Cancelling checkout of %s...done" file)) + + ;; Sync any buffers over the file itself. + ;; + (clearcase-sync-from-disk file t) + + ;; Update any dired buffers as to the existence of the kept file. + ;; + (if kept-file + (dired-relist-file kept-file))))) + +(defun clearcase-uncheckout-seq (files) + "Uncheckout a sequence of FILES." + + (mapcar + (function clearcase-assert-file-ok-to-uncheckout) + files) + + (mapcar + (function clearcase-uncheckout) + files)) + +;;}}} + +;;{{{ Describe + +(defun clearcase-describe (file) + "Give a ClearCase description of FILE." + + (clearcase-utl-populate-and-view-buffer + "*clearcase*" + (list file) + (function + (lambda (file) + (clearcase-ct-do-cleartool-command "describe" file 'unused))))) + +(defun clearcase-describe-seq (files) + "Give a ClearCase description of the sequence of FILES." + (error "Not yet implemented")) + +;;}}} + +;;{{{ Mkbrtype + +(defun clearcase-commented-mkbrtype (typename &optional comment) + (if (null comment) + (clearcase-comment-start-entry (format "mkbrtype:%s" typename) + "Enter a comment for the new branch type." + 'clearcase-commented-mkbrtype + (list typename)) + (clearcase-with-tempfile + comment-file + (write-region comment nil comment-file nil 'noprint) + (let ((qualified-typename typename)) + (if (not (string-match "@" typename)) + (setq qualified-typename + (format "%s@%s" typename default-directory))) + + (clearcase-ct-cleartool-cmd "mkbrtype" + "-cfile" + (clearcase-path-native comment-file) + qualified-typename))))) + +;;}}} + +;;{{{ Browse vtree (using Dired Mode) + +(defun clearcase-file-ok-to-browse (file) + (and file + (or (equal 'version (clearcase-fprop-mtype file)) + (equal 'directory-version (clearcase-fprop-mtype file))) + (clearcase-file-is-in-mvfs-p file))) + +(defun clearcase-browse-vtree (file) + (if (not (clearcase-fprop-file-is-version-p file)) + (error "%s is not a Clearcase element" file)) + + (if (not (clearcase-file-is-in-mvfs-p file)) + (error "File is not in MVFS")) + + (let* ((version-path (clearcase-vxpath-cons-vxpath + file + (or (clearcase-vxpath-version-part file) + (clearcase-fprop-version file)))) + ;; nyi: Can't seem to get latest first here. + ;; + (dired-listing-switches (concat dired-listing-switches + "rt")) + + (branch-path (clearcase-vxpath-branch version-path)) + + ;; Position cursor to the version we came from. + ;; If it was checked-out, go to predecessor. + ;; + (version-number (clearcase-vxpath-version + (if (clearcase-fprop-checked-out file) + (clearcase-fprop-predecessor-version file) + version-path)))) + + (if (file-exists-p version-path) + (progn + ;; Invoke dired on the directory of the version branch. + ;; + (dired branch-path) + + (clearcase-dired-sort-by-date) + + (if (re-search-forward (concat "[ \t]+" + "\\(" + (regexp-quote version-number) + "\\)" + "$") + nil + t) + (goto-char (match-beginning 1)))) + (dired (concat file clearcase-vxpath-glue)) + + ;; nyi: We want ANY directory in the history tree to appear with + ;; newest first. Probably requires a hook to dired mode. + ;; + (clearcase-dired-sort-by-date)))) + +;;}}} + +;;{{{ List history + +(defun clearcase-list-history (file) + "List the change history of FILE. + +FILE can be a file or a directory. If it is a directory, only the information +on the directory element itself is listed, not on its contents." + + (let ((mtype (clearcase-fprop-mtype file))) + (if (or (eq mtype 'version) + (eq mtype 'directory-version)) + (progn + (message "Listing element history...") + + (clearcase-utl-populate-and-view-buffer + "*clearcase*" + (list file) + (function + (lambda (file) + (clearcase-ct-do-cleartool-command "lshistory" + file + 'unused + (if (eq mtype 'directory-version) + (list "-d"))) + (setq default-directory (file-name-directory file)) + (while (looking-at "=3D*\n") + (delete-char (- (match-end 0) (match-beginning 0))) + (forward-line -1)) + (goto-char (point-min)) + (if (looking-at "[\b\t\n\v\f\r ]+") + (delete-char (- (match-end 0) (match-beginning 0))))))) + (message "Listing element history...done")) + + (error "%s is not a ClearCase element" file)))) + +;;}}} + +;;{{{ Diff/cmp + +(defun clearcase-files-are-identical (f1 f2) + "Test if FILE1 and FILE2 have identical contents." + + (clearcase-when-debugging + (if (not (file-exists-p f1)) + (error "%s non-existent" f1)) + (if (not (file-exists-p f2)) + (error "%s non-existent" f2))) + + (zerop (call-process "cleardiff" nil nil nil "-status_only" f1 f2))) + +(defun clearcase-diff-files (file1 file2) + "Run cleardiff on FILE1 and FILE2 and display the differences." + (if clearcase-use-normal-diff + (clearcase-do-command 2 + clearcase-normal-diff-program + file2 + (append clearcase-normal-diff-arguments + (list file1))) + (clearcase-do-command 2 + "cleardiff" + file2 + (list "-diff_format" file1))) + (let ((diff-size (save-excursion + (set-buffer "*clearcase*") + (buffer-size)))) + (if (zerop diff-size) + (message "No differences") + (clearcase-port-view-buffer-other-window "*clearcase*") + (goto-char 0) + (shrink-window-if-larger-than-buffer)))) + +;;}}} + +;;{{{ What rule + +(defun clearcase-what-rule (file) + (let ((result (clearcase-ct-cleartool-cmd "ls" + "-d" + (clearcase-path-native file)))) + (if (string-match "Rule: \\(.*\\)\n" result) + (message (substring result + ;; Be a little more verbose + (match-beginning 0) (match-end 1))) + (error result)))) + +;;}}} + +;;}}} + +;;{{{ File property cache + +;; ClearCase properties of files are stored in a vector in a hashtable with the +;; absolute-filename (with no trailing slashes) as the lookup key. +;; +;; Properties are: +;; +;; [0] truename : string +;; [1] mtype : { nil, view-private-object, version, +;; directory-version, file-element, +;; dir-element, derived-object +;; } +;; [2] checked-out : boolean +;; [3] reserved : boolean +;; [4] version : string +;; [5] predecessor-version : string +;; [6] oid : string +;; [7] user : string +;; [8] date : string (yyyymmdd.hhmmss) +;; [9] time-last-described : (N, N, N) time when the properties were last read +;; from ClearCase +;; [10] viewtag : string +;; [11] comment : string +;; [12] slink-text : string (empty string if not symlink) +;; [13] hijacked : boolean + +;; nyi: other possible properties to record: +;; mtime when last described (lets us know when the cached properties +;; might be stale) + +;;{{{ Debug code + +(defun clearcase-fprop-unparse-properties (properties) + "Return a string suitable for printing PROPERTIES." + (concat + (format "truename: %s\n" (aref properties 0)) + (format "mtype: %s\n" (aref properties 1)) + (format "checked-out: %s\n" (aref properties 2)) + (format "reserved: %s\n" (aref properties 3)) + (format "version: %s\n" (aref properties 4)) + (format "predecessor-version: %s\n" (aref properties 5)) + (format "oid: %s\n" (aref properties 6)) + (format "user: %s\n" (aref properties 7)) + (format "date: %s\n" (aref properties 8)) + (format "time-last-described: %s\n" (current-time-string (aref properties 9))) + (format "viewtag: %s\n" (aref properties 10)) + (format "comment: %s\n" (aref properties 11)) + (format "slink-text: %s\n" (aref properties 12)) + (format "hijacked: %s\n" (aref properties 13)))) + +(defun clearcase-fprop-display-properties (file) + "Display the recorded ClearCase properties of FILE." + (interactive "F") + (let* ((abs-file (expand-file-name file)) + (properties (clearcase-fprop-lookup-properties abs-file))) + (if properties + (let ((unparsed-properties (clearcase-fprop-unparse-properties properties))) + (clearcase-utl-populate-and-view-buffer + "*clearcase*" + nil + (function (lambda () + (insert unparsed-properties))))) + (error "Properties for %s not stored" file)))) + +(defun clearcase-fprop-dump-to-current-buffer () + "Dump to the current buffer the table recording ClearCase properties of files." + (interactive) + (insert (format "File describe count: %s\n" clearcase-fprop-describe-count)) + (mapatoms + (function + (lambda (symbol) + (let ((properties (symbol-value symbol))) + (insert "\n" + (format "key: %s\n" (symbol-name symbol)) + "\n" + (clearcase-fprop-unparse-properties properties))))) + clearcase-fprop-hashtable) + (insert "\n")) + +(defun clearcase-fprop-dump () + (interactive) + (clearcase-utl-populate-and-view-buffer + "*clearcase*" + nil + (function (lambda () + (clearcase-fprop-dump-to-current-buffer))))) + +;;}}} + +(defvar clearcase-fprop-hashtable (make-vector 31 0) + "Obarray for per-file ClearCase properties.") + +(defun clearcase-fprop-canonicalise-path (filename) + ;; We want DIR/y and DIR\y to map to the same cache entry on ms-windows. + ;; We want DIR and DIR/ (and on windows DIR\) to map to the same cache entry. + ;; + ;; However, on ms-windows avoid canonicalising X:/ to X: because, for some + ;; reason, cleartool+desc fails on X:, but works on X:/ + ;; + (setq filename (clearcase-path-canonicalise-slashes filename)) + (if (and clearcase-on-mswindows + (string-match (concat "^" "[A-Za-z]:" clearcase-pname-sep-regexp "$") + filename)) + filename + (clearcase-utl-strip-trailing-slashes filename))) + +(defun clearcase-fprop-clear-all-properties () + "Delete all entries in the clearcase-fprop-hashtable." + (setq clearcase-fprop-hashtable (make-vector 31 0))) + +(defun clearcase-fprop-store-properties (file properties) + "For FILE, store its ClearCase PROPERTIES in the clearcase-fprop-hashtable." + (assert (file-name-absolute-p file)) + (set (intern (clearcase-fprop-canonicalise-path file) + clearcase-fprop-hashtable) properties)) + +(defun clearcase-fprop-unstore-properties (file) + "For FILE, delete its entry in the clearcase-fprop-hashtable." + (assert (file-name-absolute-p file)) + (unintern (clearcase-fprop-canonicalise-path file) clearcase-fprop-hashtable)) + +(defun clearcase-fprop-lookup-properties (file) + "For FILE, lookup and return its ClearCase properties from the +clearcase-fprop-hashtable." + (assert (file-name-absolute-p file)) + (symbol-value (intern-soft (clearcase-fprop-canonicalise-path file) + clearcase-fprop-hashtable))) + +(defun clearcase-fprop-get-properties (file) + "For FILE, make sure its ClearCase properties are in the hashtable +and then return them." + (or (clearcase-fprop-lookup-properties file) + (let ((properties + (condition-case signal-info + (clearcase-fprop-read-properties file) + (error + (progn + (clearcase-trace (format "(clearcase-fprop-read-properties %s) signalled error: %s" + file + (cdr signal-info))) + (make-vector 31 nil)))))) + (clearcase-fprop-store-properties file properties) + properties))) + +(defun clearcase-fprop-truename (file) + "For FILE, return its \"truename\" ClearCase property." + (aref (clearcase-fprop-get-properties file) 0)) + +(defun clearcase-fprop-mtype (file) + "For FILE, return its \"mtype\" ClearCase property." + (aref (clearcase-fprop-get-properties file) 1)) + +(defun clearcase-fprop-checked-out (file) + "For FILE, return its \"checked-out\" ClearCase property." + (aref (clearcase-fprop-get-properties file) 2)) + +(defun clearcase-fprop-reserved (file) + "For FILE, return its \"reserved\" ClearCase property." + (aref (clearcase-fprop-get-properties file) 3)) + +(defun clearcase-fprop-version (file) + "For FILE, return its \"version\" ClearCase property." + (aref (clearcase-fprop-get-properties file) 4)) + +(defun clearcase-fprop-predecessor-version (file) + "For FILE, return its \"predecessor-version\" ClearCase property." + (aref (clearcase-fprop-get-properties file) 5)) + +(defun clearcase-fprop-oid (file) + "For FILE, return its \"oid\" ClearCase property." + (aref (clearcase-fprop-get-properties file) 6)) + +(defun clearcase-fprop-user (file) + "For FILE, return its \"user\" ClearCase property." + (aref (clearcase-fprop-get-properties file) 7)) + +(defun clearcase-fprop-date (file) + "For FILE, return its \"date\" ClearCase property." + (aref (clearcase-fprop-get-properties file) 8)) + +(defun clearcase-fprop-time-last-described (file) + "For FILE, return its \"time-last-described\" ClearCase property." + (aref (clearcase-fprop-get-properties file) 9)) + +(defun clearcase-fprop-viewtag (file) + "For FILE, return its \"viewtag\" ClearCase property." + (aref (clearcase-fprop-get-properties file) 10)) + +(defun clearcase-fprop-comment (file) + "For FILE, return its \"comment\" ClearCase property." + (aref (clearcase-fprop-get-properties file) 11)) + +(defun clearcase-fprop-vob-slink-text (file) + "For FILE, return its \"slink-text\" ClearCase property." + (aref (clearcase-fprop-get-properties file) 12)) + +(defun clearcase-fprop-hijacked (file) + "For FILE, return its \"hijacked\" ClearCase property." + (aref (clearcase-fprop-get-properties file) 13)) + +(defun clearcase-fprop-set-comment (file comment) + "For FILE, set its \"comment\" ClearCase property to COMMENT." + (aset (clearcase-fprop-get-properties file) 11 comment)) + +(defun clearcase-fprop-owner-of-checkout (file) + "For FILE, return whether the current user has it checked-out." + (if (clearcase-fprop-checked-out file) + (clearcase-fprop-user file) + nil)) + +(defun clearcase-fprop-file-is-vob-slink-p (object-name) + (not (zerop (length (clearcase-fprop-vob-slink-text object-name))))) + +(defun clearcase-fprop-file-is-version-p (object-name) + (if object-name + (let ((mtype (clearcase-fprop-mtype object-name))) + (or (eq 'version mtype) + (eq 'directory-version mtype))))) + +;; Read the object's ClearCase properties using cleartool and the Lisp reader. +;; +;; nyi: for some reason the \n before the %c necessary here so avoid confusing the +;; cleartool/tq interface. Completely mysterious. Arrived at by +;; trial and error. +;; +(defvar clearcase-fprop-fmt-string + + ;; Yuck. Different forms of quotation are needed here apparently to deal with + ;; all the various ways of spawning sub-process on the the various platforms + ;; (XEmacs vs. GnuEmacs, Win32 vs. Unix, Cygwin-built vs. native-built). + ;; + (if clearcase-on-mswindows + (if clearcase-xemacs-p + ;; XEmacs/Windows + ;; + (if clearcase-on-cygwin + ;; Cygwin build + ;; + "[nil \\\"%m\\\" \\\"%f\\\" \\\"%Rf\\\" \\\"%Sn\\\" \\\"%PSn\\\" \\\"%On\\\" \\\"%u\\\" \\\"%Nd\\\" nil nil nil \\\"%[slink_text]p\\\" nil ]\\n%c" + ;; Native build + ;; + "[nil \\\"%m\\\" \\\"%f\\\" \\\"%Rf\\\" \\\"%Sn\\\" \\\"%PSn\\\" \\\"%On\\\" \\\"%u\\\" \\\"%Nd\\\" nil nil nil \\\"%[slink_text]p\\\" nil]\n%c") + + ;; GnuEmacs/Windows + ;; + "[nil \"%m\" \"%f\" \"%Rf\" \"%Sn\" \"%PSn\" \"%On\" \"%u\" \"%Nd\" nil nil nil \"%[slink_text]p\" nil]\\n%c") + + ;; Unix + ;; + "'[nil \"%m\" \"%f\" \"%Rf\" \"%Sn\" \"%PSn\" \"%On\" \"%u\" \"%Nd\" nil nil nil \"%[slink_text]p\" nil]\\n%c'") + + "Format for cleartool+describe command when reading the +ClearCase properties of a file") + +(defvar clearcase-fprop-describe-count 0 + "Count the number of times clearcase-fprop-read-properties is called") + +(defun clearcase-fprop-read-properties (file) + "Invoke the cleartool+describe command to obtain the ClearCase +properties of FILE." + (assert (file-name-absolute-p file)) + (let* ((truename (clearcase-fprop-canonicalise-path (file-truename (expand-file-name file))))) + + ;; If the object doesn't exist, signal an error + ;; + (if (or (not (file-exists-p (clearcase-vxpath-element-part file))) + (not (file-exists-p (clearcase-vxpath-element-part truename)))) + (error "File doesn't exist: %s" file) + + ;; Run cleartool+ describe and capture the output as a string: + ;; + (let ((desc-string (clearcase-ct-cleartool-cmd "desc" + "-fmt" + clearcase-fprop-fmt-string + (clearcase-path-native truename)))) + (setq clearcase-fprop-describe-count (1+ clearcase-fprop-describe-count)) + + ;;(clearcase-trace (format "desc of %s <<<<" truename)) + ;;(clearcase-trace desc-string) + ;;(clearcase-trace (format "desc of %s >>>>" truename)) + + ;; Read all but the comment, using the Lisp reader, and then copy + ;; what's left as the comment. We don't try to use the Lisp reader to + ;; fetch the comment to avoid problems with quotation. + ;; + ;; nyi: it would be nice if we could make cleartool use "/" as pname-sep, + ;; because read-from-string will barf on imbedded "\". For now + ;; run clearcase-path-canonicalise-slashes over the cleartool + ;; output before invoking the Lisp reader. + ;; + (let* ((first-read (read-from-string (clearcase-path-canonicalise-slashes desc-string))) + (result (car first-read)) + (bytes-read (cdr first-read)) + (comment (substring desc-string (1+ bytes-read)))) ;; skip \n + + ;; Plug in the slots I left empty: + ;; + (aset result 0 truename) + (aset result 9 (current-time)) + + (aset result 11 comment) + + ;; Convert mtype to an enumeration: + ;; + (let ((mtype-string (aref result 1))) + (cond + ((string= mtype-string "version") + (aset result 1 'version)) + + ((string= mtype-string "directory version") + (aset result 1 'directory-version)) + + ((string= mtype-string "view private object") + (aset result 1 'view-private-object) + + ;; If we're in a snapshot see if it is hijacked by running + ;; ct+desc FILE@@. No error indicates it's hijacked. + ;; + (if (clearcase-file-would-be-in-snapshot-p truename) + (aset result 13 + (condition-case nil + (stringp + (clearcase-ct-cleartool-cmd + "desc" + "-short" + (concat (clearcase-path-native truename) + clearcase-vxpath-glue))) + (error nil))))) + + ((string= mtype-string "file element") + (aset result 1 'file-element)) + + ((string= mtype-string "directory element") + (aset result 1 'directory-element)) + + ((string= mtype-string "derived object") + (aset result 1 'derived-object)) + + ;; For now treat checked-in DOs as versions. + ;; + ((string= mtype-string "derived object version") + (aset result 1 'version)) + + ;; On NT, coerce the mtype of symlinks into that + ;; of their targets. + ;; + ;; nyi: I think this is approximately right. + ;; + ((and (string= mtype-string "symbolic link") + clearcase-on-mswindows) + (if (file-directory-p truename) + (aset result 1 'directory-version) + (aset result 1 'version))) + + ;; We get this on paths like foo.c@@/main + ;; + ((string= mtype-string "branch") + (aset result 1 'branch)) + + ((string= mtype-string "**null meta type**") + (aset result 1 nil)) + + (t + (error "Unknown mtype returned by cleartool+describe: %s" + mtype-string)))) + + ;; nyi: possible efficiency win: only evaluate the viewtag on demand. + ;; + (if (aref result 1) + (aset result 10 (clearcase-file-viewtag truename))) + + ;; Convert checked-out field to boolean: + ;; + (aset result 2 (not (zerop (length (aref result 2))))) + + ;; Convert reserved field to boolean: + ;; + (aset result 3 (string= "reserved" (aref result 3))) + + ;; Return the array of properties. + ;; + result))))) + +;;}}} + +;;{{{ View property cache + +;; ClearCase properties of views are stored in a vector in a hashtable +;; with the viewtag as the lookup key. +;; +;; Properties are: +;; +;; [0] ucm : boolean +;; [1] stream : string +;; [2] pvob : string +;; [3] activities : list of strings +;; [4] current-activity : string + +;;{{{ Debug code + +(defun clearcase-vprop-dump-to-current-buffer () + "Dump to the current buffer the table recording ClearCase properties of views." + (insert (format "View describe count: %s\n" clearcase-vprop-describe-count)) + (mapatoms + (function + (lambda (symbol) + (let ((properties (symbol-value symbol))) + (insert "\n" + (format "viewtag: %s\n" (symbol-name symbol)) + "\n" + (clearcase-vprop-unparse-properties properties))))) + clearcase-vprop-hashtable) + (insert "\n")) + +(defun clearcase-vprop-dump () + (interactive) + (clearcase-utl-populate-and-view-buffer + "*clearcase*" + nil + (function (lambda () + (clearcase-vprop-dump-to-current-buffer))))) + +(defun clearcase-vprop-unparse-properties (properties) + "Return a string suitable for printing PROPERTIES." + (concat + (format "ucm: %s\n" (aref properties 0)) + (format "stream: %s\n" (aref properties 1)) + (format "pvob: %s\n" (aref properties 2)) + (format "activities: %s\n" (aref properties 3)) + (format "current-activity: %s\n" (aref properties 4)))) + +;;}}} + +;;{{{ Asynchronously fetching view properties: + +(defvar clearcase-vprop-timer nil) +(defvar clearcase-vprop-work-queue nil) + +(defun clearcase-vprop-schedule-work (viewtag) + ;; Add to the work queue. + ;; + (setq clearcase-vprop-work-queue (cons viewtag + clearcase-vprop-work-queue)) + ;; Create the timer if necessary. + ;; + (if (null clearcase-vprop-timer) + (if clearcase-xemacs-p + ;; Xemacs + ;; + (setq clearcase-vprop-timer + (run-with-idle-timer 5 t 'clearcase-vprop-timer-function)) + ;; FSF Emacs + ;; + (progn + (setq clearcase-vprop-timer (timer-create)) + (timer-set-function clearcase-vprop-timer 'clearcase-vprop-timer-function) + (timer-set-idle-time clearcase-vprop-timer 5) + (timer-activate-when-idle clearcase-vprop-timer))))) + +(defun clearcase-vprop-timer-function () + ;; Process the work queue and empty it. + ;; + (mapcar (function (lambda (viewtag) + (clearcase-vprop-get-properties viewtag))) + clearcase-vprop-work-queue) + (setq clearcase-vprop-work-queue nil) + + ;; Cancel the timer. + ;; + (cancel-timer clearcase-vprop-timer) + (setq clearcase-vprop-timer nil)) + +;;}}} + +(defvar clearcase-vprop-hashtable (make-vector 31 0) + "Obarray for per-view ClearCase properties.") + +(defun clearcase-vprop-clear-all-properties () + "Delete all entries in the clearcase-vprop-hashtable." + (setq clearcase-vprop-hashtable (make-vector 31 0))) + +(defun clearcase-vprop-store-properties (viewtag properties) + "For VIEW, store its ClearCase PROPERTIES in the clearcase-vprop-hashtable." + (set (intern viewtag clearcase-vprop-hashtable) properties)) + +(defun clearcase-vprop-unstore-properties (viewtag) + "For VIEWTAG, delete its entry in the clearcase-vprop-hashtable." + (unintern viewtag clearcase-vprop-hashtable)) + +(defun clearcase-vprop-lookup-properties (viewtag) + "For VIEWTAG, lookup and return its ClearCase properties from the +clearcase-vprop-hashtable." + (symbol-value (intern-soft viewtag clearcase-vprop-hashtable))) + +(defun clearcase-vprop-get-properties (viewtag) + "For VIEWTAG, make sure it's ClearCase properties are in the hashtable +and then return them." + (or (clearcase-vprop-lookup-properties viewtag) + (let ((properties (clearcase-vprop-read-properties viewtag))) + (clearcase-vprop-store-properties viewtag properties) + properties))) + +(defun clearcase-vprop-ucm (viewtag) + "For VIEWTAG, return its \"ucm\" ClearCase property." + (aref (clearcase-vprop-get-properties viewtag) 0)) + +(defun clearcase-vprop-stream (viewtag) + "For VIEWTAG, return its \"stream\" ClearCase property." + (aref (clearcase-vprop-get-properties viewtag) 1)) + +(defun clearcase-vprop-pvob (viewtag) + "For VIEWTAG, return its \"stream\" ClearCase property." + (aref (clearcase-vprop-get-properties viewtag) 2)) + +(defun clearcase-vprop-activities (viewtag) + "For VIEWTAG, return its \"activities\" ClearCase property." + + ;; If the activity set has been flushed, go and schedule a re-fetch. + ;; + (let ((properties (clearcase-vprop-get-properties viewtag))) + (if (null (aref properties 3)) + (aset properties 3 (clearcase-vprop-read-activities-asynchronously viewtag)))) + + ;; Now poll, waiting for the activities to be available. + ;; + (let ((loop-count 0)) + ;; If there is a background process still reading the activities, + ;; wait for it to finish. + ;; + ;; nyi: probably want a timeout here. + ;; + ;; nyi: There seems to be a race on NT in accept-process-output so that + ;; we would wait forever. + ;; + (if (not clearcase-on-mswindows) + ;; Unix synchronization with the end of the process + ;; which is reading activities. + ;; + (while (bufferp (aref (clearcase-vprop-get-properties viewtag) 3)) + (save-excursion + (set-buffer (aref (clearcase-vprop-get-properties viewtag) 3)) + (message "Reading activity list...") + (setq loop-count (1+ loop-count)) + (accept-process-output clearcase-vprop-async-proc))) + + ;; NT synchronization with the end of the process which is reading + ;; activities. + ;; + ;; Unfortunately on NT we can't rely on the process sentinel being called + ;; so we have to explicitly test the process status. + ;; + (while (bufferp (aref (clearcase-vprop-get-properties viewtag) 3)) + (message "Reading activity list...") + (save-excursion + (set-buffer (aref (clearcase-vprop-get-properties viewtag) 3)) + (if (or (not (processp clearcase-vprop-async-proc)) + (eq 'exit (process-status clearcase-vprop-async-proc))) + + ;; The process has finished or gone away and apparently + ;; the sentinel didn't get called which would have called + ;; clearcase-vprop-finish-reading-activities, so call it + ;; explicitly here. + ;; + (clearcase-vprop-finish-reading-activities (current-buffer)) + + ;; The process is apparently still running, so wait + ;; so more. + (setq loop-count (1+ loop-count)) + (sit-for 1))))) + + (if (not (zerop loop-count)) + (message "Reading activity list...done")) + + (aref (clearcase-vprop-get-properties viewtag) 3))) + +(defun clearcase-vprop-current-activity (viewtag) + "For VIEWTAG, return its \"current-activity\" ClearCase property." + (aref (clearcase-vprop-get-properties viewtag) 4)) + +(defun clearcase-vprop-set-activities (viewtag activities) + "For VIEWTAG, set its \"activities\" ClearCase property to ACTIVITIES." + (let ((properties (clearcase-vprop-lookup-properties viewtag))) + ;; We must only set the activities for an existing vprop entry. + ;; + (assert properties) + (aset properties 3 activities))) + +(defun clearcase-vprop-flush-activities (viewtag) + "For VIEWTAG, set its \"activities\" ClearCase property to nil, +to cause a future re-fetch." + (clearcase-vprop-set-activities viewtag nil)) + +(defun clearcase-vprop-set-current-activity (viewtag activity) + "For VIEWTAG, set its \"current-activity\" ClearCase property to ACTIVITY." + (aset (clearcase-vprop-get-properties viewtag) 4 activity)) + +;; Read the object's ClearCase properties using cleartool lsview and cleartool lsstream. + +(defvar clearcase-vprop-describe-count 0 + "Count the number of times clearcase-vprop-read-properties is called") + +(defvar clearcase-lsstream-fmt-string + (if clearcase-on-mswindows + (if clearcase-xemacs-p + ;; XEmacs/Windows + ;; + (if clearcase-on-cygwin + ;; Cygwin build + ;; + "[\\\"%n\\\" \\\"%[master]p\\\" ]" + ;; Native build + ;; + "[\\\"%n\\\" \\\"%[master]p\\\" ]") + ;; GnuEmacs/Windows + ;; + "[\"%n\" \"%[master]p\" ]") + ;; Unix + ;; + "'[\"%n\" \"%[master]p\" ]'")) + +(defun clearcase-vprop-read-properties (viewtag) + "Invoke cleartool commands to obtain the ClearCase +properties of VIEWTAG." + + ;; We used to use "ct+lsview -properties -full TAG", but this seemed to take + ;; a long time in some circumstances. It appears to be because the + ;; ADM_VIEW_GET_INFO RPC can take up to 60 seconds in certain circumstances + ;; (typically on my laptop with self-contained ClearCase region). + + ;; Accordingly, since we don't really need to store snapshotness, the minimum + ;; we really need to discover about a view is whether it is UCM-attached. For + ;; this the much faster ct+lsstream suffices. + ;; + (let* ((result (make-vector 5 nil))) + (if (not clearcase-v3) + (let ((ucm nil) + (stream nil) + (pvob nil) + (activity-names nil) + (activity-titles nil) + (activities nil) + (current-activity nil) + (ret "")) + + ;; This was necessary to make sure the "done" message was always + ;; displayed. Not quite sure why. + ;; + (unwind-protect + (progn + (message "Reading view properties...") + (setq ret (clearcase-ct-blocking-call "lsstream" "-fmt" + clearcase-lsstream-fmt-string + "-view" viewtag)) + + (setq clearcase-vprop-describe-count (1+ clearcase-vprop-describe-count)) + + (if (setq ucm (not (zerop (length ret)))) + + ;; It's apparently a UCM view + ;; + (let* ((first-read (read-from-string (clearcase-utl-escape-backslashes ret))) + (array-read (car first-read)) + (bytes-read (cdr first-read))) + + ;; Get stream name + ;; + (setq stream (aref array-read 0)) + + ;; Get PVOB tag from something like "unix@/vobs/projects" + ;; + (let ((s (aref array-read 1))) + (if (string-match "@" s) + (setq pvob (substring s (match-end 0))) + (setq pvob s))) + + ;; Get the activity list and store as a list of (NAME . TITLE) pairs + ;; + (setq activities (clearcase-vprop-read-activities-asynchronously viewtag)) + + ;; Get the current activity + ;; + (let ((name-string (clearcase-ct-blocking-call "lsact" "-cact" "-fmt" "%n" + "-view" viewtag))) + (if (not (zerop (length name-string))) + (setq current-activity name-string))) + + (aset result 0 ucm) + (aset result 1 stream) + (aset result 2 pvob) + (aset result 3 activities) + (aset result 4 current-activity)))) + + (message "Reading view properties...done")))) + + result)) + +(defvar clearcase-vprop-async-viewtag nil) +(defvar clearcase-vprop-async-proc nil) +(defun clearcase-vprop-read-activities-asynchronously (viewtag) + (let ((buf-name (format "*clearcase-activities-%s*" viewtag))) + ;; Clean up old instance of the buffer we use to fetch activities: + ;; + (let ((buf (get-buffer buf-name))) + (if buf + (progn + (save-excursion + (set-buffer buf) + (if (and (boundp 'clearcase-vprop-async-proc) + clearcase-vprop-async-proc) + (condition-case nil + (kill-process clearcase-vprop-async-proc) + (error nil)))) + (kill-buffer buf)))) + + ;; Create a buffer and an associated new process to read activities in the + ;; background. We return the buffer to be stored in the activities field of + ;; the view-properties record. The function clearcase-vprop-activities will + ;; recognise when the asynch fetching is still underway and wait for it to + ;; finish. + ;; + ;; The process has a sentinel function which is supposed to get called when + ;; the process finishes. This sometimes doesn't happen on Windows, so that + ;; clearcase-vprop-activities has to do a bit more work. (Perhaps a race + ;; exists: the process completes before the sentinel can be set ?) + ;; + (let* ((buf (get-buffer-create buf-name)) + (proc (start-process (format "*clearcase-activities-process-%s*" viewtag) + buf + clearcase-cleartool-path + "lsact" "-view" viewtag))) + (process-kill-without-query proc) + (save-excursion + (set-buffer buf) + ;; Create a sentinel to parse and store the activities when the + ;; process finishes. We record the viewtag as a buffer-local + ;; variable so the sentinel knows where to store the activities. + ;; + (set (make-local-variable 'clearcase-vprop-async-viewtag) viewtag) + (set (make-local-variable 'clearcase-vprop-async-proc) proc) + (set-process-sentinel proc 'clearcase-vprop-read-activities-sentinel)) + ;; Return the buffer. + ;; + buf))) + +(defun clearcase-vprop-read-activities-sentinel (process event-string) + (clearcase-trace "Activity reading process sentinel called") + (if (not (equal "finished\n" event-string)) + ;; Failure + ;; + (error "Reading activities failed: %s" event-string)) + (clearcase-vprop-finish-reading-activities (process-buffer process))) + +(defun clearcase-vprop-finish-reading-activities (buffer) + (let ((activity-list nil)) + (message "Parsing view activities...") + (save-excursion + (set-buffer buffer) + (if (or (not (boundp 'clearcase-vprop-async-viewtag)) + (null clearcase-vprop-async-viewtag)) + (error "Internal error: clearcase-vprop-async-viewtag not set")) + + ;; Check that our buffer is the one currently expected to supply the + ;; activities. (Avoid races.) + ;; + (let ((properties (clearcase-vprop-lookup-properties clearcase-vprop-async-viewtag))) + (if (and properties + (eq buffer (aref properties 3))) + (progn + + ;; Parse the buffer, slicing out the 2nd and 4th fields as name and title. + ;; + (goto-char (point-min)) + (while (re-search-forward "^[^ \t]+[ \t]+\\([^ \t]+\\)[ \t]+[^ \t]+[ \t]+\"+\\(.*\\)\"$" nil t) + (let ((id (buffer-substring (match-beginning 1) + (match-end 1))) + (title (buffer-substring (match-beginning 2) + (match-end 2)))) + (setq activity-list (cons (cons id title) + activity-list)))) + + ;; We've got activity-list in the reverse order that + ;; cleartool+lsactivity generated them. I think this is reverse + ;; chronological order, so keep this order since it is more + ;; convenient when setting to an activity. + ;; + ;;(setq activity-list (nreverse activity-list)) + + (clearcase-vprop-set-activities clearcase-vprop-async-viewtag activity-list)) + + (kill-buffer buffer)))) + (message "Parsing view activities...done"))) + +;;{{{ old synchronous activity reader + +;; (defun clearcase-vprop-read-activities-synchronously (viewtag) +;; "Return a list of (activity-name . title) pairs for VIEWTAG" +;; ;; nyi: ought to use a variant of clearcase-ct-blocking-call that returns a buffer +;; ;; rather than a string + +;; ;; Performance: takes around 30 seconds to read 1000 activities. +;; ;; Too slow to invoke willy-nilly on integration streams for example, +;; ;; which typically can have 1000+ activities. + +;; (let ((ret (clearcase-ct-blocking-call "lsact" "-view" viewtag))) +;; (let ((buf (get-buffer-create "*clearcase-temp-activities*")) +;; (activity-list nil)) +;; (save-excursion +;; (set-buffer buf) +;; (erase-buffer) +;; (insert ret) +;; (goto-char (point-min)) +;; ;; Slice out the 2nd and 4th fields as name and title +;; ;; +;; (while (re-search-forward "^[^ \t]+[ \t]+\\([^ \t]+\\)[ \t]+[^ \t]+[ \t]+\"+\\(.*\\)\"$" nil t) +;; (setq activity-list (cons (cons (buffer-substring (match-beginning 1) +;; (match-end 1)) +;; (buffer-substring (match-beginning 2) +;; (match-end 2))) +;; activity-list))) +;; (kill-buffer buf)) + +;; ;; We've got activity-list in the reverse order that +;; ;; cleartool+lsactivity generated them. I think this is reverse +;; ;; chronological order, so keep this order since it is more +;; ;; convenient when setting to an activity. +;; ;; +;; ;;(nreverse activity-list)))) +;; activity-list))) + +;;}}} + +;;}}} + +;;{{{ Determining if a checkout was modified. + +;; How to tell if a file changed since checkout ? +;; +;; In the worst case we actually run "ct diff -pred" but we attempt several +;; less expensive tests first. +;; +;; 1. If it's size differs from pred. +;; 2. The mtime and the ctime are no longer the same. +;; +;; nyi: Other cheaper tests we could use: +;; +;; (a) After each Emacs-driven checkout go and immediately fetch the mtime of +;; the file and store as fprop-checkout-mtime. Then use that to compare +;; against current mtime. This at least would make this function work +;; right on files checked out by the current Emacs process. +;; +;; (b) In the MVFS, after each Emacs-driven checkout go and immediately fetch +;; the OID and store as fprop-checkout-oid. Then use that to compare +;; against the current oid (the MVFS assigns a new OID at each write). +;; This might not always be a win since we'd still need to run cleartool +;; to get the current OID. + +(defun clearcase-file-appears-modified-since-checkout-p (file) + "Return whether FILE appears to have been modified since checkout. +It doesn't examine the file contents." + + (if (not (clearcase-fprop-checked-out file)) + nil + + (let ((mvfs (clearcase-file-is-in-mvfs-p file))) + + ;; We consider various cases in order of increasing cost to compute. + + (cond + ;; Case 1: (MVFS only) the size is different to its predecessor. + ;; + ((and mvfs + (not + (equal + (clearcase-utl-file-size file) + ;; nyi: For the snapshot case it'd be nice to get the size of the + ;; predecessor by using "ct+desc -pred -fmt" but there doesn't + ;; seem to be a format descriptor for file size. On the other hand + ;; ct+dump can obtain the size. + ;; + (clearcase-utl-file-size (clearcase-vxpath-cons-vxpath + file + (clearcase-fprop-predecessor-version + file))))) + ;; Return: + ;; + 'size-changed)) + + ;; Case 2: (MVFS only) the mtime and the ctime are no longer the same. + ;; + ;; nyi: At least on Windows there seems to be a small number of seconds + ;; difference here even when the file is not modified. + ;; So we really check to see of they are close. + ;; + ;; nyi: This doesn't work in a snapshot view. + ;; + ((and mvfs + (not (clearcase-utl-filetimes-close (clearcase-utl-file-mtime file) + (clearcase-utl-file-ctime file) + 5)) + ;; Return: + ;; + 'ctime-mtime-not-close)) + + (t + ;; Case 3: last resort. Actually run a diff against predecessor. + ;; + (let ((ret (clearcase-ct-blocking-call "diff" + "-options" + "-quiet" + "-pred" + file))) + (if (not (zerop (length ret))) + ;; Return: + ;; + 'diffs-nonempty + + ;; Return: + ;; + nil))))))) + +;;}}} + +;;{{{ Tests for view-residency + +;;{{{ Tests for MVFS file residency + +;; nyi: probably superseded by clearcase-file-would-be-in-view-p +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; nyi: this should get at least partially invalidated when +;; VOBs are unmounted. + +;; nyi: make this different for NT +;; +(defvar clearcase-always-mvfs-regexp (if (not clearcase-on-mswindows) + "^/vobs/[^/]+/" + + ;; nyi: express this using drive variable + ;; + (concat "^" + "[Mm]:" + clearcase-pname-sep-regexp))) + +;; This prevents the clearcase-file-vob-root function from pausing for long periods +;; stat-ing /net/host@@ +;; +;; nyi: is there something equivalent on NT I need to avoid ? +;; + +(defvar clearcase-never-mvfs-regexps (if clearcase-on-mswindows + nil + '( + "^/net/[^/]+/" + "^/tmp_mnt/net/[^/]+/" + )) + "Regexps matching those paths we can assume are never inside the MVFS.") + +(defvar clearcase-known-vob-root-cache nil) + +(defun clearcase-file-would-be-in-mvfs-p (filename) + "Return whether FILE, after it is created, would reside in an MVFS filesystem." + (let ((truename (file-truename filename))) + (if (file-exists-p truename) + (clearcase-file-is-in-mvfs-p truename) + (let ((containing-dir (file-name-as-directory (file-name-directory truename)))) + (clearcase-file-is-in-mvfs-p containing-dir))))) + +(defun clearcase-file-is-in-mvfs-p (filename) + "Return whether existing FILE, resides in an MVFS filesystem." + (let ((truename (file-truename filename))) + + (or + ;; case 1: its prefix matches an "always VOB" prefix like /vobs/... + ;; + ;; nyi: problem here: we return true for "/vobs/nonexistent/" + ;; + (numberp (string-match clearcase-always-mvfs-regexp truename)) + + ;; case 2: it has a prefix which is a known VOB-root + ;; + (clearcase-file-matches-vob-root truename clearcase-known-vob-root-cache) + + ;; case 3: it has an ancestor dir which is a newly met VOB-root + ;; + (clearcase-file-vob-root truename)))) + +(defun clearcase-wd-is-in-mvfs () + "Return whether the current directory resides in an MVFS filesystem." + (clearcase-file-is-in-mvfs-p (file-truename "."))) + +(defun clearcase-file-matches-vob-root (truename vob-root-list) + "Return whether TRUENAME has a prefix in VOB-ROOT-LIST." + (if (null vob-root-list) + nil + (or (numberp (string-match (regexp-quote (car vob-root-list)) + truename)) + (clearcase-file-matches-vob-root truename (cdr vob-root-list))))) + +(defun clearcase-file-vob-root (truename) + "File the highest versioned directory in TRUENAME." + + ;; Use known non-MVFS patterns to rule some paths out. + ;; + (if (apply (function clearcase-utl-or-func) + (mapcar (function (lambda (regexp) + (string-match regexp truename))) + clearcase-never-mvfs-regexps)) + nil + (let ((previous-dir nil) + (dir (file-name-as-directory (file-name-directory truename))) + (highest-versioned-directory nil)) + + (while (not (string-equal dir previous-dir)) + (if (clearcase-file-covers-element-p dir) + (setq highest-versioned-directory dir)) + (setq previous-dir dir) + (setq dir (file-name-directory (directory-file-name dir)))) + + (if highest-versioned-directory + (add-to-list 'clearcase-known-vob-root-cache highest-versioned-directory)) + + highest-versioned-directory))) + +;; Note: you should probably be using clearcase-fprop-mtype instead of this +;; unless you really know what you're doing (nyi: check usages of this.) +;; +(defun clearcase-file-covers-element-p (path) + "Determine quickly if PATH refers to a Clearcase element, +without caching the result." + + ;; nyi: Even faster: consult the fprop cache first ? + + (let ((element-dir (concat (clearcase-vxpath-element-part path) clearcase-vxpath-glue))) + (and (file-exists-p path) + (file-directory-p element-dir)))) + +;;}}} + +;;{{{ Tests for snapshot view residency + +;; nyi: probably superseded by clearcase-file-would-be-in-view-p +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defvar clearcase-known-snapshot-root-cache nil) + +(defun clearcase-file-would-be-in-snapshot-p (filename) + "Return whether FILE, after it is created, would reside in a snapshot view. +If so, return the viewtag." + (let ((truename (file-truename filename))) + (if (file-exists-p truename) + (clearcase-file-is-in-snapshot-p truename) + (let ((containing-dir (file-name-as-directory (file-name-directory truename)))) + (clearcase-file-is-in-snapshot-p containing-dir))))) + +(defun clearcase-file-is-in-snapshot-p (truename) + "Return whether existing FILE, resides in a snapshot view. +If so, return the viewtag." + + (or + ;; case 1: it has a prefix which is a known snapshot-root + ;; + (clearcase-file-matches-snapshot-root truename clearcase-known-snapshot-root-cache) + + ;; case 2: it has an ancestor dir which is a newly met VOB-root + ;; + (clearcase-file-snapshot-root truename))) + +(defun clearcase-wd-is-in-snapshot () + "Return whether the current directory resides in a snapshot view." + (clearcase-file-is-in-snapshot-p (file-truename "."))) + +(defun clearcase-file-matches-snapshot-root (truename snapshot-root-list) + "Return whether TRUENAME has a prefix in SNAPSHOT-ROOT-LIST." + (if (null snapshot-root-list) + nil + (or (numberp (string-match (regexp-quote (car snapshot-root-list)) + truename)) + (clearcase-file-matches-snapshot-root truename (cdr snapshot-root-list))))) + +;; This prevents the clearcase-file-snapshot-root function from pausing for long periods +;; stat-ing /net/host@@ +;; +;; nyi: is there something equivalent on NT I need to avoid ? +;; + +(defvar clearcase-never-snapshot-regexps (if clearcase-on-mswindows + nil + '( + "^/net/[^/]+/" + "^/tmp_mnt/net/[^/]+/" + )) + "Regexps matching those paths we can assume are never inside a snapshot view.") + +(defun clearcase-file-snapshot-root (truename) + "File the the snapshot view root containing TRUENAME." + + ;; Use known non-snapshot patterns to rule some paths out. + ;; + (if (apply (function clearcase-utl-or-func) + (mapcar (function (lambda (regexp) + (string-match regexp truename))) + clearcase-never-snapshot-regexps)) + nil + (let ((previous-dir nil) + (dir (file-name-as-directory (file-name-directory truename))) + (viewtag nil) + (viewroot nil)) + + + (while (and (not (string-equal dir previous-dir)) + (null viewtag)) + + ;; See if .view.dat exists and contains a valid view uuid + ;; + (let ((view-dat-name (concat dir (if clearcase-on-mswindows + "view.dat" ".view.dat")))) + (if (file-readable-p view-dat-name) + (let ((uuid (clearcase-viewdat-to-uuid view-dat-name))) + (if uuid + (progn + (setq viewtag (clearcase-view-uuid-to-tag uuid)) + (if viewtag + (setq viewroot dir))))))) + + (setq previous-dir dir) + (setq dir (file-name-directory (directory-file-name dir)))) + + (if viewroot + (add-to-list 'clearcase-known-snapshot-root-cache viewroot)) + + ;; nyi: update a viewtag==>viewroot map ? + + viewroot))) + +(defun clearcase-viewdat-to-uuid (file) + "Extract the view-uuid from a .view.dat file." + ;; nyi, but return non-nil so clearcase-file-snapshot-root works + t + ) + +(defun clearcase-view-uuid-to-tag (uuid) + "Look up the view-uuid in the register to discover its tag." + ;; nyi, but return non-nil so clearcase-file-snapshot-root works + t + ) + +;;}}} + +;; This is simple-minded but seems to work because cleartool+describe +;; groks snapshot views. +;; +;; nyi: Might be wise to cache view-roots to speed this up because the +;; filename-handlers call this. +;; +;; nyi: Some possible shortcuts +;; 1. viewroot-relative path [syntax] +;; 2. under m:/ on NT [syntax] +;; 3. setviewed on Unix [find a containing VOB-root] +;; 4. subst-ed view on NT (calling net use seems very slow though) +;; [find a containing VOB-root] +;; 5. snapshot view +;; +(defun clearcase-file-would-be-in-view-p (filename) + "Return whether FILE, after it is created, would reside in a ClearCase view." + (let ((truename (file-truename (expand-file-name filename)))) + + ;; We use clearcase-path-file-really-exists-p here to make sure we are dealing + ;; with a real file and not something faked by Emacs' file name handlers + ;; like Ange-FTP. + ;; + (if (clearcase-path-file-really-exists-p truename) + (clearcase-file-is-in-view-p truename) + (let ((containing-dir (file-name-as-directory (file-name-directory truename)))) + (and (clearcase-path-file-really-exists-p containing-dir) + (clearcase-file-is-in-view-p containing-dir)))))) + +(defun clearcase-file-is-in-view-p (filename) + (let ((truename (file-truename (expand-file-name filename)))) + ;; Shortcut if the file is a version-extended path. + ;; + (or (clearcase-file-snapshot-root truename) + (clearcase-vxpath-p truename) + (clearcase-fprop-mtype truename) + + ;; nyi: How to efficiently know if we're in a dynamic-view root + ;; 1. Test each contained name for elementness. + ;; Too inefficient. + ;; 2. If it is viewroot-relative. + ;; Okay but not sufficient. + ;; How about case v:/ when view is substed ? + ;; 3. We're setviewed. + ;; Okay but not sufficient. + ;; Maintain a cache of viewroots ? + ))) + +(defun clearcase-file-viewtag (filename) + "Find the viewtag associated with existing FILENAME." + + (clearcase-when-debugging + (assert (file-exists-p filename))) + + (let ((truename (file-truename (expand-file-name filename)))) + (cond + + ;; Case 1: viewroot-relative path + ;; ==> syntax + ;; + ((clearcase-vrpath-p truename) + (clearcase-vrpath-viewtag truename)) + + ;; Case 2: under m:/ on NT + ;; ==> syntax + ;; + ((and clearcase-on-mswindows + (string-match (concat clearcase-viewroot-drive + clearcase-pname-sep-regexp + "\\(" + clearcase-non-pname-sep-regexp "*" + "\\)" + ) + truename)) + (substring truename (match-beginning 1) (match-end 1))) + + ;; Case 3: setviewed on Unix + ;; ==> read EV, but need to check it's beneath a VOB-root + ;; + ((and clearcase-setview-viewtag + (clearcase-file-would-be-in-mvfs-p truename)) + clearcase-setview-viewtag) + + ;; Case 4: subst-ed view on NT + ;; ==> use ct+pwv -wdview + ;; Case 5: snapshot view + ;; ==> use ct+pwv -wdview + (t + (clearcase-file-wdview truename))))) + +(defun clearcase-file-wdview (truename) + "Return the working-directory view associated with TRUENAME, +or nil if none" + (let ((default-directory (if (file-directory-p truename) + truename + (file-name-directory truename)))) + (clearcase-ct-cd default-directory) + (let ((ret (clearcase-ct-blocking-call "pwv" "-wdview" "-short"))) + (if (not (string-match " NONE " ret)) + (clearcase-utl-1st-line-of-string ret))))) + +;;}}} + +;;{{{ The cleartool sub-process + +;; We use pipes rather than pty's for two reasons: +;; +;; 1. NT only has pipes +;; 2. On Solaris there appeared to be a problem in the pty handling part +;; of Emacs, which resulted in Emacs/tq seeing too many cleartool prompt +;; strings. This would occasionally occur and prevent the tq-managed +;; interactions with the cleartool sub-process from working correctly. +;; +;; Now we use pipes. Cleartool detects the "non-tty" nature of the output +;; device and doesn't send a prompt. We manufacture an end-of-transaction +;; marker by sending a "pwd -h" after each cleartool sub-command and then use +;; the expected output of "Usage: pwd\n" as our end-of-txn pattern for tq. +;; +;; Even using pipes, the semi-permanent outboard-process using tq doesn't work +;; well on NT. There appear to be bugs in accept-process-output such that: +;; 0. there apparently were hairy race conditions, which a sprinkling +;; of (accept-process-output nil 1) seemed to avoid somewhat. +;; 1. it never seems to timeout if you name a process as arg1. +;; 2. it always seems to wait for TIMEOUT, even if there is output ready. +;; The result seemed to be less responsive tha just calling a fresh cleartool +;; process for each invocation of clearcase-ct-blocking-call +;; +;; It still seems worthwhile to make it work on NT, as clearcase-ct-blocking-call +;; typically takes about 0.5 secs on NT versus 0.05 sec on Solaris, +;; an order of magnitude difference. +;; + +(defconst clearcase-ct-eotxn-cmd "pwd -h\n") +(defconst clearcase-ct-eotxn-response "Usage: pwd\n") +(defconst clearcase-ct-eotxn-response-length (length clearcase-ct-eotxn-response)) + +(defconst clearcase-ct-subproc-timeout 30 + "Timeout on calls to subprocess") + +(defvar clearcase-ct-tq nil + "Transaction queue to talk to ClearTool in a subprocess") + +(defvar clearcase-ct-return nil + "Return value when we're involved in a blocking call") + +(defvar clearcase-ct-view "" + "Current view of cleartool subprocess, or the empty string if none") + +(defvar clearcase-ct-wdir "" + "Current working directory of cleartool subprocess, +or the empty string if none") + +(defvar clearcase-ct-running nil) + +(defun clearcase-ct-accept-process-output (proc timeout) + (accept-process-output proc timeout)) + +(defun clearcase-ct-start-cleartool () + (interactive) + (clearcase-trace "clearcase-ct-start-cleartool()") + (let ((process-environment (append '("ATRIA_NO_BOLD=1" + "ATRIA_FORCE_GUI=1") + ;;; emacs is a GUI, right? :-) + process-environment))) + (clearcase-trace (format "Starting cleartool in %s" default-directory)) + (let* ( ;; Force the use of a pipe + ;; + (process-connection-type nil) + (cleartool-process + (start-process "cleartool" ;; Absolute path won't work here + " *cleartool*" + clearcase-cleartool-path))) + (process-kill-without-query cleartool-process) + (setq clearcase-ct-view "") + (setq clearcase-ct-tq (tq-create cleartool-process)) + (tq-enqueue clearcase-ct-tq + clearcase-ct-eotxn-cmd ;; question + clearcase-ct-eotxn-response ;; regexp + 'clearcase-ct-running ;; closure + 'set) ;; function + (while (not clearcase-ct-running) + (message "waiting for cleartool to start...") + (clearcase-ct-accept-process-output (tq-process clearcase-ct-tq) + clearcase-ct-subproc-timeout)) + ;; Assign a sentinel to restart it if it dies. + ;; nyi: This needs debugging. + ;;(set-process-sentinel cleartool-process 'clearcase-ct-sentinel) + + (clearcase-trace "clearcase-ct-start-cleartool() done") + (message "waiting for cleartool to start...done")))) + +;; nyi: needs debugging. +;; +(defun clearcase-ct-sentinel (process event-string) + (clearcase-trace (format "Cleartool process sentinel called: %s" event-string)) + (if (not (eq 'run (process-status process))) + (progn + ;; Restart the dead cleartool. + ;; + (clearcase-trace "Cleartool process restarted") + (clearcase-ct-start-cleartool)))) + +(defun clearcase-ct-kill-cleartool () + "Kill off cleartool subprocess. If another one is needed, +it will be restarted. This may be useful if you're debugging clearcase." + (interactive) + (clearcase-ct-kill-tq)) + +(defun clearcase-ct-callback (arg val) + (clearcase-trace (format "clearcase-ct-callback:<\n")) + (clearcase-trace val) + (clearcase-trace (format "clearcase-ct-callback:>\n")) + ;; This can only get called when the last thing received from + ;; the cleartool sub-process was clearcase-ct-eotxn-response, + ;; so it is safe to just remove it here. + ;; + (setq clearcase-ct-return (substring val 0 (- clearcase-ct-eotxn-response-length)))) + +(defun clearcase-ct-do-cleartool-command (command file comment &optional extra-args) + "Execute a cleartool command, notifying user and checking for +errors. Output from COMMAND goes to buffer *clearcase*. The last argument of the +command is the name of FILE; this is appended to an optional list of +EXTRA-ARGS." + + (if file + (setq file (expand-file-name file))) + (if (listp command) + (error "command must not be a list")) + (if clearcase-command-messages + (if file + (message "Running %s on %s..." command file) + (message "Running %s..." command))) + (let ((camefrom (current-buffer)) + (squeezed nil) + status) + (set-buffer (get-buffer-create "*clearcase*")) + (setq buffer-read-only nil) + (erase-buffer) + (set (make-local-variable 'clearcase-parent-buffer) camefrom) + (set (make-local-variable 'clearcase-parent-buffer-name) + (concat " from " (buffer-name camefrom))) + + ;; This is so that command arguments typed in the *clearcase* buffer will + ;; have reasonable defaults. + ;; + (if file + (setq default-directory (file-name-directory file))) + + (mapcar + (function (lambda (s) + (and s + (not (zerop (length s))) + (setq squeezed + (append squeezed (list s)))))) + extra-args) + + (clearcase-with-tempfile + comment-file + (if (not (eq comment 'unused)) + (if comment + (progn + (write-region comment nil comment-file nil 'noprint) + (setq squeezed (append squeezed (list "-cfile" (clearcase-path-native comment-file))))) + (setq squeezed (append squeezed (list "-nc"))))) + (if file + (setq squeezed (append squeezed (list (clearcase-path-native file))))) + (let ((default-directory (file-name-directory + (or file default-directory)))) + (clearcase-ct-cd default-directory) + (if clearcase-command-messages + (message "Running %s..." command)) + (insert + (apply 'clearcase-ct-cleartool-cmd (append (list command) squeezed))) + (if clearcase-command-messages + (message "Running %s...done" command)))) + + (goto-char (point-min)) + (clearcase-view-mode 0 camefrom) + (set-buffer-modified-p nil) ; XEmacs - fsf uses `not-modified' + (if (re-search-forward "^cleartool: Error:.*$" nil t) + (progn + (setq status (buffer-substring (match-beginning 0) (match-end 0))) + (clearcase-port-view-buffer-other-window "*clearcase*") + (shrink-window-if-larger-than-buffer) + (error "Running %s...FAILED (%s)" command status)) + (if clearcase-command-messages + (message "Running %s...OK" command))) + (set-buffer camefrom) + status)) + +(defun clearcase-ct-cd (dir) + (if (or (not dir) + (string= dir clearcase-ct-wdir)) + clearcase-ct-wdir + (clearcase-ct-blocking-call "cd" (clearcase-path-native dir)) + (setq clearcase-ct-wdir dir))) + +(defun clearcase-ct-cleartool-cmd (&rest cmd) + (apply 'clearcase-ct-blocking-call cmd)) + +;; NT Emacs - needs a replacement for tq. +;; +(defun clearcase-ct-get-command-stdout (program &rest args) + "Call PROGRAM. +Returns PROGRAM's stdout. +ARGS is the command line arguments to PROGRAM." + (let ((buf (get-buffer-create "cleartoolexecution"))) + (prog1 + (save-excursion + (set-buffer buf) + (apply 'call-process program nil buf nil args) + (buffer-string)) + (kill-buffer buf)))) + +;; The TQ interaction still doesn't work on NT. +;; +(defvar clearcase-disable-tq clearcase-on-mswindows + "Set to T if the Emacs/cleartool interactions via tq are not working right.") + +(defun clearcase-ct-blocking-call (&rest cmd) + (clearcase-trace (format "clearcase-ct-blocking-call(%s)" cmd)) + (save-excursion + (setq clearcase-ct-return nil) + + (if clearcase-disable-tq + ;; Don't use tq: + ;; + (setq clearcase-ct-return (apply 'clearcase-ct-get-command-stdout + clearcase-cleartool-path cmd)) + + ;; Use tq: + ;; + (setq clearcase-ct-return nil) + (if (not clearcase-ct-tq) + (clearcase-ct-start-cleartool)) + (unwind-protect + (let ((command "")) + (mapcar + (function + (lambda (token) + ;; If the token has imbedded spaces and is not already quoted, + ;; add double quotes. + ;; + (setq command (concat command + " " + (clearcase-utl-quote-if-nec token))))) + cmd) + (tq-enqueue clearcase-ct-tq + (concat command "\n" + clearcase-ct-eotxn-cmd) ;; question + clearcase-ct-eotxn-response ;; regexp + nil ;; closure + 'clearcase-ct-callback) ;; function + (while (not clearcase-ct-return) + (clearcase-ct-accept-process-output (tq-process clearcase-ct-tq) + clearcase-ct-subproc-timeout))) + ;; Error signalled: + ;; + (while (tq-queue clearcase-ct-tq) + (tq-queue-pop clearcase-ct-tq))))) + (if (string-match "cleartool: Error:" clearcase-ct-return) + (error "cleartool process error %s: " + (substring clearcase-ct-return (match-end 0)))) + (clearcase-trace (format "command-result(%s)" clearcase-ct-return)) + clearcase-ct-return) + +(defun clearcase-ct-kill-tq () + (setq clearcase-ct-running nil) + (setq clearcase-ct-tq nil) + (process-send-eof (tq-process clearcase-ct-tq)) + (kill-process (tq-process clearcase-ct-tq))) + +(defun clearcase-ct-kill-buffer-hook () + + ;; NT Emacs - doesn't use tq. + ;; + (if (not clearcase-on-mswindows) + (let ((kill-buffer-hook nil)) + (if (and (boundp 'clearcase-ct-tq) + clearcase-ct-tq + (eq (current-buffer) (tq-buffer clearcase-ct-tq))) + (error "Don't kill TQ buffer %s, use `clearcase-ct-kill-tq'" (current-buffer)))))) + +(add-hook 'kill-buffer-hook 'clearcase-ct-kill-buffer-hook) + +;;}}} + +;;{{{ Invoking a command + +;; nyi Would be redundant if we didn't need it to invoke normal-diff-program + +(defun clearcase-do-command (okstatus command file &optional extra-args) + "Execute a version-control command, notifying user and checking for errors. +The command is successful if its exit status does not exceed OKSTATUS. +Output from COMMAND goes to buffer *clearcase*. The last argument of the command is +an optional list of EXTRA-ARGS." + (setq file (expand-file-name file)) + (if clearcase-command-messages + (message "Running %s on %s..." command file)) + (let ((camefrom (current-buffer)) + (pwd ) + (squeezed nil) + status) + (set-buffer (get-buffer-create "*clearcase*")) + (setq buffer-read-only nil) + (erase-buffer) + (set (make-local-variable 'clearcase-parent-buffer) camefrom) + (set (make-local-variable 'clearcase-parent-buffer-name) + (concat " from " (buffer-name camefrom))) + ;; This is so that command arguments typed in the *clearcase* buffer will + ;; have reasonable defaults. + ;; + (setq default-directory (file-name-directory file) + file (file-name-nondirectory file)) + + (mapcar + (function (lambda (s) + (and s + (not (zerop (length s))) + (setq squeezed + (append squeezed (list s)))))) + extra-args) + (setq squeezed (append squeezed (list file))) + (setq status (apply 'call-process command nil t nil squeezed)) + (goto-char (point-min)) + (clearcase-view-mode 0 camefrom) + (set-buffer-modified-p nil) ; XEmacs - fsf uses `not-modified' + (if (or (not (integerp status)) (< okstatus status)) + (progn + (clearcase-port-view-buffer-other-window "*clearcase*") + (shrink-window-if-larger-than-buffer) + (error "Running %s...FAILED (%s)" command + (if (integerp status) + (format "status %d" status) + status))) + (if clearcase-command-messages + (message "Running %s...OK" command))) + (set-buffer camefrom) + status)) + +;;}}} + +;;{{{ Viewtag management + +;;{{{ Started views + +(defun clearcase-viewtag-try-to-start-view (viewtag) + "If VIEW is not apparently already visible under viewroot, start it." + (if (not (member viewtag (clearcase-viewtag-started-viewtags))) + (clearcase-viewtag-start-view viewtag))) + +(defun clearcase-viewtag-started-viewtags-alist () + "Return an alist of views that are currently visible under the viewroot." + (mapcar + (function + (lambda (tag) + (list (concat tag "/")))) + (clearcase-viewtag-started-viewtags))) + +(defun clearcase-viewtag-started-viewtags () + "Return the list of viewtags already visible under the viewroot." + (let ((raw-list (if clearcase-on-mswindows + (directory-files clearcase-viewroot-drive) + (directory-files clearcase-viewroot)))) + (clearcase-utl-list-filter + (function (lambda (string) + ;; Exclude the ones that start with ".", + ;; and the ones that end with "@@". + ;; + (and (not (equal ?. (aref string 0))) + (not (string-match "@@$" string))))) + raw-list))) + +;; nyi: Makes sense on NT ? +;; Probably also want to run subst ? +;; Need a better high-level interface to start-view +;; +(defun clearcase-viewtag-start-view (viewtag) + "If VIEWTAG is in our cache of valid view names, start it." + (if (clearcase-viewtag-exists viewtag) + (progn + (message "Starting view server for %s..." viewtag) + (clearcase-ct-blocking-call "startview" viewtag) + (message "Starting view server for %s...done" viewtag)))) + +;;}}} + +;;{{{ All views + +;;{{{ Internals + +(defvar clearcase-viewtag-cache nil + "Oblist of all known viewtags.") + +(defvar clearcase-viewtag-dir-cache nil + "Oblist of all known viewtag dirs.") + +(defvar clearcase-viewtag-cache-timeout 1800 + "*Default timeout of all-viewtag cache, in seconds.") + +(defun clearcase-viewtag-schedule-cache-invalidation () + "Schedule the next invalidation of clearcase-viewtag-cache." + (run-at-time (format "%s sec" clearcase-viewtag-cache-timeout) + nil + (function (lambda (&rest ignore) + (setq clearcase-viewtag-cache nil))) + nil)) +;; Some primes: +;; +;; 1, +;; 2, +;; 3, +;; 7, +;; 17, +;; 31, +;; 61, +;; 127, +;; 257, +;; 509, +;; 1021, +;; 2053, + +(defun clearcase-viewtag-read-all-viewtags () + "Invoke ct+lsview to get all viewtags, and return an obarry containing them." + (message "Fetching view names...") + (let* ((default-directory "/") + (result (make-vector 1021 0)) + (raw-views-string (clearcase-ct-blocking-call "lsview" "-short")) + (view-list (clearcase-utl-split-string-at-char raw-views-string ?\n))) + (message "Fetching view names...done") + (mapcar (function (lambda (string) + (set (intern string result) t))) + view-list) + result)) + +(defun clearcase-viewtag-populate-caches () + (setq clearcase-viewtag-cache (clearcase-viewtag-read-all-viewtags)) + (let ((dir-cache (make-vector 1021 0))) + (mapatoms + (function (lambda (sym) + (set (intern (concat (symbol-name sym) "/") dir-cache) t))) + clearcase-viewtag-cache) + (setq clearcase-viewtag-dir-cache dir-cache)) + (clearcase-viewtag-schedule-cache-invalidation)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;;}}} + +;; Exported interfaces + +;; This is for completion of viewtags. +;; +(defun clearcase-viewtag-all-viewtags-obarray () + "Return an obarray of all valid viewtags as of the last time we looke d." + (if (null clearcase-viewtag-cache) + (clearcase-viewtag-populate-caches)) + clearcase-viewtag-cache) + +;; This is for completion of viewtag dirs, like /view/my_view_name/ +;; The trailing slash is required for compatibility with other instances +;; of filename completion in Emacs. +;; +(defun clearcase-viewtag-all-viewtag-dirs-obarray () + "Return an obarray of all valid viewtag directory names as of the last time we looked." + (if (null clearcase-viewtag-dir-cache) + (clearcase-viewtag-populate-caches)) + clearcase-viewtag-dir-cache) + +(defun clearcase-viewtag-exists (viewtag) + (symbol-value (intern-soft viewtag (clearcase-viewtag-all-viewtags-obarray)))) + +;;}}} + +;;}}} + +;;{{{ Pathnames + +;;{{{ Pathnames: version-extended + +(defun clearcase-vxpath-p (path) + (or (string-match (concat clearcase-vxpath-glue "/") path) + (string-match (concat clearcase-vxpath-glue "\\\\") path))) + +(defun clearcase-vxpath-element-part (vxpath) + "Return the element part of version-extended PATH." + (if (string-match clearcase-vxpath-glue vxpath) + (substring vxpath 0 (match-beginning 0)) + vxpath)) + +(defun clearcase-vxpath-version-part (vxpath) + "Return the version part of version-extended PATH." + (if (string-match clearcase-vxpath-glue vxpath) + (substring vxpath (match-end 0)) + nil)) + +(defun clearcase-vxpath-branch (vxpath) + "Return the branch part of a version-extended path or of a version" + (if (clearcase-vxpath-p vxpath) + (clearcase-vxpath-cons-vxpath + (clearcase-vxpath-element-part vxpath) + (file-name-directory (clearcase-vxpath-version-part vxpath))) + (file-name-directory vxpath))) + +(defun clearcase-vxpath-version (vxpath) + "Return the numeric version part of a version-extended path or of a version" + (if (clearcase-vxpath-p vxpath) + (file-name-nondirectory (clearcase-vxpath-version-part vxpath)) + (file-name-nondirectory vxpath))) + +(defun clearcase-vxpath-cons-vxpath (file version &optional viewtag) + "Make a ClearCase version-extended pathname for ELEMENT's version VERSION. +If ELEMENT is actually a version-extended pathname, substitute VERSION for +the version included in ELEMENT. If VERSION is nil, remove the version-extended +pathname. + +If optional VIEWTAG is specified, make a view-relative pathname, possibly +replacing the existing view prefix." + (let* ((element (clearcase-vxpath-element-part file)) + (glue-fmt (if (and (> (length version) 0) + (= (aref version 0) ?/)) + (concat "%s" clearcase-vxpath-glue "%s") + (concat "%s" clearcase-vxpath-glue "/%s"))) + (relpath (clearcase-vrpath-tail element))) + (if viewtag + (setq element (concat clearcase-viewroot "/" viewtag (or relpath element)))) + (if version + (format glue-fmt element version) + element))) + +;; NYI: This should cache the predecessor version as a property +;; of the file. +;; +(defun clearcase-vxpath-of-predecessor (file) + "Compute the version-extended pathname of the predecessor version of FILE." + (if (not (equal 'version (clearcase-fprop-mtype file))) + (error "Not a clearcase version: %s" file)) + (let ((abs-file (expand-file-name file))) + (let ((ver (clearcase-utl-1st-line-of-string + (clearcase-ct-cleartool-cmd "describe" + "-pred" + "-short" + (clearcase-path-native abs-file))))) + (clearcase-path-canonicalise-slashes (concat + (clearcase-vxpath-element-part file) + clearcase-vxpath-glue + ver))))) + +(defun clearcase-vxpath-version-extend (file) + "Compute the version-extended pathname of FILE." + (if (not (equal 'version (clearcase-fprop-mtype file))) + (error "Not a clearcase version: %s" file)) + (let ((abs-file (expand-file-name file))) + (clearcase-path-canonicalise-slashes + (clearcase-utl-1st-line-of-string + (clearcase-ct-cleartool-cmd "describe" + "-fmt" + (concat "%En" + clearcase-vxpath-glue + "%Vn") + (clearcase-path-native abs-file)))))) + +(defun clearcase-vxpath-of-branch-base (file) + "Compute the version-extended pathname of the version at the branch base of FILE." + (let* ((file-version-path + (if (clearcase-fprop-checked-out file) + ;; If the file is checked-out, start with its predecessor version... + ;; + (clearcase-vxpath-version-extend (clearcase-vxpath-of-predecessor file)) + ;; ...otherwise start with the file's version. + ;; + (clearcase-vxpath-version-extend file))) + (file-version-number (string-to-int (clearcase-vxpath-version file-version-path))) + (branch (clearcase-vxpath-branch file-version-path))) + (let* ((base-number 0) + (base-version-path (format "%s%d" branch base-number))) + (while (and (not (clearcase-file-is-in-snapshot-p base-version-path)) + (not (file-exists-p base-version-path)) + (< base-number file-version-number)) + (setq base-number (1+ base-number)) + (setq base-version-path (format "%s%d" branch base-number))) + base-version-path))) + +(defun clearcase-vxpath-version-of-branch-base (file) + (clearcase-vxpath-version-part (clearcase-vxpath-of-branch-base file))) + +(defun clearcase-vxpath-get-version-in-buffer (vxpath) + "Return a buffer containing the version named by VXPATH. +Intended for use in snapshot views." + (let* ((temp-file (clearcase-vxpath-get-version-in-temp-file vxpath)) + (buffer (find-file-noselect temp-file t))) + + ;; XEmacs throws an error if you delete a read-only file + ;; + (if clearcase-xemacs-p + (if (not (file-writable-p temp-file)) + (set-file-modes temp-file (string-to-number "666" 8)))) + + (delete-file temp-file) + buffer)) + +(defun clearcase-vxpath-get-version-in-temp-file (vxpath) + "Return the name of a temporary file containing the version named by VXPATH. +Intended for use in snapshot views." + + (let ((temp-file (clearcase-utl-tempfile-name vxpath))) + (progn + (clearcase-ct-blocking-call "get" + "-to" + (clearcase-path-native temp-file) + (clearcase-path-native vxpath)) + temp-file))) + +;;}}} + +;;{{{ Pathnames: viewroot-relative + +;; nyi: make all this work with viewroot-drive-relative files too + +(defun clearcase-vrpath-p (path) + "Return whether PATH is viewroot-relative." + (string-match clearcase-vrpath-regexp path)) + +(defun clearcase-vrpath-head (vrpath) + "Given viewroot-relative PATH, return the prefix including the view-tag." + (if (string-match clearcase-vrpath-regexp vrpath) + (substring vrpath (match-end 0)))) + +(defun clearcase-vrpath-tail (vrpath) + "Given viewroot-relative PATH, return the suffix after the view-tag." + (if (string-match clearcase-vrpath-regexp vrpath) + (substring vrpath (match-end 0)))) + +(defun clearcase-vrpath-viewtag (vrpath) + "Given viewroot-relative PATH, return the view-tag." + (if (string-match clearcase-vrpath-regexp vrpath) + (substring vrpath (match-beginning 1) (match-end 1)))) + +;; Remove useless viewtags from a pathname. +;; e.g. if we're setviewed to view "VIEWTAG" +;; (clearcase-path-remove-useless-viewtags "/view/VIEWTAG/PATH") +;; ==> "PATH" +;; (clearcase-path-remove-useless-viewtags "/view/z/view/y/PATH") +;; ==> /view/y/"PATH" +;; +(defvar clearcase-multiple-viewroot-regexp + (concat "^" + clearcase-viewroot + clearcase-pname-sep-regexp + clearcase-non-pname-sep-regexp "+" + "\\(" + clearcase-viewroot + clearcase-pname-sep-regexp + "\\)" + )) + +(defun clearcase-path-remove-useless-viewtags (pathname) + ;; Try to avoid file-name-handler recursion here: + ;; + (let ((setview-root clearcase-setview-root)) + (if setview-root + ;; Append "/": + ;; + (setq setview-root (concat setview-root "/"))) + + (cond + + ((string-match clearcase-multiple-viewroot-regexp pathname) + (clearcase-path-remove-useless-viewtags (substring pathname (match-beginning 1)))) + + ((and setview-root + (string= setview-root "/")) + pathname) + + ;; If pathname has setview-root as a proper prefix, + ;; strip it off and recurse: + ;; + ((and setview-root + (< (length setview-root) (length pathname)) + (string= setview-root (substring pathname 0 (length setview-root)))) + (clearcase-path-remove-useless-viewtags (substring pathname (- (length setview-root) 1)))) + + (t + pathname)))) + +;;}}} + +;; Don't pass the "INPLACE" parameter to subst-char-in-string here since the +;; parameter is not necessarily a local variable (in some cases it is +;; buffer-file-name and replacing / with \ in it wreaks havoc). +;; +(defun clearcase-path-canonicalise-slashes (path) + (if (not clearcase-on-mswindows) + path + (subst-char-in-string ?\\ ?/ path))) + +(defun clearcase-path-canonical (path) + (if (not clearcase-on-mswindows) + path + (if clearcase-on-cygwin + (substring (shell-command-to-string (concat "cygpath -u '" path "'")) 0 -1) + (subst-char-in-string ?\\ ?/ path)))) + +(defun clearcase-path-native (path) + (if (not clearcase-on-mswindows) + path + (if clearcase-on-cygwin + (substring (shell-command-to-string (concat "cygpath -w " path)) 0 -1) + (subst-char-in-string ?/ ?\\ path)))) + +(defun clearcase-path-file-really-exists-p (filename) + "Test if a file really exists, when all file-name handlers are disabled." + (let ((inhibit-file-name-operation 'file-exists-p) + (inhibit-file-name-handlers (mapcar + (lambda (pair) + (cdr pair)) + file-name-handler-alist))) + (file-exists-p filename))) + +(defun clearcase-path-file-in-any-scopes (file scopes) + (let ((result nil) + (cursor scopes)) + (while (and (null result) + cursor) + (if (clearcase-path-file-in-scope file (car cursor)) + (setq result t)) + (setq cursor (cdr cursor))) + result)) + + +(defun clearcase-path-file-in-scope (file scope) + (assert (file-name-absolute-p file)) + (assert (file-name-absolute-p scope)) + + (or + ;; Pathnames are equal + ;; + (string= file scope) + + ;; scope-qua-dir is an ancestor of file (proper string prefix) + ;; + (let ((scope-as-dir (concat scope "/"))) + (string= scope-as-dir + (substring file 0 (length scope-as-dir)))))) + +;;}}} + +;;{{{ Mode-line + +(defun clearcase-mode-line-buffer-id (filename) + "Compute an abbreviated version string for the mode-line. +It will be in one of three forms: /main/NNN, or .../branchname/NNN, or DO-NAME" + + (if (clearcase-fprop-checked-out filename) + (if (clearcase-fprop-reserved filename) + "RESERVED" + "UNRESERVED") + (let ((ver-string (clearcase-fprop-version filename))) + (if (not (zerop (length ver-string))) + (let ((i (length ver-string)) + (slash-count 0)) + ;; Search back from the end to the second-last slash + ;; + (while (and (> i 0) + (< slash-count 2)) + (if (equal ?/ (aref ver-string (1- i))) + (setq slash-count (1+ slash-count))) + (setq i (1- i))) + (if (> i 0) + (concat "..." (substring ver-string i)) + (substring ver-string i))))))) + +;;}}} + +;;{{{ Minibuffer reading + +;;{{{ clearcase-read-version-name + +(defun clearcase-read-version-name (prompt file) + "Display PROMPT and read a version string for FILE in the minibuffer, +with completion if possible." + (let* ((insert-default-directory nil) + (predecessor (clearcase-fprop-predecessor-version file)) + (default-filename (clearcase-vxpath-cons-vxpath file predecessor)) + + ;; To get this to work it is necessary to make Emacs think + ;; we're completing with respect to "ELEMENT@@/" rather + ;; than "ELEMENT@@". Otherwise when we enter a version + ;; like "/main/NN", it thinks we entered an absolute path. + ;; So instead, we prompt the user to enter "main/..../NN" + ;; and add back the leading slash before returning. + ;; + (completing-dir (concat file "@@/"))) + (if (and (clearcase-file-is-in-mvfs-p file) (not clearcase-on-mswindows)) + ;; Completion only works in MVFS: + ;; + (concat "/" (read-file-name prompt + completing-dir + (substring predecessor 1) + ;;nil + t + (substring predecessor 1))) + (concat "/" (read-string prompt + (substring predecessor 1) + nil))))) + +;;}}} + +;;{{{ clearcase-read-label-name + +;; nyi: unused + +(defun clearcase-read-label-name (prompt) + "Read a label name." + + (let* ((string (clearcase-ct-cleartool-cmd "lstype" + "-kind" + "lbtype" + "-short")) + labels) + (mapcar (function (lambda (arg) + (if (string-match "(locked)" arg) + nil + (setq labels (cons (list arg) labels))))) + (clearcase-utl-split-string string "\n")) + (completing-read prompt labels nil t))) + +;;}}} + +;;}}} + +;;{{{ Directory-tree walking + +(defun clearcase-dir-all-files (func &rest args) + "Invoke FUNC f ARGS on each regular file f in default directory." + (let ((dir default-directory)) + (message "Scanning directory %s..." dir) + (mapcar (function (lambda (f) + (let ((dirf (expand-file-name f dir))) + (apply func dirf args)))) + (directory-files dir)) + (message "Scanning directory %s...done" dir))) + +(defun clearcase-file-tree-walk-internal (file func args quiet) + (if (not (file-directory-p file)) + (apply func file args) + (or quiet + (message "Traversing directory %s..." file)) + (let ((dir (file-name-as-directory file))) + (mapcar + (function + (lambda (f) (or + (string-equal f ".") + (string-equal f "..") + (member f clearcase-directory-exclusion-list) + (let ((dirf (concat dir f))) + (or + (file-symlink-p dirf) ;; Avoid possible loops + (clearcase-file-tree-walk-internal dirf func args quiet)))))) + (directory-files dir))))) +;; +(defun clearcase-file-tree-walk (func &rest args) + "Walk recursively through default directory. +Invoke FUNC f ARGS on each non-directory file f underneath it." + (clearcase-file-tree-walk-internal default-directory func args nil) + (message "Traversing directory %s...done" default-directory)) + +(defun clearcase-subdir-tree-walk (func &rest args) + "Walk recursively through default directory. +Invoke FUNC f ARGS on each subdirectory underneath it." + (clearcase-subdir-tree-walk-internal default-directory func args nil) + (message "Traversing directory %s...done" default-directory)) + +(defun clearcase-subdir-tree-walk-internal (file func args quiet) + (if (file-directory-p file) + (let ((dir (file-name-as-directory file))) + (apply func dir args) + (or quiet + (message "Traversing directory %s..." file)) + (mapcar + (function + (lambda (f) (or + (string-equal f ".") + (string-equal f "..") + (member f clearcase-directory-exclusion-list) + (let ((dirf (concat dir f))) + (or + (file-symlink-p dirf) ;; Avoid possible loops + (clearcase-subdir-tree-walk-internal dirf + func + args + quiet)))))) + (directory-files dir))))) + +;;}}} + +;;{{{ Buffer context + +;; nyi: it would be nice if we could restore fold context too, for folded files. + +;; Save a bit of the text around POSN in the current buffer, to help +;; us find the corresponding position again later. This works even +;; if all markers are destroyed or corrupted. +;; +(defun clearcase-position-context (posn) + (list posn + (buffer-size) + (buffer-substring posn + (min (point-max) (+ posn 100))))) + +;; Return the position of CONTEXT in the current buffer, or nil if we +;; couldn't find it. +;; +(defun clearcase-find-position-by-context (context) + (let ((context-string (nth 2 context))) + (if (equal "" context-string) + (point-max) + (save-excursion + (let ((diff (- (nth 1 context) (buffer-size)))) + (if (< diff 0) (setq diff (- diff))) + (goto-char (nth 0 context)) + (if (or (search-forward context-string nil t) + ;; Can't use search-backward since the match may continue + ;; after point. + ;; + (progn (goto-char (- (point) diff (length context-string))) + ;; goto-char doesn't signal an error at + ;; beginning of buffer like backward-char would. + ;; + (search-forward context-string nil t))) + ;; to beginning of OSTRING + ;; + (- (point) (length context-string)))))))) + +;;}}} + +;;{{{ Synchronizing buffers with disk + +(defun clearcase-sync-after-file-updated-from-vob (file) + ;; Do what is needed after a file in a snapshot is updated or a checkout is + ;; cancelled. + + ;; "ct+update" will not always make the file readonly, if, for + ;; example, its contents didn't actually change. But we'd like + ;; update to result in a readonly file, so force it here. + ;; + (clearcase-utl-make-unwriteable file) + + (or + ;; If this returns true, there was a buffer visiting the file and it it + ;; flushed fprops... + ;; + (clearcase-sync-from-disk-if-needed file) + + ;; ...otherwise, just sync this other state: + ;; + (progn + (clearcase-fprop-unstore-properties file) + (dired-relist-file file)))) + +(defun clearcase-sync-from-disk (file &optional no-confirm) + + (clearcase-fprop-unstore-properties file) + ;; If the given file is in any buffer, revert it. + ;; + (let ((buffer (find-buffer-visiting file))) + (if buffer + (save-excursion + (set-buffer buffer) + (clearcase-buffer-revert no-confirm) + (clearcase-fprop-get-properties file) + + ;; Make sure the mode-line gets updated. + ;; + (setq clearcase-mode + (concat " ClearCase:" + (clearcase-mode-line-buffer-id file))) + (force-mode-line-update)))) + + ;; Update any Dired Mode buffers that list this file. + ;; + (dired-relist-file file) + + ;; If the file was a directory, update any dired-buffer for + ;; that directory. + ;; + (mapcar (function (lambda (buffer) + (save-excursion + (set-buffer buffer) + (revert-buffer)))) + (dired-buffers-for-dir file))) + +(defun clearcase-sync-from-disk-if-needed (file) + + ;; If the buffer on FILE is out of sync with its file, synch it. Returns t if + ;; clearcase-sync-from-disk is called. + + (let ((buffer (find-buffer-visiting file))) + (if (and buffer + ;; Buffer can be out of sync in two ways: + ;; (a) Buffer is modified (hasn't been written) + ;; (b) Buffer is recording a different modtime to what the file has. + ;; This is what happens when the file is updated by another + ;; process. + ;; (c) Buffer and file differ in their writeability. + ;; + (or (buffer-modified-p buffer) + (not (verify-visited-file-modtime buffer)) + (eq (file-writable-p file) + (with-current-buffer buffer buffer-read-only)))) + (progn + (clearcase-sync-from-disk file + ;; Only confirm for modified buffers. + ;; + (not (buffer-modified-p buffer))) + t) + nil))) + + +(defun clearcase-sync-to-disk (&optional not-urgent) + + ;; Make sure the current buffer and its working file are in sync + ;; NOT-URGENT means it is ok to continue if the user says not to save. + ;; + (if (buffer-modified-p) + (if (or clearcase-suppress-confirm + (y-or-n-p (format "Buffer %s modified; save it? " + (buffer-name)))) + (save-buffer) + (if not-urgent + nil + (error "Aborted"))))) + + +(defun clearcase-buffer-revert (&optional no-confirm) + ;; Should never call for Dired buffers + ;; + (assert (not (eq major-mode 'dired-mode))) + + ;; Revert buffer, try to keep point and mark where user expects them in spite + ;; of changes because of expanded version-control key words. This is quite + ;; important since otherwise typeahead won't work as expected. + ;; + (widen) + (let ((point-context (clearcase-position-context (point))) + + ;; Use clearcase-utl-mark-marker to avoid confusion in transient-mark-mode. + ;; XEmacs - mark-marker t, FSF Emacs - mark-marker. + ;; + (mark-context (if (eq (marker-buffer (clearcase-utl-mark-marker)) + (current-buffer)) + (clearcase-position-context (clearcase-utl-mark-marker)))) + (camefrom (current-buffer))) + + ;; nyi: Should we run font-lock ? + ;; Want to avoid re-doing a buffer that is already correct, such as on + ;; check-in/check-out. + ;; For now do-nothing. + + ;; The actual revisit. + ;; For some reason, revert-buffer doesn't recompute whether View Minor Mode + ;; should be on, so turn it off and then turn it on if necessary. + ;; + ;; nyi: Perhaps we should re-find-file ? + ;; + (or clearcase-xemacs-p + (if (fboundp 'view-mode) + (view-mode 0))) + (revert-buffer t no-confirm t) + (or clearcase-xemacs-p + (if (and (boundp 'view-read-only) + view-read-only + buffer-read-only) + (view-mode 1))) + + ;; Restore point and mark. + ;; + (let ((new-point (clearcase-find-position-by-context point-context))) + (if new-point + (goto-char new-point)) + (if mark-context + (let ((new-mark (clearcase-find-position-by-context mark-context))) + (if new-mark + (set-mark new-mark)))) + + ;; Restore a semblance of folded state. + ;; + (if (and (boundp 'folded-file) + folded-file) + (progn + (folding-open-buffer) + (folding-whole-buffer) + (if new-point + (folding-goto-char new-point))))))) + +;;}}} + +;;{{{ Utilities + +;;{{{ Displaying content in special buffers + +(defun clearcase-utl-populate-and-view-buffer (buffer + args + content-generating-func) + "Empty BUFFER, and populate it by applying to ARGS the CONTENT-GENERATING-FUNC, +and display in a separate window." + + (clearcase-utl-edit-and-view-buffer + buffer + (list args) + (function + (lambda (args) + (erase-buffer) + (apply content-generating-func args))))) + +(defun clearcase-utl-edit-and-view-buffer (buffer + args + content-editing-func) + "Empty BUFFER, and edit it by applying to ARGS the CONTENT-EDITING-FUNC, +and display in a separate window." + + (let ( ;; Create the buffer if necessary. + ;; + (buf (get-buffer-create buffer)) + + ;; Record where we came from. + ;; + (camefrom (current-buffer))) + + (set-buffer buf) + (clearcase-view-mode 0 camefrom) + + ;; Edit the buffer. + ;; + (apply content-editing-func args) + + ;; Display the buffer. + ;; + (clearcase-port-view-buffer-other-window buf) + (goto-char 0) + (set-buffer-modified-p nil) ; XEmacs - fsf uses `not-modified' + (shrink-window-if-larger-than-buffer))) + +;;}}} + +;;{{{ Temporary files + +(defvar clearcase-tempfiles nil) +(defun clearcase-utl-tempfile-name (&optional vxpath) + (let ((ext "")) + (and vxpath + (save-match-data + (if (string-match "\\(\\.[^.]+\\)@@" vxpath) + (setq ext (match-string 1 vxpath))))) + (let ((filename (concat + (make-temp-name (clearcase-path-canonical + ;; Use TEMP e.v. if set. + ;; + (concat (or (getenv "TEMP") "/tmp") + "/clearcase-"))) + ext))) + ;; Store its name for later cleanup. + ;; + (setq clearcase-tempfiles (cons filename clearcase-tempfiles)) + filename))) + +(defun clearcase-utl-clean-tempfiles () + (mapcar (function + (lambda (tempfile) + (if (file-exists-p tempfile) + (condition-case nil + (delete-file tempfile) + (error nil))))) + clearcase-tempfiles) + (setq clearcase-tempfiles nil)) + +;;}}} + +(defun clearcase-utl-touch-file (file) + "Attempt to update the modtime of FILE. Return t if it worked." + (zerop + ;; Silently fail if there is no "touch" command available. Couldn't find a + ;; convenient way to update a file's modtime in ELisp. + ;; + (condition-case nil + (prog1 + (shell-command (concat "touch " file)) + (message "")) + (error nil)))) + +(defun clearcase-utl-filetimes-close (filetime1 filetime2 tolerance) + "Test if FILETIME1 and FILETIME2 are within TOLERANCE of each other." + ;; nyi: To do this correctly we need to know MAXINT. + ;; For now this is correct enough since we only use this as a guideline to + ;; avoid generating a diff. + ;; + (if (equal (first filetime1) (first filetime2)) + (< (abs (- (second filetime1) (second filetime2))) tolerance) + nil)) + +(defun clearcase-utl-emacs-date-to-clearcase-date (s) + (concat + (substring s 20) ;; yyyy + (int-to-string (clearcase-utl-month-unparse (substring s 4 7))) ;; mm + (substring s 8 10) ;; dd + "." + (substring s 11 13) ;; hh + (substring s 14 16) ;; mm + (substring s 17 19))) ;; ss + +(defun clearcase-utl-month-unparse (s) + (cond + ((string= s "Jan") 1) + ((string= s "Feb") 2) + ((string= s "Mar") 3) + ((string= s "Apr") 4) + ((string= s "May") 5) + ((string= s "Jun") 6) + ((string= s "Jul") 7) + ((string= s "Aug") 8) + ((string= s "Sep") 9) + ((string= s "Oct") 10) + ((string= s "Nov") 11) + ((string= s "Dec") 12))) + +(defun clearcase-utl-strip-trailing-slashes (name) + (let* ((len (length name))) + (while (and (> len 1) + (or (equal ?/ (aref name (1- len))) + (equal ?\\ (aref name (1- len))))) + (setq len (1- len))) + (substring name 0 len))) + +(defun clearcase-utl-file-size (file) + (nth 7 (file-attributes file))) +(defun clearcase-utl-file-atime (file) + (nth 4 (file-attributes file))) +(defun clearcase-utl-file-mtime (file) + (nth 5 (file-attributes file))) +(defun clearcase-utl-file-ctime (file) + (nth 6 (file-attributes file))) + +(defun clearcase-utl-kill-view-buffer () + (interactive) + (let ((buf (current-buffer))) + (delete-windows-on buf) + (kill-buffer buf))) + +(defun clearcase-utl-escape-double-quotes (s) + "Escape any double quotes in string S" + (mapconcat (function (lambda (char) + (if (equal ?\" char) + (string ?\\ char) + (string char)))) + s + "")) + +(defun clearcase-utl-escape-backslashes (s) + "Double any backslashes in string S" + (mapconcat (function (lambda (char) + (if (equal ?\\ char) + "\\\\" + (string char)))) + s + "")) + +(defun clearcase-utl-quote-if-nec (token) + "If TOKEN contains whitespace and is not already quoted, +wrap it in double quotes." + (if (and (string-match "[ \t]" token) + (not (equal ?\" (aref token 0))) + (not (equal ?\' (aref token 0)))) + (concat "\"" token "\"") + token)) + +(defun clearcase-utl-or-func (&rest args) + "A version of `or' that can be applied to a list." + (let ((result nil) + (cursor args)) + (while (and (null result) + cursor) + (if (car cursor) + (setq result t)) + (setq cursor (cdr cursor))) + result)) + +(defun clearcase-utl-any (predicate list) + "Returns t if PREDICATE is satisfied by any element in LIST." + (let ((result nil) + (cursor list)) + (while (and (null result) + cursor) + (if (funcall predicate (car cursor)) + (setq result t)) + (setq cursor (cdr cursor))) + result)) + +(defun clearcase-utl-every (predicate list) + "Returns t if PREDICATE is satisfied by every element in LIST." + (let ((result t) + (cursor list)) + (while (and result + cursor) + (if (not (funcall predicate (car cursor))) + (setq result nil)) + (setq cursor (cdr cursor))) + result)) + +(defun clearcase-utl-list-filter (predicate list) + "Map PREDICATE over each element of LIST, and return a list of the elements +that mapped to non-nil." + (let ((result '()) + (cursor list)) + (while (not (null cursor)) + (let ((elt (car cursor))) + (if (funcall predicate elt) + (setq result (cons elt result))) + (setq cursor (cdr cursor)))) + (nreverse result))) + +(defun clearcase-utl-elts-are-eq (l) + "Test if all elements of LIST are eq." + (if (null l) + t + (let ((head (car l)) + (answer t)) + (mapcar (function (lambda (elt) + (if (not (eq elt head)) + (setq answer nil)))) + (cdr l)) + answer))) + +;; FSF Emacs - doesn't like parameters on mark-marker. +;; +(defun clearcase-utl-mark-marker () + (if clearcase-xemacs-p + (mark-marker t) + (mark-marker))) + +(defun clearcase-utl-syslog (buf value) + (save-excursion + (let ((tmpbuf (get-buffer buf))) + (if (bufferp tmpbuf) + (progn + (set-buffer buf) + (goto-char (point-max)) + (insert (format "%s\n" value))))))) + +;; Extract the first line of a string. +;; +(defun clearcase-utl-1st-line-of-string (s) + (let ((newline ?\n) + (len (length s)) + (i 0)) + (while (and (< i len) + (not (eq newline + (aref s i)))) + (setq i (1+ i))) + (substring s 0 i))) + +(defun clearcase-utl-split-string (str pat &optional indir suffix) + (let ((ret nil) + (start 0) + (last (length str))) + (while (< start last) + (if (string-match pat str start) + (progn + (let ((tmp (substring str start (match-beginning 0)))) + (if suffix (setq tmp (concat tmp suffix))) + (setq ret (cons (if indir (cons tmp nil) + tmp) + ret))) + (setq start (match-end 0))) + (setq start last) + (setq ret (cons (substring str start) ret)))) + (nreverse ret))) + +(defun clearcase-utl-split-string-at-char (str char) + (let ((ret nil) + (i 0) + (eos (length str))) + (while (< i eos) + ;; Collect next token + ;; + (let ((token-begin i)) + ;; Find the end + ;; + (while (and (< i eos) + (not (eq char (aref str i)))) + (setq i (1+ i))) + + (setq ret (cons (substring str token-begin i) + ret)) + (setq i (1+ i)))) + (nreverse ret))) + + +(defun clearcase-utl-add-env (env var) + (catch 'return + (let ((a env) + (vname (substring var 0 + (and (string-match "=" var) + (match-end 0))))) + (let ((vnl (length vname))) + (while a + (if (and (> (length (car a)) vnl) + (string= (substring (car a) 0 vnl) + vname)) + (throw 'return env)) + (setq a (cdr a))) + (cons var env))))) + + +(defun clearcase-utl-augment-env-from-view-config-spec (old-env tag &optional add-ons) + (let ((newenv nil) + (cc-env (clearcase-misc-extract-evs-from-config-spe tag))) + + ;; 1. Add-on bindings at the front: + ;; + (while add-ons + (setq newenv (clearcase-utl-add-env newenv (car add-ons))) + (setq add-ons (cdr add-ons))) + + ;; 2. Then bindings defined in the config-spec: + ;; + (while cc-env + (setq newenv (clearcase-utl-add-env newenv (car cc-env))) + (setq cc-env (cdr cc-env))) + + ;; 3. Lastly bindings that were in the old environment. + ;; + (while old-env + (setq newenv (clearcase-utl-add-env newenv (car old-env))) + (setq old-env (cdr old-env))) + newenv)) + +(defun clearcase-utl-make-writeable (file) + ;; Equivalent to chmod u+w + ;; + (set-file-modes file + (logior #o0200 (file-modes file)))) + +(defun clearcase-utl-make-unwriteable (file) + ;; Equivalent to chmod u-w + ;; + (set-file-modes file + (logand #o7577 (file-modes file)))) + +;;}}} + +;;}}} + +;;{{{ Menus + +;; Predicate to determine if ClearCase menu items are relevant. +;; nyi" this should disappear +;; +(defun clearcase-buffer-contains-version-p () + "Return true if the current buffer contains a ClearCase file or directory." + (let ((object-name (if (eq major-mode 'dired-mode) + default-directory + buffer-file-name))) + (clearcase-fprop-file-is-version-p object-name))) + +;;{{{ clearcase-mode menu + +;;{{{ The contents + +;; This version of the menu will hide rather than grey out inapplicable entries. +;; +(defvar clearcase-menu-contents-minimised + (list "ClearCase" + + ["Checkin" clearcase-checkin-current-buffer + :keys nil + :visible (clearcase-file-ok-to-checkin buffer-file-name)] + + ["Edit checkout comment" clearcase-edit-checkout-comment-current-buffer + :keys nil + :visible (clearcase-file-ok-to-checkin buffer-file-name)] + + ["Checkout" clearcase-checkout-current-buffer + :keys nil + :visible (clearcase-file-ok-to-checkout buffer-file-name)] + + ["Hijack" clearcase-hijack-current-buffer + :keys nil + :visible (clearcase-file-ok-to-hijack buffer-file-name)] + + ["Unhijack" clearcase-unhijack-current-buffer + :keys nil + :visible (clearcase-file-ok-to-unhijack buffer-file-name)] + + ["Uncheckout" clearcase-uncheckout-current-buffer + :visible (clearcase-file-ok-to-uncheckout buffer-file-name)] + + ["Find checkouts" clearcase-find-checkouts-in-current-view t] + + ["Make element" clearcase-mkelem-current-buffer + :visible (clearcase-file-ok-to-mkelem buffer-file-name)] + + "---------------------------------" + ["Describe version" clearcase-describe-current-buffer + :visible (clearcase-buffer-contains-version-p)] + + ["Describe file" clearcase-describe-current-buffer + :visible (not (clearcase-buffer-contains-version-p))] + + ["Annotate version" clearcase-annotate-current-buffer + :visible (clearcase-buffer-contains-version-p)] + + ["Show config-spec rule" clearcase-what-rule-current-buffer + :visible (clearcase-buffer-contains-version-p)] + + ;; nyi: enable this also when setviewed ? + ;; + ["Edit config-spec" clearcase-edcs-edit t] + + "---------------------------------" + (list "Compare (Emacs)..." + ["Compare with predecessor" clearcase-ediff-pred-current-buffer + :keys nil + :visible (clearcase-buffer-contains-version-p)] + ["Compare with branch base" clearcase-ediff-branch-base-current-buffer + :keys nil + :visible (clearcase-buffer-contains-version-p)] + ["Compare with named version" clearcase-ediff-named-version-current-buffer + :keys nil + :visible (clearcase-buffer-contains-version-p)]) + (list "Compare (GUI)..." + ["Compare with predecessor" clearcase-gui-diff-pred-current-buffer + :keys nil + :visible (clearcase-buffer-contains-version-p)] + ["Compare with branch base" clearcase-gui-diff-branch-base-current-buffer + :keys nil + :visible (clearcase-buffer-contains-version-p)] + ["Compare with named version" clearcase-gui-diff-named-version-current-buffer + :keys nil + :visible (clearcase-buffer-contains-version-p)]) + (list "Compare (diff)..." + ["Compare with predecessor" clearcase-diff-pred-current-buffer + :keys nil + :visible (clearcase-buffer-contains-version-p)] + ["Compare with branch base" clearcase-diff-branch-base-current-buffer + :keys nil + :visible (clearcase-buffer-contains-version-p)] + ["Compare with named version" clearcase-diff-named-version-current-buffer + :keys nil + :visible (clearcase-buffer-contains-version-p)]) + "---------------------------------" + ["Browse versions (dired)" clearcase-browse-vtree-current-buffer + :visible (clearcase-file-ok-to-browse buffer-file-name)] + ["Vtree browser GUI" clearcase-gui-vtree-browser-current-buffer + :keys nil + :visible (clearcase-buffer-contains-version-p)] + "---------------------------------" + (list "Update snapshot..." + ["Update view" clearcase-update-view + :keys nil + :visible (and (clearcase-file-is-in-view-p default-directory) + (not (clearcase-file-is-in-mvfs-p default-directory)))] + ["Update directory" clearcase-update-default-directory + :keys nil + :visible (and (clearcase-file-is-in-view-p default-directory) + (not (clearcase-file-is-in-mvfs-p default-directory)))] + ["Update this file" clearcase-update-current-buffer + :keys nil + :visible (and (clearcase-file-ok-to-checkout buffer-file-name) + (not (clearcase-file-is-in-mvfs-p buffer-file-name)))] + ) + "---------------------------------" + (list "Element history..." + ["Element history (full)" clearcase-list-history-current-buffer + :keys nil + :visible (clearcase-buffer-contains-version-p)] + ["Element history (branch)" clearcase-list-history-current-buffer + :keys nil + :visible (clearcase-buffer-contains-version-p)] + ["Element history (me)" clearcase-list-history-current-buffer + :keys nil + :visible (clearcase-buffer-contains-version-p)]) + "---------------------------------" + ["Show current activity" clearcase-ucm-describe-current-activity + :keys nil + :active (clearcase-vprop-ucm (clearcase-fprop-viewtag default-directory))] + ["Make activity" clearcase-ucm-mkact-current-dir + :keys nil + :visible (clearcase-vprop-ucm (clearcase-fprop-viewtag default-directory))] + ["Set activity..." clearcase-ucm-set-activity-current-dir + :keys nil + :visible (clearcase-vprop-ucm (clearcase-fprop-viewtag default-directory))] + ["Set NO activity" clearcase-ucm-set-activity-none-current-dir + :keys nil + :visible (clearcase-vprop-ucm (clearcase-fprop-viewtag default-directory))] + ["Rebase this stream" clearcase-gui-rebase + :keys nil + :visible (clearcase-vprop-ucm (clearcase-fprop-viewtag default-directory))] + ["Deliver from this stream" clearcase-gui-deliver + :keys nil + :visible (clearcase-vprop-ucm (clearcase-fprop-viewtag default-directory))] + "---------------------------------" + (list "ClearCase GUI" + ["ClearCase Explorer" clearcase-gui-clearexplorer + :keys nil + :visible clearcase-on-mswindows] + ["Project Explorer" clearcase-gui-project-explorer + :keys nil] + ["Merge Manager" clearcase-gui-merge-manager + :keys nil] + ["Snapshot View Updater" clearcase-gui-snapshot-view-updater + :keys nil]) + "---------------------------------" + + ;; nyi: + ;; Enable this when current buffer is on VOB. + ;; + ["Make branch type" clearcase-mkbrtype + :keys nil] + + "---------------------------------" + ["Report Bug in ClearCase Mode" clearcase-submit-bug-report + :keys nil] + + ["Dump internals" clearcase-dump + :keys nil + :visible (or (equal "rwhitby" (user-login-name)) + (equal "esler" (user-login-name)))] + + ["Flush caches" clearcase-flush-caches + :keys nil + :visible (or (equal "rwhitby" (user-login-name)) + (equal "esler" (user-login-name)))] + + "---------------------------------" + ["Customize..." (customize-group 'clearcase) + :keys nil])) + +(defvar clearcase-menu-contents + (list "ClearCase" + + ["Checkin" clearcase-checkin-current-buffer + :keys nil + :active (clearcase-file-ok-to-checkin buffer-file-name)] + + ["Edit checkout comment" clearcase-edit-checkout-comment-current-buffer + :keys nil + :active (clearcase-file-ok-to-checkin buffer-file-name)] + + ["Checkout" clearcase-checkout-current-buffer + :keys nil + :active (clearcase-file-ok-to-checkout buffer-file-name)] + + ["Hijack" clearcase-hijack-current-buffer + :keys nil + :active (clearcase-file-ok-to-hijack buffer-file-name)] + + ["Unhijack" clearcase-unhijack-current-buffer + :keys nil + :active (clearcase-file-ok-to-unhijack buffer-file-name)] + + ["Uncheckout" clearcase-uncheckout-current-buffer + :active (clearcase-file-ok-to-uncheckout buffer-file-name)] + + ["Make element" clearcase-mkelem-current-buffer + :active (clearcase-file-ok-to-mkelem buffer-file-name)] + + "---------------------------------" + ["Describe version" clearcase-describe-current-buffer + :active (clearcase-buffer-contains-version-p)] + + ["Describe file" clearcase-describe-current-buffer + :active (not (clearcase-buffer-contains-version-p))] + + ["Annotate version" clearcase-annotate-current-buffer + :keys nil + :active (clearcase-buffer-contains-version-p)] + + ["Show config-spec rule" clearcase-what-rule-current-buffer + :active (clearcase-buffer-contains-version-p)] + + ;; nyi: enable this also when setviewed ? + ;; + ["Edit config-spec" clearcase-edcs-edit t] + + "---------------------------------" + (list "Compare (Emacs)..." + ["Compare with predecessor" clearcase-ediff-pred-current-buffer + :keys nil + :active (clearcase-buffer-contains-version-p)] + ["Compare with branch base" clearcase-ediff-branch-base-current-buffer + :keys nil + :active (clearcase-buffer-contains-version-p)] + ["Compare with named version" clearcase-ediff-named-version-current-buffer + :keys nil + :active (clearcase-buffer-contains-version-p)]) + (list "Compare (GUI)..." + ["Compare with predecessor" clearcase-gui-diff-pred-current-buffer + :keys nil + :active (clearcase-buffer-contains-version-p)] + ["Compare with branch base" clearcase-gui-diff-branch-base-current-buffer + :keys nil + :active (clearcase-buffer-contains-version-p)] + ["Compare with named version" clearcase-gui-diff-named-version-current-buffer + :keys nil + :active (clearcase-buffer-contains-version-p)]) + (list "Compare (diff)..." + ["Compare with predecessor" clearcase-diff-pred-current-buffer + :keys nil + :active (clearcase-buffer-contains-version-p)] + ["Compare with branch base" clearcase-diff-branch-base-current-buffer + :keys nil + :active (clearcase-buffer-contains-version-p)] + ["Compare with named version" clearcase-diff-named-version-current-buffer + :keys nil + :active (clearcase-buffer-contains-version-p)]) + "---------------------------------" + ["Browse versions (dired)" clearcase-browse-vtree-current-buffer + :active (clearcase-file-ok-to-browse buffer-file-name)] + ["Vtree browser GUI" clearcase-gui-vtree-browser-current-buffer + :keys nil + :active (clearcase-buffer-contains-version-p)] + "---------------------------------" + (list "Update snapshot..." + ["Update view" clearcase-update-view + :keys nil + :active (and (clearcase-file-is-in-view-p default-directory) + (not (clearcase-file-is-in-mvfs-p default-directory)))] + ["Update directory" clearcase-update-default-directory + :keys nil + :active (and (clearcase-file-is-in-view-p default-directory) + (not (clearcase-file-is-in-mvfs-p default-directory)))] + ["Update this file" clearcase-update-current-buffer + :keys nil + :active (and (clearcase-file-ok-to-checkout buffer-file-name) + (not (clearcase-file-is-in-mvfs-p buffer-file-name)))] + ) + "---------------------------------" + (list "Element history..." + ["Element history (full)" clearcase-list-history-current-buffer + :keys nil + :active (clearcase-buffer-contains-version-p)] + ["Element history (branch)" clearcase-list-history-current-buffer + :keys nil + :active (clearcase-buffer-contains-version-p)] + ["Element history (me)" clearcase-list-history-current-buffer + :keys nil + :active (clearcase-buffer-contains-version-p)]) + "---------------------------------" + ["Show current activity" clearcase-ucm-describe-current-activity + :keys nil + :active (clearcase-vprop-ucm (clearcase-fprop-viewtag default-directory))] + ["Make activity" clearcase-ucm-mkact-current-dir + :keys nil + :active (clearcase-vprop-ucm (clearcase-fprop-viewtag default-directory))] + ["Set activity..." clearcase-ucm-set-activity-current-dir + :keys nil + :active (clearcase-vprop-ucm (clearcase-fprop-viewtag default-directory))] + ["Set NO activity" clearcase-ucm-set-activity-none-current-dir + :keys nil + :active (clearcase-vprop-ucm (clearcase-fprop-viewtag default-directory))] + ["Rebase this stream" clearcase-gui-rebase + :keys nil + :active (clearcase-vprop-ucm (clearcase-fprop-viewtag default-directory))] + ["Deliver from this stream" clearcase-gui-deliver + :keys nil + :active (clearcase-vprop-ucm (clearcase-fprop-viewtag default-directory))] + "---------------------------------" + (list "ClearCase GUI" + ["ClearCase Explorer" clearcase-gui-clearexplorer + :keys nil + :active clearcase-on-mswindows] + ["Project Explorer" clearcase-gui-project-explorer + :keys nil] + ["Merge Manager" clearcase-gui-merge-manager + :keys nil] + ["Snapshot View Updater" clearcase-gui-snapshot-view-updater + :keys nil]) + "---------------------------------" + + ;; nyi: + ;; Enable this when current buffer is on VOB. + ;; + ["Make branch type" clearcase-mkbrtype + :keys nil] + + "---------------------------------" + ["Report Bug in ClearCase Mode" clearcase-submit-bug-report + :keys nil] + + ["Dump internals" clearcase-dump + :keys nil + :active (or (equal "rwhitby" (user-login-name)) + (equal "esler" (user-login-name)))] + + ["Flush caches" clearcase-flush-caches + :keys nil + :active (or (equal "rwhitby" (user-login-name)) + (equal "esler" (user-login-name)))] + + "---------------------------------" + ["Customize..." (customize-group 'clearcase) + :keys nil])) + +(if (and clearcase-minimise-menus + (not clearcase-xemacs-p)) + (setq clearcase-menu-contents clearcase-menu-contents-minimised)) + +;;}}}1 + +(if (>= emacs-major-version '20) + (progn + ;; Define the menu + ;; + (easy-menu-define + clearcase-menu + (list clearcase-mode-map) + "ClearCase menu" + clearcase-menu-contents) + + (or clearcase-xemacs-p + (add-to-list 'menu-bar-final-items 'ClearCase)))) + +;;}}} + +;;{{{ clearcase-dired-mode menu + +;;{{{ Related functions + +;; nyi: this probably gets run for each menu element. +;; For better efficiency, look into using a one-pass ":filter" +;; to construct this menu dynamically. + +(defun clearcase-dired-mark-count () + (let ((old-point (point)) + (count 0)) + (goto-char (point-min)) + (while (re-search-forward + (concat "^" (regexp-quote (char-to-string + dired-marker-char))) nil t) + (setq count (1+ count))) + (goto-char old-point) + count)) + +(defun clearcase-dired-current-ok-to-checkin () + (let ((file (dired-get-filename nil t))) + (and file + (clearcase-file-ok-to-checkin file)))) + +(defun clearcase-dired-current-ok-to-checkout () + (let ((file (dired-get-filename nil t))) + (and file + (clearcase-file-ok-to-checkout file)))) + +(defun clearcase-dired-current-ok-to-uncheckout () + (let ((file (dired-get-filename nil t))) + (and file + (clearcase-file-ok-to-uncheckout file)))) + +(defun clearcase-dired-current-ok-to-hijack () + (let ((file (dired-get-filename nil t))) + (and file + (clearcase-file-ok-to-hijack file)))) + +(defun clearcase-dired-current-ok-to-unhijack () + (let ((file (dired-get-filename nil t))) + (and file + (clearcase-file-ok-to-unhijack file)))) + +(defun clearcase-dired-current-ok-to-mkelem () + (let ((file (dired-get-filename nil t))) + (and file + (clearcase-file-ok-to-mkelem file)))) + +(defun clearcase-dired-current-ok-to-browse () + (let ((file (dired-get-filename nil t))) + (clearcase-file-ok-to-browse file))) + +(defvar clearcase-dired-max-marked-files-to-check 5 + "The maximum number of marked files in a Dired buffer when constructing +the ClearCase menu.") + +;; nyi: speed these up by stopping check when a non-qualifying file is found +;; Better: +;; - hook the menu constuction and figure out what ops apply +;; - hook mark/unmark/move cursor + +(defun clearcase-dired-marked-ok-to-checkin () + (let ((files (dired-get-marked-files))) + (or (> (length files) clearcase-dired-max-marked-files-to-check) + (clearcase-utl-every (function clearcase-file-ok-to-checkin) + files)))) + +(defun clearcase-dired-marked-ok-to-checkout () + (let ((files (dired-get-marked-files))) + (or (> (length files) clearcase-dired-max-marked-files-to-check) + (clearcase-utl-every (function clearcase-file-ok-to-checkout) + files)))) + +(defun clearcase-dired-marked-ok-to-uncheckout () + (let ((files (dired-get-marked-files))) + (or (> (length files) clearcase-dired-max-marked-files-to-check) + (clearcase-utl-every (function clearcase-file-ok-to-uncheckout) + files)))) + +(defun clearcase-dired-marked-ok-to-hijack () + (let ((files (dired-get-marked-files))) + (or (> (length files) clearcase-dired-max-marked-files-to-check) + (clearcase-utl-every (function clearcase-file-ok-to-hijack) + files)))) + +(defun clearcase-dired-marked-ok-to-unhijack () + (let ((files (dired-get-marked-files))) + (or (> (length files) clearcase-dired-max-marked-files-to-check) + (clearcase-utl-every (function clearcase-file-ok-to-unhijack) + files)))) + +(defun clearcase-dired-marked-ok-to-mkelem () + (let ((files (dired-get-marked-files))) + (or (> (length files) clearcase-dired-max-marked-files-to-check) + (clearcase-utl-every (function clearcase-file-ok-to-mkelem) + files)))) + +(defun clearcase-dired-current-dir-ok-to-checkin () + (let ((dir (dired-current-directory))) + (clearcase-file-ok-to-checkin dir))) + +(defun clearcase-dired-current-dir-ok-to-checkout () + (let ((dir (dired-current-directory))) + (clearcase-file-ok-to-checkout dir))) + +(defun clearcase-dired-current-dir-ok-to-uncheckout () + (let ((dir (dired-current-directory))) + (clearcase-file-ok-to-uncheckout dir))) + +;;}}} + +;;{{{ Contents + +;; This version of the menu will hide rather than grey out inapplicable entries. +;; +(defvar clearcase-dired-menu-contents-minimised + (list "ClearCase" + + ;; Current file + ;; + ["Checkin file" clearcase-checkin-dired-files + :keys nil + :visible (and (< (clearcase-dired-mark-count) 2) + (clearcase-dired-current-ok-to-checkin))] + + ["Edit checkout comment" clearcase-edit-checkout-comment-dired-file + :keys nil + :visible (and (< (clearcase-dired-mark-count) 2) + (clearcase-dired-current-ok-to-checkin))] + + ["Checkout file" clearcase-checkout-dired-files + :keys nil + :visible (and (< (clearcase-dired-mark-count) 2) + (clearcase-dired-current-ok-to-checkout))] + + ["Uncheckout file" clearcase-uncheckout-dired-files + :keys nil + :visible (and (< (clearcase-dired-mark-count) 2) + (clearcase-dired-current-ok-to-uncheckout))] + + ["Hijack file" clearcase-hijack-dired-files + :keys nil + :visible (and (< (clearcase-dired-mark-count) 2) + (clearcase-dired-current-ok-to-hijack))] + + ["Unhijack file" clearcase-unhijack-dired-files + :keys nil + :visible (and (< (clearcase-dired-mark-count) 2) + (clearcase-dired-current-ok-to-unhijack))] + + ["Find checkouts" clearcase-find-checkouts-in-current-view t] + + ["Make file an element" clearcase-mkelem-dired-files + :visible (and (< (clearcase-dired-mark-count) 2) + (clearcase-dired-current-ok-to-mkelem))] + + ;; Marked files + ;; + ["Checkin marked files" clearcase-checkin-dired-files + :keys nil + :visible (and (>= (clearcase-dired-mark-count) 2) + (clearcase-dired-marked-ok-to-checkin))] + + ["Checkout marked files" clearcase-checkout-dired-files + :keys nil + :visible (and (>= (clearcase-dired-mark-count) 2) + (clearcase-dired-marked-ok-to-checkout))] + + ["Uncheckout marked files" clearcase-uncheckout-dired-files + :keys nil + :visible (and (>= (clearcase-dired-mark-count) 2) + (clearcase-dired-marked-ok-to-uncheckout))] + + ["Hijack marked files" clearcase-hijack-dired-files + :keys nil + :visible (and (>= (clearcase-dired-mark-count) 2) + (clearcase-dired-marked-ok-to-hijack))] + + ["Unhijack marked files" clearcase-unhijack-dired-files + :keys nil + :visible (and (>= (clearcase-dired-mark-count) 2) + (clearcase-dired-marked-ok-to-unhijack))] + + ["Make marked files elements" clearcase-mkelem-dired-files + :keys nil + :visible (and (>= (clearcase-dired-mark-count) 2) + (clearcase-dired-marked-ok-to-mkelem))] + + + ;; Current directory + ;; + ["Checkin current-dir" clearcase-dired-checkin-current-dir + :keys nil + :visible (clearcase-dired-current-dir-ok-to-checkin)] + + ["Checkout current dir" clearcase-dired-checkout-current-dir + :keys nil + :visible (clearcase-dired-current-dir-ok-to-checkout)] + + ["Uncheckout current dir" clearcase-dired-uncheckout-current-dir + :keys nil + :visible (clearcase-dired-current-dir-ok-to-uncheckout)] + + "---------------------------------" + ["Describe file" clearcase-describe-dired-file + :visible t] + + ["Annotate file" clearcase-annotate-dired-file + :visible t] + + ["Show config-spec rule" clearcase-what-rule-dired-file + :visible t] + + + ["Edit config-spec" clearcase-edcs-edit t] + + "---------------------------------" + (list "Compare (Emacs)..." + ["Compare with predecessor" clearcase-ediff-pred-dired-file + :keys nil + :visible t] + ["Compare with branch base" clearcase-ediff-branch-base-dired-file + :keys nil + :visible t] + ["Compare with named version" clearcase-ediff-named-version-dired-file + :keys nil + :visible t]) + (list "Compare (GUI)..." + ["Compare with predecessor" clearcase-gui-diff-pred-dired-file + :keys nil + :visible t] + ["Compare with branch base" clearcase-gui-diff-branch-base-dired-file + :keys nil + :visible t] + ["Compare with named version" clearcase-gui-diff-named-version-dired-file + :keys nil + :visible t]) + (list "Compare (diff)..." + ["Compare with predecessor" clearcase-diff-pred-dired-file + :keys nil + :visible t] + ["Compare with branch base" clearcase-diff-branch-base-dired-file + :keys nil + :visible t] + ["Compare with named version" clearcase-diff-named-version-dired-file + :keys nil + :visible t]) + "---------------------------------" + ["Browse versions (dired)" clearcase-browse-vtree-dired-file + :visible (clearcase-dired-current-ok-to-browse)] + ["Vtree browser GUI" clearcase-gui-vtree-browser-dired-file + :keys nil + :visible t] + "---------------------------------" + (list "Update snapshot..." + ["Update view" clearcase-update-view + :keys nil + :visible (and (clearcase-file-is-in-view-p default-directory) + (not (clearcase-file-is-in-mvfs-p default-directory)))] + ["Update directory" clearcase-update-default-directory + :keys nil + :visible (and (clearcase-file-is-in-view-p default-directory) + (not (clearcase-file-is-in-mvfs-p default-directory)))] + ["Update file" clearcase-update-dired-files + :keys nil + :visible (and (< (clearcase-dired-mark-count) 2) + (clearcase-dired-current-ok-to-checkout) + (not (clearcase-file-is-in-mvfs-p default-directory)))] + ["Update marked files" clearcase-update-dired-files + :keys nil + :visible (and (>= (clearcase-dired-mark-count) 2) + (not (clearcase-file-is-in-mvfs-p default-directory)))] + ) + "---------------------------------" + (list "Element history..." + ["Element history (full)" clearcase-list-history-dired-file + :keys nil + :visible t] + ["Element history (branch)" clearcase-list-history-dired-file + :keys nil + :visible t] + ["Element history (me)" clearcase-list-history-dired-file + :keys nil + :visible t]) + "---------------------------------" + ["Show current activity" clearcase-ucm-describe-current-activity + :keys nil + :active (clearcase-vprop-ucm (clearcase-fprop-viewtag default-directory))] + ["Make activity" clearcase-ucm-mkact-current-dir + :keys nil + :visible (clearcase-vprop-ucm (clearcase-fprop-viewtag default-directory))] + ["Set activity..." clearcase-ucm-set-activity-current-dir + :keys nil + :visible (clearcase-vprop-ucm (clearcase-fprop-viewtag default-directory))] + ["Set NO activity" clearcase-ucm-set-activity-none-current-dir + :keys nil + :visible (clearcase-vprop-ucm (clearcase-fprop-viewtag default-directory))] + ["Rebase this stream" clearcase-gui-rebase + :keys nil + :visible (clearcase-vprop-ucm (clearcase-fprop-viewtag default-directory))] + ["Deliver from this stream" clearcase-gui-deliver + :keys nil + :visible (clearcase-vprop-ucm (clearcase-fprop-viewtag default-directory))] + "---------------------------------" + (list "ClearCase GUI" + ["ClearCase Explorer" clearcase-gui-clearexplorer + :keys nil + :visible clearcase-on-mswindows] + ["Project Explorer" clearcase-gui-project-explorer + :keys nil] + ["Merge Manager" clearcase-gui-merge-manager + :keys nil] + ["Snapshot View Updater" clearcase-gui-snapshot-view-updater + :keys nil]) + "---------------------------------" + + ["Make branch type" clearcase-mkbrtype + :keys nil] + + "---------------------------------" + ["Report Bug in ClearCase Mode" clearcase-submit-bug-report + :keys nil] + + ["Dump internals" clearcase-dump + :keys nil + :visible (or (equal "rwhitby" (user-login-name)) + (equal "esler" (user-login-name)))] + + ["Flush caches" clearcase-flush-caches + :keys nil + :visible (or (equal "rwhitby" (user-login-name)) + (equal "esler" (user-login-name)))] + + "---------------------------------" + ["Customize..." (customize-group 'clearcase) + :keys nil])) + +(defvar clearcase-dired-menu-contents + (list "ClearCase" + + ;; Current file + ;; + ["Checkin file" clearcase-checkin-dired-files + :keys nil + :active (and (< (clearcase-dired-mark-count) 2) + (clearcase-dired-current-ok-to-checkin))] + + ["Edit checkout comment" clearcase-edit-checkout-comment-dired-file + :keys nil + :active (and (< (clearcase-dired-mark-count) 2) + (clearcase-dired-current-ok-to-checkin))] + + ["Checkout file" clearcase-checkout-dired-files + :keys nil + :active (and (< (clearcase-dired-mark-count) 2) + (clearcase-dired-current-ok-to-checkout))] + + ["Uncheckout file" clearcase-uncheckout-dired-files + :keys nil + :active (and (< (clearcase-dired-mark-count) 2) + (clearcase-dired-current-ok-to-uncheckout))] + + ["Hijack file" clearcase-hijack-dired-files + :keys nil + :active (and (< (clearcase-dired-mark-count) 2) + (clearcase-dired-current-ok-to-hijack))] + + ["Unhijack file" clearcase-unhijack-dired-files + :keys nil + :active (and (< (clearcase-dired-mark-count) 2) + (clearcase-dired-current-ok-to-unhijack))] + + ["Make file an element" clearcase-mkelem-dired-files + :active (and (< (clearcase-dired-mark-count) 2) + (clearcase-dired-current-ok-to-mkelem))] + + ;; Marked files + ;; + ["Checkin marked files" clearcase-checkin-dired-files + :keys nil + :active (and (>= (clearcase-dired-mark-count) 2) + (clearcase-dired-marked-ok-to-checkin))] + + ["Checkout marked files" clearcase-checkout-dired-files + :keys nil + :active (and (>= (clearcase-dired-mark-count) 2) + (clearcase-dired-marked-ok-to-checkout))] + + ["Uncheckout marked files" clearcase-uncheckout-dired-files + :keys nil + :active (and (>= (clearcase-dired-mark-count) 2) + (clearcase-dired-marked-ok-to-uncheckout))] + + ["Hijack marked files" clearcase-hijack-dired-files + :keys nil + :active (and (>= (clearcase-dired-mark-count) 2) + (clearcase-dired-marked-ok-to-hijack))] + + ["Unhijack marked files" clearcase-unhijack-dired-files + :keys nil + :active (and (>= (clearcase-dired-mark-count) 2) + (clearcase-dired-marked-ok-to-unhijack))] + + ["Make marked files elements" clearcase-mkelem-dired-files + :keys nil + :active (and (>= (clearcase-dired-mark-count) 2) + (clearcase-dired-marked-ok-to-mkelem))] + + + ;; Current directory + ;; + ["Checkin current-dir" clearcase-dired-checkin-current-dir + :keys nil + :active (clearcase-dired-current-dir-ok-to-checkin)] + + ["Checkout current dir" clearcase-dired-checkout-current-dir + :keys nil + :active (clearcase-dired-current-dir-ok-to-checkout)] + + ["Uncheckout current dir" clearcase-dired-uncheckout-current-dir + :keys nil + :active (clearcase-dired-current-dir-ok-to-uncheckout)] + + "---------------------------------" + ["Describe file" clearcase-describe-dired-file + :active t] + + ["Annotate file" clearcase-annotate-dired-file + :active t] + + ["Show config-spec rule" clearcase-what-rule-dired-file + :active t] + + + ["Edit config-spec" clearcase-edcs-edit t] + + "---------------------------------" + (list "Compare (Emacs)..." + ["Compare with predecessor" clearcase-ediff-pred-dired-file + :keys nil + :active t] + ["Compare with branch base" clearcase-ediff-branch-base-dired-file + :keys nil + :active t] + ["Compare with named version" clearcase-ediff-named-version-dired-file + :keys nil + :active t]) + (list "Compare (GUI)..." + ["Compare with predecessor" clearcase-gui-diff-pred-dired-file + :keys nil + :active t] + ["Compare with branch base" clearcase-gui-diff-branch-base-dired-file + :keys nil + :active t] + ["Compare with named version" clearcase-gui-diff-named-version-dired-file + :keys nil + :active t]) + (list "Compare (diff)..." + ["Compare with predecessor" clearcase-diff-pred-dired-file + :keys nil + :active t] + ["Compare with branch base" clearcase-diff-branch-base-dired-file + :keys nil + :active t] + ["Compare with named version" clearcase-diff-named-version-dired-file + :keys nil + :active t]) + "---------------------------------" + ["Browse versions (dired)" clearcase-browse-vtree-dired-file + :active (clearcase-dired-current-ok-to-browse)] + ["Vtree browser GUI" clearcase-gui-vtree-browser-dired-file + :keys nil + :active t] + "---------------------------------" + (list "Update snapshot..." + ["Update view" clearcase-update-view + :keys nil + :active (and (clearcase-file-is-in-view-p default-directory) + (not (clearcase-file-is-in-mvfs-p default-directory)))] + ["Update directory" clearcase-update-default-directory + :keys nil + :active (and (clearcase-file-is-in-view-p default-directory) + (not (clearcase-file-is-in-mvfs-p default-directory)))] + ["Update file" clearcase-update-dired-files + :keys nil + :active (and (< (clearcase-dired-mark-count) 2) + (clearcase-dired-current-ok-to-checkout) + (not (clearcase-file-is-in-mvfs-p default-directory)))] + ["Update marked files" clearcase-update-dired-files + :keys nil + :active (and (>= (clearcase-dired-mark-count) 2) + (not (clearcase-file-is-in-mvfs-p default-directory)))] + ) + "---------------------------------" + (list "Element history..." + ["Element history (full)" clearcase-list-history-dired-file + :keys nil + :active t] + ["Element history (branch)" clearcase-list-history-dired-file + :keys nil + :active t] + ["Element history (me)" clearcase-list-history-dired-file + :keys nil + :active t]) + "---------------------------------" + ["Show current activity" clearcase-ucm-describe-current-activity + :keys nil + :active (clearcase-vprop-ucm (clearcase-fprop-viewtag default-directory))] + ["Make activity" clearcase-ucm-mkact-current-dir + :keys nil + :active (clearcase-vprop-ucm (clearcase-fprop-viewtag default-directory))] + ["Set activity..." clearcase-ucm-set-activity-current-dir + :keys nil + :active (clearcase-vprop-ucm (clearcase-fprop-viewtag default-directory))] + ["Set NO activity" clearcase-ucm-set-activity-none-current-dir + :keys nil + :active (clearcase-vprop-ucm (clearcase-fprop-viewtag default-directory))] + ["Rebase this stream" clearcase-gui-rebase + :keys nil + :active (clearcase-vprop-ucm (clearcase-fprop-viewtag default-directory))] + ["Deliver from this stream" clearcase-gui-deliver + :keys nil + :active (clearcase-vprop-ucm (clearcase-fprop-viewtag default-directory))] + "---------------------------------" + (list "ClearCase GUI" + ["ClearCase Explorer" clearcase-gui-clearexplorer + :keys nil + :active clearcase-on-mswindows] + ["Project Explorer" clearcase-gui-project-explorer + :keys nil] + ["Merge Manager" clearcase-gui-merge-manager + :keys nil] + ["Snapshot View Updater" clearcase-gui-snapshot-view-updater + :keys nil]) + "---------------------------------" + + ["Make branch type" clearcase-mkbrtype + :keys nil] + + "---------------------------------" + ["Report Bug in ClearCase Mode" clearcase-submit-bug-report + :keys nil] + + ["Dump internals" clearcase-dump + :keys nil + :active (or (equal "rwhitby" (user-login-name)) + (equal "esler" (user-login-name)))] + + ["Flush caches" clearcase-flush-caches + :keys nil + :active (or (equal "rwhitby" (user-login-name)) + (equal "esler" (user-login-name)))] + + "---------------------------------" + ["Customize..." (customize-group 'clearcase) + :keys nil])) + +(if (and clearcase-minimise-menus + (not clearcase-xemacs-p)) + (setq clearcase-dired-menu-contents clearcase-dired-menu-contents-minimised)) + +;;}}} + +(if (>= emacs-major-version '20) + (progn + (easy-menu-define + clearcase-dired-menu + (list clearcase-dired-mode-map) + "ClearCase Dired menu" + clearcase-dired-menu-contents) + + (or clearcase-xemacs-p + (add-to-list 'menu-bar-final-items 'ClearCase)))) + +;;}}} + +;;}}} + +;;{{{ Widgets + +;;{{{ Single-selection buffer widget + +;; Keep the compiler quiet by declaring these +;; buffer-local variables here thus. +;; +(defvar clearcase-selection-window-config nil) +(defvar clearcase-selection-interpreter nil) +(defvar clearcase-selection-continuation nil) +(defvar clearcase-selection-operands nil) + +(defun clearcase-ucm-make-selection-window (buffer-name + buffer-contents + selection-interpreter + continuation + cont-arglist) + (let ((buf (get-buffer-create buffer-name))) + (save-excursion + + ;; Reset the buffer + ;; + (set-buffer buf) + (setq buffer-read-only nil) + (erase-buffer) + (setq truncate-lines t) + + ;; Paint the buffer + ;; + (goto-char (point-min)) + (insert buffer-contents) + + ;; Insert mouse-highlighting + ;; + (save-excursion + (goto-char (point-min)) + (while (< (point) (point-max)) + (condition-case nil + (progn + (beginning-of-line) + (put-text-property (point) + (save-excursion + (end-of-line) + (point)) + 'mouse-face 'highlight)) + (error nil)) + (forward-line 1))) + + ;; Set a keymap + ;; + (setq buffer-read-only t) + (use-local-map clearcase-selection-keymap) + + ;; Set up the interpreter and continuation + ;; + (set (make-local-variable 'clearcase-selection-window-config) + (current-window-configuration)) + (set (make-local-variable 'clearcase-selection-interpreter) + selection-interpreter) + (set (make-local-variable 'clearcase-selection-continuation) + continuation) + (set (make-local-variable 'clearcase-selection-operands) + cont-arglist)) + + ;; Display the buffer + ;; + (pop-to-buffer buf) + (goto-char 0) + (shrink-window-if-larger-than-buffer) + (message "Use RETURN to select an item"))) + +(defun clearcase-selection-continue () + (interactive) + (beginning-of-line) + (sit-for 0) + ;; Call the interpreter to extract the item of interest + ;; from the buffer. + ;; + (let ((item (funcall clearcase-selection-interpreter))) + ;; Call the continuation. + ;; + (apply clearcase-selection-continuation + (append clearcase-selection-operands (list item)))) + + ;; Restore window config + ;; + (let ((sel-buffer (current-buffer))) + (if clearcase-selection-window-config + (set-window-configuration clearcase-selection-window-config)) + (delete-windows-on sel-buffer) + (kill-buffer sel-buffer))) + +(defun clearcase-selection-mouse-continue (click) + (interactive "@e") + (mouse-set-point click) + (clearcase-selection-continue)) + +(defvar clearcase-selection-keymap + (let ((map (make-sparse-keymap))) + (define-key map [return] 'clearcase-selection-continue) + (define-key map [mouse-2] 'clearcase-selection-mouse-continue) + (define-key map "q" 'clearcase-utl-kill-view-buffer) + ;; nyi: refresh list + ;; (define-key map "g" 'clearcase-selection-get) + map)) + +;;}}} + +;;}}} + +;;{{{ Integration with Emacs + +;;{{{ Functions: examining the ClearCase installation + +;; Discover ClearCase version-string +;; +(defun clearcase-get-version-string () + ;; Some care seems to be necessary to avoid problems caused by odd settings + ;; of the "SHELL" environment variable. I found that simply + ;; (shell-command-to-string "cleartool -version") on Windows-2000 with + ;; SHELL==cmd.exe just returned a copy of the Windows command prompt. The + ;; result was that clearcase-integrate would not complete. + ;; + ;; The follow seems to work. + ;; + (if clearcase-on-mswindows + (shell-command-to-string "cmd /c cleartool -version") + (shell-command-to-string "sh -c \"cleartool -version\""))) + +;; Find where cleartool is installed. +;; +(defun clearcase-find-cleartool () + "Search directories listed in the PATH environment variable +looking for a cleartool executable. If found return the full pathname." + (let ((dir-list (parse-colon-path (getenv "PATH"))) + (cleartool-name (if clearcase-on-mswindows + "cleartool.exe" + "cleartool")) + (cleartool-path nil)) + (catch 'found + (mapcar + (function (lambda (dir) + (let ((f (expand-file-name (concat dir cleartool-name)))) + (if (file-executable-p f) + (progn + (setq cleartool-path f) + (throw 'found t)))))) + dir-list) + nil) + cleartool-path)) + +(defun clearcase-non-lt-registry-server-online-p () + "Heuristic to determine if the local host is network-connected to +its ClearCase servers. Used for a non-LT system." + + (let ((result nil) + (buf (get-buffer-create " *clearcase-lsregion*"))) + (save-excursion + (set-buffer buf) + (erase-buffer) + (let ((process (start-process "lsregion" + buf + "cleartool" + "lsregion" + "-long")) + (timeout-occurred nil)) + + ;; Now wait a little while, if necessary, for some output. + ;; + (while (and (null result) + (not timeout-occurred) + (< (buffer-size) (length "Tag: "))) + (if (null (accept-process-output process 10)) + (setq timeout-occurred t)) + (goto-char (point-min)) + (if (looking-at "Tag: ") + (setq result t))) + (condition-case nil + (kill-process process) + (error nil)))) + ;; If servers are apparently not online, keep the + ;; buffer around so we can see what lsregion reported. + ;; + (sit-for 0.01); Fix by AJM to prevent kill-buffer claiming process still running + (if result + (kill-buffer buf)) + result)) + +;; We could have an LT system, which lacks ct+lsregion, but has ct+lssite. +;; +(defun clearcase-lt-registry-server-online-p () + "Heuristic to determine if the local host is network-connected to +its ClearCase servers. Used for LT system." + + (let ((result nil) + (buf (get-buffer-create " *clearcase-lssite*"))) + (save-excursion + (set-buffer buf) + (erase-buffer) + (let ((process (start-process "lssite" + buf + "cleartool" + "lssite" + "-inquire")) + (timeout-occurred nil)) + + ;; Now wait a little while, if necessary, for some output. + ;; + (while (and (null result) + (not timeout-occurred) + (< (buffer-size) (length " view_cache_size"))) + (if (null (accept-process-output process 10)) + (setq timeout-occurred t)) + (goto-char (point-min)) + (if (re-search-forward "view_cache_size" nil t) + (setq result t))) + (condition-case nil + (kill-process process) + (error nil)))) + + ;; If servers are apparently not online, keep the + ;; buffer around so we can see what lssite reported. + ;; + (sit-for 0.01); Fix by AJM to prevent kill-buffer claiming process still running + (if result + (kill-buffer buf)) + result)) + +;; Find out if the ClearCase registry server is accessible. +;; We could be on a disconnected laptop. +;; +(defun clearcase-registry-server-online-p () + "Heuristic to determine if the local host is network-connected to +its ClearCase server(s)." + + (if clearcase-lt + (clearcase-lt-registry-server-online-p) + (clearcase-non-lt-registry-server-online-p))) + +;;}}} +;;{{{ Functions: hooks + +;;{{{ A find-file hook to turn on clearcase-mode + +(defun clearcase-hook-find-file-hook () + (let ((filename (buffer-file-name))) + (if filename + (progn + (clearcase-fprop-unstore-properties filename) + (if (clearcase-file-would-be-in-view-p filename) + (progn + ;; 1. Activate minor mode + ;; + (clearcase-mode 1) + + ;; 2. Pre-fetch file properties + ;; + (if (file-exists-p filename) + (progn + (clearcase-fprop-get-properties filename) + + ;; 3. Put branch/ver in mode-line + ;; + (setq clearcase-mode + (concat " ClearCase:" + (clearcase-mode-line-buffer-id filename))) + (force-mode-line-update) + + ;; 4. Schedule the asynchronous fetching of the view's properties + ;; next time Emacs is idle enough. + ;; + (clearcase-vprop-schedule-work (clearcase-fprop-viewtag filename)) + + ;; 5. Set backup policy + ;; + (unless clearcase-make-backup-files + (make-local-variable 'backup-inhibited) + (setq backup-inhibited t)))) + + (clearcase-set-auto-mode))))))) + +(defun clearcase-set-auto-mode () + "Check again for the mode of the current buffer when using ClearCase version extended paths." + + (let* ((version (clearcase-vxpath-version-part (buffer-file-name))) + (buffer-file-name (clearcase-vxpath-element-part (buffer-file-name)))) + + ;; Need to recheck the major mode only if a version was appended. + ;; + (if version + (set-auto-mode)))) + +;;}}} + +;;{{{ A find-file hook for version-extended pathnames + +(defun clearcase-hook-vxpath-find-file-hook () + (if (clearcase-vxpath-p default-directory) + (let ((element (clearcase-vxpath-element-part default-directory)) + (version (clearcase-vxpath-version-part default-directory))) + + ;; 1. Set the buffer name to @@//. + ;; + (let ((new-buffer-name + (concat (file-name-nondirectory element) + clearcase-vxpath-glue + version + (buffer-name)))) + + (or (string= new-buffer-name (buffer-name)) + + ;; Uniquify the name, if necessary. + ;; + (let ((n 2) + (uniquifier-string "")) + (while (get-buffer (concat new-buffer-name uniquifier-string)) + (setq uniquifier-string (format "<%d>" n)) + (setq n (1+ n))) + (rename-buffer + (concat new-buffer-name uniquifier-string))))) + + ;; 2. Set the default directory to the dir containing . + ;; + (let ((new-dir (file-name-directory element))) + (setq default-directory new-dir)) + + ;; 3. Disable auto-saving. + ;; + ;; If we're visiting @@//199 + ;; we don't want Emacs trying to find a place to create a "#199#. + ;; + (auto-save-mode 0)))) + +;;}}} + +;;{{{ A dired-mode-hook to turn on clearcase-dired-mode + +(defun clearcase-hook-dired-mode-hook () + ;; Force a re-computation of whether the directory is within ClearCase. + ;; + (clearcase-fprop-unstore-properties default-directory) + + ;; Wrap this in an exception handler. Otherwise, diredding into + ;; a deregistered or otherwise defective snapshot-view fails. + ;; + (condition-case nil + ;; If this directory is below a ClearCase element, + ;; 1. turn on ClearCase Dired Minor Mode. + ;; 2. display branch/ver in mode-line + ;; + (if (clearcase-file-would-be-in-view-p default-directory) + (progn + (if clearcase-auto-dired-mode + (progn + (clearcase-dired-mode 1) + (clearcase-fprop-get-properties default-directory) + (clearcase-vprop-schedule-work (clearcase-fprop-viewtag default-directory)))) + (setq clearcase-dired-mode + (concat " ClearCase:" + (clearcase-mode-line-buffer-id default-directory))) + (force-mode-line-update))) + (error (message "Error fetching ClearCase properties of %s" default-directory)))) + +;;}}} + +;;{{{ A dired-after-readin-hook to add ClearCase information to the display + +(defun clearcase-hook-dired-after-readin-hook () + + ;; If in clearcase-dired-mode, reformat the buffer. + ;; + (if clearcase-dired-mode + (progn + (clearcase-dired-reformat-buffer) + (if clearcase-dired-show-view + (clearcase-dired-insert-viewtag)))) + t) + +;;}}} + +;;{{{ A write-file-hook to auto-insert a version-string. + +;; To use this, put a line containing this in the first 8 lines of your file: +;; ClearCase-version: +;; and make sure that clearcase-version-stamp-active gets set to true at least +;; locally in the file. + +(defvar clearcase-version-stamp-line-limit 1000) +(defvar clearcase-version-stamp-begin-regexp "ClearCase-version:[ \t]<") +(defvar clearcase-version-stamp-end-regexp ">") +(defvar clearcase-version-stamp-active nil) + +(defun clearcase-increment-version (version-string) + (let* ((branch (clearcase-vxpath-branch version-string)) + (number (clearcase-vxpath-version version-string)) + (new-number (1+ (string-to-number number)))) + (format "%s%d" branch new-number))) + +(defun clearcase-version-stamp () + (interactive) + (if (and clearcase-mode + clearcase-version-stamp-active + (file-exists-p buffer-file-name) + (equal 'version (clearcase-fprop-mtype buffer-file-name))) + (let ((latest-version (clearcase-fprop-predecessor-version buffer-file-name))) + + ;; Note: If the buffer happens to be folded, we may not find the place + ;; to insert the version-stamp. Folding mode really needs to supply a + ;; 'save-folded-excursion function to solve this one. We won't attempt + ;; a cheaper hack here. + + (save-excursion + (save-restriction + (widen) + (goto-char (point-min)) + (forward-line clearcase-version-stamp-line-limit) + (let ((limit (point)) + (v-start nil) + (v-end nil)) + (goto-char (point-min)) + (while (and (< (point) limit) + (re-search-forward clearcase-version-stamp-begin-regexp + limit + 'move)) + (setq v-start (point)) + (end-of-line) + (let ((line-end (point))) + (goto-char v-start) + (if (re-search-forward clearcase-version-stamp-end-regexp + line-end + 'move) + (setq v-end (match-beginning 0))))) + (if v-end + (let ((new-version-stamp (clearcase-increment-version latest-version))) + (goto-char v-start) + (delete-region v-start v-end) + (insert-and-inherit new-version-stamp))))))))) + +(defun clearcase-hook-write-file-hook () + + (clearcase-version-stamp) + ;; Important to return nil so the files eventually gets written. + ;; + nil) + +;;}}} + +;;{{{ A kill-buffer hook + +(defun clearcase-hook-kill-buffer-hook () + (let ((filename (buffer-file-name))) + (if (and filename + ;; W3 has buffers in which 'buffer-file-name is bound to + ;; a URL. Don't attempt to unstore their properties. + ;; + (boundp 'buffer-file-truename) + buffer-file-truename) + (clearcase-fprop-unstore-properties filename)))) + +;;}}} + +;;{{{ A kill-emacs-hook + +(defun clearcase-hook-kill-emacs-hook () + (clearcase-utl-clean-tempfiles)) + +;;}}} + +;;}}} +;;{{{ Function: to replace toggle-read-only + +(defun clearcase-toggle-read-only (&optional arg) + "Change read-only status of current buffer, perhaps via version control. +If the buffer is visiting a ClearCase version, then check the file in or out. +Otherwise, just change the read-only flag of the buffer. If called with an +argument then just change the read-only flag even if visiting a ClearCase +version." + (interactive "P") + (cond (arg + (toggle-read-only)) + ((and (clearcase-fprop-mtype buffer-file-name) + buffer-read-only + (file-writable-p buffer-file-name) + (/= 0 (user-uid))) + (toggle-read-only)) + + ((clearcase-fprop-mtype buffer-file-name) + (clearcase-next-action-current-buffer)) + + (t + (toggle-read-only)))) + +;;}}} +;;{{{ Functions: file-name-handlers + +;;{{{ Start dynamic views automatically when paths to them are used + +;; This handler starts views when viewroot-relative paths are dereferenced. +;; +;; nyi: for now really only seems useful on Unix. +;; +(defun clearcase-viewroot-relative-file-name-handler (operation &rest args) + + (clearcase-when-debugging + (if (fboundp 'clearcase-utl-syslog) + (clearcase-utl-syslog "*clearcase-fh-trace*" + (cons "clearcase-viewroot-relative-file-name-handler:" + (cons operation args))))) + + ;; Inhibit the handler to avoid recursion. + ;; + (let ((inhibit-file-name-handlers + (cons 'clearcase-viewroot-relative-file-name-handler + (and (eq inhibit-file-name-operation operation) + inhibit-file-name-handlers))) + (inhibit-file-name-operation operation)) + + (let ((first-arg (car args))) + ;; We don't always get called with a string. + ;; e.g. one file operation is verify-visited-file-modtime, whose + ;; first argument is a buffer. + ;; + (if (stringp first-arg) + (progn + ;; Now start the view if necessary + ;; + (save-match-data + (let* ((path (clearcase-path-remove-useless-viewtags first-arg)) + (viewtag (clearcase-vrpath-viewtag path)) + (default-directory (clearcase-path-remove-useless-viewtags default-directory))) + (if viewtag + (clearcase-viewtag-try-to-start-view viewtag)))))) + (apply operation args)))) + +;;}}} + +;;{{{ Completion on viewtags + +;; This handler provides completion for viewtags. +;; +(defun clearcase-viewtag-file-name-handler (operation &rest args) + + (clearcase-when-debugging + (if (fboundp 'clearcase-utl-syslog) + (clearcase-utl-syslog "*clearcase-fh-trace*" + (cons "clearcase-viewtag-file-name-handler:" + (cons operation args))))) + (cond + + ((eq operation 'file-name-completion) + (save-match-data (apply 'clearcase-viewtag-completion args))) + + ((eq operation 'file-name-all-completions) + (save-match-data (apply 'clearcase-viewtag-completions args))) + + (t + (let ((inhibit-file-name-handlers + (cons 'clearcase-viewtag-file-name-handler + (and (eq inhibit-file-name-operation operation) + inhibit-file-name-handlers))) + (inhibit-file-name-operation operation)) + (apply operation args))))) + +(defun clearcase-viewtag-completion (file dir) + (try-completion file (clearcase-viewtag-all-viewtag-dirs-obarray))) + +(defun clearcase-viewtag-completions (file dir) + (let ((tags (all-completions file + (clearcase-viewtag-all-viewtags-obarray)))) + (mapcar + (function (lambda (tag) + (concat tag "/"))) + tags))) + +;;}}} + +;;{{{ File name handler for version extended file names + +;; For version extended pathnames there are two possible answers +;; for each of +;; file-name-directory +;; file-name-nondirectory +;; +;; 1. that pertaining to the element path, e.g. +;; (file-name-directory "DIR/FILE@@/BRANCH/VERSION") +;; ==> "DIR/" +;; 2. that pertaining to the version path, e.g. +;; (file-name-directory "DIR/FILE@@/BRANCH/VERSION") +;; ==> "DIR/FILE@@/BRANCH/" +;; +;; Often we'd like the former, but sometimes we'd like the latter, for example +;; inside clearcase-browse-vtree, where it calls dired. Within dired on Gnu +;; Emacs, it calls file-name-directory on the supplied pathname and in this +;; case we want the version (i.e. branch) path to be used. +;; +;; How to get the behaviour we want ? + +;; APPROACH A: +;; ========== +;; +;; Define a variable clearcase-treat-branches-as-dirs, which modifies +;; the behaviour of clearcase-vxpath-file-name-handler to give answer (1). +;; +;; Just before we invoke dired inside clearcase-browse-vtree, dynamically +;; bind clearcase-treat-branches-as-dirs to t. Also in the resulting Dired Mode +;; buffer, make clearcase-treat-branches-as-dirs buffer-local and set it. +;; +;; Unfortunately this doesn't quite give us what we want. For example I often +;; invoke grep from a dired buffer on a branch-qua-directory to scan all the +;; version on that branch for a certain string. The grep-mode buffer has no +;; buffer-local binding for clearcase-treat-branches-as-dirs so the grep +;; command runs in "DIR/" instead of in "DIR/FILE@@/BRANCH/". +;; +;; APPROACH B: +;; ========== +;; +;; Modify the semantics of clearcase-vxpath-file-name-handler so that +;; if the filename given is a pathname to an existing branch-qua-directory +;; give answer 2, otherwise give answer 1. +;; +;; APPROACH C: +;; ========== +;; +;; Use the existence of a Dired Mode buffer on "DIR/FILE@@/BRANCH/" to +;; change the semantics of clearcase-vxpath-file-name-handler. +;; +;; (A) is unsatisfactory and I'm not entirely happy with (B) nor (C) so for now +;; I'm going to disable this filename handler until I'm more convinced it is +;; needed. + +(defun clearcase-vxpath-file-name-handler (operation &rest args) + (clearcase-when-debugging + (if (fboundp 'clearcase-utl-syslog) + (clearcase-utl-syslog "*clearcase-fh-trace*" + (cons "clearcase-vxpath-file-name-handler:" + (cons operation args))))) + ;; Inhibit recursion: + ;; + (let ((inhibit-file-name-handlers + (cons 'clearcase-vxpath-file-name-handler + (and (eq inhibit-file-name-operation operation) + inhibit-file-name-handlers))) + (inhibit-file-name-operation operation)) + + (cond ((eq operation 'file-name-nondirectory) + (file-name-nondirectory (clearcase-vxpath-element-part + (car args)))) + + ((eq operation 'file-name-directory) + (file-name-directory (clearcase-vxpath-element-part + (car args)))) + + (t + (apply operation args))))) + +;;}}} + +;;}}} +;;{{{ Advice: Disable VC in the MVFS + +;; This handler ensures that VC doesn't attempt to operate inside the MVFS. +;; This stops it from futile searches for RCS directories and the like inside. +;; It prevents a certain amount of clutter in the MVFS' noent-cache. +;; + +(defadvice vc-registered (around clearcase-interceptor disable compile) + "Disable normal behavior if in a clearcase dynamic view. +This is enabled/disabled by clearcase-integrate/clearcase-unintegrate." + (if (clearcase-file-would-be-in-view-p (ad-get-arg 0)) + nil + ad-do-it)) + +;;}}} + +;;{{{ Functions: integrate and un-integrate. + +(defun clearcase-integrate () + "Enable ClearCase integration" + (interactive) + + ;; 0. Empty caches. + ;; + (clearcase-fprop-clear-all-properties) + (clearcase-vprop-clear-all-properties) + + ;; 1. Install hooks. + ;; + (add-hook 'find-file-hooks 'clearcase-hook-find-file-hook) + (add-hook 'find-file-hooks 'clearcase-hook-vxpath-find-file-hook) + (add-hook 'dired-mode-hook 'clearcase-hook-dired-mode-hook) + (add-hook 'dired-after-readin-hook 'clearcase-hook-dired-after-readin-hook) + (add-hook 'kill-buffer-hook 'clearcase-hook-kill-buffer-hook) + (add-hook 'write-file-hooks 'clearcase-hook-write-file-hook) + (add-hook 'kill-emacs-hook 'clearcase-hook-kill-emacs-hook) + + ;; 2. Install file-name handlers. + ;; + ;; 2.1 Start views when //view/TAG or m:/TAG is referenced. + ;; + (add-to-list 'file-name-handler-alist + (cons clearcase-vrpath-regexp + 'clearcase-viewroot-relative-file-name-handler)) + + ;; 2.2 Completion on viewtags. + ;; + (if clearcase-complete-viewtags + (add-to-list 'file-name-handler-alist + (cons clearcase-viewtag-regexp + 'clearcase-viewtag-file-name-handler))) + + ;; 2.3 Turn off RCS/VCS/SCCS activity inside a ClearCase dynamic view. + ;; + (if clearcase-suppress-vc-within-mvfs + (when clearcase-suppress-vc-within-mvfs + (ad-enable-advice 'vc-registered 'around 'clearcase-interceptor) + (ad-activate 'vc-registered))) + + ;; Disabled for now. See comments above clearcase-vxpath-file-name-handler. + ;; + ;; ;; 2.4 Add file name handler for version extended path names + ;; ;; + ;; (add-to-list 'file-name-handler-alist + ;; (cons clearcase-vxpath-glue 'clearcase-vxpath-file-name-handler)) + ) + +(defun clearcase-unintegrate () + "Disable ClearCase integration" + (interactive) + + ;; 0. Empty caches. + ;; + (clearcase-fprop-clear-all-properties) + (clearcase-vprop-clear-all-properties) + + ;; 1. Remove hooks. + ;; + (remove-hook 'find-file-hooks 'clearcase-hook-find-file-hook) + (remove-hook 'find-file-hooks 'clearcase-hook-vxpath-find-file-hook) + (remove-hook 'dired-mode-hook 'clearcase-hook-dired-mode-hook) + (remove-hook 'dired-after-readin-hook 'clearcase-hook-dired-after-readin-hook) + (remove-hook 'kill-buffer-hook 'clearcase-hook-kill-buffer-hook) + (remove-hook 'write-file-hooks 'clearcase-hook-write-file-hook) + (remove-hook 'kill-emacs-hook 'clearcase-hook-kill-emacs-hook) + + ;; 2. Remove file-name handlers. + ;; + (setq file-name-handler-alist + (delete-if (function + (lambda (entry) + (memq (cdr entry) + '(clearcase-viewroot-relative-file-name-handler + clearcase-viewtag-file-name-handler + clearcase-vxpath-file-name-handler)))) + file-name-handler-alist)) + + ;; 3. Turn on RCS/VCS/SCCS activity everywhere. + ;; + (ad-disable-advice 'vc-registered 'around 'clearcase-interceptor) + (ad-activate 'vc-registered)) + +;;}}} + +;; Here's where we really wire it all in: +;; +(defvar clearcase-cleartool-path nil) +(defvar clearcase-clearcase-version-installed nil) +(defvar clearcase-lt nil) +(defvar clearcase-v3 nil) +(defvar clearcase-v4 nil) +(defvar clearcase-v6 nil) +(defvar clearcase-servers-online nil) +(defvar clearcase-setview-root nil) +(defvar clearcase-setview-viewtag) +(defvar clearcase-setview-root nil) +(defvar clearcase-setview-viewtag nil) + +(progn + ;; If the SHELL environment variable points to the wrong place, + ;; call-process fails on Windows and this startup fails. + ;; Check for this and unset the useless EV. + + (let ((shell-ev-value (getenv "SHELL"))) + (if clearcase-on-mswindows + (if (stringp shell-ev-value) + (if (not (executable-find shell-ev-value)) + (setenv "SHELL" nil))))) + + ;; Things have to be done here in a certain order. + ;; + ;; 1. Make sure cleartool is on the shell search PATH. + ;; + (if (setq clearcase-cleartool-path (clearcase-find-cleartool)) + (progn + ;; 2. Try to discover what version of ClearCase we have: + ;; + (setq clearcase-clearcase-version-installed (clearcase-get-version-string)) + (setq clearcase-lt + (not (null (string-match "ClearCase LT" + clearcase-clearcase-version-installed)))) + (setq clearcase-v3 + (not (null (string-match "^ClearCase version 3" + clearcase-clearcase-version-installed)))) + (setq clearcase-v4 + (not (null (string-match "^ClearCase version 4" + clearcase-clearcase-version-installed)))) + (setq clearcase-v5 + (not (null (string-match "^ClearCase \\(LT \\)?version 2002.05" + clearcase-clearcase-version-installed)))) + (setq clearcase-v6 + (not (null (string-match "^ClearCase \\(LT \\)?version 2003.06" + clearcase-clearcase-version-installed)))) + + ;; 3. Gather setview information: + ;; + (if (setq clearcase-setview-root (if (not clearcase-on-mswindows) + (getenv "CLEARCASE_ROOT"))) + (setq clearcase-setview-viewtag + (file-name-nondirectory clearcase-setview-root))) + + ;; 4. Discover if the servers appear to be online. + ;; + (setq clearcase-servers-online (clearcase-registry-server-online-p)) + + (if clearcase-servers-online + + ;; 5. Everything seems in place to ensure that ClearCase mode will + ;; operate correctly, so integrate now. + ;; + (progn + (clearcase-integrate) + ;; Schedule a fetching of the view properties when next idle. + ;; This avoids awkward pauses after the user reaches for the + ;; ClearCase menubar entry. + ;; + (if clearcase-setview-viewtag + (clearcase-vprop-schedule-work clearcase-setview-viewtag))))))) + +(if (not clearcase-servers-online) + (message "ClearCase apparently not online. ClearCase/Emacs integration not installed.")) + +;;}}} + +(provide 'clearcase) + +;;; clearcase.el ends here + +;; Local variables: +;; folded-file: t +;; clearcase-version-stamp-active: t +;; End: diff --git a/rc/xemacs/custom.el b/rc/xemacs/custom.el new file mode 100644 index 0000000..ddc6d01 --- /dev/null +++ b/rc/xemacs/custom.el @@ -0,0 +1,9 @@ +(custom-set-variables + '(default-toolbar-position (quote right)) + '(kill-whole-line t) + '(paren-mode (quote sexp) nil (paren)) + '(pending-delete-mode t nil (pending-del))) +(custom-set-faces + '(default ((t (:size "12pt" :family "Lucida Sans Typewriter"))) t) + '(zmacs-region ((t (:foreground "white" :background "Steelblue"))) t)) + diff --git a/rc/xemacs/init.el b/rc/xemacs/init.el new file mode 100644 index 0000000..53a70d9 --- /dev/null +++ b/rc/xemacs/init.el @@ -0,0 +1,124 @@ +(if (string-match "cygwin" +(downcase (shell-command-to-string "uname"))) +(progn + (setq exec-path (cons "C:/Cygwin/bin" exec-path)) + (setenv "PATH" (concat "C:/Cygwin/bin;" (getenv "PATH"))))) + +(setenv "CVS_RSH" "ssh") + +;; NT-emacs assumes a Windows command shell, which you change here. +(setq process-coding-system-alist '(("bash" . undecided-unix))) +(setq w32-quote-process-args ?\") +(setq shell-file-name "bash") +(setenv "SHELL" shell-file-name) +(setq explicit-shell-file-name shell-file-name) + +;; This removes unsightly ^M characters that would otherwise +(add-hook 'comint-output-filter-functions + 'comint-strip-ctrl-m) +(setq shell-command-switch "-cf") + +(global-set-key "\C-x\C-c" 'kill-buffer) +(global-set-key "\C-x\C-n" 'save-buffers-kill-emacs) +(global-set-key "\C-x\C-z" 'suspend-emacs-or-iconify-frame) + +(setq-default column-number-mode t) +(display-time) +(setq-default line-number-mode t) + +(global-set-key 'kp-tab 'tab-to-tab-stop) +(set-default 'indent-tabs-mode t) +(set-default 'tab-width 8) +(c-set-offset 'substatement-open 0) +(c-set-offset 'case-label '+) + +; Preload common modes +(load-file "/usr/share/xemacs/xemacs-packages/lisp/sh-script/sh-script.el") +(load-file "~/.xemacs/clearcase.el") +(load-file "~/.xemacs/visual-basic-mode.el") +(autoload 'sh-mode "sh-mode") +(autoload 'perl-mode "perl-mode") +(autoload 'javascript-mode "javascript-mode") +(autoload 'visual-basic-mode "visual-basic-mode") + +(defvar my-c-style + '((c-auto-newline . nil) + (c-toggle-auto-state . 1) + (c-basic-offset . 2) + (c-block-comments-indent-p . t) + (c-comment-only-line-offset . nil) + (c-echo-syntactic-information-p . nil) + (c-hanging-comment-ender-p . t) + (c-recognize-knr-p . t) ; use nil if only have ANSI prototype + (c-tab-always-indent . t) + (comment-column . 40) + (comment-end . " */") + (comment-multi-line . t) + (comment-start . "/* ") + (c-hanging-comment-ender-p . nil) + (c-offsets-alist . ((knr-argdecl-intro . +) + (case-label . +) + (knr-argdecl . 0) + (label . 0) + (statement-case-open . +) + (statement-cont . +) + (substatement-open . 0)))) + "my c-style for cc-mode") + +(add-hook 'c-mode-common-hook + '(lambda () + (c-add-style "MINE" my-c-style) + (c-set-style "MINE"))) +(load-default-sounds) + +(load "recent-files") +(recent-files-initialize) + +;; Set ftp program to use Windows FTP. Using Cygwin's ftp is problematic for a Windows +;; oriented XEmacs +;(setq efs-ftp-program-name "C:/Windows/System32/Ftp") + +;; Set shell-file-name and shell-command-switch for Tramp +;(setq shell-file-name "cmd.exe") +;(setq shell-command-switch "/e:4096 /c") + +;; Set default tramp-default-method to ssh as well as +;; insert ":" into shell-prompt-pattern +(setq tramp-default-method "ssh") +(setq shell-prompt-pattern "^[^#$%>:\n]*[#$%>:] *") + +;; Set mail server +(set-variable 'smtpmail-smtp-server '"defaria.com") + +;;default font and face properties. +(progn + (set-face-foreground 'default "black") +;; (set-face-background 'default "#e4deb4") + (set-face-background 'default "white") + (set-face-font 'default "Lucida Console:Regular:10")) + +;; Startup a certain size +(set-frame-height (selected-frame) 50) +(set-frame-width (selected-frame) 90) + +;; Set background +;;(set-face-background-pixmap 'default +;; (expand-file-name "P:/Documents/pad.jpg") +;; (get-buffer "*info*")) + +;; VC mode +;;require 'vc + +;; Fix problem with Clearcase.el +(require 'timer) + +;; Change comment-column to 0 for perl. comment-column is used for one +;; of the two types of comment lines that the perl mode supports - +;; full line comments and inline comments. The comment-column is used +;; for the inline comments, which I rarely use. Setting it to 0 means +;; don't try to align things. So I'll just align them myself. +(defun perl-zero-comment-column () + (setq comment-column 0)) (add-hook 'cperl-mode-hook 'perl-zero-comment-column) + +(global-set-key "\C-z" 'undo) +(mouse-avoidance-mode 'cat-and-mouse) diff --git a/rc/xemacs/mwheel.el b/rc/xemacs/mwheel.el new file mode 100644 index 0000000..b808aad --- /dev/null +++ b/rc/xemacs/mwheel.el @@ -0,0 +1,53 @@ +;;============================================================================= +;; scroll on mouse wheel +;;============================================================================= +;; scroll on wheel of mouses +(define-key global-map 'button4 + '(lambda (&rest args) + (interactive) + (let ((curwin (selected-window))) + (select-window (car (mouse-pixel-position))) + (scroll-down 5) + (select-window curwin) + ))) +(define-key global-map [(shift button4)] + '(lambda (&rest args) + (interactive) + (let ((curwin (selected-window))) + (select-window (car (mouse-pixel-position))) + (scroll-down 1) + (select-window curwin) + ))) +(define-key global-map [(control button4)] + '(lambda (&rest args) + (interactive) + (let ((curwin (selected-window))) + (select-window (car (mouse-pixel-position))) + (scroll-down) + (select-window curwin) + ))) + +(define-key global-map 'button5 + '(lambda (&rest args) + (interactive) + (let ((curwin (selected-window))) + (select-window (car (mouse-pixel-position))) + (scroll-up 5) + (select-window curwin) + ))) +(define-key global-map [(shift button5)] + '(lambda (&rest args) + (interactive) + (let ((curwin (selected-window))) + (select-window (car (mouse-pixel-position))) + (scroll-up 1) + (select-window curwin) + ))) +(define-key global-map [(control button5)] + '(lambda (&rest args) + (interactive) + (let ((curwin (selected-window))) + (select-window (car (mouse-pixel-position))) + (scroll-up) + (select-window curwin) + ))) \ No newline at end of file diff --git a/rc/xemacs/perlcritic.el b/rc/xemacs/perlcritic.el new file mode 100644 index 0000000..9d09b29 --- /dev/null +++ b/rc/xemacs/perlcritic.el @@ -0,0 +1,687 @@ +;;; perlcritic.el --- minor mode for Perl::Critic integration + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; $URL: http://perlcritic.tigris.org/svn/perlcritic/trunk/distributions/Perl-Critic/extras/perlcritic.el $ +;;; $Date: 2009/04/23 15:46:13 $ +;;; $Author: andrew $ +;;; $Revision: 1.1 $ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + +;;; Readme +;; +;; This is a minor mode for emacs intended to allow you to +;; automatically incorporate perlcritic into your daily code +;; writing. When enabled it can optionally prevent you from saving +;; code that doesn't pass your enabled perlcritic policies. +;; +;; Even if you don't enable the automatic code checking you can still +;; use the automatic checking or the `perlcritic' function. + + +;;; Installation instructions: +;; +;; Copy perlcritic.el to your ~/.site-lib directory. If you don't +;; have a .site-lib directory create it and add the following line +;; to your .emacs file. This location isn't special, you could use +;; a different location if you wished. +;; +;; (add-to-list 'load-path "/home/your-name/.site-lisp") +;; +;; Add the following lines to your .emacs file. This allows Emacs +;; to load your perlcritic library only when needed. +;; +;; (autoload 'perlcritic "perlcritic" "" t) +;; (autoload 'perlcritic-region "perlcritic" "" t) +;; (autoload 'perlcritic-mode "perlcritic" "" t) +;; +;; Add the following to your .emacs file to get perlcritic-mode to +;; run automatically for the `cperl-mode' and `perl-mode'. +;; +;; (eval-after-load "cperl-mode" +;; '(add-hook 'cperl-mode-hook 'perlcritic-mode)) +;; (eval-after-load "perl-mode" +;; '(add-hook 'perl-mode-hook 'perlcritic-mode)) +;; +;; +;; If you think you need perlcritic loaded all the time you can +;; make this unconditional by using the following command instead +;; of the above autoloading. +;; +;; (require 'perlcritic) +;; +;; Compile the file for extra performance. This is optional. You +;; will have to redo this everytime you modify or upgrade your +;; perlcritic.el file. +;; +;; M-x byte-compile-file ~/.site-lib/perlcritic.el +;; +;; Additional customization can be found in the Perl::Critic group +;; in the Tools section in the Programming section of your Emacs' +;; customization menus. + + +;;; TODO +;; +;; Find out how to get perlcritic customization stuff into the +;; customization menus without having to load perlcritic.el +;; first. +;; +;; This needs an installer. Is there anything I can use in +;; ExtUtils::MakeMaker, Module::Build, or Module::Install? +;; Alien::? +;; +;; XEmacs compatibility. I use GNU Emacs and don't test in +;; XEmacs. I'm happy to do what it takes to be compatible but +;; someone will have to point things out to me. +;; +;; Make all documentation strings start with a sentence that fits +;; on one line. See "Tips for Documentation Strings" in the Emacs +;; Lisp manual. +;; +;; Any FIXME, TODO, or XXX tags below. + + +;;; Change Log: +;; 0.10 +;; * Synched up regexp alist with Perl::Critic::Utils and accounted for all +;; past patterns too. +;; 0.09 +;; * Added documentation for perlcritic-top, perlcritic-include, +;; perlcritic-exclude, perlcritic-force, perlcritic-verbose. +;; * Added emacs/vim editor hints to the bottom. +;; * Corrected indentation. +;; 0.08 +;; * Fixed perlcritic-compilation-error-regexp-alist for all +;; severity levels. +;; * Added documentation strings for functions. +;; 0.07 +;; * Moved perlcritic-compilation-error-regexp-alist so it is in the +;; source before it's used. This only seems to matter when +;; perlcritic.el is compiled to bytecode. +;; * Added perlcritic-exclude, perlcritic-include + +;; 0.06 +;; * Code cleanliness. +;; * Comment cleanliness. +;; * Nice error message when perlcritic warns. +;; * Documented perlcritic-top, perlcritic-verbose. +;; * Regular expressions for the other standard -verbose levels. +;; * Reversed Changes list so the most recent is first. +;; * Standard emacs library declarations. +;; * Added autoloading metadata. +;; 0.05 +;; * perlcritic-bin invocation now shown in output. +;; * Fixed indentation. +;; * perlcritic-region is now interactive. +;; 0.04 +;; * Removed a roque file-level (setq perlcritic-top 1) +;; * Moved cl library to compile-time. +;; 0.03 +;; * compile.el integration. This makes for hotlink happiness. +;; * Better sanity when starting the *perlcritic* buffer. +;; 0.02 +;; * perlcritic-severity-level added. +;; * Touched up the installation documentation. +;; * perlcritic-pass-required is now buffer local. +;; 0.01 +;; * It's new. I copied much of this from perl-lint-mode. + +;;; Copyright and license +;; +;; 2006 Joshua ben Jore +;; +;; This program is free software; you can redistribute it and/or +;; modify it under the same terms as Perl itself + + + + +;;; Code: + +;;; Customization and variables. +(defgroup perlcritic nil "Perl::Critic" + :prefix "perlcritic-" + :group 'tools) + +(defcustom perlcritic-bin "perlcritic" + "The perlcritic program used by `perlcritic'." + :type 'string + :group 'perlcritic) + +(defcustom perlcritic-pass-required nil + "When \\[perlcritic-mode] is enabled then this boolean controls +whether your file can be saved when there are perlcritic warnings. + +This variable is automatically buffer-local and may be overridden on a +per-file basis with File Variables." + :type '(radio + (const :tag "Require no warnings from perlcritic to save" t) + (const :tag "Allow warnings from perlcritic when saving" nil)) + :group 'perlcritic) +(make-variable-buffer-local 'perlcritic-pass-required) + +(defcustom perlcritic-profile nil + "Specify an alternate .perlcriticrc file. This is only used if +non-nil." + :type '(string) + :group 'perlcritic) +(make-variable-buffer-local 'perlcritic-profile) + +(defcustom perlcritic-noprofile nil + "Disables the use of any .perlcriticrc file." + :type '(boolean) + :group 'perlcritic) +(make-variable-buffer-local 'perlcritic-noprofile) + +(defcustom perlcritic-severity nil + "Directs perlcritic to only report violations of Policies with a +severity greater than N. Severity values are integers ranging from +1 (least severe) to 5 (most severe). The default is 5. For a given +-profile, decreasing the -severity will usually produce more +violations. Users can redefine the severity for any Policy in their +.perlcriticrc file. + +This variable is automatically buffer-local and may be overridden on a +per-file basis with File Variables." + ;; FIXME: My GNU Emacs doesn't show a radio widget or a menu here. + :type '(radio + (const :tag "Show only the most severe: 5" 5) + (const :tag "4" 4) + (const :tag "3" 3) + (const :tag "2" 2) + (const :tag "Show everything including the least severe: 1" 1)) + :group 'perlcritic) +(make-variable-buffer-local 'perlcritic-severity) + +(defcustom perlcritic-top nil + "Directs \"perlcritic\" to report only the top N Policy violations in +each file, ranked by their severity. If the -severity option is not +explicitly given, the -top option implies that the minimum severity +level is 1. Users can redefine the severity for any Policy in their +.perlcriticrc file. + +This variable is automatically buffer-local and may be overridden on a +per-file basis with File Variables." + :type '(integer) + :group 'perlcritic) +(make-variable-buffer-local 'perlcritic-top) + +(defcustom perlcritic-include nil + "Directs \"perlcritic\" to apply additional Policies that match the regex \"/PATTERN/imx\". +Use this option to override your profile and/or the severity settings. + +For example: + + layout + +This would cause \"perlcritic\" to apply all the \"CodeLayout::*\" policies +even if they have a severity level that is less than the default level of 5, +or have been disabled in your .perlcriticrc file. You can specify multiple +`perlcritic-include' options and you can use it in conjunction with the +`perlcritic-exclude' option. Note that `perlcritic-exclude' takes precedence +over `perlcritic-include' when a Policy matches both patterns. You can set +the default value for this option in your .perlcriticrc file." + :type '(string) + :group 'perlcritic) +(make-variable-buffer-local 'perlcritic-include) + +(defcustom perlcritic-exclude nil + "Directs \"perlcritic\" to not apply any Policy that matches the regex +\"/PATTERN/imx\". Use this option to temporarily override your profile and/or +the severity settings at the command-line. For example: + + strict + +This would cause \"perlcritic\" to not apply the \"RequireUseStrict\" and +\"ProhibitNoStrict\" Policies even though they have the highest severity +level. You can specify multiple `perlcritic-exclude' options and you can use +it in conjunction with the `perlcritic-include' option. Note that +`perlcritic-exclude' takes precedence over `perlcritic-include' when a Policy +matches both patterns. You can set the default value for this option in your +.perlcriticrc file." + :type '(string) + :group 'perlcritic) +(make-variable-buffer-local 'perlcritic-exclude) + + +(defcustom perlcritic-force nil + "Directs \"perlcritic\" to ignore the magical \"## no critic\" +pseudo-pragmas in the source code. You can set the default value for this +option in your .perlcriticrc file." + :type '(boolean) + :group 'perlcritic) +(make-variable-buffer-local 'perlcritic-force) + +(defcustom perlcritic-verbose nil + "Sets the numeric verbosity level or format for reporting violations. If +given a number (\"N\"), \"perlcritic\" reports violations using one of the +predefined formats described below. If the `perlcritic-verbose' option is not +specified, it defaults to either 4 or 5, depending on whether multiple files +were given as arguments to \"perlcritic\". You can set the default value for +this option in your .perlcriticrc file. + +Verbosity Format Specification +----------- ------------------------------------------------------------- + 1 \"%f:%l:%c:%m\n\", + 2 \"%f: (%l:%c) %m\n\", + 3 \"%m at %f line %l\n\", + 4 \"%m at line %l, column %c. %e. (Severity: %s)\n\", + 5 \"%f: %m at line %l, column %c. %e. (Severity: %s)\n\", + 6 \"%m at line %l, near ’%r’. (Severity: %s)\n\", + 7 \"%f: %m at line %l near ’%r’. (Severity: %s)\n\", + 8 \"[%p] %m at line %l, column %c. (Severity: %s)\n\", + 9 \"[%p] %m at line %l, near ’%r’. (Severity: %s)\n\", +10 \"%m at line %l, column %c.\n %p (Severity: %s)\n%d\n\", +11 \"%m at line %l, near ’%r’.\n %p (Severity: %s)\n%d\n\" + +Formats are a combination of literal and escape characters similar to the way +\"sprintf\" works. See String::Format for a full explanation of the +formatting capabilities. Valid escape characters are: + +Escape Meaning +------- ---------------------------------------------------------------- +%c Column number where the violation occurred +%d Full diagnostic discussion of the violation +%e Explanation of violation or page numbers in PBP +%F Just the name of the file where the violation occurred. +%f Path to the file where the violation occurred. +%l Line number where the violation occurred +%m Brief description of the violation +%P Full name of the Policy module that created the violation +%p Name of the Policy without the Perl::Critic::Policy:: prefix +%r The string of source code that caused the violation +%s The severity level of the violation + +The purpose of these formats is to provide some compatibility with text +editors that have an interface for parsing certain kinds of input. + + +This variable is automatically buffer-local and may be overridden on a +per-file basis with File Variables." + :type '(integer) + :group 'perlcritic) +(make-variable-buffer-local 'perlcritic-verbose) + +;; TODO: Enable strings in perlcritic-verbose. +;; (defcustom perlcritic-verbose-regexp nil +;; "An optional regexp to match the warning output. +;; +;; This is used when `perlcritic-verbose' has a regexp instead of one of +;; the standard verbose levels.") +;; (make-local-variable 'perlcritic-verbose-regexp) + + +;; compile.el requires that something be the "filename." I've tagged +;; the severity with that. It happens to make it get highlighted in +;; red. The following advice on COMPILATION-FIND-FILE makes sure that +;; the "filename" is getting ignored when perlcritic is using it. + +;; These patterns are defined in Perl::Critic::Utils + +(defvar perlcritic-compilation-error-regexp-alist + '(;; Verbose level 1 + ;; "%f:%l:%c:%m\n" + ("^\\([^\n]+\\):\\([0-9]+\\):\\([0-9]+\\)" 1 2 3) + + ;; Verbose level 2 + ;; "%f: (%l:%c) %m\n" + ("^\\([^\n]+\\): (\\([0-9]+\\):\\([0-9]+\\))" 1 2 3) + + ;; Verbose level 3 + ;; "%m at %f line %l\n" + ("^[^\n]+ at \\([^\n]+\\) line \\([0-9]+\\)" 1 2) + ;; "%m at line %l, column %c. %e. (Severity: %s)\n" + ("^[^\n]+ at line\\( \\)\\([0-9]+\\), column \\([0-9]+\\)." 1 2 3) + + ;; Verbose level 4 + ;; "%m at line %l, column %c. %e. (Severity: %s)\n" + ("^[^\n]+ at line\\( \\)\\([0-9]+\\), column \\([0-9]+\\)." 1 2 3) + ;; "%f: %m at line %l, column %c. %e. (Severity: %s)\n" + ("^\\([^\n]+\\): [^\n]+ at line \\([0-9]+\\), column \\([0-9]+\\)" 1 2 3) + + ;; Verbose level 5 + ;; "%m at line %l, near '%r'. (Severity: %s)\n" + ("^[^\n]+ at line\\( \\)\\([0-9]+\\)," 1 2) + ;; "%f: %m at line %l, column %c. %e. (Severity: %s)\n" + ("^\\([^\n]+\\): [^\n]+ at line \\([0-9]+\\), column \\([0-9]+\\)" 1 2 3) + + ;; Verbose level 6 + ;; "%m at line %l, near '%r'. (Severity: %s)\\n" + ("^[^\n]+ at line\\( \\)\\([0-9]+\\)" 1 2) + ;; "%f: %m at line %l near '%r'. (Severity: %s)\n" + ("^\\([^\n]+\\): [^\n]+ at line \\([0-9]+\\)" 1 2) + + ;; Verbose level 7 + ;; "%f: %m at line %l near '%r'. (Severity: %s)\n" + ("^\\([^\n]+\\): [^\n]+ at line \\([0-9]+\\)" 1 2) + ;; "[%p] %m at line %l, column %c. (Severity: %s)\n" + ("^\\[[^\n]+\\] [^\n]+ at line\\( \\)\\([0-9]+\\), column \\([0-9]+\\)" 1 2 3) + + ;; Verbose level 8 + ;; "[%p] %m at line %l, column %c. (Severity: %s)\n" + ("^\\[[^\n]+\\] [^\n]+ at line\\( \\)\\([0-9]+\\), column \\([0-9]+\\)" 1 2 3) + ;; "[%p] %m at line %l, near '%r'. (Severity: %s)\n" + ("^\\[[^\n]+\\] [^\n]+ at line\\( \\)\\([0-9]+\\)" 1 2) + + ;; Verbose level 9 + ;; "%m at line %l, column %c.\n %p (Severity: %s)\n%d\n" + ("^[^\n]+ at line\\( \\)\\([0-9]+\\), column \\([0-9]+\\)" 1 2 3) + ;; "[%p] %m at line %l, near '%r'. (Severity: %s)\n" + ("^\\[[^\n]+\\] [^\n]+ at line\\( \\)\\([0-9]+\\)" 1 2) + + ;; Verbose level 10 + ;; "%m at line %l, near '%r'.\n %p (Severity: %s)\n%d\n" + ("^[^\n]+ at line\\( \\)\\([0-9]+\\)" 1 2) + ;; "%m at line %l, column %c.\n %p (Severity: %s)\n%d\n" + ("^[^\n]+ at line\\( \\)\\([0-9]+\\), column \\([0-9]+\\)" 1 2 3) + + ;; Verbose level 11 + ;; "%m at line %l, near '%r'.\n %p (Severity: %s)\n%d\n" + ("^[^\n]+ at line\\( \\)\\([0-9]+\\)" 1 2) + ) + "Alist that specified how to match errors in perlcritic output.") + + + +;; The Emacs Lisp manual says to do this with the cl library. +(eval-when-compile (require 'cl)) + +;;;###autoload +(defun perlcritic () + "\\[perlcritic]] returns a either nil or t depending on whether the +current buffer passes perlcritic's check. If there are any warnings +those are displayed in a separate buffer." + (interactive) + (print "in perlcritic") + (save-restriction + (widen) + (perlcritic-region (point-min) (point-max)))) + +;;;###autoload +(defun perlcritic-region (start end) + "\\[perlcritic-region] returns a either nil or t depending on +whether the region passes perlcritic's check. If there are any +warnings those are displayed in a separate buffer." + + (interactive "r") + + (message "In perlcritic-region") + + ;; Kill the perlcritic buffer so I can make a new one. + (if (get-buffer "*perlcritic*") + (kill-buffer "*perlcritic*")) + + ;; In the following lines I'll be switching between buffers + ;; freely. This upper save-excursion will keep things sane. + (save-excursion + (let ((src-buf (current-buffer)) + (err-buf (get-buffer-create "*perlcritic*"))) + + (set-buffer src-buf) + (let ((perlcritic-args (loop for p in (list + ;; Add new bin/perlcritic + ;; parameters here! + (perlcritic--param-profile) + (perlcritic--param-noprofile) + (perlcritic--param-severity) + (perlcritic--param-top) + (perlcritic--param-include) + (perlcritic--param-exclude) + (perlcritic--param-force) + (perlcritic--param-verbose)) + unless (null p) + append p))) + ; + (message "Perl critic...running") + ;; Seriously. Is this the nicest way to call + ;; CALL-PROCESS-REGION with variadic arguments? This blows! + ;; (apply FUNCTION (append STATIC-PART DYNAMIC-PART)) + (message "perlcritic1") + (message (concat "Starting " perlcritic-bin)) + (let ((rc (apply 'call-process-region + (nconc (list start end + perlcritic-bin nil + (list err-buf t) + nil) + perlcritic-args)))) +(message "perlcritic2") + + ;; Figure out whether we're ok or not. perlcritic has to + ;; return zero and the output buffer has to be empty except + ;; for that "... source OK" line. Different versions of the + ;; perlcritic script will print different things when + ;; they're ok. I expect to see things like "some-file source + ;; OK", "SCALAR=(0x123457) source OK", "STDIN source OK", + ;; and "source OK". + (let ((perlcritic-ok (and (numberp rc) + (zerop rc) + (progn + (set-buffer err-buf) + (goto-char (point-min)) + (delete-matching-lines "source OK$") + (zerop (buffer-size)))))) + ;; Either clean up or finish setting up my output. + (if perlcritic-ok + ;; Ok! + (progn + (kill-buffer err-buf) + (message "Perl critic...ok")) + + + ;; Not ok! + (message "Perl critic...not ok") + + ;; Set up the output buffer now I know it'll be used. I + ;; scooped the guts out of compile-internal. It is + ;; CRITICAL that the errors start at least two lines + ;; from the top. compile.el normally assumes the first + ;; line is an informational `cd somedirectory' command + ;; and the second line shows the program's invocation. + ;; + ;; Since I have the space available I've put the + ;; program's invocation here. Maybe it'd make sense to + ;; put the buffer's directory here somewhere too. + (set-buffer err-buf) + (goto-char (point-min)) + (insert (reduce (lambda (a b) (concat a " " b)) + (nconc (list perlcritic-bin) + perlcritic-args)) + "\n" + ;; TODO: instead of a blank line, print the + ;; buffer's directory+file. + "\n") + (goto-char (point-min)) + ;; TODO: get `recompile' to work. + + ;; just an fyi. compilation-mode will delete my local + ;; variables so be sure to call it *first*. + (compilation-mode "perlcritic") + (set (make-local-variable 'perlcritic-buffer) src-buf) + (set (make-local-variable 'compilation-error-regexp-alist) + perlcritic-compilation-error-regexp-alist) + (ad-activate #'compilation-find-file) + ; (ad-deactivate #'compilation-find-file) + (display-buffer err-buf)) + + ;; Return our success or failure. + perlcritic-ok)))))) + + + + +;;; Parameters for use by perlcritic-region. +(defun perlcritic--param-profile () + "A private method that supplies the -profile FILENAME parameter for +\\[perlcritic-region]" + (if perlcritic-profile (list "-profile" perlcritic-profile))) + +(defun perlcritic--param-noprofile () + "A private method that supplies the -noprofile parameter for +\\[perlcritic-region]" + (if perlcritic-noprofile (list "-noprofile"))) + +(defun perlcritic--param-force () + "A private method that supplies the -force parameter for +\\[perlcritic-region]" + (if perlcritic-force (list "-force"))) + +(defun perlcritic--param-severity () + "A private method that supplies the -severity NUMBER parameter for +\\[perlcritic-region]" + (cond ((stringp perlcritic-severity) + (list "-severity" perlcritic-severity)) + ((numberp perlcritic-severity) + (list "-severity" (number-to-string perlcritic-severity))) + (t nil))) + +(defun perlcritic--param-top () + "A private method that supplies the -top NUMBER parameter for +\\[perlcritic-region]" + (cond ((stringp perlcritic-top) + (list "-top" perlcritic-top)) + ((numberp perlcritic-top) + (list "-top" (number-to-string perlcritic-top))) + (t nil))) + +(defun perlcritic--param-include () + "A private method that supplies the -include REGEXP parameter for +\\[perlcritic-region]" + (if perlcritic-include + (list "-include" perlcritic-include) + nil)) + +(defun perlcritic--param-exclude () + "A private method that supplies the -exclude REGEXP parameter for +\\[perlcritic-region]" + (if perlcritic-exclude + (list "-exclude" perlcritic-exclude) + nil)) + +(defun perlcritic--param-verbose () + "A private method that supplies the -verbose NUMBER parameter for +\\[perlcritic-region]" + (cond ((stringp perlcritic-verbose) + (list "-verbose" perlcritic-verbose)) + ((numberp perlcritic-verbose) + (list "-verbose" (number-to-string perlcritic-verbose))) + (t nil))) + + +;; Interactive functions for use by the user to modify parameters on +;; an adhoc basis. I'm sure there's room for significant niceness +;; here. Suggest something. Please. +(defun perlcritic-profile (profile) + "Sets perlcritic's -profile FILENAME parameter." + (interactive "sperlcritic -profile: ") + (setq perlcritic-profile (if (string= profile "") nil profile))) + +(defun perlcritic-noprofile (noprofile) + "Toggles perlcritic's -noprofile parameter." + (interactive (list (yes-or-no-p "Enable perlcritic -noprofile? "))) + (setq perlcritic-noprofile noprofile)) + +(defun perlcritic-force (force) + "Toggles perlcritic's -force parameter." + (interactive (list (yes-or-no-p "Enable perlcritic -force? "))) + (setq perlcritic-force force)) + +(defun perlcritic-severity (severity) + "Sets perlcritic's -severity NUMBER parameter." + (interactive "nperlcritic -severity: ") + (setq perlcritic-severity severity)) + +(defun perlcritic-top (top) + "Sets perlcritic's -top NUMBER parameter." + (interactive "nperlcritic -top: ") + (setq perlcritic-top top)) + +(defun perlcritic-include (include) + "Sets perlcritic's -include REGEXP parameter." + (interactive "sperlcritic -include: ") + (setq perlcritic-include include)) + +(defun perlcritic-exclude (exclude) + "Sets perlcritic's -exclude REGEXP parameter." + (interactive "sperlcritic -exclude: ") + (setq perlcritic-exclude exclude)) + +(defun perlcritic-verbose (verbose) + "Sets perlcritic's -verbose NUMBER parameter." + (interactive "nperlcritic -verbose: ") + (setq perlcritic-verbose verbose)) + + + + + +;; Hooks compile.el's compilation-find-file to enable our file-less +;; operation. We feed `perlcritic-bin' from STDIN, not from a file. +(defadvice compilation-find-file (around perlcritic-find-file) + "Lets perlcritic lookup into the buffer we just came from and don't +require that the perl document exist in a file anywhere." + (let ((debug-buffer (marker-buffer marker))) + (if (local-variable-p 'perlcritic-buffer debug-buffer) + (setq ad-return-value perlcritic-buffer) + ad-do-it))) + + + + + +;; All the scaffolding of having a minor mode. +(defvar perlcritic-mode nil + "Toggle `perlcritic-mode'") +(make-variable-buffer-local 'perlcritic-mode) + +(defun perlcritic-write-hook () + "Check perlcritic during `write-file-hooks' for `perlcritic-mode'" + (if perlcritic-mode + (save-excursion + (widen) + (mark-whole-buffer) + (let ((perlcritic-ok (perlcritic))) + (if perlcritic-pass-required + ;; Impede saving if we're not ok. + (not perlcritic-ok) + ;; Don't impede saving. We might not be ok but that + ;; doesn't matter now. + nil))) + ;; Don't impede saving. We're not in perlcritic-mode. + nil)) + +;;;###autoload +(defun perlcritic-mode (&optional arg) + "Perl::Critic checking minor mode." + (interactive "P") + + ;; Enable/disable perlcritic-mode + (setq perlcritic-mode (if (null arg) + ;; Nothing! Just toggle it. + (not perlcritic-mode) + ;; Set it. + (> (prefix-numeric-value arg) 0))) + + (make-local-hook 'write-file-hooks) + (if perlcritic-mode + (add-hook 'write-file-hooks 'perlcritic-write-hook) + (remove-hook 'write-file-hooks 'perlcritic-write-hook))) + +;; Make a nice name for perl critic mode. This string will appear at +;; the bottom of the screen. +(if (not (assq 'perlcritic-mode minor-mode-alist)) + (setq minor-mode-alist + (cons '(perlcritic-mode " Critic") + minor-mode-alist))) + +(provide 'perlcritic) + +;; Local Variables: +;; mode: emacs-lisp +;; tab-width: 8 +;; fill-column: 78 +;; indent-tabs-mode: nil +;; End: +;; ex: set ts=8 sts=4 sw=4 tw=78 ft=perl expandtab shiftround : + +;;; perlcritic.el ends here diff --git a/rc/xemacs/perltidy.el b/rc/xemacs/perltidy.el new file mode 100644 index 0000000..06bab99 --- /dev/null +++ b/rc/xemacs/perltidy.el @@ -0,0 +1,150 @@ +;;; perltidy-mode.el --- Minor mode to automatically perltidy. + +;;; Perltidy is a program that is available on CPAN. + +;;; Copyright 2006 Joshua ben Jore + +;;; Author: Joshua ben Jore +;;; Version: 0.02 +;;; CVS Version: $Id: perltidy.el,v 1.1 2009/04/23 15:46:13 andrew Exp $ +;;; Keywords: perl perltidy +;;; X-URL: http://search.cpan.org/~jjore/perltidy-mode/ + +;;; This program is free software; you can redistribute it and/or +;;; modify it under the same terms as Perl itself. + +;;; To install this first generate your perltidy-mode.el file by running +;;; perltidy-mode.PL with your copy of perl. Copy the generated perltidy-mode.el to +;;; your ~/.site-lisp/ directory or a different preferred location. +;;; +;;; Add the following lines to your .emacs file to inform emacs of the directory +;;; and of the two main functions provided by this library. +;;; +;;; (add-to-list 'load-path "~/.site-lisp/") +;;; (autoload 'perltidy "perltidy-mode" nil t) +;;; (autoload 'perltidy-mode "perltidy-mode" nil t) +;;; +;;; Add the following snippet to enable full-auto mode. +;;; +;;; (eval-after-load "cperl-mode" +;;; '(add-hook 'cperl-mode-hook 'perltidy-mode)) +;;; +;;; Add the following snippet to set the C-ct key sequence to trigger +;;; perltidy. +;;; +;;; ; Run perltidy when the C-ct key sequence is used. +;;; (global-set-key "\C-ct" 'perltidy) + + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Perltidy +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defvar perltidy-bin "perltidy" + "The command to run perltidy.") + +(defmacro mark-active () + "Xemacs/emacs compatibility macro. It returns either nil or non-nil +and there are no guarantees about what constitutes \"non-nil\"." + (if (boundp 'mark-active) + `mark-active + `(mark))) + +(defun perltidy (start-in end-in) + "Run perltidy on the current region or buffer." + (interactive "r") + + (let ((start (or start-in (point-min))) + (end (or end-in (point-max))) + (original-line (point->line (point))) + (error-buffer (get-buffer-create "*perltidy-errors*"))) + + ; Clear the error buffer if needed. + (or (zerop (buffer-size error-buffer)) + (save-excursion (set-buffer error-buffer) + (erase-buffer))) + + ; Inexplicably, save-excursion doesn't work to restore the + ; point. I'm using it to restore the mark and point and manually + ; navigating to the proper new-line. + (let ((result + (save-excursion + (if (zerop (shell-command-on-region start end perltidy-bin error-buffer)) + ; Success! Clean up. + (progn + (kill-buffer error-buffer) + t) + + ; Oops! Show our error and give back the text that + ; shell-command-on-region stole. + (progn (undo) + (display-buffer error-buffer) + nil))))) + + ; This goto-line is outside the save-excursion becuase it'd get + ; removed otherwise. I hate this bug. It makes things so ugly. + (goto-line original-line) + result))) + + +(defun point->line (point) + "Get the line number that POINT is on." + ; I'm not bothering to use save-excursion because I think I'm + ; calling this function from inside other things that are likely to + ; use that and all I really need to do is restore my current + ; point. So that's what I'm doing manually. + (let ((line 1) + (original-point (point))) + (goto-char (point-min)) + (while (< (point) point) + (incf line) + (forward-line)) + (goto-char original-point) + line)) + + + + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Automatic perltidy +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defvar perltidy-mode nil + "Automatically `perltidy' when saving.") +(make-variable-buffer-local 'perltidy-mode) + +(defun perltidy-write-hook () + "Perltidys a buffer during `write-file-hooks' for +`perltidy-mode'. If perltidy returns nil then the buffer isn't saved." + (if perltidy-mode + (save-restriction + (widen) + ; Impede the save if perltidy is false. + (not (perltidy (point-min) (point-max)))) + ; Don't impede the save. + nil)) + +(defun perltidy-mode (&optional arg) + "Perltidy minor mode." + (interactive "P") + + ; Cargo-culted from the Extending Emacs book. + (setq perltidy-mode (if (null arg) + ; Toggle it on and off. + (not perltidy-mode) + ; Enable if >0. + (> (prefix-numeric-value arg) 0))) + + (make-local-hook 'write-file-hooks) + (funcall (if perltidy-mode #'add-hook #'remove-hook) + 'write-file-hooks 'perltidy-write-hook)) + +; Add this to the list of minor modes. +(if (not (assq 'perltidy-mode minor-mode-alist)) + (setq minor-mode-alist + (cons '(perltidy-mode " Perltidy") + minor-mode-alist))) + +(provide 'perltidy-mode) + +;;; perltidy-mode.el ends here diff --git a/rc/xemacs/visual-basic-mode.el b/rc/xemacs/visual-basic-mode.el new file mode 100644 index 0000000..b138fdb --- /dev/null +++ b/rc/xemacs/visual-basic-mode.el @@ -0,0 +1,933 @@ +;; visual-basic-mode.el --- A mode for editing Visual Basic programs. +;; Modified version of Fred White's visual-basic-mode.el + +;; Copyright (C) 1996 Fred White +;; Copyright (C) 1998 Free Software Foundation, Inc. +;; (additions by Dave Love) + +;; Author: Fred White +;; Adapted-by: Dave Love +;; : Kevin Whitefoot +;; Version: 1.3 (May 1, 1996) +;; Keywords: languages, basic, Evil + + +;; (Old) LCD Archive Entry: +;; basic-mode|Fred White|fwhite@alum.mit.edu| +;; A mode for editing Visual Basic programs.| +;; 18-Apr-96|1.0|~/modes/basic-mode.el.Z| + +;; This file is NOT part of GNU Emacs but the same permissions apply. +;; +;; GNU Emacs is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published +;; by the Free Software Foundation; either version 2, or (at your +;; option) any later version. +;; +;; GNU Emacs is distributed in the hope that it will be useful, but +;; WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;; General Public License for more details. +;; +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, write to the +;; Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. +;; This program is free software; you can redistribute it and/or +;; modify it under the terms of the GNU General Public License as +;; published by the Free Software Foundation; either version 2 of the +;; License, or (at your option) any later version. + +;;; Commentary: + +;; Purpose of this package: +;; This is a mode for editing programs written in The World's Most +;; Successful Programming Language. It features automatic +;; indentation, font locking, keyword capitalization, and some minor +;; convenience functions. + +;; Installation instructions +;; Put basic-mode.el somewhere in your path, compile it, and add the +;; following to your init file: + +;; (autoload 'visual-basic-mode "visual-basic-mode" "Visual Basic mode." t) +;; (setq auto-mode-alist (append '(("\\.\\(frm\\|bas\\|cls\\)$" . +;; visual-basic-mode)) auto-mode-alist)) + +;; Of course, under Windows 3.1, you'll have to name this file +;; something shorter than visual-basic-mode.el + +;; Revisions: +;; 1.0 18-Apr-96 Initial version +;; 1.1 Accomodate emacs 19.29+ font-lock-defaults +;; Simon Marshall +; 1.2 Rename to visual-basic-mode +;; 1.3 Fix some indentation bugs. +;; 1.3+ Changes by Dave Love: [No attempt at compatibility with +;; anything other than Emacs 20, sorry, but little attempt to +;; sanitize for Emacs 20 specifically.] +;; Change `_' syntax only for font-lock and imenu, not generally; +;; provide levels of font-locking in the current fashion; +;; font-lock case-insensitively; use regexp-opt with the font-lok +;; keywords; imenu support; `visual-basic-split-line', bound to +;; C-M-j; account for single-statement `if' in indentation; add +;; keyword "Global"; use local-write-file-hooks, not +;; write-file-hooks. +;; 1.4 September 1998 +;; 1.4 KJW Add begin..end, add extra keywords +;; Add customisation for single line if. Disallow by default. +;; Fix if regexp to require whitespace after if and require then. +;; Add more VB keywords. Make begin..end work as if..endif so +;; that forms are formatted correctly. +;; 1.4.1 KJW Merged Dave Love and KJW versions. +;; Added keywords suggested by Mickey Ferguson +;; +;; Fixed imenu variable to find private variables and enums + +;; Changed syntax class of =, <, > to punctuation to allow dynamic +;; abbreviations to pick up only the word at point rather than the +;; whole expression. + +;; Fixed bug introduced by KJW adding suport for begin...end in +;; forms whereby a single end outdented. + +;; Partially fixed failure to recognise if statements with +;; continuations (still fails on 'single line' if with +;; continuation, ugh). + +;; +;; Notes: +;; Dave Love +;; BTW, here's a script for making tags tables that I (Dave Love) have +;; used with reasonable success. It assumes a hacked version of etags +;; with support for case-folded regexps. I think this is now in the +;; development version at and should +;; make it into Emacs after 20.4. + +;; #! /bin/sh + +;; # etags-vb: (so-called) Visual (so-called) Basic TAGS generation. +;; # Dave Love . Public domain. +;; # 1997-11-21 + +;; if [ $# -lt 1 ]; then +;; echo "Usage: `basename $0` [etags options] VBfile ... [etags options] " 1>&2 +;; exit 1 +;; fi + +;; if [ $1 = "--help" ] || [ $1 = "-h" ]; then +;; echo "Usage: `basename $0` [etags options] VBfile ... [etags options] + +;; " +;; etags --help +;; fi + +;; exec etags --lang=none -c '/\(global\|public\)[ \t]+\(\(const\|type\)[ \t]+\)*\([a-z_0-9]+\)/\4/' \ +;; -c '/public[ \t]+\(sub\|function\)[ \t]+\([a-z_0-9]+\)/\2/' \ +;; "$@" + +;; End Notes Dave Love + + +;; Known bugs: +;; Doesn't know about ":" separated stmts +;; Doesn't recognize single line if statements if these are broken by +;; line continuation characters + + +;; todo: +;; fwd/back-compound-statement +;; completion over OCX methods and properties. +;; IDE integration +;; Change behaviour of ESC-q to recognise words used as paragraph +;; titles and prevent them being dragged into the previous +;; paragraph. +;; etc. + + +;;; Code: + +(defvar visual-basic-xemacs-p (string-match "XEmacs\\|Lucid" (emacs-version))) +(defvar visual-basic-winemacs-p (string-match "Win-Emacs" (emacs-version))) +(defvar visual-basic-win32-p (eq window-system 'win32)) + +;; Variables you may want to customize. +(defvar visual-basic-mode-indent 4 "*Default indentation per nesting level.") +(defvar visual-basic-fontify-p t "*Whether to fontify Basic buffers.") +(defvar visual-basic-capitalize-keywords-p t "*Whether to capitalize BASIC keywords.") +(defvar visual-basic-wild-files "*.frm *.bas *.cls" "*Wildcard pattern for BASIC source files.") +(defvar visual-basic-ide-pathname nil "*The full pathname of your VB exe file, if any.") +;; KJW Provide for my preference in if statements +(defvar visual-basic-allow-single-line-if nil "*Whether to allow single line if") + +(defvar visual-basic-defn-templates + (list "Public Sub ()\nEnd Sub\n\n" + "Public Function () As Variant\nEnd Function\n\n" + "Public Property Get ()\nEnd Property\n\n") + "*List of function templates though which visual-basic-new-sub cycles.") + +(defvar visual-basic-imenu-generic-expression + '((nil "^\\s-*\\(public\\|private\\)*\\s-+\\(declare\\s-+\\)*\\(sub\\|function\\)\\s-+\\(\\sw+\\>\\)" + 4) + ("Constants" + "^\\s-*\\(private\\|public\\|global\\)*\\s-*\\(const\\s-+\\)\\(\\sw+\\>\\s-*=\\s-*.+\\)$\\|'" + 3) + ("Variables" + "^\\(private\\|public\\|global\\|dim\\)+\\s-+\\(\\sw+\\>\\s-+as\\s-+\\sw+\\>\\)" + 2) + ("Types" "^\\(public\\s-+\\)*type\\s-+\\(\\sw+\\)" 2))) + + + +(defvar visual-basic-mode-syntax-table nil) +(if visual-basic-mode-syntax-table + () + (setq visual-basic-mode-syntax-table (make-syntax-table)) + (modify-syntax-entry ?\' "\<" visual-basic-mode-syntax-table) ; Comment starter + (modify-syntax-entry ?\n ">" visual-basic-mode-syntax-table) + (modify-syntax-entry ?\\ "w" visual-basic-mode-syntax-table) ; backslash is not an escape. + (modify-syntax-entry ?\= "." visual-basic-mode-syntax-table) + (modify-syntax-entry ?\< "." visual-basic-mode-syntax-table) + (modify-syntax-entry ?\> "." visual-basic-mode-syntax-table)) ; Make =, etc., punctuation so that dynamic abbreviations work properly + +(defvar visual-basic-mode-map nil) +(if visual-basic-mode-map + () + (setq visual-basic-mode-map (make-sparse-keymap)) + (define-key visual-basic-mode-map "\t" 'visual-basic-indent-line) + (define-key visual-basic-mode-map "\r" 'visual-basic-newline-and-indent) + (define-key visual-basic-mode-map "\M-\C-a" 'visual-basic-beginning-of-defun) + (define-key visual-basic-mode-map "\M-\C-e" 'visual-basic-end-of-defun) + (define-key visual-basic-mode-map "\M-\C-h" 'visual-basic-mark-defun) + (define-key visual-basic-mode-map "\M-\C-\\" 'visual-basic-indent-region) + (define-key visual-basic-mode-map "\M-q" 'visual-basic-fill-or-indent) + (define-key visual-basic-mode-map "\M-\C-j" 'visual-basic-split-line) + (cond (visual-basic-winemacs-p + (define-key visual-basic-mode-map '(control C) 'visual-basic-start-ide)) + (visual-basic-win32-p + (define-key visual-basic-mode-map (read "[?\\S-\\C-c]") 'visual-basic-start-ide))) + (if visual-basic-xemacs-p + (progn + (define-key visual-basic-mode-map "\M-G" 'visual-basic-grep) + (define-key visual-basic-mode-map '(meta backspace) 'backward-kill-word) + (define-key visual-basic-mode-map '(control meta /) 'visual-basic-new-sub)))) + + +;; These abbrevs are valid only in a code context. +(defvar visual-basic-mode-abbrev-table nil) + +(defvar visual-basic-mode-hook ()) + + +;; Is there a way to case-fold all regexp matches? +;; Change KJW Add enum, , change matching from 0 or more to zero or one for public etc. +(eval-and-compile + (defconst visual-basic-defun-start-regexp + (concat + "^[ \t]*\\([Pp]ublic \\|[Pp]rivate \\|[Ss]tatic\\|[Ff]riend \\)?" + "\\([Ss]ub\\|[Ff]unction\\|[Pp]roperty +[GgSsLl]et\\|[Tt]ype\\|[Ee]num\\)" + "[ \t]+\\(\\w+\\)[ \t]*(?"))) + +(defconst visual-basic-defun-end-regexp + "^[ \t]*[Ee]nd \\([Ss]ub\\|[Ff]unction\\|[Pp]roperty\\|[Tt]ype\\|[Ee]num\\)") + + +;; Includes the compile-time #if variation. +;; KJW fixed if to require a whitespace so as to avoid matching, for +;; instance, iFileName and to require then. + +;; Two versions; one recognizes single line if just as though it were +;; a multi-line and the other does not. Modified again to remove the +;; requirement for then so as to allow it to match if statements that +;; have continuations. +;;(defconst visual-basic-if-regexp +;; "^[ \t]*#?[Ii]f[ \t]+.*[ \t]+[Tt]hen[ \t]*.*\\('\\|$\\)") +(defconst visual-basic-if-regexp + "^[ \t]*#?[Ii]f[ \t]+.*[ \t_]+") + +(defconst visual-basic-ifthen-regexp "^[ \t]*#?[Ii]f.+\\<[Tt]hen\\>\\s-\\S-+") + +(defconst visual-basic-else-regexp "^[ \t]*#?[Ee]lse\\([Ii]f\\)?") +(defconst visual-basic-endif-regexp "[ \t]*#?[Ee]nd[ \t]*[Ii]f") + +(defconst visual-basic-continuation-regexp "^.*\\_[ \t]*$") +(eval-and-compile + (defconst visual-basic-label-regexp "^[ \t]*[a-zA-Z0-9_]+:$")) + +(defconst visual-basic-select-regexp "^[ \t]*[Ss]elect[ \t]+[Cc]ase") +(defconst visual-basic-case-regexp "^[ \t]*[Cc]ase") +(defconst visual-basic-select-end-regexp "^[ \t]*[Ee]nd[ \t]+[Ss]elect") + + +(defconst visual-basic-for-regexp "^[ \t]*[Ff]or\\b") +(defconst visual-basic-next-regexp "^[ \t]*[Nn]ext\\b") + +(defconst visual-basic-do-regexp "^[ \t]*[Dd]o\\b") +(defconst visual-basic-loop-regexp "^[ \t]*[Ll]oop\\b") + +(defconst visual-basic-while-regexp "^[ \t]*[Ww]hile\\b") +(defconst visual-basic-wend-regexp "^[ \t]*[Ww]end\\b") + +;; Added KJW Begin..end for forms +(defconst visual-basic-begin-regexp "^[ \t]*[Bb]egin)?") +;; This has created a bug. End on its own in code should not outdent. +;; How can we fix this? They are used in separate Lisp expressions so +;; add another one. +(defconst visual-basic-end-begin-regexp "^[ \t]*[Ee]nd") + +(defconst visual-basic-with-regexp "^[ \t]*[Ww]ith\\b") +(defconst visual-basic-end-with-regexp "^[ \t]*[Ee]nd[ \t]+[Ww]ith\\b") + +(defconst visual-basic-blank-regexp "^[ \t]*$") +(defconst visual-basic-comment-regexp "^[ \t]*\\s<.*$") + + +;; This is some approximation of the set of reserved words in Visual Basic. +(eval-and-compile + (defvar visual-basic-all-keywords + '("Add" "Aggregate" "And" "App" "AppActivate" "Application" "Array" "As" + "Asc" "AscB" "Atn" "Attribute" + "Beep" "Begin" "BeginTrans" "Boolean" "ByVal" "ByRef" + "CBool" "CByte" "CCur" + "CDate" "CDbl" "CInt" "CLng" "CSng" "CStr" "CVErr" "CVar" "Call" + "Case" "ChDir" "ChDrive" "Character" "Choose" "Chr" "ChrB" + "ClassModule" "Clipboard" "Close" "Collection" "Column" "Columns" + "Command" "CommitTrans" "CompactDatabase" "Component" "Components" + "Const" "Container" "Containers" "Cos" "CreateDatabase" "CreateObject" + "CurDir" "Currency" + "DBEngine" "DDB" "Data" "Database" "Databases" + "Date" "DateAdd" "DateDiff" "DatePart" "DateSerial" "DateValue" "Day" + "Debug" "Declare" "Deftype" "DeleteSetting" "Dim" "Dir" "Do" + "DoEvents" "Domain" + "Double" "Dynaset" "EOF" "Each" "Else" "End" "EndProperty" + "Enum" "Environ" "Erase" "Err" "Error" "Exit" "Exp" "FV" "False" "Field" + "Fields" "FileAttr" "FileCopy" "FileDateTime" "FileLen" "Fix" "Font" "For" + "Form" "FormTemplate" "Format" "Forms" "FreeFile" "FreeLocks" "Friend" + "Function" + "Get" "GetAllSettings" "GetAttr" "GetObject" "GetSetting" "Global" "GoSub" + "GoTo" "Group" "Groups" "Hex" "Hour" "IIf" "IMEStatus" "IPmt" "IRR" + "If" "Implements" "InStr" "Input" "Int" "Integer" "Is" "IsArray" "IsDate" + "IsEmpty" "IsError" "IsMissing" "IsNull" "IsNumeric" "IsObject" "Kill" + "LBound" "LCase" "LOF" "LSet" "LTrim" "Left" "Len" "Let" "Like" "Line" + "Load" "LoadPicture" "LoadResData" "LoadResPicture" "LoadResString" "Loc" + "Lock" "Log" "Long" "Loop" "MDIForm" "MIRR" "Me" "MenuItems" + "MenuLine" "Mid" "Minute" "MkDir" "Month" "MsgBox" "NPV" "NPer" "Name" + "New" "Next" "Not" "Now" "Nothing" "Object" "Oct" "On" "Open" + "OpenDatabase" + "Operator" "Option" "Optional" + "Or" "PPmt" "PV" "Parameter" "Parameters" "Partition" + "Picture" "Pmt" "Print" "Printer" "Printers" "Private" "ProjectTemplate" + "Property" + "Properties" "Public" "Put" "QBColor" "QueryDef" "QueryDefs" + "RSet" "RTrim" "Randomize" "Rate" "ReDim" "Recordset" "Recordsets" + "RegisterDatabase" "Relation" "Relations" "Rem" "RepairDatabase" + "Reset" "Resume" "Return" "Right" "RmDir" "Rnd" "Rollback" "RowBuffer" + "SLN" "SYD" "SavePicture" "SaveSetting" "Screen" "Second" "Seek" + "SelBookmarks" "Select" "SelectedComponents" "SendKeys" "Set" + "SetAttr" "SetDataAccessOption" "SetDefaultWorkspace" "Sgn" "Shell" + "Sin" "Single" "Snapshot" "Space" "Spc" "Sqr" "Static" "Step" "Stop" "Str" + "StrComp" "StrConv" "String" "Sub" "SubMenu" "Switch" "Tab" "Table" + "TableDef" "TableDefs" "Tan" "Then" "Time" "TimeSerial" "TimeValue" + "Timer" "To" "Trim" "True" "Type" "TypeName" "UBound" "UCase" "Unload" + "Unlock" "Val" "Variant" "VarType" "Verb" "Weekday" "Wend" + "While" "Width" "With" "Workspace" "Workspaces" "Write" "Year"))) + +(defvar visual-basic-font-lock-keywords-1 + (eval-when-compile + (list + ;; Names of functions. + (list visual-basic-defun-start-regexp + '(1 font-lock-keyword-face nil t) + '(2 font-lock-keyword-face nil t) + '(3 font-lock-function-name-face)) + + ;; Statement labels + (cons visual-basic-label-regexp 'font-lock-keyword-face) + + ;; Case values + ;; String-valued cases get font-lock-string-face regardless. + (list "^[ \t]*case[ \t]+\\([^'\n]+\\)" 1 'font-lock-keyword-face t) + + ;; Any keywords you like. + (list (concat "\\<" (regexp-opt + '("FooBar" "ElseIf") t) + "\\>") + 0 'font-lock-keyword-face) + ))) + +(defvar visual-basic-font-lock-keywords-2 + (append visual-basic-font-lock-keywords-1 + (eval-when-compile + `((,(concat "\\<" (regexp-opt visual-basic-all-keywords t) "\\>") + 0 font-lock-keyword-face))))) + +(defvar visual-basic-font-lock-keywords visual-basic-font-lock-keywords-1) + + +(put 'visual-basic-mode 'font-lock-keywords 'visual-basic-font-lock-keywords) + +(defun visual-basic-mode () + "A mode for editing Microsoft Visual Basic programs. +Features automatic indentation, font locking, keyword capitalization, +and some minor convenience functions. +Commands: +\\{visual-basic-mode-map}" + (interactive) + (kill-all-local-variables) + (use-local-map visual-basic-mode-map) + (setq major-mode 'visual-basic-mode) + (setq mode-name "Visual Basic") + (set-syntax-table visual-basic-mode-syntax-table) + + (add-hook 'local-write-file-hooks 'visual-basic-untabify) + + (setq local-abbrev-table visual-basic-mode-abbrev-table) + (if visual-basic-capitalize-keywords-p + (progn + (make-local-variable 'pre-abbrev-expand-hook) + (add-hook 'pre-abbrev-expand-hook 'visual-basic-pre-abbrev-expand-hook) + (abbrev-mode 1))) + + (make-local-variable 'comment-start) + (setq comment-start "' ") + (make-local-variable 'comment-start-skip) + (setq comment-start-skip "'+ *") + (make-local-variable 'comment-column) + (setq comment-column 40) + (make-local-variable 'comment-end) + (setq comment-end "") + + (make-local-variable 'indent-line-function) + (setq indent-line-function 'visual-basic-indent-line) + + (if visual-basic-fontify-p + (visual-basic-enable-font-lock)) + + (make-local-variable 'imenu-generic-expression) + (setq imenu-generic-expression visual-basic-imenu-generic-expression) + + (set (make-local-variable 'imenu-syntax-alist) '(("_" . "w"))) + (set (make-local-variable 'imenu-case-fold-search) t) + + ;(make-local-variable 'visual-basic-associated-files) ; doing this here means we need not check to see if it is bound later. + (add-hook 'find-file-hooks 'visual-basic-load-associated-files) + + (run-hooks 'visual-basic-mode-hook)) + + +(defun visual-basic-enable-font-lock () + ;; Emacs 19.29 requires a window-system else font-lock-mode errs out. + (cond ((or visual-basic-xemacs-p window-system) + + ;; In win-emacs this sets font-lock-keywords back to nil! + (if visual-basic-winemacs-p + (font-lock-mode 1)) + + ;; Accomodate emacs 19.29+ + ;; From: Simon Marshall + (cond ((boundp 'font-lock-defaults) + (make-local-variable 'font-lock-defaults) + (setq font-lock-defaults + '( + (visual-basic-font-lock-keywords + visual-basic-font-lock-keywords-1 + visual-basic-font-lock-keywords-2) + nil t + ((?\_ . "w")) + ))) + (t + (make-local-variable 'font-lock-keywords) + (setq font-lock-keywords visual-basic-font-lock-keywords))) + + (if visual-basic-winemacs-p + (font-lock-fontify-buffer) + (font-lock-mode 1))) + )) + +;; KJW should add some odds and bobs here to cover "end if" one way +;; could be to create the abbreviations by removing whitespace then we +;; could put "end if", "end with" and so on in the keyword table +;; Another idea would be to make it intelligent enough to substitute +;; the correct end for the construct (with, select, if) +;; Is this what the abbrev table hook entry is for? +(defun visual-basic-construct-keyword-abbrev-table () + (if visual-basic-mode-abbrev-table + nil + (let ((words visual-basic-all-keywords) + (word nil) + (list nil)) + (while words + (setq word (car words) + words (cdr words)) + (setq list (cons (list (downcase word) word) list))) + + (define-abbrev-table 'visual-basic-mode-abbrev-table list)))) + +;; Would like to do this at compile-time. +(visual-basic-construct-keyword-abbrev-table) + + +(defun visual-basic-in-code-context-p () + (if (fboundp 'buffer-syntactic-context) ; XEmacs function. + (null (buffer-syntactic-context)) + ;; Attempt to simulate buffer-syntactic-context + ;; I don't know how reliable this is. + (let* ((beg (save-excursion + (beginning-of-line) + (point))) + (list + (parse-partial-sexp beg (point)))) + (and (null (nth 3 list)) ; inside string. + (null (nth 4 list)))))) ; inside comment + +(defun visual-basic-pre-abbrev-expand-hook () + ;; Allow our abbrevs only in a code context. + (setq local-abbrev-table + (if (visual-basic-in-code-context-p) + visual-basic-mode-abbrev-table))) + +(defun visual-basic-newline-and-indent (&optional count) + "Insert a newline, updating indentation." + (interactive) + (expand-abbrev) + (save-excursion + (visual-basic-indent-line)) + (call-interactively 'newline-and-indent)) + +(defun visual-basic-beginning-of-defun () + (interactive) + (re-search-backward visual-basic-defun-start-regexp)) + +(defun visual-basic-end-of-defun () + (interactive) + (re-search-forward visual-basic-defun-end-regexp)) + +(defun visual-basic-mark-defun () + (interactive) + (beginning-of-line) + (visual-basic-end-of-defun) + (set-mark (point)) + (visual-basic-beginning-of-defun) + (if visual-basic-xemacs-p + (zmacs-activate-region))) + +(defun visual-basic-indent-defun () + (interactive) + (save-excursion + (visual-basic-mark-defun) + (call-interactively 'visual-basic-indent-region))) + + +(defun visual-basic-fill-long-comment () + "Fills block of comment lines around point." + ;; Derived from code in ilisp-ext.el. + (interactive) + (save-excursion + (beginning-of-line) + (let ((comment-re "^[ \t]*\\s<+[ \t]*")) + (if (looking-at comment-re) + (let ((fill-prefix + (buffer-substring + (progn (beginning-of-line) (point)) + (match-end 0)))) + + (while (and (not (bobp)) + (looking-at visual-basic-comment-regexp)) + (forward-line -1)) + (if (not (bobp)) (forward-line 1)) + + (let ((start (point))) + + ;; Make all the line prefixes the same. + (while (and (not (eobp)) + (looking-at comment-re)) + (replace-match fill-prefix) + (forward-line 1)) + + (if (not (eobp)) + (beginning-of-line)) + + ;; Fill using fill-prefix + (fill-region-as-paragraph start (point)))))))) + + +(defun visual-basic-fill-or-indent () + "Fill long comment around point, if any, else indent current definition." + (interactive) + (cond ((save-excursion + (beginning-of-line) + (looking-at visual-basic-comment-regexp)) + (visual-basic-fill-long-comment)) + (t + (visual-basic-indent-defun)))) + + +(defun visual-basic-new-sub () + "Insert template for a new subroutine. Repeat to cycle through alternatives." + (interactive) + (beginning-of-line) + (let ((templates (cons visual-basic-blank-regexp + visual-basic-defn-templates)) + (tem nil) + (bound (point))) + (while templates + (setq tem (car templates) + templates (cdr templates)) + (cond ((looking-at tem) + (replace-match (or (car templates) + "")) + (setq templates nil)))) + + (search-backward "()" bound t))) + + +(defun visual-basic-untabify () + "Do not allow any tabs into the file." + (if (eq major-mode 'visual-basic-mode) + (untabify (point-min) (point-max))) + nil) + +(defun visual-basic-default-tag () + (if (and (not (bobp)) + (save-excursion + (backward-sexp) + (looking-at "\\w"))) + (backward-word 1)) + (let ((s (point)) + (e (save-excursion + (forward-sexp) + (point)))) + (buffer-substring s e))) + +(defun visual-basic-grep (tag) + "Search BASIC source files in current directory for TAG." + (interactive + (list (let* ((def (visual-basic-default-tag)) + (tag (read-string + (format "Grep for [%s]: " def)))) + (if (string= tag "") def tag)))) + (grep (format "grep -n %s %s" tag visual-basic-wild-files))) + + +;;; IDE Connection. + +(defun visual-basic-buffer-project-file () + "Return a guess as to the project file associated with the current buffer." + (car (directory-files (file-name-directory (buffer-file-name)) t "\\.vbp"))) + +(defun visual-basic-start-ide () + "Start Visual Basic (or your favorite IDE, (after Emacs, of course)) +on the first project file in the current directory. +Note: it's not a good idea to leave Visual Basic running while you +are editing in Emacs, since Visual Basic has no provision for reloading +changed files." + (interactive) + (let (file) + (cond ((null visual-basic-ide-pathname) + (error "No pathname set for Visual Basic. See visual-basic-ide-pathname")) + ((null (setq file (visual-basic-buffer-project-file))) + (error "No project file found")) + ((fboundp 'win-exec) + (iconify-emacs) + (win-exec visual-basic-ide-pathname 'win-show-normal file)) + ((fboundp 'start-process) + (iconify-frame (selected-frame)) + (start-process "*VisualBasic*" nil visual-basic-ide-pathname file)) + (t + (error "No way to spawn process!"))))) + + + +;;; Indentation-related stuff. + +(defun visual-basic-indent-region (start end) + "Perform visual-basic-indent-line on each line in region." + (interactive "r") + (save-excursion + (goto-char start) + (beginning-of-line) + (while (and (not (eobp)) + (< (point) end)) + (if (not (looking-at visual-basic-blank-regexp)) + (visual-basic-indent-line)) + (forward-line 1))) + + (cond ((fboundp 'zmacs-deactivate-region) + (zmacs-deactivate-region)) + ((fboundp 'deactivate-mark) + (deactivate-mark)))) + + + +(defun visual-basic-previous-line-of-code () + (if (not (bobp)) + (forward-line -1)) ; previous-line depends on goal column + (while (and (not (bobp)) + (or (looking-at visual-basic-blank-regexp) + (looking-at visual-basic-comment-regexp))) + (forward-line -1))) + + +(defun visual-basic-find-original-statement () + "If the current line is a continuation, move back to the original stmt." + (let ((here (point))) + (visual-basic-previous-line-of-code) + (while (and (not (bobp)) + (looking-at visual-basic-continuation-regexp)) + (setq here (point)) + (visual-basic-previous-line-of-code)) + (goto-char here))) + +(defun visual-basic-find-matching-stmt (open-regexp close-regexp) + ;; Searching backwards + (let ((level 0)) + (while (and (>= level 0) (not (bobp))) + (visual-basic-previous-line-of-code) + (visual-basic-find-original-statement) + (cond ((looking-at close-regexp) + (setq level (+ level 1))) + ((looking-at open-regexp) + (setq level (- level 1))))))) + +(defun visual-basic-find-matching-if () + (visual-basic-find-matching-stmt + visual-basic-if-regexp visual-basic-endif-regexp)) + +(defun visual-basic-find-matching-select () + (visual-basic-find-matching-stmt + visual-basic-select-regexp visual-basic-select-end-regexp)) + +(defun visual-basic-find-matching-for () + (visual-basic-find-matching-stmt + visual-basic-for-regexp visual-basic-next-regexp)) + +(defun visual-basic-find-matching-do () + (visual-basic-find-matching-stmt + visual-basic-do-regexp visual-basic-loop-regexp)) + +(defun visual-basic-find-matching-while () + (visual-basic-find-matching-stmt + visual-basic-while-regexp visual-basic-wend-regexp)) + +(defun visual-basic-find-matching-with () + (visual-basic-find-matching-stmt + visual-basic-with-regexp visual-basic-end-with-regexp)) + +;;; If this fails it must return the indent of the line preceding the +;;; end not the first line because end without matching begin is a +;;; normal simple statement +(defun visual-basic-find-matching-begin () + (let ((original-point (point))) + (visual-basic-find-matching-stmt visual-basic-begin-regexp + visual-basic-end-begin-regexp) + (if (bobp) ;failed to find a matching begin so assume that it is + ;an end statement instead and use the indent of the + ;preceding line. + (progn (goto-char original-point) + (visual-basic-previous-line-of-code))))) + + +(defun visual-basic-calculate-indent () + (let ((original-point (point))) + (save-excursion + (beginning-of-line) + ;; Some cases depend only on where we are now. + (cond ((or (looking-at visual-basic-defun-start-regexp) + (looking-at visual-basic-label-regexp) + (looking-at visual-basic-defun-end-regexp)) + 0) + + ;; The outdenting stmts, which simply match their original. + ((or (looking-at visual-basic-else-regexp) + (looking-at visual-basic-endif-regexp)) + (visual-basic-find-matching-if) + (current-indentation)) + + ;; All the other matching pairs act alike. + ((looking-at visual-basic-next-regexp) ; for/next + (visual-basic-find-matching-for) + (current-indentation)) + + ((looking-at visual-basic-loop-regexp) ; do/loop + (visual-basic-find-matching-do) + (current-indentation)) + + ((looking-at visual-basic-wend-regexp) ; while/wend + (visual-basic-find-matching-while) + (current-indentation)) + + ((looking-at visual-basic-end-with-regexp) ; with/end with + (visual-basic-find-matching-with) + (current-indentation)) + + ((looking-at visual-basic-select-end-regexp) ; select case/end select + (visual-basic-find-matching-select) + (current-indentation)) + + ;; A case of a select is somewhat special. + ((looking-at visual-basic-case-regexp) + (visual-basic-find-matching-select) + (+ (current-indentation) visual-basic-mode-indent)) + + ;; Added KJW: Make sure that this comes after the cases + ;; for if..endif, end select because end-regexp will also + ;; match "end select" etc. + ((looking-at visual-basic-end-begin-regexp) ; begin/end + (visual-basic-find-matching-begin) + (current-indentation)) + + (t + ;; Other cases which depend on the previous line. + (visual-basic-previous-line-of-code) + + ;; Skip over label lines, which always have 0 indent. + (while (looking-at visual-basic-label-regexp) + (visual-basic-previous-line-of-code)) + + (cond + ((looking-at visual-basic-continuation-regexp) + (visual-basic-find-original-statement) + ;; Indent continuation line under matching open paren, + ;; or else one word in. + (let* ((orig-stmt (point)) + (matching-open-paren + (condition-case () + (save-excursion + (goto-char original-point) + (beginning-of-line) + (backward-up-list 1) + ;; Only if point is now w/in cont. block. + (if (<= orig-stmt (point)) + (current-column))) + (error nil)))) + (cond (matching-open-paren + (1+ matching-open-paren)) + (t + ;; Else, after first word on original line. + (back-to-indentation) + (forward-word 1) + (while (looking-at "[ \t]") + (forward-char 1)) + (current-column))))) + (t + (visual-basic-find-original-statement) + + (let ((indent (current-indentation))) + ;; All the various +indent regexps. + (cond ((looking-at visual-basic-defun-start-regexp) + (+ indent visual-basic-mode-indent)) + + ((and (or (looking-at visual-basic-if-regexp) + (looking-at visual-basic-else-regexp)) + (not (and visual-basic-allow-single-line-if + (looking-at visual-basic-ifthen-regexp)))) + (+ indent visual-basic-mode-indent)) + + ((or (looking-at visual-basic-select-regexp) + (looking-at visual-basic-case-regexp)) + (+ indent visual-basic-mode-indent)) + + ((or (looking-at visual-basic-do-regexp) + (looking-at visual-basic-for-regexp) + (looking-at visual-basic-while-regexp) + (looking-at visual-basic-with-regexp) + (looking-at visual-basic-begin-regexp)) + (+ indent visual-basic-mode-indent)) + + (t + ;; By default, just copy indent from prev line. + indent)))))))))) + +(defun visual-basic-indent-to-column (col) + (let* ((bol (save-excursion + (beginning-of-line) + (point))) + (point-in-whitespace + (<= (point) (+ bol (current-indentation)))) + (blank-line-p + (save-excursion + (beginning-of-line) + (looking-at visual-basic-blank-regexp)))) + + (cond ((/= col (current-indentation)) + (save-excursion + (beginning-of-line) + (back-to-indentation) + (delete-region bol (point)) + (indent-to col)))) + + ;; If point was in the whitespace, move back-to-indentation. + (cond (blank-line-p + (end-of-line)) + (point-in-whitespace + (back-to-indentation))))) + +(defun visual-basic-indent-line () + "Indent current line for BASIC." + (interactive) + (visual-basic-indent-to-column (visual-basic-calculate-indent))) + +(defun visual-basic-split-line () + "Split line at point, adding continuation character or continuing a comment. +In Abbrev mode, any abbrev before point will be expanded." + (interactive) + (let ((pps-list (parse-partial-sexp (save-excursion + (beginning-of-line) + (point)) + (point)))) + ;; Dispatch on syntax at this position. + (cond ((equal t (nth 4 pps-list)) ; in comment + (indent-new-comment-line)) + ((equal t (nth 4 pps-list)) ; in string + (error "Can't break line inside a string")) + (t (just-one-space) ; leading space on next line + ; doesn't count, sigh + (insert "_") + (visual-basic-newline-and-indent))))) + +(provide 'visual-basic-mode) + + +;;; Some experimental functions + +;;; Load associated files listed in the file local variables block +(defun visual-basic-load-associated-files () + "Load files that are useful to have around when editing the source of the file that has just been loaded. +The file must have a local variable that lists the files to be loaded. +If the file name is relative it is relative to the directory +containing the current buffer. If the file is already loaded nothing +happens, this prevents circular references causing trouble. After an +associated file is loaded its associated files list will be +processed." + (if (boundp 'visual-basic-associated-files) + (let ((files visual-basic-associated-files) + (file nil)) + (while files + (setq file (car files) + files (cdr files)) + (message "Load associated file: %s" file) + (visual-basic-load-file-ifnotloaded file default-directory))))) + + + +(defun visual-basic-load-file-ifnotloaded (file default-directory) + "Load file if not already loaded. +If file is relative then default-directory provides the path" + (let((file-absolute (expand-file-name file default-directory))) + (if (get-file-buffer file-absolute); don't do anything if the buffer is already loaded + () + (find-file-noselect file-absolute )))) + + + + + + + +;(setq visual-basic- +; (defun visual-basic-standardize-spacing() +; "Scan buffer and add or remove spaces so that keywords are separated by single spaces. Like it or not your code will look like this if you edit it in VB so we might as well do it here." +; (interactive) +; (save-excursion +; ( + +;;; visual-basic-mode.el ends here diff --git a/tcl/Display.tcl b/tcl/Display.tcl new file mode 100644 index 0000000..ec9dbcf --- /dev/null +++ b/tcl/Display.tcl @@ -0,0 +1,59 @@ +package provide Display 1.0 +package require Tcl 8.4 + +namespace eval ::Display { + namespace export \ + display \ + verbose \ + debug \ + set_debug \ + set_verbose + + set debug 0 + set verbose 0 +} + +proc ::Display::display {msg} { + puts $msg +} + +proc ::Display::debug {msg} { + global debug + + if {$Display::debug} { + display "DEBUG: $msg" + } +} + +proc ::Display::error {msg} { + display "ERROR: $msg" + exit 1 +} + +proc ::Display::verbose {msg} { + global verbose + + if {$Display::verbose} { + display $msg + } +} + +proc ::Display::set_debug {newValue} { + global debug + + set oldValue $Display::debug + + set Display::debug $newValue + + return $oldValue +} + +proc ::Display::set_verbose {newValue} { + global verbose + + set oldValue $Display::verbose + + set Display::verbose $newValue + + return $oldValue +} diff --git a/test/.-_hist b/test/.-_hist new file mode 100644 index 0000000..e69de29 diff --git a/test/.cvsignore b/test/.cvsignore new file mode 100644 index 0000000..70eea3b --- /dev/null +++ b/test/.cvsignore @@ -0,0 +1,2 @@ +.-_hist +.cvsignore diff --git a/test/testclearcase.pl b/test/testclearcase.pl new file mode 100755 index 0000000..f0cfde9 --- /dev/null +++ b/test/testclearcase.pl @@ -0,0 +1,68 @@ +#!/usr/bin/perl +use strict; +use warnings; + +use FindBin; +use Term::ANSIColor qw(:constants); + +my $libs; + +BEGIN { + $libs = $ENV{SITE_PERLLIB} ? $ENV{SITE_PERLLIB} : "$FindBin::Bin/../lib"; + + die "Unable to find libraries\n" + unless -d $libs; +} # BEGIN + +use lib $libs; + +use Clearcase; +use Display; + +my ($status, @output) = $Clearcase::CC->execute ('-ver'); + +error 'Clearcase is not installed on this system', 1 + if $status; + +display YELLOW . "Global Clearcase Variables\n" . RESET; + +my $view_drive = $Clearcase::VIEW_DRIVE; +my $vob_mount = $Clearcase::VOB_MOUNT; +my $win_vob_prefix = $Clearcase::WIN_VOB_PREFIX; +my $vobtag_prefix = $Clearcase::VOBTAG_PREFIX; +my $countdb = $Clearcase::COUNTDB; + +display MAGENTA . "View Drive:\t\t" . RESET . $view_drive; +display MAGENTA . "VOB Mount:\t\t" . RESET . $vob_mount; +display MAGENTA . "Windows VOB prefix:\t" . RESET . $win_vob_prefix; +display MAGENTA . "VOB Tag Prefix:\t\t" . RESET . $vobtag_prefix; +display MAGENTA . "CountDB:\t\t" . RESET . $countdb; + +display CYAN . "\nGlobal Clearcase Configuration\n" . RESET; + +display MAGENTA . "Client:\t\t\t" . RESET . $Clearcase::CC->client; +display MAGENTA . "Hardware type:\t\t" . RESET . $Clearcase::CC->hardware_type; +display MAGENTA . "License host:\t\t" . RESET . $Clearcase::CC->license_host; +display MAGENTA . "OS:\t\t\t" . RESET . $Clearcase::CC->os; +display MAGENTA . "Region:\t\t\t" . RESET . $Clearcase::CC->region; +display MAGENTA . "Registry host:\t\t" . RESET . $Clearcase::CC->registry_host; +display MAGENTA . "Sitename:\t\t" . RESET . $Clearcase::CC->sitename; +display MAGENTA . "Version:\t\t" . RESET . $Clearcase::CC->version; + +display GREEN . "\nCleartool Access\n" . RESET; + +display_nolf MAGENTA . "Views:\t" . RESET; + +($status, @output) = $Clearcase::CC->execute ("lsview -s"); + +display scalar @output; + +display_nolf MAGENTA . "VOBs:\t" . RESET; + +($status, @output) = $Clearcase::CC->execute ("lsvob -s"); + +display scalar @output; + +($status, @output) = $Clearcase::CC->execute ("invalid command"); + +display $_ foreach (@output); diff --git a/test/testclearquest.pl b/test/testclearquest.pl new file mode 100644 index 0000000..c659f32 --- /dev/null +++ b/test/testclearquest.pl @@ -0,0 +1,386 @@ +#!/usr/bin/perl +use strict; +use warnings; + +=pod + +=head1 NAME $RCSfile: testclearquest.pl,v $ + +Test the Clearquest libary + +This script tests various functions of the Clearquest library + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 2.8 $ + +=item Created: + +Mon Nov 12 16:50:44 PST 2012 + +=item Modified: + +$Date: 2013/03/14 23:39:39 $ + +=back + +=head1 SYNOPSIS + + Usage: testclearquest.pl [-u|sage] [-v|erbose] [-d|ebug] + [-get] [-add] [-modify] [-change] [-delete] + [-username ] [-password ] + [-database ] [-dbset ] + [-module] [-server ] [-port ] + + Where: + -usa|ge: Displays usage + -v|erbose: Be verbose + -de|bug: Output debug messages + + -get: Test get + -add: Test add + -modify: Test modify + -change: Test change + -delete: Test delete + + -use|rname: Username to open database with (Default: from config file) + -p|assword: Password to open database with (Default: from config file) + -da|tabase: Database to open (Default: from config file) + -db|set: Database Set to use (Default: from config file) + -m|odule: Type of Clearquest module to use. Must be one of 'api', + 'client', or 'rest'. The 'api' module can only be used if + Clearquest is installed locally. The 'client' module can only + be successful if a corresponding server is running. And the + 'rest' module can only be used if a CQ Web server has been set + up and configured (Default: rest) + -s|erver: For module = client or rest this is the name of the server that + will be providing the service + -p|ort: For module = client, this is the point on the server to talk + through. + + +=head1 Options + +Options are keep in the cq.conf file in etc. They specify the default options +listed below. Or you can export the option name to the env(1) to override the +defaults in cq.conf. Finally you can programmatically set the options when you +call new by passing in a %parms hash. To specify the %parms hash key remove the +CQ_ portion and lc the rest. + +=for html
    + +=over + +=item CQ_SERVER + +Clearquest server to talk to (Default: From cq.conf) + +=item CQ_PORT + +Port to connect to (Default: From cq.conf) + +=item CQ_WEBHOST + +The web host to contact with leading http:// (Default: From cq.conf) + +=item CQ_DATABASE + +Name of database to connect to (Default: From cq.conf) + +=item CQ_USERNAME + +User name to connect as (Default: From cq.conf) + +=item CQ_PASSWORD + +Password for CQREST_USERNAME (Default: From cq.conf) + +=item CQ_DBSET + +Database Set name (Default: From cq.conf) + +=back + +=cut + +use FindBin; +use Getopt::Long; + +use lib "$FindBin::Bin/../lib"; + +use Clearquest; +use Display; +use TimeUtils; +use Utils; + +my ($cq, %opts); + +sub displayRecord (%) { + my (%record) = @_; + + display '-' x 79; + + foreach (keys %record) { + display_nolf "$_: "; + + if (ref $record{$_} eq 'ARRAY') { + display join ", ", @{$record{$_}}; + } elsif ($record{$_}) { + display $record{$_}; + } else { + display ""; + } # if + } # foreach + + return; +} # displayRecord + +sub displayResults (@) { + my (@records) = @_; + + if (@records) { + displayRecord %$_ foreach (@records); + } else { + display "Did not find any records"; + } # if + + return; +} # displayResults + +sub testGetRecord ($$;@) { + my ($table, $key, @fields) = @_; + + my $startTime = time; + + display "Testing get table: $table key: $key"; + + displayRecord $cq->get ($table, $key, @fields); + + display_duration $startTime; + + return; +} # testGetRecord + +sub testFindRecord ($$;@) { + my ($table, $condition, @fields) = @_; + + my $startTime = time; + + display "Testing find table: $table condition: $condition"; + + my ($result, $nbrRecs) = $cq->find ($table, $condition, @fields); + + display "$nbrRecs records qualified"; + + while (my %record = $cq->getNext ($result)) { + displayRecord %record; + } # while + + display_duration $startTime; + + return; +} # testFindRecord + +sub testModifyRecord ($$;%) { + my ($table, $key, %update) = @_; + + my $startTime = time; + + display "Testing modify table: $table key: $key"; + + $cq->modify ($table, $key, undef, \%update); + + $cq->checkErr; + + display_duration $startTime; + + return; +} # testModifyRecord + +sub testChangeState ($$) { + my ($table, $key) = @_; + + my $startTime = time; + + my %record = $cq->get ($table, $key, ('State')); + + $cq->checkErr ("Unable to find $table where key = $key"); + + return if $cq->error; + + my ($action, %update); + + if ($record{State} eq 'Assigned') { + $action = 'AdminAssignToSubmit'; + $update{Stability_Issue} = 'User Fault'; + } else { + $action = 'Assign'; + $update{Stability_Issue} = 'Assert'; + } # if + + display "Testing change state table: $table key: $key action: $action"; + + $cq->modify ($table, $key, $action, \%update); + + $cq->checkErr; + + display_duration $startTime; + + return; +} # testChangeState + +sub testAddRecord ($%) { + my ($table, %record) = @_; + + my $startTime = time; + + display "Testing adding table: $table"; + + $cq->add ($table, \%record, qw(Projects VersionStr)); + + $cq->checkErr; + + display_duration $startTime; + + return; +} # testAddRecord + +sub testDeleteRecord ($$) { + my ($table, $key) = @_; + + my $startTime = time; + + display "Testing deleting table: $table key: $key"; + + $cq->delete ($table, $key); + + $cq->checkErr; + + display_duration $startTime; + + return; +} # testDeleteRecord + +## Main +GetOptions ( + \%opts, + usage => sub { Usage }, + verbose => sub { set_verbose }, + debug => sub { set_debug }, + 'get', + 'add', + 'modify', + 'change', + 'delete', + 'module=s', + 'username=s', + 'password=s', + 'database=s', + 'dbset=s', + 'server=s', + 'port=i', +) || Usage; + +my $processStartTime = time; + +local $| = 1; + +# Translate any options to ones that the lib understands +$opts{CQ_USERNAME} = delete $opts{username}; +$opts{CQ_PASSWORD} = delete $opts{password}; +$opts{CQ_DATABASE} = delete $opts{database}; +$opts{CQ_DBSET} = delete $opts{dbset}; +$opts{CQ_SERVER} = delete $opts{server}; +$opts{CQ_PORT} = delete $opts{port}; +$opts{CQ_MODULE} = delete $opts{module}; + +# If nothing is set then do everything +unless ($opts{get} or + $opts{add} or + $opts{modify} or + $opts{change} or + $opts{delete} + ) { + $opts{get} = $opts{add} = $opts{modify} = $opts{change} = 1; +} # unless + +# If we are testing add or delete then toggle on the other one +$opts{delete} = 1 if $opts{add}; +$opts{add} = 1 if $opts{delete}; + +my $startTime = time; + +$cq = Clearquest->new (%opts); + +display_nolf 'Connecting to Clearquest database ' . $cq->connection; + +unless ($cq->connect) { + $cq->checkErr ('Unable to connect to database ' . $cq->connection); + + if ($cq->module eq 'client') { + display 'Unable to connect to server ' + . $cq->server () + . ':' + . $cq->port (); + } # if + + exit $cq->error; +} else { + display ''; + display_duration $startTime; +} # unless + +$cq->setOpts (emptyStringForUndef => 1); + +if ($opts{get}) { + # Get record by key + testGetRecord 'Project', 'Athena'; + + # Get record by condition + testFindRecord 'VersionInfo', 'Deprecated = 1'; + + # Get record by key with field list + testFindRecord 'VersionInfo', 'VersionStr = 1.0', ('VersionStr', 'Deprecated'); + + # Get record by condition with field list + testFindRecord 'CategorySub', 'Category="Software"', ('Category', 'CategoryType', 'SubCategory'); +} # if + +if ($opts{add}) { + # Add a record + testAddRecord 'VersionInfo', ( + VersionStr => '2.0', + Projects => ['Island', '21331', 'Hera'], + Visibility => 'Nokia Corporation', + ); +} # if + +if ($opts{modify}) { + # Modify a record + testModifyRecord ('VersionInfo', '1.0', ( + Deprecated => 1, + Projects => ['Island', 'Athena'], + )); +} # if + +if ($opts{change}) { + # Change State + testChangeState 'Defect', 'apd00000034'; +} # if + +if ($opts{add}) { + # Delete that record + testDeleteRecord 'VersionInfo', '2.0'; +} # if + +display_nolf 'Total process time '; + +display_duration $processStartTime; diff --git a/test/testclearquestServer.pl b/test/testclearquestServer.pl new file mode 100644 index 0000000..beb85e1 --- /dev/null +++ b/test/testclearquestServer.pl @@ -0,0 +1,38 @@ +#!cqperl +use strict; +use warnings; + +use FindBin; + +use lib "$FindBin::Bin/../lib"; + +use Clearquest::Client; +use Display; +use TimeUtils; + +$| = 1; + +# Let's time this... +my $startTime = time; + +my $cq = Clearquest::Client->new; + +my $dbname = $cq->username () . '@' . $cq->database () . '/' . $cq->dbset (); + +display_nolf "Connecting to Clearquest database $dbname"; + +unless ($cq->connect) { + display ' Failed!'; + + error "Unable to connect to database $dbname", 1; +} # unless + +display_duration $startTime; + +my ($result, $nbrRecs) = $cq->find ('defect', 'assert == 0', ('id', 'title')); + +while (my %record = $cq->getNext ($result)) { + display "$_: $record{$_}" foreach (sort keys %record); +} # while + +display 'done'; \ No newline at end of file diff --git a/test/testcmdline.pl b/test/testcmdline.pl new file mode 100755 index 0000000..bf23842 --- /dev/null +++ b/test/testcmdline.pl @@ -0,0 +1,30 @@ +#!/usr/bin/perl +use strict; +use warnings; + +use FindBin; + +use lib "$FindBin::Bin/../lib"; + +use CmdLine; +use Display; +use Term::ANSIColor qw (color); + +my $me = $FindBin::Script; + $me =~ s/\.pl$//; + +my $prompt = color ('BOLD CYAN') . "$me:" . color ('RESET'); + +$CmdLine::cmdline->set_prompt ($prompt); + +my ($line, $result); + +while (($line, $result) = $CmdLine::cmdline->get) { + last unless defined $line; + last if $line =~ /exit|quit/i; + + display "Would have executed $line" + if $line !~ /^\s*$/; +} # while + +display 'done'; \ No newline at end of file diff --git a/test/testelement.pl b/test/testelement.pl new file mode 100755 index 0000000..53fb232 --- /dev/null +++ b/test/testelement.pl @@ -0,0 +1,72 @@ +#!/usr/bin/perl +use strict; +use warnings; + +use FindBin; +use Term::ANSIColor qw(:constants); + +my $libs; + +BEGIN { + $libs = $ENV{SITE_PERLLIB} ? $ENV{SITE_PERLLIB} : "$FindBin::Bin/../lib"; + + die "Unable to find libraries\n" if !$libs and !-d $libs; +} # BEGIN + +use lib $libs; + +use Clearcase; +use Clearcase::Element; +use Display; + +error "Usage: $0 ", 1 if !$ARGV[0]; + +my $element = new Clearcase::Element (pname => $ARGV[0]); + +display MAGENTA . "Element:\t" . RESET . $element->pname; +display MAGENTA . "Version:\t" . RESET . $element->version; +display MAGENTA . "Pred:\t\t" . RESET . $element->pred; + +display MAGENTA . "Activities:" . RESET; + +if (my %activities = $element->activities) { + display "\t\t$_: $activities{$_}" foreach (keys %activities); +} else { + display CYAN . "\t\tNone" . RESET; +} # if + +display MAGENTA . "Attributes:" . RESET; + +if (my %attributes = $element->attributes) { + display "\t\t$_=$attributes{$_}" foreach (keys %attributes); +} else { + display CYAN . "\t\tNone" . RESET; +} # if + +display MAGENTA . "Hyperlinks:" . RESET; + +if (my @hyperlinks = $element->hyperlinks) { + display "\t\t$_" foreach (@hyperlinks); +} else { + display CYAN . "\t\tNone" . RESET; +} # if + +display MAGENTA . "Comments:" . RESET . $element->comments; +display MAGENTA . "Create_date:\t" . RESET . $element->create_date; +display MAGENTA . "User:\t\t" . RESET . $element->user; +display MAGENTA . "Group:\t\t" . RESET . $element->group; +display MAGENTA . "User_mode:\t" . RESET . $element->user_mode; +display MAGENTA . "Group_mode:\t" . RESET . $element->group_mode; +display MAGENTA . "Other_mode:\t" . RESET . $element->other_mode; +display MAGENTA . "Mode:\t\t" . RESET . $element->mode; + +display MAGENTA . "Labels:" . RESET; + +if (my @labels = $element->labels) { + display "\t\t$_" foreach (@labels); +} else { + display CYAN . "\t\tNone" . RESET; +} # if + +display MAGENTA . "Rule:\t\t" . RESET . $element->rule; +display MAGENTA . "Xname:\t\t" . RESET . $element->xname; diff --git a/test/testmail.pl b/test/testmail.pl new file mode 100755 index 0000000..8ac46bd --- /dev/null +++ b/test/testmail.pl @@ -0,0 +1,88 @@ +#!/usr/bin/env cqperl +################################################################################ +# +# File: $RCSfile: testmail.pl,v $ +# Revision: $Revision: 1.1 $ +# Description: Tests Mail.pm +# Author: Andrew@DeFaria.com +# Created: Wed Aug 1 09:16:42 MST 2007 +# Modified: $Date: 2007/12/07 05:52:36 $ +# Language: perl +# +# (c) Copyright 2007, ClearSCM, Inc., all rights reserved +# +################################################################################ +use strict; +use warnings; + +use FindBin; + +my $libs; + +BEGIN { + $libs = $ENV{SITE_PERLLIB} ? $ENV{SITE_PERLLIB} : "$FindBin::Bin/../lib"; + + die "Unable to find libraries\n" if !$libs; +} + +use lib $libs; + +use Mail; + +my $data = < + + + RANCQ00008837 + Add new VOB to other projects + NeedingInfo + p6258c + p5602c + 2007-07-26 15:19:53 + + + RANCQ00012317 + RoseRT Crashing + NeedingInfo + p6258c + p29353 + 2007-07-18 11:49:57 + + + RANCQ00012821 + http://ranweb requests + Verifying + p6258c + p6001c + 2007-07-26 15:40:47 + + + RANCQ00012830 + Not all errors are being reported when doing rebase from UCM GUI. + NeedingInfo + p6258c + p57413 + 2007-07-26 11:40:37 + + + +END + +my $footing = < $to, + "subject" => $subject, + "mode" => "html", + "heading" => $heading, + "footing" => $footing, + "data" => $data, +) diff --git a/test/testrest.pl b/test/testrest.pl new file mode 100644 index 0000000..f14c6f6 --- /dev/null +++ b/test/testrest.pl @@ -0,0 +1,202 @@ +#!/usr/brcm/ba/bin/perl +use strict; +use warnings; + +use FindBin; +use Getopt::Long; + +use lib "$FindBin::Bin/../lib"; + +use Clearquest::REST; +use Display; +use Utils; + +my $cq; + +=pod + +Usage $FindBin::Script: [-get] [-add] [-modify] [-change] [-delete] + +=cut + +END { + # Always remember to call disconnect for any instanciated Clearquest::REST + # object + $cq->disconnect if $cq; +} # END + +sub displayRecord (%) { + my (%record) = @_; + + display '-' x 79; + + foreach (keys %record) { + display_nolf "$_: "; + + if (ref $record{$_} eq 'ARRAY') { + display join ", ", @{$record{$_}}; + } elsif ($record{$_}) { + display $record{$_}; + } else { + display ""; + } # if + } # foreach +} # displayRecord + +sub displayResults (@) { + my (@records) = @_; + + if (@records) { + displayRecord %$_ foreach (@records); + } else { + display "Did not find any records"; + } # if +} # displayResults + +sub testGetRecord ($$;@) { + my ($table, $key, @fields) = @_; + + display "Testing get table: $table key: $key"; + + displayRecord $cq->get ($table, $key, @fields); +} # testGetRecord + +sub testFindRecord ($$;@) { + my ($table, $condition, @fields) = @_; + + display "Testing find table: $table condition: $condition"; + + my ($result, $nbrRecs) = $cq->find ($table, $condition, @fields); + + display "$nbrRecs records qualified"; + + while (my %record = $cq->getNext ($result)) { + displayRecord %record; + } # while +} # testFindRecord + +sub testModifyRecord ($$;%) { + my ($table, $key, %update) = @_; + + display "Testing modify table: $table key: $key"; + + my $errmsg = $cq->modify ($table, $key, undef, %update); + + display $errmsg; +} # testModifyRecord + +sub testChangeState ($$) { + my ($table, $key) = @_; + + my %record = $cq->get ($table, $key, ('State')); + + my ($action, %update); + + if ($record{State} eq 'Assigned') { + $action = 'AdminAssignToSubmit'; + $update{Stability_Issue} = 'User Fault'; + } else { + $action = 'Assign'; + $update{Stability_Issue} = 'Assert'; + } # if + + display "Testing change state table: $table key: $key action: $action"; + + my $errmsg = $cq->modify ($table, $key, $action, %update); + + display $errmsg; +} # testChangeState + +sub testAddRecord ($%) { + my ($table, %record) = @_; + + display "Testing adding table: $table"; + + my $errmsg = $cq->add ($table, %record); + + display $errmsg; +} # testAddRecord + +sub testDeleteRecord ($$) { + my ($table, $key) = @_; + + display "Testing deleting table: $table key: $key"; + + my $errmsg = $cq->delete ($table, $key); + + display $errmsg; +} # testDeleteRecord + +my %opts; + +GetOptions ( + \%opts, + 'get', + 'add', + 'modify', + 'change', + 'delete' +) || Usage; + +# If nothing is set then do everything +unless ($opts{get} or + $opts{add} or + $opts{modify} or + $opts{change} or + $opts{delete} + ) { + $opts{get} = $opts{add} = $opts{modify} = $opts{change} = 1; +} # unless + +# If we are testing add or delete then toggle on the other one +$opts{delete} = 1 if $opts{add}; +$opts{add} = 1 if $opts{delete}; + +$cq = Clearquest::REST->new; + +if ($opts{get}) { + # Get record by key + testGetRecord 'Project', 'Athena'; + + # Get record by condition + testFindRecord 'VersionInfo', 'Deprecated = 1'; + + # Get record by key with field list + testFindRecord 'VersionInfo', 'VersionStr = 1.0', ('VersionStr', 'Deprecated'); + + # Get record by condition with field list + testFindRecord 'CategorySub', 'Category="Customer-HW"', ('Category', 'CategoryType', 'SubCategory'); +} # if + +if ($opts{add}) { + # Add a record + testAddRecord 'VersionInfo', ( + VersionStr => '2.0', + Projects => { + Project => ['Island', '21331', 'Hera'], + }, + Visibility => 'Nokia Corporation', + ); +} # if + +if ($opts{modify}) { + # Modify a record + testModifyRecord ('VersionInfo', '1.0', ( + Deprecated => 1, + Projects => { + Project => ['Island', 'Athena'], + }, + )); +} # if + +if ($opts{change}) { + # Change State + testChangeState 'Defect', 't_sbx00018584'; +} # if + +if ($opts{add}) { + # Delete that record + testDeleteRecord 'VersionInfo', '2.0'; +} # if + +display "done"; diff --git a/test/testrexec.pl b/test/testrexec.pl new file mode 100644 index 0000000..5f35bc3 --- /dev/null +++ b/test/testrexec.pl @@ -0,0 +1,42 @@ +#!/usr/bin/perl +use strict; +use warnings; + +use FindBin; + +use lib "$FindBin::Bin/../lib"; + +use Rexec; + +my ($status, $cmd, @output); + +my $hostname = $ENV{HOST} || 'localhost'; +my $username = $ENV{USERNAME}; +my $password = $ENV{PASSWORD}; + +my $remote = Rexec->new ( + host => $hostname, + username => $username, + password => $password, + timeout => 30, +); + +if ($remote) { + print "Connected to $username\@$hostname using " + . $remote->{protocol} . " protocol\n"; + + $cmd = "/bin/ls /nonexistent"; + + @output = $remote->execute ($cmd); + $status = $remote->status; + + print "$cmd status: $status\n"; + + $remote->print_lines; + + print "$_\n" foreach ($remote->execute ('cat /etc/passwd')); +} else { + print "Unable to connect to $username@$hostname\n"; +} # if + + diff --git a/test/testspreadsheet.pl b/test/testspreadsheet.pl new file mode 100644 index 0000000..1cb0d81 --- /dev/null +++ b/test/testspreadsheet.pl @@ -0,0 +1,95 @@ +#!/usr/bin/perl +use strict; +use warnings; + +=pod + +=head1 NAME $RCSfile: testspreadsheet.pl,v $ + +Test the SpreadSheet libary + +This script tests various functions of the SpreadSheet libary + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.1 $ + +=item Created: + +Mon Nov 12 16:50:44 PST 2012 + +=item Modified: + +$Date: 2012/11/21 02:53:28 $ + +=back + +=head1 SYNOPSIS + + Usage: testclearquest.pl [-u|sage] [-v|erbose] [-d|ebug] + -filename + + Where: + -usa|ge: Displays usage + -v|erbose: Be verbose + -de|bug: Output debug messages + + -filename: Spreadsheet file + +=cut + +use FindBin; +use Getopt::Long; + +use lib "$FindBin::Bin/../lib"; + +use SpreadSheet; +use Display; +use Utils; + +sub displayData (@) { + my (@rows) = @_; + + my $row = 2; + + foreach (@rows) { + my %row = %$_; + + display "Row: $row"; $row++; + + foreach (keys %row) { + my $value = $row{$_} || ''; + + display "$_: $value"; + } # foreach + } # foreach + + return; +} # displayRecord + +## Main +local $| = 1; + +my %opts; + +GetOptions ( + \%opts, + usage => sub { Usage }, + verbose => sub { set_verbose }, + debug => sub { set_debug }, + 'filename=s', +) || Usage; + +Usage "Must specify -filename " unless $opts{filename}; + +my $spreadSheet = SpreadSheet->new ($opts{filename}); + +displayData ($spreadSheet->getSheet); diff --git a/test/testspreadsheet.xls b/test/testspreadsheet.xls new file mode 100644 index 0000000000000000000000000000000000000000..c5e4eea95bcdeea234a926446f8db2a0880044e5 GIT binary patch literal 12763 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 + display MAGENTA . "Accessed by:\t\t" . RESET . $view->accessed_by; + display MAGENTA . "Accessed date:\t\t" . RESET . $view->accessed_date; + display MAGENTA . "Access path:\t\t" . RESET . $view->access_path; + display MAGENTA . "Active:\t\t\t" . RESET . $view->active; + + display_nolf MAGENTA . "Additional groups:\t"; + + foreach ($view->additional_groups) { + display_nolf "$_ "; + } # foreach + + display ""; + + display MAGENTA . "Created by:\t\t" . RESET . $view->created_by; + display MAGENTA . "Created date:\t\t" . RESET . $view->created_date; + display MAGENTA . "CS updated by:\t\t" . RESET . $view->cs_updated_by; + display MAGENTA . "CS updated date:\t" . RESET . $view->cs_updated_date; + display MAGENTA . "Global path:\t\t" . RESET . $view->gpath; + display MAGENTA . "Group:\t\t\t" . RESET . $view->group; + display MAGENTA . "Group mode:\t\t" . RESET . $view->group_mode; + display MAGENTA . "Host:\t\t\t" . RESET . $view->host; + display MAGENTA . "Mode:\t\t\t" . RESET . $view->mode; + display MAGENTA . "Modified by:\t\t" . RESET . $view->modified_by; + display MAGENTA . "Modified date:\t\t" . RESET . $view->modified_date; + display MAGENTA . "Other mode:\t\t" . RESET . $view->other_mode; + display MAGENTA . "Owner:\t\t\t" . RESET . $view->owner; + display MAGENTA . "Owner mode:\t\t" . RESET . $view->owner_mode; + display MAGENTA . "Properties:\t\t" . RESET . $view->properties; + display MAGENTA . "Region:\t\t\t" . RESET . $view->region; + display MAGENTA . "Server host:\t\t" . RESET . $view->shost; + display MAGENTA . "Text mode:\t\t" . RESET . $view->text_mode; + + display_nolf MAGENTA . "Type:\t\t\t" . RESET; + + if ($view->snapshot) { + display_nolf "snapshot"; + } else { + display_nolf "dynamic"; + } # if + + if ($view->ucm) { + display_nolf ",ucm"; + } # if + + display ""; + + display MAGENTA . "UUID:\t\t\t" . RESET . $view->uuid; +} # DisplayViewInfo + +error "Usage $0 ", 1 if !$ARGV[0]; + +foreach (@ARGV) { + my $view = new Clearcase::View (tag => $_); + + DisplayViewInfo $view; +} # foreach + diff --git a/test/testviews.pl b/test/testviews.pl new file mode 100755 index 0000000..eb1c2ff --- /dev/null +++ b/test/testviews.pl @@ -0,0 +1,32 @@ +#!/usr/bin/perl +use strict; +use warnings; + +use FindBin; +use Term::ANSIColor qw(:constants); + +my $libs; + +BEGIN { + $libs = $ENV{SITE_PERLLIB} ? $ENV{SITE_PERLLIB} : "$FindBin::Bin/../lib"; + + die "Unable to find libraries\n" if !$libs and !-d $libs; +} # BEGIN + +use lib $libs; + +use Clearcase; +use Clearcase::Views; +use Display; + +my $views = new Clearcase::Views; + +my $nbr_views = $views->views; +my @view_list = $views->views; + +display YELLOW . "Clearcase Views\n" . RESET; + +display MAGENTA . "Number of views:\t\t" . RESET . $nbr_views; +display MAGENTA . "View list:\n" . RESET; + +display "\t$_" foreach (@view_list); diff --git a/test/testvob.pl b/test/testvob.pl new file mode 100755 index 0000000..a44522a --- /dev/null +++ b/test/testvob.pl @@ -0,0 +1,65 @@ +#!/usr/bin/perl +use strict; +use warnings; + +use FindBin; +use Term::ANSIColor qw(:constants); + +my $libs; + +BEGIN { + $libs = $ENV{SITE_PERLLIB} ? $ENV{SITE_PERLLIB} : "$FindBin::Bin/../lib"; + + die "Unable to find libraries\n" if !$libs and !-d $libs; +} # BEGIN + +use lib $libs; + +use Clearcase; +use Clearcase::Vobs; +use Clearcase::Vob; +use Display; + +my $vobs = new Clearcase::Vobs; + +my @vob_list = $vobs->vobs; + +my $vob; +my $i = 0; + +$vob = new Clearcase::Vob (tag => $vob_list[$i++]); + +display YELLOW . "Clearcase VOB\n" . RESET; + +display MAGENTA . "Tag:\t\t" . RESET . $vob->tag; +display MAGENTA . "Global path:\t" . RESET . $vob->gpath; +display MAGENTA . "Sever host:\t" . RESET . $vob->shost; +display MAGENTA . "Access:\t\t" . RESET . $vob->access; +display MAGENTA . "Mount options:\t" . RESET . $vob->mopts; +display MAGENTA . "Region:\t\t" . RESET . $vob->region; +display MAGENTA . "Active:\t\t" . RESET . $vob->active; +display MAGENTA . "Replica UUID:\t" . RESET . $vob->replica_uuid; +display MAGENTA . "Host:\t\t" . RESET . $vob->host; +display MAGENTA . "Access path:\t" . RESET . $vob->access_path; +display MAGENTA . "Family UUID:\t" . RESET . $vob->family_uuid; + +display YELLOW . "\nVOB Statistics\n" . RESET; +display MAGENTA . "Elements:\t" . RESET . $vob->elements; +display MAGENTA . "Branches:\t" . RESET . $vob->branches; +display MAGENTA . "Versions:\t" . RESET . $vob->versions; +display MAGENTA . "DB Size:\t" . RESET . $vob->dbsize; +display MAGENTA . "Adm Size:\t" . RESET . $vob->admsize; +display MAGENTA . "CT Size:\t" . RESET . $vob->ctsize; +display MAGENTA . "DO Size:\t" . RESET . $vob->dbsize; +display MAGENTA . "Src Size:\t" . RESET . $vob->srcsize; +display MAGENTA . "Size:\t\t" . RESET . $vob->size; + +display YELLOW . "\nVOB manipulation\n" . RESET; + +display "Umounting " . $vob->tag . "..."; + +$vob->umount; + +display "Mounting " . $vob->tag . "..."; + +$vob->mount; diff --git a/test/testvobs.pl b/test/testvobs.pl new file mode 100755 index 0000000..f88ac9c --- /dev/null +++ b/test/testvobs.pl @@ -0,0 +1,40 @@ +#!/usr/bin/perl +use strict; +use warnings; + +use FindBin; +use Term::ANSIColor qw(:constants); + +my $libs; + +BEGIN { + $libs = $ENV{SITE_PERLLIB} ? $ENV{SITE_PERLLIB} : "$FindBin::Bin/../lib"; + + die "Unable to find libraries\n" if !$libs and !-d $libs; +} # BEGIN + +use lib $libs; + +use Clearcase; +use Clearcase::Vobs; +use Display; + +my $vobs = new Clearcase::Vobs; + +my $nbr_vobs = $vobs->vobs; +my @vob_list = $vobs->vobs; + +display YELLOW . "Clearcase VOBs\n" . RESET; + +display MAGENTA . "Number of vobs:\t\t" . RESET . $nbr_vobs; +display MAGENTA . "VOB list:\n" . RESET; + +display "\t$_" foreach (@vob_list); + +if ($vobs->umount) { + display "Unmounted all vobs"; +} # if + +if ($vobs->mount) { + display "Mounted all vobs"; +} # if diff --git a/web/.htaccess b/web/.htaccess new file mode 100644 index 0000000..4e25a32 --- /dev/null +++ b/web/.htaccess @@ -0,0 +1 @@ +php_value include_path /usr/local/psa/home/vhosts/clearscm.com/httpdocs/php diff --git a/web/Contract Addendum - Mindteck.doc b/web/Contract Addendum - Mindteck.doc new file mode 100644 index 0000000000000000000000000000000000000000..1329744976e33192bd79a35be5e483f626112f32 GIT binary patch literal 24064 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 Jw2Hs~`+sxsh)Vzf literal 0 HcmV?d00001 diff --git a/web/Contract Addendum.doc b/web/Contract Addendum.doc new file mode 100644 index 0000000000000000000000000000000000000000..ccee57fb4c879a5ae5b95c654bb5c7a7565c3f95 GIT binary patch literal 24064 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 literal 0 HcmV?d00001 diff --git a/web/Icons/Download.jpg b/web/Icons/Download.jpg new file mode 100644 index 0000000000000000000000000000000000000000..52f3a15021a846b41d5c8a5673c114f07f2010ab GIT binary patch literal 1152 zcmex=U zW@aX!`+*(+s;&jfGq4D<3Mm>ovIz$!vMUve7&T5@$f4}C@t|nX#SbdRNkvVZTw>x9 zl2WQ_>Kd9_CZ=ZQ7M51dF0O9w9-dyoA)#U65s^{JDXD4c8JStdC8cHM6_r)ZEv;?s z9i3g1CQq3GGAU*RJ2V zdF$b$$4{OPfBE|D`;VW$K>lK6V1{@LNJ2b@<}X2@znFm0!om*n7b88f2KE_o9%~}Y zXK;@p{B?_ghnW!=dCYb@_;NmzT=6_BY2Oc{j(d-L%_ig8B61)^{TFjN?on{%7cj zGrssmjTbjk+b{QBUQR=KHJ_>(XC5X>u}6= zI~K0D+~PdX(FS#fN!ib1Io@3Stz+!<>e6zvJ=?=Jz4#Jca_@bt;H$9vTxY(2yt%$Z zN#A6X4olQVA*K7ei+23nap&j^=H?dVds|KXTdy6V@x_Oq=xc+8d^3=_eE1*pob&7o0pEmAd}O zdYN0d9dmOZS+7{Uy;r$CX@TJ?!xVg zeyFqkV&?U4xBeJqMs*)$-Fjkc(R-W8C%KkSpY!&v@U};FFU}c%x@6vDa+dj!oWhmT z?%Ah( literal 0 HcmV?d00001 diff --git a/web/Icons/HomeSmall.gif b/web/Icons/HomeSmall.gif new file mode 100644 index 0000000000000000000000000000000000000000..ba6700bc526aaee20a118921ac2cd60461727bc2 GIT binary patch literal 1004 zcmZ?wbhEHb6k-rzXlDQcCTkmeWl2RHbxvnDS#Njas(O>E`gkLiG8wLFafTWx)+#x! zYDK;pWx;$4l|n0HM`w$I==kEK)SNB%8YEdJ%5%+Md%6u!8c{i%@?$+e(31*lS%P=j4duFc8oNB8rDQx?TMVezW4`}clG7&s#BYMh3 z;+(hSu>i3vA+k3^Wp6~t--%Z|R3>z)Sn_n8+_g-#I~ghuQk33RY0c?~KHs8vrA_H- zr^@9Xjl0tf@69r~HQ(lIi{6U`cF$KhzFy__VT<3VZ9$)Rg?!x~`R!nIOXrlP-q};; zt(viT-Tt-RdpAwrwQFC#y7<53c2jvL{j*|>GIb=LGEI8QAA*^N7!OSw7nTSsNup!ZBmI+7XmI_7ZL&DrrCng9c2sCntNzUro6Xe*~#meT~(Xb%x ziH9Mll!d{Lq^8TOGZz?0IBaN`ygK1}LSn#%GpB`28yH?bbnTUz!FqUxq46S)$*LZU z=2#{ldmuhnrA5QD$ffI?A=~x_#^guG1eBeYwQw>YQ<$n3BdV=L zA&a?ZMzymDh6m-Ws5Y8d$N}SV8f)`fU_4G^EnZ>=EE&cR3_vjwAq^I+p$RXDuo-Bu z0b`nW>O64zm-rt(uO4P+q+6{_cTfKldOFgtXXk!2x%jqxGM*Xl{y7}JetLI4r=OF9 xg|o$_)60)n!`#v2^zVz;n{Q8^tbTuaee+{@Gg|w-e}6aV^Ty8c_4v!6^+zE+X5;_> literal 0 HcmV?d00001 diff --git a/web/Icons/arrow_right.gif b/web/Icons/arrow_right.gif new file mode 100644 index 0000000000000000000000000000000000000000..e06edf6b7cc08a7f8621b14c3f1604f6ac7e1ff3 GIT binary patch literal 872 zcmZ?wbhEHbDEJ2YmR6QBFfeS|wyCSHtFpGTZ&KgBefvaY zM09m^Wn^STL_~nHqhK@yhEoVA{$ybU`9}vtfbxU`hbsdkhs=$H1&++YZ5&9O?{=95Nmo o794D5XVwVOaBw)(D!{BDBeCG&k#2tFD3=`t%5Lo(%uEc{0OP9_jsO4v literal 0 HcmV?d00001 diff --git a/web/Icons/orange_arrow_right.gif b/web/Icons/orange_arrow_right.gif new file mode 100644 index 0000000000000000000000000000000000000000..6eb6bcef3ca4b1f5bc0da9f42eac469dd484bf66 GIT binary patch literal 827 zcmZ?wbhEHbWMyDy_|Cv!XlVE!2+lE#g3%BdfgzyylZBCifssK6aNv*tayc9X Z8W%*Wkq}_81_1xF4gdfE literal 0 HcmV?d00001 diff --git a/web/Images/AndrewDeFaria.jpg b/web/Images/AndrewDeFaria.jpg new file mode 100644 index 0000000000000000000000000000000000000000..22e4e2610e8202da636e5ee8d719acba9a17ecf0 GIT binary patch literal 9532 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 literal 0 HcmV?d00001 diff --git a/web/Images/BMLeft.jpg b/web/Images/BMLeft.jpg new file mode 100644 index 0000000000000000000000000000000000000000..005627a5aedb88d3f56b4075d16109ff2490f967 GIT binary patch literal 33173 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 z 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

    yC!@0iQsD#EIDR` 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%0Ds?kmipzIz#p=Y@ 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

    E!QX0_OcafIqnL)9XhXmiya#>rPEV zoBY6;$WJcxw)OkN0ntd-F~u}2&pOKUt?SJuTu#?or32Uzf@-Wf+O+t?`*S4IwYS0T zw$3el8)CH3Jz8WDB22KR4}yHNRYjfm$u90erOK79QiOdq{*xp2f1r3px(a%=)jl3L zL%Tf{pd1j>`6C$qQgZxi1=f~hN&<>ur#ZnnV^K%l@}WN`ZdJKw?})VF!iom|e1ViL z^m^j`ZKGyWJZ-iSdXcP&%81<;!@nBvnu?yNK#CW(`tGx?L`H`GvkQWN1DLyIw-+2> zpCRI%R?Ln{I{uGYhdJJvbJ@}Dh5LKg7=u$YZjMYxdz#zp<5Z@qWGQp@SV`gZ3mI>C zo*q~nK_`*fJ4-GqoWp=pHjWkWWf7!*(|Ht6o;}hB?|0)tQxbt%r}FbRzlyxqus__XE=eVMh3z~ z=iEUgLxyQ2)rJm5X5|Rq86Pb@?y8{|w;AS>m-nwAvHqyv|3ERkeEllQZHQoVyowy^ z$OJ12FXx>-1iXFAj14g7TzN4;4v&i+P8LKmt0uFYq@8(7gO?o z5!&GfoKUh3WYV-v)OlWM5{RAaQXIF5BB0wcn;zuR(_k_5zRq z?p`T8Wg5ZKLLn)M6xBKF#PGcp3pxX>Y4N#@WTV1Xw#yKR=)dab>&v- z(Qxkzxlg=h-zrj&8{BWRw171vYPhuHI@8=Ho=xvN%^|EQ-}U{GF3BdxepcF-;Ha2b zzelSMDS_Ghb0a!;^UXwyGJaeK>$grjtGnxW?i^pEvD>8+161(5I5B66N|;TS;VN*s ze%PU|&9HyJiqEt8_J<1DCg!a_93&uie-)P-Gwa^Dzd=!bTF@wVQmF`b=73;im3cRW z6BoHYkMeE$k!!u>{v<2e zN;l-t&6p*S2ik-RLaeTp@+Hf$i*6K%nkmgveT!1(uIs2@`1ac%_F&wPx*@K%17W`R zQo%Wu931el9-YXeg2!0m-6dY-fAn3LzKVSkNlMbWuUMQUCp9VG8ADNkUnyj*S8-#l zTx1T?iaYl@B-1V2 z>tV3sPcOQtORDONF;1sD|U~*9p){(HU-Li$HSGvL>6-hA6(8w$j$)i`+@J z-*4}K7PlB8xlo+yiY&ONnhC{?@W8}2;_Ppo!AV;)sbw1a>kHm+5wvcOKoli6^%536 z{%r4oe(ztU_es7L1&{tUUL=!a3jWr9yo%GQFg*xkk_<<>`Bd;~v8w5@*TFS#%dZ;a zV@&IxULChxNlT**I%}tL2-qB&V^-x}at9e&?$5OQ@jGw#0|!iU0$yY*d;vfSlQJl+ z^ZoqtL3xT7BSjFR=Fxfqx>VJamx_dN%ArXfK{1TAwMrFTp1zTOO;yR*sO+8~yITfBPiHaJ`JO%V1w?{8vKdS68@xsETA+*_vbh z_RWjj)FVgE1muZZd>ZRG;Ch#5WuaA1CLH7<{8i-%>!5<$JB-YBzA{fkhj^QhkeiuB zPwdtDo5%=4g)UMH8MO%vk{^5Rdinasc(hhiZ*kRJX6ssYDCSSu6GxE=_*O0LeKqB# zwy8Wna2ehyWHd5{7jMZNFSu&O#hiw>>vKHJ@VbGY@_-_HK`Lb8(F ztwOuT`E0J&^r5=kSB+mHHNx0D4sUb%!R-Tc7Afr}(Zj4pg~WPl$Z}rvKD*Cw;LO=&^-|EPnI4ToqxpVcD6K4wFWyGWsBrmaXAJEa>~3u&KU zQ2^?ff#Y40$!cduP%xI2MIA0~Lx?Y}mi27MtC;N~AikBCLP@drih<`CWTli*0#yv+ z{mpxc!>Y4JtL+Y+4(>H5E`EZ7B0aT1`xXjsgqq^gg9QftB3ClW75NU`D1!)p=aP^6 z

    PiZls2t=es^u^w5^%=5-Jb2XYb#qqa{Mt$klyB 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 F|JgZTwL<^^ literal 0 HcmV?d00001 diff --git a/web/Images/orange_gradient.gif b/web/Images/orange_gradient.gif new file mode 100644 index 0000000000000000000000000000000000000000..e7f7fe3fa6bcf54f2ac167a97ac42079e6ed6bd9 GIT binary patch literal 855 zcmZ?wbh9u|WM&X$_|Cxa|Nf-^_h3r z98gAf6h+D+N>wTdLSR^@ihxSlTo9yG>WGS_A8@eryqW*cJ#+4R_ul{J>)Z9+0N*Hc zmH`L`z>8c!KLiNrFUkW)00d9~fSi_PXwi)6QE#}NnjY2N~2OSI-S9w(^)eCAqYZ2DRc~@vzc@zn=^|9 zhcjvV@?uRzGxMe+{XIa?z*?{dfd~K-5JEuuYLEsX5@hw$fB;PUzeaXM!O2#T8BQD` zY{fiJzK9@uM`2`o3xES2A0qZt+PVk=GPpC6NRy|*#55)vLceTCR>ZdzyX*;wz!Ne? ziYj@nJ%0;Co^pLBV38J>KnSvgf!9|OaLd?yX<{})0cKd=a@F}**VqU;0cC0y@nOUI zSG+umwgsZq?B7DBl5LQ6!}qFYIcQB*vs1bA^T*}~#7UKbc;ZGqm~_AsjuyvBj{Jwi zFy;tDQ}R~|L8N-#xP+?k8O*>*4Q^j-d@pgne&VtZyUg}R^N!lk;;NH6 z_ra%D=Z+Sb&s$a59X5KEKNiUtH#?Ae`1Cx7ya*-sApdpWXczUu`zU5dznkEiT?3y{ znl@|_NBomzFWek3*Sc_BY_;*xoh6Q@PlD+@$vb9cmt%I*_rHf!1tvBq7qx|zI`nKT z8DdG^=~ihX?kKf7mM}af`<;V<^D^$6Q+DH$$8z((1X*70qjM33i!~Blf9iCnhp1Tg zsLMBTcW3a@fn0;N&s^)<6vyTbw1pb_-a3@q$SL48)$SOtAKt}Ho0{#!AoM^rHN~&N z*H4Pa(|uH0%LFlw^H7PinH+*rU>;73njR$11#+QG)sk7z?@!=-I8t)4VqjIo!p3Eq z_ALL@{rfoz8f`1(MU5R3;hDL&2c41Kt`M^|+-i)Qb0*n}hOFtQK2Aw-?M*V>+bj2U zE|hAo|NJ!Sx#3xFbxTp0F#!QpwfQ$7Mc%_D@O9luSsgOoGpJo;VYJ>>RGR9;xkuMUr7Fx)eE=Zh00s zuU_nGwDVJDA%0&)LkdSqlWQ7<=F%nIotg+NES2 z-l=2m0YeP8OFG|InC-iKWck>S51an zooY^#6m09;>htQi(zMz}5nJS?vW~7aaIO9GkhbhZ2j93LsFz4qdxFhXREsbrqxj&y zp)g&0v=6={vCiycrGo8egQ?0}pDe~Qm|p#3y>16t0jbK4=dkY)CSM)Dl4%(23_Msb zLqC3p;;cl%$Zxs(`Yvyl8Vy`hOm8*=Y>`WhPr^-juo+KctjreL;DfQsWUYmz#jo*U zWp=j$?T?u@aB;N3n{r&7K3txQC(wWm;Kg2WiMMh=4J&p2uOZ%CN!iH5hgMQ_NDxK! z9@7vJe9>`LVQ>Ao*K&%Oo1nN_baGY|C+O4_N~9rjX~RAzW*%#S{iTK+8eN*zO>0#) zWo#e$5vV&?q@>&bTS#kx4W9bZxePtYM8#NxFF

    L87y$g& z0Fa`Vp8yuXeFThIFdn>0e;x~=WU7U;UZ^V01;xt8fQSEn4S-N8*}^#>tRmsqxmi)i&p6d^)z81P5*Vrm zVG9GnI1>0vh^fGeDuH}VZ}waTrVD*Gw>$(CVJs4bQ7n;#FZ!$XA|R#(M5q#TK|=uB zgp=iC%XJx5XN3D$)@8}V>g+~+#8{Z553ZB{g{iT6umW%%J{%VgvST4UwE3ywyLpMx zDFY9>`DTtJI_v=wY@il%q-JDOTx@)*dH^$O>F5U`Zbdky`@x1kvvD}0BMw@nKba7O zvD~JTvd*qerjBLNeQ!;A{lA$e!A>?z_6e_N%0RyNSB%ZoGp30csf<@a{EaL_YR*={lV2JPtR6@Of*YRP+>k5dVk2IM;xzhZhGV&< zdqBdte>>)U-I2t#@$aAa;N8t;Y_0Rp^6tj&%t(t+{ljS>B`^Dc;iJ0pviq*J_JXp} zQOddun;uu47VVTV8=p0??aDX5W(M|LBfc{{7A}eR)L+wd-7*qayJBSzma!vXrMKWu&Zjmd5Syxj6Atr|6C;?U};xjaE;8mjtjYt7uVsqZ{an33(5xQYYVw z3`W@-PPTM{uIhAuQDSyY^`i8OpN96_#$i}b=r0mX&v}jCa+CIRD*w$4`VpU_u*Kwi zSH;OK>71d7!^4N~uoO%?FX1C?9MPbzZd5=TUo@X=k(eNTSiVYrMRAGkFMH2SmlvOnt%UTc@)RZ8 zr9N{Wb%1;I8_(axPQKpmK^%7rr{v-1o@g;5I&i&h4Ks*hoPW2{6c>&R)EKMRy14gv z@95I_$(dCTTeS(P;wCZEzC39YRm(9&IT>!EXJW zs3l3VzNVs2#Go|um6xB;msPL5W2pn#XmO3FWLD06$JSBgZ&=fqB#1k7xoOs8;qu`( z_uRHuvXiPOlK)JwiL4@5Q;gaT`ucd1tVfhX!RlVx$us<-b6P=~9=ogVbQgB(wQ~G> zQp%mf4_XEL$m_T4Iv&ShojA2~J0G3jdN`$^|9x1v{mtVb`%YVSfN&qJX@B#2M%ISn zIPDo3MW{}|KQ10Qm9WiYL+>MMEiQ8*d)(XEHEgQ$;SI~XVhTl}D$HiJP&z zr{@!zZ+d2FlZJ(Dz*URrL$Z^S%br6XojPg*JRVvF52~(}49O_sZ#21NvTJk9(c!W3 zUGb;a$})eVeeo;?8oBUuTq=#`lBAkfK^7%QgiFmRjShWIMis~c6~Pg@=sgI>Or~k7mydevS-rr6wi7s 0) { + x4 = "sub"; + } // if + + var c_horizontal = true; + + if (level == 0) { + if ((ob1 = obj.getElementsByTagName ("LI") [0]) && + (ob1.style.width.indexOf ("100%") + 1)) { + c_horizontal=false; + } // if + } // if + + var a = obj.parentNode.getElementsByTagName ("UL") [0]; + var id = a.id.substring (a.id.indexOf ("_") + 1); + + x3 = document.createElement ("DIV"); + x3.id = "pi" + a.id; + x3.style.position = "absolute"; + x3.style.visibility = "hidden"; + x3.style.fontSize = "0px"; + x3.style.lineHeight = "0px"; + x3.style.zIndex = 999; + + x3.setAttribute ("ispointer", 1); + x3.setAttribute ("scxy", "0,0"); + x3.setAttribute ("offxy", "0,0"); + + if ((level == 0) && (c_horizontal)) { + x3.setAttribute ("ish", 1); + x3.setAttribute ("fxoff", x25 (dto.main_pointer_image_offy)); + x3.setAttribute ("sloff", x25 (dto.main_pointer_image_offx)); + } else { + x3.setAttribute ("fxoff", x25 (x27_pointer (x4 + "_pointer_image_offx", dto, id))); + x3.setAttribute ("sloff", x25 (x27_pointer (x4 + "_pointer_image_offy", dto, id))); + } // if + + wht = ""; + + if ((tval = x27_pointer (x4 + "_pointer_image_width", dto, id))) { + wht += "width='" + tval + "'"; + } // if + + if ((tval = x27_pointer (x4 + "_pointer_image_height", dto, id))) { + wht += "height='" + tval + "'"; + } // if + + x5 = x27_pointer (x4 + "_pointer_image", dto, id); + + if (!x5 || x5.toLowerCase () == "none") { + obj.setAttribute ("noimage", 1); + } else { + obj.removeAttribute ("noimage"); + } // if + + var dexist = false; + var dobj = obj.childNodes; + + for (var d=0; d'; + obj.appendChild (x3); + } // if + + obj.onmousemove = function (e) { + e = e || window.event; + + var x32 = this; + + if (this.tagName == "DIV") { + x32 = this.getElementsByTagName ("UL") [0]; + } // if + + if ((x32.className.indexOf ("imncc") + 1) || + (x32.parentNode.className.indexOf ("imncc") + 1) || + this.getAttribute("noimage")) { + imenus_hide_pointer(); + + if (!x32.id || x32.id.indexOf ("imenus") == -1) { + im_kille (e); + } // if + + return false; + + } // if + + var lc = this.lastChild; + var bid; + + if (!lc.getAttribute ("ispointer")) { + bid = this.getElementsByTagName ("UL") [0].id; + lc = document.getElementById ("pi"+bid); + } // if + + if (!lc.getAttribute ("initialized")) { + imenus_initialize_pointer (this,lc); + } // if + + offxy = eval ("new Array(" + lc.getAttribute ("offxy") + ")"); + sloff = parseInt (lc.getAttribute ("sloff")); + scxy = eval ("new Array(" + lc.getAttribute ("scxy") + ")"); + + if (lc.getAttribute ("ish")) { + npos = e.clientX - offxy [0] + sloff + scxy [0]; + + if (window.dp_zoomc) { + npos=dp_zoomc(npos); + } // if + + setTimeout ("imenus_pointer_move('" + lc.id + "'," + npos + ",'h')", 0); + } else { + npos = e.clientY - offxy [1] + sloff + scxy [1]; + + if (window.dp_zoomc) { + npos=dp_zoomc(npos); + } // if + + setTimeout ("imenus_pointer_move('" + lc.id + "'," + npos + ")", 0); + } // if + + var a; + + if (a = window.imenus_event_mc_onmousemove) { + a (); + } // if + + im_kille (e); + + return false; + }; +} // imenus_add_pointer_image + +function imenus_pointer_move (id,npos,type) { + var md = document.getElementById (id); + + if(type == "h") { + md.style.left = npos + "px"; + } else { + md.style.top = npos + "px"; + } // if + + if (md.getAttribute ("initialized")) { + md.style.visibility = "inherit"; + } // if +} // imenus_pointer_move + +function x25 (val) { + if (val == null) { + return 0; + } else { + return val; + } // if +} // x26 + +function imenus_hide_pointer (check) { + if (ulm_last_pointer && ulm_last_pointer.parentNode != check) { + ulm_last_pointer.style.visibility = "hidden"; + ulm_last_pointer.removeAttribute("initialized"); + } // if +} imenus_hide_pointer + +function imenus_initialize_pointer (obj, lc) { + imenus_hide_pointer (); + + ulm_last_pointer = lc; + + var txy = x26 (obj); + + if (hpi = document.getElementById ("hpi_pad")) { + if (a = hpi.scrollLeft) { + txy [0] -= a; + } // if + + if (a = hpi.scrollTop) { + txy [1] -=a; + } // if + } // if + + lc.setAttribute ("offxy", txy); + + var pxy = parseInt (lc.getAttribute ("fxoff")); + + if (lc.getAttribute ("ish")) { + lc.style.top = pxy + "px"; + } else { + lc.style.left = pxy + "px"; + } // if + + pobj = document.body; + + if ((!(pobj.scrollLeft+pobj.scrollTop)) && + (document.documentElement)) { + pobj=document.documentElement; + } // if + + lc.setAttribute ("scxy", pobj.scrollLeft + "," + pobj.scrollTop); + lc.setAttribute ("initialized", 1); +} // imenus_initialize_pointer + +function x27_pointer (pname, dto, index) { + if ((rval = dto [pname + index]) != null) { + return rval; + } else { + return dto [pname]; + } // if +} // x27_pointer + +function imenus_box_ani_init (obj, dto) { + var tid = obj.getElementsByTagName ("UL") [0].id.substring (6); + + if (!(ulm_navigator && ulm_mac) && + !(window.opera && ulm_mac) && + !(window.navigator.userAgent.indexOf("afari")+1) && + !ulm_iemac&&dto.box_animation_frames > 0 && + !dto.box_animation_disabled) { + ulm_boxa ["go" + tid] = true; + ulm_boxa.go = true; + ulm_boxa.all = new Object (); + }else { + return; + } // if +} // imenus_box_ani_init + +function imenus_box_ani (show, tul, hobj, e) { + if (tul.className.indexOf ("imcanvassubc") + 1) { + hover_handle (hobj); + return; + } // + + if (!ulm_boxa.cm) { + ulm_boxa.cm = new Object (); + } // if + + if (!ulm_boxa ["ba" + hobj.id]) { + ulm_boxa ["ba" + hobj.id] = new Object (); + } // if + + ulm_boxa ["ba" + hobj.id].hobj = hobj; + + var bo = ulm_boxa ["ba" + hobj.id]; + + bo.id = "ba" + hobj.id; + + if (!bo.bdiv) { + bdiv = document.createElement ("DIV"); + bdiv.className = "ulmba"; + bdiv.onmousemove = function (e) { + if (!e) { + e=event; + } // if + + e.cancelBubble = true; + }; + bdiv.onmouseover = function (e) { + if (!e) { + e = event; + } // if + e.cancelBubble = true; + }; + bdiv.onmouseout = function (e) { + if (!e) { + e=event; + } // if + + e.cancelBubble = true; + }; + bo.bdiv = tul.parentNode.appendChild (bdiv); + } // if + + var i; + + for (i in ulm_boxa) { + if ((ulm_boxa [i].steps) && !(ulm_boxa [i].id.indexOf (hobj.id) + 1)) { + ulm_boxa [i].reverse = true; + } // if + } // for + + if (((hobj.className.indexOf ("ishow") + 1) && bo.hobj) || + (bo.bdiv.style.visibility == "visible" && !bo.reverse)) { + return true; + } // if + + imenus_box_show (bo, hobj, tul, e); +} // imenus_box_ani + +function imenus_box_h (hobj) { + if (hobj.className.indexOf ("imctitleli") + 1) { + return; + } // if + + var bo = ulm_boxa ["ba" + hobj.id]; + + if (bo && bo.bdiv && bo.pos) { + bo.reverse = true; + bo.pos = bo.steps; + bo.bdiv.style.visibility = "visible"; + imenus_box_x44 (bo); + } // if +} // imenus_box_x44 + +function imenus_box_reverse (x17) { + if (!ulm_boxa.go) { + return; + } // if + + var i; + + for (i in ulm_boxa.all) { + if (ulm_boxa.all [i] && ulm_boxa [i].hobj != x17) { + var bo = ulm_boxa [i]; + + bo.reverse = true; + ulm_boxa.all [i] = null; + } // if + } // for +} // imenus_box_reverse + +function imenus_box_show (bo, hobj, tul, e) { + var type; + var tdto = ulm_boxa ["dto" + parseInt (hobj.id.substring (6))]; + + clearTimeout (bo.st); + + bo.st = null; + + if (bo.bdiv) { + bo.bdiv.style.visibility = "hidden"; + } // if + + bo.pos = 0; + bo.reverse = false; + bo.steps = tdto.box_animation_frames; + bo.exy = new Array (tul.offsetLeft, tul.offsetTop); + bo.ewh = new Array (tul.offsetWidth, tul.offsetHeight); + bo.sxy = new Array (0, 0); + + if (!(type = tul.getAttribute ("boxatype"))) { + type = tdto.box_animation_type; + } // if + + if (type == "center") { + bo.sxy = new Array (bo.exy [0] + parseInt (bo.ewh [0] / 2), + bo.exy [1] + parseInt (bo.ewh [1] / 2)); + } else if (type == "top") { + bo.sxy = new Array (parseInt (bo.ewh [0] / 2), 0); + } else if (type == "left") { + bo.sxy = new Array (0, parseInt (bo.ewh [1] / 2)); + } else if (type=="pointer") { + if (!e) { + e = window.event; + } // if + + var txy = x26 (tul); + + bo.sxy = new Array (e.clientX - txy [0], (e.clientY - txy [1]) + 5); + } // if + + bo.dxy = new Array (bo.exy [0] - bo.sxy [0], bo.exy [1] - bo.sxy [1]); + bo.dwh = new Array (bo.ewh [0], bo.ewh [1]); + bo.tul = tul; + bo.hobj = hobj; + + imenus_box_x44(bo); +} // imenus_box_show + +function imenus_box_x44 (bo) { + var a = bo.bdiv; + var cx = bo.sxy [0] + parseInt ((bo.dxy [0] / bo.steps) * bo.pos); + var cy = bo.sxy [1] + parseInt ((bo.dxy [1] / bo.steps) * bo.pos); + + a.style.left = cx + "px"; + a.style.top = cy + "px"; + + var cw = parseInt ((bo.dwh [0] / bo.steps) * bo.pos); + var ch = parseInt ((bo.dwh [1] / bo.steps) * bo.pos); + + a.style.width = cw + "px"; + a.style.height = ch + "px"; + + if (bo.pos <= bo.steps) { + if (bo.pos == 0) { + a.style.visibility="visible"; + } // if + + if (bo.reverse == true) { + bo.pos--; + } else { + bo.pos++; + } // if + + if (bo.pos == -1) { + bo.pos = 0; + a.style.visibility = "hidden"; + } else { + bo.st = setTimeout ("imenus_box_x44(ulm_boxa['" + bo.id + "'])", 8); + ulm_boxa.all[bo.id]=true; + } // if + } else { + clearTimeout (bo.st); + + bo.st = null; + ulm_boxa.all [bo.id] = null; + + if (!bo.reverse) { + if ((bo.hobj) && (bo.pos > -1)) { + hover_handle (bo.hobj); + } // if + } + + a.style.visibility = "hidden"; + } // if +} // imenus_box_x44 + +function iao_iframefix () { + if (ulm_ie && !ulm_mac && !ulm_oldie && !ulm_ie7) { + for (var i = 0;i < (x31 = uld.getElementsByTagName ("iframe")).length; i++) { + if ((a = x31 [i]).getAttribute ("x30")) { + a.style.height = (x32 = a.parentNode.getElementsByTagName ("UL") [0]).offsetHeight; + a.style.width = x32.offsetWidth; + } // if + } // for + } // if +} // iao_iframefix + +function iao_ifix_add (b) { + if (ulm_ie && + !ulm_mac && + !ulm_oldie && + !ulm_ie7 && + window.name != "hta" && + window.name != "imopenmenu") { + b.parentNode.insertAdjacentHTML("afterBegin","

    "); + } // if +} // iao_ifix_add + +// ---- IM Code + Security [7.5 KB] ---- +im_version = "10.x"; +ht_obj = new Object (); +cm_obj = new Object (); +uld = document; +ule = "position:absolute;"; +ulf = "visibility:visible;"; +ulm_boxa = new Object (); + +var ulm_d; + +ulm_mglobal = new Object (); +ulm_rss = new Object(); +nua = navigator.userAgent; +ulm_ie = window.showHelp; +ulm_ie7 = nua.indexOf ("MSIE 7") + 1; +ulm_mac = nua.indexOf ("Mac") + 1; +ulm_navigator = nua.indexOf ("Netscape") + 1; +ulm_version = parseFloat (navigator.vendorSub); +ulm_oldnav = ulm_navigator && ulm_version < 7.1; +ulm_oldie = ulm_ie && nua.indexOf("MSIE 5.0") + 1; +ulm_iemac = ulm_ie && ulm_mac; +ulm_opera = nua.indexOf ("Opera") + 1; +ulm_safari = nua.indexOf ("afari") + 1; +x42 = "_"; +ulm_curs = "cursor:hand;"; + +if (!ulm_ie) { + x42 = "z"; + ulm_curs = "cursor:pointer;"; +} // if + +ulmpi = window.imenus_add_pointer_image; + +var x43; + +for (mi = 0; mi < (x1 = uld.getElementsByTagName ("UL")).length; mi++) { + if ((x2 = x1 [mi].id) && x2.indexOf ("imenus") + 1) { + dto = new window ["imenus_data" + (x2 = x2.substring (6))]; + ulm_boxa.dto=dto; + ulm_boxa["dto"+x2]=dto; + ulm_d=dto.menu_showhide_delay; + + if (ulm_ie && !ulm_ie7 && !ulm_mac && (b = window.imenus_efix)) { + b (x2); + } // if + + imenus_create_menu (x1 [mi].childNodes, x2 + x42, dto, x2); + (ap1 = x1 [mi].parentNode).id = "imouter" + x2; + ulm_mglobal ["imde" + x2] = ap1; + + var dt = "onmouseover"; + + if (ulm_mglobal.activate_onclick) { + dt = "onclick"; + } // if + + document [dt] = function () { + var a; + + if (!ht_obj.doc) { + clearTimeout (ht_obj.doc); + + ht_obj.doc = null; + } else { + return; + } // if + + ht_obj.doc = setTimeout ("im_hide()", ulm_d); + + if (a = window.imenus_box_reverse) { + a (); + } // if + + if (a = window.imenus_expandani_hideall) { + a (); + } // if + + if (a = window.imenus_hide_pointer) { + a (); + } // if + + if (a = window.imenus_shift_hide_all) { + a (); + } // if + }; + + imarc ("imde", ap1); + + if (ulm_oldnav) { + ap1.parentNode.style.position="static"; + } // if + + if (!ulm_oldnav&&ulmpi) { + ulmpi (x1 [mi], dto, 0, x2); + } // if + + x6 (x2, dto); + + if ((ulm_ie && !ulm_iemac) && (b1 = window.iao_iframefix)) { + window.attachEvent ("onload", b1); + } // if + + if (b1 = window.imenus_box_ani_init) { + b1 (ap1, dto); + } // if + + if (b1 = window.imenus_expandani_init) { + b1 (ap1, dto); + } // if + + if (b1 = window.imenus_info_addmsg) { + b1 (x2, dto); + } // if + + if (b1 = window.im_conexp_init) { + b1 (dto, ap1, x2); + } // if + } // if +} // for + +function imenus_create_menu (nodes, prefix, dto, d_toid, sid, level) { + var counter = 0; + + if (sid) { + counter=sid; + } // sid + + for (var li = 0; li < nodes.length; li++) { + var a = nodes [li]; + var c; + + if (a.tagName == "LI") { + a.id = "ulitem" + prefix + counter; + + (this.atag = a.getElementsByTagName ("A") [0]).id = "ulaitem" + prefix + counter; + + if (c = this.atag.getAttribute ("himg")) { + ulm_mglobal ["timg" + a.id] = new Image (); + ulm_mglobal ["timg" + a.id].src = c; + } // if + + var level; + + a.level = (level = prefix.split (x42).length - 1); + a.dto = d_toid; + a.x4 = prefix; + a.sid = counter; + + if ((a1 = window.imenus_drag_evts) && level > 1) { + a1 (a, dto); + } // if + + a.onkeydown = function (e) { + e = e || window.event; + + if (e.keyCode == 13 && !ulm_boxa.go) { + hover_handle (this,1); + } // if + }; + + if (dto.hide_focus_box) { + this.atag.onfocus = function () { + this.blur () + }; + } // if + + imenus_se (a,dto); + + this.isb = false; + x29 =a .getElementsByTagName ("UL"); + + for (ti = 0; ti < x29.length; ti++) { + var b = x29 [ti]; + + if (c = window.iao_ifix_add) { + c (b); + } // if + + var wgc; + + if (wgc = window.getComputedStyle) { + if (wgc (b.parentNode, "").getPropertyValue ("visibility") == "visible") { + cm_obj [a.id] = a; + + imarc ("ishow", a, 1); + } // if + } else if (ulm_ie && b.parentNode.currentStyle.visibility == "visible") { + cm_obj [a.id] = a; + + imarc ("ishow", a, 1); + } // if + + if ((dd = this.atag.firstChild) && + (dd.tagName == "SPAN") && + (dd.className.indexOf ("imea") + 1)) { + this.isb=true; + + if (ulm_mglobal.eimg_fix) { + imenus_efix_add (level,dd); + } // if + + dd.className = dd.className + "j"; + dd.firstChild.id = "ea" + a.id; + + dd.setAttribute ("imexpandarrow", 1); + } // if + + b.id = "x1ub" + prefix + counter; + + if (!ulm_oldnav && ulmpi) { + ulmpi (b.parentNode, dto, level); + } // if + + new imenus_create_menu (b.childNodes, prefix + counter + x42, dto, d_toid); + } // for + + if ((a1 = window.imenus_button_add) && level == 1) { + a1 (this.atag, dto); + } // if + + if (this.isb && ulm_ie && level == 1 && document.getElementById ("ssimaw")) { + if (a1 = window.imenus_autowidth) { + a1(this.atag,counter); + } // if + } // if + + if (!sid && + !ulm_navigator && + !ulm_iemac && + (rssurl = a.getAttribute ("rssfeed")) && + (c=window.imenus_get_rss_data)) { + c (a,rssurl); + } // if + + counter++; + } // if + } // for +} //imenus_create_menu + +function imenus_se (a, dto) { + var d; + + if (!(d = window.imenus_onclick_events) || + !d (a, dto)) { + a.onmouseover = function (e) { + var a, b, at; + + clearTimeout (ht_obj.doc); + + ht_obj.doc = null; + + if (((at = this.getElementsByTagName ("A") [0]).className.indexOf ("iactive") == -1) && + at.className.indexOf ("imsubtitle") == -1) { + imarc ("ihover", at, 1); + } // if + + if (b = at.getAttribute ("himg")) { + if (!at.getAttribute ("zhimg")) { + at.setAttribute ("zhimg", at.style.backgroundImage); + } // if + + at.style.backgroundImage = "url(" + b + ")"; + } // if + + if (b = window.imenus_shift) { + b (at); + } // if + + if (b = window.imenus_expandani_animateit) { + b (this); + } // if + + if ((ulm_boxa ["go" + parseInt (this.id.substring (6))]) && + (a = this.getElementsByTagName ("UL") [0])) { + imenus_box_ani (true, a, this, e); + } else { + if (this.className.indexOf ("ishow") == -1) { + ht_obj [this.level] = setTimeout ("hover_handle(uld.getElementById('" + this.id + "'))", ulm_d); + } // if + + if (a = window.imenus_box_reverse) { + a (this); + } // if + } // if + + if (a = window.im_conexp_show) { + a(this); + } // if + + if (!window.imenus_chover) { + im_kille (e); + return false; + } // if + }; + + a.onmouseout = function (e) { + var a, b; + + if((a = this.getElementsByTagName ("A") [0]).className.indexOf ("iactive") == -1) { + imarc ("ihover", a); + imarc ("iactive", a); + } // if + + if (this.className.indexOf ("ishow") == -1 && (b = a.getAttribute ("zhimg"))) { + a.style.backgroundImage = b; + } // if + + clearTimeout (ht_obj [this.level]); + + if (!window.imenus_chover) { + im_kille (e); + return false; + } // if + }; + } // +} // imenus_se + +function im_hide (hobj) { + for (i in cm_obj) { + var tco = cm_obj [i]; + var b; + + if (tco) { + if (hobj && hobj.id.indexOf (tco.id) + 1) { + continue; + } // if + + imarc ("ishow", tco); + + var at = tco.getElementsByTagName ("A") [0]; + + imarc ("ihover", at); + imarc ("iactive", at); + + if (b = at.getAttribute ("zhimg")) { + at.style.backgroundImage = b; + } // if + + cm_obj [i] = null; + i++; + + if (ulm_boxa ["go" + parseInt (tco.id.substring (6))]) { + imenus_box_h (tco); + } // if + + var a; + + if (a = window.imenus_expandani_hideit) { + a (tco); + } // if + + if (a = window.imenus_shift_hide) { + a (at); + } // if + } // if + } // for +} // im_hide + +function hover_handle (hobj) { + im_hide (hobj); + + var tul; + + if (tul = hobj.getElementsByTagName ("UL") [0]) { + try { + if ((ulm_ie && !ulm_mac) && + (plobj = tul.filters [0]) && + tul.parentNode.currentStyle.visibility=="hidden") { + if (x43) { + x43.stop (); + } // if + + plobj.apply (); + plobj.play (); + + x43 = plobj; + } // if + } catch (e) { + } + + var a; + + if(a = window.imenus_stack_init) { + a (tul); + } // if + + if (a = window.iao_apos) { + a (tul); + } // if + + var at = hobj.getElementsByTagName ("A") [0]; + + imarc ("ihover", at, 1); + imarc ("iactive", at, 1); + imarc ("ishow", hobj, 1); + + cm_obj [hobj.id] = hobj; + + if (a = window.imenus_stack_ani) { + a (tul); + } // if + } // if +} // hover_handle + +function imarc (name, obj, add) { + if (add) { + if (obj.className.indexOf (name) == -1) { + obj.className += (obj.className ? ' ' : '') + name; + } // if + } else { + obj.className = obj.className.replace (" " + name, ""); + obj.className = obj.className.replace (name, ""); + } // if +} // imarc + +function x26 (obj) { + var x = 0; + var y = 0; + + do { + x += obj.offsetLeft; + y += obj.offsetTop; + } while (obj = obj.offsetParent) { + return new Array (x,y); + } // while +} // x26 + +function im_kille (e) { + if (!e) { + e=event; + } // if + + e.cancelBubble = true; + + if (e.stopPropagation) { + e.stopPropagation (); + } // if +} // im_kille + +function x6 (id,dto) { + x18 = "#imenus" + id; + sd = ""; + sd += ""; + + uld.write (sd); +} // x6 + diff --git a/web/JavaScript/common.js b/web/JavaScript/common.js new file mode 100644 index 0000000..af11895 --- /dev/null +++ b/web/JavaScript/common.js @@ -0,0 +1,67 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// File: common.js +// Description: Common Javascript functions +// Author: Andrew@DeFaria.com +// Created: Thu Oct 6 14:16:05 PDT 2011 +// Language: javascript +// +//////////////////////////////////////////////////////////////////////////////// +function basename (path) { + return path.replace (/\\/g, '/').replace (/.*\//, ''); +} // basename + +function dirname (path) { + return path.replace (/\\/g, '/').replace (/\/[^\/]*$/, ''); +} // dirname + +function getText (item) { + // There's this annoying thing about getting the text of an HTML object - both + // Chrome and IE use innerText but Firefox uses textContent. + return item.innerText ? item.innerText : item.textContent; +} // getText + +function getVar (variable) { + var query = window.location.search.substring (1); + + var vars = query.split ('&'); + + for (var i=0; i < vars.length; i++) { + var pair = vars[i].split ('='); + + if (pair[0] == variable) { + return pair[1]; + } // if + } // for + + return null; +} // getVar + +function keys (obj) { + // keys: Emulate Perl's keys function. Note that hashes in Javascript are + // implemented as associative arrays, which are really objects. There is no + // keys function and as an Objects there are functions in there. So we use the + // hasOwnProperty function to insure that this is a pproperty and not a + // method. + var keys = []; + + for (key in obj) { + if (obj.hasOwnProperty (key)) keys.push (key); + } // for + + return keys; +} // keys + +function objLength (object) { + // The .length property doesn't exist for JavaScript objects and associative + // arrays. You need to count the properties instead. + var count = 0; + + for (property in object) { + if (object.hasOwnProperty (property)) { + count++; + } // if + } // for + + return count; +} // objLength \ No newline at end of file diff --git a/web/Logos/Ameriquest.gif b/web/Logos/Ameriquest.gif new file mode 100644 index 0000000000000000000000000000000000000000..7b94f478b40259f03e017fc6270a00b76dabc0ce GIT binary patch literal 3274 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 MS6)F(eN*56KLQt*Q2+n{ literal 0 HcmV?d00001 diff --git a/web/Logos/Cisco.gif b/web/Logos/Cisco.gif new file mode 100644 index 0000000000000000000000000000000000000000..0a20045cd66d29f7a344e7c09b27085ca0326879 GIT binary patch literal 1968 zcmeH`jXTqM0KmUL8@4P*(UiAcJ(bsc)~&o3DtW2AJ{~>8HDtDVIr5T1t>!q=@ z)OB-0jheBmm!g<=gE`RJHL;g4esH?AP8fw>Co8zwr8S+2lRsy>X9en72{6$j*mdTq zeD>nSPOA3&XM2N|Xi>{2rddB<;on)*8%T7T{NVqh%5U#<7Tsay)oGfj_(?`oP)GSM zs@bv7@Z;}9+Ro|>Uda0d?g(wotRp-rwjhWGOIQiLffq31L-gX_NRS@Xh)w~Zb$7nP-_G4agx$)oM87N!OUo++gd9s5 z<4Q4E_JCAfdxT-kTad95#g0VQ)mLPBnZ`(-ygakKHl~8Au^q$Co^~Wv{wTUmdRS21 zMye_-+Nxoy&mJIhQhgU|IB^rTmP`iz0Hw5ITS6``r_3$XR#|Z=)vEexrhaYNL#9P_ zYti;1t8F5m^pqFaUdIxCi?6S1w!+6Y-1(+1wyDD@qmJL9tHb4YDzC>0e!|Ccd3}_- z@jTJ#XO^vFt8KPzQ_N6V`~0>~Y=>fdVWo8Tk7wN9b|#iqIa{K^3X97j+!?Zq}6!@pk(zBbJk?B0MWe_ zu82XA%+D#|{LKK}9LjmZyMuCfmU{vwaf!9Z-Ak-Fy$ZxZKprP>VsWID-0ptmqhg2V z()jxYG`xA`z7|EQA0^6~xxpUiuODWU#h;ALC4y!%;=N49(4!;v5{d5(C7q4pSa^C5 M1Tu^05Cok61-hYOrvLx| literal 0 HcmV?d00001 diff --git a/web/Logos/ClearSCM.jpg b/web/Logos/ClearSCM.jpg new file mode 100644 index 0000000000000000000000000000000000000000..df7174a5ceca4e9e54d5ccde20cedbbae0b7d351 GIT binary patch literal 11203 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 literal 0 HcmV?d00001 diff --git a/web/Logos/HPLogo.gif b/web/Logos/HPLogo.gif new file mode 100644 index 0000000000000000000000000000000000000000..fc890a8e626e4022ba1f2199074518dd29ebcaef GIT binary patch literal 997 zcmZ?wbhEHboWr2Ru#^D=SQw(17@7q*{^cZ}<>$Dnq4+<#E{Lv0MvL_M4;K*Yj*dt ze{2j}Obol(7`#{*ivR!r&%oft!SGL5;4A}U6dS`D7KW?U)knehQ$#5KWMO0gdPN6h z3dm0mZ2uf47I^4L^`BT$bTY?k{)LQl^Y^#Fa;fz%t ztXv{;b0SV`P;|~^l=PV6QMiafSXt?bw#SA9Yuo7;)-vzZTB2RZr8PxE&}ZHpMP1F% zfCDXecV?|DT2wTZ@BBXxNBhnx3Wf(78=3YkU~Rc66u*43%Jf~Y(}S{h2m3EQw7BJr zr`qy|3nuMaRieJDFk<$Ey}Pcex@0|jz_oMx$!T|Y2i?kQ+j(d9x zFNQJ8?8v)z^v=Awf1^z&_?}$2i_3D(StFr%9+v2P&sKgCI#D0Y?#r{bgM~+e>BKzW z-DQsicND+AxA*tq%z0~~-Jh-4{QlCJ{C0o6GkbP^R%HoDIpLs`wzTjXzix)ZgYpBb zmgINw$4z_X@pPV6io^v)$EIojJ0jd26A!f|pA63aCSkfJTG$><7i*m)Vj-o&oVvmzOvcFLoSp4K zLB$TmsNlr(V9Uc&vD$eV4|<+_e4;er$;FK1&b+ga#jR|bKHr(VxuC7bKYq?tl_?RY z4&5y6R7~%ekV?}`i!g6(a4~-V~1#McZ*i#fXENeRA z>JirWNcTn3#Cb~$7@37R5?-&}@l2~BMych&>-7iNq?wo$dJZ&p6oN1+vGN-_v rb&3& literal 0 HcmV?d00001 diff --git a/web/Logos/LynuxWorks.gif b/web/Logos/LynuxWorks.gif new file mode 100644 index 0000000000000000000000000000000000000000..4ea672b778f006cd53b489d45b9a663401fdeb1b GIT binary patch literal 2421 zcmeH``#+Nl1HhkWd&X?Do-qeY^|+K=Vj`sXZO_^;QZ1%%RH`GA`XI$AM>8WGT_#h> zB@Vglv`nJAcQa{8MJ*KFNN&?vl2Equp8w+g;r;#v-_Q5+`T7U=d9U6n0yY5O06?$T z>vTW`_`iAk$l8I=f||lYYl5+ z@|&Hb*OIMyrsi(ib_;E@(dYiKFVnLpatfw3U28J=7XH|!D)h`+3)iahtD5P zE0~@8J~A;iJgS+WpFeOQ-QAC0BWakNoV+RTx^d&yz`)R^=)$S5(^6?u&8^4Z=e`#f zN%446duM-l-;nCXAd3^CR=>vyYwYn2c4mxo9q3+YS@j^G@|FXUg|aE?qxV0nlfcL^W&#wzexMn@^<*d#UZ zy2FF>YxAagx7D?wCylH$;`UXN%s->-m&woq`|JjbCYw;WNy;LIQ0Oy;83<$$hYL%1 zWWE#Am}IsxtnY>vHQel`j?O6`-l|M1Cy4G`uzn*E^_?DC?Pc?nSlCB1TQ}OE*jy?MYjS1;;yMoBfUje3)90&Eg)kH%P<5An=c^f3i8WRsFlZdclv_d+t(>T**2}RDFqMrZl9E{j9Z`e8c=0Z4u9;a7 z7balKP?3=-Nv>e5;L%ZN9iGjS{YLgcO}C1}mAR%Pj$AS}md-Uci{!9y%}H=9WSZ7F z!m^P?aIL6ZdL^D%<_3ck29|AijYSbqwUm544!fmF3oLRimO#wCBy>R9wuOELb)1@8 zAmh_oolKv^t=9k{c=0&n-#=1;k?->ke4!pI90UfVdOXjsygqVm1s;C~Q0=tT0K^ow$pPfglmBc-N8R%q65KcJJ$tJ{mJ@D#9Ij zxVTjbBqd9w^Bx)b32fvo*)h z8t%(mhpnCKLoaWk1@O&cXdXC0Zz*DZtSqh zkLY8T+WpB*3`Cq$0o+Yl^Swn(p4K#n`pQ?dP<3WUlOsoMa=!7Hoy$gBr;sx8vs>n0 z9r;ipsvqF36tdq%>R@#tDM6N1^gieS^y|V($OPIv+L4LNJc}lvb?~_;LxvJ#kv%PN zrM5zC!M!MTX=DDpG9C*fOq*`!(Y^BcZbDI`Y?bm@tQ@;1z6GG4bqSQ70qRUu7#dfR zL0T!)_kz`vOX0ZO{=+EHAz=V}7e*>BpIc}t2Qo+`Vz4g!8dtiOCv3byUi0bNgp3#EM@%tUv(Zf z2H|pnpr3!q3&E4c?{F9mN`iKRmc4+1Anm=;rcS`#@S5_yo+ilu zYTkbQu4RK;g#FP@g2z|ZQOm?nXhQH5MNXqTy~>tn*|)3?Q^hc_FS{M9?eS$Nx4AUr z6QS-V3kT-|2ale+tF3I3m(=o3=8E4gGq)|858~Yv^bZGmIy^r_+9xIEHSbCBA@{nZ b*iM=hcG%q=+)#I<=h2(#+dV5l5Loj!$&HX6 literal 0 HcmV?d00001 diff --git a/web/Logos/Salira.gif b/web/Logos/Salira.gif new file mode 100644 index 0000000000000000000000000000000000000000..a685588424b7fcd6f5bf2a93b4750356c8951946 GIT binary patch literal 2173 zcmV-@2!i)VNk%w1VXpu_0QUd@0RaL50}TQU4GRSf3k?bc3ls_q5(^F*2ND$q6&MQ< z6AKa=3>q2|4jdC091Ij93>PU38zK%LED{?d5*#ZMCoB^qG7%v=7AZ6oE;$+@F&Zj1 z8Y?>$F*_SFJsUYYAt*WCqpfiM-KZK|}k+4O9ol%RQMvAFIjJ8OStwxx>OO>=s zmAFlsvr3w~RgAh?lcrUgx=WzGQlPwKmb*%r!d08YOryz7tJhMa!cn8jSF6KStIS%Z z!c(o)Q?1)utk7Dm*;}y7SF+w(wA)&})Lp#ZUa#j|wc%c~>0G+xT)gXKo5W_K$!n+7 zWVP0Hx!q^4>SVO$VzlaGyyjuO>TR>+Y_;igwc~KJ>UO&4a=Yw*zTtVf>3Y8MPRjjU zz~)rN{a4NNRLTBb!t-3l{awrTT*~}k(DGc-{bk7OW5)Gk#{6W<^<~TcX~Xbo#{Ox` z^l8ieV%F_r)AeN0{bbnvYSQ&;(*0}N{eQ>rcf$N~%=U84{d~jpf5rcM&HZ=O_H)wx zblmlF-Tr>k_j}X*e%StN-GHX`}FMj{PFkq@b&!i{q^+w{rdL){r&a* z{rmv`0J#7FA^8LW008~~EC2ui0IvW(000R70R0IZNU)&6g9sBUT*$DY!-o(fN}Ncs zqQ#3CGiuz(v7^V2AVZ2ANwTC#jlzVTT*MR ziLIy6qY0a-8_KjPO{7qHI(yet-pyqy6AGI~vuM<>C4~VUx(?g1iN&B{TL!l6$7@W< zWxXiutlPU7u?;;p7q8!*dV{`W%kY>pY={#(Tc-1`Zgt)?^8s@>IGcTii?ciWmlR2JAS z@V~=?8YBj9D7SY}j+Z%WD2^E7**w*YD^bmZ#ZRAhe<5)5~{wjOILo;e)cv zKEFRgc{8;`5P&t2($mDy>ITHP>V)qCnOB z6Jj?tf#Z)k0*;uWHs|=$8%@dhgN!~Dx`-h$$2jKiPm4N67}!6kkvP+N4((Co zV>CrGP&7|DBnJ^cU`fT3c%d`XGgHz88bfAWPy`!p08xM#T)HL9OyLYfO-@_5F2YUG{*~f1j+#dY#7u=1Qq}) zkPHmqKm-OT#IX<+OfWi7dH&Dv;ZIHPIMYvj2eARbmJ3M%fD{LD5CK9MU_cNO1_a{I z8WVJ|5DX9G;i^E_gcC`LG#N_9~*psYWFC=f+LKCn=MvH1FP zS(RM<6Hh`<44{A(2$_JyAPop%&Y(Eel~!3rvn z@B#+`>_E^6SEp;bKTUKmP!zu!l!XB!SV0O}u!0Bl&rKPb#>Z+Bqk<4B^~^TVZW0S#W5fe7^Ak01cR3kNv>08r)+3IN~;O@M+CPJp~T zEP!t`000;WG6n$z!E+Vh+d#MgfDp97FUl~DY1*Ng;siw=B%6T@Y5;>G2;>bqc!ACg z;)56Tz#nMPK@3iCf)|W{2u>n{4tVedfy`kDIsjIl?uCUI)UF_0sKE;^<`~{+vL@)j zMtcl0w4fNnFEKd46)?1&YiQ#e<2c7T$gz!WTqAc0S;hiq!VJeyK?lt2m_dZm!FcIf7 literal 0 HcmV?d00001 diff --git a/web/Logos/Sun.jpg b/web/Logos/Sun.jpg new file mode 100644 index 0000000000000000000000000000000000000000..31a38ed832630cd78e021c0e15be50508d190bb4 GIT binary patch literal 4021 zcmbW3c{J32+sD6S#+EE&R~lmc$X2#c!-ysd!2KA-n3EL48UcmhtdN; z^pFL~0sw6S&;c015C{a!NPjUhGBPnAWnrcVCmS240=;Nd;S2Zair z6g+m~goua;kAN6l3>#%n)*4Ni@JLH z1}H;|D_1SiR@OESj!w6nU0mIK{rm$0u|dHRkx|k2AH>8aC8rQl)1IV1eU+V)o0nfu z_`3XEMP*g>`xmf?daS z0I`~m>)Hs~kpE%O>@CO7)T^xxEo`VNXoe?+0+2m&yZg6X!?f~;K9~SS&&h=YIg*zaS-uQL$XSBLQb@1-UOBAAvlSQBt*w+*FxXW z0*to(u)6f3mE!ph_Y)I4Hs4uShE7p9vHDU)t z(mY5iGBz=g3FfQ2n)m| zG3h%HL=R=s-z@cKOZfBc@C4k+B)zFWP6M>?2k99!;9)kEZzKZ0#7P5^mGET$371;` z;nE_T@U8xb*(~ykbOFkT0;}Ho48llq8 z$IRRiG1c=C7T*&oXj^s~@DfSA)I|e6_WhxI58dobG5QoNU&h}+>^j%J(K6bz{ZK-| ziRn%->B2|sI|rYMXU=hbmwD&My-}rbj_;CxRQQAZ`gOkix48#J>wIHh@GZ3@uR{Y3$SlNZ7v-=nG-KDuX*z7lC z>WVXk@Sn4aX~0M%4cL==?W~>Qy6la1XN`+^d4*reJjY{W5%2+8TY} zq_Wayy8#+0!Bj9j+zzt@W9?TCW%6Qk%|Uck8KcpnG8b1jPw5=UH(bFpe^b}Mp|{~= zyQm72$hEvx0pwU(^=YM&bu;mq1rkekS15$3a`yg>BeAT--^5(_PnIBmRo&1h;NI=|~gkgM&tBrwi`E>(zYq)K*gY^BV{k2VdNr`=})$P=8jGC|^r+cBdH!h}&MLJ>% zaP!ICXO2V+&XeQja4N3txVlMW7>eXZoi?VVWnY`(Doc;U!~m!KrCEDwN->Ao2&iwd z^~YIH#nWeJzlfzNB-WF`k6K4=o4}$Q1I({RoDz+Ar0=#DGGmv>9P8(hvvLPqWUUA0 zrva(tUz}UQ;N6{)GB?wt5?Ds;xh32#(HLV^o@mxz(Rl+g{*|E<;BMw;Uijo2cUNWg z8#4t-42l2i=f1<0kST2jhnjua42Mdb;~Sq%=!*~^t4y9^3*vN}>d>zcsfm5ValP`M zgSt!8GKz_2&k{=lZ07G1X+U$?ZM)m~L9k(ugD&Oz8ub;eo&^h=;+$8@%Uy?e0YbHA zK*-JUWa|kL<@EaZpOGQYIJ+B=vw^MGkGE)Ymem`rc2v5@a4WEWK;Kjx1)O8MC?Mkf zk$O+QAARfP0}6=91U4GLK*dq{`f)zwoKz%FF=Mg!l>9^3LaeRB=A_7z@WD4|GGC=( zl7Gs{0ZVv=Ebc5tH*4w>!uGGar0eEteT!xHjGLS|pJ$EYp&#Zdy={>9Ue?qN{@jw& z*+1V}8`|13lAt(n#sdQr+LWxGmXfAAZ zYrk!tn&Ku!T_@sy++6+0O{g4Ld@qW5?b#8~&LZv#=eR}y;^uL!f6Kw}Ch``^UhWV( z@N@~Is|%BNBnl?t%8ckWE;pAj>XWL`YV`8CVRx3^tCe~L&I!0AMM;A zTX8lTJ`VHF)L04Jsm;qin^kZP1b72W zz|4{8218{&iA9}24aqr<*jm+S1RpnsrDNd9mk&Paf<7O& zQl964gcztF7>Jll(SWGTpaK`D%iU9b9x4+CJ_{8YuLuT$f_~yJ=hQmZjn8rlf3tEy zKgQ}@9H>WvJu&ICPdfa@xⅆ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 bF1b1(x_vid^jY2Q@kOaqk$xly1X%ii(S-Zo literal 0 HcmV?d00001 diff --git a/web/Resumes/Andrew/Cisco.gif b/web/Resumes/Andrew/Cisco.gif new file mode 100644 index 0000000000000000000000000000000000000000..23566667e3b076e8020e7f5ee427af4cf438241d GIT binary patch literal 1231 zcmV;=1TgzYNk%w1VQT?`0QBeRhGsIU{goK33%F65J=Blcyp#%i+$;tZm z_O>%K2?+`R|NqYC=VqCiA^!_OL{CCRI!RJ7E-(V#?EoS91ONa4001li0001M09OD2 z1plzfNvpj$>&?6WU?`4cX`X1Ru59bRa4gSsZQppV?|kq7z@TtQR4EpSh2mvEA`HdO z06_)3rpQ5*Lro-KNdwJ~ zeS&{&YbsxVg9vq02wDvXm}r=h6Gi7Q=Ps61>q!OAXt>R8zza z>MLK2v*$^UNR$nzVl-Wl^{_@WB+bl zQ#JBgG>7xtg{-xp$u%!lw(YXk>X#h^M@HIeGo_tEmBFMT5k+soPZaXz*@UIkmC7m? zRtelm>*p|pLUn0cwIYENM-|gSbw`w>k_*EYoFs>?+5}DprSY2QB_)}=c=KWqz_+j8 zzkmY^9!$8fVZJL4D_+dF@!!ObBTJqv05avvmsV9rO75s^trpKHfh4=-#n+8~=asU3MRU z`3V@a_#0}5Da00U<>P(Y#rEPAGu1sq_&nqF2m=>btbpyijF1lj-te`r}Jp*VD@ zz#4`MfTfjxdKp`mi^55(ssb>|r=$chAZDLX?g^{}!sdzRl{_e`!2h6-V#%j70yN6# zqP;>9zyVNpS!ryN&dC6r0a$74Cblwa<+D?Qa>J&6s;MTtat?Ck8w6~cho5UEQ0t+f zE-U5)gLc_wobmS1s0i&y(C`NjW3cd+6l)MAt1q~TF%4IaI>Mq6v!H+g;AB}Su_R<^ zz_u&r`De#xUa2XTxb_)<16Llp>?m30xpI`-+Pw0;sVac;%K<1VGoNep$tSZZt2%DJ zKMN`)mRbhDC;^y?iRqp?U%V~XT5pP}rkrj9sR3UPTG_3^+L=NDmBq=xsI%tjDFfr; zNq_)eb}YfR1ec3vwSxBv_|v@lDmc@Ao6P5$cdI}*o@z@Vz-p9(f@<@xSAIREv<^cm zX=OB<9D3^BqOE}Aoaa04>zihZY6=F&`Ru-4uBq{woWl!foD>KTD)0m6>wA|8thwp& tu;!bkw8Wp=LdXnT&}ci{kAQu};D;~%_~e&w{`u&qum1Y%w@-cq06P?&L_h!l literal 0 HcmV?d00001 diff --git a/web/Resumes/Andrew/GEHealthcare.gif b/web/Resumes/Andrew/GEHealthcare.gif new file mode 100644 index 0000000000000000000000000000000000000000..ed01c425167a3167b5a819264068dcc6e8e8c2a0 GIT binary patch literal 1726 zcmb7?>pz=^0>_^xH;*(fP0KRvwjfFfVy%)g=RtxhOH`;!MxnBF`47(T#rM_s^ZCB{`5yIgWyRD1 zM*uYd$eU*-AFd^34{p7B>o4l)L>C_a(k_b=xj9t&M?BKLsyb31XhH1HHSkSZKWpLbMR#q}A$*eHoUxvv z*dKSCCNd;hoHEk?OLj%21J=+8hc^Z>TrN?@&@(V15~^BSX;>&0i_NIz0yMnAQ6mU* z#*%bYeoK>05bQD@?5s5`Ro*MqZ#E{hPY%z;6NZfO4?5j@7P*+_+hvcR^Ra^Fg{PZa zlh4N2-@xtijjf$UC{B}8-lixD>>|C3gkGWm2G>BTkyxNu&_d)iZ)2Ly(Z@e9|k7C1hDn}jfW(pTk34>!KW@=SK(%)Ez-`l zI4tBcH1;iGm*82Y<~dJE_{+$(B@cR#kLp7{KA`uvTsN->ut?BP&n8aI*zzH&d-+{i zgWwdgd&xwj`j?S|Us?lsAR#0&!SLg#&w5TxWz)ksF+BxAi&&-$m^Vy80Qd7a>N;KX z$Rr2l)fLPXJ#Vls8rMdt{{1!@AQ(Z%8Nx6%Jrw(~c(^{Y92TV44Oi`G&Z(?$zQoCE|qZzBj)_`9aq#jv~lWdoYPfiUa2E}eE_Qi+FL}W*d{P} z5^^a|t|o0hYn;Wl%%=iC^ZJ*D*kA;s3GbmQ9(-DJo5oGNp#Uz&d^ug|l}{5;yf5@i zgv(Ygp5I*xTNI?h3Ki z2rHKzbKI~%_v*2Sa1N~+@>MV;)H?@g&wj!`cp+r_S8G}z6nkv1oKW_OC40n1KeAeD z73;D4I}`+|Z>H*h5!ggG=p!+^L6UY8-8*WpEd%89rYM-9WsQN&^Ko#E-;08vbmxp9Tsx`-^n4fwF7&Gkl1 zN8y#`I;#amv~$Dnb+r&c3tGSCc1f#uC&Gg_W{^2eD6e?tZ`*vWQytb?c3t;6S5yaixOpjwU8uErd|^S+k4+nR?%E z&;`w_(~TP?+iT2KdB7ugOkd&rR#O)5jN1|e-xY7z3nEht9Q11C)PxH$@J?2^d?igC z{YSi2y|GJWYoGA1-Gc{=6)DgBIXlS8P2RG2bWiNA(~Nk+JUfz^<=ZhjY%`H*?)p&M zbi%plpEZYNq8Yz~sM=`yhZJk2D-K+)ujfSfe%o1NvYwCS~N_R!oXjzt@h1ewAkZMuQ-Q!d>0hU$Fi3vQ@3)#mN*64j(3tQV_2BMlJaTu>NLI{Q-hcZMQ0+%$lkO z_i=y39GG5|Fl5Ls-r9XxHk5DlIqR78JzUw9swov5L{4sZSXJnAnO`W4uS+(okj=}# zRE!lqV~tkX4>Z`ge_f{F-JvYl%vh^f1)n5pO*M15F8$%8xB$udZnz?bmr@aSXrW?V z&2Idaj|nhnDf`OYlCpDWEHlY_kE$gP0BwDP!uLE5t$1%sI=#(2!#?WLRO z+md*Hn~FScq1h0-Hw=-#J8|1@|3FmbAv%YIK;JNIyj&!;*jQWgC8@93H?}6+i~iHK LX4`mk0C4*ce-+zW literal 0 HcmV?d00001 diff --git a/web/Resumes/Andrew/General_Dynamics_logo.jpg b/web/Resumes/Andrew/General_Dynamics_logo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..dc8d98ab26d812c024b197bd51861111b18f882d GIT binary patch literal 23841 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ⅆ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 + + + + + ClearSCM: Our People: Andrew DeFaria - President + + + + + + + + + + + + + + + +

    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 rb&3& literal 0 HcmV?d00001 diff --git a/web/Resumes/Andrew/LynuxWorks.gif b/web/Resumes/Andrew/LynuxWorks.gif new file mode 100644 index 0000000000000000000000000000000000000000..2902be0651fef962cfa2a9a1658d5e9cfc70a611 GIT binary patch literal 2387 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 literal 0 HcmV?d00001 diff --git a/web/Resumes/Andrew/Resume.doc b/web/Resumes/Andrew/Resume.doc new file mode 100644 index 0000000000000000000000000000000000000000..1cde172e52fc356c9a3304823e4e17f9cf198ff8 GIT binary patch literal 141312 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

    +
    + +

    Andrew P. DeFaria

    +
    + 11064 West Ocean Air Drive #230
    + San Diego, California 92130-4605
    +
    +

    + Phone: 408-596-4937
    + Email: Andrew@DeFaria.com
    + + + + + + +
    + Download an MS Word copy!
    +
    +
    + Sorry for the blink but for some reason recruiters can't find this link!

    +
    +
    + + +

    Objective

    + +

    To work with state of the art operating systems and networks to + insure the smooth running of an organization's information flow.

    + +

    Hardware

    + +

    Workstations and servers from Sun, HP, x86 class machines, dual core, + quad core, 32 and 64 bit. + +

    Operating Systems

    + +

    Unix (Solaris, HP-UX), + Windows XP/Vista/Windows 7, + Linux (Redhat, Ubuntu Desktop/Server, + Mandrake, SuSE, Redhat, LynuxOS).

    + +

    Networking

    + +

    Knowledge of TCP/IP, Ethernet, XP Firewall, DSL Routers, Windows + and Unix Networking (NIS/Automount/ftp/ping/etc), some Active Directory/LDAP experience and Samba experience.

    + +

    Software

    + +

    Clearcase, Clearquest, CVS, Apache, Build Forge, VMWare, MySQL, + Mozilla Firefox, Thunderbird, Perl, + Bash, PHP, Emacs, CDE, C++, VUE 3.0 (Alpha + Tester), Cygwin. Also, various tools + and applications on Microsoft Windows too numerous to mention.

    + +

    Education

    + +

    A.A.S. in Computer Science from Union County College in Scotch + Plains, New Jersey.

    + +

    Attended approximately one year at Fairleigh Dickenson + University, Rutherford, New Jersey, in pursuit of BS in Computer + Science, concentrating on computer courses. Have also attended San + Jose State University, Mission College and Chico State in pursuit of + my degree.

    + +

    References

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    David Petro(425)-391-4185david.petro@ge.comManager, General Electric
    Tom Connor(512)-422-1172tomhillconnor@yahoo.comCoworker, Consultant
    James Chen(408)-845-5360jchen@salira.comVice President of Engineering,
    Salira Optical Network Systems
    Omair Ahmed(224) 715-9786omair.ahmed@ge.comCoworker, General Electric
    Shivdutt Jha(408)-806-3476shivdutt_jha@hotmail.comCoworker, Consultant
    + +
    +
    + +

    Clients

    + +

    Broadcom

    + +

    December 2011 - Present
    + Contract
    + Workblog

    + +

    Worked as a Clearquest Designer and hook code writer. The Clearquest + database used Visual Basic. Implemented fixed and feature development for + Clearquest as well as wrote several Perl scripts to perform data maintenance as + required by utilizing ClearSCM's Clearquest + module. Also utilized Clearquest::Server, + Clearquest::Client + and Clearquest::REST modules.

    + +

    Worked with Electric + Commander migrating a group from their unsupported build environment into + the standard Electric Commander based solution. This involved using Cygwin, + bash and LSF to farm builds out to a pool of Windows servers to perform builds. + Builds were down using Visual Studio 8.0, 9.0 and 10.0. Build system also used + Perforce and Perforce trigger to fire builds as the engineers checked in code.

    + +

    Implemented Perl module, Clearquest::REST, + to replace a Clearquest Daemon + that was in use so that systems that did not have Clearquest installed (e.g. + Linux build machines) could talk to Clearquest to update defects when required. + Modified Perforce and Git triggers to use this REST interface.

    + +

    Served as a mentor to the group on issues of programming in Perl as well as + using and configuring Eclipse IDE.

    + +
    + +

    Tellabs

    + +

    March 2011 - December 2011
    + Contract
    + +

    Automated various informational systems using Perl/MySQL/Oracle and the + web. This often involved transforming data from far away databases to more + local data structures for presentation on the local Intranet.

    + +

    Developed a command line debugger called raid which provided a consistent + interface with complete command history and variable substitution. This Perl + process utilized Inline::C to interface to the developer libraries and provide + a consistent interface for the various command line debuggers developed by + various different groups.

    + +

    Modified and extended a serious of web pages and graphs to extend the + functionality of Mercury Quality Center, interfacing to Test Directory's + SQL database to the web giving project manager's drill down functionality + regarding testing activities to an unprecedended level.

    + +
    + +

    General Electric

    + +

    January 2010 - December 2010
    + Contract
    + Workblog
    + +

    Performed Clearcase/Clearquest administration with an emphasis on + UCM administration. Wrote several Perl scripts including an Evil Twin + Finder. Created UCM Projects and streams as appropriate as well + as created and updated Build Forge jobs to automate work + flow. Assisted in consultations with UCM concepts such as + component/composite baselines and projects. Wrote Perl scripts for + conversions of Clearquest data with other systems (Siebel).

    + +
    + +

    General Dynamics

    + +

    June 2007 - October 2009
    + Contract
    + Workblog
    + +

    Served as Clearcase/Clearquest Administrator, Build Release and + Automation using Perl scripts. Implemented several enhancements and + new functionality with a C++/Qt application that integrates the highly + specialized UCM/Clearquest integrated environment into one tool.

    + +

    Instrumental in establishment of Perl standards and introduction of + Perl tools such as Perl::Critic and + Perl::Tidy. Worked at promoting + usage of CPAN modules.

    + +

    Developed an extensive test driver application in Perl to + interface and drive tests using NetHawk EAST Simulators + as well as interfacing to other simulators and external hardware. The + system automates the running of regression tests, official testing before + the customer, assists with validation of test results, collecting of log + files, checking log files into Clearcase and records status into a MySQL + database. Developed a PHP web page to present the data in various forms + including graphs, reports, exporting to CSV files and emailing of reports. + Implemented maintenance programs to scrub and keep the data clean. This system + was instrumental in Functional Quality Testing for the + MUOS + program.

    + +

    Worked on many enhancements to the extensive Clearquest system in use + at GD. Designed and developed the record set implementing node configurations. + Implemented required forms and action hook code. Designed and developed Perl + scripts to initially load data into the new records.

    + +

    Developed a server process (daemon) to process baseline records that were + then tracked by Clearquest. Implemented scripts to create baseline records + from other automated process such as Build Forge. Tied together baseline + records with node configurations through action hook code.

    + +

    Participated in code reviews for all production code.

    + +
    + +

    Texas Instruments

    + +

    October 2006 - June 2007
    + Contract
    + Workblog + +

    Serving as Clearcase/Clearquest Administrator working with Perl + scripts and Clearquest schemas. Responsible for development and + deployment of a Perl/Oracle application to track information about + projects worldwide. Also wrote, modified and maintained several + scripts for tracking Clearcase license usage and load balancing of + Clearquest web servers.

    + +
    + +

    Hewlett
+  Packard Company

    + +

    February 2006 - October 2006
    + Contract
    + Workblog

    + +

    Managed and executed day to day build and release duties. Served + as Clearcase/Clearquest Administrator as well as overall support of + systems. Assisted with creating UCM streams and handling of rebase + and delivery issues for engineers and the build/release process. + Wrote UCM triggers to notify users of deliveries from UCM + development streams. Created baselines for official builds. Took + over day to day build and release duties. Created a build script + that united the various quick and dirty build scripts that were + oriented per stream and per build option. This standardized the + build process. Augmented this build script to be a daemon that + continually builds software when deliveries are detected. Wrote a + build status web page that tracks and monitors the continuous + building. Created a dynamic web page to show Junit test + history. Converted Windows build from bat files and scheduled tasks + -> Cygwin and cron thus making the build script identical on both + Linux and Windows.

    + +
    + +

    Broadcom

    + +

    September 2005 - January 2006
    + Contract
    + Workblog

    + +

    Served as Clearcase/Clearquest Administrator as well as overall + support of systems. Developed several triggers as + well as ported my mktriggers + script which automates the maintenance of triggers.

    + +

    Developed a complex Perl script to + merge two Clearquest databases to a new database with many schema + changes. This script handled all aspects of the conversion including + changing non US ASCII characters found in the data to their HTML + equivalents, dynamic creation of dynamic lists, field renaming and + dynamically creating new stateless records as needed.

    + +

    Developed a script to better handle merging from UCM deliveries + and rebases by delaying any non automatic merges to the end of the + process as well as handle binary element merge. This process, + written in Perl, utilized PerlTk to present the user with a GUI + dialog box to choose which version of the binary file to merge.

    + +

    Designed and developed another Clearquest database for the Mobile + Multimedia group.

    + +

    Wrote several other scripts including one to interface CVS to IMS + (a defect tracking system) recording the change set at commit time, + a script to strip out MIME/HTML and attachments for defects + submitted to GNATS (another defect tracking system). Also + implemented several script to log Clearcase activity, check + Clearcase's pulse and gather site and vob statistics. These scripts + were the start for creation of a set Object Oriented Perl modules to + encapsulate Clearcase in a Perl like manner (still in + development).

    + +
    + +

    LynuxWorks

    + +

    December 2004 - September 2005
    + Workblog

    + +

    Served as a build engineer in the Integration Group responsible + for building LynxOS (Linux RTOS) as well as tool chains, testing, + releasing and process improvement. LynuxWorks uses CVS for version + control.

    + +

    Developed a process of providing full text search of the + company's defect database using Perl and Htdig (See ECRDig). Developed a web + based report to show CVS activity as well as several other CVS + related utilities(See CVS Utilities) as + well as report on the differences between two CVS tags. Automated + the build process so that nightly builds could be + performed. Developed a web application that allows one to maintain + CVS account information including account creation, + setting/resetting of password, etc.

    + +
    + +

    Ameriquest Mortgage Company

    + +

    March 2004 - December 2004
    + Contract
    + Workblog

    + +

    Served as Clearcase/Clearquest administrator to this major + mortgage company. As Ameriquest is just starting out I have been + busy with importing source code from flat file systems as well as + PVCS and Visual Source Safe. Also setting up vobs and regions taking + into account security restrictions and concerns. Assisted with + designing of the Multisite scheme to India. Participated in design + of UCM model to be used for Ameriquest.

    + +
    + +

    Salira
+  Optical Network Systems

    + +

    August 2001 - February 2004
    + Workblog

    + +

    After consulting briefly with Salira Optical Network Systems I + joined this startup company serving in the role of Clearcase/Clearquest Administrator for this + mostly Windows shop. I helped others in setting up the + Clearcase/Clearquest environment as well as provided Training.

    + +

    I also served in the role of Release + Engineer managing the build process. I employed wide usage of + Cygwin, which is a product that + provides an extremely workable Unix like environment and engineered + a build environment around that using GNU + make and other standard Unix and GNU utilities. When users + complained that building remotely was slow I performed an analysis on build + performance. I also performed Build Stress Testing + where I characterized the effect of multiple simultaneous builds + performed on the server.

    + +

    I also setup and developed their Clearquest bug tracking system as well as + served as an advisor/expert on Clearcase issues, branching + strategies, labeling and release management.

    + +

    While working at Salira I designed and developed a tool in C that + packaged the product into a more compact form.

    + +

    I designed and implemented a Clearquest Daemon + which served as an interface between processes and Clearquest + data. This daemon serviced requests from web pages and triggers in + order to get and validate data from Clearquest.

    + +

    Developed release web pages that managed releases and produced + release notes for every release.

    + +

    Developed process automation scripts to perform automatic branch + merging and syncing.

    + +

    Performed product installation testing for the web component on + Linux (SuSE) and Solaris as well as browser testing + (Netscape).

    + +

    Implemented test scaffolding in TCL/TK for test automation.

    + +
    + +

    Hewlett
+  Packard Company

    + +

    August 1999 - February 2001
    + Contract
    + +

    Systems + Technology Division

    + +

    Enterprise Java Lab

    + +

    Setup security system automating the running of Medusa (an + internal security audit tool) on approximately 100 machines. Reports + are generated automatically and are viewable on the web. Setup and + maintained security related patch depots.

    + +

    Implemented nightly automation for the lab's machines including + security checks, automatic installation of line printer models, + etc. This automation was bundled into an SD-UX bundle.

    + +

    Migrated user data to HP NetStorage 6000. Worked extensively with + HP NetStorage 6000 Support on problems with this machines OS and + interfacing with Windows 2000.

    + +

    Migrated HP-UX applications from one application server to + another.

    + +

    Participated in several critical planned networked down times + where the team was able to implement changes to the infrastructure, + including migration to Clearcase 4.0, migration of project and user + data to HP NetStorage 6000's and other such changes.

    + +

    Set up Netscape Enterprise Web Server and iPlanet 4.1 Web + Server.

    + +
    + +

    Cisco
+  Systems

    + +

    March 1999 - August 1999
    + Contract
    + +

    Served as Clearcase/Unix Systems Administrator. Responsible for + all Clearcase operations in CNS/AD on Sun Solaris, HP-UX, Windows NT + 4.0 and Windows 2000. Assisted in creating additional View and Vob + servers and balancing the Clearcase load amongst them. Participated + in Rational's Beta program for Windows 2000. Installed, tested and + documented Clearcase on Windows 2000 as well as Windows NT 4.0.

    + +

    Assisted in recovery of a catastrophic disk failure in a critical + vob. Assisted with implementing a backup strategy with Arcserve + Open. Helped evaluate system monitoring packages.

    + +

    As CNS/AD was in a secured and isolated network, learned and + assisted users with ssh/scp.

    + +
    + +

    Sun
+  Microsystems

    + +

    December 1998 - March 1999
    + Contract
    + +

    Worked on the Sunpeak Configuration Management team performing + promotions of code updates into test and production + environments. Also worked on improving the process flow of + promotions utilizing make and rdist.

    + +
    + +

    Hewlett
+  Packard Company

    + +

    February 1988 - November 1998
    + (60-Level Software Engineer) + +

    Systems + Technology Division

    + +

    California Language + Labs

    + +

    Primary Clearcase and Multisite Administrator for a large + Clearcase environment with approximately 1400 views and 180 + vobs. Most vobs are multisited between several other labs and I am + responsible for resolving Multisite problems. I also serve as + general System Administrator, overseeing approximately 400 machines + in the lab. I help institute policies and procedures to keep the + network running smoothly. Also participate in the design and + restructuring the network topology and Clearcase topology by bring + in many Kittyhawks, Mohawks and Bravehawks (about 40 of them) for + use as Clearcase Vob, View and Build, Mail, Application, X Terminal + and Web servers. Assist in documenting setup and configuration as + well as trouble shooting and handling of patches for all lab wide + shared resources. + +

    Responsible for setup and running of Windows NT domain, account + setup and print serving. Setup and evaluated Clearcase 3.2 on + NT. Developed backup strategy for NT systems. Maintain a repository + of software tools as well as evaluated and recommended several PC + packages for lab usage. Main point of contact for Windows 95/NT + problem solving in the lab. Also sought after by many people in + Hewlett Packard relating to both PC and Unix configurations and + problem solving.

    + +

    Also served as webmaster for the lab as well as consult on HTML + questions and design issues. Installed, configured and maintain the + Netscape Suitespot Servers + including the Enterprise and Directory servers. Developed several + web pages and forms for the lab as well as run The Unofficial Quicken® Web + Page.

    + +

    I developed an Application + Server providing many machines with many software packages + without the need for individual system administration utilizing + scripting and NFS heavily.

    + +

    Prior to the Productivity Project I worked on COBOL/SoftBench + product which consists of encapsulating some core HP Micro Focus COBOL + tools using C++ 3.0 and the SoftBench Encapsulator libraries. Also, + working on porting an X/Motif application to MS Windows 3.1. The + code is written using C++ 3.0 on both the HP workstation and the PC + (Borland C++ 3.1).

    + +

    Worked in the Ada project on Ada/SoftBench. This project was + similar to COBOL/SoftBench in that it involved some SoftBench + encapsulations using a language called edl.

    + +

    Worked producing Ada Bindings to Xlib, Xt and Motif. This + involved using a modified C compiler to translate C header and + source files to Ada declarations and function prototypes. Using this + methodology we were able to migrate our product from X11 R3 and + Motif 1.0 to X11 R4 and Motif 1.1 in one week!

    + +

    Worked on a project that produced Ada Bindings to HP-UX, which + enabled me to get good breath knowledge into all system calls, and + another binding to Starbase graphical subsystem.

    + +

    Performed destructive testing on MPE/XL 1.0-1.3. Wrote several + programs to stress the OS. Submitted 300+ Service Requests many of + which appeared on Must Fix lists.

    + +
    + + + +

    This resume is freely available; you can + redistribute it and/or modify it under the terms of the GNU + General Public License as published by the Free Software + Foundation; either version 2 of the License, or (at your option) + any later version. This means that if you modify this resume you + must include a copy of the original source or refer to its origin + at http://clearscm.com/Resumes/Andrew.

    + +

    This resume is distributed in the hope + that it will be useful, but WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A + PARTICULAR PURPOSE. See the GNU General Public License for more + details.

    + +

    You should have received a copy of the GNU + General Public License along with this resume; if not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA.

    + + + + + + + + + diff --git a/web/Resumes/Don/Aspen.gif b/web/Resumes/Don/Aspen.gif new file mode 100644 index 0000000000000000000000000000000000000000..0262df68b4df184af1614da9272eef2a7101d976 GIT binary patch literal 5969 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 literal 0 HcmV?d00001 diff --git a/web/Resumes/Don/DonSkanes.doc b/web/Resumes/Don/DonSkanes.doc new file mode 100644 index 0000000000000000000000000000000000000000..f66312b3b7acdd8e5f2f823ae7a9c0a1df7e39a6 GIT binary patch literal 52224 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 a*Nry4ae?#PS$DpcvTo(Ja(-<%@P7gGV*c&` literal 0 HcmV?d00001 diff --git a/web/Resumes/Don/Edentree.jpg b/web/Resumes/Don/Edentree.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0fca22be29f2616647b3e33054d759d53ca003a8 GIT binary patch literal 4624 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 OKVJR+;eR2NF!NufuZwH| literal 0 HcmV?d00001 diff --git a/web/Resumes/Don/Nortel.gif b/web/Resumes/Don/Nortel.gif new file mode 100644 index 0000000000000000000000000000000000000000..748e4350ac9499a1380c4042d5f605123711c01a GIT binary patch literal 1588 zcmV-42Fv+JNk%w1VbB040J8u9l5@JIjK|l@=Et|({rvvo+U@G(^7ZogbW5jyS+2UK z(``bc@$B~a_WS?;|7ks-A^8LV00000EC2ui0MGy@000F4@X1N5y*TTAK@kZjj$~<` zXsWK{5-^SfE^KYzc&?iZ0LKmkfL2);jk~0B$J3Hl77jaZa%4kg%|bwHFv1s@ay(1@{J3_=6{LJG5!OV&Uj2MPvcb#fNKi7lB~ zGYKT2LJ{{+9fDZITq}zd7VQfF&EEldC1b{@cu}dImqlZ$ln{Vsi~%=CN~fD-OT|~FdRH0bSkwc97m}@yo3eB6%bMXQYh7ihaLprWQO}{FTBRhz{gwTZM)S){0!a*z((%ueRs~mAf#&+6W88d22Cr zx_HDYRa)sO1~9cW6;(Q>dT4|N{P5KOaoP4WJL-Pq(4wdCCf|f6Nyw`n4fNPArxRTH8lOs`JE*0~>P3Ji9#ACL zkl5lw7!PeSwrzxcyaRB+UT*eQ(! z2Vy`0Jon7A!O4@;2QjI=(ILRRAlXB|+mHYRzbewiQrXpmrcbbd(k}~J ze7Sx02k7`34jPO|=-+#j>`~Au-1hc<%$43i*6n*xpyKI0mXntY>H!8|KmZ3Qz=@qI z5TgLk)8 z;T+>fb|GP8l=CYr1RySg(A)jUwwqUMBXU{#lv79-Fx<@og$i+?6DTAc7!|DmMx?@K z1i+cKOo4ah%APf(7bT$K+>C!Yi%AKQ6%!avHQi8M)BLH;SglFE!H;D*<0g!~G#VP=R07ep$M1@z1Vaf~E@l9Wl!YGMFMR2)ETIzuzk!~}qau%$!^N+?o9q==DSbP-V0pbH47d-ojg=ed9Hk9&X2%sXe^dCxQN`^?N!MDzU_ zgVIF-0Dz%-y7`0S-(aI5pyB4cCxV+V*iO-%g$?&Zw(`iGWW|EQ>tBlWYTv_?>P*gfM ze5bcAt3Ji$&nFKK9ZJ1+(o^wD{_Wd0Kfi1{JJv;(owIY|zrJj5Z+}Tmt?KQaSXM?*Z$*EMbhx@E9y@kkEdO{F08p(|Hc!=c?r8rpDP9n!c`oEh6 zH|1NFmPua-`O88bov%hlZJ9fWoPs7f9OFjF7dAEJ=W!Up2nfqM+9jP1Kg z7{E7wu(K&9PymcP%9l>3mF4HVA6CWB7*HN~|6yPOLDs)|%cfi5O*G6rXprIYk>jn( zqnid4u}Z2y8C+as;}2%k#xifcT3W1Fc#S>3D1bg}Z#Q;SN2Z!tJR)hcL5K%>#KwkE zfGU)#!AQ$+9QlNT7+%`BdWuR^b?St|CZ%i6eB`|YB}v4xpl&|OQ6rfgVqxJ;90g#L z
    5iOP}oCgO;}31YMiD9Sctenp_v@jC(~;NHp4-T1>foN< z>GVAw?X$D$)jLi#7dSDe5N@m$W)MrszoU-`z%_R#sHlUH<`rm@Y5YQesW+;!BXqJh zHG3?Z;*Xd<7mf929d!}Fko658*!Uf++z+qt~L|m9&Q7Vs#DWXzguZcI&aN0R|Q?<3X%Uya9 zju3u4jO=a4O}m9V@&kjx(>ColfQcV%5iaiaQUckBm3+FLl6HH&Lde&PC*ZUgA zM*!Z*#&JIkC>P z?*G+xW8vWyT-W&lwU%#w-_xok;isP=y#s=tEtq3MlKBDlWMm#QR~ep9H2oUpS;VQ} z!R+u?EkJ6~O4$fjNR!%!iVX+gpN#q!%-;JEmk}r4!SXqxfv_eml(D*?9 zY*KkpEsu*O8rorzvO`n~ax=Za@pa2{IkU1_R6V&%K|_NX)H_QtVBD}x}-mf3(jfU(li`gGa~Tr zUeq9M4eNyWBZ6WAV`VSQo3bSM)ej!J@2R4Aq`<@G$7hpB*nr+ar zar#UKK5(d_2#h9d9vGQ=btQY zJ(=pKDZgjqE@2v;p*U|Qnb%50^;5A>2m;@e>kb7|ePn!&v!vrJ9){J{R2A&YkuicJ zwGc|4Ze}U{=|yP@9^{gkV1I5lQf7d{Xi0yJjLfkJl<09kNj3HCt7=>q=@Gk?97!tP zVFNx;%V_fi7VCOLaivL@cc+PBtUTzKaehPH`USzBx# z{Z8-(`f(HDU$(qn3u38Yoy5>Cbk=UD74aP;}{;vaeU*H{_(y=eW>4_Kks= zefa`}AQn93(PB=uZ zP5bNBF#b z3l@YnWdn-&k@$cBbnltM(-D~P+_RCNyIWC4u>28Cg}6}+#IM_mWeYb{ZqyUm4ktFz&mdK=77-40#Y zPP+j5(C_j24hxZ6_jR5J`t1v6RPQqwQ>^w`6ZUR7Q-H|R5j5ocw5Jd&shs`X&3!JN zq@oo9HQbXCfZ0VR9}>$+F-o)+v2|y2vuWo=hlE2xxap5tba&B<%1sw9nP})$$8B!K zMX70-R_^mp=aYXcDR8h1YaBId-l_L-c-&M*El7vd&s|kU@T+)g?5Bzi&|1m zVlz{5pOaQYVo6j~X#t%Tblj|g*dB%?S~6le4jQy-dSg}u{-s6mQjqK&o7qM5j@p)Q zk=mh8E9Or+n(v2a%(!S=mG9%;{pE6(ri%*%6l79g7T4PuG0zA-ujNszPMfKs%U-kL zDSLNnY0Wd__<|BwO6ih@hb?MOQTsPdrJtd_XIcTGO*-cLGEW^jSs<1%I_>DnkR0D# zfm91$HjblDMtRIfkyGOKKSjrtwCD&O?UcN0`z@?g_dc zi_QfgZktjgVcs5xy%;!vr5K%#$R=d|7m0W#L&Dc(6c3Do0Nn1#Z#r%bZL$jkz6%gE q$`B9!UdU~*NTtm0n&tmBIS>GK<+tX81H<1{JV15#brZWXdH)8M$`x(^ literal 0 HcmV?d00001 diff --git a/web/Resumes/Don/index.php b/web/Resumes/Don/index.php new file mode 100644 index 0000000..d86bc30 --- /dev/null +++ b/web/Resumes/Don/index.php @@ -0,0 +1,424 @@ + + + + + + ClearSCM: Our People: Don Skanes + + + + + + + + + + +
    +
    + +

    Donald G. Skanes

    + +

    MS Word format

    + + +

    Summary

    + +

    Highly skilled senior software engineer with over 27 years of + experience. Special strengths in the areas of design and + implementation of Software Configuration Management tools, operating + systems, programming languages, compilers, databases, system + validation tools, build and deployment automation.

    + + +

    Operating Systems

    + +

    Highly skilled in Windows, Unix, and MacOS operating systems. + +

    Software

    + +

    Extensive Experience in Rational's ClearCase and ClearCase + UCM for UNIX and Windows Vista/XP/2000/2003, ClearQuest, C#, + .Net, Visual Basic + 6, Perl, C/C++, Qt, Make, Tcl/TK, Java, MS Access, SQL + Server, Oracle, Install Shield, GNU tools, C shell, Bourne + shell and BASH. + +

    Education

    + +

    B. Sc. Computer Science - Memorial University of Newfoundland, + Canada, 1981

    + +

    Clients

    + +
    + +

    ACC Capital Holdings Corporation

    + +

    Nov 21, 2005 - Present
    + Manager, Configuration Management

    + +

    Summary of responsibilities:

    + +
      +
    • CM Manager responsible for the Rational tool administration + for ClearCase and Team Unifying Platform. Provide mentorship and + leadership to other CM team members from various organizations + within ACC Capital Holdings, Ameriquest, AMC Mortgage Services, + Tavant Technologies and Argent Mortgage.
    • + +
    • Provided enterprise solutions and for Configuration Management, + Change Management and Release Management for several lines of + business.
    • + +
    • Responsible for providing training and documentation including + charters, requirements and CM plans for various CM teams.
    • + +
    • Corporate subject matter expert for Rational Tools and + Configuration Management.
    • + +
    • Managed a large team of CM, Tools, Build and Release engineers + for various Mortgage application systems.
    • + +
    • Lead the rollout of ClearCase UCM and ClearQuest for one of + the largest software development shops in US.
    • +
    + +
    + +

    Aspen Software Consultants

    + +

    Jan 2005 - Nov 2005
    + Contract
    + Senior CM Administrator, Argent Mortgage

    + +

    Summary of responsibilities:

    + +
      +
    • Administration of IBM's Rational ClearCase and ClearQuest + included providing consulting and mentorship to development and + Configuration Management and Release Management members as well as + development teams.
    • + +
    • Coordinated and implementation of approximately 75 ClearCase + UCM projects integrated with ClearQuest and Test Director in + addition to providing training, ongoing support and + mentorship.
    • + +
    • Responsible for documenting CM plans and training Job aides + for implementing CM processes and procedures in addition to naming + standards for CM and development teams.
    • + +
    • Developed tools and triggers using Perl and C for ClearCase + integrations to applications such as Empower, Oracle and various + stream automated deliver and deployment operations.
    • + +
    • Designed and developed build automation tools from within + ClearCase UCM which incorporated information derived from within + ClearQuest Build Request implementations using the CQ API and + Perl.
    • + +
    • Provided automated scripts and worked with the build and + deployment teams to improve processes to deploy applications to + integration, test, certification, and production financial + systems
    • +
    + +

    April 2004 - Dec 2005
    + Contract
    + Senior CM Strategist for Ameriquest Mortgage
    + ADS Program Management Office

    + +

    Summary of Responsibilities:

    + +
      +
    • SCM Responsibilities using IBM's Rational ClearCase included + providing training and mentorship to the current CM members, in + addition to developments and QA teams.
    • + +
    • Participated in the coordination and roll out of approximately + 2000 clients and 100 ClearCase UCM projects.
    • + +
    • Developed tools and triggers for ClearCase integrations to + applications such as Informatica.
    • + +
    • Responsible for documenting CM plans and training Job aides + for implementing CM processes and procedures in addition to naming + standards for CM and development teams.
    • + +
    • ClearCase and ClearQuest administration including + Multisite.
    • +
    + +
    + +

    Mindlance Corporation

    + +

    Mar 2003 - Feb 2004
    + SCM Consultant for INTEL Corporation
    + Tools and Environment team in CPD - Folsom, Ca

    + +

    Summary of responsibilities:

    + +
      +
    • Software Configuration Management responsibilities using IBM's + Rational ClearCase included providing consulting and mentorship to + the current SCM team members in addition to development sponsored + subject matter experts.
    • + +
    • Application Development and implementation using Windows XP, + C#, Visual Basic 6, Perl and SQL Server 2000.
    • + +
    • Designed and implemented tools and triggers to enhance the + ClearCase Software Configuration Management system. This included + a client/server transaction based service which executes SCM tasks + across networks in an effort to enhance ClearCase Multi site + capabilities.
    • + +
    • ClearCase administration duties including multisite to + Bangalore.
    • + +
    • Documentation provided for various policies and procedures for + CM.
    • +
    + +
    + +

    Edentree Technologies

    + +

    May 2002 - Feb 2003
    + Senior Software Engineer, Software Development

    + +

    Summary of responsibilities:

    + +
      +
    • Software Configuration Management responsibilities using + Visual SourceSafe
    • + +
    • Application Development using Visual Basic, Python, C/C++, + TCL, Perl, Java and Aspect (Procomm). Designed and implemented a + universal interface for client implementation for the CONNECT_ET® + product.
    • + +
    • Designed and implemented an application level protocol for a + Client Server application using sockets and TCP/IP.
    • + +
    • Designed various GUI and applications for a new test + automation product line using Visual Basic and C++ with Qt on both + Windows and Unix Operating Systems.
    • + +
    • Designed and implemented a database interface for client + server applications
    • +
    + +
    + +

    Vpacket Communications, Inc. (now Zhone Technologies)

    + +

    Mar 2001- Apr 2002
    + Senior Software Engineer, Software Configuration Management

    + +

    Summary of responsibilities:

    + +
      +
    • Designed and implemented a software version control system + utilizing ClearCase as the software development + environment. Provided ClearCase support to the development + groups.
    • + +
    • Architected and documented software process definitions in + correspondence with ClearCase Unified Change Management (UCM) + policies and procedures derived business requirements.
    • + +
    • Designed and implemented an automated software build system, + and provided regular builds to the software development + groups.
    • + +
    • Coordinated multi-site support SCM team between the Vpacket + sites.
    • + +
    • Designed and implemented a Bug Tracking schema using + Rational's ClearQuest.
    • +
    + +
    + +

    Nortel Networks, Inc.

    + +

    1997- 2001

    + +

    Desktop Environment Support

    + +

    Desktop Environment support entailed the following responsibilities:

    + +
      +
    • Port of a Solaris based development environment to Windows + NT.
    • + +
    • ClearCase software version control system support + activities.
    • + +
    • Make file support for the overall development environments + build system.
    • + +
    • Windows NT environment support tools such as ClearCase, Wind + Rivers Tornado products and Cygwin's BASH (Bourne Again + Shell).
    • + +
    • Windows NT and UNIX interoperability support issues.
    • +
    + +

    ClearCase Administration and software development tools + developer

    + +

    The following responsibilities were performed as part of this + position:

    + +
      +
    • ClearCase VOB (Version Object Base) Administration. Comparable + to database administration type activities such as VOB layout, and + ongoing maintenance.
    • + +
    • Multi-site software development support using the Rational's + ClearCase multi-site capability to provide multiple sites (4) the + capability of updating the same source code.
    • + +
    • Tool development for developer productivity and process + control based around the ClearCase development environment.
    • + +
    • Tool development for an API between ClearCase and a + proprietary problem tracking system.
    • + +
    • Tool development of a SQL database used for reporting + different aspects of the software build and software release + process.
    • + +
    • Product software load build process support activities.
    • +
    + +
    + +

    Nortel Networks, Inc.

    + +

    Northern Telecom Canada Ltd./Bell-Northern Research

    + +

    1981-1996

    + +

    ClearCase Administrator and software development tools developer

    + +

    Investigated and prototyped ClearCase implementations using base + ClearCase and Perl. Small projects were migrated from a proprietary + CM tool to ClearCase. ClearCase Multi-site was implemented from + Ottawa to Billerica, Raleigh and Simi Valley.

    + +

    WEB Administrator

    + +

    This activity entailed the administration of a Web site used + within a directorship. It involved the maintenance of the Web site + as well as keeping the HTML based information system up to date.

    + +

    Load Build Support

    + +

    Provided load build support to a team preparing release software + loads to customers. Build scripts developed and deployed using + ClearCase, RCS and PLS.

    + +

    Software Developer - X.25

    + +

    Developed a software trace facility for an X.25 system for a + proprietary product that enabled remote tracing capabilities

    + +

    Software Developer - NUI

    + +

    Developed a NUI (Network User Interface) database based on a + VAX/VMS with X.25 and ITI interfaces into a proprietary product line + to facilitate X.25 and X.29 NUI capabilities

    + +

    Software Developer - Network Management Systems

    + +

    Implemented a data collection facility for the inception of + statistics received from various proprietary devices

    + +

    System Administration

    + +

    Provided system administration for a broad range of VAX/VMS + systems and VAX clusters

    + +

    Software Verification

    + +

    Participated on a team of validation members responsible for + testing various network protocols on proprietary product + platforms

    + +
    + +

    Technical Summary

    + +

    Extensive Experience in Rational's ClearCase and ClearCase UCM + for UNIX and Windows Vista/XP/2000/2003, ClearQuest, C#, .Net, + Visual Basic 6, Perl, C/C++, Qt, Make, Tcl/TK, Java, MS Access, SQL + Server, Oracle, Install Shield, GNU tools, C shell, Bourne shell and + BASH.

    + +

    Highly skilled in Windows, UNIX, and MacOS operating systems.

    + +

    Member of IEEE and IEEE Computer Society.

    + +

    Attended Rational Conferences in 1998 and 2000 (presentation + provided in 2000 on UCM)

    + +

    Strong communication skills. Strong interpersonal skills. Fast + learner.

    + +

    References: strong references available upon request.

    + +

    Citizenship: Canadian on TN Visa in US since 1997

    + + + +

    This resume is freely available; you can + redistribute it and/or modify it under the terms of the GNU + General Public License as published by the Free Software + Foundation; either version 2 of the License, or (at your option) + any later version.

    + +

    This resume is distributed in the hope + that it will be useful, but WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A + PARTICULAR PURPOSE. See the GNU General Public License for more + details.

    + +

    You should have received a copy of the GNU + General Public License along with this resume; if not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA.

    + + + + + + + + + diff --git a/web/Resumes/Kevin/Resume.doc b/web/Resumes/Kevin/Resume.doc new file mode 100644 index 0000000000000000000000000000000000000000..7e694080b7b3a7af44ebc5382c94afd27ee71db7 GIT binary patch literal 28160 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 + + + + + ClearSCM: Our People: Tom Connor + + + + + + + + + + +
    +
    + +

    Kevin L. Haralson

    +

    System Administrator
    + kevinharalson@ddct.org +

    MS Word format

    + + +

    Objective

    + +

    19 years experience in the IT field. 5 years as a manager, +overseeing projects, and/or budgets. 4 years in a technical sales +capacity, and 10 years as a Technician (Tech Support/Helpdesk to +Desktop/Hardware/Config Tech to System Admin to Network Admin).

    + +

    In the last 3 years I have been involved in building the +infrastructure for medical, legal and financial offices, including +installing most/all of their industry specific software. I've worked +in three large organizations (over 2000 employees) as an IT Tech, and +a number of smaller organizations (20 - 200 employees) from help desk +to director of IT.

    + +

    My hope is to be a part of an exciting environment of growth and +collaboration, or to help build a group/organization that possess +these qualities.

    + +

    Professional Skills

    + +
      +
    • Platforms: X86 (Standard PC, Linux), Sun, HP, Dell, DEC, Sony, + IBM
    • + +
    • O/S: MS Windows NT4.0 (Servers), 9.x, 2K(Servers & Pro), + XP (2003 Servers and Pro), Vista (SP1- testing), UNIX: Solaris, + Linux (installations, maintenance, and troubleshooting)
    • + +
    • Networking: AD, WINS, DNS, NIS, NFS, SAMBA, SNMP, LDAP, + VTP, VLAN, STP, RIP, BGRP, EIGRP, NTP, Trunking
    • + +
    • Backup: Legato, Solstice Disk Suite (Sun), + Veritas(Symantec) (Standard backup procedure or as apart of Disaster + Recovery solution)
    • + +
    • Cisco Switches/Routers/SBSC, Cisco Pix, SonicWALL, WatchGuard, + Barracuda, VPN (multi vendors), Gibraltar Firewall, Soho Firewall, + M0n0Wall (Soekris Device) (installation, configuration, maintenance, + and troubleshooting)
    • + +
    • MS Exchange (5.5, 2000, 2005), SQL, Sendmail, Postfix, Cyrus, + MySQL, SpamAssasin, ClamAV (configure, maintenance, and + troubleshoot)
    • + +
    • System Admin Responsibilities: Created in MS/Unix/Linux + (AD, LDAP, and NIS) accounts and directories (email, aliases, + passwords, dir. Sharing/ group policies).
    • + +
    • MacAfee Anti-Virus (Enterprise), Norton Anti-Virus (Enterprise), + Pest Patrol (Enterprise), Symantec Ghost Server (Enterprise), + Symantec Endpoint Protection, Barracuda Network Appliance, Untangle + Box
    • +
    + +

    Working Experience

    + +

    TeamLogic IT Santa Clara (TLIT), Dec 2008 - Current

    + +

    Senior Managing Consultant

    + +

    Responsible for fixing infrastructure/network/computer/phone +issues. From simple break-fix of a computer/server (Windows, Mac, +Linux) to installing a Cisco UC500 for a full office solution (5 - 64 +employees) (firewall/switch/router/VPN/WiFi/PBX). Administration of +remote managed machines in addition to above mentioned tasks.

    + +

    Abundant Life Christian Fellowship (ALCF), April 2005 - Sept +2008

    + +

    IT Director

    + +

    Responsible for all technological aspects within ALCF ($10 +million/year non-profit organization) as a manager/network +admin/system admin. Planned, implemented, and managed 3 infrastructure +rebuilds within as many years due to rapid growth of staff (from 32 +machines an 2 servers to 100 machines and 10 servers - site to site +VPN and redundant network connectivity). Implemented a hybrid +HelpDesk/Tech support utilizing staff, IT staff, consultants, +vendors. Brought email in-house to reduce cost and increase filtering +using Postfix, MySQL, SpamAssasin, ClamAV, and Cyrus (to setup +administration via web). Due to rampant virus and malware issues I +installed Symantec End Point AV and Pest Patrol, followed by a +Barracuda Web Content Filter. Established security policies and +procedures for internal and external network (IDS, ASA, NAC) by using +Snort/ Acid-Base (to monitor), Barracuda WCF (to block), creating +vlans and filtering on the Cisco Catalyst 2950 router and a Catalyst +6509 Switch. Planned, implemented, and managed backup/disaster +recovery solution. Managed 64 vendors.

    + +

    Employee Benefits Specialists (EBS), October 2004 - April 2005

    + +

    System Administrator

    + +

    Responsible for the day to day operational of all networked +systems, internal and co-located (6 servers, 48 desktops, 10 laptops, +2 Cisco Switches, 2 co-lo servers (Citrix controlled), WatchGuard +Firewall). Migrated mail server from Exchange 2000 to 2005. Maintain +and oversee all security (HIPPA Standards).

    + +

    Open Country, September 2004 - April 2005 (as Contractor)

    + +

    System/Network Administrator

    + +

    Created a multi-LAN'd QA Lab with Provisioning from a central host +machine used Shorewall to protect the intranet from the lab and the +lab from the intranet. Setup DNS, DHCP, NFS, Samba, and printing, in +the corporate intranet and within the lab. Setup numerous firewalls +(Gibraltar), with VPN capabilities. Fixed Windows and Linux machines +(hardware and software), created a /backup strategy/disaster recovery +plan, and implemented plan with little to no down time. QA testing for +OC Manager (OC Agent, OC Host, and OC Provisioning), on multiple +distributions of Linux (Red Hat 7.3, 9.0, EL3; Mandrake 10; SuSE 8.2, +9.1, SLES 8, SLES 9, JDS 2.0; Lineox; Asianux 1; Monta Vista; +Whitebox).

    + +

    Caminar Inc, September 2003 - June 2004 (Contractor)

    + +

    System Administrator

    + +

    Troubleshooting server, desktop, and network infrastructure issues +for a 100+ staff organization in multiple sites (10) throughout the +Bay Area. Suggested, tested, and implemented workflow procedures, +created documentation on all phases of work flow/troubleshooting +procedures. Setup a number of TCO applications to administer the +domain, setup and installed Linux Gibraltar Firewalls, and Cisco PIX +firewalls.

    + +

    Persistence Software Inc, September 1999 - April 2001 (employee) +(Contractor )January 2002 - July 2003 /August 2004 - January 2005 +

    + +

    System Administrator

    + +

    Provided Technical Support/System Administration which included: +creating/deleting user accounts (Unix and Windows), trouble shooting +problems (individual/systemic), installing MS and Unix (Sun) s/w and +applications, upgrading o/s with latest patches, backing up servers, +managing LDAP, installing h/w on servers (memory, hd, motherboards, +scsi controllers, ext hd), setup VPN. Shell scripting. Backup +Administrator, setup of environment and backup policies, +troubleshooting, and setup and document Disaster Recovery +procedures.

    + +

    Netscape Communications, Mountain View, April 1996 - July 1999

    + +

    Technical Support Engineer/System Administrator

    + +

    Coordinate resources between I.S. and Facilities for all project +moves, technician for all computer and network considerations, while +managing and balancing resources to ensure least amount of +downtime. Developed move procedures that would result in successful +moves (creation of shutdown scripts (VI), and trouble shooting startup +problems), and put in place policies that ensure the continuance of +these procedures.

    + +

    Haralson Consulting and Design, Santa Clara, September 1992 - 1995

    + +

    Owner/Computer Technician/ Technical Sales

    + +

    LifeScan, Milpitas, May 1992 - September 1992 (Consultant)

    + +

    Desktop Technician

    + +

    Apple Computers, Cupertino, July 1991 - March 1992 (Consultant)

    + +

    Computer Technician

    +
    +
    + + diff --git a/web/Resumes/Mohammed/Resume.doc b/web/Resumes/Mohammed/Resume.doc new file mode 100644 index 0000000000000000000000000000000000000000..2379d0636902e000662db7bd65020d75743c15a7 GIT binary patch literal 103936 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 + + + + + ClearSCM: Our People: Mohammed Ansari + + + + + + + + + + +
    +
    + +

    Mohammed Ansari

    +

    Project/Program Leader
    + SCM Architect
    + Configuration Management Specialist
    + mymsn@hotmail.com +

    MS Word format

    + + +

    OBJECTIVE

    + +

    Hands-on Project/Program Leader / SCM Architec Configuration +Management Specialist

    + + +

    Oversee, design, and provide solutions for +implementation of configuration management, process creation and +control, Software Quality Assurance Architecture/Framework, and to +enhance the overall quality of software configuration and release +control at the project, product, organization or enterprise level

    +
    + +

    SUMMARY

    + +
      +
    • Superior record of steady career progression over 20 + years working directly with some of the top IT consulting + firms (EDS, Capgemini, and CSC) while serving some of the + largest and global clients (US Treasury, + Fannie Mae, Census Bureau, Gulfstream Aerospace, + Ford, GM, and GMAC).
    • + +
    • Proficient in working with multiple stake holders, + multiple vendors, project resources, reporting to + sr. management, gathering requirements, documentation, + design, and presenting ideas for review/approval.
    • + +
    • Proven ability to implement organizational change and manage + enterprise SCM projects working with multiple teams and + geographic locations.
    • + +
    • Experience includes management of IT and SCM + projects.
    • + +
    • Work Ethics - Reliable, persistent, and + hard working to ensure project success. Take pride in + designing and implementing SCM solutions. Passionate about + implementing the best practical solutions to meet the + organizational goals while providing the highest ROI. + Always in-tuned in paying attention to the client, promptly acting + upon the direction from the senior management, and in building + lasting relationships with co-workers and project teams.
    • + +
    • SDLC - Practical experience and in-depth knowledge of + SDLC gained while supporting multiple software development projects + for multiple clients in various roles (QA, Software + Programmer, Technical Leadership, Management, + SCM Solution Design and Implementation). Hands-on + experience in Architecting, Designing, Developing data driven + applications using C/C++ and Java programming + languages.
    • + +
    • SCM - Over 11 years of experience in designing and + implementing SCM Solutions. Assessing as-is environments and + implementing practical solutions to meet organizational + needs. Expert knowledge of Source Code Control, Branching, SW + Integration, Baselines, CM Audits, Change, Defect, + Build, Release, and Configuration Management + practice. Hands-on experience with ClearCase, + ClearQuest, PVCS, VM, Dimensions, + SVN, CVS, Remedy, Mercury, and additional SCM + tools.
    • + +
    • Best Practices - Experienced in Implementing SCM + Solutions while utilizing SDLC, CMMi, ITIL, + ISO900, SEI, and IEEE best practices.
    • + +
    • QA - Over 5 years of hands-on experience working as a + Quality Control Analyst. Developed test plans and test cases + for C/C++, and Java applications running on Windows, UNIX, and + Mainframe environments.  Utilized testing tools for debugging + and generation of Metrics. 
    • + +
    • Databases - Thorough knowledge of relational + databases, architecture, SQL, PL/SQL, + schemas and hands-on experience in working with + Oracle, SQL Server, Sybase, DB2, Ingress, and + Access database.
    • + +
    • OS/Platforms - Extensive experience with all + major UNIX (Solaris, HP-UX, IRIX, AIX), Linux, and + Windows Platforms (NT, 2000/2003, XP, Vista, Windows 7), + including some experience with Mainframe.
    • + +
    • Automation tools- Proficient in design, development, and + implementation of SCM automation tools and scripts within SDLC and + Agile environments.
    • +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TECHNICAL SKILLS +
    AREA + TECHNOLOGY +
    OS NT, Windows 2000/2003, XP, + Unix (Sun Solaris, HP-UX, AIX), Linux (RedHat), MF
    Version ControlCVS, Subversion (SVN), + ClearCase, PVCS, VM, Dimensions, + Harvest
    Build & Integrationmake, ant, build forge
    App/Web ServersIIS, Apache, IBM Websphere
    Languages & ScriptingC, C++, Java/J2EE, VB, SQL, .NET, Ant, UNIX Shell (bsh/ksh/csh/tcsh), Perl, Python
    Defect Tracking ToolsClearQuest, Team Track, Remedy, Mercury
    DatabaseOracle, Sybase, DB2, MS SQL Server, MS Access, MySQL
    MethodologiesSDLC, RAD, Agile, Scrum, RUP, Waterfall
    Best PracticesITIL, CMMI Level 5, ISO9000, SEI, IEEE
    Miscellaneous ToolsVisio, MS Office Suite, MS Project, Clarity, Business Objects, Micro Strategy
    + +

    CERTIFICATION & TRAINING

    + +
      +
    • Borland Requirements Management Essential Training (2003)
    • + +
    • Project Management Training by Keane Consulting Inc. (2002)
    • + +
    • Systems Engineering & Systems Analyst Training by EDS + (1996 - 1998
    • + +
    • Oracle & PL/SQL Training by Oracle (2002)
    • + + +
    • Java Object Oriented Programming Training by Sun + Microsystem (2001)
    • + +
    • SAP Document Management Training by SAP (2001)
    • + +
    • ClearCase Training by Rational (2001)
    • + +
    • Dale Carnegie Relationship Management Training by Dale + Carnegie Institute (1991)
    • + +
    • CMMi Training (level 3 - 5 environments) provided by EDS + and Keane Inc. (1996 - 2003)
    • +
    + +

    PROFESSIONAL EXPERIENCE

    + +

    Enterprise SCM Architect (08/10 - 10/10)

    + +

    Independent Contractor Client - Fannie Mae, Herndon, VA

    + +
      +
    • Note that his assignment was shortened due to unexpected budget + cuts. Will provide excellent internal references from Fannie + Mae.
    • + +
    • Worked as an Enterprise SCM Architect and Process + Lead. Was responsible of Process Engineering and Operational + Support as listed below.
    • + + +
    • PROCESS ENGINEERING: Establish, document, and sustain + standardized change management processes specific to business + requirements, application defects, and application + enhancements. Train, facilitate implementation, and standardized + these processes across Fannie Mae enterprise.
    • + +
    • LIASON and COORDINATION SUPPORT: Based on defined + requirements, create / manage change requests. Schedule the change + based on project goals i.e. emergency fix, risk minimization, or + resources. Monitor approval of the requests and facilitate as + needed. Shakeout or test changes prior to turnover.
    • + +
    • OPERATIONAL SUPPORT: Perform tool administration for + Rational RequisitePro, ClearQuest and + ClearCase. Analyze and provide problem resolution on + technical issues. Identify potential improvements to increase the + reliability, availability, support, and + performance of the applications and infrastructure. Interface + with developers, test users; communicate with + technical, management, and external + teams to identify and resolve problems.
    • + +
    • ROOT CAUSE ANALYSIS: Investigate problems to determine + their root cause, including conducting research, interviews, and + evidence gathering; and writing reports of findings, conclusions, + and recommendations for subsequent actions.
    • + +
    • COLLABORATION: Collaborate with Quality and Process + Management architects to develop, maintain and improve process + automation, monitoring, and reporting + tools. Collaborate with application and infrastructure architects + and developers to maintain and improve the overall performance, + reliability, scalability, supportability of the application and + infrastructure.
    • +
    + +

    Lead SCM Specialist (03/10 - 08/10)

    + +

    ICS, Client - Department of Commerce (Census Bureau), Suitland, MD

    + +
      +
    • Joined ICS at the peak of 2010 Census to evaluate and streamline + SCM processes.
    • + +
    • Accessed existing processes to eliminate inefficiencies and + non-compliance. Provided recommendations and implemented solutions + to enhance and streamline SCM processes.
    • + +
    • Designed and Implemented defect tracking process utilizing + existing licenses of HP Mercury.
    • + +
    • Automated and streamlinedbuild process with + SVN and ANT. Implemented Agile Continuous + Integration process to automate daily builds, + releases, and deployments. Kept 100+ resources + (Developers, DBS, Testers, and Operational Support personal) + informed regarding the build, release and deployment schedules.
    • + +
    • Enhanced processes for making (DDL, DML, and + DCL) changes to Oracle (test, beta, and production) + databases.
    • + +
    • Implemented scripts to perform smoke test after each + deployment.
    • + +
    • Chaired and facilitated the daily Architectural Review + Board (ARB) and Change Control Board (CCB) + meetings.
    • + +
    • Responded to audit requests and worked with the auditors + to ensure compliance.
    • + +
    • Implemented scripts to continually monitor QA, BETA, + PRF, and PROD environments.
    • + +
    • Implemented scripts to send alerts in the event of + performance degradation or failure.
    • + +
    • Provided SCM support and training for new resources joining the + team.
    • + +
    • Implemented application versioning scheme.
    • +
    + + +

    Sr. SCM Architect/Process Lead (07/09 - 03/10)

    + +

    AlonInc, Client - US Treasury (Office of the Comptroller of the Currency), Washington, DC

    + +
      +
    • Worked as a SCM Enterprise Process Architect and + Process Lead. Responsible of reviewing, + designing, enhancing, and controlling SCM + processes at the enterprise level.
    • + +
    • Responsible of communicating, documenting, and + implementing CM policies.
    • + +
    • Responsible of evaluating existing SCM process and + tools, working with the SCM tool vendors + and suppliers to evaluate SCM tools and to + determine the best fit, provided recommendations to + streamline SCM processes at enterprise level to reduce service + disruptions and downtime for the user base consisting of + 4000+ user.
    • + +
    • Responsible of providing recommending and designing new + processes, presenting new ideas to the sr. management, receiving + authorizations to implement, enhancing existing processes, + updating SCM plans, procedures, flowcharts, and + documents.
    • + +
    • Provided training to the user base in using new tools and + processes (PMs, SW Developers, DBAs, and IT Operational Support + Personal).
    • + +
    • Provided a complete set of requirements to implement a + new defect tracking system using ClearQuest. This has + been designed, developed, implemented, and currently being used at + the OCC as an enterprise defect management system.
    • + +
    • Provided requirements and recommendations for implementing + CMDB.
    • + +
    • Implemented processes for conducting SCM audits within + the OCC environment.
    • + +
    • Implemented versioning scheme for major, minor, and + emergency releases.
    • + +
    • Implemented requirements for back-out procedures.
    • + +
    • Participated in CCBs, assessed risk for various + implementation plans, participated in release schedule + meeting, reviewed communication and deployment + plans, and provided weekly release readiness status to + the sr. management.
    • + +
    • Implemented processes surrounding the use of CM tools + (ClearCase, VM, PVCS) in branching, + merging, promoting code through various lifecycle + stages (development, testing, and production), locking source code, + and creating baselines.
    • +
    + +

    Sr. Software Configuration, Change, and Release Manager (06/08 - 05/09)

    + +

    Independent Consulting, Client - Intelizign, Bloomfield Hills, MI

    + +
      +
    • Designed and implemented an end-to-end Configuration + Management Plan for 80+ member team with a highly automated + build and deploy process on UNIX platforms.
    • + +
    • Implemented branching scheme for parallel + development, merging, and code integration.
    • + +
    • Implemented a web based defect tracking system accessible + 24x7 in the United States and India.
    • + +
    • Performed daily configuration, change, + build, and deployment activities.
    • + +
    • Chaired and facilitated CCB meetings.
    • + +
    • Managed test, beta, and production environments.
    • +
    + +

    SCM Architect and Process Lead (10/06 - 06/08)Capgemini, +Client - General Motors, Southfield, MI

    + +
      +
    • Worked as an Architect and Process Lead + responsible of overseeing the SCM activities for two separate + teams (software development, software maintenance/support) + consisting of over 350 resources located in four + geographical regions (Michigan, Chicago, Canada, and + India).
    • + +
    • Gathered corporate requirements from the project office, and project requirements from the stake holders to conduct initial assessment.
    • + +
    • Manage a geographically dispersed release management team + consisting of nine CMs.
    • + +
    • Planned, defined, received approvals, and + implemented the following CM processes and + tools in support of GM initiatives at the Capgemini + development center.
    • + +
        +
      • SW Development Process based on SDLC along with + a fully automated integration, build, and + deployment process to (test, beta, performance, and + production) environments.
      • + +
      • Configuration Management Process utilizing SVN and + VM
      • + + +
      • Sharing of test and development environments by the + development, QA, and the support team.
      • +
      + +
    • Implemented processes and created plans to + consolidate code into a single repository for + over 150 applications. The source code was initially + scattered over at multiple vendor sites in various geographical + locations. Identified and worked with the SCM Leads for each of the + applications to track, transfer, and integrate code into a single + repository.
    • + +
    • Provided SCM training to create + awarenessand to insure compliance with the SCM + policies and procedures.
    • + +
    • Participated in CCBsand release planning meetings.
    • + +
    • Conducted SCM audits on high visibility projects + to verify compliance. Provided audit reports to the + stake holders.
    • +
    + +

    Capgemini, Client - MDS Pharmaceuticals, Dallas, Tx

    + +
      +
    • Implemented a complete IT Asset Management solution for + MDS Pharmaceuticals based on CMDB within an ITIL and + FDA regulated environment.
    • + +
    • The complete solution was implemented in three phases.
    • + +
        +
      • Requirements gathering (detailed information on CIs and + attributes to be captured, maintained over its lifespan, and + retired).
      • + +
      • Architecture, design, and implementation of CMDB + and auto-discovery tools.
      • + +
      • Implementation of tools, training, and work + methods to analyze and reconcile the differences between the + CMDB and the data collected by the auto-discovery + tools. Implementation also included CMDB Audit Plan, + Physical Inventory Audits Plan, Missing Data Work + Instructions (WI), Data Normalization WI, Processing + Duplicate CIs, setting up templates along with the routing and + approval process to capture approvals prior to adding new CIs and + making changes to existing CIs, and more.
      • + +
      + +
    • This project was administered and managed from the Capgemini + Data Center located in Dallas.
    • + +
    • The project was implemented at an enterprise level and comprised + of over 6000 CIs located at multiple facilities in Canada.
    • +
    + +

    Software Development Manager (11/05 - 10/06) CSC, Client - +GD Gulfstream Aerospace Corporation, Savannah, GA

    + +
      +
    • Worked as a SW Development Manager managing multiple + projects within the VIPER portfolio of Client/Server and + Web based Applications. These applications were developed in + .Net using C#, VB, and ASP programming with SQL Server + backend.
    • + +
    • Responsible of client interface in managing daily activities, + interfacing lead engineers of various engineering + disciplines to design, enhance, and + re-engineer PLM/PDM process in Enovia/LCA + environment.
    • + +
    • Responsibilities included managing a team of BAs, Architects, + DBAs, Programmers, Testers, Administrators, and Network + Engineers to build, integrate, test, and deploy 3-tier + solutions.
    • + +
    • Provided direction to Configuration Managers in streamlining SCM + processes, ensuring proper baselines are created and maintained, + integrating, and managing source code received from multiple project + resources and vendors (IBM, Axiom, Dassault, and Insight) in the + Harvest repository.
    • +
    + +

    Software Development Manager (05/04 - 07/05)Keane Consulting +Inc., Client - General Motors Acceptance Corp. (GMAC), Southfield, +MI

    + +
      +
    • Managed a development portfolio of PC-Fast/OSCAR + applications

    • Managed a team of on-shore and + near-shore (Canadian team) of technical resources + (programmers, analysts, & DBAs).
    • + +
    • Acted as a conduit through which all work request and new + requirements were channeled, assigned, monitored, and approved.
    • + +
    • Ensured all PC-Fast source code and artifacts were properly + tracked and promoted in the PVCS repository.
    • + +
    • In-charge of ensuring adhering to the CMMi level 5 + processes.
    • + +
    • In-charge of ensuring all documents were up-to-date, approved, + signed-off, and properly tracked.
    • + +
    • Performed Release Management activities utilizing + CVS repository for the OSCAR application.
    • + +
    • OSCAR application was developed in Java/J2EE, on a + Websphere platform in a UNIX environment.
    • + +
    • OSCAR application also utilized server clusters for + load balancing and failover capabilities. Whereas + PC-Fast portfolio contained a set of client/server + applications that were developed using VB/VBA with SQL + Server backend.
    • + +
    • Administered CVS repository and users on a Linux + platform to manage OSCAR source code.

    • Wrote step-by-step + deployment instructions for IBM resources that controlled the + production environment. The instructions included: + +
        +
      • Configuration changes to the production environment.

        +
      • Deployment of Java/J2EE code on Websphere platform.
      • + +
      • DDL, DML, and DCL changes to the Oracle database.
      • +
      + +
    • Conducted Release Readiness reviews.
    • + +
    • Coordinated nightly deployments requiring + Keane, IBM, and HP resources.
    • +
    + +

    Configuration/Release Manager/PDM Metaphase Administrator (03/99 - 05/04) Keane Consulting Inc., Client - Ford Motors, Dearborn, MI

    + +
      +
    • Worked as a dedicated and people-oriented + Metaphase Administrator with a positive attitude.
    • + +
    • Administered Metaphase application on the distributed + UNIX platforms.
    • + +
    • Configured, Administered, and Managed + R&D, test, integration, and training environments.
    • + +
    • Implemented Metaphase patches, updates, and new releases.
    • + +
    • With every new Metaphase release, identified deprecated APIs in + the customized Metaphase code.
    • + +
    • Notified project teams to update customizations by removing + deprecated code.
    • + +
    • Worked as a gatekeeper and as a configuration manager in + a multi-vendor environment to receive, integrate, + manage, and build customized code. Vendors were + located at various facilities within the United States, Germany, and + India.
    • + +
    • Build and deploy C/C++ and Java code on the + UNIX platforms (HP-UX, AIX, and Solaris).
    • + +
    • Streamlined build and deployment process using makefiles, + Perl, and UNIX scripts.
    • + +
    • Created cron jobs and wrote Perl, UNIX, and SQL scripts + to automate maintenance and monitoring of + multiple environments.
    • + +
    • Maintained and updated backend Oracle databases.
    • + +
    • Created scripts to continually monitor and + alert on-call support personal when issues were + detected.
    • + +
    • Managed ClearCase VOBS, created branches + for parallel development, setup standard ClearCase + views to integrate and build code.
    • + +
    • Issued and tracked ClearCase branches to the project teams and + vendors.
    • + +
    • Created naming and numbering schemes for + major, minor, and emergency release.
    • + +
    • Worked with multiple vendors and project teams to + diagnose and resolve build issues.
    • +
    + +

    C/C++ Programmer/Configuration Manager (07/95 - 02/99) EDS, Client - General Motors, Warren, MI

    + +
      +
    • Under the guidance of the Senior Unigraphics development team, + designed, enhanced, and customized the Unigraphics code on + UNIX platforms (Sun and HP) using C/C++ programming + language.
    • + +
    • Customized Unigraphics code for the CAD designers to by + automating the manual time consuming tasks.
    • + +
    • Reduced design time for repetitive manual tasks through + customization and automation from weeks to hours.
    • + +
    • Participated in and facilitated code reviews. As a + facilitator ensured all of the changes identified during the code + review are correctly updated and checked-in to the SCM + repository.
    • + +
    • Played a key role as a configuration and release manager for + this project.
    • + +
    • Implemented proprietary EDS configuration management tools on + the UNIX platforms.
    • + +
    • Implemented version numbering scheme.
    • + +
    • Created makefiles to streamline the build + process.
    • + +
    • Created UNIX scripts to track, package, and + deploy correct versions of the code at various vendor + sites. Each vendor had a unique environment which required proper + versions to be installed.
    • + +
    • Created UNIX scripts to baseline, package, archive, and extract + proper versions from the archives.
    • + +
    • Maintained compliance with the ISO 9000 best practices and + requirements.
    • + +
    • Helped the organization and the project team in strictly + adhering to CMMi Level 3 guidelines and in achieving CMMi Level 3 + certification.
    • +
    + + +

    QA/Test Engineer (1989 - 1995) ICI, Client - Ford Motors, +Dearborn, MI

    + +
      +
    • Developed test design and test plan documents for the SBDS + Diagnostic System Software.
    • + +
    • Created test cases to test each of the SBDS diagnostic + routines.
    • + +
    • Worked with the QA team to identify bugs and log issues.
    • + +
    • Worked with the Software Development team and the Automotive + Engineers to resolve the identified issues.
    • +
    + +

    Association Memberships

    + +
      +
    • Capgemini Global
    • + +
    • North America Big 5 Consultants
    • +
    + +

    Education

    + +

    Anwar-Ul-Uloom College, Hyderabad, India

    + +

    Pre-Engineering with emphasis on (Math, Physics, and Chemistry), +completed (1980 to 1982)

    + +

    Oakland Community College, Auburn Heights, MI

    + +

    Automobile Technology Program, completed with GPA 3.8/4.0 (1986 to +1988)

    + +

    Electronic Data Systems, Southfield, MI

    + +

    EDS Core IT Systems Engineering Program, completed & ranked top 3% of the +class (1996 - 1998)

    + +

    With emphasis on Software Development, Process Analysis & +Design, Relational Databases, and Quality Control

    + +

    Lawrence Technological University, Southfield, MI

    + +

    Mechanical Engineering, fourth year incomplete. (1989 to 1993)

    + +

    References

    + +

    Available upon request

    +
    +
    + + diff --git a/web/Resumes/Ron/Resume.doc b/web/Resumes/Ron/Resume.doc new file mode 100755 index 0000000000000000000000000000000000000000..0b996dd0533ae9e32e29b591b0f6a374a88c439f GIT binary patch literal 69120 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 K=I`$h1^z!Y-iVU` literal 0 HcmV?d00001 diff --git a/web/Resumes/Ron/index.php b/web/Resumes/Ron/index.php new file mode 100755 index 0000000..c495591 --- /dev/null +++ b/web/Resumes/Ron/index.php @@ -0,0 +1,464 @@ + + + + + + ClearSCM: Our People: Ron Van Scherpe - Associate + + + + + + + + + + +
    +
    + +

    Ron Van Scherpe

    +

    1225 Vienna Drive #157
    + Sunnyvale, California 94089
    + (650) 722-0759
    + Email: rvanscherpe@gmail.com + +

    MS Word format

    + + + +

    Objective

    + +

    To obtain a Systems/Network Administration position, in a mixed + operating system environment.

    + +

    Summary of Qualifications

    + +
      +
    • 9 Years of System Administration in a mixed computing + environment: Unix & 2000
    • + +
    • 12+ years of work experience in Silicon Valley in a variety of + different positions.
    • + +
    • Excellent working knowledge of the following Microsoft Windows + operating systems: Windows 95, 98, NT, 2000 and XP.
    • + +
    • Hands on work experience with the following Linux + distributions: Red Hat, Mandrake and SuSE.
    • + +
    • Ability to compile Linux sources and kernel.
    • + +
    • Working knowledge of Solaris 6 and 7.
    • + +
    • Hands on experience with the following Linux/Unix desktops: + KDE and Gnome.
    • + +
    • Practical knowledge of Microsoft Windows technologies such as: + Active Directory, Domain Controllers, Global Catalog and + Bridge-head Servers.
    • + +
    • Understanding knowledge of networking protocols and + applications: WINS, TCP/IP, 802.11b wireless, DHCP, DNS, SAMBA, + NIS and NFS.
    • + +
    • Familiar with Cisco 2500 routers, Intel & Dell layer 3, + Packeteers, Sonicwall and Netscreen firewalls and Juniper + Network's SA-1000 VPN.
    • + +
    • Experience with HTML and various UNIX shells.
    • + +
    • Familiar with the following internet applications: Apache and + PHP.
    • + +
    • Familiar with the following open source CMS: Mambo, Drupal, + Xoops!
    • + +
    • Working knowledge of network monitoring tools: Nagios & + MRTG
    • + +
    • Hands on experience with the following Windows tools: Veritas + Backup Exec, Norton Anti-virus Enterprise Edition, Cygwin, + Lanware's NMS Console, Terminal Services, Remote Desktop and + Visio.
    • + +
    • Familiar with PC (IBM e335 servers, Dell 1850s, 2650s, 2850s) + and Sun (Ultra 5, Utlra 10, Ultra 80 & Ultra 450 Sunfire210) + hardware as well as Network Appliance and EMC filers.
    • + +
    • Familiar with Inter-Tel PBX systems.
    • + +
    • Understanding knowledge of T-1's voice T-1's, Frame-Relay and + DSL connections.
    • + +
    • Excellent communication skills, working with all levels of + upper management including VP's and CEO
    • + +
    • Experience in working in start-up environments.
    • +
    + +

    Experience

    + +

    Penwin Solutions, Sunnyvale, CA President & CEO

    + +

    04-present

    + +
      +
    • Formed a consulting business in 04 which specialized in + Windows and Linux environments.
    • + +
    • Contracted with Salira Optical Networks to maintain their + network, server and desktop infrastructure. Also installed an + Exchange 2003 server and migrated users mailbox from Exchange 2000 + to the new 2003 server./li> + +
    • Worked with a variety of home users and small business.
    • + +
    • Was a member of the Mountain View Chamber of Commerce.
    • +
    + +
    + +

    Silicon Optix, San Jose, CA, Network Administrator

    + +

    04-present

    + +
      +
    • One of 5 Network Administrators which spanned across multiple + sites, San Jose, Orlando, Toronto and Hannover.
    • + +
    • Main support for headquarters in San Jose supporting 25 + end-users which were mostly Execs including the CEO.
    • + +
    • Supported remote users, sales and executives along with + individuals in our Shanghai and Taiwan offices.
    • + +
    • Responsible for purchasing all new hardware and software for + site. Worked with a variety of vendors to get the best deal.
    • + +
    • Planned and implemented a Exchange 2003 upgrade which + consisted of upgrading Active Directory and removing Active + Directory Connectors prior to the upgrade due to previous Exchange + 5.5 servers. Was the first of 3 sites to perform the + upgrade.
    • + +
    • Installed and configured a server to run ClearCase.
    • + +
    • Installed and configured a second Domain Controller for + site.
    • + +
    • Cleaned up and organized server room by purchasing a server + cabinet and installing all servers in it.
    • + +
    • Installed a EMC2 SAN solution to replace a Dell PowerVault NAS + box.
    • + +
    • Was responsible for facilities, by making sure a card access + system was installed.
    • + +
    • Researched, priced, and implemented a web based Enterprise + helpdesk and asset manager application, so that end users could + submit trouble tickets and keep track of progress.
    • + +
    • Deployed Nagios and MRTG to monitor network and server + outages.
    • + +
    • Installed WSUS server which provided automatic Microsoft + updates to desktop and laptops.
    • + +
    • Revamped backup strategy by moving from 3 separate backup + servers to one and installing clients on all other servers.
    • +
    + +
    + +

    LinkIT, Portland, OR, Contractor

    + +

    04-04

    + +
      +
    • Hired on as a contractor to install and configure 8 Solaris + servers for training department for a client of LinkIT, Credence, + in Milpitas.
    • + +
    • Helped in the transitioning and cleanup of LDAP services
    • + +
    • Participated, as a team member, in implementing new networking + hardware throughout the Corporate Enterprise.
    • +
    + +
    + +

    Salira Optical Networks, Santa Clara, CA, IT Manager

    + +

    01-04

    + +
      +
    • IT Manager for a world-wide company residing in Santa Clara, + CA. and Shanghai, China, which consisted of a Windows 2000 + infrastructure, 80 users, 2 sites (Santa Clara & Shanghai), 150 + desktops running Windows NT 4.0, 2000, XP and 8 Windows 2000 + servers.
    • + +
    • Primary duties included: phone system administration, network + administration, server administration and desktop administration, + building security administration, 24 Hour on call support and + facilities management.
    • + +
    • Designed and implemented a Windows 2000 Active Directory + infrastructure which connected two sites(Santa Clara, CA and + Shanghai, China)
    • + +
    • Traveled to Shanghai where I installed a Windows 2000 + infrastructure, which consisted of a Domain Controller, Exchange + and ClearCase servers, Intel routers and switches as well as a + Sonicwall firewall, which connected the two sites together via a + secure VPN tunnel.
    • + +
    • Managed and guided system administrator in Shanghai + office.
    • + +
    • Installed and managed dual Exchange 2000 servers (Santa Clara + and Shanghai).
    • + +
    • Implemented Microsoft's RAS server so that remote users could + connect to the corporate network.
    • + +
    • Designed and configured multiple LANS using layer 3 switches + which allowed for VLANs.
    • + +
    • Recovered from a devastating network disaster by replacing the + core layer3 switch, which went bad, with a Linux box and 2 quad + Ethernet cards.
    • + +
    • Responsible for purchasing all IT capital equipment and + Software and vendor relations.
    • + +
    • Managed the planning and implementation of a company move, + twice, (phones, T-1s, network, servers, desktops, labs, etc.) to + new facilities, which consisted of managing outside consultants + and contractors.
    • + +
    • Planned and managed the upgrade of corporate PBX, which also + consisted of the installation of a voice T-1 and DIDS.
    • + +
    • Implemented a centralized corporate anti-virus software + solution to protect all desktops and servers from viruses.
    • + +
    • Maintained and rolled out service paks across all desktops and + servers.
    • + +
    • Wrote a corporate IT Policy explaining various functions of IT + and how it works to protect and serve the company.
    • + +
    • Proactively monitored a variety networking resources and + servers, using such tools as Lanware's NMS console, which provides + monitoring of Windows 2000 performance counters and application + services
    • + +
    • Worked with the Software Test Engineering team to help + troubleshoot network problems related to the company's + product.
    • + +
    • Designed and built a Software lab and network, using Linux as + a router/firewall into the lab.
    • + +
    • Designed and implemented a streaming video demo which ran + across the company's product, which was presented to future + Venture Capitalist firms.
    • +
    + +
    + +

    Copper Mountain Networks, Palo Alto, CA. Systems Administrator

    + +

    01-04

    + +
      +
    • Senior Systems Administrator for a site that included a user + base of Executives, Marketing and Engineers: 150 desktops and 25 + servers.
    • + +
    • Installed and configured various NT networking components in a + multiple NT domain environment: PDC, BDCs, WINS, and
    • + +
    • Exchange 5.5 servers.
    • + +
    • Implemented various networking services using both Linux and + NT: DNS, NIS, DHCP and Samba.
    • + +
    • Managed T-1s and administered the routers, which connected the + company to the other corporate sites, Fremont and San
    • + +
    • Diego as well as the internet.
    • + +
    • Installed and maintained inter-departmental websites using + Apache and Linux
    • + +
    • Configured Linux boxes as routers and firewalls for test + purposes in a lab environment.
    • + +
    • Created Audio/Video streaming servers, both on demand and + Multi-cast streams, using both Linux and Microsoft's Windows
    • + +
    • Streaming Media Server, to generate 'real world' traffic, for + use with in house test networks.
    • + +
    • Designed and built a 1,400 square foot Engineering lab which + consisted of centralized patch panels, telco racks, descending +
    • + +
    • cable trays, benches, power and storage areas.
    • + +
    • Installed, configured and administered a network of Sun + workstations and servers, Linux, NT and Win2K workstations in the + Software Engineering Lab.
    • + +
    • Hosted DNS and NIS for machines in the Software Engineering + Lab.
    • + +
    • Created a simulated network of SNMP agents (6000), using + multi-homed Linux work-stations (16) and JAVA, for internal + testing of Network Management Software.
    • +
    + +
    + +

    Ipsilon Networks/Nokia, Sunnyvale, CA. Systems Administrator

    + +

    97 - 98

    + +
      +
    • Provided help desk support for a user base of 150 users that + included a worldwide sales team.
    • + +
    • Supported a variety of operating systems (10+) in a complex IP + network.
    • + +
    • Performed daily Systems Administrator tasks: creating new user + accounts, setting up machines, Corporate backups and
    • + +
    • updating DNS and NIS records.
    • + +
    • Administered Firewall, CheckPoint and installed client + software, Secure Remote.
    • +
    + +
    + +

    N.E.T., Redwood City, CA. Systems Administrator

    + +

    96-97

    + +
      +
    • Established a network of SGI workstations for the mechanical + engineering team.
    • + +
    • Provided SGI training to the internal IT department.
    • + +
    • Provided help desk support, in an Enterprise environment, + which consisted of an engineering team (200+ users) with Solaris + workstations on the desktop.
    • + +
    • Participated as a team player, in the redesign of the + engineering network infrastructure, by planning the new lab layout + and consolidation of equipment into a smaller location.
    • +
    + +
    + +

    Adaptive, Redwood City, CA, Software Test Engineer

    + +

    93 - 96

    + +
      +
    • Developed, automated and implemented test cases, to test + complex broadband networking switch software, in a Unix + environment
    • + +
    • Managed and improved the software build process, by automating + the process with scripts, thus decreasing the build time by + 20%.
    • + +
    • Designed, moved, and managed Software Engineering Test Labs + and received an award.
    • +
    + +
    + +

    Adaptive, Redwood City, CA ManufacturingTest Technician

    + +

    91 - 93

    + +
      +
    • Built, tested, debugged and configured Sonet Broadband + Switches to support customer shipment schedules
    • + +
    • Documented test procedures.
    • + +
    • Trained manufacturing personnel on all system test + procedures.
    • + +
    • Assisted in the layout of both the system test and mechanical + assembly areas.
    • + +
    • Completed training required for ISO 9002 certification
    • +
    + +
    + +

    Network Equipment Technologies, Redwood City, CA Manufacturing + Test Technician

    + +

    89 - 91

    + +
      +
    • Effectively tested modules at a system level in a temperature + controlled environment meeting all production schedules.
    • + +
    • Assisted Advanced Manufacturing in troubleshooting test + scripts.
    • +
    + +
    + +

    Electronic Arts, San Mateo, CA, Customer Support

    + +

    88 - 89

    + +
      +
    • Assisted in resolving product related problems to meet + production schedule.
    • + +
    • Provided technical phone support to customers.
    • + +
    • Participated in beta testing of various software + packages.
    • +
    + +
    + +

    Education

    + +

    Unix Systems Administration Certificate - U.C. Santa Cruz Extension
    + Sun Networking Certificate - Sun Micro Systems
    + Windows 95 and Windows NT 4.0 Training - Mastering Computers

    + +

    Computer Skills

    + +

    Solaris, Linux, Windows 2000

    + +

    References

    + +

    Available upon request

    + + + + + + + diff --git a/web/Resumes/Tom/Resume.doc b/web/Resumes/Tom/Resume.doc new file mode 100644 index 0000000000000000000000000000000000000000..225ac87521197672c055fbb13ef4e4321f47306b GIT binary patch literal 35328 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 literal 0 HcmV?d00001 diff --git a/web/Resumes/Tom/Tom.png b/web/Resumes/Tom/Tom.png new file mode 100644 index 0000000000000000000000000000000000000000..1b2ce29bdb1f1dad5b81954ac3a77c546396fecc GIT binary patch literal 44434 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 literal 0 HcmV?d00001 diff --git a/web/Resumes/Tom/index.php b/web/Resumes/Tom/index.php new file mode 100644 index 0000000..80b7a41 --- /dev/null +++ b/web/Resumes/Tom/index.php @@ -0,0 +1,606 @@ + + + + + + ClearSCM: Our People: Tom Connor + + + + + + + + + + +
    +
    + +

    Tom Connor

    +

    Software Developer
    + Build/Release Engineer
    + Software Configuration Manager
    + tomhillconnor@yahoo.com +

    MS Word format

    + + +

    Synopsis

    + +

    Sr. Software Engineer with over 20 years of varied experience as a +software developer. Specializations in Software Configuration +Management, Release Engineering, Build Engineering, workflow +automation, Installer Development, and Deployment +Engineering. Creative problem solver with strong analytical and +communication skills.

    + +

    Technical Skills

    + +

    Langauges

    + +

    Perl, Java, C/C++, Visual Basic, Awk, XML, bash, korn, sh, +InstallScript, 4NT/4DOS shell, NT/DOS Shell, Ratfor, FORTRAN, +PostScript, Assembler

    + +

    Tools

    + +

    ClearCase, UCM, ClearQuest, ClearTrigger, MultiSite, cqperl, +clearmake, make, gmake, omake, Maven, Cruise Control, Ant, Visual +SourceSafe, PVCS, InstallShield, InstallAnywhere, InstallBuilder, +InnoSetup, CodeWright, Eclipse, Interwoven TeamSite

    + +

    Environments

    + +

    Redhat Linux, Sun Solaris, Cygwin, Windows XP/2k/NT/9x/3x, DOS, +other unix (Tandem Non-StopUX, AIX, HPUX, Solaris, QNIX), VM/CMS, +OS/TSO, VAX/VMS

    + +

    Business Experience

    + +

    Computer Sciences Corporation (11/2008 - present)

    + +

    Tools Writer, PDTools Team, MUOS program, General Dynamics, +Scottsdale, AZ

    + +
      +
    • The RAN tools team and other tools teams was reorganized into + the PD Tools team, with division-wide scope.
    • + +
    • I continue to support MUOS RAN, ClearCase, and ClearQuest, but + am now focused more on writing workflow automation tools for + MUOS-wide application, primarily in perl, utilizing the ClearQuest + API. The focus is much more on the Windows platform, though of + course I make the tools cross-platform where feasible.
    • +
    + +

    Computer Sciences Corporation (6/2007 - 11/2008)

    + +

    Software Configuration Engineer, SDE Tools Team, MUOS program, +General Dynamics, Scottsdale, AZ

    + +
      +
    • At close of Land Warrior program, I transitioned to a much + larger SCM challenge, joining the 15-person RAN Software Development + Environment Tools Team. The team uses ClearCase, ClearQuest, + CearTrigger, Build Forge, etc. to achieve SCM of a large + codebase. This is over 300 VOBs, 3 million lines of code, in a + complex hybridized UCM/Base-ClearCase environment, with lots of + triggers and heavy workflow automation.
    • + +
    • Writing GUI and command-line tools in Perl, Tk, and C++.
    • + +
    • Supporting SWIT teams with SCM, UCM project architecture, + workflow automation, and test automation
    • + +
    • Providing UCM project architecture, UCM administration, writing + UCM support tools
    • + +
    • Developing/documenting new and changed procedures
    • + +
    • Diagnosing and solving user problems.
    • + +
    • Diagnosing, proposing, implementing improvements to the CM tools + and workflow automation
    • + +
    • Lead for implementing automated testing of overall SCM workflow + automation.
    • + +
    • Doing ClearCase and ClearQuest administration
    • +
    + + +

    Computer Sciences Corporation (3/05 - 6/07)

    + +

    Build Engineer/Software Configuration Manager, Land Warrior +program, General Dynamics, Scottsdale, AZ

    + +
      +
    • I was asked to rejoin this development team, to lead the + 2-person SCM/Build/Release sub-team. This is a pure linux and + linux-embedded development effort using Open Source components, + in-house linux kernel and driver modification, and object-oriented + application development using C++.
    • + +
    • Responsible for SCM in a linux environment. Major tools: + Clearcase/UCM (Unified Change Management) and Bugzilla.
    • + +
    • Made major improvements in build and release automation. I + developed a toolset of 80+ scripts totaling over 5,800 lines of bash + and perl code.
    • + +
    • Responsible for document/data CM in a Windows environment. + Major tools: SubVersion.
    • + +
    • Wrote SCM plans that embodied best practices compliant with CMMI + Level 3.
    • + +
    • Supervised the design of Data Configuration Management + Plans.
    • + +
    • Developed SCM audit procedures.
    • + +
    • For enhanced change control, I improved the ClearCase/UCM + coupling to Bugzilla, our bug-tracking tool.
    • + +
    • Performed Linux and WinXP administration.
    • + +
    • Provided ClearCase/UCM mentoring and training to + developers.
    • + +
    • Performed ClearCase administration and troubleshooting.
    • + +
    • Designed/created ClearCase/UCM projects and project strategies, to + support both the development team and the automated testing + team.
    • + +
    • Developed makefiles.
    • + +
    • Developed RPM-based installation packages.
    • + +
    • Diagnosed and fixed build scripts and makefiles written by + others.
    • + +
    • Made contributions to the application code.
    • + +
    • Developed disaster recovery plans and carried out + procedures.
    • + +
    • Performed builds, prepared media, provided deliverables to the + customer.
    • + +
    + +

    JP Morgan Chase Investment Bank (11/04 - 3/05)

    + +

    Build Engineer/Software Configuration Manager

    + +
      +
    • Worked as an employee in the Houston Development Center.
    • + +
    • Worked with Ant, Maven, and Cruise Control in a + Java/BEA/Websphere shop.
    • + +
    • Worked on a ClearCase UCM implementation team, developing + policies and standards for UCM implementation and working with + individual teams, taking them through the process of conversion + (typically from CVS) to Clearcase and UCM.
    • + +
    • Left to heed the call to return to the team I left at CSC. JPMC + says please consider them in futery -- I’d be welcome back.
    • +
    + +

    Computer Sciences Corporation (5/04 - 10/04)

    + +

    Build Engineer, Land Warrior program, General Dynamics, Scottsdale, AZ

    + +
      +
    • Became part of a two-man Software Configuration Management and + Build team administering Linux and Clearcase in a 20-developer shop. +
    • + +
    • In a pure Linux shop, doing Clearcase UCM management, Clearcase + administration, builds, build automation.
    • + +
    • Laid off, along with most of the team, because of a work + stoppage.
    • +
    + +

    Dell (3/04 - 4/04)

    + +

    Build/Installer Engineer

    + +
      +
    • Joined a 10-person PowerEdge/OpenManage installer team.
    • + +
    • Although originally hired to fix java bugs in an older suite + installer, I worked with InstallShield DevStudio 9, setting up build + scripts for complex msi/msm builds and doing similar things for + producing Linux RPM builds.
    • +
    + +

    Dell (6/03 - 3/04)

    + +

    Build/SCM Engineer

    + +

    Joined the 6-person PowerEdge/OpenManage SCM Team, to assist with a +massive migration into ClearCase Multi-Site/UCM, and to +design/implement a CMM Level 2 compliant Build Management Facility. +However, the BMF was shelved and I took on the task of developing +extended monitoring and reporting capability around the ClearCase and +UCM activities.

    + +

    Current responsibilities are:

    + +
      +
    • Writing report-generators for management’s view into the + activities of teams based in ClearCase MultiSite/UCM.
    • + +
    • These custom reports track development efforts by reporting and + doing metrics on UCM projects, activities, baselines, streams, and + components, and provide notification capability for conditions that + may need attention.
    • + +
    • These reports run daily and are presented on the SCM internal + website. In addition, users can generate their own reports as + needed. The report generators, 7k lines of Perl, are in the form of + several scripts that share a re-usable library of common code.
    • + +
    • Devised an automated regression-test framework, following Kent + Beck’s Extreme Programming approach of first writing the test, + making sure it fails, then writing the functionality that will make + the test succeed. The result is that most of the functionality can + be regression-tested with a single command, and this has been a big + help in rapidly evolving these highly visible reports without a lot + of error.
    • + +
    • As a part of the reporting/monitoring effort, driving a project + to create a shadow database in SQL Server, for the purpose of + providing a more general query/drill-down/reporting capability. + This involves directing the work of a DB developer, and getting + requirements from three managers. In addition, suggested using + Rational ClearQuest as a database engine, to provide this database + engine capability, have received approval, and we are about to + engage in an evaluation.
    • + +
    • Have learned how to write and interface custom reports to + ClearCase Report Builder.
    • + +
    • Run a bi-weekly meeting with the monitoring/reporting + stakeholders, 6 representatives from various management, + development, and test areas, for the purpose of iteratively + gathering requirements, in order to ensure the project satisfies + it's customer's needs.
    • + +
    • Consult with teams on build issues and build automation + design.
    • + +
    • In charge of driving the SCM Team’s process improvement + effort, which involves facilitating a weekly SCM team meeting and + associated process improvement supervisory activities.
    • + +
    • Identifying needs and writing ClearCase triggers for workflow + enhancement.
    • + +
    • Setting up ClearCase and NT Server monitoring processes.
    • + +
    • Provided scripts to help teammates with migration efforts, for + example to correct case errors in element names when doing secondary + imports from SourceSafe to ClearCase.
    • +
    + +

    Marsh PM Internet Technology Group (9/02 - 11/02)

    + +

    Consultant

    + +
      +
    • Was hired to create the next generation company-wide automated + software production system. This consisted of two efforts:
    • + +
    • Determine requirements and specify the release engineering + process.
    • + +
    • develop an automated build system:
    • + +
    • Deterministic: build system completely determines build + environment so that build proceeds from a known state.
    • + +
    • Repeatable: build state info is archived so that past build + environments can be re-constructed.
    • + +
    • Accountable: all build states, activities, and outputs are + recorded in a structured way.
    • + +
    • Extensible: the build system framework is independent of that + which is built. A well documented interface permits new build + inputs and outputs to be spedified.
    • +
    + +

    Interwoven (9/01 - 8/02)

    + +

    Senior Release Engineer

    + +
      +
    • Designed/implemented an automated dependency-tracking system
    • + +
    • Interwoven needed an accurate and automated way to determine + exactly what files to include in a service pack or patch.
    • + +
    • Made major contributions to the design, and did the + implementation of a system that compares the current build to the + baseline build, and provides this list of only files that have + changed in a meaningful way. This meant writing sophisticated + language-aware tools for one-time initialization of over 100,000 + source files, plus new workflow and build-time production + modules.
    • + +
    • Wrote these in Perl, such that they could run on both UNIX and + Win2k platforms. The work required that I gain an in-depth + understanding of the internals of both UNIX and Windows versions of + TeamSite 4.2-5.51, the source file organization, the source code + branches and branching strategy, source generators and the complex + Interwoven build system.
    • + +
    • Since tracking the dependencies required embedding tracking + information in source and built files, I had to understand compiler + output formats (C, C++, and Java), as well as understand the syntax + of XML, HTML, ASP, and JSP files, in order to create a scanner that + could locate and read the tracking data in built output + (i.e. executables, Java JAR/WAR/EAR files, etc.)
    • + +
    • As part of this effort I also developed a sophisticated perl + TeamSite Workflow module for the source file submission + workflow.
    • + +
    • maintained complex Windows and UNIX service pack + installers.
    • + +
    • These were being put together by hand, by copying the previous + service pack’s source. There was no source control. The UNIX + service pack install scripts were Bourne shell based. The Windows + service pack installers were based in InstallShield 5.5 or 6.3, with + custom InstallScript. When I started, there were many versions of + these installers, each with slightly different functionality, and a + new one was created for each new service pack release. Devised + methods and scripts to more effectively reuse existing code. This + involved comparing all the installer variations and re-writing large + parts of the installer code, to support modularized functionality. + Once the installers were properly modularized, I aggregated all the + variations into a single configurable installer, which I made + configurable from property files. I did this for both UNIX and + Windows installers.
    • + +
    • During this process I discovered that locked file support was + not being used in the Windows installers, and I implemented this + feature, which involved enhancing the way NT services were + stopped/started versus when to reboot, and how to ensure that + post-install configuration steps do indeed get postponed until after + the reboot, if there was one.
    • +
    + +

    Trilogy Development Group (5/98 - 7/01)

    + +

    Build Manager and Installation Engineer

    + +
      +
    • Led Trilogy’s software build and installation teams and + trained/mentored 5 other team members. This team produced builds + and installers, but also functioned as a company-wide advocate for + Software Configuration Management/Release Engineering best + practices.
    • + +
    • Successfully improved Trilogy’s software build process. + Technology platforms included C++/VB/COM+/Windows, EJB/J2EE, and + other cross-platform Java. This was a complex build system that + produced builds for weekly releases of over 50 products in several + suites. When I started, there were 5 build machines and one server. + I automated many of the manual steps, permitting the system to grow + to 20 build machines and 6 servers, with web-driven hourly + builds.
    • + +
    • Helped develop a cutting edge in-house source control system, + using ClearCase as an engine. I developed procedures for converting + source trees from the old systems to the new system. Contributed to + the modification of the build system to accommodate the new source + control system.
    • + +
    • Working with a PhD in SCM and a ClearCase expert as teammates, I + learned how to administer a complex Rational ClearCase source + control system consisting of hundreds of VOBs and thousands of + Views. I contributed to strategy for VOB automated backups, and + View and VOB organization. I gained experience with command line + usage of ClearCase, and how to call it from Perl scripts. While + solving a subtle and recurring intermittent NT server crash, I + became aware of some of ClearCase's limitations, and it's + implications for our administration strategy. I participated in + discussions on trigger strategy, and helped analyze how to implement + ClearCase Multi-Site between Austin and India.
    • + +
    • Administered large (400,000 files!) PVCS Version Manager + system
    • + +
    • Administered large (900,000 files!) Visual SourceSafe system. + As the main company resource, I worked with groups in Core + Development, Consulting, and several of Trilogy’s spinoffs.
    • + +
    • mentored disparate groups on best usage practices for using the + main database, and for administering their own database, if + justified. This included describing VSS design flaws, admonitions + about the impact that renaming has on repeatability of builds, the + difference between deleting and purging, the sanctity of the + archive, how to avoid corrupting the archive, backup strategies, + when to branch, etc.
    • + +
    • Interfaced the automated build system to VSS databases from the + command line.
    • + +
    • Wrote conversion scripts when we moved on to another source + control system.
    • + +
    • Set up customer-specific secure VSS databases, accessed by our + on-site consultants.
    • + +
    • Upgraded main database to a RAID 5 server, and improved the + backup strategy.
    • + +
    • Devised best practices for other VSS administrators to + follow.
    • + +
    • Analyzed and fixed multiple VSS databases.
    • + +
    • Analyzed impact of moving from VSS 4 to VSS 5 to VSS 6.
    • + +
    • Determined the main company VSS database was too large and + devised the strategy for dividing it into smaller pieces.
    • + +
    • Administered and improved multiple build system NT servers and + source control NT servers, as well as the 10-20 build machines.
    • + +
    • Developed tools to further automate the build and release + process
    • + +
    • enhanced complex InstallShield-based Installers. These + installers were re-usable, configurable from sets of INI files, and + consisted of 30,000+ lines of InstallScript.
    • + +
    • Developed Java-based cross-platform installers using + InstallShield MultPlatform, including writing custom java + extensions.
    • + +
    • Helped develop Java-based cross-platform installer to replace + InstalShield MultiPlatform
    • + +
    • Developed automated source escrow generation system
    • + +
    • Helped develop an automated test system. Trilogy wanted to + create a web-driven automated test lab to support an Extreme + Programming inspired automated test framework. I provided input on + how best to layer software onto a test machine, starting with a + ghost image of the desired basic OS, followed by optional + re-packaged installations of Oracle, Office, etc.
    • + +
    • Served as a resource for the developers and QA engineers on + source code
    • Integrated third party tools and components + into the build and release process. + +

    • Interfaced with Product Management, Consulting, Development, and + QA to develop and implement product installation, packaging, + licensing, and distribution strategy.
    • + +
    • Performed scheduled and on-demand software product builds for + Development and QA
    • + +
    • Developed and implemented product-wide configuration management + processes and practices to support new and existing products.
    • + +
    • Developed revision control and software process policies and + interacted with groups across Development and Consulting on build + and release issues.
    • +
    + + +

    Data Strategies International (11/97 - 4/98)

    + +

    Consultant

    + +
      +
    • Wrote Windows device driver for Network Packet Driver (16 bit + DLL, written in C)
    • + +
    • Advanced Micro Devices
    • + +
    • Worked on improvements to AMD’s software test and simulation + facility
    • +
    + +

    VTEL Corporation (11/94 - 11/97)

    + +

    Staff Engineer, Windows Programmer (18 months as consultant, +then 18 months as employee)

    + +
      +
    • Devised an ownership protocol whereby Diagnostics and + Conferencing software could safely share the hardware. Implemented + in C++ as a DLL with a C API, this centralized all hardware access + services.
    • + +
    • Ported DOS-based diagnostics to Win32 platform, and + designed/implemented GUI.
    • + +
    • Responsible for all Plug and Play aspects of VTEL hardware.
    • + +
    • Developed sophisticated software installers using InstallShield + InstallScript.
    • + +
    • Created the Build Team and trained the personnel.
    • + +
    • Provided Windows development support
    • + +
    • Analyzed and fixed problems with low-level DLLs
    • +
    + +

    Computer Task Group (10/91 - 11/94)

    + +

    Consultant

    + +
      +
    • Database development in C on a QNIX-based distributed + environment.
    • + +
    • Dell Computers
    • + +
    • GUI development using VB
    • + +
    • Tandem Computers
    • + +
    • Test automation development for testing UNIX real-time kernel + processes.
    • +
    + +

    Shafir, Inc. (10/91 - 11/94)

    + +

    Cofounder, Shafir, Inc.

    + +
      +
    • founded this startup in the computer mapping and eCommerce + space. Two co-founders and three employees.
    • + +
    • Developed GPS-enabled street-mapping graphics software using VB + and VC++
    • + +
    • Did Java toolkit development. We won a JARS Top 5% Award.
    • + +
    • Did Java-based business graphics toolkit
    • + +
    • Wrote a Java-based installer
    • +
    + +

    Frank Russell Corporation

    + +
      +
    • Provided graphics driver support for Impressionist-based shop + producing thousands of graphs and charts per month.
    • +
    + +

    Execucom

    + +
      +
    • Eight years as software developer on Graphics Team (5 years as + Lead for the Impressionist team)
    • +
    + +

    EDUCATION

    + +
      +
    • BS in Physics, University of Texas, Austin
    • + +
    • Graduate work (60 hrs) in Theoretical High Energy Nuclear + Physics, Texas A&M University
    • +
    +
    +
    + + diff --git a/web/addendum.php b/web/addendum.php new file mode 100644 index 0000000..f6ee8dd --- /dev/null +++ b/web/addendum.php @@ -0,0 +1,165 @@ + + + + + + ClearSCM: Addendum + + + + + + + + + + + +
    +
    +

    Contract Addendum

    + +

    MS Word Copy

    + +

    This addendum is between ClearSCM, Inc. or a representative + from ClearSCM, Inc., hereafter known as Contractor and                                  , + hereafter known as Agent or Agency, with + respect to the project at                                  , hereafter known as + Client, and is made on                                  . This addendum supersedes + or augments any prior agreements made between Contractor + and Agency regarding any engagements with the + Client, including but not limited to the afore-mentioned + project.

    + +

    Terms: ClearSCM's terms are Net/30 with a 2% late charge + that will be applied to the invoice amount for any invoices not + paid in the initial 30 day period. Exceptions can be made for the + first pay period and if so are stipulated here:

    + +

                                                                                                                                                                                                                       

    + +

    Contract Duration: The contract duration shall be at + least from the agreed upon start date. If the contract is + terminated for any reason except non-performance, then an early + termination fee will be assessed:

    + +
      +
    • For contracts of 6 months or less - 30 day early termination + fee
    • + +
    • For contracts longer than 6 months - 60 day early + termination fee
    • +
    + +

    This early termination fee will be equal to 30 (or 60) days of + assumed 8 hour billable duration and will be assessed against the + Agent. Standard rate and terms apply.

    + +

    Non-performance shall be solely judged by the Client, and + concerns over non-performance shall be provided in written form to + the Contractor by the Client. The Contractor shall + always have a reasonable period of time from the date of the + written notice to improve performance to the Client's satisfaction + before the contract may be terminated without incurring the early + termination fee.

    + +

    Rate Disclosure: The hourly rate that the Client is + paying the Agent is:                                  . The rate that the + Contractor is billing the Agent is:                                  .

    + +

    After Hours Work: The Contractor is willing to + work hours above and beyond the standard 8 hours a day and/or 40 + hours a week and is willing to carry a pager or be “on + call” to the Client. No hours will be billed for being + “on call.” However any hours actually worked as a + result of being called into action in (beyond the standard 8 hours + a day/40 hours a week) will be billed at the standard rate. The + Contractor does not negotiate or juggle compensatory time + off for hours worked after hours.

    + +

    Vacation/Holidays and Furloughs: The Contractor + normally works 8 hours a day for 5 days in a standard work week of + 7 days. Standard holidays are observed:

    + +
      +
    • New Year's Day (January 1)
    • + +
    • Birthday of Martin Luther King, Jr. (Third Monday in + January).
    • + +
    • Washington's Birthday (Third Monday in February).
    • + +
    • Memorial Day (Last Monday in May).
    • + +
    • Independence Day (July 4).
    • + +
    • Labor Day (First Monday in September)
    • + +
    • Columbus Day (Second Monday in October).
    • + +
    • Veterans Day (November 11).
    • + +
    • Thanksgiving Day (Fourth Thursday in November).
    • + +
    • Christmas Day (December 25).
    • +
    + +

    Holidays are considered non-billable days unless the Client + requests, and the Contractor agrees, that the + Contractor should work that holiday. Holiday billable rate + will be 2 times the normal billing rate.

    + +

    The Contractor agrees to make reasonable accommodations + to work within the Client's vacation schedule and will give at + least 2 weeks notice before any vacation time it taken. If the + Client shuts down or furloughs their company (thereby preventing + Contractor from working under the contract) then there will + be a $2000 furlough fee assessed against the Agent for each + week the Contractor is unable to work under the Contract. + This fee shall be pro-rated per business day (rounded up), in the + event only a partial week is lost to furlough..

    + +

    Pre-existing art / Copyright: ClearSCM utilizes a + library of development / diagnostic tools. Such tools contain code + both written by Contractor and by others (e.g. GPL'd + code). To the extent of Contractor's copyright interest in + the tools, Client is hereby granted a royalty-free license to use + these tools in-house for the duration of the contract, including + modifications made by Contractor during the contract. Upon + full payment by Agency under the contract, Client's + royalty-free in-house license becomes a perpetual one. This license + notwithstanding, unless otherwise agreed to in writing, ClearSCM + retains the rights to all code developed prior to the start of + this contract or during this contract.

    + +

    Contractor offers NO WARRANTY regarding the tools + (regarding performance, functionality, non-infringement of + copyright or patent, or on any other matter), and EXPRESSLY + DISCLAIMS any warranty that might be implied by law. Such + DISCLAIMED WARRANTIES include, but are not limited to, the IMPLIED + WARRANTY OF MERCHANTABILITY, THE IMPLIED WARRANTY OF FITNESS FOR A + PARTICULAR PURPOSE, AND ANY IMPLIED WARRANTIES OF + NON-INFRINGEMENT.

    + +

    + +

    BY:                                                                                  DATE:                           
    +      ClearSCM, Inc.

    + +

    + +

    BY:                                                                                  DATE:                           
    +      Agent

    + +
    + + + + + + + diff --git a/web/businesscard.html b/web/businesscard.html new file mode 100644 index 0000000..91a2e41 --- /dev/null +++ b/web/businesscard.html @@ -0,0 +1,96 @@ + + + + Business Card + + + + + + + + + + + + + + + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + +
    President
     1742 Seagull Court #101
    The power to see clearly...Reston, Virginia 20194-4309
    Professional SCM ConsultantsPhone: (408) 596-4937
    +
    +
    +
    + + diff --git a/web/clearcase/EvilTwin.php b/web/clearcase/EvilTwin.php new file mode 100644 index 0000000..ff3171c --- /dev/null +++ b/web/clearcase/EvilTwin.php @@ -0,0 +1,152 @@ + + + + + + + ClearSCM: Clearcase: Triggers: Evil Twin + + + + + + + + + + + + + + + +
    +
    + +

    Evil Twin Trigger

    + +

    This trigger prevents the creation of Evil Twins. An + evil twin is where a user attempt to create a new Clearcase + element with the same name as an element that was previously + created, perhaps on another branch of the parent + directory.

    + + +

    What are Evil Twins?

    + +

    Simply put, an evil twin is a condition that can be caused in + Clearcase when a user attempts to add an element to source control + that has a name that is the same as an element on another + branch. If it is allowed to be created it will be difficult if not + impossible to merge in the future. You don't want evil twins to be + created to start with.

    + +

    Let's look a little bit deeper into how this can happen. Let's + assume that a user adds the element named "foo" to the directory + bin. Sometime later that element name is removed with + rmname. Finally assume that it is decided that foo is again + needed. When it is recreated the second time you will be creating + an evil twin because Clearcase will get confused between foo in + say version 3 of the parent directory and this new foo destined + for say version 7 of the parent directory.

    + +

    Here's a few steps to create the evil twin scenario:

    + +
    +$ # First check out the current directory and create foo
    +$ ct co -nc .
    +$ echo "bar" > foo
    +$ ct mkelem -nc foo
    +$ # Now check it all in
    +$ ct ci -nc foo .
    +$ # Now check out the parent directory and rmname foo
    +$ ct co -nc .
    +$ ct rmname foo
    +$ ct ci -nc .
    +    
    + +

    At this point we have a directory with foo in it and then the + next version of the directory has foo rmnamed + (i.e. uncataloged). Now let's attempt to create an evil twin:

    + +
    +$ # Let's create foo's evil twin
    +$ ct co -nc .
    +$ echo "Evil Twin" > foo
    +$ ct mkelem -nc foo
    +    
    + +

    At this point we should see the following dialog box preventing + the creation of the evil twin:

    + +

    Insert dialog box here

    + +

    This is telling us that we are about to create an evil + twin. Note that it also tells us where it found the first twin in + the view extended syntax (the part starting with @@). There is + another dummy in the bin directory in "andys_branch" version + 1.

    + +

    Merging the Original Elements Back

    + +

    The preferred way to resolve this problem is to merge the + original elements back from the proper directory version of the + parent directory into the current branch. To do this you must + first locate the branch where the evil twin existed. From the + above example that would be + \main\Andrew_Integration\adefaria_Andrew\3. Locate that version of + the parent directory (e.g. the adm\bin directory of andy vob in + the above example) in the Clearcase Version Tree Browser. To be + clear, locate the parent directory for the element dummy in the + Clearcase Explorer, right click on it and select Version Tree then + look in the version tree for the + \main\Andrew_Integration\adefaria_Andrew\3 version.

    + +

    Note: A good way to find this directory version in + directory elements that have large or complicated verion trees is + to use the Locate toolbar button (the button with the "flashlight" + icon). You can search for versions by version name, branch, + etc).

    + +

    Next right click on that version (the version of the parent + directory that has the original elements) and select Merge + to. Your mouse cursor will change to a little "target" icon. Next + select the version of the directory that your view selects (this + can be found by locating the little "eye" icon).

    + +

    Just before you select OK to start the merge make sure you + toggle on the Merge the element graphically toggle. This will + start cleardiffmrg and prompt you to select each merge.

    + +

    This will bring up cleardiffmrg and allow you to confirm each + merge of the diredtory. During the merge choose the entries from + the parent directory for the elements that you wish to "recover" + or "reinstate" .

    + +

    Another way to resolve this condition is to hardlink to the + previous version of this element but this is not always what you + want to do. For one it can be confusing. In any event if you need + help because you've hit and evil twin be sure to contact the Help + Desk and we'll help you out.

    + + + + +
    +
    + + + + + diff --git a/web/clearcase/OpenSourceBuild.php b/web/clearcase/OpenSourceBuild.php new file mode 100644 index 0000000..5838a01 --- /dev/null +++ b/web/clearcase/OpenSourceBuild.php @@ -0,0 +1,206 @@ + + + + + + ClearSCM: Open Source Builds + + + + + + + + + + + + +
    +
    + +

    Open Source Builds

    + + +

    More and more organizations are using Open Source in their + product builds but is the Open Source build mechanisms efficient? + This article approaches this subject and shows how often Open + Source can be more trouble than it's worth.

    + +

    Open Source Model

    + +

    Much hype has been given to the Open Source movement and + rightfully so. Developers can leverage off of Open Source + development and modules. This article will not address Open Source + in general nor will it go into the legalities of using Open Source + in your product. It will instead focus on common Open Source + building mechanisms in light how efficient or inefficient they may + be when included in your own build mechanisms.

    + +

    Problems with code sharing

    + +

    Unless you employ people who are active in the Open Source + community, people who not only participate in using Open Source + but also contributing to Open Source, you will enevitably come + face to face with a real problem. If you try to improve the Open + Source code in any way, unless you donate your changes back to the + community at large and those changes are accepted, you will + run into the fact that when the next version of the Open Source in + question comes out you will have porting work to do. You will need + to incorporate your changes with changes from the whole + community. In some cases these changes may be done by the + community in a similar manner as you had done them. In such cases + you can abandon your changes and take the communities solution and + then there is one less conflict for you to worry about.

    + +

    Other times the communities change is similar to your change + but differs enough that you still have to make some minor + adjustments. Sometimes you can come up with a more generic way to + doing something that will make everybody happy. In such cases you + should really consider donating your changes back under the "what + comes around goes around" principal. Then next update your generic + solution will not need to be merged again.

    + +

    Still other times what you need to do is not like what anybody + else needs to do or wants. Or it maybe that while your solution is + brilliant for the limited set of architectures that you are + considered about the community needs to be concerned about a large + or different set of architectures and thus cannot accept your + solution as a general solution that is good for all. In such cases + you are stuck with maintaining your solution for each iterration + of the module in question.

    + +

    Most developers can relate to the above few paragraphs from an + "inside the code" level. But what is often overlooked is that part + above the "inside the code" level - at the build and release + level.

    + +

    Building Software Efficiently (AKA Build Avoidance)

    + + + + + +
    + +

    In the beginning there was make(1) and it was + good...

    + +
    + +

    Earlier on most software was built using the standard Unix + make(1) utility. Make seeks to build only that which need to be + build. Make uses a number of assumptions in order to perform its + magic. For example, make assumes that you are using 3rd generation + languages such as C, FORTRAN, etc. Further make assume you have + all of the source contained in files in the file system and that + the source code transforms into object code of some kind using + some process (e.g. foo.o is derived from foo.c using the C + complier).

    + +

    As more and more languages evolved luckily make was able to + adapt and you could add new transformation rules and tell make how + to transform these newer language source files into their + respective derived object files and how to piece everything + together. Further you could enhance and automatically define + dependencies in order to have your build system remain efficient + and continue to try to achieve that all elusive "rebuild only that + which requires rebuilding".

    + +

    However make is easily thwarted if an eye on how make works and + how to use it efficiently and effectively is not paid mine. For + example, since make uses files and their timestamps in order to + determine if a target needs to be rebuild, putting a bunch of + functions into one large file is not a good idea since any change + to any of those functions will result in that whole file being + recompiled. However, one file per function is the other extreme of + this. In most software projects related functions comprising some + group of related software, a module, is a good compromise between + these two extremes.

    + +

    Using Source RPMs

    + +

    One popular construct in the Open Source world is that of + source RPMs. RPM stands for Redhat Package Manager and was + Redhat's answer to the question of how to install software on a + Linux system. But rpm when farther than that to include what it + calls Source RPMs. The concept is simple but also beautiful. While + an rpm is considered a binary install package a source rpm (AKA + rpms) contains all of the source and related other files like + makefiles, installation scripts, etc. In short everything is in + there for you to build the package from scratch. This is usual on + Linux systems as there are many systems on different architectures + where a package needs to be compiled before it is installed on the + system.

    + +

    Many companies are taking Redhat Source RPMs and then modifying + only those packages that they wish to change. Other packages are + rebuilt from source untouched. This allows developers to + essentially build their own complete system with their changes + incorporated. A pretty ideal setup - but are RPM Source builds + efficient?

    + +

    RPM Source Builds

    + +

    Turns out that RPM source builds are not efficient at all. In + most cases everything gets recompiled everytime. One reason for + this is that source rpms are distributed as one large + file. Another is that a source rpm is really the derived + file not the set of source files before compilation. Because + of this make's assumptions have been violated and make is forced + to recompile everything.

    + +

    The rpm -b or rpmbuild execution itself highlights the + problem. In the normal execution of rpm -b or rpmbuild the + following actions happen:

    + +
      +
    1. In the %prep section the standard %setup macro's first job + is to remove any old copies of the build tree
    2. + +
    3. The next step of the standard %setup macro is to untar the + source from the embedded tarball
    4. + +
    5. The final step is to cd to the build directory and set + permissions appropriately
    6. +
    + +

    So even before we get a chance to build anything we have a + "fresh" environment which is also an environment where make has no + chance of doing any build avoidance! Open Source source RPMs that + use the %setup macro will always build everything every time.

    + +

    The configure redundancy

    + +

    Additionally most Open Source packages first run configure to + interrogate the environment and configure the package so that it + can successfully build. In theory it's a good idea. In practice + it's slow. Also, each module performs this long configure step + again and again. Configure itself is smart enough to create a + cache of its findings so running it a second time in the same + directory or module will not have to go through all that work + again but remember, because of how source rpms work we are always + going through configure for the first time. Plus configure does + not create the cache for the system as a whole but the module + itself. Descend into another directory representing a module and + you'll be running configure, again and again...

    +
    + + +
    + + +v + + diff --git a/web/clearcase/RemoveEmptyBranch.php b/web/clearcase/RemoveEmptyBranch.php new file mode 100644 index 0000000..fa3931f --- /dev/null +++ b/web/clearcase/RemoveEmptyBranch.php @@ -0,0 +1,53 @@ + + + + + + ClearSCM: Clearcase: Triggers: Remove Empty Branch + + + + + + + + + + + + + + +
    +
    + +

    Remove Empty Branch Trigger

    + +

    This trigger removes the branch and the zero element that is + left when a user checks out an element on a branch then cancels + the checkout. Normally this causes a branch with only a zero + element which is identical to the version from which it was + branched. Essentially this empty branch is useless. This trigger + prevents that.

    + + + +
    + +
    + + + + + diff --git a/web/clearcase/index.php b/web/clearcase/index.php new file mode 100644 index 0000000..76877a1 --- /dev/null +++ b/web/clearcase/index.php @@ -0,0 +1,124 @@ + + + + + + ClearSCM Inc. + + + + + + + + + + + + + +
    +
    + +

    Clearcase

    + + +

    A Little History

    + +

    Many of our clients utilize IBM Rational Clearcase for their + SCM system of course. Clearcase is the Cadillac of SCM systems. Born + in the old Unix workstation company named Apollo and originating + from the DSEE project when HP bought out Apollo, + engineers on DSEE project didn't want to see their beloved DSEE + die so they started a company named Atria.

    + +

    Atria did well and was soon bought out by another software + company, makers of Purify - a software + product that helps developers find memory leaks in their code.

    + +

    Later Rational, purveyors of many + software engineering environments and tools, bought PureAtria and for many years it was known + simply as Rational Clearcase. + +

    Finally IBM, seeing the wisedom in the Rational Approach, + bought out Rational where Clearcase, Multisite, Clearquest and the + rest of the Rational suite of tools reside today.

    + +

    Base Clearcase

    + +

    Base Clearcase is how Clearcase was originally developed. As + such it's a full featured, large, complex and flexible SCM + system. Many companies still use Base Clearcase and have build + their own set of scripts around Base Clearcase to represent, + control enforce policies and automate workflow. IBM Rational saw + this and decided to collect the various ways that people use + Clearcase to come up with UCM. Still developing software is about + as varied as designing snowflakes so UCM does not always fit the + environment. As such Base Clearcase is still available and used + today.

    + +

    Unified Change Management (UCM)

    + +

    Unified Change Management is a layer built on Base + Clearcase to provide additional Software Configuration Management + features. These changes include integration with ClearQuest to + enforce defect and change tracking with code development through + the use of activities. This is part of the Rational Unified + Process (RUP) which describes the lifecycle of change management + for IBM Rational's software development process. It also gives + integrators ownership of projects and streams to allow policy and + feature management by project leaders and release engineers. UCM + removes the ability/requirement that users manage a configuration + specification for a view. UCM is used and configured via either + CLIs or GUIs. + +

    Multisite

    + +

    Multisite enables fast, reliable access to software assets + across distributed locations. This extends software configuration + management across geographically distributed projects through + repository replication. This gives you the following benefits:

    + +
      +
    • Automatic replication and synchronization of Rational + Clearcase repositories enables access to current information, + regardless of location
    • + +
    • Simplifies administration with an easy-to-use Web-based + interface
    • + +
    • Maintains data integrity by resending information in the + event of network failure and automatic recovery of repositories + in the event of system failure
    • + +
    • Works with Clearquest® Multisite for integrated workflow + management and defect and change tracking across multiple + locations and time zones
    • + +
    • Scales to support thousands of users, working in dozens of + sites, managing terabytes of data
    • +
    +
    + + +
    + + + + + diff --git a/web/clearcase/triggers.php b/web/clearcase/triggers.php new file mode 100644 index 0000000..45b832c --- /dev/null +++ b/web/clearcase/triggers.php @@ -0,0 +1,66 @@ + + + + + + ClearSCM: Triggers + + + + + + + + + + + + +
    +
    + +

    Clearcase Triggers and Utilities

    + + +

    Many of our consultants have served as Clearcase + administrators. Along the way we have often developed scripts for + out clients. It doesn't take long to realize that often you're + doing the same thing over and over again. This page is a way of + pulling together and documenting these scripts.

    + +

    Triggers

    + +

    Clearcase has triggers which are scripts that are executed when + certain Clearcase operations happen. There are some comon ones and + some not to common ones. Here are some of them.

    + + +
    + + +
    + + + + + diff --git a/web/clearquest/CheckCodePage.php b/web/clearquest/CheckCodePage.php new file mode 100644 index 0000000..645c130 --- /dev/null +++ b/web/clearquest/CheckCodePage.php @@ -0,0 +1,50 @@ + + + + + + + ClearSCM: Clearquest: CheckCodePage.pl + + + + + + + + + + + + + + + +
    +
    + +

    CheckCodePage.pl

    +

    Checks to see if there are any non US ASCII characters in + the database fields

    + + + +
    + +
    + + + + + diff --git a/web/clearquest/PQA.pm.php b/web/clearquest/PQA.pm.php new file mode 100644 index 0000000..1be2801 --- /dev/null +++ b/web/clearquest/PQA.pm.php @@ -0,0 +1,49 @@ + + + + + + + ClearSCM: Clearquest: pqamerge: PQA.pm + + + + + + + + + + + + + + + +
    +
    + +

    pqamerge

    +

    Perl Module to hold common routines

    + + + +
    + +
    + + + + + diff --git a/web/clearquest/check_attachments.php b/web/clearquest/check_attachments.php new file mode 100644 index 0000000..fee41d1 --- /dev/null +++ b/web/clearquest/check_attachments.php @@ -0,0 +1,50 @@ + + + + + + + ClearSCM: Clearquest: check_attachments + + + + + + + + + + + + + + + +
    +
    + +

    check_attachments

    +

    Checks to make sure that the size of the attachments added + up after the merge

    + + + +
    + +
    + + + + + diff --git a/web/clearquest/cqd/BeforeCQD.jpg b/web/clearquest/cqd/BeforeCQD.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3819e0543eef4d1fd165a711d500153721a88abc GIT binary patch literal 233360 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%0AG$_R0_g0nFyP3xp7n_#@RJ&M`!()z5b|R2Drp_9 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≷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�$t~@R|>^#Tf#vQzF^|4>W#EP~lN(KZvTk zn91dc?WUyKMtnTccsnVx>?>-nizOBk-`fdFG4ad3_%ekWrHqhv+*u|DGE|xTmY*>l zQdL0d9W*RlE^8Ve1>HfPr>qE&{pl;3+D;)HQEGNGZu)z&$aj&Y&G9e6WSXCf;c@D) z!Mz-jnX6uUp6hD+?mru9^#qdnQU<%%lOVILvuA4gS_SSv;aTQ0##}0W=HhVk zF;tK+Umx9s+Sl%%&@4k=jY037mNlUc_hIiq52HHB_tZz@i7+Zg3N>|=F^|3DHFR@G z$M95ZqBl<3X$$P6;K@mC=4J2Xbj{KlNhuIBfgC!hnB^obf-IFpb`t?VOYpC zrp=BnN5GFEBTmrsW4zzlRiU6Q)UEOhs8pN!h6b=ERz^N_&u2_WX8+V5iPf;+)@MP6VD`hw>~r2 zJVE}iRRQE{0Uf=LQv^(lfd5z>GYM#;pX{QD0%cbLzZo}k@-{rCvfOJ>GxWg`d+D!A z>(>UU_3s|{Sv@%YV>nd${{6D-TdNsVKU~Id(sg~It0aPEhbDl4j| zyr@jtMVc^RL;_QERC#V&PgHvH7_1KVRvWUS39PS2^m9@?xYa(XYySQ_@KbDsHyeWQlwug>n$40>-0N~UpcuD$PcG~OWV8nxsj($#!B*M_q& zAKI>KAt7O=q)Lu^{z&D5t`QeZydaGlExB&Z<{)YxemN}a-?-j?@yh>}Xa8^G^7nuL zF|G0c1z!0-1F!zieD)V4_+K5Z|3paepKB}s?De(FJWm7o6!IS4dyWqa-IZ7|yfXAy z6?rzivQqJIip(KK&EVy8ACJfzd-5nJsnYOrXzLN8OJFbpp*;>tXurMaArKz)l~VLD z)B5S@ucyrR1pCU2lOyd+6^-jt7f_gJ2JxbMK*6z3T&9qn zAevz9kTad*4{XLyY6tX1O1evnW?SR4<|Ut(zeUIM>Dl1pnf%aEkyy%*4err<>zu{G zlGt?p4R7n0Nf4o!fU;%ot7ag5+5+met1mWW{qTpQ6MfC+Y^R^7C+Ldo5SGx`cYfwc zyt6|xQM%%4#<+isnsP%rCGfnwDN_3_q%p-PTt-;6&InWqlskCf1U!Fe$N3KmBA=4> zu#rCzD4+C33lpu5(l51k3UAhmdcOAC7V1KNutI+E5HV8L+jjyxJB*7W_EYt7yY!OvcjbAdOrRty5P&k>vaa6(SX+S9;6;0oJ~_XKPiT= zaL>Iw<+!dc<6VBENcMI%cb)z{`K|u}z($kSousqqQI%9AXe=rwCUcb+674HNl=YDw zEL{ZKH&vK6hQ992wS6(G5&-gY)`c7X9kl5$;`#p$0IxqMuKvkuVSS)FOg*2xDq_m} zZ1$=2*=r^?vXg}f|A1WJK0?W6`SKESW{MY(ZzCOG`%o>h5x>!yCA29S;5)@xHxg7I z^E)uM8k&Cql~2)+YECa$aJI<75Z?`fedm|P+uKJBJ_d#wTx%C-e8Pmash{+7c%OH; zuGV^;>@T)K^}8RwbAJ|naqjNf_2E}%oz&o?dA%Gb&C6G^X^+X0;I+0 zqR#-2%0FLvQ!~0Pj73}F+`lumq_J@lKQy&(tya9q`j))BZkCEQPSE0%k;Bj5c=p5G zj2JBSHYa3m7Uin@))KE8Ski?d^xeU2rTiShC4d;DRI;6_O4WPICt7LrYQM&PO$Mv_ z8M-O3P=^E+s^MGbMt{L?8}^(}oX4=~5#b3CR{ok{UrMS;&zX9&NftU~QI8@bW_oyy z0ql$g(#_#G@)dxgLKA1EgqFhWUqz-~8v!%u2T(5Nb=5hRuf8BRnS+abECtR(()2^p zAEK~;BD{qZHzh(u(5EQ*b$xAaCN1ItsE%tO;ktsC=3Z2}a9UT(_bKYs^yuZY^WLn8{YUlm5vXmhF-U;lfRQ>WNI}W5&em9!EFDRz$v`f zYM<|m{=}jIDAOzO?S9l;ikM)T?273~C#}@FvNk^Zg9J9@nvs)~&wo6l1xoQ>= zStRGcj&c#`+I!5jM6cPYrs(F#)7i$WJ(J^ZhzB;XZZK@xx3`&-xq_K4Xch;MEX|0I z_xzVri_nKT&)Iv6Q0wGz%T~xv_TGMUXbJiX3)0pnfEiu)J3;Xhz%{|RmJpNDSvLu~3_dFb=6Ryt*YWcG<90cLB>Qfa^SGrGmE*5w_#_e}b@=F; zQQkn52XzI6G!-Xm)z!rDF$7N`^|OX`n8_yDl)Xitw?KI7K^#{=3{2?5!OEjY^5ZpB za@!Q$43lUwpsWKo;p(#BoZby5KbU&x|6*794|SbmjMQCUFkbcY#Y4|eXz#Nuuc8>5cJ$~2 zE|l>?L;zB%oUy2H@dV-Qov>XrS#~c1MQ1JO${gZNLOeLe0Gg#=F;Z#!1iSU_HH%ViUs;?5a*XB97>lyxg)XQ# zE`_|=IQWK8;AUq8SU8J0N6XR+= zee?VX5iDQsH4vGG38K%^x!4y7=uw7v+{OoW&;b(LF>?D**C)XE#{VRdPC zE~=`2`Xkb3^)jSU;3m+13b~v@7X8F zVkO=~C(an#V8OEGtgzf#+cOJuk!R++)cQmdI{Jidq+LX;McW+Goz~aut*mVkAm$4_ z#`O66$mp+F@$X0EfAs#J#EMK%UE*m-e3K?uz?^0A)lK6AsJd9q^c>Zm+_QfB2w>+= z?Z`}dl4yQz=;+xXKCoj@0Q7HFkT6CWnMpZfchjkq61VXcdS3Vq;@4W({{LIGzfYTQ4r(QXyHvRp~Dna+ko5V|AI8H88B9TOm^2}Xsrfu*%W4K%;7 z3xhwK%x2nPf>2eoN;B5pyf#`$oHj-76G7{b5Ckl%zB=nKJ-?GpJDbf>pF>pa+92-u zF*5h%qbM3D<=rspP5%5LV8kn?OD}%U;+&+CQldA%Xt&nWdEArkTeeSIXWkOgvR4kB z-J6h5(x@{jFKnOz6MoW8MK6y;Yl=KBeIquH?XV84h%AvD-$zhJ4dC~}d|ZT`4Tj3B zOLM|~`uMKa9Jj}96hC1>(MLG1z*Orb=FuqhB;Ati&%eYyNEd!$pi9k4H+KA96evJCh#1+uG)fVx)5c=0L~)9U6wJ&C82TSy{Ac?^TUkCq#SkI}(H zZi(cJJKw71%CN-ar|G)!1}B@((zVC0>`IQ*!6o`_=w_^S*w)mNMru~z!ENwbuygSt z_9XzrIJ(~IzUS>QS2ki8b`XQKP-$(2>=)8tb-={fF7&~6XXcaFMUzcos7cA1;buuh z8LkZ(?pLB%SyE!8&Q7Qea*42u??m4!%<&8&ChK-g18_bpg?e&1!XuV?d+KC`3@I8( zR%$aj(M@;k?h}vc)qCWDsT&DyBKzwVT`{tljqnr7NKvMBlv#-C zXi27Lm8k8>t#WlExQKDZz0@|Z2l=BBb!IkEMJhTEaIkY zf<&IYsISRdncFk-+mNaBuIpN4z9yTp@#2Eh6(vjh6`hn!soagp0{wH9Z(fbo9dPY@ zQ=TkeZnmbC=x!==__y3%$A0|O@%xtEmJ>mx&AoD?nN(&@S$-yqI9ivUT%D||7QWKc z*-%lZlc__*-wy|Kq|OKb-e>tos_@r2FC{JdqD19`TFR?rTWg-*viYefA-rkvAZ!OH z_F|bCZX%6gAEDs8x%akhwTS{VXKU~d(NEujnDL5m`L{oov)?Xck3n=n50#kGmtdLIa9f--c=c>}HtzdB*0nZma+Jlj%{4 ztFqZ7Cx>o zgS=-u{LZ-PB+)0Ca#K6-P2m~6!8Yr}McSKCpM$gfj$ahb0JG}2`9%GJG{bMnjEtA0 z0Mc|3oPG{6Vb0yVNyC7VKNbGOAdKZ2@gc>PQ`R~q)-1Q+(*_qe;C=b!lthSTB1zET z#O=wWdGi-9J(VWKM(};o(cI;0ujPW&AA$;yhFr)auqP*gj@Gy4>J5y2DZ7RabnRq1 z)O~Vfv0A- zNn03c^;BsME!f+9*^}NpvDby`W}iBjaL-It`0vG*j)66(d@}r})&d4N4bqst#jQ2t zKxo+uS4-}mGLWgZg(tG6Hn4|5?A{z0--Gq2Up$)sl8)1{?L1BHqd*vN6b}-*=9)n| zfn$*Hw%iRcOAy?GSz8Z^BmcXj!>G#iQI4QKgez1I17U+DG?2R{E($u&Aggu_id+@` z>s3SiZ|=i@8nB=45dBTAMT|tqn}9l+P>$*1QMaJsd4$+&e+%w z@6VJ)^U7MA9i%%pbUGg+3p-o;je{DQem`p69D-#`!~&WRqJIEU-}uZV zp!9N_1pGF6-kIa`sNO*=b2!5<%O(WbnFfmIR&&*l4b|0DIAOXPI#tJ7vo~iXZJ8gg zVX{|7(fLiH(;wX+28C&&tUpw&c{$KOWXJw1K2*2*!C>zhNbXtXLx+MZg#~wH(tz&M zO2V$QZvt=}aCQdAR(byO&EC5pOy5+Cj@0FBYVg!tfjF-3)3o%*Y#m z@IG$9CvR$7qr4*QVn5OHB@!ydkjwO_1Y08L+ZPmkZ`3G53Nf?FZ;caPjYt(|mN>a8 zWNc-Xv6^wGYcm~c-Gv@l)4pG`d`#NE>d?pi$iPMUQED6a6}&c=#Zw@HV{J6q zqc)}EK=pb0=(NSZl(_qQNBJMU7yF9V1cGUO39tiPIkW;@E?cR3COHnON5_%-_z)sH zyoU{JW-RWK$$5~$!CPL9>vlsG>Mi_%b3S}xt)rXypa>%l(;@^t+{O@Zh0$%{|!-y&3%o*DCVOzw&3pSQIlu?c?6x;7o$TldTZ;xkWmj`&5y^3qlq<$ocF|5W5&7 z3k7qb1DBc)V`zE+$g;@eV8YU_@zFs>+J1es>Mx#GvFLLkUoMai@*@nlVKhF=D9q0k z%@_!f1O@6j`6Lp*Z;N_}^d4pq1fz{ca^_)2N#RBxjQBYD5KTe`mMviPY zhQ(qK0zCqhJfVJ_*OCfS`Pc~2ThHa}qNlL^!J0tUJ|oQo4fV#9F0bhi#M3Z{SH vsByfE@9UV-n3w`9k#lx-c04Mtc~l;Pd}poWL)ZRDYy9>2pBn?dUt|9lrVnGq literal 0 HcmV?d00001 diff --git a/web/clearquest/cqd/CQD.jpg b/web/clearquest/cqd/CQD.jpg new file mode 100644 index 0000000000000000000000000000000000000000..520cb22bcfb20c9bf3ce65f8072e8e3d19955de3 GIT binary patch literal 634625 zcmeFZc~}!!w=Wz=M8pV)CnUo=sxsgU@5KuxA5t*clA(W(2x!d>NbH8)X>GwS6p7YQ5J`ywJay)&;$?d|$OYR_f1O?#f6k@+Gk`&D64aY^axH)Yi|wRQCke>T2r@96C6?)lK$ClHE0 z4}BT_Ix>n(P0!4V=g{x-Kjd0KSoFVD_CE#tH*zTfxfU*7ylAo954jdB3;@?6rNv7& z?p&&T&{od%ipr*4x0kIxbpKgFmAv+DJ6QGn)wbnpHX91IAU{O=hh+ab!GixwlKpSN z{vWye2rCvX0E@Rsi9jXb__owY!vC8ty-1|AmJ>!(^qKG{g%40*$6JPy5f=r$NexEM zAviDjGjik7ao@|u{gPz{TkcsYF@ynr({3M^&bHiQb;=0qm~Mk{s5k6A_%ZQ3tCdtl z4fm(5U>@l7&mm6uDhOIaD)cjt<}**DeBrsx&6kaWv;_&F`8x!yMN1R+nw~?<>L5j? zYS!5iCvrrw6=XJOswr8DH>GG*mLrb$SgH(rQD$A`<;_|<(MieAjB7QtHQe2d_ZN5V zJ7-^?qSI!urLAb$mc+rYECPIveV40_nsi@puTUA}uk>hKnXFcS#Y}w&+ie%4p057- zFJF&9T29*&&+L=VSE4PtE~GcLY-_Z?=emz_X5ZC(M}x?{H%?h)q^YL&Rq`)Xl=1!= zqIz=r%VdPX5jZn~qRyZTpjw-do5Oc1%!S9$HX$`)U?bd9t!ZC7K!PlYN5B%)p*>|-XY0=^8pz%s&J%oi33<|2*A%hAjG*bVA=DG!D8$bl8HHkC=$ zC3`3)?c{u>IsAUCen9gT`=m=@9aO*>9~S8yA5W?Qldq9(hn9@e6e|u2V(+l_AuUzx z<%}A^mz*5$n_WN@S3&IEdH&rQeaOPstQOy1_UcVp0p8cfufq-Sx-LIHYChXidetcb zQ)vQJkn>SS@M%VNi6mQC8<@w7S`D+fyO_sdb{!{T%A?OHHp17($GMKZ zib)g%T|YczDZT5P?u>d@^FC0NNgs^9{`#7;q3&1skHj}z`;e^gniCv*1!u#m?XNw% zGSweEbFj$WoNf0gd0*BcMGFHYs_n=NhkMWC_BC#8e-M(}e!Z;i>)f&HNixEc73hs@ zMvhCHFRqR`#+t+KHMEFP20GoX@oe8cBL%0=tk>F%HzSj^iY7e{$fZg|O^{Ic*k~+A z!S{yG1-3PM7ryhBwW)nZTw=0b$FjWLz%Zv$&Z*lk=Uc~dX*_p<2V2gFe|wMmnu?SK zh)zr~JJKZUVFQJlP?ssZ{s!5NtLeAY9@yOw&5;qR-JdeJ4Z`dyQ47%vCw0%fG1U_) z=$nXYbf4E`p01*I;IDq0loF?Cn8~lucSdD2?fa~~j6g{EzJAHv1QppYxSZ$t6mO+1 zEs|=D`jSLy5fjBWcPQI?xI3zZ^yN$gV71kimpV?vyil{-OscS$+g(fzzu9ot|A5N5 zf)2Hso`Dr*h{EW6Y{;yKkv9dZhG08dZ%>1*$D*ouzA}@R<*lWH$_OGW`=fBc9b&XU zX_*I9Xq}Ue1_}cip*C_}iQ>2bNG*bSSU@8(+<$@N(}hu!s-qOWR`${0Ve5D?-jG=# z4TUaQIUnosYf<*VjSyCvFv!NC8#^c>s$nP4Bq3G9YyN$fT#e;X9v9h~CA5rc?653A z_rv`*ZAA%kKX;5Xca=0gFLLS$uP=B^mk}0ArT6nDOkI6tgk`77uaIkrE3mUL=U%Y_ zKb)pC>8}}RgANPnVJ^GSB*PwKigv=dv5AZjfvckAlO%XYSiZzX=7a z&Tl?Cjr0%G;(&yPhK9LB$Eew@H1!`l2j^C?O$1fhpA=#O)zTv<8i-j)#SM|~et|we zdm~NZ^$#JPnYqS&| zu1X;wYE|rzfz{|Eo;+Kq7Fj_?w({#z41EL4+9>ka&#-&_(Ah(hQ!jyGevrmGp73?PygB2Dtcdsz`ZuMckC@&i|Hz&h4NR?YQ?S$^3_JlmcH@Se1+y5Y2Sos-hFc2 z*h_`oCL^3@_ws)6T|9b|9)tO--Q}3BBZua(bir1egIIJNZpSsf#Q0{I8tUVV1qnH; z8i2(D8^atkJt+EnN;e6oT@d5Drkc5oY>7)592=%#HM5TD4&AcMK>5cX3ZUTPW};B7 zmO;@&W2*-?!{69K@^YeP-5^^LT~Ki_jp25#sIrxL;2JfoLN7y_XSiQu8OJEF8KS*0 z_h&1OmG)p~V%093UcXXD&CKu}(&b_2zcHld3uE7@T5}^wnV0Hz^TvUZZRR5dC!U?M zyLVP+^`|FKwp{!&LwoFot2c>H%Lr)+Qnri$Phr4^zQn|$-sE04JccHQz^>K=kI4v$ zCZBnNN4$$I^Ltuu(NK35aHi>fX4tNT@i#KUv0hj~Mp!Xf!^7S61XQ7cfcMpmCL<_} zfTzfunVFIKC>enbt_2WwzZ(pChSfxxxhf+R$_Tg?EsTb>0aYtrl~265xyt%%-dvwu_ZFXl$rD2$zz5YC5#uy2xepnRYnjDq2Tpq&8xJ( z8-AUo_$*i%9~2B18X(}y2)iFawdZP}))*eH)&UlZMjQDWbW}!Ysgn_I@$lpGW;H1~ zaL+xzK&leMtk#4S$)vuh-`X+TveK(t{Yz|+5+TB8_P{RT=)2Kf%Qg-scLp!pyQx%f znp4LwAl_n z1|)E|!wE4=#q5sjerESbIC1LXl`B_=LZ0o{ zt3s7t--gJoe zMiAOJ6(=e+j*&Z}+tzCA}q_@D)TWmVW+1+X0yN&Cb0sOtCcovUq+68@u06~9a> zF2&XpB@k3arVi$^OG>LrA!U=dn2=-_0}^K;v5~;%+$Tw>N{wFnz*)u{KTWs#QWI(| zZRLoQP3FBo-T;l5daHts>w=7Vp)`hH z?eh8g-@awU4D@yP4eW^zjEWe#5V>qagw~mixbD58QvnwpHZ$b{BsV^y?}eV!i|k&y zXmY}H@WYk|s%SGjZHtoN>A}mK9V^}bU}!Y(H9^GM$wfJtMNykiamlEeAXbZM^7?gY zc@f1Za_VD$sR3Ez&RaY2hjAATxucCo9qJs@4<$Y9+wOmH?HkH{>&evZJ0B&dsp<4? z<>x1xwzQh)O}F6cBJoQZ;rRuSt)dWgo`;8fP0{=0M6~+JaA0u7+qfH;o6ru4(iu;l zGFL4Y{iT>6?qm~QK@~Qe1aek+8K-C>r7>Uw=HDv=0V%R?`*qmB;VQ#!koLNj=B9d zqS!^Jy~`YHjgW+fC?1$Rk*kCEQS{M+Szc^8o(EmSnR_~7W1I=sj0g#NTKd#K$Iyx&_eo>>eHg~k-4!9y&WiMS-`NYbJ>7uAcyPJ zGw)J}jFy_TYSzjkX}&-CHg1H{ga^!=kcNm9aYuuex{W-V>dP>=9h6=^w#PDk)P;l` zh|UiiTg8IgXz|d?fC*1TB986i?i)!lZky4~#mI2dpbnrP)Eh9pa(0-VP9*myo%$D; zCEQXse~w*8k1V^CoXc2h6GkDjo#Bua{f7-Hi=eeuPnidkO@EH?%w&!Yk~Hc9VD?)_ ztz~GE#KW6kye1Rw7%c9Y&=*W9qfMe?nF9V&>{ybBH$Iku9qGV=26<|uW$ZA@5~f9Y zw~450Geo{SYtv+Qfy8kdd z#J!EE2#L3_{esE$j5Ki|VicbaN7;1aTNu+-6vH-CXs}XSlv2?LMOW-g?PHM6YN;Y( z-XNsO@b8;Gjm+IDQq-%%)!PhZgr}^f_yB7^Vm4tV;)Ovfz7u|&;GR7LMp0ERn<`Pq zTvY8GoN%vbvM*3DH$Dva2paAkAt_Nd8yeJwTW!}a^=v3z-a z_o&HSJwJ?PAT6LQM~U?=yW!B{tPy-;n9J)Tgfm_xusT0nx07KhpoK9N1yog5bIMxu znqX4>6MF2NdtF~xJxv1*)!(G&>%|DF`MYp1jXW-KOt4th@MZ6g)uTt>J-Ko^d+!Zv z-KjSR9iE&`O)B&n*(H*HkNy5AkGK6Y|H}NjqpTZ1-yS9R6lh48SH_zJ@I3aH=d?BU+Hac`(4n~g5!HMLBSx3s?d zCxMD8$ftS}=Z~39EC5h`POK**d<{*Lx(iz7Nf!V(y;2QL@u@9^L^tPu$st`J_9a%R zEiDCtdq~A$Qf+PVEihM!L{&zZIcy8S;1$VfsC^gJgNRXi1vDAqh!JUwx)N%Y=z!;9 zRAhws?|9H3f$IF}(M)^+d1@My?4H>biA@9e_gb1u9a%>zlEX>-4;&dGRUKgT*uEP4 z{(Z^MkU%O0@M(VI3mM@IdJPZjrQ+*JN+ZCyq7WHjhzo?&sxQUnvjE_<#42P2%3snIsXRcj{jJ9SZq=#Xto0xf z7ux{Mt_Py)137;Z8}Wv_Y_Ns2q2cdh_pcXKzYqVt#{O2h|8AYb3i80zvY|m`km!ST zq8{GHX`%kT#{N#Xv+OYH*Pw?$ss?!(p^5oJ&I8f(f1zh?;6ac7FO9Ji%Ek7{2#3en z0O#(t!JUDeuK&L^{&Q=ju{y;v0 z-IsChoSXElaoLxIMfXF??}^C&+DUIf9Y4HOtpI@C|7{JUKP;9Th^~`Z0sa{Emy&9l zg!HA%Lr${_8Y)`D33-HJLV5V<=>@)sXW)~Js;pW}ow0|iknO3VX@ z^M_GelvbBmo9L2S!aL+xXq|4-DL55(+#%g=yWgn&BX{|Fi_sHLobRP}XJvG3%t%i3 z% zWt5EyW{(_rp0WbI5TE5OY+1w56TaNL{?x^&wBRb+WTy2duhk<}Ui|}h^iLrhIJ%t= zPlTvEN{saR`Y3JL8}nX${%m=Wn*FGS-qdB|grns0Sc-n<2R`HBX?SwN z6=4fv!)7(TK7TKZ?hb<|1uapg`v+-@N0dJng;~KZcZ-}Awap5reDcm;kxx7hd^3Ux zJjzT@5(o+X@D2z=a%TQSEfZg7?&u)jOSHBzi3WL1J{STmM7iIJS zqE>IRWp;Stjl^us$tKw7z@n;~^>0!~owx}>$a0ZQm%mDYr}q_if%MS2@$}NV?n1hL zNQv0#DYms)4Qu{Qu~PXYqDBWC?`(PRmVnPeH%X1uN{9^@5@_+H=tw}~vTz7SXFkr8ww zfFQR4wI0+>ooRgLDADafyP>L^AdK&%!Y8N_a#;iZ*l@elzMB4B^Ac68m+*fiR>dUv zq8#=-RvZUdnhgjY=df=X;Yt4Z1@_)*HHFh8Xc^S&X(&zS%IR~i;4u_ zGE!UWU#Uw@E!$WUs^RT%zO=M7aP)F9-{0 zN*Lg39;M>-Z1Jyr*p4IhfY2?9K{d4Z_%@-pgK)24*v>jeV^NscaPr&1?k`!V%zaz- zUCpu#SbIpnZ~H*L;ueb$ug~A+M=o$JXM?KqoX;I7xSE|;v2A~0{YTMS?anXRZL~$f z^}<)<4m&O{FM5e)ue==RvF**l_fN8{-&xqsWxclzura4({*tz?uHFK`qRz}d05!9b$DJWhV*nfa# z-eKf?oIaCxO5>cxdyO$hv6aHf!XCo|7JCgIoCa*!vF@S?>gjfl>e2e-?9;uYYpxy+ z@Lk-fx*FoF2XW_ct|Pu63|Jk7#=cHE^$dh!5um@>3B#Wf*?q47d$mnQKqiqi$tl|R zArgK7H%d548fyT!sdWr|y9Hp~cN_3@*uNI=QR1Ul+RtKs(p)F6@oLdR`Sr733O!OQ zm@Rw%nvQ&w{e@BXN7%Df0#}$<+gSTqO?+j4XsP#4pGJN{`>U;t`#_57 zTU475W>&-FO zuq-?K^w0*g567%W6Z6cipSuO6={B(AJTi3~^&0tq)j!WZnx31+Nls1>Ji|Yqbo>)~ z={OM!Pl63y`a8Z9?|~HZzWMPLm>1z&-IrkL403jqqJ*4^&7z@h4U`p~mw!DOIXYSG zvb)WQPpq7|a(GRq7F#QmzT$e%?=R-3cqjJ;{|Eft559~#^@!G+g#V=>22s5mm|t*^ z<3}TSu9p4Xf;f#rfh4?jVI3cvRv|f%Vvp#l($AEUsJdNlzun~2Jd3BV%ZGO!+^>@u# zb*8s*X7!i%^_+~g>GYEf7sUG)RTe*KFol{=MS(#*rGF%l)`HsZB)XLv%rd~vzih{M zF!(g-Q(seC0V$HVitiL`sY!O}^!1PJi>d}WL^V0>U}lSeymW-JuGKJhF~ixcl_yva zmpsgH@|<=iPiA7O_P8y83`;hEsJ&eJ5}HlnZgQN;l9+q3)qRxsD?$dD!F$2Euv)^h@!|AS~vA}4W6^uX2&R}~BHS$#5x2GK1_xRa!tcy)t`c@Cwl|6k?_A1@V{$6&Yg{__Vq;SaYO0eytN6JHg zWpB3~PS}6fO`=T#}2Tpv7Mie5&`wjYTa~ZC)K6 z-BEhibU!Q-(qd-#u_?;v4IvMF(kkvIMwCmE6Iy7H28W|MX2_8;N)$q`!KpA5YRFgg zD*INM_BCP693b)ccmLXfAk=Ed$y`> zv(BV_*0T32ktc4O+Xpl4j66GXRE^Yszk59U(Wvc@|ePd0gZ_qYY} zM0;~P{<}Yf>Z(oHxTj-E$mM8+D%M7O;WQDa?f`voC?N)NXRl-kCuWlEdISM=Yz<1K zKt@nI%e;oh4OZHTj&yO?nWuWGpPj$>A?C}JL$V;Taqyvt*1udlZ72N$@;Y$Xw(K>R z$3+#8SKq|qZ=MDQ`QP#>T4>^#ZC$;~qi9iGuQxnPEeDIh zls=amg5AZr0A&|vVu_rI-N3yhNRo`;&=Rrzfg?S?$G37va`ChTKVB$thBL9q0d*PS zMUZbGlGY#@^1@>@*ZU^;j>A% z+C2MwYc?mb++u~CiDAT1jU)$HTq1#~0w=e$xQj^r3c{JhfEf4Uhe_v-)eNe=g;rwL z`o00AdCQRj6SNy9(IY;L5p?47-NW>YrTU(Y@hY1C=+;(vSyvBEyBcC1zSQU0kqXlfZ@>Mmhmday@A zXpxl7J1^Z|J?XxJ$J#b)6T{bnJcf9k`u1MVUYxYkr)^9sKQ)BL%RjU#d5Ev&iKEJ- zIPj4r_R`YvZ=Yp^bG`!lT|R7Owfj&ky^-|6r@{(w<6v{mC|!!iZ7!40BZ|qpko>JB z63yrF^Ff>*JGd~yOYeP&!$FXTpFOp)cu4X@(?i(HKP8uyE zct%s_5{)rcJ@MPlFLa(-EfGXY*hVwJ#)@qSUAps@T|lkw|2oG#H94W4&QDs|Su9!c z_M@Q{d<|cISg>=LK@*otA5(Puju@AOA+h3vNLXm`#L=+#8}0~=t8Mz+4z|uL>mBeI zxWQFmWFPd2-~KWx%DX@LlsCheS7!`u(g{_)u>WjCxjd5fZgwa*6( z%(h~h=nPojF|fUZ`BnWaBaCy$Nb?t3bEJ>PFmLK$B{cO18{I~dCVX;}5q?Qj4FW|7 z{*=jI(6}R6YFV@X^`xK%gCyQmFY4es;&*e>1i%ZI4hs2F8kOKJ-7|(Y{*xPXHGloH z1oc1jdyvHH|09WYvjIsgs_TdsQ8DFEu7DHv&u$D8RiMvVAc*{WnkwC7mUjnVE+eew z{RC=u)1nvKQn_@r4k>kNHMPuOu158qj-Z>HtPX?zDk}|P=7d=;XTldLKUNWY3aAO$T~o%SDNt0s zyfl85vhhWY*YX$|0`tP@t-<{IQbo{r72TJ9ry&z<0{@ND_bK`?&J}WcUqQ=%dl|BQ zn9)}}X87dg>#bQQ?m*`m5%mt<&N-RO*WA?E%rRL0NAJ1pE9Q`4G5vkxbFMEt+0q}_ zDiz7lY7+E}ACypfZJKijwxXFxsW=7EELwcyeq$cPxdvLnyF?}Sc^y&m4S4<)klq!C zGo9Y$UGWwM*=;?2q-KzWtv+lka*|eXmwE<^zEse|*e{d6+}=_c`-B|U-@q?UsPh>2 z{VOsY8GDIlT=VLvEj9Op2l`oIZ8Y!0!FwHvs%tO2KGUBU;H;vPee+$VQ}jehf6kf0 zWTAKEsO69cZca)e!S6|>$?VA@KDx~&l%CHTzAqwv=y{u15<67Nzvd~RM&Jv&xSg-H zj@+TG`Ex~ze_Z2*odsNwZB_nY7O}^z5N;QRQ53s|^&TWlko&}CJ6^?ufyoBe zTIn;c>DTwAyVOcc%8O9TOMMQd54Y51h8vuZ%jX?=k+&VDD|A`!Zv08ti2ljmkM{25 zMQii#8SFpeH-VHW;lcNEJb=1yv-BC{Lki_hodaGWg=p=fFT?e`f`s~5c~;v1sbeDS zQ$@F_24Y_$BfJ7jn)ov{C=H{a&WOqAd*SiH&@uZW^Wr13lXpnPnd3jwtW#Z}BsJH7 z=+x4QPG&(u_MH^dR;CR=5(yzY(yy&{956t)Acv2_VRbf&BMB;-@1Gu1=Y{0?MOz8t zikc*Uiahnk`Vew7yD;D_r_cC!`%G*!q=4y(1cI?FBl5Db#JHqhM#o!|&TwrIjA8|) zwTkM##tl?Ori1eKD4HrttaZ^uG*cE7b!~J_{LXBt{`gQu-cmm=qRBfb{7sWu=uDhR zyLf))L2aLjP54h<3t+JAdgHb~9Bd1~^-HA%)Y%lurXKVUM~OSI z$Wy!_H5%yb`@t8gfFgiMs1HTlWsM1Ru&6>vq6knz#=0*et_dW79^-{<*z<5XRJ8_Q zski&5Sm9j5%)Q;pO2pb|>q9T(tFr|O!K4LT7p@or!T+AP-AU-z)8MXqOa!>6mkcHYOv8SA7@ zk6xVc8?{dIPrR)1NaJjD@9Sd!pdf1wzM5PEeV;?vxF#)3v@26Qr#h(B8d$U3Ng3gDMZ!nMFt&{r3R;^c zMO4g$rwV;9O8d7T;8@URHXrtuKXdFiHm>~!z`dQ{$*(EJpiuMli5b2UR8SgkkY6J$ z=n0;pj4;fvhfsgkb}*=?*}of|C1hX-FFp(dF!!j{imQ3F+Q z2r~vm4YUq$tz9VCn48mCxxm_18ot1EBsQ!8mO|SsS($g&2Vcah8rW9QTFg;nT1^T1#ipOq0F z)=5E+%XlAkFoic?#2ZX2#^z>{1TJgeF?ZC7cIB`C^Xgw`ujQsZxpIbOlQ3AYGt;j4 z=-%z9&w=gPD(1R%mhtABJC4szS03&3#(*$#7xkTLogsKo)2EUJ~jsjOxo<=gD32Qkv>t>0a%mb8qH0tc}Y@t;~Gxg%eu2 zThHm=qbfn(6BeUmHCoDu(Kq%p7e?`>jz0cDwrzQ&f@RV}UB-tU5z;F_Zn zYk%$46Or}vv^5`H_dKErCweCOB|B8*Gcc{L_My$2E+A^-_HdZ_ZQFw@v=fd(s@8K0 ze_6DmJHBnP^3FFab5m+IBN{F+#t8Tzg0HGO>Mbg`0ufETO5E7UuRMr)-GKBX1;j;Gd~ ziWNUW>2$^1SkElRWCK<0b89}UNy#<6uL+JdM+!)$8Y-wQm)L?GMpBy0-I5tVzg|rR9pM)5N~xZXKkhJg=(ABykb&9|vg$G|p%3hDP5YTlNbRZv;>jY(PM(a)mq(erIY( zb98zCK0nvZ8ycJ66gxq$W>4ucyz#f{R6Hp!hxO7_S4P-W8p<1AkOnNitgmzot#{@* zerNv-y2Cb5cS3a9a^^u$$N|x7@%wg)8fIn_s%6p+62;hixIUZ^K1*FuC1T5GhWn8} zu*LDk!7cSEa_p0>&k=H{Q4r~L4|89h^N5xu1+z4yI;pA=JAR_N!%Lm zf)seI6bfFGLP0JTbH1gcX%bCFKVV=rFvWoNv~8Vqd_80C47wiyk+7`OUQJKXqQKmX za(tg3H!vKjaLWq!DL&vx6h8#|0j~vLfO-Q#hrnzk&*SEtubH@2nrz5c@=jUb$#fMu zpMs^swZ7Y@>y(*W&}!DY0W?go=Dslcn$SIjX^i-rF61ki(nPUKMsPW#vBwm&J**vb zAs2IDYP8?|RBoPtysFoe*G^G~8uh{e!%;!UQ%vvCkf9Nhbc>cHAh`BRV`e5FHn8;3 z0@y{c_st!^R3$)O)LyCna;V|2S6-yGxFV`|{B_6ppZeGGPkR|BE_~}&8GACmIxsNH z2E93lDbG_Mo&d5$;UsW(K#LG4h)Sv+b1oxDV3ai z(mhu);G5oDF%{IC;cenlNcZ;7aH;WE{qi_t?5FJyHLKcZ{o<{5iJkYw?=SbDpBfl+ zPWodLc}wwM&PPh7H?8UGjt*SOzT%$anedy}4 z7oZPo9mRYPW}T-vioxyO>!HsaiA$5iObf-AyP<1vOO1>G%)3EhgyMV}R|Hj>V)uJR z3K3;HR4{Wu6CTXOHn)DhvG}DPBT2Y4Y{8piUg>m5-}R^Q`~ZW3=LaeRJhMGpy!G!T zK6lg$^68%Pynod9ib2}?YI}_jbxVexw4ck-@QWPnJNEl#WcQB5k~pQ#kg^oEQrk1< zp3$Z-Eq`OP`Fz z7I59%x652TyqmHEHj80td&cKYH9~{D-UcxQ8rraZ`Jc|#*3IO^h6BM!HE)W%??#TD zg$;@#_S`WBv3+r7M`qiPeFyA9`BB$$yqsprw#JHlSB*I}e1M9Lj8W^6*NZkAM^{e>dK{mRTg#9sG zMV`ZTpiu#OhZe^u1d+j~6t9OQIcFz;TPZ_>GD83Txk@P79aH1r_Fzk2vIwLkl3Zxy z;USLLo`V435IhSQf%DMdQ%FqnkCpE7aG2Iof&LuZDI*k|<;{{;_{QMtsSPqhNuf9m zU-1;s9{2Hfp7??mpc1;K;ibHe%e=oH{xS+|Lhk28)A%GB=W>d?#Kx3oBstDEXQtxA%G}Vw!UB~AG2ST)*XOV7y=iyP-6eW^XJxuZlclxt1MkF@mY*DSODD7|tuLQSZGYHi z+*UM>+w`=tA5&p#GHMbiz9fCaw9R1Zfqt<@$uHO@wD_7&g4*w(?*bCG^sxm)Mf=<3 zDqnYTSE1JQh_a*i;}Qd-CTN27TBLS;x2NcS`>Ry7c|fBy8zTsNxrsBiwdl zR??E9N;z>hk|%9EL!U{O6LRmk3aKF^h4buP%lyc}yk%ri2Q}!SPq4emQa*2Bh(DWyZ1zblOaanBu9mkz~PMcP7CLQ@IX?= z$OCheHEPO@K2smpTu=V&ex3z^`S`n(*0cnhxQ}}Z9}n~k-N!ATqtM{V`JkD5bj$`m zT(f5`QEt2q39b6#wx{43^B456;L~can;*$HyxkDT8rtfL1^!tsXw=)A5}mOW>1R z9J}(wM*ppie$I|Pq|qWhk(NPPO?I)1Zrh`g>fZoDcJSXY>UjNx*L6GIN_~fuBUvrU z=cbLc90CoTdwcr_N}@VSzp4U|`|^og9c48-hIG_Vp&TJC%v1?}N59QlgGM8B0{?;g zH-zVI@`bfmu>}aP*5+>CWUUT2vRYL2YGj^Q3x&?dCtrlWEnAu6Jy>a7Q*TTGSwKjP z0zdS3?Aah0-4XD%3bqtbqbbG=cN%Js1PFD2D!6;11bf~H>A?f4NwxFz9~B(+mOB0Y z4U`3lS}bMPS1m=GFyln}9qek^dKW4Zc89CoeC;VXNHw{zA8k0(W~gcZEvea+;9>PFNqh1xTdeE4`bzQKv9bf>-zQ-6tk%T6rG%cU{8 z8{S^Wb>1M?v$7aMD~Aeq$e&}`O#DC=pT5PoIKvJro4h+qT*}Z9KsB+;y>WT85PA5P zy8|?C-^LX|Pm5d)C(LDiJLWpb4_7Be`tC5KF2!t-_t61EV+$c=gF@B0$V=VD2Q&^< zb`U6mX!8%g3-tvjc-TrTBj$RT6G_>^q`+&U;4%9$v@PIy&2vn-W0n-|vI@P3&~4!1 zNVe~4w}um4GcAPCls!f0!yQ}&E5Ka_$1@Lr1Toam zcM*sLwUe5F?ZmVt`^TV#hHQD%y{05(7kXD`LXJ9-m&4GhZ6@EtHvon$QRj9AeURF_ ze7X)-W!D*}sCF}{8EAs#8}yF1;JcCHp_*!!4k7h}ir5zMcF7E2-yx=sVcRSB`Smb;$@GpRUyu zeAUu=gPg=Q+x)CRA|)=CK6N|}*FS#VhkDy|R4^0u^Y5>T7LXU6W_T>>r6^*Ka99I9 zgs;doWGoD9VlT!{!e*frK>|;I4O_P?MU^@etNa4jm6;c|JUQcaz4crkH_fB)eMwJ& z&BEsGPw!W5eQgs;T7fCS?tj2DTof++0*tWd#vfrLInMOAU_z`-J+*jYHdQS6&s zVEM(r_6(E9S($=>T52C#e5CLaeKD(rx~f83NDJQ6A7P__DWG$Lwb%^-C$u%|yM7UL zm%nP9rN>ZvtL~5uxYz)}nrN6`v*${-N3Pxs)vmK>Lc<#H$Wan~J@fY5{9=9klb!ib z3p$EEnbgZ0aOjhhm!C)Wy?^(-*m)O$J|69H)W|TINtWc-!i?uT4!rPj>E}R^>v^HFjZchq5?KJw3 zWA~_oHSl~}{fQ@{_4{9Bq;1K4nv$<`N`>85x+L)Ev|g`KdffPYFUPEXc#QQ}V#jKp zAFdyZ?d2Ro$KXs*1>aF|AO|fxi5fp;4A=AlU{0*Lsrn_^HQSrA3C@Uv0&>u+^?h@v zUKXG6Z}O#1*uQrxQYlhfNWQ@l-NEI1KyP&kDiSZ3zVWxYXOH(Jg60{X`-HuK98*E^ z&SvgF9P4a)!^EK-re+-P6k_MhetnCvAE(JRsb}E_y-!qIOwYq46upRuR@3(i`kI+qGW^=E0#v$uUAd=Z~9GPUrJ0uUe07&l)ZgNs(kq1 zNzjuq`(y)Wo$)W}+h(8SKmJ8*YM|lGE9^V$KK&d z%P%+I9mWd4DO!M5Hv~-|Wo==&i*-<%za)_9iyFOPx=1U(p!8?>pR`w#wS0MoKQPD_ zJlO6-k~PAMFCpYgu}K$$_4_wY+aEZ2i`niu)OJ+Lg%EEle3FLWvuNra;?Jl8{8qi3giUGwA5#BgzCZPZHCX2B39KA@2fvelZ4gP}?Rg z;>~aPVJGgaM(T__`W+mEPLp^-Ge5VDPfgA*uLl;pgoxi*@~M;ba+A{zeeIwzJ~F_F!*8%a4)y;hSpsCaPOE_ST~Z>-<^I;MmX^-vnXpV4L?S{>^&}Bo0MoZ)o_Gg zJ5Pp5Wqyutc|T6WesIIX1MmyHdn63r2J}wDYe+MPdDC04UjdbS`?Ta=FW%t_P_?F_ zDu6pCa$j(6Ljn+*UN%TqO=6AETI%;fd!C@12U~ai#V+8#Esr3Q5AcJ>Ck-;M&1JkP zFZ0aL&)@7*(PO;Z(sXLKrD3f0Zk7MT6ct*&y8*wa;y+7-kthk*^aMHND2NwEs2qq{ zS_!~JY(ZoJdvCBm)Ru?=^Qk5RGD5gbd?P@*#0`Rj!$9k6@HBoJIH!$_@Vg4Wyclr) zW#Ekmpo8=d>jQhfg{0(-{h}ug;ti&Qz+%P!VEmT<1Ltp(i>@F^bTDOjIEtw$QO3Ji zx^QW@AA1S-GaMOrgj6EvHu{p6>TTlNzAe!Aux00jK0WuF%5bP~*=LuNj^7veObn=+ z5!dsK#H!v7w6ke{jh(o=l>PEL=-@rSIa(8z1_o4~>xh$NR^=+{X)Pu%|Kh z?AqX*G@cj>x<(3$)ukV#IUuI-LrDb*2|c zT0gP<;4+&Jv_28O%o(hTNcx=TPK|^XaY;BxYF??0w}HxQc%ptxY#MeAwUumQ%6G6f zVfzDv0?oVVM)YN)>f}(Se%6Q4o}1U%0ci~Z-ub;6jAGr3ar6!5K9?bfy|Exqz5t=y zhcT5x@mDJozSvco$6bXTLH1;>eb6sx?yeYEh89+<{Xx2`MJa_FYK>z)<~;9-$JeLS zT>YZ=#*OhA`aCA40E*0B`h+(giS3aQI&CaD{VgAWB7kWGTi|~ZQ@!VSi|IVimIxEW z_=E@%zwer3lu$8ybd_rI=U1q!tLszd#>g(R>ZOkQtU+II$AZXhn|!Z+b4d9jLWtr6 z1lBvK@b^UorYh<~&?ErMw6Pp?6x+1+;1(&n00ppAK}I;W?fZbXj1W*GBlL_wBP-@j zBDZm7)_+&wSI9j@)efPwJkZ0&4$DB&tI_tRw*#Q$KN?WB=l2AWh0Q9OS7O&d(hifm zwVN8GTHWslj*C1`9sKRgrRc%23Jn464trIFnUJ{VG#XG_Mh>o60=G-*xjW%syF!{F z&d9)7%QQ65<-v{FEp5f}7nK{Y1!`6f6$Bqo?bWQcqJK$QfTm@l*J{-Hx0tSiy-eT1 za{&$oJ#5Q!PNIXIGs!pK?er?EkIixF|72+2Elo7Y8$hNq;rOTPsy66|g zwfp3lQ`Oa(eoryi?YUW!oOYtiU0 zW>0n0(G|IIy_qUO8!lG8p+@f#R?LxV|A-+VB%S%A7qQ)fvzSip=k*Gfiv*zC$K5{K z(=3#Ewk}sr?ZO)ybs?${LRx`XZ_m^`gCv?8(zX%!?wNT~>s%{-z&3qn*xWR0>aOCp4& z6%kn@K?q4yWRog^SdyjZw!inCdERgAcjlXG{+s#33#3RT+*Nh%bIxx$3puw)j|#Po zG!u$%I@ekTiu~5RPiqxvtdpGn=>oj4?YJnVH7;!RV#zZD&D~+TiUMXEhL&%NxFj{I z@G;T@@RTc@+4jCL`)ag*@IeGaqu7?3T!sIvFR+geE%kYU@BO0jxlR4gQ!R$kj*!bB zRadGpfHxy2j4itXf0*MeR$mVoBFA)?Q+IJ|42^5Z6m!<*>I})gfr^ulGuj69E1!Bi zz2NNTou>ah^%YLOm;56{%lUFC3Y(NoRgT!t=coYGzAV(DZgDzayUDN9*q|+jE*RM* zoVBmHJ~T6XG=5?MhTK}bO@j-*e z4gC+db8NVS+CSw)G9HvmV|{DN%Vs|B_kW*ZICPsZS7F`q*ODUh3wcNrUS|wjXr})>UGg_OYr>T&be)JjWgq`VW`KAs)Ak;j<51q+~=Yr#sj}zDi}wj~Op6 z{I13O!}qC&mhk7w_nzfiLXC|kyNU^!o708Yh5w2mj}%1zY3;97s#5&(*yUYHiVrut zZ@tGJ*;Bl=&8gkp)!}T~u}y|P=k_$eh};=DWBV#{+w&Jr{_?;H8Fe4g_uGysx0Gti z@}W=+y49n92vS)#Edum&gkq!?n23^)w&La-iP~ zdaIc!tRG>1y8FH-{Zs>}**36=1&_RHC>jkw!`+`aI=XHtzVmj1M_)94?04t&ukK$t z6CfMiLSOgSlEAk&faAiz-rF?!K~Uu|J)q6;(Qu!9r8@{%$0*@4rc_6=2}q2Y#pvBe z`{SY!$78a)Hv5Wvq&Qh#<0jG=X6clC2fMZjumEI^EwQf(V;-ijaI$b{?33;o)o9E( zel9|IA+t=tlHu6koM~Vc<{PZ@?A&Dy>_C5Om#XBV1{PaZBvKyAeU8wC_dd1bZSrTy^2vi5N zFGM_?$o~QM%zNnbqKZfKCVTC#YOKk#v@fqHpWc+ObmVj8uai@6QvO=915-y>&H5Q? zK1>+P)22z+#)vN>UBI+$X1h{mnALGRZ(Ai z8w-)DYi=v+=N>>9?jrvBdXv{cjlU6aEa@*hJzn{`#%E>^kVb9=)&OdNE8WWeQA$8= z2rQCW>#1DdHf|e18T2Ty%2X`Hrw@1)m12~_d3MPa3k2;6ZZK!m;PShVn!JPAk-Uia znR(@32Q(B1=D8u`*t_iS85vY?#T;1*6igWI69i<#cH;Z6ZFeg%F*XKt*pW4mVw;e- zhV04p=1L7$7Hgzyloyj7kM=|lMLo>*e3C!zQax}wV?mT;RF~7&UCV&+mZdj)MSD6; zTTUQ*$gi7i4u_mL?vhWhib*q!(>i*#?zF#ALmd2e9)3~4VcFYSzu>0E@=IBK@f6{9 zcQPM7QzTX6$X9FtmwQgeqOPXc{!gh(gTr_S&>;tHg?&(Wq4v4pQoZoG%y6rdbgatJ%7fwc501 zCaT}Wrr$$@;Ms#{k*JiI#D{C3a6?MqL-M)i`tbw`6b{7&9bfY@nAVb0{{FU+aqH?W z-0RR?<}gVdCd&srj&5`_NxBxOb*JCM?gCmfW@*v1ro_K-R1ZBqR<6)IXqEU9Th-lq zF?+E?te<|LH~O$WbTq=xJ4@pJBUk0R($nWH$!$7AVui%Ih z!3q=%E?{YVt@6eo;J=cxIuzu&VN-tqtDO4ai>%es^N2~YMRM@!1A#%Ia6!AqY_!VR z(KiP|RqC?%fr6&PwdE=wE_iCHUulUyPcz;guth5;TZ|iZOVkc{W$Y7Y>D+k3N#dE~ zsN$Ow_4Vzsr~Yr<9U=4Csi2S18hl0hnls{v-SQRAvNLsJ1=uuS8Y#Q#Cq;bldr z6Vx?>(Zowsb?``E2_tB2Hx@Rc;eh}9#z62XQ((=|93I(cl8U`rNoqjqJ)&!YOlQZ*~w;uLUddMuL5Kpa&&S>Y!n&Dl6NjWgcf(%*aWy7IwAlPdVZC*h}&pOL}L(zj6adbyr&Xg!^F za`>Uo=m=TTTs!z>Rfm2@a#Tp>>>b&55&kjC$~D6&ZP{5TS5}iaro}>Sw`HY9b8PvF z2oV$A>X{0&g|(S*;MkWIC&w|5FBcnw0!7_ALukFImA~dRWoe8+A|a`Toh5s^a{|1Pj^yyQc-BhlK;|upFZ8@b z-bWCV*z4E}$Iy(7(E#>6g^6y|LLvr~lgqu*!?N3ikiG6AdJ9oW8iHQBFYj9%q&++) zABL(K?d%~(p~0o{8P*!<=K5OLB|(lsgT?Bo=*~wzCc+b`DaVT(2W&P)m^YP^FRQ~> zmc5;_>Yl|(w-eEAIN3V>v?^#v=*vJ^w|I0w%uOV`^Mtvwi>8pw!p~C%>7TN!!0M*A zo)Fyt&N{wf4(XPGfh*3Z1$Junv>hqblGHh@y_0diDu(p-%K8%pGn;1yK94Mp8p{np zCG+@OCt*GqdSoHro-GDh?}WS8=OWM&aFdl2#)O0)pk_BIFe`Y&7j&eUMtDlZr_k`? zDZT1|mGrpm7HcV)2!6Y!zfaU-HeyRL77UlhZ#oEf zwjp|-!rw%V=tv6h)x4N1GxxiiztKm}C)Mq)C;f42d|0{0a3sC4?&Zc%lRrF`f_4IW9 zdhEeEbQ9w4i#Rtt^e0TaOMZ>72ZnZU5jU|y)p&}Clv2OKc-UExOA<^qJzxH6wb>r?8~*9(gl#72;N@5x_Z6tIVaS3lY=3Yy;WxLAAbte1zgS=iw1z`?5pIdR5A4y=cX+5r^e`dGm(~$Z9QrL(Jo7tcFECv5?Ty4V zyCg3fv?()GQ9l8K&}n$%@I`9!J+hxO*GyH%S@w}R&TZ8*M$YoceMeCo5-iR@6lat} z!Fn`1MiJBTpck0PL?j)07&zux*CsbvB_#0XzeTaF^6mX{J?VPz0Ut{<=CQ#nLksn>lerKU$}4O@)0GSuct!xm%pWXD6f z9#3JDY|t$cSHLdQRj2U!sMRbd$2{*`@l=3bzkFG^#v+pzJ-dRuq0vi-Q47o8nn6VV5S@&oX;b+t&)1nD&$(!0ju*2 zVR`VgSeL{a=E^X(@o=fxiJdQfgQpmcJT)Knoh12`Ywy|EtEmw$t9WXA}r!sqkZ<^-U)`W(pk zi47FTe=RAc300{c!ft7DC0_z^+D{cf|NnUUcky&o#~IK8?x7qY=@YPln4ry9@`1Ii zkNtitYsWXl9wmLVxI%z|ks>UDrdKfKx9>$cPJ>P;wTN#vM%1P*Z9{fVaafkX5U@hN zmJ)-;Y23lDqa5$4eiEIVr^i1U=TpO2N)~a{0zzw{H7pF;?oV)`1Lkr~grk%HFGHdE z`Y`P(MH%qw@k(f-z$N}(<+p5cz3ujZl{JU)Tdwp^cs`ifY9Bw*R@#0f<9DF_7Ycci zu5Qk|>>rf%P06T=ZJvx;iBz2|wUES+_YaF>ni6|Qx#EjG?*}PP5-j<}&&`^i*~0n7 z(~4up^C@oeE&#W&WnU`aZc@l_g1zX}ZJ~?)v&p%$d$B$}Qrp5%-Yjjw-@^a(LY#HH zo&8``+?2nI!+@#e$zZtOjJ0cJR-E;)^&kF0bwG>ay(K+Ubly#A{Qfn|0&?N6S%$6! z8J6k+mYbrtef;>Ww4iv~i)W>VKVMd*>?;A~4k6gl$?FJW(EKM$U;bj$u%Btr|0&vK z$X&=xXDOoRr#u_3SK!RzNn;>dEqI_xB3W~P>D2l9XF8@7ooVyPd-4b#9gNT9AL6@oa+UJne z?e+HqbaS*S5}Q8uRQ#MY?!>+s&WDfYZI$vc(~6=yjE`^0x#Fwnkw_3}UIsH(P=Y0z zdGA4Yy++OulWL(qhoWWR@l+#L-xVqKWrzi zMSFsAU4#T1V5wvvc0(`d&F1)Rjet&q=8`U&T^3ShZFMYUlz=it4So^>tH_dQWyKsN z)^*epRvcqYF9;-TW$7x^XN;7{GbxIVc8bI-Lt~tKJ~i5xvF6O#!Y0qr)urUBRxhXR zl|(YFHJkUKF5a}V>jGp``RB~%Ta1(;f2|kmJeAeb%d5kLmcKsijF*W6bS zXLQu7o~LD4`}Ezt`odYqzIy?6)HHZGRZki&D`8I8FnKgiPH+qJqBtgt*Fj^S%vBBR zXrpdIng;GaKUWw=sEJ9BGHPHin;P_`JZ_G`f~W*f&bC4-qJfQ}(r5%L(zv~NK3OnZ z7jloi{?)mco%puKQ$a+YlY#cpJ5RSBpQwX^_h<61L>=pm^w`oHZD47XpJ?@ap`N#q z@$g992z%l@5M?!9Zut*YH0DyZNK61ZbDG@<$P-e<%h1R(UjPn)9)Loils5~X@twfg zVn{yDV+{f6AHGAvWQC7K9F4yrUy(YtagUrYyTmf44K6GNEX@C zd-(6a<|NJHUJ`fD=FGHDE!acmD`;~2ZU8Bqkqu6q2o$q{Tx~7m9r!pE8~=+^o23Z0 z_Ci1dvtplSN`9u=0Ijtjm#+>J075Ny9gqY;Lkh^aJZH4wEdQ(47-8B1bcKs1ZV)aL z1=;VqS-{j?xb;7s-BF$Y2B)Y3SVk%Fb1D-x#C@|s1h8!xf@rW?TiDU?4Y}9FT%vYrfC_oYgX~%Mo(Y;Q7v%LC1Mw_i`KZI0zgw`xA5g* zrIKM;h=8%UwGt<_20;KI{Y8(Mz`r33EbY_3mRz52QlGPvnrBY~Y|btSv+|5{?JVGDjl0S&brg*RS*En%2J@G%g17Xwnh z7RYrR#Z~~hs+8prV2RWi2pCGf|7i!WfpqC!>PGMfut3A1>xXG0AbiMSqbAV1c$_T- zysv7ZL>KBAqIO}?c)11;R(O2>vrgHm0HL|W(4pwtp^v0%4{~#s(%E?~4`4QW-USA)^-~Bp*7|7jHCA%Sj?hS)_8r*y+Myq@t|)kxe?k!eKh9nSUhp<@ z!wZdma5#|kEqfZTdOJ4z5+y?5WN-S^Or~m9ZsGbkbQ8_{@xPX?F`1nZ$yXEuND|yP zm;VI~B~srI7Q2b7jXqPcTEvj^Se2;UcmvVWX$v++YI#ndd#Q1gV1(52JNwNTt#dHi z-`%;sfBVAThuG;s?&@aoZk18uAJZK(l+EBaJ`ZlskCDgLF@mqc}&4NMCD+e$6`lS4vid&MMoQD%#(Mv+8LqRrPM-=BaME|ZzWeho;IT!w0&xUJ$_j5NLo0~SU zyY}LfQ0o&^HQ)Kh_`#E>mzCYPNH96Np1%27ePpCb3BRH%4 zT;XKS{&1GgfLujB&^r-3koll0sED7dj1Jtd&GGYYEm1o1i|H({PwwO;z6CPt_-{P7 zlJVBeS$^%(S49^D0RFx_?hPEjyBu72li#%7;|b9u+80ef!YMwleh;O4`4j^&?WJ7R zFghvml0qFTCVz(t0yqH*|Ik7Dzvl_C^Z)P!z$6kvoe8HwTWz0>0`f()_#=5~_q40z z*C$8f9v?lg++VV@eeHG7Q!tfAqF4d_8ddjobyKhxVwWIw8KbZDMJ0fs)%`=5XRHfd zt`6KP=i^D7Mc^T+8&?uqKbl~ES1&P^Wh*L|V z#bSC=jl>E)75cKy9Iw}V{Bw1Wgh-S6Uw=RJ>#j16`oeZw8{63Hc_A*73&(vOLQ7t| zrCZvL?(&Gw8ESWHaGiU6bels#vDVy{tkmeBhZ(_zhfT z*ILg`=$?LxFgRYDW!&CuLGX;R5O;M=3sb+Qy2uYWHBKcy&AM=}Huq5F(}CW7;7(qL z#%O(>1*XlzlkEuJuXMLWMcz|?w)%%@ls9tN+4SM{kgbllShB`a5^g@Z>Fok zZnwO^(dK!$Nth+e$0H_*plmYCE3KWft2!ThJ2KC<$&%^qD5bv+GnxZcLbEMJ?W|3c z8rU3J3!Tk#IvXUKl+*CzRoC>O8QfJo7CwJv)Nd`Z@gU@?4MZn4&w8>f&Loq}j2%ABx%ba*W*q5fn#@*2 zngmJXS|bgg&)qgx#&sGs9)r>16GmQ%mMvG@3alJ}t;&w*AOrW@oZJ4U1xW351AH^g z{p@Y7vFOE(*iU)$O<8%-pRKn6-EudIBT63_$X9^eEa(fMfb?xiBu*FoqxS$#iTy76 z4-f?^h>O`tde-Rge=WhvdEGoZx+_8MW+wqf)zH7lAgl1|ex?l^rU8v)m&-pLlfiR8 z{x@De|5wL=*H0=0ynX;v;U&lhJ^sVz`Y!Of7X0Ipubfu?*KHt+d82^=o}t&LeggrL z-G+NasEN}x{&iXHGcNEz+RTF^k?{;RhgUJ5?dIXve7U3>?6h}VsrfhKZUYEKvd(SkS7cZ z)vHpAyHi}2I#;+VoTc3`FyG+6f1I<}Z}gc{8q#+RpI7{AquqswT`QMTJif)?Ztx(Q z5T-xLcK{0{P!>ddYo;Wazhu>A!gA%wC$T%9{q}f$W@wzP8?&aJo9GhWWobKilp`TU z9~?09sH^Yw*}uN3)a2CH`_<9rQ~bj%PT~FS#`2*rm$PSLILCzU_}!_gizcmZzk%_(^Zo3D%97G znlq}%d7-t_;$CJe=}q0?-srvOJ3Z@?ENWAok$DZqkJZJ2PYw+S4BFTUb8lT~*?aIk zRQ+Oc`xn*hBQ5XLo(YY-@;4cJJlO@{{=2yQZ!YFh_E$TvnM^>}!E`Vd!(%1F@0UZ7Acx+x zUQw1LfaYbpf?-GLF6pf=ZEIbR*#bkHzYX?)diNhh-%u^|k6Q6htToV|4Hn?Gs%@XCs9qVu#5%6s!C4XM2+t3Qi>t$@Hy`Tfk*K)_u{H|Ge_)6=k8o2#Fokl^_P zNP$dx#$#R?BtCA|8wqwrG_o(0`-R1=#cpi*VC3@n>#hsbIBQ1Df2l0TJ19{g*#(0e zM&>)?&ODgt;r(&1CQ-d0rG3$_)9aVYrc+fitaYsyjwY2XY@L%g6neqePwONKC-S*g>xij zIcXnQ-;S4_53?q4C9NqJ0~N|I6t($dB&0*Dppf{4FOfFgs3#V#ioMY-CW=oLra|2V zqw%SP9GSm+N~zWJ{_#AksCRk36&dM<9ygC|wX8RtwD1ip^o>-{h|*TR4FzAf`Z?n6I&i9hi|Oe}$3Lr-3eY9It-cr6wZ!nJ5LVj+Um zc|E|8#a#iNa?bb&N7nWUPQlFYq?yyiYVp5JF+WmUa(r0Y7l8?S&3=*#hmU$}bR|sB zN_W~0m8=cFY;S&5rPUd~Lk+ADG86uDOb#J1I)dYvVV)$z)FUPH( zz4O+7BaOFR@jIn@qOHhlwNKj~*MenS)Ate7mm3Q$pS0}C=*_eA$Vl^SBL3RI#&ri3 z%d}BKae=G@!wn&$jJ*nJmV{>%2e=P{o{1COkDklGJ5 z-^6~-An?58FB96>i!ACA?qUS@YsU9~K1=)K-yhol)$hSW4#L#;Tr)BMbl{a9-~&B( z_mV$*1TFqb_fwRr%MGc=i3*{7u!C!XN|XX((;y8kB~rN0crieY*Nz1dMD?2xU%rjo zHYQ(Bl3fCuY2Qufx>z91^_ZAayZU}ezY6)sl{SH|kza1-!Va)t}GCmMK-v%9w zH6r<|SrJCw&@CPS9j`idsY;#vSqU`Rp?5fjfo99EuL5>MSMS{Wds+k14Yv4o^MuQK zs~>Y~R1LflQgWk*FarAwt3q8*{>{f;2(%N}HIzdpxg5C?+~}J=kj2n_IasUxfX$qW zEq_m^HENFKDYW7DB9Vpn$#yL~F*C4yh5BQU;^XlAxNlSJzX}g#n!dNnG|<=DZy5QG z=ZL&^x}O`dIdeE}^Z9%{nlMI8q++<#pQVQpdJ^S_F_Q9-?t#_-9FkO5M2_L)82>9{ zkEh6O(9AE@Pv_UH3&(3OI_|1zd-;0!mr57YGj)Huf;(fqe76ucgc187b}5Z}Emld- z$cm$JtEn#-%OW=ts8cO0?Ln!M2+MQnC|^&U$Z>klenwZ3nj=xWe00@>FVAacOp5jP z3h6ZBx@tZh6blO=>maOPsZj?A)$C+2ig_$^Moq+CNhS*MMJBg|mh*A^A5g_M?cf%MVk{FtXQ&|% z70@-`qLIVi)yNhaknMBWN;UkYWbA&J(aOIHY(<`;beKoDy7(#q7&w#N1?BCzp=D%~ zw$VX9!jEW`@bKbi-vEDaPK2ppQI~|ePjG%^<~DMg{64ZLS>NCir$6e^e8r7-rM|fy zrOvp66V92YpR#K_bLUD#Cx?$_IDXA(poZ>Uv*5I-V=pnM4r5nCy;KwV$QSI2h!0=s zSD--LMYeLI`cLNFtvSw*MSm^fB{<1Sqz9N)zxM94xGLRrN2=dH92Mj1lz%B^#f#H8 zsTz>?RM^a31p~n%yOVhdXZDVkO5&)4MAho4eyU0N0`@0)H$z>nBDLs0P5PIe>S%7< z*zi3iAeiL4XlDWC=WE@qdMe2HMH@o-+fWb@ zu^g8-og%%4u~}_0t+|84+Et5MAXwq4jA*i{QE4i1)!{*HYIX@HH2y;QN0}WBiX;tn zwg&9VenxqEiWwEFCAb-P+zGd17G^dl|m(J9c{eo@|U1_#gkdryEL*ZrU*3#Gk)bL{g-PW zTezhHXbni*bx6^(%20?--0@=U$}lUxFvswax(|Ygh}N?VbPI91FLVwKZX?#(U8>Oh zkld&SZrC+4*1WD+q?OrBcj8^y{rB)GCIvY$@~MWuGOV(u@jRl@R>&BdmlLROsamMh z7-onAP~9SQSH7C;{-?XS96g-uKHg%4+BvGwLm|Y$rN_|6g~Ta6{Q;2T+V&gQbotr! zu6fbd!j`bn>Zq$1#4oG+e+?Afz^8<06EeBba@? zr)!3)uMPdSW4FVu=HvQn%WgjEKbg^R-oE*M#c*Zs4D0@!l(E7RdUh-1BUKeaFT1#A$?N4j*wWDwD=6rx%r22io z*lehnSnNN-3)QsB<4O;kmO5F90(zpGjGW;!QcaF!+R4(OJ-)j`d9^FBZpG^ebreF? zm3Qok>209<6hK{G`_=c#NbqA=g6mv*;g*N?_ug$N`z@ z0Oq#)(zUVavBV@?W6&5|hkX8(zdv#rw8)#6c7G z#v`Q;Zee)d?c*;-ikw&UQWI6&O1_@F8QjZPX~LCIo#joKhY#p-re->!cYzl{0r3_mXFND8SAvp=H}L4c?dDq*#z^QkuJqoN ze1#(@CR8OSpeY-QEfd+n6oZQRayj(Eggthj{jF#*kXLoIf_hzTg^b(c@>TN`NU?Md z04?C|uKzu$mz`pd^^(5sA8($OY{z0jmJ%3dCc}Bnf#3UnJ>|D&|9{i#_`pwxk~J&#)xA^q=gB z&h4*cc^8H|-cWC8|FEOzYd1`jt|5sl;hAZMqK!3(q+8G+k;^Kw^5MkIeKsz*nRVw7 zPkzhYyLx&96w)2CySTb%rmW&aFDdFuzPy{0bMQvYcGxADwBGA?Bgka{y(Od%4U3C< zhCFF2WcNmZ#9dGDSg;7Bhvnff+QPD4^+Aj*bufQ_whP_M#xIDEq_BN^~GC z?73Hxc2tSSgZpb+T1}6nKy=V6O~Q#~V|2fg7}xJk?BHhD2U3Lkpf^SK9;yA>-|O zmT3GKVQq+>K`&KB8u+LRHHIbP(-F&HYCmY-w9T90tn@+QY3oS0bfvkWv%NPz zHb1YQ&+V9;kC3k8*O}I!Byl-+d^RGOWCLqTR~vP(cA*S-xD{JVn!y%H!@-8q&>BOv z?vt-3@4{S*HO~_fwd@7uG0lUXti3Gs1|kv$<&9su!C`GjY#%%o*xcM@#x0mLBCuIS>FH=@M&p8ewskFzm?!C;NvYG13TK2g=K%0N=f4^T#X! z!O1pm09S1FKH_T1L0N5iqb5=S5;6tKI>|11ADEIrafd~mfFBWy^aUsZa7p)_6F?Xa zvcbj{lYf-zkc-#}ELtNVg*9=l{ncw=-=6rS_iQ6w|Pl+}i?8%M)<*-f^dP&m-mL85v*7MtZB97kzlM z{%XBl!^8t$4OrW4# zPv;LoH9T5F8UxWhT~7h2eR{9dXiMI`kVoAQE#utGH&z$FkL50(QnTEa4RC`pxkt|fnOpZsUQsAE`Mq&SH_!0nfWOpW-AIS$x? zT%j?BO4#VnV1lwoHjSl^j`mcm;=9}GuPDwJH8Ym6R#PkyOge=K(is7X)BO{5`4xQ`d)-&W+D# z3Ltf|f{@wbGOQ*Tr$sZ?O+w2l1Q96-yN-n=2RhdeNWDb-p-v&SPykGIEab|s%QohJ zX=SX&uOfFp10NOu^%!s7BtR`vY&L}=jspJW`Kj)8l>I#s?GG7S>haP+cO~mfU}iRkF>{+HlLj zY)nuuZzhnBH=%Z0VcXd2!1TpRutN)rINfR}riH(%5fk`OibI^}Nd^Or8xT7W^jsOr zIWX|aqRmJL1mE*h)$Hi;Eovhz zn=L{8QPYC5mkQO>ub5I+s>#wHt-<69-h7sJk9_;P}Cs%Vk7QCaX5dqlq4c1 zvo_I09qsfq>JmxhLsOZ8ASn;A6OZ&k5pj?b>gaT6OTX?Ggm#I#`VE=yhOA^(yd_^I z{ls^?16ryEh2&h1o_8=#4Imk&P>^o8;6V+{5p<-RF1Im4{93Wgp+WI#L5PzJ#m?UiGC8s~0t^(JV~I9Yd2vT?g9v>cLrr)nRP48mw+uJ~n(E z;Y2tR&?(+7yKl2pjD^Nbd_!U`MS2PmJ(nL(^L=Z1aB94{a zH+3@U4NrACD`_2Ff^a#cA1Qt?#p^(;RaR=D)G%-Rd^mpznaA~6A`3<=E>YH1fI%Ph ztI;)w%ow!A4^N}V4!JFCF9$5jZUvOi(XPlhm6rM5!>-^@Sty;je#{LGAt#)UTx)&$c}?c(#Xj#F8p?1gQ?_j zRU36Hx0t$=lA21wi;;30bllEQ z%K+UymX34jl^ag>K+XGYoDe-QUg$1@=d@X5`M#n{enVxAsu${rgepb4DP5yWz+8Hf zn(&bgA7&4^|1xOWkn#b=a72sv8r|W2Z-LHDxDz z`VJH|H$)h4)(oS>_EBB9YxJ&wuZ+GL=VYKFPAo2Y`%}2JA*OliBuncAM^~d3nb6_nw2nDN6Z5tq)sO9&>E|PB5aO267wz+6rR z6u5O!%(vuWfy+#{MEm3Dx(3?&r@*|g5DLNlAO|_4TJ;4V^R9D+qf!&u9UH%5|B3fq zMZ@z>n|$&Ox4fRw!@Z7`P9RNELd}ld52*U? z&yXo?4A6p+-KI|wR(<6EAa%TO0WlB)w@9x1MGt-_P|h$oU4)|pfGI~$!|TAb#MU>{ zdI}p~P6ZY&JxI1WsAu|a08tO`$+IGpZKdam4SiooAAh)AzTCmlcjwKLW?SoG1L||r zVXC8i*|a1FD9PV(hX1>|at=__6!%ew8P&nlUXs-zYouFI5-jS#DiK1;iS5Ly?)G?6 zyE$uHZmZt1ej6`=C)Y-y81;-$XtnZW{21m*U5~u4E!fgj6y{k9Mcg@{%+c>V5IuEr zkHeGcabwPrcjB6zH-nbCh217r=9KDp8C%{yuD`UgD1;enzuMEmblf0rYf}y1rC**M z!hGd|ZXTa;LcWN3vIN$0>HzSGzu2e*cXmKJryKVPW!Pp26VYfFaW9mQ0ek1B0aM10 zlkQN9YXv#MHUCSS@5)?~@Z%%^)Rv}+vH zFKjMa>UML)^5HA|bni`Hx70J|>mz(JH44@Bt24qDt<9o;2W9jRi2!^2{|N5*_soGg zFmoVf$$$6HNcz1Q3{Yj>KlmUH*Z=_J`XK0=op=gO251R8pp{sVSPlC~TtUm=2=w9* zFt8v>c3}Wraq=swKm;TCJ^(EB9}cnfe*1Tj`G4{I9{>lC^o;|k4UiY8*@6AH+wo1a zFREa=;r9}sVi2Dyd%Y|hquPg4F9Aj2<{l@s2LgOHnz6~{nY(84HG`}fJd zpU+pk^@4YSN}w2^*6T0q9EF6$#a}_21u&0U%TRS7Hj_!H(3?aFNRB6WA`iY*0Ia3Z z^S_o*`3&&}DqwLgrcpvG-s0fhILVJi-%hh6A6byC`@5bvfVV`T=~)ExjB0`?wlRm) zgz9t-QIni~^eKX+vy{l6RqXP|{ddZ4Zaq=Zwa0ez&EuZxg5{D&S3GHE$BeDgp9ido53GuR8~b(W!Dx9Vkdir} z5y&}?In@kxMUI> z1l3QvoFOWbL2v8_Svp>yoHN^D1voBqu*43fJw%G7&S*q%JRBo@@bI$zmTS6RLRt%G zz5N7U;a+$7gAdL)f+@8-v$SxoW})o&QMJ8irX))zeDyR1%$aZrMY`)UJ|dz(Rwn;h z8k*FT`#!sq#Jewro;!X5ySTm6nJQH$S}hn7qD2*&>n@ ztgV&fIvXnSH3NCi6KfN(OXgUZaiQW=6tECrtIa2+$irB>DPc%aGZ8iXG7Z4KpLl?r ze24nBKA=G2ELB50k!@nfJTQJ!R?h#JzY!j2O9Pfdrf(?wM1(gkt@R7#^j2N$Grn%M zq``Y3&hctZH-@~IVrHB;diSBp-Nn<;3GC`g!c!!8W<(N?)^a|{E>cdy7*1Kc^jMpW z3B=cmAU9qE-M{neTzjp_bnS(Dk9LO54M$vgGNMU?tKordoL!EZH19GS>cg8QuF+vV zXFo#T9_#$TVe3Q@qN4-*#5T_ERAc$%H@*5KDj*cK0LB%CSMwm&>Rn?KY zytaK{8GB@DCKVC06bLT$LuD}$HeTTtGhPTyM4E}v86Ez!k~jK1Na83xh?W-Vu7d-G3gK8i z)&?Jo(Mq&KvL8LAYThY0pL#~8(U;BvJU5nju9bc!Je16??)sGYFdVO#J567KdU1eO z@YSWU?xiSn5C$2a(fv?47Vb{6v3>xI1eF?0ObfgEIMG;vpJeTu4Ny^UrS=Xig4v_nS3mR<+<8+ktWZ;l%o zo$o7L;qcm%H8W)}2|DE2zk>M^JCPz0`x0we$f6lPxd73;+UO#bxw&8{mT2IZ?usc8 z_0;4Bvb@FU0OACIGTRfFT_4pgN z7Qp=_UQFMxwGVr*lpHO#i~zXx3n#H-_laRwr`inP+WAA`Sbz_vVs+#ylx460p><$V zAyxcJ)IF}1wHduDVyK{)0~sML8g(>x~>pDn`&Om8&UYi~Z?`!gw5fF6*X zpuQdpV`$+`0;qpNs?nJI;V}~6tgYj+_8gli@9hI~yex#uShbK?-4BJ4FQQ#k7a%t6ba;V(0qB0qDwJH7*^i*;lfR7Et6JT91hMptM<_S~~Ohh(>#a{-1$wGIni zcRoFueT}`ABn?8K0Hw`UhuOn;bN29=Gtp-WQ1qG2?1%61>L0xS*k2owR+xRzVb@}f zR($xH8&zkD_4gbpG>T3`G-6Hj-$w6zlp1y$C>k31+rAo^%g@_vfX41aRpvFHDLd@ZAfzE)Fr*ZNe%s0FKpldpZ_`?f{3E$MDKdFpUq%~td0MgAif z9w|Y1XslSQJu6*X3gq(dC0izlkJvL^dR3yP-3R)mt3}4)0zwN@#Rmm2e*(q6kX+T0 zbNe-UPDH$dW{@#9jiKDAD!?Vlm8w$(%x02?{x=rH9dAHZsNX-qUx~+)IX+kX zKGq+#7fgD91}FE=7SWnFw+<58X9=yByDH&d#F z_P|vlJ$lHfzYGXWaC)FJgePKtv`cc_dyE`&w10E2nR49K_caZDHKsMJs)nR+$5L|S z@vO~aZG?~pJ%*->Kr_}Gz!pF+M=?iSuASyaW+GNNOfwj3T(9`4*I2HFHVF>gl{&$V zqestljH7XFu_uQ&CR1M8>hV=_#}sG$U2&VISb-{DFDGZovIDy1Q|`h6noSdasQRZA+C`w#g~|XaLL3)Y-cVoRLONM+HdR6my!hZ zWWH*nlZXT~i1j@+Q-l>L*SiUpKaD7gtD*WOb587to;?2eXxMRNZBVCYdWeJi2BU!9 zw2ZKcV%wRmHCt4^~y z{R*t3*;u)r%UJxmqk|M&N$BD@O&UIi#iIUY_YfL~kgTWLFBp{Sh-Q`EhMC7Kk_V#p zx{Y|wIt^?m&12SvXvT6Aq#lJ+dTPkZrv-d@jQlmW4%>lwqaeUslW(IAK~)y%R6{W0 zz`pQ63Cb4d$ubtOSF-UdpTg4m^sMW_LY%sE8QP36K{BXCSz0z4=KCP`c$|Q=#2%3x z>g=Z??qeWkR*1FQGv(&`G_=%#^tTHI2L9ezI( zFm5W?^ZfbNYVO@c=JMvg-1niixkLQIMYHj{i`Q+Ttq`I$D_xTyzXA;#0p%8kLz2hO zL#=^q*a5S+8h9r$rKSW`_SY}mal$0D7J5zJ0a|zjm;%&jFQ>%w(D*w%00!9OU<~qm zb?IH8x&J@Ewg-#@y#`*kmGIFH0Lt*|Z$@|H{Qr%;_l|06ZTCf8%Yup+1u4>^0wU5> zK#-QTL_k1@fOH`?L_kCYBxuM~kq%}F0!oW?sS)Wd5kgstigW=fNi0-J5W<8c)BC#a zx9{2e?)81+>~qH%1neBbvr~Hcf8o3?4r0@m4b)xsRPG7>l2BRRx+i zwmll3`xef5)i%GlqA~v|aZBz^1juXTPq6Wq(Bf24d^iKe7b{^%wvi&%3Q5{VSURE% z&vRM`M%nN}%bHDz=j}qDo%};=5`94mZnWtoIPB}%HG(4rW2MI4X1i6r@TN*tbJjci z-a@k}YA>*ANvG!3Do-wUDI|6)CLRt6x!9#uq1Lu&p*S>y06$^Rq%$2~Tr!m-ilVb+ z)wii-csdR9P7V|~7T?b+8b5vi{HE3Ucb}!o-Pp|8<2mtrRue;R3=UN%I!%PR2onAt zaJu>kYDr?h20Q%1$c2Dik()sq1iOo_*do;~TgeZBx3k+8f2mM>qW%^C@UeE>hBtSj zKT_6whh_?Q{1-bL|K3;azq=g&Q(caK>%L&3yWsrM_G5gum8ackL<^BZ}_ zuy33Gn;{5|@$y5t_an+mwc*Ax#I{>2a}36`;ZEkXnxW9MQrD<9Q05O%>x({ktCU@_ ze^mmHj4V!$T6=9=;eG`ZPXOP2luB&JaIcWrr^y1B2{sn6Pnzdg+U1t|45Y4-9 zP_o|-V@{jQCNE+!Uj94`=>ynVT;mwkw{+*jdE1e$AM0vqcMZkZG%k=-1#KC|*E`0_ zQ(zkoLT&bu-^+1|T6$s_0+MZEnBFRFmR$<9DwR#?rVVEL=Hh4D4!?Tp;}jw-#~<2Z zoL@MuR2AShbGF<8qZWYKiirXXE~CQI_?*I(@!RytgXKt=ZovZ zMBbuB0Jp!}MkrR{d}vt7S!4)jZzV$gwLetUrCQwZnh7u>CwpDY-6-YD~Lb*(Ni1!68z$Xo{ z%(r5#t|@oVRX!ikDRXjoFt%*vTQW!^O+&l!zD%!Z;yzfJTPjE?r-~4;HJrkid29u6 zt3(YiGSQpxPOj_+)uk?=#SshyR4_Z6B-ty>tb#!fHM@vj2A_ak)pa}5Z0Z^F9-5N) zYqKpk2dN4Ih#tS+#G}A3&9hGb!>vCkSYfj)t({m&H`MVn0~R)qa*rqF%brLNw@VHk ziD<60OC(6uJ6P_>NU}bryNj9LT>DRyZ!w^zq5co&VuR6RKb?!6J%@v%tQ2R(vL;vR&6+u1Y zuMJZBDcS&i@n`o=s70gNKN`raU!$t1B zKu6Ba;k_3}y?~-fl;n8U#CSps)UGkSR@*ImIBUhEhW#e3Bb}*gkZ#t)82CJ(;irIn zsI02%@i6*bu%9MLRRXD@7Lffkhua~RIu5v~6vOM?!5p|-44>NSvBx^_45`gybCsYW z2pVx~_t-HQyy-m4~5#jIT+43*JEqR2!u)SXq@~(m0Ca2uu zaRjztVK(v)0JQz2KMC4Iz~qjC@wl>6kMRL`tC41F?KyN2MilZT-}$j_Gyh@Y2Xdhvaka`(dMxi_T`xo_Oom1!RK;(6}ZBesS1c%1)%#x_fX}SWirB?T$+0K zJXo>pom-%uN3*Ra5^nH+hlg3}PE4n8f~hxX>B}2%tluS3&f;Cvs7Jj$lm%7D4r2d^ zc`G>q%fJnHwtA=ml&j1s7%=MH1&=i;h9NB4h@Gl`)WnWzE*#20M|xuoJH#)x zxR}2Z|BL7mNt<3Z)7pE)EqrD6P_*$;*mAZ5hw>v>u@3kFxiM9)BZ#V{$a``%g$|6( zMn(w+;ntk55maTuArPG+mt;g-hbB8RCrb>VZL_Y%Nyc$E#)W$bxt8GPP7-|js<6?n zA<+bA5*w>4jWp)q#@yjXJGR8bFizo_UZOI-uR{VSK~%LWKswh7?^BEqOf_7ty{H&K zd{09`*Nikh8aU`S_iZy&8Kk$z5La-A2!P+@ElUdtwlng3U;}3J`c`sv$V-Q0sti1% z`b3VeQ#PA+Er}TpB+=hKNZr#o1X_G9edphoA$#!XGqIBv`Q15$-`V%3$YO?aY+i?l z?1Uhbsy&UV(rv*-{oKYf_ko)Ou^)Sj$pyB$R{im|6tB>cw!JWnK|;gAu4x~tOV@_( z@B?z-sFt-hZh}(ANLEa+dS0TNww>-Hq?11nwPD#j{mKW{;D9$pxs4sSBx3_m8oaEU16m&UD ztLiQe=z-?de!kr}@r@mRM$fbMz41|`2UFuUzkBk)rv!!=n0Du4e1EKafRQ0e_*w`b zj3&hq1&M>xGtOX-7WaKRhe-oG`7V|H1oxnM2X2 zOR>0$$-~BHw_Wmv^s$ZWRRV3K7Z<;Ws<>+=_YrFr4}y^J_ddS=g3{@L2h+iQa}x;t z3N#%lweWxp&$w17%)Xo z`)M@s=JPt}zL7YxfM&m%_4{EGNv_w0nla3<0(cIWSbMS|o8mUHh%1c}*j=;bCS=psdnf>)tnHe20 zUE@tZEv;2FfQa~94`wnOvsOu)%&J~_OAz5WA$IP(U0}SI1Hfm{GSIp=0x}Tu?a+F4 zz%V&TUaRO#lKYB)EH3W^0EjRT{3NOXK`O=X+?xP%4-3L+%g?AXPJaoT?b1RlzRt~q zO^d@5f@W1UeBc8%}Ln&cR_)vW-6Opv;9;4K}0YqYgte?Fj9^zJ8}khZC0 z%c*^%ZVDSSy(Noj(?euCN-uAZes5)=kW3dXqg5iA(w9o~T{ z&cLJTY_G;OKPj-0LgdP2Fd=DB>CCC2%cjF$9@vHWYfg?n}HvcPl>E!T4%zte^d>C6OwI2{Bj6p z?=?0^0<}Q+ryPJ0o3e#TxlgVPnfGIidMI;HEo51s>gWkq1aBjl3&?=?GDEP3-Aa8V zcYd7@0qBov^qo?Lh6LBamu?E&7(gWJUM>2Z79Vd##_a%qr~pZP1^p0TY9cr z4_j7d@2|dY$^EW;zf2@me?bljxFr}`#+-OY=>|K*z5CwdXclv2E|3>SkX3p6GL`E~ z733s-KtGa5=kDWR=CS_Q3p^9|`|d6uJS;gz#^a&j9+vTkwpCo_H>DHqKI}6+3wqwU;T2spQP>4GW_29qdDh&uU|+2eF;RGBZS_J@kEbyu}q9j zOT58wr#s&G*I}Fi;kf`XPb)Psm#w#2xjk8*Pc0tFyQTf`ATHB1s4?AO_Fj!w3WNC> zcc{fSo~Yu`e!3u~+etr0U79y^{dD?>DDk2HqWP53MSNTvQJx(cUJ94Ar+rIcK*Dg~ zspi2Z6DD@2Pm2Ej@a!uieod*j9+Z{NN-%f?@HSc%3D`NX$ZcbKm_0L?U%0W*--pL5+$A zCw{W{wQ!jAfXfU+aSyyVw4z)=jtPp81BBlM6hr^)nVYUiQ~|**$>Ke-0k4JY`zE?# z-QOh4JBmNpZ3f!{L{U`r=BHFPDGpKe#_b}i;V;jO3zuld+`y@&CK9szn93rbpQq)< zgl6qk?lS+ie!4#BYrAE?!lspEpcmj;e51#R5m9E~VHnp`1;N`_mMw5rx#%|Hp?;eG z*pcrq$=io;?1?$gk`6VZGQqW9yZLr&);>W&(C$o|Yu3oL`QZ>@GuB`4`nl~OC?`Uo97+1AYl~FyJ zDC}ln;4G@AUgr1ofu7$yBjF$183&1Hl-4>@%wfJ&ymRTr1pPBfk&;`iYt2P$9KU?N z_AdCKUZG=YVxYHT?&j8=OGUN`QX~4c9E)!w{KUbo!t_Sye@U8TChOlzlVmvm@5*Gq zNY4Kbo|<&9f-fPR@&?T?4g@$Z1u(~m`=*an*U%=(8}TMgQU_TK@5-^;Z9`_sDNK^? zQumGH4eSqn{QY`^SM0uC&GC^d-$aL>2LHha*h?Wr;D*u^!rM-KP2B>UvhCdm4Z*mP z+t_dD3m652-`@Ls^)4?&LEDJ(uCObM*%{lUUOd{-=S@A_;4h2UY`L|)&{rp==&3nN z#H#()_UwxkIK--+#2qwhH{KK8J!r+rIqc8F~H3GA^pvO&CXw=q>umu9&UU&C%YVv(Kk?1WxSt6V-`6c)gMn3tjp>vM=&BK2ZKrwc&0N-bk z4hE(P#68nMjUj5VqSVS;C-ym z?u=_<9DwB-uW7wwLb<5e`j8!8rR3!qr@Hn3WIyJcZYw!j6!+>1_E3v``kBLaxoRC5 zMf%UHGL$y!+}6wG3Oj)#t0f%o3!=mwX#qr$n=E&D{Ummzn@Q;qQE-J{ad3>q4+JAY z5>XrhEGH?#ae`za!CLUJX*WmAnKKbbP~ly|Ul=lDlSqgxLA7^YXJPZq^N;w}Fk#0& z!tn}XKZb1wJ@Am|(+KLm4?5Jw@>4fvX|l@;k!TTuQxV@Q+?JD79m+;?zr(-qRQN!@ zI+R^v3u}cI!jfQKD(b{kj=spI#4Tw`@lVh|2Ps0c3BznQu>eBXW!}>2wJZrRRTDZ2 zDrG~0QQl12cF?BZ@UHRwl2M>JCn~DCOvbzy5t>f3p*$rVuVlZ=WX+?_^p!SYHfCk# z!YnM?Df}woe1SV%CpD&&uf#}q_YwY}EvKSxm=5ab@Cax!>nKLv@I23S7W2Hj1Cj*< z)rhjsk7|i}fbOJz%jn<;uibb9r^H;MfPnU6c8g`ow9&RTZM;3X+gJgg-mys7_nh64 z$)JTVOrnKlGDTU1f*U|WRk{ZpvmOPp{kqEa0)21MF;<9R$FV3x_6P>Kh3>(|VkS#^ z@dl+U&8PVy_pI^Z_tg_yiEpI`fvE!2 zmPU@`n=;8;JqTV&+@QWD2aQzCdxk2-`0)0WhJo@pZs%M&r`7?$*ABzx%<`PH@=9hV z)`C+|5|Ha7O^VXwTi~_*Wk8>I?UYs*1GgDgH3zckWWK@?7w;q&*u>gwhqh>f<=P+@ znO$`nf+oLo!^%;y8gu@9YjFMFHgd$dPm1wLZhij2W2%!xN=V_X#}QzzXNW8L@X<#e zU6wo@Ow>m|nfpj_(UIum^eddCK zCDaI|m8b=$a_C$ib}e}Ph4}JyY%h8khE7FuB#Jn~lSbur6C0~<>Z*)gxQ#)#$>@)8`NE_z zWpXp3*Gk@8MT@oKket}YWM-FtbQ1CHVS-ivQhI{K#0eU$$ylzU`gvvlvYheIOVHIE;#yo)gpn!;C;8PIeqw4}+X<`&&Cj!{A{U-vb zWA2l{XSHD11-1Szs29Wt3I(+S5HrQl^SJ6T8mMKaiO>ip)O_tT9kZm93t~$Ej-T88 zeU6SOHFhkl?FmHj^)5mDCCm*&&{f&!@qB1`VR-O0=Dw@t6irG8Na~2d5uxW<6VdwI zGIlBx+p!R9d>F>$z?Y~oJd6ilcOVD`v-3C^!9EDhw}%Z_kOZvjoZfy*mmcn!J7#(m z)vgKa86aEa*f^$t8!jB!MEPj$0M7E7t&kkt$S5RLNntzzu4|=j$5fUMTRB<>C5$gT z8RfwqdTD);p7zo8T4O$L*t`UWoCbodE%r*LIp&9**Os(H_JUhyO=e?J^EeS-8hdV( zHs8HTBO?n11f_n8w)w!e+K{LRzUw4b#0SGB2`Z1fYqqaF$Yz!u3#uZHV@C6~(9+h+-vS()?^l?cZu++)7FV$DAicjBEYQ|+dgd7GMse+%C5sA$63M)Bh=W=5<4`%;GE{cNk<(Ln?H zj?eN~yT(&*qo=_A+zEQzEfUbwHv(AF(GPi7p`~NCfB&}AYE%ynj4gNS$^oFu8)amB z8Dc&MaTH|%bjP%F2gGOun6OWzB@n=)Q=rK>%uv9SAJ-`YNm-ElMb0$>>M^E|j_h**&n-Cbo{uod2X`uPKLG#R z8{jDZ_I=8CR1OJ_sasf+IFDfm`!Ay4#OL1`c83++xj3)_3cz zx%HZ$0|N@$Mm(2w;zioxJK_1u6ZFLCn@+kZDJxFby8}1efF4>0Wo_rA#sl4yO8U4l z9Lnq#g@rqbJNqXG`N}NZrrBGhNWKFj^KyTZU-1=JZc>16k$39*PZ3#z%%fL^8A*gA z7Bc>koFkQG8{@iE4sN$NlOkUwDBU;yBHqWem-no-sW6R_SF_}c9Wf(`vHW^)Y4wTYLu9rs z>4O!cu#>XO&G`f0sYgf7pP}2qGtxM`{SomXUTE(0Ov9JdYbk?&eE0yoH-Mrjb4+IHo1XAa=%?~u-JWfi`8I3?TfjE2a%ufZBJ7VL;v|QuWpTaBW z|HqMuL!Xr77aRPGi37$SudQeo$UJQphZ4SYl zTM+nYMh>jB-yHa{?s^OGljZv(NfAl}I{>kO_WWPEFqGDASnoTx83YEfQQfm5_DWNe ziU5xZ{?2+w_T(KV7+r!6h6ZEe56wu2#DA_%cDD8p&F3rjQ5AfTN?u>DPR%cLIbx{R zr42O%uO@({9wbP?cy*7sad!)Hs!;%OxRtNN(O;OPyhM)(0S3$~!;`g(lzY@Y04xA} z7J+fc*ghGzQRhvSg|B>tT|SPP26C?}o*b|X9mLuz6h-D|cqRut`s#$j%0*^c2KApU zzTps`D{Ny+QY*;aJ#O{!M)g=yY)qZCY2HUy>!pSXn=XH?RB$qA;t-8JN@iPi4{NcE z^NU67J6*l#UD*3Haix?%bh{W z2Zw5FlOYotAK-u84a{7;+R|h!EoX)p3wMjnK z|0vh`*)g;Jg;z4V_`LC2lY2fp>_QS_t;@?t`K^G?C3+7?=b--Apuj7*{{RKn{oe-# zj>N%+za?=1V~Dv$QS%<;`|BqB#_EaTs>AD_s_oR>Ipp)zzzmZhjdVei9Mq}`nvK=u ziDG;f1Q_DmWxlu5Byi|_W$uQwTqm#jk!K})fE@KZ9I!k6X&hkp6o`iqECh!CNkhb` zlGr^_xzIi_LSlAD7;olpXNRpMQ1`+e)7gDhT^H2nf@X!o5{mJpwp*o7N3O_AzMuc- z?yEocUA?)~Oz(nQ9Ir%Gl`@QiBO8&exLKYyn1wMonm^X*k8l^i$-b*}MTy;cd4}@4 zbsx7pf9v5#xts&~%ie*{=9RZ!De_P++E03er+4^os}t6Wv~JIEy8rsS1%na07Jogo z;G#$J)&!o)1%>3vfD++@MS;DAF+s&=3RsRil8>4Bv-igexF9X?1(-*KR*6OA(W1bw zp)42KTMi!M)HUQS2F4RWbb40S%bhj+X@%`YkXT*1d<6>-~2!aK3>^R*NrZDBK+S1xnBdV+x zcKA3N%k^G@{Vq2|76i>3e>s_(_U+xfBjO91etX7_AVvy8rU8G*IovV&?cC;Wn$G9T zq5Hj)c<6aGReIRPk=rr@*-qr)8@I8O4nag&6KvdPE+{UZ*?WJp@ia$*-rO+Pm#L!g6* zt`(ZU&6DE$HUlnVEf4~8g_VoFZ;g}7 zW-Tk2#=ve9juR2;ke43JeT2s{hwf{~@fv3vMO59g`cC&4TIZTW&y=GGwgpvG&y`{L8`hzZh!&e{EX!TA43#5fKK#oV!^-gD%nT1ApdU2>8JTu&Hax zjv>AFa9ry{c&jyvX%3nxap)IA^O9^yuxRef`pRqeE{xjtQ1=o{>iVjK2FtX&aGEXS z`+QYPifEOSjYz(s7*lvc0!vN zV0m0Vcp`+kAT0G?WLe*2KzE-&AH*>N=gAE#B+67b^$M8AN&Vxt|H9S<&x}RL9LORr z$AIS@6axc5)N8{skbYg27E}YQ85I(OyhyMbxpm?WfZu^mpym#~CA^VAN+4J6?J+b! z(J%YQ7^@1(4XuDZ8*OqSny_^(^}$ub#CBh4U3#wKe5uBhB6HoCrIR@oPm=6-=;c4P z?$)jQAah6Qj^3wDdo9*2DV=%vPRNp5dA3pJZtJIk!)KJtq)Ms5hZb)XDmkKd$CMY! zTfACqa8A>*9@f!LsYrZqJ;U>0tj4@$_p8NxukY4`2#~L|Ya4^g^WNX2I0O_pxox1T z3FR3;!+c`P#9BX2q+}s>z64=}18;12LZH0W63zz|2m;c@f8>m~eIS$ijrNI-@uTsy zaUV|6q}Yds*`j1BmxXB ztzsjiRuFBx9o&-;oAR|fbOdJ6iYfzr2%lUKmlR1Cs z3m*Xg{p)M}+9~j>Q2bqW9IqBA9ugy{a3on?cd5#F5;qt9%T+gqBuN{y?rqtW1PJHG zC_uVd+n!xKqc7K2R*Wy~lpg33E^{2RvY;q5)ICs+_(ob%Eka&F)z{*sW^mG&YckiL zd^Df8VlSRMOj=#3PBk)DI5v-qMYDgJl5=r$wXC7&*cG}&2+L&=Ne)ERsYSfuiQSz+pwePqQMC!V!hw`7-#i#q)!rX+z zySyEov0ht>99ry(KV?iiL}@i9HC-F^o9SkEwl@W1;TY;3n`W1R5)B>ZiPi@`aazI3R0#A+LczVi-BOzP|i1`Pmlv$lkp?pMvh?UVi%=;@^7}E z6)^gYd^39pTC6fJTqS!A8Nk$h&L0@c_+o(=yS&JQVfg4)SOo%t7aXIlrLA+Sj#Ih%Rg}Af{I#!_-C8Gkod#9 z3WJ&j3#*T`^cUN0bGytF^ZMOv$vWgSf_%yhIeS%sXM-O0Ohf zE|7<5Er?X#Ma2l78HH&bV$oY>BJNn<9AmZ40{yzw$4>7(>p*m&+>zp6I<5yi?EIB> zhkyk&;3MU^w8=Hw;{JNs>CWiS z2LsYq&Sr1vyAkWxsuk|7VzvMl!D1k9>F3&mFESVSk1#txr3vCnLE}LrA2azza}|^& z?I_+6R2+G&4&&8;xFQ-rJKThW;&IVyzAq><_Q2L}Cyl5p;A%5+m4 zaw1e$5c^_5a3TkaqN+l#-g9J-u5vXH{J9{RvTZn!(`-L^{9g!h{`xOI`+ue3$S>cc z=6~V+Zpw~T7ivMav%$oTXh1kYvc-bcy_GreAWkBCd&=NL-z)&cgK5L=_Bnd!W2wZ^hQhdNfk&u9<3O-m7bPbc`=OKdEmmsA(j!ntggA z=6n3PjBr1W{yDQ>_LK*XRzoIia%AXb&4VoiZJ!6eXIwj)AbTbgU9M!Q7kj_C>n;>(8+^uR3s&;y>l;*c`rgkl#?Xa?Rq~V3%h>!^GDG_`ncfOqfdp z!S?~ukE<}BKgN+^EX-LkGVQsRoJ+2$(R@`;P9uw0d7eXW(TCy*I;>_%#@Co-VcKxc zAj`C8Y5un9V6N713t>gtD}i9~%RPgrs`_v)4v|FjchQk((i~_B#q8yP@-5qkc3>HI zmuT1%0(tXK3+nu8fP;KDlwki?K!fD1$W8iQWq~7TdRMb=n}i40$Ra-Tt_^>5-|hZ6 zmCF)jE1lePPdO{}Fe*;!BvWtr0_oP4{a1QE#q7?4nuC!-uX z#ogAE>Fe*(9m?i?-j%C#XczS*yOi{oIlj8_yT$KjswsMKQMIhKrnfC67+8U(St#Z< z-nyF7uo5B=7UV(KpwTX_3jlqhS+#BCdzcFKp-7@MtlNehX?!1q8Nss1e8q7Utnd|m zQnVL)A#yKOkjkh^b?XNSX7ttZZzrpGCJj9Mx|+VS@@xA9PkI+33k$qQ5B zKo5ZDdgw;Sao2%DY-!97cMqRHU$rrN5t7VMxWCq4xzWX}>{in(YFWwhTeEA5_rS9S zahdOmeF0yHS)!#gw{1cLZ!Ee_tK!mlO2huM1L@2Eg;bRo_8*#L`s4pYVCJt}$QcGH zg82+iB-l@t5!H&`;EmZ0UGW#4zS!ofOZRi@s0G6&G=nT;9%7UMv|Ei2@lW;{igQ5w zj6u6szHoIR2rZ1g2->tASqC|tvFGZ0!F*HB=@fY9LGXeQe;UwgLH@fDJdC+V1d$Nu z;*WJ`XwA_99%6S+cE(cG`IdlZ?w$vp%VK}xLB5-y5i|MJSdLo7HlOzdQi#{mp&b5J zL76dzuL(o>(Q16p+$o6$|GPvKL5tlP-lThO!Qa1NN?x{aDdDiF-h3IV!G~PY-I*Zu zyuZnl7vScnsLQ{q$-Mtz&mW<+aOMHS@IU`tqD+m;up1Ayd)ArbRNK9rN0z3CiX88M zmwMLF&(JxZEGzD;Vd-O`Vr`lC>0`R>BX9bvvV;sTRU2()x`nZIQuS!!?23BR#5?Gx z%gjaYJ3%&WvUH+SnJ=#ECP)X?eqjn{fQS9zJQsY_S$f30PnZE=otf#ZtCBJhd+t6Y@R87utV9V#k7uT45DZi;V7`4OK zNzyf|U=0&NFv|Cy4+{&>ndkpu0YOG@1p{y2OnhvHFmTKtpIx@Asuw0Rh$Y*sx1E@2 zcWN85OV)qH5_%g`=4knIQ}$^8=jiu8>7v_z{DRkex#=b!IhRVG)~3do&v zU~E+e9O9`kXiKTm()Ux<-MOQQ^eHd@VSP2$J2wd-Fi$uBSR^nZpM9q|$N(U{L}Zj9ZX zXz(Oi@J;IVv#+7wa48oK+9YqYwk$6#%KJJub$<4O=jPfxPg~od{*hE=3`VU!)We}* zx;LZOIogOm%O`4@$1@6{7hqH9QXXb@Isp_7F zKv|T2L*e0M@|li_?Nb=`NzxtSUN#hE=zE?g0}fVaQ##Whb!A+!vND}}oqcP@vF6rz zx_{B=?_paBx{2ckb?56(mOGBmeA$q3q%+{4-@`hi+sKw=-cdLN8ig(ZI1`~!i~It4 zgRCb^KJ3h##2Y+-DKkx!)>32RGbt{uhuYrBK1eS#Ny}%wW@z{%@5*ruQRZZOR= zTecJc8!6NTb^l_`7A$Zkarkhwgz$K7IlZacabGdG_*sw=Av}Sel+2 z9eCQ?D!ExM$zn_+g_u=}q+lk~(N#`uV0`bz4FmeXL=7OAH$-q};2zLqj3o-A2`ZDb z3EP-EW7t%%=Y5cJc zJ5d=1Njt!r(db3nL4b2Vs|>8z*4md$fJXJtdC~_S(AemIwo%+}ZEeW9I)%%{?H`fL zx|FW?C|oUOn@>e+qLsocwHRr|EAPI&F3wA92eo4D2?Qwwub{n;S#`qgwGxh{$lubt zYI;RQuf8|eF7HfYT)>z6u3s!|{XC{EHAYH)tUJL9{FtO(`h567K(Z$sW0!z`-|MLtaR6D~mTNHr z0T#T1gb7+KuR^*zf2@1IT?@QZ0xin>E*!%Qz7q!{{PZZWL48k!zFYM6oE_pTH7Ktv zWBAJa-@$OQ0rP+Ry#g}mAU&1!Lc|;a%)xMI96jr`6`%;L!Gj2gcF|VzrmvLyBBEnr zyfrc+hV1#V?z20Zoiat9=m&p>%n|yr?%>paIu_J&CBQorFn~pi;pYi{Z%~z!NEr-d zpsK>`kRE@phRWv+^|g_#6GP|jxnxItdT^{T37fDtX6$$qY2QH=wRcZN^~fUp8}EZ- z&R6zVy|uDxetaKSd19@(vNnu%DH?Yv>$rOSU9-Tw9oXRQ=yw%*K6S;rZ|)CzW|dO> z>2nD>*k&gFqpyu2g;}^I-|Hsn@moW8_Ndak7yKwK);6;6?KEtl#y|1+^9%mTNm|33b?@b~tuYeBgers^)F5n)h=MRtJaHGh_@qLZ8)s>MJ z-xP0S?|`MZ-1$n@^4-noIIVLB52&@D0VA+U8eAAa(rg`h{S~?WSiM8tP?J7c?#hIoSAo$zX&wjNE}Bv1J{?4#sJGf& zm3j0#7d%7vyr*tOW(HiXRV}u-F+ypN^DX(4@?_rQLv`j0w`k8Zp>63yJX|YDJP;&? zbvLuzQa*h&cpMgjN*QcxS`m73LG^T5)XTo7(>-{#A&vXzbHBzj?9R?wD19RX?d$&M zp`Fo&ek3|G-$^)zwOzUGyeh-TKKUSg)hr~QVpb({e>K}KgP~{PVyW(5B=Bv&$hkS? zCoi+=cCQx1*0&80JGW_6{|T<--LTtE-T=@vuGUjc5%op(_i4}XAHi;EP9Uc;p{+AX zAAPj&>2!Ow9UrEpfheKZ+##IJqR#Wk1M?Gal3gCUt?rFOwwmy)K_b}ansbZA1u^5^ zS+rm`CkH+41b8jVJPWM9wih>h;7MNOiJi4S9V|DLM##Q6a^IWA2vd7rB3AY1!^Wou z11@)jzUh&sh1>b2^wmz32E zspIi(J{eh2ttJQa%>v3S^L>)NJQj8~X+ai+Do;4t@5guN9pVFt!n;6P7lm@;qXxGafkw-VZ=y!4)!0ciww(q=}aya|KtxwOCjaO%JZODGRv^Gzvm_>?r zR9o-$_>3}r`&_%5N%6;WdCO9@Qnjx~@%Xp^4|9wd|e@ z*^$|oCHeUfGYa@Tw08^60viiR9g>7TWy4Vd9!j7y?6j-`#ADAxubq$s(C{CGv!Dmo z!ZR9L8aD;BQNwhk-6aIcvBl6Q#A%ycfj+y#J zoP87wZd6P;QG&<1bO?CT$Nt6C`p>Gf1P>IW1MF4eIKinuMYb(J1H5NMXv)_m~ zQN<>}X(Ig-?9+1Cn-g;juxjQQGRN}&fNUbZsgsJD+UI^d+w@6i0Rr&9$-$ZM5bezJ&_L ze>J&>+uA{}Z`*y%IyOQ!donwrNI&>suZy3g{-afu?y-auIb(y(`eeDZFt1UB`Dn&$ z`B?o;?1;L2v(xI5@f|o0@_uHj%}r8#HjGhK@%(c5`$lCsy{|$Qlysu}XehwBT~q2H zwm-$4{VJzB?mYZ;6fx$I2_fY4jMqi_3EGnP(q_+jSSAS~Yh7&vki|~QgFJi~kN;N}hqo)c?M9sxww&13W zRarLc^BFYBPoa?1rPUN&nWQuHbJsuI8c_0Rc5;kM&};9Fy%7-SJ*IpuD%nIN7XMn@ zE2?O7N8Z_t#21!t0WJsi5GV+Yyym&GSNOXMTlIA^M#wSLeSCLLNGoaM74E3@AdR!0 zIrNfrk0xR)4`1sJJ;U>gG4uoU_2ZS}EyjJ%OzR?EQNGbkUnE)gEM@9DSt#`_jmoGf zl0s4zT}Rs2H(c8~x;s}XP#>lW5X71ii*0IH9GGI8fXsMr9 z>pQzDA(7B!@U7_Ndi<+V9Y^cGKG3(dnk;DZ> z8hh6S@)5Y3{LkwVQ@WnhUcRBtZ6zb6*M)rlBdAK~y9G}T;8Nq^?sn;kC!fPW2;T`- z*~QgGN&gY(8fgUSD^iUYkB5%{~ z-Z3Ms+aB7ad&yr@4K#dk>@%SgT68~ITWjzOQGy~rVZ#+sy42!;tVo@6p?pd$t&qUMHf-GIWp&~(Rq6jC3D8l84 zb@r31hMMf}FR4d3S(OJ%UTunEG+W|x(oXg&BU2?X6~vxf2@|#cbN8NJ0}+2Cn04H% zd}D4`48o^X_7T=|$2#XY_AgKDkNvR;dGE_F=3H*d&-DCoIWa=E!ZDuOm!cpZeLrdX z()0JV>B(zo|GT)x^aX>ko;U&a?mz31fcyM+I6Ru(zaKgWQ(|%4g->7u3)m$NdxB&8 zIiih%T8?4nK4KFhEYN0B#2BO{JtM1y|_~2$P0MRlJ_|L z){$fWV5xgY%l^v`jt!sW(R~vwmQLp-hEbCjucZP-nK;kONwaB=S)zku#Mc{E?AIf*!kf1_9NHZ%p$~HGxD(t$@&@9yI5~`TZ;OoiFy|xqGj7U zZltYMUcZq{yB!5zD{DS8Fu=P;JzE&9P8|h!9g&f9hZ;E=F@6s^yZcKobiQApq+xu! zR}C=3801gV{B3Y;{O3rxAUwnfPKb&o7?jN*dIOrw_rYTduD())-Io(?YBwJr5tK`^SxIxj?akD(% zFawjyUcLoS(PKteI=?@0^VQrJRp=}Ax+1$HMde;P+Cc|7fSv_g#Z_(>J*$=3`}X49 zuETY%i_V6f>jX^|hsv{UQoO@k97_kJUgwQTor&L<{^;RAN~^Q^LFYo7h^r1x%eU;y zEc(mliUQA^$=zM6ySe?Hk4jz3G3}gkA`@znF;sdsMSD)Z+Z0ON;7F@den3wfF3yU)9O-+Rw}=e>3RIBOZj6vNHyQ zD@{k#m!~w@iVs%rMI*12a=Jd1T6efflzS8_r}{|W`IE*q-Y2?6ahDWKsfP#%M$tjQ(HUX2$6}4ZQAYDhTaB ze#s}a=}}g$a!VS^83Y01a4x@I@k1RFRq#Q zD>x$GLIUo2>2krS+0D^d%9lexXCMsN2wuLVvoC3*2Cy+9;ymhk+K#IlGK5NaZv%LZ zaU>PFQ^oiU7~;5{e^W>7E#Ssi6y5bNa_O9RpgHw=`W^j#`kU9hQGz|^&zExM3m=>F zeEmMzq&zVUmZIv|Mq^L8XQ-0Gln$i@oLy-@ANtA6FJo!$de5og*Ni9T-YTINjm1{y zY-^V{sWZfGY*>?9yHF%g0`#P)lJ&v(6L*9{*_WZHfrXvvJibWeJt6TW%5lBma{ToX zh5EsDRcys3HX0_%ft5f$R|jtmlg7knepe|vW((xW70Rszq%8t_MS+^TtVNBe%C zW^QYNv4phTrLXokrH382ZW)Wc9cHO7Tr#rD;~y;2A)OBTWP-fN1#cQyw1p}6&|x`J zkjAZDRbCfO7^pV|CZQx1xHvcl2Qy^QT@x*dF=Rpo-n_1Hq~cZUvxy~7eFMjVi0s|s z`@9}{;^9xHGJ@qtmRfTMz;`6kVCtT_x7>D5hP1J-8|%gIpCCNd!xUZC=h$J(rfxPy zS}S8|4nr`frlb#M#`afZaB>)C41!)QzN<|GD~O+Q%k2}LL{WIYn>4-j4o-ni6PJtY8gBbK zT>Rxz9Zb=8QN!x^s>~toy|AjD42`?eatf)Rv?0`_b%YDyyv>d#WMC(LI8|FJ79i6y z(WQymdh;5oXEi*&WjhYvp3|H+VYn&CVA{3aB&+bf*R@`O)p!mcQf~CL;dNySzp|d|gKE>YVkKPyM-nb%17;XnSRNBaew={~-hHQVIt!+)K*98)^dMrT4X>LVsG)-1&Eo#&;I;kdh2;n%EHzDlH= zW3_R81Y+}2jYXKGMUYet%GH)7YOZPZwEOlvpuC}n5pH7&mM#voZvWDkEl1BdHFCfBL`dk z1e(8Z=?3*=qQvZR8*AU%#aj=O$RHH@NhUqJuL~|cjhE4+F)OfJQs&0gDiCO7YpY3_ z(6Jx}l>+}`AgQJ9HKqu&v$9i=K=kxJw5y*@KK|RcU!R*RgxE3eVY?g4Y%x@6n`D|= zWlUb|r|Rw}Z3asXaiaXcyGj)pi#oWTcFB^qtKX@+)Mb?*l|vxoFzs3Jci&zA)V7N7 z{w{m4v@)^dw)f{!E8)0~l9bo?A6y_8xRBf}%MaapZFw_gp}+L-dDE;c&STKCRDv}B zJF5+{X#brcxEKXp6yR9`UpDV!C}W;iAvC^5Nu$J4Hm-9Fc-%-{#!Bf<3Rrf|QRV}n&IXw?_f`X6+;#rN^X~dZ+b(KNMDbkZTRW|AisJ!7P%Fih#YzVezrAq z?Ej&z`?q!EfA;UbZ_NxEr$C$yX3v^~VC1C|mI3zNrs%mQ2?hPjr+5!pT0GMX;8qC* z`_eOkW5}6Gmf5$%kdy`3>gGaJ-bX1q1z3bSLC)zvEx3g9F=q)YhbwtmxxQvAKrIqd zL{tBte~EY;acJQY26^X6C$8V1 zghy*a)acMYwbSz{!W{*B$4#W=1}LSK;Q$vm#71ICvq$}w#C zOm1cBUt5 z=q6^d)Po7-e(lKokJd} zhq~gmE(P13u*y0uVXjS*K)Br7W}P+=*Sci=`n`f}d5VXT$90=C#l6kOsY8)x|2q3! zI_pxw12;|_6 zM!08Jg9bk7)5wVRA7L-$8bEW6*IzNMR+siTUX^(gU#B7=yu*v-@dHiM0r`mYlDJYH z|AqHo3aU(ycd+rR!jqi^J-M9^n3Y{aQIi$vdQF&mYkP6i;|re+55}v#E?N?J6Y>sR zjvK*ZKY{YebsoQ9;e>gB+r4z1`jtFZ#>6G8Mk!a=psGrk#_>MZ<6XYwnTx5K6vb!+ zyUdyma++UwZ3?PU%UOMghAd9>E?8W&xDfVstWl|0MZ1CPP^=_BXs*6x>#h)5YBX>Q%edT0%eZ8Z8<@gOb9wbs`{OPMhdIGZqQ4Svq`4lf+3AuLD_96i~s5L4%8o5b{*l90Fs9`KijeQw8 z?KGvdDIU`Ya2r#?Q8PWB>}R*8fHRGmMgwyVNtV~DQF3uYDj%G_UQiEz+4IOgDWH-{ z8;|7iOU*i8WWNNFJ{r6CAR^m`)j7KWJK{^@$4hfFT95xI4Q*F7=!6~Nn6p$m13^l6 zOp|{c$9dM=wVS8(nFNcfhcOczoN3*;u~V=sEUl-mOPJhax*J+4@KK zEX<)>^3n&&B^Y_;14UMoCDJLFD-Xkl&Mn1`+=vS(F+CTb*Q>@6<%=ww?UD30Js+-} zpLeKDyFe87Sb4-$=}d~{#QXnzEfi$kB40z*#AqKP3>U$U2^03u0t2D{u2J-=bxKzU6v?UW{?eh=zXG#lIfwR zS(=HW+i|r*;){;>$2;NS!yKkPNZBzCC>m28FnC;$C%N2u@V0bCv)!; zBtsX|?YX9}7_Ga_feN)&f1j+ZC15w+K@lapF?-nI+}8-P1++L4gHEK>B0;dJ zdlg=6sEnfzj{b?TT4V=2o(+VH2d1-k{oPCRaL?u~ShX4cFBY)AmB9-P90Kmibz75#HbV=(hdPCN9&uVQ8~Q{cXmsJ;=%(dlm8h z=@u8xJ_cs3Of&h{MedZ7gqhT9(^3ufpZ)Ytn{YFD2ot`@b>!RH-Cc9o^+5*2)oZ%@ zp81uON23p|>Z1boq|6Z1;iLy^io||Pxyvdcm@kSl|;M`Y2 z_ZI-vtVfq!)LJ!Ushw|=L#Xij;`U}|eFZboZ>OQpTlL{q3-_L9QsR`n3Y1e+m+=nXW#Wmm&y$W)C=f|P56~R>)VwEKkG(qbBh{a zt!ITeClWr*+YAXtB)bOeYVbabRWI#?I(Y>4v2C82?_Mt0TK%vXXj|+jEoLQS=sR`M zW;m?-#Y^b??J1%uv{C%M@lqaeKif!;(L;a(!DY2c7#z9n-F=6E=UV>;g;&md77Zi5G{4S+48?ZmFItvxd>v zjfLTV8vrFRw4-Z} z>xKN+66Oj@mq?;g79lxeDaDt+Eqqz;nKbiYH*aiw|CjERe;Jy`+9LdQjsUh9Qh)=Z zn~!$Fcl|fOh7yzn@)RjEb8s>IuTc2z!;n7oA+i<|IXl|XtHs81Q!9BY4j!}`+B-p^ zS$4G_dZLEW9jzx?hC%2If~=WIMkICp5CFS~yT2^#M27hZtO}crkTSDfI~kMC&w8#j z)Hd^|w8|9L0^u1D>u(zxS1=<$xi-!tnXNTu{ zJeL~1pO@~;Ic-s!cc&twNPfcfz2@k5))@W}wCznOG{Ecv z?6vJTUjcJ*r;)8y1o{N;9df_kW$B%wC=D4O3rfuh7VMCpTE$}xYSTN!>m_Z&8D=U( zKFHVtq+vA41PagZq( z%_MMyNnco6aka#spg~6VNW91h&~O&#)+NDy0v^yb%Grsskw82f+!OW0*<%--ex zGJ$En4D>J=YvLILy#+%LKoVjL zNCTji;f&1_Z?MEUqPpyj&%QmYuGCi1h;P^X2{W1OKTs98S;x=#19UG*2_lbwrC~_# zGD8q`g}D}gwA?vjH+_6a4^@bB?VQk< zt4n7Hs&u#~rIhb*hQUm5WKXc1fh~nI<>Ij*s{ zAR>{J}C2e+7T<`EQS#iLrXvS2)dK zh2b^(zH{=UsJa`fTO&sWjE~>3XINzq_MN%0w?K5lxvCDjuX!4%kYNHwg{NBVdMr{E zFMUG!-%0LQX*8KQAM+^LL^I&!C%+UX+bQPL#gE;5DRVfM>d{eyJqrtMN%r|RX-cAu)Dyqm zm1#^c$(U2lsyQNhUtPJb_|whgA|<&H6vou*S$4&D>6DdBQgVF{lMqej>)tx=i!+F> z=B%~uRW5`J8Z4j0J1bjwWK?e5^g5z>3Ws%{6)GDn&^hcA=4^p!GhGGJ4~Okr{}$ds zh5fHcN;XFSSL7+?U(g$;-&0m>(g{m)!XV_#iDwZRMPoaAyYVWk=6B-z4MmrTd;iWNRX@<0!2WygE>may|%q7xrS zl&2JO>z`E@<4503_yc^X3kJTqAP75o%~KxjoZ(4hxle7t5J<6z4kohTL%U!9-abr` zM(Fe2(`s*mwY*gWZv#3KvsF4K2-I>X18zCx9!+o>N4dsat&AsN1h3)`eFn2++9#zg z-K%=P_!Z;C+4rDOkZj!@qNsxk-xA?A>KRDu~YTcO@QB?>k_?J=E{q|EcPD{8cjIXvoCza66p~$wU>)uY?v8lincX4=Far zN?qr(^8X6b@LiX4^G)s#fx*o+NtFFF5Ny|%3Jc{J7voi+!3x}H8%&7eNel})z>-o6O!T~Zoi>T}W%NbPSPAAeS{9sOHLoH=vKvG8$A{!Lq z=4VcMgb1?4+d|CDxGyN!atqXvQ8{baRZ#BCQ2`7{r?aln5LOQjb~)@ot?*3>pQo(qH$HH%tfjopP#y`2 zzF9||S(j*>8Z4W9;OC*;%uo-__DHy%c=qnc@5#-LE29Cy%VuZN%~wS$=IPrJhy!3B z_p6>Aqwk5Mal9WYu~nFPo+Z?tjbe8V%X4!#dj$HTa&*y=u-cq#R(dCKzaKJ^c(Rgg z$Q+!pt(rAApi3?i1@TIScr|i$bt@CmI75qc;Ty%Do^={SUPs;))SZ=2+`Y)*>;Kyx zcelp$*|bDvQJW5`(=&F;?%F7JjqFqCu4#+-nM@hk^qev_9R@B4NUo`MXax@Hfh)dd zEXv(d>i(>5CksDU9Q!P6;a0JnqDheCqfm>(C66CvO%;|DXbfBCo!sckFo+ZJGjAEG z9DMJE?F5bE|0EFLN98#FFpw+rxD@2d6>cmTq$i<4tAFRcQd)E*+2XLWMC-h65jOJvgQ zxUXt^QPd{+sxO|rGekDJKmFIwIjTO?8!jO$H=JMS`Q1NKq zdGbJh^|<8~$CH_s%KVl0DUh(zO?ZX>3G!!7(s|7+WmP-+b{9ba9|TZ{075CpWLR%s zp2xPPtSFQ!L+gyK+zTbPw;oR_qyTboyp<2=XzxsMG=~tXy-X{{NXtEhAQeC;v4O65 z<9;$Km-n7ng>B#Mi<{PB!Q(@;aTmJ?;=i!2p#=~M__O`KFcG{3AhA6qhQKh#C+d;{*s5nW!C^CP(6X#qg?{FmGfYNxn z%*U@;J&eNAG7nc{XmD2^m+nF|Btfx$!SY430b59kC7wK1n&1I)XGCm?lMUgLRXcTu zhlwqLeg*Ek+5>Fwua$BujkgXi*3QfwdSAHiGp?$UmV@zY*)(%WpYEnVATg!bTP$?c znlDZ8{Vs~52c1I5;x?Rhsj?TjCAGl@vp+-Q@OSUAwJii2D03z;-f@acd+L`MuQU^j zJ&bdqb_CuFe+BSwFF3}jV<+GTi+p2egDIe6v}_w)9^dMyLnoF+hfxH>>n~I zjJpVR;w$eNLK6u1v-3D~hmLrJ@iQLWy&f%UQFAcwJoI!|(~~_o1}ff^_y>(&N~48W zRQ0qDPr#{Yv1rREW%BTs|x)wy9>n;)>GO$Wlh@?I2&rs&3dpk6}a4PdjZl#Grx^P&7G)Q-g(e7XCdc) zr0YWEl?b<3>OlPAM~T&m7jAeD7r*~tmZPR2`d4;=gpqd(UGcqqL7V1uec<}y-~%Gj zhv^C$B8(}LP&!pIxByJ?K%?}YHcLzSgm0n||N6jb6_H`4P7RXqh7W9+kSCThvLyLd3qAeXOOOzc^AuKtAvSE#pUsCW->CJz|vY%!+t9%MGM; z$^Og~5A0EyUxD3%)L5@#1^(9r&wonnGWu`Ho_4xHX)ZFnL zhmq7)9dA zi|&G|gblS`aK6%vB8ULTPM!1QG~#x`sSKdC%9#e-)+@ zQuh$#@cnw=Up!z`1&-`5fb~5Miq-&GYrG05xuW&)R!Y3`|8D4+kBi#h_WS{GpfMB` zgtz>G@f#S`V61=Ni{yrJ!0%jqFx^=dUk2Pb=y2d$`smMl(UVAfOWUwkWQphNUgpLG}ePQe-ivpGk!#_Up83%@`^h`rY_s0*D zea@i0hPN(Rj(j>)wsI%KJl84uiNT72^A&$h=wH~fr|7m$gA-c9S69q$Kbc7=Dw@Q~C#Um?J2hps9iFfJ9;~tU` zMAtVHm<(7{<%9BCDdM1>a=U8{Lf!#!BlYX0R-X*ITX@g8OSaB&z20Lntm4Df1!jEd z{T;Up3l2Ycl%k~Fr9PQyX7wGu82iW9aY_VEntH%7?82IKALeW0)LA24fv2%;-OLU}xoY*Dz0tT%HbRMomm;S>C3uI8iI8>QX+j9S2QSJ z=hP-5$pC2b4jf z*K+2sjamWlr}Z=5B!6iqxU;i>7h8%MRPF32#1MBCAsQIdYIrblGu_%KVj(~MP%rKc z$Pr~m#&bmdz{-tX8DxD$!j^0^xSy&@afBH{6yYu{nl*)o53cH6Qe_v+HYY|so(M@L zlwuGPS4mPWDC%|wpgvb;Y0@4@5#A||8B|j~-g&DM?+kVcV232h&G+dIV0(EoP_d*$ zi6z^~=lDH^F@gd|rqS+R0|?SbuqG$*HfuKNiD#=4+4;mE@gCseGq8IoS|;^VGF z1a`ZkF+GiI4-Q-V9JbDbzcG*sFL`}f-P6Z4rDUl5&|o-hzVh`0=dHfu z-#D`eocN;K?q(_32}Zvuxk6qX?!9o5q5Q={JuOoa4`XpgI6c08VBau)k`agJt|B(YjQ6$V1(U=bKF&dBGoGy4_8-!MG zp+z`0tb5sNd0TDYh)0H6UIBHd1^q=F%+?CP;oZ$U3Olw>@C}ih}JW3nF8; zF28cOb&-K=N?q`mm^b}*Lc4vL_KUnjOqYz?V(e`#yVw)PcQG2B9h$3b{Bi1w2d6$R zTXx%I>&XfTZDsTq4!JpP{}HAXWK3}53oy+ecr=%I_R`Hpdc>)+Vk5oiOP_95wNiBb zg3gfF%fT#Kl=u^&ZG}>^RypgXUqr9g*w^d5#C zgS5INk5rS=3T|)oc=}+yy?jJlc*$iL$#~LNkmII9Do7*IxsRyV`~5NnWGuVZMqsmA z>=Qs4gy0><30r`*>*+3*zJDphC0oC+o-09UlL^_!0fY?U9z2Rt6WJbr0(r2T83{^V z3)GRKc%8;OpwRDFbNX3wee^tnKs1fm=A_4*L->H0=892 zS&P|HpH#xRZq`p-eO0hGto zeW%mAT0wk+Hw9z2qeYytz@)m|G``wVF=$a~Cl+|;nF}CdB=V#Nl^ucv=*IdZ`PstsonEFuDUqDoP+*?cN0le21i6=w`Ff z9-Jg^osDOTQ+KC~?FNMsNZH7a_Y6Uwj%h^W=dySd_$}*sW#Mq8D>FwoGogsTzs4y?BmOg4aHfka|JQn4eB>D{-_&$f{tbLI)46f)|H<6&gFjY6m?Sk z@Tzaf9{_UsZZh^C=YuEzF^waYWyYK0Jo`Ju`XdpTiiXZ6a>P+=KX7ATg{(}NYo&Sb zSw!|L)=08g&5Y2K9nH5*3S%5C?|`79#3G6kyXPu7vY}vFRz{sLBm^+`u@#6^!Qk6!WPQG;ty-7y>hAD685{aY zXKZ4!Mk^)Q^`e7jc~Ie!j@}8!v8l`SsBt)BD8v;^S+N{o>;Wm$ZVWI#(V|>*(2*{O zS$0vNmm3+`5%M;Nw!D=42O7=W@x2ntMaIyfFgWztAQMA z_4I-3%M3cD3d0w46=V@Ilp57G_oEqsh>mH?-q!Ff$ZlC3RspG#120{qjQP`Ud1>tkghN^pdC7CBRgj1MM;A;O z_4bl&M^kmYA_H@!NKSk3ak$Rm+_Rdpxw;}=_6<5k_39Sj7NQ^JPm*<{hG=~4UfN(xl522tU={=C!Ou4uLH<$CvtZoN43n3yDMynplE?cJUL5SdFL3%FXDCfL;x+F3JR6^T? zoOJS}NpWELo~?2YPLU+v(?1m9D4j zl_*)(-j%rGclvNrk(|n)pQ~hRZ>Nn*|JBtz1s&^oc+3s+ovn!_?#gO#?OU+>n#uTa zK7#$1@8kcC@8j=!-OAS-tzCrsv^M0Ok9!6`v2=`Mz*5hF6ZUhoNAQCC93YxGQ$7-O z$Kgt?0Eb(SN4@b7*BoI_2o^>7+O#n5hT`fOMpdA(+AjQ487P!VsHCrFkEdGK94mQM zbMcs{^^qi#nLU@zF!|(LknPCYz7FC6XPok^F+?gi^JGr%4B9deMwUR`H-_~zW>&GL zE#wirez`kBs%aQ)SWRK}MPp%|fu~fH?b{fNBfO)?u2y7N4L4Y7n*Z7o(M4mT)=!!jJ5NBm zHjw|Ukg&VXS}eTIKdzme;Wj3LpEL9xH}ulh!-m8G{&)Dho|L0a7|7eieJNj7zZ z9MPG0{|uHvIA z4mEDvt{=VqOi3-5)RcYNq2j*!_b*9Z)3neKJ*e~@dIGdJ;FyyYrLR0>WpRb0`3-f| zbwhd=8Q6ot&8QkrmK(MVGcC-jk5?BIuyQ{GmNq>n5M1}Tx?$){gDfG4QR-}Zr*Q~x zf_-z-7*n*!>I*6}_EUc>X&zpeUNC;9=C7;En8L-h$AOQD_sf2%c}uFRsk^#56QjPG z<1-68;RZj>KuO=j`#86CgNasDyqvp;I(Nqg?urgXM)uSN90)TAJwX*^_=I`Lk{6;}2Q%2RIAy z$qY3uQl)1>@W|At&#K~WWdG3;t?cVxht1C?X-wA(Ep z#3%Dkxph&--Iros>33d6XdRhd)lb!{7Gjs4A59UfBNr$mXghSAMXZpv)8E{4hE@;9S{} z0|+=?4*chL%!FN+GlqPT#W!=0G(+3ZISu@I`RVlSmC@PKa;J8{)uK9u4s&!Mn_1#P zXWkI+M8w)ow|T$sOPXl|ODICmt!zo`*yMN!lcUAjjto)3>oUbVVTa89yy@_0gaETX zkqq|^@|27J!joYoM7lCmPk1&f`KV?OHQmd*zb#fxyfAHxLw_|u7eJ$(At&AJ^&-`n z)W`;q7tnUS-055|iyV~okcX5&ACGCjg`roI<;5f4YU9-(Haa^7q!iyvE$Q$0dZRN) z`Mb;E!UNyF{eCN9zSK&_j{lPSYm1Ynez#5*$j@mmTUe1tYb<0pB2|axK0QvkVMsv8 z{_&qz+=8A5a-0M9+ytZ>5O}3?M0e@%Rm`q_$TWfvG6Z|sC|Ios@nnb`=OFqgsHaO- zpG(C5G8=1d=K{r-O*-Ha{d$JEOF4U3TZMV$I43IYD8~XXSV>#VK@6(LU!XZL<+B-m zC!rv&%<{Kxb|aKL;{=ykgdJ^F<@KQthn&MJGz_##D8y_95HS-ku{ava*B3 zba9JvQ*=YBqHSEBq6JAO_4l&jbxA+>9-C1jYVN@ZM-2J~hRjLm%D^%`F_PknLkRN* zCbU)s%3T1hzZUz-gCcvDfI;uUWh@&FtgMqw03YAS)KLk2We@6V)5H)m&QhI@301Ro zq#=2xW+uaEz;DbqaY>kc3xel{d_vbGzDs=O7?&uYg;*LPwpJs&U5c z6Zc|+D(WKBlFge(*UQ!X%CAf_Oezm>xjoZ;-jBbb6uxY~s|4Jka(x?eV!fCTr_Qa6DwP0NV$S75`cNKVQg%QYezj+s~?Kq00#I zP7uW0b55}2E9r@X55R#kEV4uAL3~g^?m|%B-RA@nNztoJ%jm=Gs)X&4pNd50x$p?QlIlpj;+C3DE%9q0E**uw9#rQ2%Sz zc9frIi!ehx3~}lvE<_M(SvR1L4R>TL%bpo3tF>-uwy8}bEFE2E^PqU<3OJQQcI~Zt z+%m^`Gw5!y3yuvMz$r&tkKhi&B%t~>p2jHN9K-?ctfog5j#zb9EwxJY`#Ni}AF{fs z`2faX?tHUpNOm0IsrG<@xiAupht;9uH3(6GBURX4Rh-zY71NFyv@YyPI8nhtF&zBR zk+!O{J;Xgr1b*@@LJ>!Fm^20JrX8&y$?Ri@ccJ(ftz5dnIvTOFy4FnZA&5;~&TC^N zE(smMo6cAW{rO9ZcX!yyRz&A5Wi#=#`ID#<-(*FKC3B`li|>wotad=S89C^C*tXxs zTrYU?amdZGPUswU#QWifxBdOQj!8M<=ORvAG}Tb*IdFK}wqDO8=&Fge`8S0CgS*-q zLAym9VFG;~@N7TvJ|F~#TXi6}4p|{+mqqB1)q<8;JyaL@5xi1`#{|5C7AvbXM7~ju z@$#R^7N+tfNi~CK*VKD;&HKyLXVpS)VZ3se#3ynJTm#-r1y7>W0ZSRl5!u&GlnsI7 zeCQp-pMD?UXq12}coMJs2IQA<2wuet$(Nb+-9#z81YZJP?$uI;eB>ls-LOOpYygpx||kUvH_>?H{3TS z$__4VUw?VntNQJ(O;wP4fmzR@Tg1e^{pNme>^EnaxGbuuF<)4L@>tey-<7NqHXc9n zYRNlkXzG((9d}yEBtKK$GVauzf_wQSuc<}+FPcCF$3wlKZ52zrDQ~5OR(AUZL$VlGK+7qNmrll+?@JOfw!| zaKB(bY2SwRlKaa_&(Uiu%k@SC&MV1GS2x}?*hPE5N@K2^vrwU(>5Ks@p9M>9xb?OF z(d^(i9uv$UHoT>YMdfft^!Ev<6D=I6cn~P}N%I z>WguY?}%1#u*`$ezIEP9!k$TS-}_$4k+#ZU%@OwBf>o!=v->h?jv?CgX{l!}QhmN$ zdn(mOzQt1Fn2Y8+T8q~O9{D5xTQ|P0Zq&{naX&6z*&yhV%LnZdey@}l9myBXPnn(= zxXQ8UF&!Yo*!5KnWKmKjP{A#< znx=Kax#Op=Dd#G-E0=IeN?s}2xa^VH(}^3pV0v;XCdUaRM4CMh_B@XJP?BPmnINlt zH2n-(8RFkn(;(3ZEuO9l4YipA>b@#W1bpL@sw-{f++pgFmkB7Yv&)4yplouweoF<-9 zX?PVBA1zF7z4CreQBdvO2h`kw^*Cnt_V~|4#yru5?0WjyIbCnGDei~gR+BCHHC3at zEb~hcM*38r)MfR$0Y1ZkJY4?yKP#mqPN#{V{}3)So2N&=c@Gu-I__byvW9}P-n);6k|-*ycw-cmL=mGuse(*aJL zK9$vT##-rLz07yDR%umQu8)U59hmrj@zoDH{QrZdlYRf4db{zzS9s3>-S#OCoEzav z+3dO*!Uemxq|!^k1xAP?&coZgOPw(vko z%YYaUyz6vex7rVWrG?2no;alqC@rqgx{;jIgqbuXSIUnD?b2b?7Li`=OL{|jop3!* zUXnc4)%P75lIxq+_ZcB;y$e)*3dOpfaG1;t5-@8qbj79Fn9SS|CBOS16ZQv6-RjEB zTFjjU>xqRjO7(5daRwPCcrVM!?i_vb?sp@>*(!Am|V%7apA^d$t~u@L~y=)bxXz?P*o0fG*{A5PcLk*uL|AxE3b! z*V5>y-39m2x9b9`q49IFFd14P`FNb z6Ul2d2AQy`gj2YjvTBR^fHd9<8GUh%c&QgimqlJ~)Iy#Z2CHe?`!nUGf7|2Wl)N;j zQ0i4*Ym;}f(+u3@U*@+V&|38qD9E9GR}A^(Z+k+hf7@gJ*oJFLXnTR)NCna_82b)P z9JvKtZ+;vS2khaKA{nsl73AjVUGz{9MGdxK*T7 z6@h3*jtF>pjxZj-0s7%6^@^QgMMN6PXzX$b5s*x zGygvOk%dD>g1hy18^Y$Ca4y=7W485wSB}2=k^NAq&O){kLR+93R<-@ZEhN>6-0If21ig_^IUo~voYo4jvjUF zewvekPN1{sjjWkcCec1AMSPSo{$jc`1od^ zT5KN9(`W#%OP^A>_E``43X>+O7XI43POQl`K6hclrp#8yB{_fb@%hVl3l}rIQjR%{ z**mlemS>lpcbe>!KA)?WEpq9T=|D=x%9n|yoz{)?>D{;0t6YK!xKNfq{Znc54@Ja3 zYkGa~kIrxuZig55cUQwNf4@zJm*c%aB z;IRX;C_tE_uK(w)7CVrwAL%<~W-}-`va>_I+<$+qD2--Tl^}F4H>P+f+bG-Qlb5r? zrAz9WUImX=A5R8PVjqLm#rh{Ek|T1FXEMLC3M5LbT|UC{4ZLP$tI7-kQgq`a_l-~T z`exZjfrum*@=h8Zw|bc1Vmr+uM=-LGg9nq}Pu>Ymlf!jeUTfebTmIly_WhsWG;0k4#$}^f z>M=@0tpqvS@$2y2Ar0-qxSm2M6j`qPZT!c&o$QZh^XeOAmJZ{+``NcD)MlEh*u6n6 zgLtjNn;uR1T`KMeMUU|{G-ijMEcd#7w$-bn(_;8+R7|D%h9Op@#`yc_?5fdxN9&mp zZ21WuYfII1hw({5biP##?7eh-q)`tE{b!T3f4f0`ep%#{c4D&R=;w!Pdvh*INia_A zw7`NJ7tMP$9z0%Yyq%enJn-ISX>rcc;(T=r&5P%Py%Qi5?YA5oz4qY21G$FAh75-a zHH9=2w}u=$j9ePVIr~eJiNycK-n)lGx%Pj<)@qeXQrU%=ifpp05HXoM6(Rd=$RvrG ziAl)FFe_zGmVF7c#Mm?0?@jh0*^Esn%*dY1*oHv z{T}xp4!YvH&TFpo`kvqO`~7Uv^~EE#lI}~zp*~>20tBa@7>-}s>0y+3<=CcO99}g` zD@O1At+8QH=m7D|7a#9qOQv<_JAD<_^>{i^Cn8a3_g@^p_|tIhqJ^i;w4=C+!|K1XuQXk9*&ER|t6{g^Y?)&q@GxOm zUZmHK7T;o#T|+R^Ip%}XF^Ia8DPp`S+n$pQpe^X6^9Z%aU$>nJjna&v`6{vzzw7(j z$ZD@Ik?zAp za+b$4FcxjJXE|ApCu(TK$UrD&%c73{@F`{38#fx+F^2UV2h}){3bs58fpQdJ7O^fZ z*mblN?P*C-kAOpkyiC?1n`&2l@H52!|wH^;cY7c zU;{t{0@dkI(d1}V;tTEL^fSdtJ0UU1DqH%b_(RgMd-3ltq)H%Dm+p9)bySd)7{G$>Bdtu*A*Y2IHC<>>H}tZ zEC#1X4{b${L8kAgQAW#IY83;GQk-7kFPpp~pNp^w6leK-?)`y62$?v~bf-W77^p0S zI_vK*PvpkDajh(45?pc+lfJhx>Lx>~-SPo?W+<)l!R-24e~mkFw-h{_E21~ZT5&gN zk?@OSGf|bpabIrdkA4Y<4$lFU*K*Y5gK8zvuCs-*LlFeI;b)BV1wCg?qw+PfDcth1 zRjSk(v5Rzx{>sezD?X*hD~dp-zd74D&O|*N4ixJ< zd9E9!%y7>d5h5S3b|ZRZ*cY@VUFv-;5b_N%cEeUV?n8gRZhmefP%t@oc1Ueoxsa_U zx={6Yw3)H9`=w|oB;{Ldr)m--CB7$Dv)mnhnOy%Q_=7M6Syp*Yv-fU#hYR60;#kKC zvYkWJ$@L;>zay9KiyiDfq4KlfBY4-^+paxHXY|nGbL>lLGcOPAGgJL+(KZ~lXy(O; z%iOOMm7!CF^enh;TzX&Vwqu>A;RrjI;EJJ&<~Ih^iQ)Yci=U<94iw#N%A8D0x;Q%5!k5PVdeWwAa$nnE zy5z6#;9W1e7>0eN4=f%y+xBTDSzHJxwKtdiS?7dQ-Cx3L11#V@pHh&CPkytPL(Fdu zGKBJ;oCHkbpYZ=pP;CC`H1pwJLwlR`+1eD=^)$sI#hlvLozHHQ?ew#3B|Q&a|20JB zWqX-}rhT)c>w()Yi7S;E?&4pvLT=orH$!htsZ|c{Ve*O=q zzYr>w$I$OvG2sOd%FU&^I^Ft!z&V|0snYi=!8fL(Z+!{>$x0u3(7osjVdH?8ix=9@ z{p1HH!-L2b^h}>3q0-0>(%AnPa{+gp$^UPU0KhAd%PD5 z?4+46e5=32zw1a&P2VTK!v*U1Q>67>Ac*D+$fXL(S+WO@ZecY9dyz*VlPw&f7fZJ5V{A z`;m@({Fs!-fYEHwpDY(OLlV)n7OGtRKxRqO>PkxQ@>h2QB#)MTK(gr|&W)V2XuP-Z z&7u}hL7sx`X}Drt@4-&aQ|1|}@*$cD&sCoEuNv%$N_D;js;p(xa5Fht)O&4Fb>={! zj(eB+R{V>k{%^S8bj7J9D)i>`oIxQ%*0V||PfqUQP2+8)_K+rqdogo^T?t)GqNS=)%*9{ zyUN@dbKJ%9>q(QGgIg@&mKb>oEF3Nl1@wn|_JtcujF_>*4;V|S95oUe>_XCa6U&$E zH~Kij%uM4H#)q*U>6eqG`yxGk4*3dgm5lc0FW|&SOe6YNA1u$lG9#Gx=}9)7|4Znm zk}Q2a*h}d!Rr^G@hO1^%uSPeLa>}|`@_=Ka$R&%s-eosks~p0`L`}1V*)g-o_>%3x z3uA+4TllTZT}6fSWXS|;g1#=#t6ZIf7O81?g;OHN#{E$)(y`lHU|h%O9wii52##ja z^rn}AVBcbyu`ki~WqAJj0jf0(A6;VT^AL_1(r9~j%h}z<5hV#hF=kev_p=-Ua$>w?2!E!$*HR4HX6iiXr!4R zBZ$ROaDq#kIpJ7tpUh9X>1RsF$0p>zbFJ{|nG7l_JpkQ;pZlM^seXSr|DD9|=YDaW z1OWQWY(pQJu&%RqNTB-_YVu0ME;CKe(l~X+od2u0-ylVp*2d~b;Ic_q#zq5-C8jG3 zWAB^yJTmx{cvcN@7gsj-v`tT$QB2TK+Z*=cUdDA2!r%>YMUFfd`8xMa1p6Il;a?OG zvDyiq>t~Q1E z8X&=8&mGl&&@VX}Y>@%Vh_)7|Cs1Yx>pX=&;DFS89F3qmH#y4o-=p=m^*-H7`AGC~ zCb*TcuhKGG0;bZutOr$B_*y1~v68gjR!7-7^nnG@-RKS6AsC7{`kjlKrAf$sMd!Op z%ZX+yt3HMt(LRQ~L3#_i-XK%ZB%iKx!ygG0$AqkrTeT%>y}hdkHiavzX{!;cd4=rj z6kc?zgvj7V!~HFqvcQE@Z?Bj4V>8UNd5#8W=#5)ECx}|*uIpOnS7-k|BP>FGRGRY0 ztnPV?3Aed+Z`^{uew})O@mTS03_*UqY8n!RL*n-Ze!|oGAwO#$W-Bs#DYzqnJ=zea za*-QHjk%hKJw9B{i)+CPVByn=gGIrlrpXAQl!@eA#DsLv(0=^Q>dLzE>UB_wk-=5K zC@3u84!wz=g0K~^THm?4MgQawdy-GH+RqqnW94j~nrED5YyCArrfbtX$KVYjw|rWy zD8ANRyW@UUWnOlXKl@uPV`&~62^4nYIS0YJpsP#+B{cprOHet}1(h=ywtx*M3O)tl z$lT8ZenZjl+qmXoCMeikmw5+c+A@8=b0w+%@sJv^B6RoQLsB3piuO#PRs~5UWp+G> z@#}EEHD$Jv2~(E1dz|zR#y0K&`A&vt4ATKmGlTD($Nh?9rlkP9w~kr>Xj<~vQ+#&? z@L-joI8!D$w*7x{XvGQqcB=B^tM6RvzA3Ec$;o7n{&%kT+r)a{{{6>$(EF3?H`ao6 zsEAg_i7@#UM=PPNe&TH5Oj|@SSVBt|x$kUhM|$c)-@ej!gU$K1Wr5 zTU=ib&(JBLS{R5AC__wx4x`ob(PL?PO%Y0>~xo=Q`L8*TIV|+cq z=*z89`}<_;@yX<5P=|ZJ&5?OeqSe5r-jFu#0!^ClTy!lp6V7uajPeNBgX8AgI6r}) z@feBU-9NNB!d65FPi~lF!OaE7gyOcH_5N_=7VAGh5c>H4`U3*|gg<*uHY0FaMWZmT zU!TUu0k{cJcZ`Po;fTaAWJ9ibcN_nmOWluHueiI7`-*4Bq_BEpaeuspLEN7{p(P^I zZjy&IpY>yw@IXN6XRSTvPY$8@b_6Ja?B4HOuj@x9gg9){nh8g*Ct&WqBKNIii+{j=Y! zhW~P=Cr_3qlGp;zrgK6XMdz*IB{|Exzr%h7Kr!6t`t-f^ZkGFEN-`rF--L?;IXdBl9(NuK6{Fkd6zVV60ekA=`oS+zEG4)Q9J~Id$RDcBbmpx+dLiA1~x4xb$`vP9)X&mU`-I`7CdIQP~B(%BfF~a7d+I2x%$~a&UwA3|2~N zMn^R6jilzZZ$H2~QzysrSrpw0Uw`!&^w+^6Y1#uw*+}b+Wy;%HMKt;`)Z`I*+U%iw zVYeU8?g3o$9JuWlxc)FKSnpcbz#@GFwf5}4q`-$g!p8!eh`nWm^4p z80GGIx%DpJc&KIlef9YLUlAubT|(XE0TJh73;mx?^hk1B#eAthlQS`n5VbC$Dr!#Z z(TzBfIi17>Au;cBr@BYa=4go3$)D1vUwqzzk;zdb_{AsmUuJseKKjk| z@df(NRb0tODkkl{cO~e`!=Jx#OC1vld*JoH1LB{JowRC1zdq#kd23T+tsh*!YjdS$`&Ym*Q-C-2q7BO0{UKmiO3xvWa`L{2kb{q0{ ziIMbuucwNvJK8kw7g1n5$R|Lk^Uh$st5Yr$ccZ?9{+NKucH2>`0Eyv>hr7$OR^iGW z^-w4*tI0RN!`Jihs|+{B(f5_LE(xZF`HRQAf{cxa%sTHr;nMp08<$AWL*L1<(z$y| z%Z5|(XVbFMo^8FjG#=e5aRcfM`nmD6O5WVsl1w9m&f3&iR%OK04)KG`gZ5pZRAWMEVIh^-sKM<^M+55Ko{cGgzW^6CNn z`=C#?I?t~ko-d*vdYk1oAaBq$&@ErpwzH2&SHD))P)_3%s=icKa!xVSDlf0Ein|`x zK60aM3@L|J$w?H5_K5BDj@4DNEh>6TG>9~#U#xML8(Jw}LbYRBuNtL%GrS*V_pVH< z;)DJLX!4tHH!`pL1dSQ``zHB0#m(YUdYH%RRUSBMiKMzPKUY@xJJG7K*Ydh~f}iI> zFN;!+_~2ziEBTZ7F+)tEZdy zbqd`jtLHJrrtr^H8_dTz-7RybL!1Gl)Sc(ac7=ULiaU~bzDy8@Z&q8bOK!WQC~ts?WJEg`fiByoSP>p7VhJnuxTXh^#|@U>qwo^p}eH6-N0qSt_&p^AF7tDvC1(L?4eN;95#Z_e^$v!Qwy^ z9@z!&;m#@f)gAd$__-1QvbG8A!tdkk0fZ`sKsgiTPFLzu7GS~{FRO>e+TaqL%h;PW z2yY|Q)FErgXceA6@C>tg{YsQ{5mxzBpDW-2@Yq-8iBH>p5<`ET<{VX&=LE8iz*d78 zMcNJke5L+Ema&QirYbx2$u{u=w)=?_rI zyStv-N)|6#q3h6Rtq{XH9mSbM5eCBCqm}NR*z(&LhJBnj_`nol9E#7lF-^0 z!?};OxNxW*VZ8JFHQj_UL~RivziTl}D)yB1Z^d&8qtdWH=xNTlM79-*AM6$kvqIi2 z`7O=L?hPP^gxF?^^cbd?MLZX{JFc%Qcc!a|H6tPMUIssV3%dqQ#~f19ZyBO}lAC!c z=X7ppX%_3OIh5;^#;qG|M<&`?AuXP}np@>}x!1;{Q+{|!bYhGQTXQqtfcvr_cE6b0 zN)~-izG!UHE9tCdb5QUKVB$3pclSaK!6z?W2KLH6z;qdihk{axmFw*6c%XY-<%V75 z_~n>Qi;&uTeh+MJwD1@oiC8$^eDM5p>;5S7D60f>eaRmea~$CWrJn&;ioXTw3BamZ z7|fa9bHZ`7tYO@?Qmvir4o966gu+dscjgg=q#>J#U=LHA5~1AyHt~4U_8FM+H@}nMM5T}& zF2l~dp5e+;NvEXhq~dNL=D$sQzBMLY;({th4Sb$J*aa8W+dRp;Sla7}P&Ms%w6{k3 z%U7RUzkY4Et6v3s?Y*142QjN??CsoJy@?m>F^}{WB?GFpr0c7>SI(c`wo@lhcRdUF zB?$QY!7*61-K6>h)1(klbgP^X@pwDbkXF^IDHD=GmhLa6R__)TRS=_6NY4Y08}ckj z1oVco&1k(8@wD;L4#n(>944L$d-m(lw`FO2!&NOP{mesSlNA*O2ikU^^9#bWGy_{v zOie90!#eAfEbCI2K;ntZ$hg!~A8IaHrq1j)oMPge0kY-1->^}eAHycHAi6WY*(Hwf zYPQ_XX>lk|!K5k?DyI7SsR+BMx4cx2GM@eHQ>Y9iLmcN)m8>~Kf;ghNd-=abS;RvCG< zJUJd?ME7`+WMfmS9V750bodCT9bVQ@(N+oA)%*<25TUpOcSDr-1-9L{$9yF2iS$Fi zVHTXJXn03f(oNYy{Ss^E_~|ny)&91v1K;6M&J*S&Q28q-@uLDoXrw6bpNbKpHW9@e zAk;8l1o*KHsjp3**$7V@V$MuomN)MQ#!H9E(DT{s>oid7uKyqJB4VqcMR z`KmSNU3|NEXdB-r=~c>HGn#1}+Nyl`_5{X+5dxaZT3CI$z?S9*iJvOy_z>krLAM>3 z#waDIqzhGC=ElILi2zmyCybeE>s#O)xu|`ZOdaq@wxbia>XqTD!$Xn%TeDpcnTo*cV5nsyv zF5B`JY>JBiL>CFpKVVM*4PKP!9x2UKua7i+dWy~U`ynG%LfP(&NG{sfDjVS^GXNkk z4lD6pUVRusMKd+x8FIHKj!5~(gpf+qHo6~{&X5l~+gbYjk{MZ4;{ zYf(186;V-&$4s8ZFN*4trtIMYOjk#;y;&J~Q=WLF$tw#H;1SE(Qxt@ucC-bmlQ&^U z=dB%2(7A)li-hLjw1${)s2o*!w1W}L9NtN6&nd!cj(jUmbG|y%$lZ}KF)uu(RyFob z0cr%I83F-9)OGshjIbS>hleFzDAnf;r8Jvmh9@}h5{!mhY(lKek4Ig;scWbcd=n~} zb|;|0)lQYR)e60q*8w=cey$tZof!wq0u?*+O@sA3eri>E;`Kt*fN;qqBJ;o%h1(K- zUb4;WUY?fu8ap!W@Gt<^Bi(bup&+BICVZv(ucWEp^;uW2yiEV!nKo3uW+Ol<3~3Z+ ztHK2wIsJf?6`h4FqQ$pKD9}7IsJO#!nayLZb}6{Kn9vUWVy3Zu&mIHX@GIUK;@5oH zYnCFX1TFaogSif|=NWIK6!mILP%rr_YZ_ zsL%>4=}htBdJ~*@V-fU;bSA~t=k^{eQeVZ(dqc|##?g68unTNECZY&58l$PCkj7JW zWHoV{=9Gxh&b;~3FGg95mC4pgV@b&ch@Jq1Q|_7cJi^2%^2n9kPqs+w{?A2?8P2Z$ z0*rpYPeh?=T^w5f)a84j2g7I4m%i)ZIwX{#k@tbFJKhuvNSd(ox zYq|SYBaG~Q-rG|UD1t$GXv7exTz46iB;Sjq3;CrPypUJxIqM|v^0p|rd&W9_4_`>P zOIRDZd7$|!MDC-BApB@pUKC?3_2JOD1h?%W0W&*%!wWjFjE0&niB z$Ku|{fTg9x8k{#b!36kWqqj9BBypoYZTOwW4*a4zfQ05__X9*I*Zuzf*A2p(kX4Jb2EmvbTvK{ID&OvOat_BKi~mngz*Mv|>SQ8b=L zZhNvC1offtKJv>L!d{}{d6DX())iTfg*Np7A>>>sI>Wm!{JU0snRf`85# z9>UGbLHYCqkpZ&PpxqpzNcA8@Qja z_Vr}N3?arCJgnmO)s}P_T5t4&LW=DhGK``@%L!NVwsmG{hkNDN8+siAJ2@3xQ#|DD z9z*#nEGy0@&v*B{4tY6Z1=DS*3O5l7owPS!q+|IHu4Nrjh~g z@C6&d6gK3%BW?Ihf=`?C3-?=Uc2Xqid0!O}cF^|%mzjLMe2;ENp{Oflv zHIu&`7ObKIzH?v^!W8rR&eb8mdlqEZe;g0kK!T*{4tz&*6(rt2MDPyb){EbCm$H6? z%;x>wxbzs61>d=@Tm~!m9uFLI21kJZ>4+foUr5V5-?{W|v*4gWqt_I}@fp1J82)Gv zd+2Y6CHr?zegmBU&jysqISJf}aPvzf)ORlSe&+e_Tpw#AmAb$E1(%ZZccX%gjS+W$ z+2mvM)&R}sL{1{^&xV6*JqAF>%DdveD`1Q96tWMtJ!0lE8t|TP-STe-mHBs%pEN-H zv(d!-=^Rj~Q?B`mgT(*Mm=gZ>@!RZx>GfyB`O`^o{r{en7JoOYe^1JPPs%^OBL9Dz zl;-00mM5R(A%8g<#5>dUi^T6SD!-w2<^L?w{&^tzACWEGPE$9w0269Gu)({>aL}j6 z*J1EMd9?8L0fk~8MSGi}3i+3X!yP~Je`#C($ew})a!y*Z-Vw`C0)D9 zNreANU>Vw31>BiMK+yFgO})m`j7&r!5u^i9-f5EvkhR{ikO-x_GM>{9M^5+hGc_;P zu@GSQDx=?5uQ)Zihk1Wn+R$x;3Kq_X_&fvON)qC5K95p1z+n=kZx#(eire6K(?+^hAZv+UB2*4>pC?`<`(2JeP`)17;JtUWm2H6^()~#o={u|AL%{ zExJl8UmiPJSIv+ed#kzwk1)ma$Cuvbge_BU3k~b1_!X`G-v0aBZ?>l^j;J{52@a;) zzjqDk@+m#`^i0KELgsvrSkr_Ejh!7JbEB#vb^5`#^)E(3$F6B#tBQBfM>m}DPJZvk zo;l?*A*i!aZu9fm5==kbr&yK^Zh^%>6Q>PGrISqPRgQrko4=dW8Z%b+oy%mhEC45J#Z(T1teFji zY{5Z>^V)T%Z3E;#@hZtc#)PE}{AhAd{L$3>kIv_CdDylv1-GmMunO51rni>W*@?G$ zcjMWhH(Vg02ykrGy}$oJv;FmJ1INpD1VZmD$i9WZunMHE3eDD1}atzjmjH_mRFTdAG3bV>7Nvrm@}`32taS~PLxm~ zqRcY0n##ewStAl>n4v_bUR%2I2(;Ul;^rf6(H8g<%_QP(&9ALHHsSU1zcZidY6cRs z$0qcs2{vg;B9xk*!DPO#ak)!Ytu@_e-(9M|j>)wt{!061T;ZmBuE47CNvwfNLfnm} z8Rovv={)^*REz;#+^SMKJm?#p4~?s^og(ltv(Aa>Nd) zHocqE2-Qg`H^p8bT*(;5!g4U?vlAmGeMRo>Q1wive|zK6{tctU!;=OPlPz{0F%($X zga)lK9!2@IQC;497?W(?w`@IW7^h{+K5#GkRsU@fve?iqL+K}IQ+65M%+BK8h z50h8X&Gna9OtKX*b_U-Vt#06*XivfU9XE&;AzRc&5)91Bo{M%}@~}E1b7CL-jPhd- z4F?r-@2?G`YDSZBrY~NwOM^z+?8ns5V=~i@buQZYeD>AhZ!5~GF-PZXQv~0J7Wvc`Usr>4`MzA8t>w0*4}2^LM5&cehs#* zatXH^Q?|Nm@br&?(BY5Kat+k}V*`abE$1h9oRwgKEx1mr)bCuQuol=(3AGO}7@~Im zDgpR|()m|fXDmkCS>>S%cePHbFzj&baX8R_Q#dL+5D1>?cK%- zR67StFhw&1^?RnYJhOmg?hTF%Xo^|Va(-3Z>@Ik9j91wpxs`?# zCQHpuNJo5Jv&R|I)th^xV0)lW)9@e?r2aA_So=JXsrRS?CVkrCM(}GRlQ-y)P><-e z-GrUq5aK5KFfzX7YyxVR+j>Z~VCaAkB0$q9mQyBioJnL!F>NTx&l`o8YC-YBgbRBH z_%yknCO4A~LhaR`HbR&Na}?t|LK@7Vf4NbF8SAZOsJvgFkhx$sf5A>KnD8qA!!te@(=%vxJx~dko$)WI zxBl;V^IyfApZ)Fa3ziB{^74pPWo4O=;eml+5hbPK;$kV=JC&7H!H&1)(rs>Jgh(OI z#3mlS^>UB-cdl#n#I*KMYMLm^lJ=qT1pRP)!Te4a;#jN;wTRnXvpCuZmDf4L+q1ge+s$}cPo2;8~bh2T@HihTryfuo0Ax z{)u$RzK4zcVe}gBvs7~X3|l?bw3+G*=hY2Xi>F(2U+AI_At{T~o)3#@R!Enj3p*Yu zm_iR;GLxg$`LNz7&KcvY(p55AX_P|AxvJ7Kv;EO2cBDYpN>10-CbElOedxhF6=6`; zYL*jbiR>pRT$E0Gf*}~OtLQll(rzW_x5NYs!4AOOl%s?p!rBsUfzthID3fE%k!B7* zFvTHEe6mNyIAz+gZIheNv2`_XFHJZiy_n9H^yuMuFW+qY$tHB;z~8~PR?lZEXp8xN7_jf715am-{BV{ABh z8WzPlkF^07aA;npus&_BWmmFkEatg~vSJQtU;}n&fiIWo4HO7rlp+;_hMGRrFPclY zYD($2XMv|NmF^~X>Q91aMq-Lrl{|o5>!uuJfmye3ib{<3%eG1ay+aYcyy7nHab3~ymFb|ig9H` z>=tSN6xcPn7b`IKxPYz&6wm}3)!Fh)m+64V?F$oz@V#vLJ~oc3cyZ2}u3aaF$T?Bm;%qyktd=&zy|_He{o00v+HH*lqq4|F8nb3nhr0`8jd@S^)`r&G}u zEmQMD!x3%%R^}BKv_qLjdq7$8Y?Geg--#r7+N}863$21$gpB(E4H5t%6%#i-9jJ z(C~CE>`x%P1P7!e4~R?|ZuT)AaNg47_TC75NeB2Ne&DDT`n*vP zYfiuTY&wQ3keuS$78BwqK<5r(3+FE}N>FzA=MeLLwdf~Wb^bD-7-+%Lcd0B-zifE> z#)CQX1E1cRW@$x%n;i=Mnf&iM&mPb?zDJ5dZA6D@poCi{EitLO618o$ix3+|<5CMt zA`p!*7Gt>)x&tiZ%dBAtT1@+gXYCLmL*{a+-Fj{R%%j%A(K{AJCq|YyP5nFR%7Py7 z<(*Tb%8Guzf%o!t>wIaPx`7C zkNZ>rPCIBI!}O`p z?1BrZURJTw7RR?iXr??Q9>XGD?GY+w#GUTdCyuQIMWVUwVA zXW|}wA;w`h8S6sOSs&x{DdwI2$|fDc!aP15XT3vJiFx5YJAN5i=LH37ZW5<^H?~n0 z=?@lgS6tJT9MYFmcNV0HI65%>_|f?oWIT{dKvp}9)(Xd z9>xS{pQn7dsIoZFN`6Oql+T~jYnOU2|BmcnS)xd@{AJI3Uwo6SxK zDk4TNS3i@e%`QT1)J$v3(UN1Js`ntYGM9$6PRN?f{Ke7sNk&e`#MZ*9BZ>m!#{iSK zw2)97)iCn)QSr8!zL=YRMIZX@+CRQkk$yQKWnm!s;(o-n*Im9xIu8+~OE*vh_Mc%z;6XjeS@b;mTk zZ+q*x1AijZr_HaMGU~KmPn69IZ@%mDYR=>nRT6;MuIu8pyzjIy0n&fdqQ{r&-QJ$M;B>Hk`vCkSO z)^k)z&8zOpoWua`bC@tnycOYI|;jqG<&6TzOg7rwF9nx{N`(3a{M71bAgc zWW2eVkAN9G0YCZgV$A=#p!45QjIkZ#;63veyzK0ta*tf!);#|#Y%8+t1BiqtibVBK zOOfucb!T~41WrXqXX9T_^KH{;BOKle6OQ@Fhvt-`piE;mHyv-fZC;leN+*7%z;N$z zeXr_u=>;Q%|D(F`t6Q4PJePXCmv;;9cxf`Rj{32Enf7~|rZ!zCa13@?S;2f)g zG)>HBg%z{wMph0l9Li=E+=@{>^iVbv1G@eD#&Uq>m)klPtSj0QqF|Y>1nY8_UH2lu zHQ<>rwtNOyp|&{zuSPf}6wamux%Hk?0hk%TW3s_ zVY6^zpw~sQW;f#5wEFU|)hv@0BJDIBE;$Wvj>K*(%wn?V#CQFlc(LOG--FC4W~GFV zWB6)IG2yHuNI2kjVhx9&u0*`}r@&DOVWw3MrgBc7uAMW*Q*)Pz_Mx&wyq~>+{hHHn zJ=eN(TDo{bDA`h2c#rA8%Ipr_WcOF#*_QOAXrhDdS0qKt^M-)L72SQCg&RgqSK|-QRVsB&%kX zkZS86gL*-bMse2NviIR=Xpv~lZ1W^T7~FHd{&O%Ve`3D4*UB@4XM*M8>8+@;iq8_J z7`C3O!rg|-7a#TGbSs?w4rlCb9aAeV$Xgi3QJw-2PXYP9E$j|D!v7}fWC(;QyV|^Y zO*v<`^f-HbY2oLFmg~N#T6_H2G3uw!j{2?yq$sde-0-W42s}M^$wU2Y2mQWtMAI~F zy@h&?8LUe<_%<;m8tF~Z^^wbKzmOnlmyi{2kR>(?KS?`|ku&5qh*r4ftn6g%J!37f zxHiV0rZGe~8yi(Pvww&n*QSyG8~T+uo@R=cV7gLa%`xLu0BPvwNmp-~kl!t7ge=wi zhI7OgYGY!c=UpkQKL-NFc^M=E3#VT8htrCR3kVqh77m1rRm*S5U)<>#N=ytP>?ePl z38CD#C2=+V^7HtiNCkr}bKR)gAEd5=Z?!=Iq;#@=vFFcjahugUx5_<1+}Jq|Cw6cYlkNCJus@8E|T zB}}HisJHGYKMhoEq02b>uh@P2IM+V$Q;hy#c_cna9gRlFFl?Tw*A@a~8Ocuh4?Tprqdp{QLT+V7CmuJ$?053?LCSVjN*+o?R;OTY zs$I-qSecPqvxq9_U-#0#Z<@iKNY1e)RXPp`ItTzJtto zJ(_O_7S0zIcxud+iT_Jg$PkHgamXX%*ToA+p{gp*Gin2n<+TB14}g?z(>}BN`*$uh zulB0-5ZfjT7EFY>k;WP)JG6zsrdypOX!Ln3CjzVGH_hWe$>%oK(A&-zilU_HP}mLD zTejrkGZBV8r-*AdjDf*#Im1|sB8<>wJ?ib1oOUCgoAg84XIPMD%WM@oxx+5!1r{xo zL*tkno@mQwX3G-=nUf}cP%8vgoS&sRX+dk<($voMnz}9cbqQ+13Pd(4(mJ9*J%D|_ zL|c^ZyCO)}W4y9Q!HzFJF(zgLs|!*zR)o6Bqm8FV^BWAX_0g!QZWBJt>*gw*4hwQ` zm^V%W`6MllY2Q{W9L(0wVpFH#S%PP`&j==(6+O?4PCK#Ipe-3Kn)p_g_VGDtO3qMo zCd$?B(~eo{!_EAUh6)8C?@XKPyU(-pb3#&8G?{)wK=H4T zD6bRptRU7d5Z0S7ENDio!+raM-g za+o2!CR7AHAoSO3+K_5`p=SAT%F1d8`$`64E-N!9_aL;VXMtPPTBjs-2t`<>Oer{uQFC7>pmOxglSGQKk&_ z#s)|yq`0IC13|}Ijbv4E-ARiO_p6Lanl2%)VrFD{%SrV>E|br3FVtu%K!OFQt3UJL zXm%)vHJ-f1G@Qx_!5)}4(p|s8q!)g=GKb+ddS((zh1wXlvsP4%wu&@?Zj?FXQWKznCrU$zHWA~mwYRQ5gmv{8KLNrxNWt&S|Ow$i%X*}Z*qrUER3Tv_O zsV=Q|F<$FMwupph7=CKHV>9UKm8psO=Ernx@eoYFiqnQn;tr=KAE2e|&aqCXO7@84 zhe3Lkcqfs1+zP>lT=f{)K#V3PxGVn zawB*%CcB-c3W^MGH#CUWn=VNxHQweoaBFmAexYA(yudc4>$T?eP1D9P3G>Tisy1^L zh8G90FoN6lV0tOJr>R!css7OeitHBe(e;adr2)!(u8op&BCqixUynPGcr27#KiYnp zEiXHC&vWCv##xMmn$?L8ak>6hC)C|9mzp{)Zh0ez(hf{#JZ%XKZxQicdvIByfV<_E zI`7vF1of<0)7SEIwhCuX7#VCV>R;Xa6!~1cbaABM5;2iA$!SY)C zcX{nUIk5k~mHGY& z|0I%e>u$uvX`Rns9>hxd#`~0?xYh}cK7D0=#?M;)QdYAR;H8R0KQO>GB zJ<#@g*ICA+1j6y=3keDEcKW0QtvB=p%syPBK$M0#=!2l-S`dsz+MVeY&gR|^HBLYs zZexvZm=~CTG$j}(o~>9KBIqO<#BM#2r@F(ay{$D$H-AH#BLlCY{e4iou}-Q+i2EV- zuVm~EWoIiZA_QG)QVu9S_Y8@hF{pV(MBLPX@-iU8S;bD4mRTIQ@CNvNO7_ zTEeHs!}&K?#20@2MLDj`vX94lE*w;)Yvef5L{>=geIY=xT8yMDUBsfSnQ1E;cfG|47Ax;U<)3RfV=k1be za-{2Ep?qQ9msKn)>jv2>KDmhXt9e%>s&edm3F77-qOa<|pxs^=;0Y7dTaXZH&Q=az zm=U0hSReFj5TQ7~__+Q#KJ>!p6PdC@Rwb8+N1WF_mqrOVOXc+n6b!!xxU{oFsR)zp z_ywDHexafm->duyg^rTvC{g|QH%jY;tX{hwFC0s}sfaz}AS8AmZc60@vdzE>VI1v8 znGPZxAHsar6?#sH$mA4|-on_&aExQSK~VKGq~!uyDPJf_NQuqoIrW-2W^ZA{-+`0( zCd3#$RX;+aNYUFvi5Pp6LFM8%p=P9hZ}`3i$Nlv+A@Y7$DetfL0)Q3-gv37?IPLd5Ax`nW5lrAh~8E|5VBnsh3PL92;>guld?XK>sx9aPv`mXxng$ZQmoU`{{d#!uj>t24JqV~qw zTGt{z89yBQO3u&OonII~F=!Y%GJ3|d|NJ-I7ccq^JKX@!)}`;;w~}nz%*kNXVeN%{f4DcM_DQ3p@tf~R&Bnqc z!+YMl-!`1CSaBXN>y|H%A9h)0Y`S9SPlEC~Z8wz+N8j-HkgaB}4u$FfY<^p?VEPZ# z7U~W`2znTM3t~M3v72WqbezujQAYA0ZnXHmbZeK`16wf)=|jrU$A@sVScDN94mr;O zlzr(b=#4YF2^zXX^#Cz!lgMmvcFYpAEHTYQTU|rYNA`FoOOf&MD7xMqX*8PUO|D*r=KM|z6A#`Oh0FkH^k+4>rMq7uzV=b#}ux-V!{v~n7mT}8X zn8)_ik&WwgXvIsL>x@tgsD#>gmqmV`QXB-B@NcL(1=lJdag;(!SCGtc(MJN}z1-#W z9DYgcdH*rttn29{?fABj$wtmr8c*IFPWmQO%A_7_9?Z+OR@`#w#E#u=TTN-bA`R zJMQteef5Fe8Aoq_I==dD;hUqWHa(fg3YRBr$=hRf(ZT*>=IxXVcT?x^7KQ|X=9QVJ zv%8|w3%Cglg%oBhZGAXXV@pL3;{6~?v)RN9VB7jtYJ`~HgN^ciph(h=u*TE#{cBE^ z4=x(rSQ+)^&#o3#<>+q@>*-F&k4LfJYlGIeemvINuH51}xT;IhA?w7STe=J9&X%dv zbr(cMs-DO>ygXYcZEcaUiHoUe{wMe0{=$wMO^!`ZKwBH-gkbI9ttTGhe4bCfw#tY& z{pha0Rpyu=Z|RIV=@4+bCJ6if{+U+<2NI9Io7U~Ca<6?l&FjAZDK+fd2GkO$r z#;Yx4BAUAkZ?$mC3{P2Lq8}dK798MIeB8d^aO!T|&3|69zwHp1n;6^p&qJ_2Nw=zw z!znGQCDmU~t44lEZe6|pUSOMMb<8~p^+z`~t*CO-dCzv;6chh?_0ODN{=r(j(cZN`=WxAQmj8*V0~6mJBm56c`F0re4_XK0 zR7l?Yyw*E7w&UXaw+#85Odod5S+HK z0jLofuR;?cz83*DdLgudQ}U=M#4pSx;SN`EhPk$-M>R1FzBmI&#JUQ65#2_R8GBd?YI~dVSfIgV4ywQciMe-!;3C*P zGoYe*a2VnPn#kM<#&?j^;nyX?r=W!=#Ghv&*wgwiZ-e>e#SNG{c_`yPJg!tEp9G*_HZS|*`&evej$sPrH0Kl{JhI8P?n&o28su?LNt zPdHaja3&7;PyX3+drj&+Q22%|50U*=P7w>pYJb85^~8pZ&Oye0SZ~ z!4}Y8c&+xP=a%1J>pbo8{UU|}7<_Nt=}!U$H*pz6A`ib1seZ#mWJ`#%tB5yCxtrHm z`xa7d;kfacD6d_W@6KiQiAy{+n`w*t=JdT!?yUrk(N<=L6&1cLr-eXGAh*Z9IR<0{ z!XlBYDI8WjL@EyD%tTO)eX=zLy2wV5uAUX!w$RphpUaP&Q&+a=pChYh?P^}g<5k$m zrTxZlmE1_ZcWdd);XnKa%Av=^=^&9Bt&8}K_!7U2)=96x-qc5=_)HLN1iVP-FDWs! z3u&Qk=vUnn%u=B+i4DFC0VcL;f}3Dx;qc-@l6#Xk=vg&U9$f&J1Qhc481&N82vGjfKE>{YGh-P|kaNxC(Hlevz15W?jLoV;Y_a6?xW(w7WfT|GYgZ>Hf+g}ic z8Ndz?yNqo1gls_whK?Dna=M%)c+KA;{JT;LMqA1SC zv$NccnZ#S*P}HJaAbb^!e+G>I0BYJpB?=Un4E`bP$PbQjBzQI zfdD8YVr*02x4f(4a1dzy07EUt$0xqKs)erAiDES?Oe&>ay9PfQY0YTM4CE?^?SZOF z1gMfugf+h;fQn|;usgRDSz|2xEY2kgfy_?W4aA4f=f}?{Oe33pIk+giG@@eO6`&hw zF8|g1pz$j0fzK71sbtR}z;~EgEc}8{$_{jr;1gA5A5@xlS6V zmseG%FAe9!b>fbOwI5;(QV9j1>`w^w2Ci8IuNKKcOnWQqndA5PSj6rJJX`SvUrgt*hd zjaX6->xBwJgsFnghLU0^;5wE`h%nd4egl}grLCQ`0r}TC6dd!o2NU=Hbk>7755bkE z3s6M;&U7PQvA=LoIU_i4{ztKBJFN*{?TaWEjI)?5cvA?XIK>P?G{vBK1Y{h;IKzT< z{Xydw64z7X`=M=@OV=XK<#h>6d-}e?^-rWUEwgKJ;yZUIbI?npt<<0}2zZ`^H zpG?eecW=MG^gdOsWkL7WjKNeW{T6n0=|(|*s!}Q&lBAkf^fAhw6cmt#X-Ng|Crs?( zv(xiPSAea$pHCWi=So_*Lib+m?)v`y(1@1PK-!hGZfkO2AbH(|{Yyw|4kbN${QUnp zo&4`;>QDw2S>=v8-4;IqGYIyl1%A$(62<8G&gjIT%Rtedo)xVh5+1{|AJP}f>n-+S zC$9cyiXr|b*Gl{x2$t}Yvcw}G>IJ%Tq)sEA8&bRy@2Z_3kI8(xDtdVuInS zZpC2P4cX0Wv|Hjn89Z6FSM!1Ng75$32>ZvXO^d!q^X`jQ)85i+=9_>vr83Wf&#;Em z*>cA%p!}*1y8=pPQ?_R8SU@O78OF34VOCaf2x~}%riCkW@;%zsdAT)i<`scY{M@4Q z^2VSoDaaPEApixtsmAU0Lf1H|Kwt>Em0s@m2u@F^?xfLy}vXr2K;GxL`xDaK;XTHdq zdFGu_S|?t4I)!UleGhbWLnROkCE7}u#W~coeZKqy#VAx%TTjR(wdUB)C{iR(2tp!w z+Sy}cUp&ITdsky0ry>iWWdIoP1M7XMa73^UgblQnoy91VRc%CnoG1^63S-1s56x9= zXOu@?4Ju{KB$`S-V!@$y19SFQzI%P3ylB2waFe#tlX&lZDdPRu;@nnkWVGgYy$+j$ zhc(@ra?vqjMI#+N0vmB6vip$?L;^*mLidrx+u4fz(dQ@YYT|u*5&X)sEnn-%>E>m@ z0s<#1tGcK#$yc|1Zw<*%R|4(uD}9yt-W`?A0c)uPJj!s_uW5KG1_>hzJ8-X5eA;I& zMGbiV40aTR=^wSkh?DYQh}efOLqe{u^Y%_AM|o`!G}2X-|?$zTG~8(ij6zC0>sm(IIgkKl(;)S+ZKK`6i1%%?~dZi4Riftr~0j zgy<@|GNr4Xm3nF!h&+`PrtJX-`kjNV;g%u57e&>49)k9d1QC| zy6t$uZG44qGV2f=&&f=~ki}-SH;l5HX@+POva=H2FWQVK@Qs^@i$?D0)b}FV`HKDZ zHjmv81ab$E0B-RC_Zda_OUp*dkH)u5iNyhKuWMuig{+G9b%z$gyTCFM0(*2-AK5Di z>kMAuCW*6E~kV;BXK|Bj@of8ul@PFy8^2A=2=z^F;h!TU2rn?Q9z zm)O;7!voB0{r^m}{(Twu|6g90{@4R%RmasLA6U&nY~`O>=RHQ=Q!j%zlZW=5J=voY zNOc$NTz31mqQ#y=gF#Zq+_g5Xb6z;({4*VDQX}ht5{d9ih9Bkh7KLNtJcym27P8^) zj_vuyf`VhmqG2_jlF`R!uQ#o|wgWuI?!V!&NeW7Z9B?^OP!sS8$c1xVs}Xl`W+i?R za$+#Iwypsl;w!n(Rs-4O{%x;}O~zgrMSA1H>#r6ce)2dN8{@T7kUOFJsO{LsKqQ6$ zYkfhKD^QuD_2BFY^Maae&f)#L!rTspJQJt=k_c3B3K)^wHm0TIrO3~(;gYDfoLeVJ z>oMA2A`qg;Y`Eyi8Ih*uXy-+7Byj&<8DRMr=Xn((6%R{*Yc-t&cHs^k+bA_rooq=e zq+S*O@c>ezYBw~pK6+Z`S#mv{e!Sdi5xCwx9epj%p*ma(JikHjG^g07QG81L2cFID zf;H!Pc#TZyFh(FN(tN34WsZ6+B!gV7?V~N4GP)Mo)~yjQWW@^2mH5>aPYg=Z1eP4~ z>M7x1$hI`0vh(rsLUBs=vTYAChCi#6=$C9vmW=}f;19B8?zC^+Wx3w(Q>@ES@}W^z zb(NyUxv|J&aW0-6jpf^v4p+_0(6*sWyd(gR?`H(CRH!@Nl>id;oA0P+i+^1~Oydd` ztl&5}r}9V9R&--+e~)*pUO;Whx!&m($vF%Y)#T5|T@JvkrZ{_c*V}Q#PEvI}b1ckc zn#n+Xq5F;v=eHV~8HHx*y;2EN3b^|5_MHpuiWS7y+W;21G>d8o+m(1xUZ64nZxpI$ZfMt+!QpM_#x#mW>((yWpPGB{ zr*W`Jhw#*Q2FD6a#!<+pbVRag_)e5+^YU((sP5BkY*CpWCv#Q^Dl`T9qKha1z;79!U2LeYEt$_HKJX};nlB; zhg;O8`kcI!;6#yy=P=7-TE0`^tCIfbbiYd5-QHo#rg4b6vS2UgW*6^w3<@C2u-oXL zg8ku-JzOc)ZUWEZIz3#bVew!KF&eu#kQ~?&`YG%qfh_fA*{GIFuKZ)^xf)AIj|T9~ za3+JDWHNrMe!s8CnQBsdB93R&xC_?K@$hivmJ4;UYnQ^sJ6{=BB zPSnWagB9?uaeTCrv+~SmqlZOq{uO4eZ@E_q)0R+LBBELV7V24eHJDvK{91Gb!HJ7$ zTiiixPDhO?l1#7d-Sy5-Ni?0o*?25)0hrfGm7RO-K!`tV)bPZWahs|(j$36Y%u&Bt zlSW?ZXOy0R8S=vMLY%>*r9tdls@f@&%JiD_i2N|)1f#@5u#M;-C2Y3x>FUFzI!v>i zt$os>J;{f7Zo8dvdK(wNl$^JUMbA+|Qc~InYFdNfHS<#hEN*myBTi$;n_n~iQ*gd# z&q>Qmb+H&#nB4R|ZqMN@k8IXmyFpTTTNZyX9(!{V%ctm0OL8~+bfb&Vx5SzP-Xd9x)l?vv66qj@VeS&%HZMd;0CW=mx1%Tj%NZ%x$nJ3Kisjb-?=&#^#>`2Fm-T0 zfe`7myKn@&f~gLCVdCQsgKQnX@^m3bA8J}y`6BRANsY;I7KPviFXBwOOYc;2!8NX( zGCBR!M*i#Td!K3+l-W2$^&(g_DX_z1-57H{iu1TLFL2!Ed_uuvO0+NxeoPehM-x2v&aEO8)=UXZECf+9wo4~5U zw|XAs%(5^rfvwyuhU~g=b9Yy=PG`2zSL>VN3Rxp{R;c6r4McPN)BU$TDq1x=KUn-Y zK?jJ3`dYA@S2ov#>OtF7r!a#xPO^^D`VGGN{g^u z?QZQe@!f9`mjYc39a9~kIT^kzkatPLdwc5GDF_3^S?0^BGQHo{yN7;)9r?Y@=F496 z=Qvr77EfMVn&MqJRZ-Ed_B!~f7t!O5Ag5zj`DX0FW76LP_|d4GFWU5qQxHcuYF;X|Pc{u$x;>b>nxOVVT2idGEI&ho2N58q2*Vj9S`U$7TvFKcb-!KLbX`l9Y;Q z0x*{i5XsE^{q~wNaWTX$RJtI2`rC%;ewx|-RX(N_%epOgd`ci(1F}1Aza)0DVC}3A zfL2WjX@^grEMuX=EWTra?E^=bdKoNl2knW>l84GhPBYJ!onG~DujfsPUlMj({z2XD zU-9#|1SHjzFP*>%fYe~MCUoabo>wbd;2d?CxT;xre8<(T!)9J@1gZnp+G8IIWA3G$ z)k-1mglkZlE^w#w0wmEG;LNA6?+4o|Y=YE3Cc`ztlb}oS9R6|-sdpBVms1+}Fe%GnMJqKf?Vk<0Z0kl>^)FoK zr|p|Ac1CQt`j}=7^+Cj)8+dfklFy2q^>k?Q+Au)=quo3A>ox7$3)#hmV}~SFc;~k2 zpWjxpPERpb_vFeQL{66IGTNKL;$?}>(0UkEM)lhs)xzU~xVEvqy#}J!s;;xR<^FE1 zEnds`ZNjT6Ob%h`$X9c<>ZR6tfq4gBWUhUHF&Af`8!5Xux;z=aEjOeEk^q>0DYJAj zuGnOrQ?`LY z2M5jtcLp@6Mipu|^k?-Ke*?(hxsese4+gdzUV{8EYugh#2f+a#a-la}mv!qK z0D!jibWx!{qa1XXNVb{xgw9Zw_8#d&6+Ok-1Xf-wP{hK7UtU)7I!(5dz|43RbxOOc zwfM30^b8=~oFxq5UlLYiONsA6zKK$=15M|hW;pmThFtw_;k@zo>lvT9O5KQ|PdDM> zIlKDrZB8~P%}?C!Myh76(Xt9AjGPwEr=IZz*z(0&fm;nkep{!DPcKPQY$z_b2GX`R zGHd)FBwtMS|Ma)1zW+75^WXBd^UvQgniS)cU)OLc`bHXA0}u`;=UV|{oE`$8Cw3U? zd!;WAxL@9q=yGrz7U{JZEJD-o{yV7I{R8dq|I43>s4qxg1-NVlxBeXye`mz{6UbUs zJoiS(dG-$s5kZ$oIX&RL&RpGQ6Hw!J%`BTjl^^&}3x7mRvCe8S%QAQA|F+o;*P zCQ`FOT#aYvUl}~SIBBa4KV4W*@A7*69jVT|`sc@s@7>TPd7s40_Wcb2z+cGc{rAsH z{)j`DkMc8x4`JYCE)j_HfxFs%EhKsd-Gf}<n5+LVq4Wl zvi!Mwj6Cf4!&9wpPMIsVXkT|YUU8SMv-T#SpkU(x`WWDgD25BEFisFU&T1hqqpqrk zL-`gQC=ta`zJ0NQyB|%~6*Iy?+S9=a1VFmI2CphVrrY;z>^5+p*!ooSa`N|2XKJjR z?yN?i2lI^)MW3+%2CNM-zi9)*Mj6~M!lmLeeRkw_FV}o=w)w{PHU2V^K`*wH99pZH z0gf7N9IUI0tY!$oJ`XGS%22EVK=3XLIOHjbwX}*L^A$BBuLDMjz(4WK3-0OlYIrwz zG*&q9adP!*>1e}UL0aBYFSNG2ydQ;s&VY>)Q5ih&opyr3P9P`6k7jYh5R)ONkM}K2 zpkol@$9k|aH!Wpc-PQ(f;Q#QX)azf)&@!s-8J?)?iZJyY4>dcFcD_yVB4sN_eUo`u z9Tk8}VzArw9%DH*$Qqy5XFK~D3BdU*d^9|MRyhOH_Bb$i?1a_W*)(I@D$gv_r>~xr zu7zgg|6UySUvYf?MV;Zl)i}Q-4Dj&!8l-rw$Pql}ec&-gL6dI;a!}rv^;gTf72({m!bQIA)b2%Uy4J#WNu zFIGu#8NEvxZwrLx*#Qu2`lYpezh(<292S!@-AJ|<(GN9F{;ybq%G0OgZf`djWt zTm(2>7L_&YdwM=5FvGTlV+ZEU$On#u5cgU;*0n!uQ;f(~3@C4{8{iR31c4m9yX zc)N7w(!Xx?>T$5~4E_nQvyG?6wZ#Q(jFE65k*`#de^fX)wx*A^3kKiYuJofV@>6`h z^rHY(`t5to5(+A_x-Msvm*s8NZjsUbnAnDe_gbUNhWKr+=MZ%Pe%zRcDQq>UF3Lv~ z;I-VZpPtvpCmU2fYlPcqtNiN1J&&}h*~Y0K=eBXusUcP}8`6$pCN6>C{4}`HY}A12 zyn{X@i7pEP&uESK;e+Qp)bzv-%#4|^+59lq%O90P78o~BCFDZSY=?HDeUvM?ge-S^ zW9ZWC&zs0pSQXFCqw|A)NwgU#LyN!}5`&dCS0c?X?thdnybaeBEW$@QTVyA| zv@wQQ?$1}{)|A_kd~@OtY}k5H)~3WD^A&j?zBpFzryiguW}=vtaH2^KqQ`)B7%w+{ z+cxt@!AQ=)$+F16j{OQ)_$bZLqao+jVwbz29h?jM`*LkP|J=MEOj8rbORKRLSA~PO zNKZ@gzu0AWE`12HTb$@{#1cBSJ>jZ_4XGGyTF7e{{c(gpJgQ$1mtvQof{2JF2QOY; zB)-s1d^J`9juIhwHYN4@uPS~9&VtkfaYkTn!jdZ4hlm!R5yh*&o8}tQNawJIj1QG% zf4Tgp>ZG4tU)j^`HWyBXEs6bB14aPOdN}=LO-%hBFaQ&_eKKk=;v4M_8$P7a#`a!M zI8x^Eo+>Zcnqv2PS?{aRLkee1>3%E1HcQ}(uj&2VUMs!@v3Vovo0vIJ(;57kB2kB} z3+GwQfcawTjxfEn$m6@g(cngYAMV)~vUA%+sdr`H`TUffN3@g14nL&o&3U$o@9_YJ6*P9@o@QIUqXy?w$o9`G(l&MWH>QM?(!kea&J`;y_SC8EpAC3E zz~1Y|4E5KPI|MbI2g~3&Q7Ymht%t}b6Wj1=Pi2-**0oQMj6=$ljIohOa z4i+q^J>ICS>~K8^u|1#kG~2SRSi!IEkDeQld!O@n$1?!N3ZlKuUffXG!w4Y1#{s&8 zXq&(CF6#jOt{BjMJIJZ~vM4>SQ!f-3LnZK8E%Dfnjco=q%l(xv(RiK7Ry~#22Y4xP zMW1l+#xHGsRnfNMN<@!8?~i$YI3Y34 zP~}dMamJb9=|!SKOUhs~W_Xt{RQ%9jXpVjZ^o^3EUg+i}X+7_!tpeX|-O^w*w&%*v zON8s}h9I}97_G5P4I4+|HJSZW2G+^S6;sf<5`CVNI0lhAY2SIsNY0w(!(cO3Y=E zqhXY|4NKZh0C*%At0b7-REbY+ zjK(LZ1lRy=_@8fZ;vr*H+zLT=|1q)SpZ9-aHdF%~V}cNX-C<-nNOVS63X3@8C2;JI zqt|Vj(DF*3^wwVzD)q#bXA_1HGd_#i5z56)PrZ2DnM}!at?5Rvq{{~;vp4pRyJ{1@ z)Dht=fMNdyUH;|5Wh|_Pn+CJGB1B|3wNvGkxb` z<7t~HUCc2lenW2g((NN1C6vr#y#_N?A5u35_|(V+2XEV09p&nJ?3cuDMx`{Oo&=2e zG@lsr3HBA~z`DY4aRa*5)6Og^`~g)4zF72iOrgKZ{bJR0N(;@PqPO1l@c|$6BPR4P zR1F>^)GZ%03i5C>te;;e-He3|Q(h9+AOQtb0QrX1phK)mYd``WSJ4Z?M^%BmR?$n) zrgJu;&M0LJKHn2m>d%s&skC+F9UnPrt|)MBE?zUyu}-?_N|@S-YV;1!qe~pZ2~UfY z>vfgG@IK@lkPNyMCh2o(YRlmOcD!`+yvehO_3rf5{=>m{-MV7tHm~rTH>tT5EJrx; zBK6>)bBgVcchWy(Fg$y(k4}JqcpnyD!I%!^tD!O^evPBlY?nIP^F6;o;BM!hu)?Gy ze0)^81=ZX+Y+3CB*cj#6QN?>rUIzo-opp6Bo;`veF&38K&w<;^0i%)d{-p`3M)@iM z_@#|Y388Fy^p!`)oMuED2|y6y%Z)W-IX~s^nM?S6c^#(s$JoJ*Q6mepk8a>4P++N> zo`Dd|a^~hcQaOFdjJ6$)CJYl>vFhdA!(n4NqU5GiwVB@=cSOn`ivWaNgay8 zXAdRg2g%{Ya@=%*$iyO0`Xy#4hppVCRT+EeBov8VW{yS7vyPu^W;YB*f{J>NPW|qy z8sD$xC5OKDPIJGno#v|E81>)VSNv6Ev4!~FWil-U=;nHp9#70C87N;d7QP#E9o>hR z8XMKfXeqReMT|Hd^ruyDTN3ZCK!1b;)7OQ&x=jVLlxKa(*OwYFOO}XszKy$s)1XDN7W8XTYqwBA7uG+`uI@^yd zt${GdEK7@M~T=zz?1!Bo@R;uVD}qgUv7_d>296$kIu zMP;l-7e*&&>M%)kj1t26(h~1@#vO_zR!UXUmr{N8KfeE7S*$bPy0hS>DeF#N(8AM?RNgi;N^kK*bSB*5gj`=2^?890Ny~0C-AEbapq?P(HyB~u!X=r zST^dza7iJTuq6LJY23YKsbwtg)-d6A<@@@<%g^L*eY`C>_s6Q2JMX-Csd#e#lGS(i zuUlokK4Dp2>39zj)^tXcLQ$F6#8R+A*8_8!v(Vnoh%m=?c1BOTihR`Z$9lIJWFBk* zIr;QkhEBC`h?MXV>h3|LT*3{NIc{)FZ?Qa~)tCpi*ve3TdovJQbR$o5fO{AxPNCtT zrrnL^-)xNq33(ji8cGfuAG`LvQfIwo{@ZG0bAxwOM^8(|ctH zmu^i5(J939&XX;V?(2xNkbtcv2X4rA=+!41k6l&Iit*98~pXv+o#L4j!7>B{#!X>&aVKiiT-mtaU>e;loY0c zlHeWVZVPCmjhGRr%MeFEfdOicn4nOR7TPUL`|5hkbRI8F^ha|M*BwBFVnictQGK;2tafGzOc-1q|KcZjzQ2vbM1TU92{x~6T^0) zf8xsr=ubysLL1P3`z4Vml*#otQnpAb7hO_a-#a&oV>;80ypKD;&yN&XaI1=|zD1X? zFp=e5+bgO>+re;Zi~u^U*WRiwD+I7-*bg|p^%^-sV>*CUF@q?CGl4z7YMJ~1yGus}tI*>7+a$NO0SF1W)Z`q^OR-mJZDz4<3VA;m;QkG=+e+-ekF2uE4Pjx)zFg21`AUhAv-E?)-eMsrPD z+7WDzKJ5ZnK~@m^E~vbwBZ>+fI|Pr;Wa*(+)WjAigbn{9l6YYL&q;Omieyj*a+ zgH!)>rLAp)1)C95@0tsrpWz_*_g&4bSV#^v26@xy*3t-bV}4glVie81JCJG#V13G7 z=gL7-W3ImHj|;K$c74-l(_~V;Z30V9ZLLvqe&+Fh%tGo@CEsch>z`{r|Lauy%N+$y zwJo>k{A-&}IPQba>0VMi5cZw+XQ$TfeG*C`J|X9yPQPtQwuAEKp!>QpHbmHT_&BDf8t7vOh!hzjBTOP=;R)51 zNEm#Jt)4;dLpA9OrF{Jg(({j-|n zLyeC5yaaqnzb>+Q*ir-2eT1g-uz%{l)P3|bT;HJ%}BuQff8Dk>|5Z;G7 z9(%%iAo`=v<^)|Vq5$Z%#ob+<>`~b(J9P}kFpY=PQZy=u5AF;w5f(l1%a(t4&?K$a zP#?#}!&=hF>Je@*qeX0uE(d|K)Y*o_@epK9!1%#5FC4!hg6io@dQ2e;?cQx$?@HYZ z+*6F-&UT%oT;x!o}rU8)$X{pldJ)T9XFQhF={1P8;S zR$!}$r6Y+}JHFyaOy+@z?H_bWLn}{PCSwKYsbuXkFe?S$BnMu|9}cCAX5Q694Beii&%HVuF{Wl;L|FbG{(HE>TZ55!?|K#_!5v2jzr@$o2qsqws z?rrsZ$8nPIizDGq?}B+cGbF!lJl*4Gvd1W#hTbY!=WzR;D!l+Zha)d4`_A#sKSH;k z#A6|LKW;co&GnPuOFIN_Cw`s@;yuw4&(Ks$)1tsxlP5-QZ(BX!Nd*3R>4t6i9>npQXYDJwr zxA>5{Bcy@lTP5CxRKdxBt(i%L*f&^JHL>UGiTrl=DgfMLU>&f~^!?99z;PU5p*e$V zn;8A)SwD7Fv`1xkJ+ZJUr>)F_LysL(0Dq9R<$92fz&awo24#Nz-=%Kz^{lev)F`--5;cXZQ?YA~fo(!n0=s4U#2*zCjawc;_KWeUS;g<3!MhGKrPBccgU~ zJwnFY%<*IssOj^<#=3F^(Rs9MrR3)0#|h#16(8eotXD6&T@v4&lzR8he!#mQ`Zr3u zOO$3X&WZL^VE_T>*|VDE=Wz_5(uC>@(t)>pp2MEpd#6{lZ#eOy30rMfJ05CSz>ol867_JM*G`qgFVKMv{gEv5QL4cmP> z68EY7`sEjn{n*a8v2T-r%VGiQVNCJxj$ZU|0kXyxeGQ5Rf#EL1wl08QzWHZiwR%5s zz|^Op08J7X{8j{RG6ytU;OSE>aM8hpkuGlI{c&g*?yu{ePeh2oyz%;jXgca4Sn0P z-AAqFzTswkv%hgk^mpIR2Tt?8;c(kFR8>|*xn~v?-9HmHHaxb32)eZcwws)vFwWtUuYeehdzA8#Ew|EvvHFc_Wn2j)nK+pfZ_!I(ML$ z@673J=ShlA!XwOdx4h_BJSPENg^e4-z9Y|uzR1)Y@eyo$$R$dDMK?zJRMh!Z5v4pS zbc>8-0ONfaIUce=KxKY{Lkmc9gwepnStF#!h&GHH?^1-N$J;V8iaV$h@Epex_7(Wr z_s;I!T_xuR{E!7}Vaf9c@IBH1&P@0)pAy<~Et`=)QAaB%nl!P@PdRguN_YLuEADzp zhILD)NBCcaL9iT~fuV#P;ipF#xPyAeqN6?fJX2eNMcb8uF5O#HX#Bdz&$^Q5B>@Zl zV}*}f4E3gJ4b=4BQ_UW<7x<}O$;ux_eg~>%;9_@DbV1@u5DHH1d{hPTtP>|O0&DrXmj^Jom zwevOTmQK|%%q+KB8CFj3h^Fz!il z)Hf(HYi_G6Pz9|1oRmA2tmv@lkiuO~Hb z=({}X?8skP#5Qww=GVj{YtB!54a-4JRf7ER$CQv}FVE`q$CTERdc3KLPI_pGMc%$e z3ywrt+8YqLm)^uyW7}p%blDhi`&>nO0(6QVtLoc~zx@MdG8c{}KJbEzoEU<(tN`52 znOWcfP8qv>PS)Wst^Phb=|+n*u{8Rl!*9k^FpOdmU2yxrS#0uy{AZTocvnT*;02bd z&n3P;^|5abk|E*cX{HliYd~9G16W~S=*>b(3g^$;gTmqK#h(l0B1b6Dk+_$%T@?c} zpR0RKu}>-ECmZX&6JaC0aWW`rBjHDsQQ6l5QF9$G#?%lvcDM0WxF*&uH)0gxou7qm zZCn@LvXIErF08)E4cuB!KRHYH+;zu3t}UTw;?XOH^g=Mw14DGqta0bQ;L)a04) zsOT)R2(~Xk4pa!brgsS}j=um!vK0LX(cQhp-h4BZ&NcIy^5{7@CNVJP z-e603S8yo5_kN)Im+Za7x`3Z&!2K@=6b=H!{)iWuCrbX3n6wuiJtjR}gD5c2<*p)A zP>+LZAjjbQqCFG8B;+s8V5pjK%~Ptfzyq&fYM4v$b%0d-GBhGf$6n{T`;F-c4k~Bt zC=iL=16egW|v@bG5b%XsN_7XL^otUVJ+_2rsL^Hj<0I4t!N+)w5}HybemcrbWv zAfQ+5Vhmt8=ZgdZUga>P8&U7T75jI%zB|nae6+Z&m!DV49qpd3vgKo@Ok#hx+zta< z@}#M~hziex?q;cYQrFPuDWR>n4u5teelMyt(==rGO9DPLs~&WqFIc8>P~aO*b~CE2 zDeo@zephUmsTObHQI?EZ13SC(XJQbGys6ulyR^l*5WAdM$w~wv)h~%y#xrX~-a%;C ziw|Jj1_YY9&Jw!lAaW5d7$5n)s49G$N>}eZQZpvo(08(-7l$}<1Ggd28UJb{U{4Hf z$*V|S8@n%ONw?TNnjEYHM=^|CMhov$L zU;tQ3fPG0^T}j5GU3m4po*e7Uef-algYB%3@w;NkDt3W6e)AgZ1!kSX{0;A_4)TT1 zujjl6f!(`!37R_?(+<=Ou*$m-`Dj~sR}rn(4cDgbk4YHrx@~}!Y~sf?)#=_QLzPqo zGN&lZN#AY6SpY?9jPN#d8+I5p`2M-D z;;rjTsG%&9ZJ0!^WJ?Tdzn7qt`~FHk>lJUKM;{>KEB4e_5+`xWm&$9B>B!Gi3s z-}%7h@MKX0XVb0(AM1?sTvqlwC6&ATpqJp*rGn2y97XLc^fB8g&uBq#}^h325TW ztFK#i({vCI!8ve_t$7aUC^)t8%)o_Z(sjty6uT$h_d{;l}bG)9y8ZR-4ru zFIbkO=S;b^J%2QD?S}NkCq<+IrlZS&XDf9>oQPuS!$Iis&ESiw;7~ST9ohHxT=(^W z7YVY-s)!?y)EU^=6@ii*oWESr)Hh|=(Xo@`skEt8B1`j&lWe|V}mj_dt>eAz+UmC*k*Di+*;48fcU*?wKS7X9yLsh)M!IpTC zwO)3`lBvFZ|0k~S2L7sF=?ZZ=y8Q#~9WVjb(=!6}$NuQ%L1KQSEv#GFd+CVq0jDYg=4RS9+)&e>Xkmd<~9tu1CcIs2}L2BWsGB#n161 z47}O-rJXHDDU<)1C0s1dWwABxb3?IfM=2TP7-2XOPS3%`lvZXY%6*OD*xtyNh`rLo zo0Fp%T|atI`Hj9~frOa&h;eV!8kMyXg`mR>WfX^)fQiSU&Dql8hvo{1q#&w-O<0Ex z(kr2sT11*pX!F|Eeb!}LB|g%`>i$!hY%egZtgyIflvy%NDL9_?<1i41FLq`&uIs6i zGnsK%s6l1GeMwyt(0QvxHn0el5sNO+-d;i~z28=J@nI|O)_SzXwv%vFVEz=87wFF(!+dbl`}swj``a}FENtmS z=v-Gk@fq&7hOY@fgtr6ukf+M{mS1?fg2ZupOA$62yM(rOQp4z0mp3Os-CUn|0CaYl zbNAb(-}zy|r-lp(M|E2VRLN0>ex_#I3XK@kzf%)efIza1=pqTvKQaRz__XgSkuKUl zwgVZj(~fKu4cDP8F{>0HO*Pd|J8m`L%TG{Z9+xAZ;beHP$j&pPtI zmFB`XC_zruz3hP-__N;PJlf)RfsiS5CB}}))rlV&VkD;^`RHaj{0TckTV2RaQ+yDo z!_K+|UEec1S7)x$t1;f%h_%~7Sp7QBrZi5Nav@Ddjp;)k%-pw<}j>7WmnHVu=bsk`{)YM2|x2=2c^YwGP`hNB6uD*S5fBlyz$8(Z>*4}HacfIRfx2~kW zU||(4%EK;Pao%dZP~xH=wSH-&lMq8%hfKn8Y)T7Wl`a9czJP!Pa|p3!*y;6Yy|JoyJue!R!Po@-Xz1DEyEU@Q47pvAz*_Gysz4hheKX`C1M-I2 z#a3J*rgl9Ha4}PSILu)cCEP`#Bkp^v$)$B-!c7$`>0lZUx*A%X03g zKAG^V%L)3pr|FSQ_2>Tu_3B@2)G$P|GEgrFJ`nKTNuO5=JRtaNt0X~rGlQ)RLJgaj z{}Z$0--S>9AMp&pVaL!fSOF`I?gNd>&r?f;k7(x~w^_YRJfF)8LG;%hk7{53<>ll$ zAL&o2L+cdJ$}ABazNe!j+!sm$wE)(ZOygwo>=V9>m7>LY_l=CzW+JxhuI*e|P-SAH zvW&_J4&HKR?J_j?|Es$8SAVYn($-m!0&cD1+W^u%O#pr#3TB%uEpO81-LK9)AL6dKhJ@Fve%>y`lz8GSdIc}s`G8ho=2_U%ltN>idPkc=LWfX z@hwMBjJU%6F*k&H+a~&Vtx38FIa38OpBH~aTS4<~eNx%CyO|I$Qf4WmIwkiqE4==I zbb1ld4pxdYcx;>RW_+v2v3Z#0*(+zsQy;0Vc#>H)?p03b~hz(p_6ch!~`XDMQAsLDh)fhy!~c zu7f6Rwml5DpF0vIuq=Ex<~lmntj67XXLV7Li%wmQ%fXs5>BioV0 zCIAes1YC$Hs;j_2m?tnVAx!1S%zWjaB5L=MbUzcMx6lUfJqZsGM{*tdh*yfnimCP~ zHskVOC9nH`TOES~Rp2_TUg40Rkk2~-ZX_Mnhx3(LO`2f9r|@GMppI*Yrw6hkS_y*n zZ{~?+L@9VL^L0tULm#KYlzXgz)dz}QoC$U5;o+;x?|RqK@iDbh8tsZ4mnVJHJNBw3 z{9__WFDg+KMDG$PdZJP_gII&4S#?))UewUH?X|YvIx467<}M(G{M~NJxRb6Xizf#T z7Ztu!i2YD2JdD2HI~EeIJ$*YfT(NDnmC?L3>)DEo!4A`oZK}Z=jCX< z!G7D^^&6^X-M{}LIz=7>;=AI`rRW&0UPd5)lGieT8cx!rXTVf2Y(fLAUFzgB z&~Fc_WJW!n*u*9VCEq_Qor`}|-A`j?8acV;`mRd3^8kIOIIO5>UsYx_9AXdEJaneh z$6Bvx+Bvsuyws*FQlh-0SY#QY!=56V;*VoK|E|=My!Ndn*Zm}=>qCXVUxiHMU18(g zhbPWPQD^RD+H-dxXI;h!N_CG1qy)!1boq{((QklrWd#6eNzh#)2`oT-#}yiUm&URJ z!o$A`(=us<9qxtNIw(`n4C ztB^omgHx0M`m-FKDJ|hU1Ei53A`Z~%ZYBug+%U*f4x||Nko|gWKXB=Z{h~ zJBp@deRUzvGVkG~iQ7D-OkTN|=qTY)!Gt`8rzs?Ffj2~>veA4!;xfEgc}Y-9c6SDP376ILcuR-0njqeZrXZ3G$9@bKV|;|2W6qDmj285$%Jko zL+F5>04bg5X5ft$!Z1HXPLS!p(Mnzl?t-U5P>W$O^fsMW4E}N(?Q4kZ7&m5bp$OJV za9&4p^KIZlO0-^+ejwbes&X1BDzRow3*Df#zL&$Q9Y@$Ya$Ac6iav|Q9m(iPQ1W^t z0vUjCGhkkDPac)H2iS^AJjM@^VaqqA-{w)N1COdeX*Nm|?O9aJw9l+pt|ZgEl7uJ1 zeZ9`+o@gd>vQmN`&j)-+P?*=_3>b{>J!X>|o90pVsJhlck@v_FQwX(~Ld-<^{ER+- z4LSi;4d3IN0|3!bW(?QPQ2jQIQ5z*1G{H|PZEw+E1Zo4#iet{>$W=&Xx(3Nu*f7Iz z%(DuPkPBc3RVDioj5TH$h-T4@IQa4{yP#hQF@&nurChUajsgJJ@A$IqD=%}#ajhp2 z_1@FqDCDz&`e&d*lbPBRpr5;yioS{?kdLJ~3~%M1s_VT}wLeYg$g=Npe6Z3l-xA={ z^ZZ|+Q4kIs4;Vc{lb|tUapJ%k)?yTNp#UL)w?iM`hqdTvzH95D3=RLiRj*KG&o{?L|QWNSqw zTnYH*d;r|F9Q%YcKA3jG&F%r^nUR)oZ1f{!*HqUDm^iVj-%M_@8i}3m>}A{Jx?a$= zMmOMfuAT?UirY|dSVm=$k-xv0wlE(DhRkkk+QRn~fYMY{B)BWc)BT>B^EM;%+&SwH z!uuo@q>D9?0YDP1j;DCLn=U?X+K=JR2mR*x;Ql9ld*#MeZ4Pq1&9sKVOo28BR5IeE zJ^7@Fpj}u(W9EV^eLe}Q#m-1@$jCM&fr1SxUgg2YL}F^QVw<;+dX_xcEwFJHx{Vu> z!R{5A%G(BawYdWUjTV5t4jV?^4?EURkRCHCjzw*yJnP7_$FDHPf*})JO_xB?03U!gJ>bz|O27c}?gMt%u+v|#qzF-kZg$4(cuL-tr@w{tbRe**Rmq%8LZ<63* zc=REGD2E`^6Byws-N?c!AtSs1crkwAN~J+}uI`b3UA*OBfKI`afq7l*UF%nMSv!xV zzNDNWy~V)ZptHV)g2ZNC|v`##t7208I z%yG+wa~Ql0!hGFklc}qQi`V9wJdE4Jt%#>V<-m{Cs4@o5U~g0XOD9>cfb`-!=jqGy zL;1HxmtE+4hjzRX{Q{1T)!n2%iUdi;mr&~fsNfu>nQqve2NLh7XtXCsD5SK|lgY9Z8Dwg(_4CwXt)KxgOaD$IbHS?-qsFzWaqQG<=A>puffRghmeD^UQ-1)&^_ zG*O>AWGclO>&D4qWM2f9j!%3x+1Fr3{*~kFJnOWIZxJ&;#}KU3K*d@rNdvfg{7e_f zoz#Wtpi}0KG0$39+PwUo(_5sw-2o@*3`#)Fh6vu;i``d?+b~IYND@dC1J?(W-%X>- zxKbS-@09{kCsD_mH>ZPWh^4`3;T(=xgn=z}Rd_6<6i(tb3n|TJ2#vQ>*bI5jnnmaX zS?di;7}K@40Hx?j=4ZzbiUiCPc0#v+?rYW}Ds!JnxT> zdev>hBs1!P>s&;oS0Ehzc7R|2loo0^1x}G16PcMtb|YTWiD*0A6bfceURP5dum{pd z{$E>9x)H4~D~LK1>mTQKDWb|X0|l;E+kNQCLlu$g42y>^id?@C>U!If1DTAUxytGr zVWRBC6u~b5OvOoH4kN)haIh@%-Q|N>)8q%#mz!0EISbITFh;A%4Z^4|D0y}8Qdtq< zmLqPjw*^aS%q%)=-$H%&2+w1XY0Lxyd;&8za0&@#k`Zk!fr5-B!rynXsm{9{PB#u>n`m-l!jm9OzykZ)NE)KS zet;z~#y1kURnZ1X%EXJv9#F&B%e|p&%bujaGO>&Y7LH4{#}50#X!%n0>0bqu>@*1KyLch_D>p90&+C^C@z4hRmR-DsBb8F9*^mI!`B zzLpXVhKVLyltxIKTvd1tki^7iw~LpbFWQz_dT-6v%Bpj0O3+}ay=<8 z&JDdL*jw(^aKmi}63Uuid4QAT?5fo@dphCE$$ zu>keD8WHpy#EhgZ2lsvAh69Fqi3m8d0{J%Hq!5TV2t?A^N*&>VN`#R!lXXOL*;`{c z(}eYI*!U0J2Ti0k$V*%E<3lql2Zs$pa@&>2zATA(skdbzRZd-gN}sW9^M4@6<22?Y zkir$s0`L`>))5*r4>bbtnXd(Rbt;Q;ePUH}9E;EeB@mVekL$Dba@i5`t&sE#x&hJq z)TMmFnvGSlRruCZz_a&nfYH8sU+ZMrAO&Xk(!cWb5f(R^4MN&_bd9{|OfuZXi{`3+ zN&>$36>fu>>}QIy+1fCu!`NZ%_?-Y&zE482#oZ2>oCfi5@5dYyZCkgB_;*DhtO&FR>keBTL8YAt*#bZLlJxm&agSIZErtz=iW z%3YK`Or%@BGkQ*h4uh~A29yyBQx22BKnH*NJgM& z$qPodE%na21vE*)T0E+dLY2U2bgHSxIVgt6`R44)c)t|UnH0)a5sK~~GI$&$6WhRu ztq7h~X&-S@84P-Qa3Ad?sULz5W9c?+xH@tNVeBhNIr*2NHU;7Z;r)Ak01fxX#FC5= zhr2{4M1zaB>z}Pq57;23bUJZrpHh3Eix|~+pK-taTKwXcM!Sv-Ezm|etll;N*qdNm!22Lb8A{peor9EJ zCY6cpV2sj?|La0I>KjumFCy?L%*!wFQBy}-`tR0=AnD9o@)jU#NI~E3vXQK^3cMjNNU_k(8v(5PhtU;_Ru zufldR(%`RFw6oZ;7U(>N>_8A(pheQw>X_o;m)&ek9F?)syFSgOcIi;{Yp%M_&I9?* zJ|yL#TJ2f@TUgbMEL5)Z3VpNX2^wFf(UjQvQui=h`{_Y#8?X5aX7tRPaW~Cv^~SPe z0qHYb+uc_*qF<%IJS;cw*BHO50ks6CG9rF9tCI#hG1iX0%tWOIam@TY(=3b7^F~In zv(JPX?MX(k?!2GL){GLb&I`^ZC)S_Pj=ik<(pqaFobONSzlxo?!rz4ULsaPoKerKP zKnq1pU|v<#1nH>;wCAWrUcp{B-G#ZPXL^!d*GJrQVf{nuM9K1+`)O@R8CVTh@bw$3 zNE2W!ECZH>ePYMWLVO3b7c~)C@Xm4HJhm^f@b0_Vv*e#RD3Ug9a!^8f*j<- z5EWQ((F|VkdBmA-26fDEO9soT^$c8(a9V0y!?atLT6ccw$s!BfS?blYDN}*u4+I{t zlIXY1+o!uu!SgfPAkXz=@qVAxRK&>xGgZ9+sf0cBbv%8`l}^-y)$ZK`bB?c4pX`8* z3b~?e#mpI8zrp0mMbEFLyjE}{3mmhUR#VTbcnX^^ShU%6rVW~}5?mRdrEKEjBY#&)I)RBlUnwA%BQa}66-@7Nll)S>L3RUDCL{>nky|IN`Dx9zGM&RmNK=K~GgGdSjb zO8xmPm^AQ1gi7hCj5k|9cqSWFc{_a>QDSX7zCBF=w}!aPk427Kly{b@>L_+Yu1=rw z>LcmzIO$yF9--z~$2k=6qRyLpA2E>5C#lwW^ zF5yPn%nuQOcV@9h08$ez}^VBhAT7qkEmw-?|3FS2V}-Fz3zfl>m#oHYO`@0qYfjGZ2f?>B%jr8v9>Q6$!D^f|p<*kDV*6v~bpLhTbjq>RA ze^sFKrm__~>NHw#BTsczM-!a6VYyX-+e1KBH!S{NR5bq)Qo1%c)w~GbH{muyGh_kC zLQlZHOX)301`sr>r$ zYhwy-klx_xn+_jZOKa(kKA~xG%Zl5<2~aAl-XZ@a%_wI}>a`uoI={y2fvfKe|I!=? z1;I<;!+XGo0BwdCw1=>o28I{a=wun;s7e3|VQ&}S6X*sPJ=@wqLmU7;F&@gcN*j{Y zI;AGr1Utl-ZSM6Bzr^!wHB;rzWhoSPbj3@afNW2jpYE^i3AGulwe`hU4=wneYyEm; zsJ8sbu*&$}lLLklS9Gxpe7^Rrw28TwykT#@igM2sEB{TIR)vQxJvL#skyq=wZ0NFNe!}v5qg6Lc_;WnjgIg`)L z38-;?=S}aK<-9Y@%w~N)|L6_zWN4@{y$KKB3N!Bt^r830H!C5PR=IqyfuD!Nz)Y~; z)}`K4{ZyO1ewIfE7p8AxgM^m&r;&OW=gR_g^PGJf_cnUFKdyTMx`(u)4rOYEji0&Z z@((ckS41M^@K_se14)T!jU3<{0x2s)p=kvN6FZ?xKH6xcd@FAQw>K^rBQgD=h_U0y zutQ~`PnL~$h{nyaWA3*#ZqbSPpwO6(%8}Y=HCb(hQFrFP2Q+s$m$k1xenx=tWJmZD zsUR5as%)?282jX`!002hc^ zBv?Dbv*Q-cDsVxq=-80yah^?&1hcX=`k}cy8+!vMKc@?tP*{HIIyRw-$?iH;KBzHQ z(x`qXLTDXcq$in&7Mo=iPVL9UW8mMkI_Bdc>ZcCOs5~lmfd9)BaNMnf!fL@~7A*l+ z>tA}&Y}60_$`rl!En8q&;1ZT8=EA|FgK)}IF5z?y!|$OU1ku5%vK13US!t%`XqL;wM$@F56yU}d`?a;UhLjjUeR^u#+Kj2sX2woz&`LVeU%vK= zd(^s2yDvIcDqd(Ppa(+mG0F`$#V5!;0NnTpX({*EK*UFDc2pA8Y}6bU(NS0Qa#&Je&kTi*# zkO98wJ22O~lIe@ti0Cak!6P?jsAO5!FelqJSOws@0P)9$aDr^pc$WAJyH~~0X+3X( z(bBvN+${JXlPdVD&7c1_Tn9vuz|e|{&7cq#{hTEJ;<9>sGXESnw04ybl>DmID<>vfd zjt?-aBcQFLnt`|z4R~q<}bGpF8hj^jp1X6%Y}`Q=MOO9sfSQYiJWN)gnPT}ojnrw zXD-dhG#&8W*mjUhi=tNQ*-)gA-Zo=>!-=GFr{xY2ukREeDE4YdDR}@~h!*gIp|i<+ zVDc?ba2tWhq!vc7j$zNmwNnr+JYebN;@-ybCTrgW@53KK!V6s7YPmqND5`E23jLKe zyH@<|w3}R_t8+VH&0Lf#;=^dCh*2X{00o-%*3{c|qTa+)tAI`0=`1>cfr1b^xv z#Hg7(3fB#91-Ov6+*Wb*QD3L6g%^2Cm9YYRzRl*QH>St$x=l8qF{D($pG__t^xY=% zjVu|K-{xine~ji}RwHqTxSoK5Uf_*x0u)|T!-sFD^VGP~F{Mr&5ryD(H4pHCMLrGo zg&wBHA<8ky?YyjzexRQ9LcUa$j&AiQtq0%uIK&*K$%du3<3tw73Q)^o95Y4VH_J?8 zxLI*ZY0-h8O7rkjT>5A;9(bbPA~~bYtHF)(>79=WC4ONa<}_f7$axk|p$70sYecxC znO5$SI|#CV;(K_=9YVI#TE8J#{2hYhHC(BWxxSv*6jU)R*^h|hMzbPj->%72Gllit zCR`m4jx2X}#;pB3;>3uejwSgP0=_#nxb9BAh;YR};5h$D%OVPFgP9MezF@DJ$r6uX zszAHGIqPYcU>$}h%$>S^I!Ga`P0xue>9-!{CwCJZ2S|CJA zNg!XzO9wYBLI>!U0!4FFLY~PjoU}hEQTZ0nTR$?3g;?^)Y36NgX9)|?#K&42UhTA2 z*NIXMIMCl@S+uo=!;rNy+Zj_L;_`D1|f z?tjP46=x62w03tVVPuKxgt?n*Zn}LAx$2+lu~JQX#PlS~+9M-K71MUrhqx2gE8T)N z-qlJHoHmS0nV}U={Ur|8KN2zj-*~OIpclt1H?}(i3?ATNHMZXd^G=sjW&wJ+lG^Yj z%a@k5s<|9q>s>Ia+(fQ6xG<)yU$^t&tNpmiIKoFq;AK;(c??u~P?+~1XJ0CV1kgtK zXlbeayX240a*A4P6w}wddXZGR_jcWLx!$a%?a7=Fkm=k0ZTTKJmXmN_j-Xorp906t zLi2MnuCBUFleV;aTAKOm<#w1! z!X2I25zH})>^senrMX6UDcp7Il6H^7ajrw#yefn7PMlUa1;h!)VVpn-kFEjBwX&S+ zMg}rb621JbVMUIx&3N0rFk_xTm`mK}Q4xmeq#0)gM6ms^M{VVw3I+q-8!+OuXbJ*33MIs7m=S_>P#pdbMwx*bA!3euyD2vRLxd&zODR7;}wAy zL!g1zqe$G310tjHz3ko&0yFT`(s)!V^^pC)n)32X3TD%{E!Ep5j|W zvZ5v?o_o`zh`vmQ7=7$YHIP4W_VfaH?(f?wITHc&P*fU}_@bPbDa~T}hD0Oq^Apl* zx|lkLT{*MhA4Ev0%P}mo<%JFc(UGuYz3ub5h3OGRE;LXr^_*93{WAGQSkK<{m2kR| zZ_Wc-C;9;nw87V^_Eg50Z6#WBvt~lMw*D0BVN+55v9P^>3wft-Ibbm6kaC5CjfmcS69vA{xuhSfM`K%$xW zV7HWUCr-M_`EJ-)D?RcIJ0pf1Z)#ZVbJceA%!Am&vaZh$1s?kJAys_N>z_EEZGZlA zvyreEz!J1a^D~YfmxK8y&_QqpkR;v4=;c#k4jpbr{S>zJJ)b_!X`=*qz$)t`63t%a zbw1I~04F)6aN)jNRw`kDLVd+5YO@SMHt4lPV1vC+x zB;cCEiJ@tG@p5i&oj)&)w1?kOTp^k6v!YWCgW!wI{Sq+kJk<61wG~T4l_2W|Bv}Bs zNBN-7>gidLrsmuW1b`i4{63kuQf7pFqZW6rewR3V!+HLeiX?Wq|7&#YS>7rdpO|@b z;&ZbmcOUIW8vTQRZKc@sv9)x<%PSBwmjGWX5-1!q%0<`U`2k^`yp_T`rqne{=L`WzV*!;JLgUE4V}xp;BJr>(77 ze86AOJ+9woA6jo5YK*x*X!ozyq5Vg7YyVHbp71dg?2t!|QEjMnqc9s=al4TEN562# zcw3C;_Eq&s%^P~OSMGOw*?Hq2kUTrP+)4qxoq>8VVArHQ5};PpW>@P1nM z2I%tYtYb`Fna->AaFNY@?z`6n-dZ!RvThlgWAHZ(k5K3ZgJ3KO^3Ypa*$yr+Fb}q|8!NE65-6^}r_%iME zE9Vn6&)FMD@La(}dkXOYIF65#RtwCy9`o%CWNX$e?GOHliJAKN_GLtI&b(~crxxFh zQu=g9b?2~r)?&wYKj&F_`Zy3TCA8P2(XC{?pZK;wolxsf(6f$q?wT{Jw~IE4LDyVn zs%|3B&?JcXs=g)+SN%(??4hcut9dnku>Q$RG)V3I2{*wd?sfLLhg(t))TRF+QkuF9 zoRDf@3QkQ~07ywzfv71;5gmaf&-QP=FpO>^Dih?x+F0K>&Nag32%Tw$Ou4Dbs;32B zTYr`5OeU@`NO3HxkFvTz0riw(2*?rfLiH5(Jx*&8{rBZJit z^5vCMJ$RuWNL#))dWzQg_(}8N>`eLV^Ldk-t+(JOqng9O;5l{DOG52YzZIyr3=gdd~h8+PYxzaPS?w%&dgHG4`NiI7K?D;Y?{s-cu!2Jn593&$SmBc=!&M`*{dUW1_K5@pId_kpJz6&+ zcg^EPn)pp(-)=$|E`b62V+C@~?1pipr$BYW1fCswe&>D@;?8Cy(!_IKvd=e&2l~3h zjIO-(eJ|?5i*h9PKHW8KgHNzTOU@e2)C&Nt@al!J1%2)gvS@_n2&Ff&kgYY8Oo zy%K5!vN_RpM6V|6=>5rTOe|_J(&k*&$j-P3oNKykZPk-Y56}A^_FkuW-KAU6#?J+6 z*??meW>nZeBXR@hqbysz;K7MhTZMK#MJtaK z3kMO*@D>HZ&7VD06h!Ak+dJ}F@EM*{YUk1tt5goZK`|gcl7Ic4_Iv$yh<^-maVk4| z_?%Zk)2_@*FT|3atnULO2ho{7B>%da_%k{ORZix8NOrRc^<&YN%!FbvYboP~`qb7n zIrewTNP~H%0|r;_=UE@)Yb#VcE83ck9IHwBWxEJk=lhHVdK4Mej25{dl0Mo`5h2M* zV2x+v*ia`fjTfSv{j4$}>hUN_Zzi1~aPeRGwVCZH%{#DSQH#VzH_%eKQdNyUcU`4J&pY8r{3uiFZOEr zcE$cxd3!SE04?+_K@rDf$siiWEswE{nyZagGQU@9KJetfEkF;%$Bn0^e}ziDPa!Hk3ygCeBi|ym?5bW zHT!G*lukI>XxhpR67j`h=pL<`eswX;%2inx8}p1UtZ}ZWB3J==95W3P)ud8dFfg7l z>Nhu&_%!bx2a2It7C89 zh{(JjqJj1%_;Tnhv9p`^Gp^AYMPKEq(Hs)YHj(IgTZe zJ9^A>0TNSA^-(i8Pn$h*_uK;l%_^^O)rMYwxbyAyYaaTv_^!++2_69$qXUhZj;Z-J zTc1c$CmL}Xv*Fy0fP~`1K)QLk+@8gR1^)QCEA%P!BNW#xAKfw`GBm3 z!F9QX-ikDJbrSnEF5~poZ7bgV2adx2*J|(md;dS$D-J5b4VTUZ=M|3h$|U(&V8E{& z0-`lO!#0%K96Q3^8f|*tQ0Lz71%6bUZMxUaNc#$ZnHF@@n5^mTrsu-$yb`I_B-$`uD{==BYDASJaflg%w+!G6{fCmlJG_P(R(rH}87r$-a18?4h&TSn^}7*Ik*fhk*HkWdA)%^Z6mdH;^C;U4Dp2 zlnV8x1${W?3+Owpl1@W=mUBP{5UzC!G_N-NEx=gs5Y>VeuG6S_?|pWmIZrfXGsVHr@Lu+`XMvCe<)V2L5RmktROvYea17F>7>&XUtu zLn+W*_Z1zv8J1^to^Lm#L%Xo2LbTN$&h3;uK^0ccFgC-l;?JdQuS|WP_TpUXthP{+ z#{3vCwv*Ha!PawJp54fp&Qo39g*OscA-HYKKm=uMXazA>5@cc!gtr zbl#wgW3pvNV?%>H1_fLA>luei8-p%)ag*8`ysdYMOn3}@xap_U?A(>!_*=2)l4sxl ztNQrY=2`s9*Dpga5a9D4zIK5KP)=BE(5HN>QJ6#>`*hc;kDdo_Sc);&VI!S48-G^FgA`w5^+q)t$c`O3TE778|u7v|b98j#V9$G-Zq zIP(>AA!PodKW;MXZsHxHgKYCD8pLk|H~q z!+*fsU1|+n5k)7&E|mI~ zrh+Q>>4&99i#-AWkV@*pP(|Bu^b35R5sYskD2Juo=@OE)f%RZnkL<3^XKWWg>{DWaN0DLMAs$A|(S?fmx!r> zAma3yTs(rs&Xiv|FKvjA!d2h-Qhefp9HwhNk?xsk?cS6*kmL7k+j6_y-(qgB5bFP( zRU<$)!z22}=!slD2rozP{^WlSCn5@j$xy;a0N||l9)C@N?P>G}9cLtbDQ}B73hzp& z*4{HT?|{*VDk0K`KZvUe-|P@`(34nxTR` zFZuzWLih+ag=pB@2r?>hlCR}*rP-5LG;@z{!k8JYdHw~^)X8+EqhIO+0*rB^6C`03 z=k=F54EEI}nT76uT~Yi`>k=yjJv8R)>2(7*CSY7qn{zjU-O8xS>MC8A@eFKM(jcuy zwc&Y7m?V4WD^Za&Q3W*H5cQ(KUP^)oalGfC@VOuG8o!l77q0$o5%3>IAD5-%L=&i< z<{$APuRi}6=>@)Cb#R{4voTg8pf0SQt+%+x+ernYx>~ICGf4j0EqVBG>PYC4$H@0= zsHJ&ee2M`F0zsyS)^b-oxwzTI5`uvoE`3=el2AIIT19(tZT#o|)Zo0zTwh<)5DQSk zqxAnByw?8=&-fvtk3&rDkx3wEM;p~Yd8WOmhy?1c%jPrJcC36bz{?jF`LFb7t=j)_ zUtv_0U0^K-7)}OyfdOBMNCamq5H3yswpl}|QFx}$Qt>&^>=ynZ7tO@0nK!3? z(_ZmRZu7=Wpo;wBZ?*Op0Nm=!*2E*aakFM68uxqndT3wO%ZIN@b=-Mzi2Tl!{bJHl zXYSw9UVh(-bIDje70m?>4V31+%Wquu6ewYNRRiZj({ypXKw;5Oa#XW>mpA{gU~Ro+ zy4UH6?)4Yw4;vGntyoSQQ2)2hBmEzC^2b^mwfgoDP-%T(v7u!WG2M$+_qvGmMnmi@ z-?L+OgWExI_l;ZU=C+*-+-OjB@7yc>KNgz;`yS28sTU|Qc*TS7$*(fdwWMKs-d%3- z88#oZ+r@I53Io3!9?5#te%DiK2lm+h|2q8tvcqxsejB9NllA=|&N+!6^*s@@);b8N z7}P&o?6cfDulv4Mc-L&-v4pMtB(1TzonWV2s={6~blnHQ5DoXo2_q{)7X6N$PS=+^>5m2D+F(7%)u-R5P-~~ zBQIY)Jddp6pVIvG_VPJUVU1p^u-{6jMeg0h_dt*HS8L2 zcaN>=YyIY;3d?xb*}tCtI^VN!G&jM_Kw##@R5BhoAf989a@Z<*J~;Vh$(nYka6GG> z7N>mYLmlOTU_X599(2VZgcoAuw&P93yk5nYvsDND{8kCl@dh^zcCE9To7sO_lYMQk z&;QtD|CT?&2E`)=X*S~oRN@NXVJb~4TN^Z9);=gHHuX{5-8{;-B8nw{(59prH9d{p za{PCL;-`Bs|2+g)eaVmqD*CIg3d^XaF<(vR-?nj_2`<}`WeGk|?WdWckfaY4zSl>kfK z_wm_QW8k(TZ+cD0jA%Yd)0AyB#KtyITHDUoqy|QW7EUj&=`U<*#&bUrrjtOxW<%-{ z>o6Le-3C*fr=&5QK$3Baha%h()(DR+!0$ z$*@9FO*%EwPb9VEsSGUa`BqwWt>4FF_QRddnv!?c*%ee3era}gVFmy_GARP(EdFMw z4G%=0NGf4pod%LWMdHqr#lo_9i6ePlE~B>Wr`VZ1Da4^D>E;^qhje7IBZrW@@OgdQ zlRDU_sCGRw3o~u=H=rcP_iqIHH*s$A2n`-%E432TCQHi@6*f6i;0RB5UNyg2@(|4B zqDz613xHx@v&-sXJ>9J6icd%Cv$bi*<3t5|kPl%LtSY0LGONKI;rVjkpU_G&TTQg* z_O?l#3ym!248Dis;(?yMYsd?gy$dwiw_{Gz4Hhr;+d>Pcj6&_KHRUZepvG@d*1SON z0DmJTiUQkF;y`fh7Fk$W8T~`#A}(gQ#y89EpnC$kQ?NGrFpNu582;t`A*>mOK-3ys zYyP39*yn_AyZIj=*c?I3*UrwwMIlJmlwmd}7% z4lM!l)(t$yH@*S*bcu9Y!bSh{3BNuGxDL8*^@8znYO8AUj8;Rroxc!Ib5|AguV**( zgN1wN-15HKb^IFS6!dQqiFc8HNI~ZuZTO%45ni<8$-S`JQ{=LivHWdX#Bf31HKA$ zI()ipeI+Q@p-0VCL5G;WPUO~rML>Ce3Cn@gQh?*p28jY01QsTrF0k06TKg%m@kXQqy>&Yaq%si)PncDWD_r zY;2p~VX3n|W~$ZKUEF-?d4zMBQ~E{O6DqewaAT_zQ5_mH1`v_81naOrAK>E!HY?Ol zxJIZRiA7bAK-PQ;x*Z6AM#J50kWy^~;UHWW9pHCA?A(YsmFobsohR*la(c-EYo<`m zy|_E#E+U@**f95CH@)6*m3~X^j7aqhbji&b*vchER)#&8wpIGlqgt0=IhF21zkk?J`>oE_244v~nK`#aR2}r{t|N?gqvRqU zfZtm`J);QPkAq(4`{--JLIkef6IC)>`9tKR`ZZkr5p4wcq4?vFCw9qw)sEfKaO$5M+Td7Dl`{JBvHwz8*}3dzXD?YIp6EMFjT()=9t9vrEi_ZbHou~~ z_>7S>p${vhX1}Lxx|&@pU(Xd38!(_ zU}wNuSq|_+AXXp~kDk;14#4dw@NX>&dJr>@ra-%}o3tiup8ct-R~jAY_aUu%G(_6k zdw*(T7ygEmTGhSDhR_&r2phQSThxT()_Re)!82~_sZZ;SlY{fau1k#iZAz5bBs0~G zO1>5XE&=N}`i!Cg`KpE=aSo6t4bv1WKzJ@mcp*CPPm&`_L^!lG7(BAw%vd{4#1Eo6 z=ArAhrMg%aOa^vuX|q^E2X&Bd0)1pJDy0uZxFK;Q@aP~=4(lD$J~mXg;A*6Mp}SRW z2Yfh-k!#AUbbbQoD?}`(n93_2QT(wiW!K`?Po( z5~IZX?T1MIq_7bOKY_J=i2Sk+YTpJr_kB?WJy$E`;x__j3RQ(W;zbuB3rG(7lXw4T zJ9s~4tWZH<0q9`=?B(a9n=ORD(>^aj{=EV{qWBMyS6~9Qe}-TbY8j$cJ^v|0mkzBJ zgx>d#U1W>SB(PoOc`5KrCLGX|)oP*{N>W1Vi=MXTzmQJ#g}=66rKK2(4T^nigty)r z*rl`lkN^aN|NKm`dk6Ms5v}I*hbCSYRIfDK6YL!R`#HYtWtEHf)@m4nhOobH0_Z%)7h{`NSrtm}aZYRo{{CBmD zTMplW%A`HSOaK7~Lm>Z^cN)p(hO-uDDSUTinj;fko4msDjj8JY!`_?6#q{_6jsBnpE{l4;OJ%ZzqPQmHhlS)zTJX_-#5 zoPO^d?+KCk8ZTHenWm#7lpTx>AsX)3ovYR?-6 z|G;PQmh}FYy9Ex3O&;4?`~?O6`kAt|xR<4ylSIJZgyI7z6=cwV9eM&3+DOh_vca}| zv#4S-;3nz{tFMs`(E1e|^516nPqOBU)r|#NFFXqx>q9d8{aY7KDzr}K)cFTqT$`kz zW3C&oiXmV5PaI#Ig>E459B^&w=yE`08Z0_4{^eyOfY+pzPsMi4B!6B_%XXub<#YwH zT?C6uxUx~V$L?_imsQThMtPGoeY6VaT3LN24xPW%ZmvXjv7MZW?Q+o%(gy)Z9Sj%w z^co&_#48}d6@(o93hM6V6%#ljX1Dj6E7zM#$xwXz? zyOwV75bKg4o^}*@Ju+4bEfCA5ee>$vhFs^4$RY}*LitA>e$;@BN@m@iD;&wXw!jXb zXZ_6Z^!uvC3F|?wGZ9qcw?M9I5qr@xv700ZoOGTtSue>%*W!K+SSI9RK7r$GSc=u> z&dEb>ZqB}8#>GY@7sUGxotZl?Vh~Z-mF#xQ-0l`jq(@7h#<;?5Ic0!ox@Ry`WJ0c` zFWhYLm?VQ(uo;mDp0$??d}yA9zs7n?UlSkEUHeM`U6ZfaOfr8a_Z%FJw0?WxxuaCy z!9Q<~|BJ{I{ws@m|EsR0IR$Drp&x;9JVVxSr^s&vN#)xt-a)Tz2uKagS%73W26$DI zcksU4X)TlB4Lm=)xN^0%8tV~ab7hz@yJ~e)*!~Y|HMY3fA)(Z$B zIEZNtyA!?^lu@K$*ZQv+WrYt@MFettEmwjqbuoFJAKV@v?jrX&qd_;8;K<9qn?EUS zg@jY_v=7n|#09p?=yLgudhlW=rjzHE-ov##5Zl0|lpMgnxP#-8zLt_xKa|QR2%aL@ zS=5;UA-N`byk7;A1bze~_=2AokkoS`8~&lfDhk^evb*E>rmN_-m&&FVsQ_A~_qx<| zl#E_5QW&7<1E3DM7T{HI^1;8Xe5>}O9&AR z$zb5bNEwakB4Qf@S1eiO>5t1PsAfr_4J@ph9eeC7eYMymr`5aISoc=#inxugDNbJP z(Z25=vHB)z8}bjgamj~5m4o3!qSdu2VHb#%}G22e$My$WWZ|$O8 zr|SpY?KixhV8LriS=#^@yS99EX+M;IK|VYC?i};FlYDt4!Nd(;N3YWnjmw{?DDPb1 zCRSnr5CDqtp#yN66#_EMnMp(11ZD@k6qH6_ahfVBLj~l{c(y*_%Z)junV13sCeXbJB&Sw=wR>Zf>cUI`C2SU%jLwIFd zE$j6T(17~VuBa@g^Wh2#Qwk&<>gBwYHqcueH=OnD*j8)TT)ZQb63KMQ(@pjd_0=^o zZ1#N+TrJn9q!Kf02wFl{CpUne47VX=KQ=V0BVbGr0&c2~*3*q5Q90t0Kz$Vzz~tfD zg~vLXT|9T4cv0~H@wBOIci5v&)4(TUwFaUViNF;(n{&6Nb7-=5unkKZ#frBgQoPzy z_*YaG(Kbbbd9|G3NG}dc8iC658K*(-;-=P%-p+Mul|P1lR3!8(gZ$3x!^L7wS9xW*>OlgFFqI?)~e8c~7^D>@MPHFS! z*p{)FGJyIqourHMR)`Cs?Mq*s90pwR(e8mpG;tw{AF zTq~qzdg~lIsRn|(^f^`zEjKN9OWkQ27ieyJtT7UD$75~m=rFLnb9N$rG;=Ov7NXA? zslxp#CNvi7g9&G~Kx-6DR(?Y?_p{IsHN$xDj3P`Z|qSq08xUktT)~4UQ&h&8cjoOQT7dO;?P<@DznzzDz+4_^m zqSo%exp*gJ#9-5uI;mXz*ByeVt$9Pc2drE4oMp-jk|Q1muD;XV-%Yso5Uag5E&#J& ztB=@9e7?+>JW!7SlNhr02f2ho`LrbDnmV?nK zk!&Ga`Lk~y1AgUC^)Ji+c3I%NX69YwN{g393oZjL*2QD!+$pXK)R;?XCkomd&Sk@# z`G^&pmP=3M*3U-NE0e9pr!n!=B^3|XHf&<#72~r4>A&!nz5(%jSjDo-DzzdvxU`gk z^BZ9@Dj2Ua#EVk}$9Szn1AG~FUHAlGYn;c|w`-;)aMZ)|WXv1V%{e$3Pa)yDSTU_X zDf!IPs@h|GkUoo{j;GX}Q3mr#UCsHkTa@B5P6Ic?n;1b1vFMF1kul6UHI0!h(GyOB z$axBw7}5+u9?xO~c<((3mWSIjjiH1%a@8v=96H<4C13!Ro@i@n2)BM2VJBmxz1L8- z2vCV2ZU;uk>$XCOu&`K^d>Ohof&@*9R|2u$?<0B0;Jt>?>&XyjfW`C0Nmf@2f&d5s zK32HSOL*eRfB5T>BZ$T{rkaLm_lTEb1sjkAp1I&5So|Te22Y`EME08pMR5eoL6xf9 z%nGP|rz76rSigvcMDJ=EMvVfpjrZA;G>pt25&C!m{?=c#eM5*x3L3o`d8c0Agp#!p zRaSX@yYyi&`9+#966i@1eHlzp+!)Ntv=ciLF#*rfCUf4t2hVRa8j9t)*u|aSkepH- zCgThFZIKhXy!vlQ+oU+TQ^p@->XbGer12bC7Wh^$O4wyvb?as2b^rRD#(l zds0eUU-VCokv}mOOmuja<7S=wlA~79&`I!UMK$<=agNs&y^EbaW8ZcK_F$%>QI!$^ zRCi5d8dtz>H1S3XARjvu|C3jQ-UWU1-NFAyzF(~06)#=^7CEO{63s@uyQVSiw&=Vt zkr7D?uxe>v2V<7~e7 zRWft#NJDyYPJSW!*hGuuz{@CQ&36KE@T78wxHd##Z^6x9(`o&?^wkn?Z5z7z^r-nj z-(u>Z*4G28ZrN)M4MW%Bk?k}tB?{Fh%|~>&Gk-iHeS7q9gq7jD$M`Cp7)nRW?IEf`>v75nNr)c*2*g4H zxj=zF=lWQz6Ick>O=DJ>q@&CRMz7XWm>og`ghZ(#FOYb8lz$Htl?4p(CHfKgGG%$H z2n-J(t7V8#DgHq~S-Rf(45G%j*I{rXE7n#!cgpbBbPYVvwRu&3QjNzMFc<4ShicYz z$NNpD4!}cHWi*uOhDtL5F)8!!R@~KtKzt2|!5gWb7gr=!qtV8iDsABKhsN;|w=2ai zlo7yR1LQ=6?vO-DUMl?A@z6Bporr+$FcY-I zPGfd?>r7*$hG1lf1QK2gNhc|AnaR3Ceym6R(ea-?Tif#VuUm~E7o84W@b6O?xH3@p zhH7&p#hga+7pIo=@ z>sP#Rw3kP}mo45?FQLp#Mv%()pgu?YoT{otkc+ zq-|Ks%(BGa(ObR2_||>LOSVDkADUGz$Je^<)Sf8K>|PFUA$WiR!DxYEKaz3yOK;(~{)Q;6Ck+k+e|z ztBs}J=W3`#Z_m)pcV69Ca5_vu?Y&0UC}2CZLM^s2E`jFeAFFXZXQD_0mSRk7m81`d9RJ(_8ph=j zL{(jUzel|6BHD=Ms{%F`Y-J%T_Cf$pcIa04!zVDz6dGCura?m!$Hk71u&e_a5zKAL zT=z%eTj=&_`%^7GF81I5!`fd*Bj)CvRSWmaoBf>zy`e<2M)5|wCK{n}(No(R`cfVg zW#kjM=^nryGDuiuP};H!o(f9-5xLIh&h&^fMe5KGdUBI_e&L&_#vRc`c5B}Tv@A{T zo_nvnPU&SrkeYwntV!v=lX6_UgT7==ZoL?r2d2d3jTWKx6s6QpV1HOktycPrA^u6A zp{uOjkR|*eXo51IyjER={>nE5sR;)=s_VX7x{~#2ROteI>tx|DeQW^tPi6AE8KT8M z2(BY*6oM0>vV6Ij>zsdH&x_}7ga=G;NHfIZWfwAp@|UTuSE_0`rCcEU#C04*Re6e{| zlXHM%aL$AP?t=Sq$fXfij}^~@DaoRGup9qWcOzM)3&vPMJ5K+1E>5dhz_ zqPr5$*?`&eQznc>a9g@q9fSv!{F(xdo#IiT7ujH}638ui5Jk(3wt%l=OD81JaH$IP zV<{NkTrb*+ZHu4A90TY*3+ek0egD}^>gQjbYSS!(JRRHXb_iT0sd};|!`)?QIoqsq zpj~-j!;ie;*Ts1V%vBu99@=y~pu6C0!96*5_3%c!(|c~|+aBI2Jz5Gd6$F+=K|kYo zN41$!6%fy?9#QSN7BG!D*Z_Mo+KGD$--6|9a`2(_MxkxA(GoUgnFqcJHOa-x1tjb# zL|gbK)NVxP-O(;o4!g*KoC}wlz-$Tx>I>o}!2-C!L`p2(0QInJkn8*6g8fei*qx)_}*n&BR-9gLNRtnZ{Uw zd}0D0{F&m3k#sy^%w=${0Uku6P+dqtS5TrT;`SpTJ!vM=fg7MN;3nV2;8k}sFGK$B zfSMRN3ALr5*HI7xMyhkcpWD8`<%`jeta1DVBsPHLp{e{w*bb&9FB;T71ZM#DhBHAX z;KK}`cwHXufA^ZbKK`fh@Cx`3yJB1JGOO`8FS1IzU716_9+-`l5*taL6mLiiP%a0p zO{BeCbNqY{he>dvOCzA&u1VK`bl|yXYY>_8ETE`TWes1x^0tD^S!RCTv(C9!3y$Vs zLah1t-#~Xj_CTeJMj=aeILy3}?WS}t@@eFmNQQiakdOq8EVlwVhQR;-FNoS)qV@m2_@sz=())Zf% zV?k!0($!3joYn7{ZR{8~l}ENI{qSg$WCYcSAL<6A_>^a$=jM5Mo!E9hYx4rjpcwzj zkz@&W&Xufi58ZFXl|duY!utuyw}SeIK;FC5lL20QebAV(<1lX0accf}o$WAVYQ)7Y zE2JD9fL9aJPKm50;=jk)50^kTf%%!uXvS3Xn6XvXH0BL_k9MfpkO5R($HncrA3TX2 z(-US}@J-PU@uTy{`o<(!o#r6(3Ft#sogSeOOfM)2a>;5WWTF->9?vDf2vR{|? z9GKQ~lAF*(Hae&En>&}r;sU3_(GfLFn612G)04#PmWPL`S`5ijQ%YkNjtsGk z&^Fc6%53Yo-3x!63)X7c++$b(BMRfRq7O}$^RjkOB^&NHA%xm7vwQuF(I#|`#H&v4 zTETm-vIy? z$lul;>jgS4jwNT^8o^mlRj@OgdW1L&(xQS<-;P~8vj?u6$?vYv8-`JkL^x?U3SYeu z=&)2fCXi_s**<_q0{qjK)uJgTbK$8C0g-6+y#h3Yi)B0(&xa=ZDd^25qxD_>VhZ6J zG}wqP0Yb_}emEvZ@{3SBtpX?TfagtPCVWU2s5QJge7!cJF(OW73GRThw`)00_)?4m zrBw0aF7yaU13x_Zikt^^=z(;a{lYC-li0BJ?9(3OCj z(*j^6NpAugRO7jE2$f&y>*2DGyf=JxF$m~*R{^)(5^6xSnMgrUAXZ~A?aUC zm7$q(3xCIn#<^Pf#HsO8t8j`Ei{llOK>B4TOBQA?`v!;&pccBq9 zW&IP95jnl{!pK1eZ__U%n$(n@U&=>43ragh4U0*wp-@kKRxQx%INu)?%8!kHlthvck@R$?3@Ygk(iyq;okRl>S9my~r zi2SlqYt`1k!{N+@CV|ShV9}=#X@^N3YoCfqrDI#Sc(w9Sef*~+J6g^S^Qk26sj(Lt zmwfp+WDbNn_!0i_c8A2PyW+3YH$42*zf!8a(cRRh;8ip6gj}g!?yR9Sy8Fq(R&nbc zPe55GaZY#5?I+32h-SQ^teEPoUsoPrv zzA?0Kdm89+Gt1JP{4U^4Y0|Oju`xXd_0~LAQy5xm<(<`6YmntwWcIxE`$7ywYK#c4KvB8g% z){JqW*RB0(WM_cDfsT(R*CVSzovE29fqMOf*LUJ5`jkYMT z?Pe^d{SChSGBO18nZX}HOUTV^)KT&TiR|bWKy53vi5?rPLDtu3u#EJux}Hg1NSQ(( z;Jr{5#KMbukAM-Vbjsvs+ENs>6o7XJDt`kF5(e6ef=IrV#!Qm+7STJPfwpcDQNQW= zC<;!34|N9{71kU|cfJwEI{!Y~>X@Ien%!n*$6Vu*tgwG~oyL`-PFg+r)jd;MiqL%dL>5 zSG}Jl4dtL`=uPWm9&gWFM=4^YSq6?zp^^9cOVosv@6FxGv<`Tz6F`JMZ2(5s(R7>1 z0{{z8{1qbLPY(!n0F zIEs&?Z14N@1{NIr7vQq*KL;+0y}-fT(lqfbM44~fwJqm+Nk%zs_5fWqwB^zUm5eMV z(};{~Fx_xetyQltw?jqhtu!S>#ran|%q0TP%U%u$%`S*tv}Dhoo9dPEp^J)NiTP=@ zfN8`>X`Nrqi^uGH3r}Hq=ac9e!^I-uGdCZMx zVk-@)VojACDD*hJn8zsha%_`ZYyV=x?rgC+U+3WYF*sFhaWlH=Q_E-AQuiNrko^0W zM_b-XaHmt#n1uTvW$_7S?n2kX{iR3&ffWnSV)6FjrwrDtY69=TxZD%Rgz6wq0rpSpLQHKwb;$ad%;X zFb7mUfcFg#%%%ZyM4@bu4HbO>P!MB$wToBQI24J75i-65D>DE}J^3pxb_9`Mj5+@f z%kOzU!1cm6X%@|3bKf!21Mg%uppjEb5?i%SldQ4prB)gB<0f^8w{#*WM3EoAW9MgO zpki`AAdz0soD1mO%jN9)c_v$W0$g{Jx@h8qR6JVmycdrD22xCF+Msatb!;1$7bV^` zISv@X(Gq~n8rP8R#URg>Fu<3VBooSDi4$Q()`gd-KKx+<2}A3&IS`ty9cTU4MKN=# z3nb@UR)M};kY7ByUkY7JS+LO309tIMf)U=@DfM>MLNAd55HlmVW<3OkmkUnea&RaB zUGr>t6X17Enu;Ao2a!nuSVrhs@#d0SSY>h>q*A;(J>XWb9r~GxEgw>LX`;HdOn9BF zeV}alh4zC_t+;I9h>tW(A<^yZp@wn}qkLo@;L-MHYloA6X?*+{j2*2nB`-rl*mZK> zW)V{Z@TJKs<0FlBr0eU9gf(!`c;#(^dmH}^!MQZBuvCk^)iw{QP6s6VTIL#9hqI29~U5g zzfio+-gZIrlc|u7jl+4-W5Y3OdYYcOW8((KRuWeU7QO2Aal++wh}o!KpF@IX(bgO9 z!b0a7OS0&e8KjcQvJb8Db<07VdfN+{*R3O?G?R&TV!BPLJ0|OqXbm~QUe8K%9 zvH7En$%&2)@JQCc35nMbW5F&#@VtcOb>CyB%FjUp2h>bRfI+kn${5@WFx!`26~Qj? z0y7EVTDcJy^)tXmYo3wI|4=&qy|LTzOh)_rk{oH_v#&Kv%%`)kwT_*jieq}=Zv=_iv*49816t=S1#Zis86GSkzo!UW;a>)Z=l?vvI29D~_{Rh?b#y(2i;R!JphHSW!MQ0ZDC(c&9fEu9!jSl)`@*NF({}ctB zyjXgM&-r@~#Ll4zZot<;CW@@pI@v{d=PzEIfLdNeWek9amPT%Z`kyqn14I5wa+${5 z3glw5!ABV#TZ>KEB|Tq@O#?UW?|AHNc5?(yFF-%Iu>J=4O=Y}Gil{xDeAuT5f5%92 zPD>ed&(Y|-{Kyv4ZjK>HR@Qm!V@)p1k}Pz(m@ii}w2=NxI0-6lg7z`6B}ae)NQ=Ay zY@f)g{D~CO#tqIho4z?4!eL^{xB6QGx{nyHuOAz%_XMIECdn6rrn@iyrWSkxvL7b` zIVdnG0jS9DGVQc2cT&NU0whj-a`VqGu#%)A^GXGTdd>epC1oO79&-Dd#JKNhw@T|L zD1K@R4Y}7}P;k?!YWID8Bb6xw?i*PG5c+YY4}VpnXX?!@oP+OonH2X-U37`SZlp%s z_ARz9u`LAw=J2Vv8ChQEl28zg+qkJ^`OhRp4~}VBrAn~CFD9q{s%Uk?|2S&h9h6}v z#C`V~Uy&3bqhb$q{{%QIN=+*fz6MAGZ|payD2teGui@NvJ1A|RFKMt6So1`nJqyGX<~WA_KEYN z$z(!|$y!E~(Nc7Eo|=lzX-UEp(#60dhpejg<8;fi_vU7)Px=um)&F6q{J#`;dGO>z zr-6HXB{B!`Awvv)Zua9E3V)%)k{68cEszH+(x1k}FofB~W;=0}ppKxNH;q|ej(CIC zd(|#!^l3q!FjBM=u8xd=2PT0~HU7BOM2}xkbP@3=WcF5gxBz zLhgrzc;e2L)q{_pg+yqt5Wfp#@M8Q%a!@%Ox{1#CO+NExt8j<>n|Eq0P??}jf^22hl${*@^7Kdz(^}r3-T5E0+|zItMiK= zw%18&h2Y9$TR|!6FVbwE>M6GmC3&MnI5*Cbm2E-*norVCUM!_v5Ap>N5wN zET8ZCAg$W{oh&~QXkpOg6T@vfsA)~VY0$qTQ}2gR#Um?30M<76w7AuJK>PCy#yi+d z7lptAD;KZl6G^>rifzCBw$I~)2Rd=VG<@@YdPI?!!0|=F%q8Ew(-?Z5<`#+9aM5;? z`X%eO+>;@D0$6pL7HPDpNbJY{4!#ymGGa`&P)|bCkjZDe1c8ZLVk)TZMQ?`CAy`$S z&G-(N5(H}imhAV%2CqVYRYmFGAN%(ziLW$(ylppw7%wuQMY4qD(5pN$!}h0XIRu%k zx1?3$kqyw*ym)4-AO^UCK)BSPc(zf!GN3?BBWZRypG4;-bGu6E3qLb*#hQWKq7=W4 zjUGY%k$&Jbtjl`3=7Jab|A%1l zx49*=#3z29(IX_myz`LwX%0FUgl>M0(-^#PCHcu=>7KeCaYP-uHIW-51NI^7o zhqHQQF*Ylp)}F8F;jiQ?b?6Ia2uDorUIRc{5o5HNlEZX9*Dg3{GUKkqespG5Getapvg=7B}3mvwTJlq3}gt` z-qj5AnUi)V3!SP9>I!(}U9{meAVe$&yZSE2&7}3An~gg3%dw$4XhbYUG+-$&Vu&p$ zbHOn<#fRg7#$k{lqPyTn#@y~n!mojPMKX*m0I3z^5~)iGZdKAXt_1n#}MM z$+!qyhPYhIu?Y%#XH%hq;g!KJc=apNcqf`K+(ykzmc5&TqK?KGz7$Bbrf z!+o`j7tYH7#emuw3l-`1{x7SC3aUXlxGPUkp|XIt2Oc1;>=oiS^L~&t(zZ5WSe__~y-vzt!xj&Xk(URY;J2dd zY4f+iBWa3acVK*SI5?rIPA6q-WzsapzxIO!Jqx{jg&SW2TskN)-0;mr*#l*}FK=4}#6#ynZl_mqq;dMhp{QJC~khhIu4yF6J{o1RBH?U(}sv z)V=g$9F1?HCQ-ehV!$?&)FXc26({?)Qj<$SsY4c;Ve#I)sd-V^OCgmpOM*QlV zS~Gs1%PPFr6k4Of@j1qqqt1JFu~TDfQFq3AErOe-P}|t(932PD+241JPR{u`=AK$U z8Lv&Np|s=BqaFYssDQ&I*Ga0NfN@!JPjkY7p#h(Trw(L@7X_n@YP@(5BuNQQfk>A8 zXCqk}dj~of055d|U=;6z%ZG{g0~8R(KgVDwp;BP_Hf8i8g=b$c+7DI4OYER!y;6d5 z?xZ-Oeo=^Tyu_q0adb{>&)3`L)gPal__&?4=RsQ=rL~ zyP6^ybY1;bxHqOg2>y79AYo~%Pfq3ZfZ+A-#BmnNQpo}&-VJ|IV_G^Ju|Gu_(4{PZ z@Kfinm*`C9@*NkEwtr5LxJ8OLOQ3G~GoANDEo=UyVojO;jbhQ?hrN3!7cU`(lQxUt z&)7rfSVQ(vZDD05zszVa?RV`pNCgjh!!?_aF&U$mD#6;@7JklNmjNP(SV@DCy( zGxl|-4$@MgWLCCbFvXB9-i!=PW0t}Zj?m;6Qz#b4-!>zUd(H$mp{}n%bz>?JV$?+n zmZ_qlKw{ZjM^Of+ZHnplV^vsgSnF_$+C;@g)!QfTt8CTzJl>NmF$-;F--ZXs%K}aS zHGYgMvtUWma5?BzfZsDn+hKuck7fhOk4s$WmH%+XOK=L+C*7`h zTdR$az43$C*z~-|(fEj}mNI5pcfg7KyoirwH#BxF-7+^deA@!`2e!OU@0d8(7cRzc;3DWm-;aVGsO!5YOWO|(T2`3qVR|w13d~1?;H>@yw2e50O5x= z(}j`uuZLFr*Yq`D1(yLJ#x4M$br@j6G{MAF;Gam#Ufy0Gp+DfdjCMy8G;XHq05>Ism_Io8I$#r2{VBZCIL5X%>|J_ z7aD|p561#``1=p!!Z(2)qKu{t1Y(no^Ts5vhZ&&h=Cvr^9cAH1D5C!Op>Y(*B0UQP zJpZ;F;79+mFonMf3NrGYTS0rxC4vvY3w;t0S%g?QI$bYa&?eoSLp_hgkq9sp zzyI+0O;Ago{{Me>|^=8TnZ`m;Xq6tAdUei$O^>2t}Jo?|P!K11;r{$!%`MGOG%Zku7h%ChD zBFfMt6V$#gVQVij`nbp>{TEloSFsF(VKcOrKrz8MPh*y#E7hG*V_xVCszL8URB}9N zf7qb##}ik@#^c0KqX;~hC-xhsPh)NZ0ffIs1OJuz*(CC>@ZOB_b0Gio`g(OWJ;*Nz z5-Wx+1wrTRrwHx*tEx?#80T=&_>0^3E^#U(t&}{xsS>!8rLzVT)uW7QGGCEb>shTDX?KWudVMRRp6D$AcL8(&$2T7CdeY z=724=?Zysm;6-v7WyDDG3O+uZ;jPUqw7^><6482+PNB2y5d`b+X=K395Tqjpyo*)X zrH93{bA7)&;U&dQyw&we)vP@ILM`^Yp!LQswfiW@EHB9F!V8TF?NT5O0D}~-O0U%5 ze9bNpOa5U$%*0!1u~6>>d=@KSDgFf<3<-XOeYu_nfEg!|1pJ{{mgqRl9$_nh&btmA zuZ9kwbZWEYCB*aR$pH5^eA(pP6FLM^4A%S%i3)89B$dM$mkAQAFI=vHG@&jF31wEkvHZhUaIsX8>A%LQ{Ju)wk-r>5LKM?ir>mPmQ)XU^VXQoPD#YxTLRr_ z+q>g->?58b?T@=bSc|qZaz>(z6nF~)l0o-Isw53v<={bC0j^t>Ql?_qY=U({tn3sB zd6A{&OPw8XvLq#5RsO(fwHnWD@QK#5^ZS=wiZ^(ZXFueIh@(}xFN%!08>|K)TEppo`>Xk#qnut`%Hm$AgdM21&zo#aZZ)uV@VjHT@O+wzR_sFf2HhL&L&PW{QZU)npE>2-W>Xk&il zJ1{G?D$AdjFCeNur*uyWi~;CRKr5y(@(lJ<6$ME?%uao%kAQF_4EH+XZ7NbOfB+$b zsMN4Jy1N7kPxX4~WwM-K5a+j2WwfQ%GO0W$KRhj<-7&x5?VCbAF6uZhJ6v#&q{wkB zl64dwKW$@UbGYVRmL|sfc8vUSP+7mRYq@HeOBSGB1x{5)_bt6L71=jtQIhipke&`R zI5_OUr3Ul0#MurdPgq~gO%hLM)D^m)$iK70;^?T(>^uIO)4u_@aXh3?1jKPR5cklx zXeA&(mC=rB#l6PI7&%i}q-BM?`zFxmVj?@lt<r*KP6wt&9$!h{|pa{9XvIdkrr2KlfCl$w};hY`Cbi(Wio@ zi*A!VO;4=w(b&5@aAnFV_iJyLRW0FpnauWrz}00Wd8*O^D9U@_@7dZf_z70Z~F&%eYM}+ z8mfHzPT+~t81&xA#upPZfg4ALv}n|IZQabyUGjCsbf@TzuJj-^Y_m(Jeu;&AzbgL3PiT>mA_XJ1t23is#Fp3CpV%%8&kL9bgc zBk>$;Wr*f)w(CNxpiz<)f3?`3CoQ;*+@pTjl*(HGpb~tGe5W)75E=A+Qf7rg?90W*6{>1FY z|IvEw#5p@1JYMk7FZQYXi96QUb?8<~I@elf#r3Fqat?f0p_2H+Whrv+Ig;R*-?*t* zn&y7^o7sh-OhPmzXZP{Y1BtkgP2LGQTjwThrF|@)6)#=I_{qcm%m>h+YgnU%#MM-7@dhtt{AM zcQj#6rIA6^+v-K<#0)=Fy*jF# z!wOl3P0OrMncWjpPj3Z1!a7xE>EL zUYa9l$Gy^jcZF*qbqFv+L{@0lf~pj0YZSLG<8vGD}X{s{M>Gw5lKBAH{4`) zNndF=O>k$oT9d6c{iQI$5%J3+>4R#NTe>Gp9#tnfCW{1i$bn~OFT_U+dCr}*jD4(S zbp3YOt!4N*0jlo=nom<5jT_aP!<$*m?BRRa%q0sp2fM#AA4plWv2l2~bJ$>f@Li76 zu8@eR4TB$SZYe){UF@+@?X-;Qv*U_aL{cO8Iv824EnX%Ks5yXPqa)ZZ)?zUj0W8`{ z+Aqmrc*YXVkSn6Y0sYvkD(Wr1j^4HhQY!o(F6SvkjBLu2xd`|RT>!t_EkL`a<7nmw z2m!nb^IDUIb0Q^Sp zWE!xhzp3n^R$MqII7I><;?W{=B`3w@e3??%wMngId_X2RT4Z6e$0My|AuZGSP2kg# z(8iX2cmO;uP?PnKfW18jx7{FrgoPPmwHZFHe?cStzqLwqg1#yMi!R9u*@_(^3^E4$ zhSzlna0P)Z_qgqXA<=0{Qcmrs3!?LQEll72$Wd@sMBHsjGS55k%2)CGITn$vmx~{}g zxdX1Ra6`)nGvuQ(hql;7HN(L!r2FWn6}eq=%_8!hLup5?JsWy5cU&}QTE|YEe3Bcx zztUlg`JViuRx9pYqa`zUya4WKYW)gS&O`GbA*YR|F=Ly?1x@&9T7@{{##JMn(|=ZAlHq3 z0vEpeebY{8bfYGyjlAJe??R`q_h10;N~z2(X>pWi2R!U5KX~j22nJVn=}VSv0SCB< zSH+C}z2Cz`s+&OC8Wv{}z}b|CUebf%Mqfc4=$3*yZd854G^XReO$F}NDX_Lpm!0mz z?{8&HV=ldNP6S!%>Hv_1-x~w-l{t{M@MhpD^nER(7kB}UR?x7Y_7E$HPWw!<=ETb} zDoV*l2HcejE?`FW>d@Aq)cs$){g!wL9&%(*9loYS+Yu^^V32kA@cW709d{sKz{6s)$Qa1uRVWS z>B|2)ptSE<??p~}bS)=|=H z%T0BH&6rJ64c`_=%LoiY=%nGJJ~8DDQLmm3Kd43 z-oMZukW>b2Qgs-8MSxq`rH7lEepn~$=P#sYAj+s*ps&4v)u(i+3`~POEK26w#S-sjkS-q+EkB)CMGVM@kY`L4Ya@gzIt_j zZhnM)PvZOi+}Mt5^DPK89GLg@{j%5xq!A&$&NSw5$+^qWrvkwzNd|oZTv|!s9j0Lh z7>n|1iET;fygZRARz84G4lkY1o{z-Av7kOMhhCE9u8*#L)%dtk*Z7jfFL?P{j${*l zVV|(%ZZ6`=37nAiw>M0RHQN7has1@i!6um9AyGZplp9QFs}(3bY&CBh(rpi%z<*8M z(~@UT9i*n_MR}V!Hly=dUP}3o*ZOF7dKRWnbY{R88l5{kzB!w3te$4W%=8pIdCkzIJdUWl}jA*;1EL{CaS0*ZXBZA$2;VOEhZ{R^04Ch3$^Pl9wdm3zf(Gdt%TeSVM`5W)4hQG8~d&3dpQqM& zoWg5tI>Smj$I>~k^&n;6NEURQh-gtND9C!Xf=bQ!2G{(5|13FyteOw zMsLo{>=6IxmEyZ-hckEw<*P=n;1*B@L~0KCJ`d=&83=V>A%BRx1^-YbU?Y#j3)gg0X+s1Ne$zz}gnqc+gC@E&-Od+FbW26_=-Q*V;LpMd1>#M~&jKTW>Qs~iLym8-YOyN3DCYg!L zWx{oZ=!YJ#0c#|S%reF=MSPQ9E-%dMqv3Oo*P-N;`ir#-lp+z#b7 z#^8W*4sgH`Vx zhJ#UuuO;lBiK zntT+VH|nZMY8QZF%7U6#<3lR&;%bS1##uAv#mB2lN@)vTdsA$Nc_(hJN-Ve)H04(Y zZoi2V3%J!Sof|q%HqLoe8933D$`O6_W4fZbxsVf~6?|ED=-}qh@ec84lai98k335@ z04&%0t)JeNIFO-i;qVLJF_fMHM05lA&8DD8*r|z(tv0dap8qbZRl-OzvGbihrm5`) zMRFD4e%g*d-6K5rGM9xMFO}iykaQ7@*Z1ZzSzPR*vHf7dsOz+iawo|=Q$XdufKcWc z0l`cg69Hkz6Y}`~SMV57g2W0ctSSvBahAY#nT3kF^5RQd7*&qU#5b??hr|N)`kn_g z-#Z5?S9#&SwPP!8?#Eu0r5WXjT+FkckBj(N-)%-(ss3;2=%Ef1=~XnABY^NyST?Iu z+D`muJmQleQ)9M&l8G6fH9@cGQrJI3oo6C?aQ+tKf2!nc$NpF((8I$z^Fj-r0F{;G zD2x4VU9N#msEebT2HN65&5Bvii83gbt@czdM`FBPg?AY&b;3@DPbz-oHE|C#N}wvj-TXuC3|vHA4k zlH{4Dx&7H0^w>cbvX@qC+lrKYCjOO^d9@IeMdI5NN_vrqo?J`jt?jL-oeKFJQO`nHYRVC@h5$G zn9)C#XFdXwfGI_V8@AkH&9=6$DB${r?SgvyP&5l&0Wb$!|SHr zD_!MSsTc=l1@K6IQ`N-Te}c~t7w4aW(s$Go#0wKF%Lp*{m3lU(-=WoOwQN(l*7qV@ zjS~h^gZmL_G}SzssD2CRf$2z~L!KSfsngDjY81cP&mzGhQN}!B$Z{T-)hxf*1(hH^sxizbJ~VnW>3G0-Xa%b~?kV3y}gN`2`d|d-ao0 z1#7hfBcX4v-3?^>(ggz*_l@36mxu7D*{1p4)eFw3&6)G3$FD3mTI2rM8#5##)xXa- zWjk3v6hQQ=W8yI^Tau>z(3_h00qtY6DICXSronoWXI(#Q?n}OU!{g3;mu$J)Ghd{0 zS^2Ptc8H$U4Hkx>n3uBW+BG6q$zETJkzszQ8Q-j)E@k+4l**SwOjw>n% zL5;q}6Xa)Z_`v7%KzU#qe%|AOrdDtYPXjp4ZGP5H;*v5E0sOdw&%z1heO8n_*u|$* zjd)s+L|4rqR}PA=U*}04zXklB0RyrJCi2#4JY>V#vk?Mx)q8)#YaLod~8(|#N zus93ra5?rS;C8_GfxD3#T`<&nP61|}JT#@xPG7fP>tX<_citJ$+8o>qf&5wM>M0}M zZfGlL#rHRcZtz!QJz#@c;V!@+cF2eLhH_h1e2I$)(j-+AL;x2EAB~2RJx#jaP6F;H3FBvz zU6t(~QZCsGm|cDT%_+cQqRTp1`rvsHW%s0gkCiuRIJAGg(2@d+Y{LCxn9Y@n5*-UR z`Gk-z=kEC~d-ETOG&hSv8bi8D`K`QVg{b?px?`^kPGjXsRsGx`!e}5sOf7KL3T;5g zJtOM4f+gzvUD*aw)BK9=D}Z;s#(#j={r6Wq6AmB(*uQcDnVLI+V%e37fkA~XOkGJK zO>af`JU^w%fM{fj$@;pE-10kS%X_;yJQtncX1MO<9?>31q|+c(PMI$H8uu_fbnR`e zhHW!(8C|q+_<>W{_Hn9nN6N9V2ak5^uS%YND=vE~qqm&M(3JTsTxZ8(h4%5qw+Zl0vQYO>mq;;9@V;?C>89(ko(6MV3JR|Nvj-SbK>br(nZJRpt^bsx~jCfyCY7LR`=|zlo*IlL~_! zR=IY>$yx@$AQ2EX%kKYL&cgIkcv5875&d*XjkY2zUGXAt<4W^~Y{qdptK^NJGvl7l z&ZIuV?v@n0QJ*~X$dyxecIwlO^-Zq9rv3h5A>Y=e>-&)$XRzlZ)2t}~n8LCj%P~sr zTSjpYXJ)D^gR+Tm)ZXv5FNL;PIyv6ced&GwB;a>{_dnC1TnF7Ru=TTC_$46Vr@^`a zBiqbYh~9(&`>ex^0L~Y`5U@4WP|-~7zM;Fuh|Zcs8&+ap;qDgTi{{TzrcDpCdMg-1 zi0>PQ>6hz*+Ev)nwJ#Q>$KA9Oe!E9jxjVOH;Fjq=Z65`p1YjZ%ydWrxPJ+}hU3@{X zWz|*?Y@+R*KG1?vO983*&Q?3F+CrgBTh4sXMCOn$E|87okRZ=l^Vg%rs#Nx>iE^Od zuL|IUqOUkHD8LLbN??8B$nn3uO-1BtiKF2I|I8>Pr~qbBwdJIkco zp4&uv7i-i8P*cAVeuY(z0jecg{zG>~|xK@0jW*?R?kPPh}uos{QP`z)|c%c4MG}6@S<@%dwQTAe0;e zhC$k~RCqve5eWqXkk0S)|Mb~KF_V(OsVArZ+r{?als**8agPp35q_(l1qju1*;OsG zaC(%}1Jgr)w~XBc6SW*iS_`1aPJ>aii~vE9WBotZ_%F+52N-No^_KR*_-F8|s(}BXKzHBY0f50opYQejzlyR} zNDr8naza7v-*%&G-Viv^DaLSaMA0IVg+@$o!-{_IcGgi#qg+gK9lmTMAko9Uu5$O+ zv&R*JG56?sB@smgnwy8njY=3ia;;YeUZvt6^m76*2{J3*fsYg|;(dV+jE(J)v+b8p zQ{K^99Q;P?o|WE<@I+4ch*4GFia&Rc=Lb!u%4#@lU03K4EGqoyB0D0~JHBi7{f zrSF+0q_}1Af>X&k-FVv@57cD#=wF*V8Y*Zsz@9pTz0)~&)GP2#kMC3tCiN*=@=hOJ zr$H^D_aZwyXt+SbQbsDV;#`7Kyo+stn`6PzUWY6+x|1etx;I=G`P_7&VcwCQNa+ke{4p$kkLwihW!4AvHh5*hRNOr@~0 z=_>~Gp{8)*zKM2$u_)Ik{49<-|NgQjm=dWmm4W7&Rxy6?SYzWIw_3Q1U0VNbp*i)j z8;KM8GA|*^XIX}{ixT|%bp}YwQ)zV;pB_$q9g5)YiSns^9o1Wk)*VMo|8hvytjjf@ zjxdBiUB+uZ?tYBwA__xm=4|0!j64s`PLDJ3pQWT?B=u=Y&3xMJ)=iZ>16Ixhs&*$L z9x1Fph_q%+BoS1ioW=!YwL4RdE>I|K?9}1rPX_J`yuWwgE-?fu5xS8Gp$~O^jnc{d z7Ii$!+t)4pN`>khM%^+6VPYT1)R8EP>vSwR^|)f9;#{NiZnbk&He*@Wv+(a2!4eFD zaR1d-qmI_&nJhAbUY1mTr~e31v(xvemvnfm#c8iY;ZZxcWXl^IyrgsC1ylXqF6QER z#ktDq$6sl^lqP?kq!oj*(#BSMqi6ypvLl%jF)O?sj9%8K!n^b^7JvK^ZVv10*4?f$ zsu9v+Bu!9$9r+L!qWi*8n z`RPtNG_j$cWL2`a(APD@P+;2KI3cZDY)^1?MvT>~jxE&TJBP&QJ+-8dsPX{pGIr%y zKUXJ8Jku&rC5zJv1+6OAhJ_ImX)=!rrtbRrU9!_r$+S_)@*4iFwh{DyXNveg_3zH< z@JGte6NDom7ezvk{!8Gw7ul=B%Z9xn@}pfS3ft6_;GUA79!HYcOD4w}51k*%C6Rq!SDxyFen2zn&ERgh~6f0zLWI`;Z^MIGoV3q|isGZ=#^*k?p3 zlF$+(H`c_V5`ax7gPfFRW^g`U!`iFV@e$o6yr9SKr%vh;TFc0(0zJ-AehLp4Ssl`$GlSf3&eqJ zhc={)=1G-tWcc?{mlWjai7SF^886mq+6ugnXEuRvu+vVMIm*6}640*H-jLHlbNJYx zV0Aiw<*e0#yy+qTd%kL^U_h}b+y@o~`H#?ZR?8Cd5|DQ5O?1i`d#DOp$gc2EpwBi7 z|BHU6QhHz9r|vU9dc0*)Kc5r|lHH?9lC2P1^!~EP;e2+WiCcxpyW;QBUUjiu@1*w0 zhU6S%d^>m{)y2dFd(z^3w(%Gxv4;*9ykR4FgKg6!DhnMxMzLiYhHkpt)B4m!I` z#66yOlPYi3xVCFd+3DtsQU1YEP#h;AU*X-!5A^?<-J3EU!^nY)W3XsWR_H<)mCwrlnNjr2c00zQw24}LnAm#P@h zd$7}MM7^MG)~`k`?3P}cy}_SR{4bPeL;9o{M|1d(2nEFBKxPeXH(I^#UR@xnexifB zNwXVwk5;URNS(jzjIV9hNi)F4=NulCYv%!9X||Q_j>qcHJi|u&Pxbm9Hkht;WcI)Dd3-q> z{Wy9{^dV6c&=!D295#RYJ<5w<7j$~G;)31jWj)`*mCkl8w_CHGlEr(w>(H{rqY?4J z=BX048>LkHYNrvL7boyKoy7@ ztIwS6OvTH8Yr0{)5)kz%&01B*Ua@U;*@nBiVS~C0VU+N_CjL1mwusoPHNR!lqA$zgOgPk4BCg; zCg&qttE<_KRp>4?Q+S)zpXPXz*a4Q#vfWpqxd5%ty5daVdAnNz{`^=StRZvonnWK% z`_Q-QU6sLF_xrCrcN_P0Elai(Ud#sU4_tXU6+w;#|6$J3^7CCYyni5j^{T$C+rjMv zjPuEs5i=Nc^EqcR9+ezV#B{>Al`twfkR*9KmPAu z<}qoVc41hlw|fl+iz0@lJc?P5s^$dvj>AOnp(z15Fm;Qc66LsOTDJN%h7$yMN?hb5 zI1QHW0ex}OiwC6-n@*B(dInS3^n$8;4rWC@-0d(3^QMgkBe;7B1Lp#9+s#gk5r%+L zxb*Iq<(#;-H1t+Ea(e_>-_c5alAm$GBtJ4k`3u2y2Bc>Fpr-1cFIs`cP$m0}qu+nr z&1g*rtkF|$GpCMRO=XDfCW=sp-|osuwk{S?n)!S3zo8)&TssL5X2lg-ad`dEa*=R= z{JPvCZv8S&TYjQ_XGFCj>V2K3djGynrQ7W;3;qE`IB()4v=oX!E=P0K3%0wx*OEX? zap@Z#n(H=EueIBU8CxQL|B@by(U4VtSd-j;oh)7HM*UI6-A3pe(#p9xH2I6;#8nCs zYpRha)~|MarDP6W1+#y_JVnm4{kc1ycMd4L=a^of^sor6Ev98FB7qUfCekxDyb2UR zh`u5`8#&#O>6FKXcj0?;BPYWAxaZ4nFb?i$sEajnGtDNq_A%yh2 z6<*re$lVI}T@NISK7sp4b#b?10GStj*!fF#EMy zi^4=ZTGL7=IO6=I?c#WTD$4iv%z5`5{L~Vid%Ei7W!vF!o7uok464y(4G~M<4%B^F zHJ*G6IC06NH6eFbDFHR|i~qnY{*AAQF&J5=08OicZ-GPqdC1xXbph_)!ro({t*)}} zld^2zmX3 z^SE}Gm+Ua^GXFN^Qhqt^cNN02v2Nt|QrPRl$VW;IjR}T@fu+8>*2~0$QSM9{6$Ni& zaIL<8J$Zyke{AG8+7TM>$a%@H6b~+DX?Lyu(;v!_{Su!*zQ~TQuy5A!3UuOZT=?oX z)Xn!h2y_TvS4AlYGSL$DcV>ynNJX$w$rRRE1htF@|9~=KY=4r%NNf@!Csi}>h1JJy7#|FZ+Rsg1l{=`(Msk!lW^3j!QTp1KL3zd;y z#a;+6jSVO$$~h=ilxcW5GN%tK2E3~r!}fr3|t_ZIC-*aUYb-ZvwTY{l<;+s ztIw0wNf=`LbeP9lo~m`efUA7FxXoz(444%V-U7mgo1TV!Hucb_*#pbJx!$~=!(RV`(cA70CYVP!A_zD zVlh(RLVxYgJe>`IV1DvA8d>hc31Q_Y&Q-h5wJJLmeM@x~UZ#`OZ#-5uG8*`hc~8DX zrMO1T@5_iI=jw$L;R}xG4L4^GjAq+u5v<=iKG^#$OzPlZui=?-)K_g^6y2D1^cGva~7g=t-D=m*qGN1Py+ZL-c@tszywj7D`#^X@S8C+d+ z&UbHd3ziJykh^6@L57vT0wt~}P5sF1ww&7CBi2vO+nmgB&HtJ5hQ`9ILi+>41K`3bN6H9q!BHr7maSf(B9 zhydiRx$sU

    4NG^y6oknm{A(zEZb3F_E!EnPA@CQ=5NDRky0DJpM;SQ(eSG;&d!} zW``#V-U&=eEZTjlONgRi;xC`ksqBO7z}%A&IC56Bl58p-SgexDEt~mDn$b_x7+3zY z2~-79Q`;9qHId8d+`|PLW(NwCAQ4u!pNxgvKqTo{=V{;i$6p2-zJJk|FAo2C0*wM*{r;gz!}*nRn}40D{%Toimv#9 z0wlcC7(Clq7q@j8k|GZqb|lj&vYur(D|&pKBV?~}J=~$EH7=@jW{Y91ki(Ui-A-F8 z7l^EDt2~L&5AzN=OS7O_Qr$B)pdvx(K-@d!@m|=9E`HP5=an;QteMNel)%%w^O1+VJh*xbf!m_!3&Rd zzd5zU@G2tYJv2Evd8C1VpXmFrW-vo~{w$x=F8Tg$$k+4(GbtF+0~t=9lmM>Dt1GAn zU!@Gz&A@o3@IGL?Rg1<;$M*A|MBxn{H^pw%B+-ez`i2tf=9k4Ska6vUHxw8cLz^GU zGC3pWO38t^vY%De00z;~k7C&bhS=-=K?R`X_-Vs_uD|CX1$NGX8CS8S8iy60N_*7D zUt$eyu2p+f^m_L;CmfX*{By^Q5BSVZmbq1zfL8zeEs%Oj(KVJZ{cH88aP)GGKmB_Z zbqmU%>8-afSx4hz&*4miS_VkM+&RZxv=Ux}|MTvrmt6nUzXn==~9)m^Hh*Hm}@W=KeWEbsAubLb2F_b+|6VElF z+x!ha)xql+q_^z!DrNrW z6GSZvuRS@zIl&5BKFGy}*L0%lNXyd<{I2jwjv2osBvr7xP#)UD>N1=7ecY{!PDu^c z2(d@HjGAa;pD*O47Idm`a>tzydrZGi(a?p{Ba%pXPgNQoNMzbQOxR z=29+RzQ2-xZrtEt)UUi8u@N6)Ey{vOm65@aCHd?BJkRppuHJHf{p;J zpC?<7bCCZW7KQ9)$@f}MvSpag%MbWp+7NnFf6(;95uAoTSJc|Ov97K$K^^NJP!zj$ zHrc@8>Hjxd=3h2V;!X_f1}CK`>IPWqBVvad(YNLvMeGl4J3QE~{;_?3_&^U;-&AS1 zL3`&kl)2*^)+NgEx%S;F2Iy@=<(g@!{!co>n8(!}6DDlN3{nkyk6oTYoGmvRwd7*i|mWa%O0i@#&8`%*>hq|jw?FUQJoca9-Ddcz|K49*Rni?(E5 z@T6i^1i6yme|`CU2I4Q@LUt?(MqE>WxvA8qG;HrbHuLOrL`Z+`SV$6Og|j^RK8MwZ zTz)Q+=`ZvQPo!EXrSH}GqLEZ4cfLN`a*_?=?Tm{!_yc z{+fJk*EIotaqb{F$HHaq)Z0|a!(pjL#2rOZ_)@S9kgOC3Z+-xdhEkDrQDz8Y`(D&abG`M5`RQja&mynxdYb=X$8LcS=-WWmVE70gIz(F$ z93u`6!Gk_EnDr8aP}FT?eGwub-qylBDF~d88P@=T!NeQjD3?~L3L(d$Rl+A=5yIMy zj{U=+Ln(Cku1~_a?%5Bk+A9>~Ls{JYq?Nc|%GNa+*E*x9^$}c2!bi?Imy>F#AaQ#r z;SuC~YJ6ljg?^v)?X$X_o_;;y^Vv5ko11NpDch=MD~B{CN1V8K+X+3j%E9{#@uY~H z*hSRbcM!G4On?)g=ioYls>3g*lz+B~EGKrHHPyp-zL)vX(#kIaP_W4SM^O#dKEKpR zbY5or+YDv<523xuDe-58ZOY*;N`qFG-x}HL??fx+;kdQ8IRN2oNmr@={O;v+Cn&=N zA7I=Y&?~;=e%G69dC@OVe_+QR%0(JgX}lMA>K9X(C$TXjignfcVZ}2RI>YMQ?q^d6 zQd9EwRAtMjb^9cOsiDEU^*8m@EZ^YD%Fn~w@a%Bnmu~o-;5tvPB9JRf=vlr>q?aLO z5|!a#ZO&PRTYsRVo@x2qe+a|P+1YJBAucRJ49<1XU|VVPBqXN3{=7VJY8gife#GD} zyYJtY+5VB?lDPe8Gjds+duoYO6YbANz#_3t#Gt&HnFt^R%_?tp5Bup`un$SLL;s`M zIf(x{V}%Q?5qzXM?56uho=WJU)j9%LTxu?n_4gV zH>4TzTQ*MxT+9^73vIHqImblReLmylfb2z))0a21QH-*9!Zsl87{gZMSdLw3S0D@n za-}ObhjzjrP%p0&-W$WU#987!g22Z@j5st4%Q`X_#(p2y8aevI`=d;k(_FO0N;T+t zYL0(;`buEw(W9w?$$A})_uch+I{kiXf7@P}HfpMrU8sN2NWj!@R9BP&CtFmtbSpJ2 zqbN|?=vHA>MNrsP7r-2l?=6(;OkP_fpR>UE`}*T{k!75nCHl6D_5OUfnEW24W-89x z4E2Nfdj~v974Fo&V;Dw4fV1$JSdZx0A=8W6LkYBZw7BmHt@So*mi%d~Ng&8sMa!$s zce|C|sS3&|M2?(v`Mm?-ModyXZXTH~9BvB<@>QSzPh?>Z{Qv&_aZqbz=&KR1cKZ57a|jTX(?V(kT46+ z?CaB(=eI)I^!YBL9M6I67I+rK6m|^Srf}>z_L1E>~xI#9M;#9HDsLm{j+rc4)RQZ4pn@GMDpkh~s;f4Sy7@n^k)tN+(;Y$uFi$cU9xt}vwih9dbJd6!s==cw`2 zU`@6KDSjVII~mnfeX$+`nf=hO*|nI*qxipil^(_!k1vk6pLqYu)!M_tAQJ6rqg=#n z=&ViwXDMDr2fYrR^TdC-c;OF!*QTGsykpax%>W;y2->e~S;BVWFDa{kxrV_^3>3=n z8&G~EIrx*QF+Z8NhizxUc3x3tks0$m4ZuAI3fwn$uZD=UD?pcUW}UKxhlN_>N@Tca zs@{%v86^N~2l=$Gzp6zNA&4{=utkfm@`1dDZ{^`iD;_da}rLVh28^*{|do zZx?iu)k25UD`)!3v9bW6BM^>ez>i)m>YUPiMj5L#Q7c}MCQsB(#|VWvJJ=bY60+NS zLb}Z|`9G}W|8xCwib0eQuoTAuvy(P-kPPN__EVAx3Owf#pxy2V89e6&Kw)fM`(vF$ ztQ61Th69Je~n^1BmVZCA8_sGtSrxsErI>+O{-(jCUG zXq0}!Utzp%ZA!L$Bw$PfZN6>r?kFGDSmAc4g*E#IBV~frTq!Nlj)RpURM*TA)>vL@ zOlU17>lE9x;6)%l^74rFc&>erf0ORf1aqya^)*;wg$vT&;dH`VB$SHUf?7|mdGsH! zsxx|O4c<1-8fE5oV`2(-Lbx9cY3$vE!HlY0AG-@fZ}sc4kg= zYf}NOqzYm7}n2i&BVT{<*Yb;kh*6*XX)$mSrC-V|9hwQ1wV}vgxEr$ z66=LK0Cb-*7tbVa4_BW}kJT`_!2m6&5rspv9%K9X-)n}QLvD#?pF-O9Yb)oCc<>W1 zw8j_dyvlIR)8BCVU$7KQ8}mF@CS^N>=xkSI52c3il{*V}u>2GMkfb1Tk1U_xl>Iau z-({*WS{;%ai5dTMtRFO18d8zA^(|u|>fG{6P4)FndCUlj15>w!-~X#u_}7cKP|HJH zTb=^HrhOY^$4P($nRjOKai%7%>{Rj{6A%3_R@QgALuangT2Y`P;G4 zy(ke-oN5#$COT2Stdyi>6^EyLrinl6VPeV6zkFHW3jWMhlvoJSrgJtf58`&b*@FCU zS~D+K77gBZl0{|$YNQlSEI1hcfnEYoMj04&EPxT=H)f6QwkrjN&M2T0jN!XXJ49Sw ze0vI`r3H6uwmSb1Wm|1djYc|LUt+={q>LKlDvg_qnpnR6Yet)%N z8~TQB|Mevt9xsH}v%+mZ{KArwuMF>q;lD^kL)v8CuC38purh+b?iLQJCEmoK;Za!h z7jP4sk#@AZg6o8l#<;`38A(;i=c*ScN>1II)i|RX9^QpG^t1FkL(R#>t5K5}uxu?7g(;?Xz8@R-%{%$!5>Tc3#Np zxv=p;Sv0W-vu8Jhky7q;`gDG^@46_DJjL>be_m zx3r6@-f8$Nb4M6Kre)0FxPn$=zIYj}r90Dg_t$$u;h$gYeE9Hn%eLZq{9x=+sr5n> zeKMW;2K2*XVPeP0z7ZggYYo`^a>~*nLp@G|0*X!nnc~@}zsCss5#P9WqvuOsqlgYa zhaN2;qKPVP(?4reNueJqs&2X!H-G=s=oL)6yOA1lz1cHUUwYtMQO`ez(p5)BOfK4A zA1=y|JRo%Z6~V)X@Ono3yp4@cVuy^}$425)H%TX9b#6E!tgmNR-+rJ7&sdU|N+ z*5&+C*<{l1_!G#4*Mj`<6(|o0Zz6V>36GwG9-`L1Ep)*au%1KMgrQy#VTZ%6= zWzN|dbdML?Kl~=DcM^X?a*!;dxc;{baYjS#NJ^KW4b?EB@v1 zCyO8LPl&)Vv5wo>gQ^em9@UNgPE}IZk(VhyB6D4G^J6S-&p{vTw?3u*=STjr#_i{7 z;QXcDe;ibwZjgeWpy*WXX_dckDUL!fGFzzTIU>2gZvHJ%j1s2ql=0-%!P${V+7p=| zhNV4zf|zczg;5CVRG)uY_e&32PvG1E={&E8T3$AnTPc}aeGNaFm4(xHVX< z(T2KsY@@menK+vk>1nJ)Ei4lX3%jgY6<=Ae)E%N3w7A(hbIBArRzY=q zYJYPiH%rK2syA3wXD^Pj9|0LsIG_GLD-Alhdm}uT`3AhJ!fMA}3JWXTyLvBApCHJ8 z3PO>;Xb1q2i_1%>D#C=58D(i&Q5dEIqTA`#nIimi4R!xnOd71u`chpk+A+5v+g`-L zUDJ;~d2vj}z6EhOKOV5w!^td=Z|@K=35d48+|l-uz-p;DIZC zb?s19w6DGq8$!IfM(cdJc&oZ>TTcjM72#=NkdWwYP}7Z0%Bi7r%yJiH?1kpn?d`8~0&R-^w|!Dg#FKyiPgiSimjc`KG2wj%VG7AY4^fvmL9#cgO%-f2 zdg8t1DCM&0t%E&%-6+HqLWXDXGitK*o}ElL0s#dDOm=^BJN&MsbK=YSv%ojz6YI1k`31Kz86#|H%jz-6m`9fDSx*u{-msu@(o?G(hb@}n*|ORbLjo_B9@~ABC^kKGz>u!Df#{>8xXtqs>a0ynbdL#+(kug|o4cl4v9jve z+J5=_n@y1{a=I2HFFIqII=N`Hlijl`%Y4)K>+l$jbyGT>^#~Qz=FCy$-!?#PwqmH) z$OqACdkdK8d!fWeMH0i#FmpNEzLPqF7NQHzF)cSnNrRrpelEp2&qMd8dG@U6nJMO6hl`yv}O;9 z&k|EeO>ILRb!Vr)X9j>cR`kyobGHTQOLNQv#iKm!YDj5@a{7xeQAb&7efYZ+nLl6L&0de2DtlNkQV{7N z2k-o{lX#DcNGXE6St;FlO_ZYVkCp2r?a9WpbCxZQ@2G2)LS!+W+D^{ z?)s?!0ybD5+&$nw1PrC1(F|-wGqHZYi$vAs(yihe`h`O3ffQXQg5M%d`C7Adj^I3z!up}3`EN>ustjyj5_T9(H8$MyEB9CQe9ft z(#%FCHE3eF#+_76Q}fqsL-O> z3)!(-IGOyqMoJR-S`bo7wQYPQ=)=mR>1E8c~jMV_duH<=LJhRrm?>@g5~Z6F?>N77qrymb*you#&p7aXH7*Z zxn{8$dDr9^8{H)l-uw%b+r@D^{=hj;;gv9r^b#;X=5rR<=_`rsFj!<1d;#M8TvJ8f zUVe#*9P2f#6{P`nv2=g+c`5VrV9ju^zCOl6-|U_BZ6{M6&uSf3@a)Fon_-p zW!RQEGkZS%sWKezItxb-FDgXB+d$PxT$b2jQF9ZK2(%Zz^UFh!3AAlEVo<7#3p=;h zhlTFKY|BGUQIA_)fDS%)4fN@ioxe9~o!%*9$nnxP%yYwdV4M0@4p%*96RHWlL{J2U z4&j%3z{4JgK4T@;b)ERW<#j(2j=z+Zs|8NH4Pzx(m_ z3;dq69lx^qOT&2YV(+RYKWk>3^Z9g=U&HGYUNdvYKfZs`5Zsj~z%AndJalY1Jl;eh zr)7aB1J4-iP56M?VR{rElSRZ_F;(YGimC2PUMl1f71j@u zaB{;JltcY!6xQd3n`(h)ZXXRg0J7{{&LDX3YJyauY%n0z+ts1xc5b z)!rfK2pU9?Ed&wJps5>5rQlJ}q0(}$Gw_78Mp~ymqJvV8kAs2g3*Eo70CjelcYIcq zCdsp|ZDd`koq?KGI}PgtVPyK*@!$3u)PY(5=0Ha7`TCK40vrDD&VCC2rOd1hPqJxz zpbK>Kmr0wdC_zJsWMVz1;zlLJ(N2~n^;spJ_Vl-cYP=O5x69Q+{!-m;K9h@u(wP`> zIc$D>&bZApAwoT|!v>fk07yqds#ZKnJUx8{$tE+bmLFBZpW2Q1&%w+lu)49l&Chwx zU=XrRz0>q?BUE1*aElV(xWmzfm77}1RtoSEWxa1s&}=Sg@BRS@s2kKReW2r)MxdMMFN=<8RHj8yVeSpvH~Av|iHec9O|F zm6lWgV1+_w;nyw0-cbIsIo}3?ZUp1A!VBpRgw~|C zV$U3)CDQ6;mRA_l6&2VaX()DVAt!7`d17v~GDP7Na{KpNq$Cp*c7G}$YJ&H*j%^|N zJP!=zP@Bj_@J*f6B5+{ooV9wr+FiHa$Jt_7OCz@55TLN{>qB>TmNWCPf-?Vg; zh{+zDN(S^|qYreK@Vg*Pw;0gpkf!#wJ^!8;)-8vVMAwd0&k1%l2bVD8z-FgE4Kr4Y zL(vydkaFTZ(?k5sQAW*OqTgyVVG|TVtUgg6sK>kV8ZZj6OBW1f-3R0>a7?uc6CA6V zI&w|rdj25VlAL;Pv||@-Bz@&V4J6u0sh3%iVxp4+vD#hsl5~7ev|S%(?U`y*3oE21 zCS)qg!bjr<8VC4Sf=8Sd2Q~AUk~dVAg6q_+ivssD8v83-kU`=m385w*diiCq*D;0BA_Cql&(Zb z%0fg$j4cQVDMf`4W6A;~N=Ouh&B@#CvZD}o zk!1g8d7kdmf9|+u>Cj%4kXwPy3pZ+=r1oF><~wj%39Ttl=O?0AqrIO81I zwO?iQCapXx-s#Vig8sUpfh110x2q-@Twx)h9@D}=(FfQy=&2CRY8a(X8sP1?OYH<4 zJx0v51@$WBu!a*1K+4@tXngfg$8-}!EW$0tC zkf{;PX=^AY-#h$uYrTh;V;X2Tk#`{Rf55JAge@^- z6Zd3vOQS=1!f)(o)_#>u@7@=CU1ud)ONE+<7X#{*x#k=VTQB;hj}{=qT$>YEYpt6D z8bR;MLJAqX<=@MpDBID%(05wrD)^N7#>YLR+L0ffc5qqD#xAevXrU5r|K`xhJ=ToqoW4wHD{kkD{L?_R(4g%vnX|X$3hA? zpuE52N^+r5sMZI#6a~HZ;izwP$;haOp!AeD^z^C#5z?P;0Yoagwcv7TR%jP5{M3dv z7D$U2wU{gh36(K_WJxL>Tp+68pszH%#IfLFLBT*+M~TJ{=OgMOT`iadi-wMny6yoIrNU8}d`W}RqEyG) z|GqhaPbJjmv&kn<<$KhOMfef#$BJ$5LGQ#*m*u>kU;K6?YvmPNfd+*2EhhxTe+B0O zy1p3rnUrJ5TmAyw{qOZDP{IvVv6cENoWkKbXB-U*z*FNuXKjCNzp~2v%mvw{_|c!I z!`XoYTA%pyrv~30p9t$HkX53L;HI{ag0SG*@1Rb!=b8yn3SmNXE6Hv1o{>HXq-o*X1g$E@gHdbJcT zYNK1_b7Uv1jRLQ4ew!&{!_NBJwLVgeP4dld1-$_@;gUMXENc3GIi|87Y}v_L zLpvs@6VyDv>7Gw8&cOjpYkAq%4VN}tbq_KQu)x)Ydm|hG(Z!Ll%fJ(+cu^I*RQ9}( zViy&olXBGx*P3)_)q4WVS`2f!clIgpSN0VwCBPwKN>1IU{4wjYk&3Fy-GXzWC3d@eXX~A(D+JUO2V;c*$chs6!qv0+7 zQ^T6=w1tonYf9))16`z~@a^;3Y&Z3DbCTLGW+%z+oSDKpYY+Br6!F&IAHT|V(hTu% z6%i9&A8(!uIaF_zGCmd!9P7EB1f4aq`~t8~)iGE%ZWFWpGA7r1M;Tl!3jRG}!eY#! z*ZY{pxuw#dvGe7HPZJ)E&2=A>SYE-TXN6~OkE}wEvS18Ldk;8lfC8PJ1-m&u;&&JI zx_j6YS)L35Hv9L`Umt940^Hf&c(GiyQ^9N_T)B)(6GY>N1uX&CQkA13y3HUVD9qX! zM)&mqPkDsWj9 z_v!C#m+A_D*Dtcft?)hsb?=3nWtidPaJk|@sY=s^HuBzeAGJ0{`~bU^*`_XD%a_zEzD>~bX(5{g%3+62{OWwn^oer;1#?(C7u@A}=sz0G8Vd(7> zb;FJK;bK~Zlmc%Y)B;Yk*pE3S z;(2E9Mk+r~mcif%EHMDv0Gv}x5Dg_Kh#l37bVc8_42GN3NBJsTrW-p2xNGIDN?L?U zAGBI{&!Y~6xnVLzq^y8_5((gstgDi*{Kkk-;-^1(O%JB|b(me23$I_Y< zsf!@a6fn_CU($?pk9ee#3G4>w(+kE`LLIX$yxVAeRdzDIZ7|FdxPr0>JE2Dii;`%3 z=Yrh%%#6<5J+O%Qc%~#-SCV8M7WO7GK(k>IRP}x>(3v z53Us2T}e&?X97fYDK$txsMaNRP)2sDSLmv7X9Y}lEy(k%)j?1R^=WqyG|PZYbrZN-Zr_LZ|Ldb)Q&vgXIJuyJPipcYSh zP*mYILP7ge%K3Mn_-`&8eeg%}F0kN}5GsofzQ&eL#&;FbbOfub(1vQFX}(a%RHl@y zFSk@KQ3N^R-??+l;NFoC-5o;YI~KDpyV*AYnq^-(UkYk4#-g7^Tx(6T1J2mm@tNfh z-HUC%le2Sbc~8Lk%LUl>4}`Gy!>`p6W6CP)XZ8)ZreY<7R=v<0+HoGs%SYyYoSmBub6Ub0$BW5d>_$C z)fVmE0qrjNsHfEC!^c~4gKFNc3o>SpvJ~c2S3Mmcvw70|b;|9A^GID{&L!1c8wYku z1g1HXKKvg&Yuu@BkOoa@Am2_e&#C6h30suz0qz__%m`Nf#aQg^q|lI_*Cnx*z&isW zpN>4nW71`8Q#r}w(7%>^$7ia&J+s+GHYhL{s0g%koaw$re~J6rpszJ|MP4tG3Xneq zQCq;;Q}=v{4??St=$>QOkR+$;b$5A-z};C0KNVQlJ(nKm9XTatrfcm=;0ruIJO8zF zPL3Jgn^miNJBXeg1;JL$yz}-c^3^|Kb98KAEgwtjf%(*E&S|Z5S8&oALM^97l7_|T z%AHO3>k0(1jA6zJ)!FBezPx}{WuF>|Lvi1@c7%B4Hr=J>5&8>v?hG~hc<15x<&RBL z<<3KpGZAy%^4X7yD)e_=G+jXjl0fA*I%@rzYz3iF7@unz}&(7_#yhyk0Nf3CR=v{Xs zfsAPvNI!S>@_gk$9U3oW7#=?{!LHNK6i6Sijb>5l!QvgqR4eGVPkHa+1*V-Ry%P_< zKW;c}K_VD^$o=R3u217Xf8db!6o2@ak$VH|Ep!Prb@pwB5)|$x+qdt2{pgJuvqi!^ z=cm}XD)&_W@h+Fuopxmxn-(r8*Zj16Pgn&0*~tP-T$0oo6|ecru6l2 zAaJ4Az5O+t;7a*&ctqM!D&Lv(X>&rdLbB?;c%tA&xH#78b#RE)(tGz}dpG!n7rt>|FQ9hY+)C3*VXms1-%FOU*e;VIsyuj~sn%iOSP!4~1VkZ=!-t!K}QW-*zO`YD|R)%n_oEgNh1x;yq`S8JpC z7WWhd#`tGh+1EbHAO9lPL!NSt0uS_V{n+Oou5sSpD(TYrsqZB$x%HRrxr)V+32$EC z@U<**s}b7HMST7HMs-A=<#EYSZ2s?(@q+rooSav+;bTMZg^5J3%DQv!-jDEpe^t4` zYn%TsHlYD49-uy!BgPQ}Tu!#YKyZ=*I}BcPH`IByfpm~H5(UW7jFl(Cx)LZ0X=~xQ zhvUWT;cTBi^@W$~tmw@g?auxczhQ>OuR0GFN+17f8ODG}gE>4uDQh@N4en41mFCx1 z{RMGgw0JT|)7gS0pu7KOUp9rw->Sksm$M8ag%_55Xv^z-1osZgAHRG2d$=Dd+NiXu z;r3d;^?FFbd3W-bUUgScQ{Rjx!vF9$M(tx4jPt!_fyQf^Jr(?PZ|&%%O}<_K_&es8 z$0^qiq%j;spV^R@0A2JR48cE`z%BsG2&Z>=9c#G`%;mY`?`e4WUYT{X%bN$Nq;ewG zUUUXlx^T_^wmMs5CNWNXkRm(xB2`_W2In@V3GfPmDPVhz6@i<{R_8&TSCMt3l_Mv> zC@CK9K2;zOX}c}uk#?Q&k)+VAsf_l^Uzp@%n<-1Hgux85TXw|0g}uaY5$85s!3AvM zq%uyiK!`eJ56;3$5jR^fyhwc=BD6Q-g<%QBm2&n{U*qR8Tt@L45hsXuXu=B&+OQ^K zZB%LXHwzlJ5pR-uV!nOWKT3Jx_bBkUV^;VaHgtqCgjZis4YmW%!nOubDSNrrOV#~? zaIFyEiO-r}`uslJUk(Xcl}VBBXqbEO&7D;^p=Bc@>}OY_wN0_>(J*X3OjDx8!;Ru9TUYTxo-K%Wj1g!0z33(-Pygn=YA8K-0HWv`GaQ2 zZsoEgIabJ8FvhVP-h`e47k!Bt*gCuM|L9o&gf%Nub)+TIO7{#|K?MOsXKS%5l=F$H zJpc+%{4JC0p|JcS1s(;{X9#6^72|WgRgXgs`q})mdE?}^n8Eoen5hZS8hme7w{cW7 zDA%iSLi94aDHYd3Uk=>#b!|wTn}@}mRWZBlII%F{R_Py>h1Io>V+(5AiDfZ`&gmtU z6P~CKp`iz4SM=tyvi_wY*5T?Csp?-EbhWdB>sI=yjI&Oh6`bp2zO&X(r1gVDyPrYt z=U6ApVt*fJuXLjrQEwjC`dwU~-TH$=#vpyWhMwn`MxM>!=4_tE{rG)k_IIV8J%mhK zPZ3SOgf7O~^tjtFjz4*)b>&d!WP%PHlfFo0uGH{$P4U;kBQiBpI*RS+NXQ*I*2nX! ziplqic+=k8tZZ}Lxj~OE?IFCyXxBD_o!r1u+kwqYf@_61eMU@|B!$2gbS~q$I=#|tE$?rpetuWWT+eH`w@F0@o&wVt1DI<+w?VTXKvt7IYT_s+DUQYAsk2jn&7})XU+-!vLT(AhEbUy#( zoXeL}+&d*YBNnpUUSoO!c^M{94wfN3^aX>A)7>a>_~@i$jn>tHBhS&_t=ZFD3jZ=R zF(1N=ufn%`o#zrclb0iJKcdp-cBMx2e8Jcu1ER+zp*DKhp2w3mQG5G7^6Nzj5uOL1 z^?tDu9ZBh!ANWG{Qbz6-i|3s#olXi&qn_E5E{poz`|#!yB@47PM$1#+0Yi*X>RSU0 zA}3YA#VMmIX?R@?)(07sn1SgUloyC2!E7Qfma$Ch3^7{6GC{<)npn67z9c0q{@h5p z<{gABk`-ACjD{*i)nmea`Cn$kre~RmffEG!o4Jrkk*GD}9Mh%Jz2P~9V;jeo-rEa; z==uX`ip^i5MTn&~8tVc##d)TCJAa~K;H0%-6g5(+81cxG992CIT$&w~^NY(!&fZ1p zu`KN^-S=wGb2bEIiIe0@KDAO;9vcbvd*^lPH_M;_ava>YT+6Q1`Hcmp|(r zsxL$vQXh4-He9A%^jt+4r*Ghk+>`dEym3x>(YkM5qYlskO3-P}NW+UlW*d7Lt?0Z; z{}IOT5ikQ)ixIOXUj2X;QxW0sHV}+;fzq4LfYFhyAz{}o<}AHVV>iC5Yv@z{QaI6a zo~*cJwrJv&(>DshNzCUEgBmF5uH&7;PU&s{4m=iUB((YE1$7 zV5*p8r=g!plsoRiLit}Fmvc*_Aw|2bnd*}ewVIl`zn7e&E@jAU9Od|Q@2z7$1mWmSVY727)le?t5Ks9o9?277#Kvy+La=i{*=Ckh714_8s!>&b1dDppFxaMS#-8C zR-D(G5bu6wfHX$6Q~sJ@!3&THR!o+(TPX*)F$p9arvu#I$1m%pyzP}{Y-4QQhGf%6 zBsHk1SzuF_>x#X@HQNyp){G5uNn_eEpWF&^LTN-N$YxsjTY9}gB#yJD+C|(PfMX)J z^!WAv6I}2n`VukjWO7B!JU6}c{zPH^oy6H@vjy7L2JK2;^*?=BlW=+$e;qK^Dr7K> zJ@sc}EY(9JBbT>u77_(3r}^epB}5eT1)uws`stu5d;Bn*(9IkZ|3?qsN83JsFv5#s z5LCK>SUF$JkQSh8J5vPGLc%JB^iOu}hdRk(J_`OGiGbN>o8=i~oNT*lw-vi>)cUZ9 z7pTIt^~PF?kMdnr>E#b6;wHoL0K#|X!Q=XQk3R|>nNvUm;DF8vH2ZuPAihZN5Hqx< z)9N2!ON}it63c_{m1|e^c>CQCgTEbwW^B!xRm-WVowl1)_ntk2boCt0u5zCe_*R|t z#>8MsLT&i{o;9ycXrmf?MvGG&WjF*`ZmEewB>4oZ1f10byI!L9SC(%=%1&E|{T8SoZVD{vuO z{9j;LjdN4eD&HWez2FLnYy`~0kQ6&r!Bv1vqZf_1dBrzIUk6h11k8f(l=6~_qJ9f@0a*2)`QQKQjkQC1Jl}AT zcl1?Ifly*VBf+2m+Oa*mo~L4I*A=@Dy4koM7<3OG(dbBr^NwQeQ*4qB^>HH>+AIto zSK!_SnJs`B8dLCItNmY~#Dhn4dNHWlvLN3VKxE31761*TL4&P01Dov}R3yk%qx1lYN4OHJ3kw);F5-kpDglqj^)&s3KtF_(=TP*s{Dwoa7 zpTGJrW^6h1rmR`^CJbXvY)rpb=2Ee0SXcJtE#tEX*nVSO2}7EJY*C?=3cwr#U49r% zf_A^|3WC$(p)a>(l3WY=8W=BS=MaqOTgz@1p8g@$N^*rpsG`R0^(?&Z<&Y(wy4TakK$fiFBo1aC0$428wIiBrd#-&m;3)upi zLuLR*WU_veKD3_wsKuYX+O^d%rb#1L5b9r7{lh=l9WVXA)S@WL)MGom-#O{5{ zS<1w6u>8m1O+l}?Gw*w1eOULWw<+W**YCir29vvUKuOH;8ukZJJRX50`;I+>2HR09 zV&;gfZiRr@j8ijN>N~3L*3SvuHZT+4>o&c1n20v!CYv6G%abqo z6c-|1%^Gw@HSc6{0;PO7jQ9ibLYN;#P5HbBto|cdeLkaQ8!j3TZ$Y=@ILauUCUQV~ zs(caF-+7(BoJNF`la}64xk^9f0{IEWpceBeXz{qmolEZd`N#%H`FEfX*{#as^fPdB zoOTb?ab8WY2AjT!L1K&1t)P9-UPEHEoX3&SVqbO0lO|Dk2YPWG>)O~7*uP2}*c(sk z+Ns;=tNPTQ?AnevsZiPH-h5=AiA(mp?#3~Vc$2VjA~=pHZVjIZ1|1m3%ohB6QWFPc zz!2a7Kx`D$C4n6nLh^6uVW#v${T-<2XXuzDmA9$*hwPR}TOitkF`_o@>s8hTg|~&d zchq`3r2SZ5SX@&{O^-M={}uTb*-vqg2u`Xv&;JU9j?8=^YpG`o=4!q8J>Q`Lc7?KK z4XxRXRrp^DkB+Tl$|R}rgk^qJZ%R888MZ|(j|#mcFMlZ*tT;QjboBg}-KU~w#hVs= z{e~DM0;AFmP-*GyF%w-W&FKLxvhO?PMCeZ!@ptMV9%a56R5az(Z%o~5d{;8^2 zXUC8yyk0Q8<*R)$Xn!~$Xt>#{el=ma#G#?FzVNtXW=rUX8#k^l9+Y)zO*qK1GmrpT zHpij&@*N>ohflcTY_rBUg6c5l>>my%mr*SUdT@A4Nb&Z*^$`D$!stw%-_m>J%1G){ zgIT9r*#Z6()cCHgt-G@=zi`bKs4TT+B=tt9Kx;-&yTbh%8(7rV3)_JrKpFM7F-eD- zkLIfU0DLyfSx)T|j(NNT0ar{zVfqd85B=5UvaXoxQO88X%4(Mlntcy)S+~HB_UNv8 z+k=0HbvcM7{qHdoRnBx9m9`*JksKWX=T*ksfD^-1)0wtOi&Qq4qM{ue8l`&Z-54+4M`g$fSEjkn3%fh^7;Ubq3nY!?Tz=z1+d;NudizGnZEQvU zb-lDBvTp54(h1d|(h(AkOK(@5AYMEO4+z^E&l!}nKA#6GFVa01NNFic$-2A*wAFj9R}HL`I(09MfXBLi z&Tbo|Tc@aj{y@+k$u9P>p7nhnkJWCo=QUQ{T>8h}-XHMJNG)*p*1B5&)B#vj?ckIl z1VlYFiipFQT*3;iB}6(1+sSi$+pJJpyF;{}pT?^7Oxod> z{@l6E7&{n!iGus%B#c)BQ=WpRqNJ!Pf6adIxSq&1KZKqLirEO25_5lEL-`wS`{JV^guzN%7}T57A>nd4I!B)?^EY|`?dSsvg=fl_e#NB zwnIOmUl)MZjx+{z5T7Pl{*Uesb|s@Fi4lugg1oi39FLeoomUbt!x9-Mi${@B^a3ds zY}BaTkyb2WzSrUWWJkXzIUgGGW{;@%fTc$uuW>aEQ#<6C zAv;+!E8G1$HGCy+r^+$_el91hnpEQ2Fj--r+6BhAH2MZ=cUzeH4&@{zT5dz})p!(~ zp0`iGL%cuUc=iS<>bzBFchSt*EIlLv$jjp3b$*|wn9}ZCc5Ojh_eht5p^VThrxAdl zu$?qLRkIFI$`zmartq0EnxE<{XwPL^QlpPQg|9x22_D-nQm`V@+- zWsu1;BeyhL-?LtTIyYO{sd1)lVXI?w#RBOwLS2YZFnm^Wh{tLb7#FEY@GdznGjah` zTv{QC^J?yP z%)XNwQsU?%z$jMZq6OE%wotS?0d@bJ#DKnm<_1@g5>=@7u^&_iO5{0)9mqzwrAiwk zP#r5Se@N|aFUGt(Ip_{^Xx4FO&-5jbeAdt8cqBRb;I4tV6_^SqRdhBm!zIWD@Hs5x z6&pIp$OSZ{&Rp!~6Iu)p)>ageBskak>c?%3-y1ZQ}Wp74a(P{;`@10GVB$yZN-}4AgOFEqbJP!`Mq*VO7%C za-}<1__ow`0auX8IL{h#Qon%(;7=0W17wqL0!1YWhQp;Gh0q4TMAe-y_H~WqsCL3~ zo_{5r_;XQPNMTw{e?rrUra5ZwfO`o#MhCS(X1 z1-bs9GET)nmb+`h;hhJb_0Un&Y55#;hOvN#g`LVGT<+dHLGywAWut_Mckxvh-TP-* zPu`b3;M?|m;RaSwe4*;w@*DaY>*om8-y?I}8n7vmbZ~(}(ZC{=Pf85<3nmx~@4*|3 zm(@P+b?*ua41h~Y(I#Dr5_t?JUh}7a+skOHSUB>lBhs1cN)?+yn%ISZ54NetQX+!$xD~RI3TrEm*iAFA^{yfq#_e7Aw`GS#)qTYSc`pGiZBp5gz zy{Y7SkKFedxfI&Mn!yXjKYzAb%DnqP8DHqYDkB>F){{{viyzsvb>L!#YlisHvfn#w zS}nG$x$<)8{(qm)ha3k-O78pv5UZ`@eCN3@PdK@pSWl=WD68&jE#XeN;hF(aFe6p6 zHt4U$$Cinvf0NU*Pu``5%U%BH2#>2*bfOF+!aAwbguV~-!$1lEce)wqKY)jfKnB>0 zw3hclsnQg6r3$g9If`bo!+{vVC@lc?Q_fYW))~!Un*%-h$CxN|A5$W9)B~UkFuuEg+x9C9dNO12--CbNv<;DDUHdYfx zxG?q$!m2MOc12T#X~p*-zN{Cwef^e`H234UQ#jZwT+x|orT-4G0AJ}r!KqPS9lvs7Ri-kku0XzOwDBq783BZCspkMOgCUNS zb+-VuZfo^pT|L&CIw|v425E>$pC!23j8tS@%w=|ZLKl7kphJz8-IgY0x;g)nv#(<( zaOCB3$04!YI-b2ZXtVN{@NMVA-!P<~1jC-{GPnX#06FyX?;@V*SS(zw*+cJUucU9I z0doDdL>WG9mbDTdr>~~>3YOCRWhe_6$mn~X@u+iS7>&Y7`)h7t`$@#L#V!TLc_*gt z>VwB@umHJVD-JpWcwvc|@s)p50J)Je;xA`q(%gwlc%DJv;>Jnar!zM~IUFYD6aN|e zU@h~QN|4R_A=`Phs&x9%a&ND~6yJ{0>Sm9h^m(45jz zb=7nSm2aL_R@$rY#6XguNQy>OJuRe@GgO6gq^^kc2L5hu-UI=fgO zWhUIn?%rl_(pAOlJ>%ZWG>`sx@$&0HGRaS~8Wj>IG|7E>h=kYlQQh1Qy+z!-OLACI)_bwiuR zhB314_i(#{@O$`uZh?Uj_#j?Q@kh&f2UIG$0i(6(Mwa6=R!ASN4n38vGE|%Ct_q~Z z#ohya2O#h8NoiM3Y>e)dD&f+kHuJm^Tl{rT1?x@{N$YeuraS+&{6h`YbIJP*D6Qo6fnsb46!x%wp z$AJD!&eK?G!r$mRhp~eKCGN8XK(vWJp+TFc7`K>jL|X<-hu3(dCA=Ogk~OqQR*)DC zs8)b`Al7h%lx0L+B%CkEJWscz?SJaMtXvck1Z-i4%UXV?vGwSrX zH;-2#TeL@>HD}@A`U|ZFBnk+Z`^&(~-$mHi4}ki5DB5jDYCDzjy1t(D6bL z5S9M5P@r9#s98bpCDa#8IYEFZ^)xXho}Fdu2rtm=p?SfoYUJ(Q;6NA_Fz!H5f7RPn z5oC7=`VLDJXFnieNDfWqu@-#O;t2!yDl9;P@b7~0CU;i7n*NGWcxo*;h(W0)gti1s z%lrVc{4}sB$#fA8IMoUjU*W{l##i z^x_>r5gMAvXY0|z_1keNh$&P)#ki+6hHoXw_af_J!v=-n_-ukf9ec&ZL3l!Tx_RI< zA5!Mup(IrVY>6Z!|K(EJ_;JV4_~Tb{4#jBBU@0T>TDmlsZE97muJZpua9G z0FB7DO9pA1r4f{%o~?)#ZF_%tU=F1Xbmau)e1mI4z+^s8#{II)hos^i*~hReM29El z+DyVuXi2JR#K2Xvi%LO^(!b+P#Lr>&%nrPm@dCGu=P2KaGlogwKZH&&R?Y~-)HSoIfMU^mdXRpFS)`n~f41&;2^=i!C9o0C`g~Oa!L_cqgF8*Rsr= zFf_MMcNL_pI8Y2Jp9&#q){jsbFBoglArMRJ-fmrY+gIx3}&3hxM^}+ zFy=y8b6CXBB2CyxtJ<)VaV~QF!%x`K|H(Yz07PX!W+BZGl%S&khip)5%2W^Q>OpX!u{lQe;ge7oE)%49dulGj`9>(IWp*`r_5r-Rz``f?# zZkgCOc5F3kcQ0*6)rny+Mj$CkHWYny#B3+M9L9icIHRi@QBz?&m~$l1t}IbcLLtg+ zWw7bbFr@^JmJ@`@cG89^lGG;YXVA2_2tQDbY$JhdcmXA}*{*rRXYI*u#o7+IxCL#A z*gl|sl~fdQ@X{x(b&zHoeHi!D|2iPK6O3@@Bxz$?f&3Hih~_X6XFAiJJ!fmBa%T$h zxpT+{3OW>|A8bQyz~gbN-Gh~uhjWJUkRYhEg= zD9+~+y21^+MlOBa`%i63!Y@GAmj4GhJ=UKE^&G9Kow`m}0$dr+Tp+zeKu5!lsyTHR zEKpH|xOz&eauzg(E`~f)IlRUGR~dEQKk--hxxFFmRjt4pKwtiFxAE@?b7{I48)+X7 zCVET61D+PB2{z;hYP$-DB9$RxYzQZ64uVj)0{A1Q@|Gr$w!4S7rW!0%%uXwTSx~Lq z;khO#=5lu?A3YJ)o>4pSn8oCg%f`ZEgNc`jRukgWU!B?$R{p+d2n5@MKm|?8NM%S1 zafSfJA#s+(jBU}C;?9K&ve+y6Khn;q$hXQfy)Vf@g3HRKIii@BZDDsF7pNmG^Y-_c z9_p(D!%jPo49@0Ij`lAtxkQ}{`WDj0PP0XUT}fuN^ssCEK>elX?l@rT@@;@C@yA88 zvw`+BxD~?I4JZ`VGgL)MaYJX=ZoseJC2KO;;g+=KQ$Nif(uBsGyO%}eR_$zSAYM2F z9JfoUn#UeO0mE)qjx0+8rUWriXnqFox&s$W8OfG<3Q?w1L746q=(8J2d{NIf{iP3U z1y{C*RrAIyRntuKx)`3%qq64@Cy+5!!V@?eF|^iDknTZA4PfuGudtW8Gp>Q2gsD!|(r^F`{lF%qc38P^6oia>@~Vh3=&NN!L&(VA}eq8d7GuC@V;j z?$BA5f^qf9xD`6|&nowjhR( zPJkl5FZn-uo%jqelhlFgBpvKvz`H?J;s{_3d#27dqg&7>xLWS0`!qvl0!R`Myv{fL zvxl`=W8T&z^Dp#K+5I@fft^`XN|i)`R|1>37_I7T#a|<=Qp6PPu7~T7 zUCDQG&5V)`)b!7g_jCpWnb_l)y3r&1n;HZl$8!2}Ae_`?JrZhP z-^6{EnSb16UH;zEoLh>EhYgzlYZ>Xkm6-mwc#USG0rk6~*-xo_8{%vJdYG%SC7GWg;TF<-L?6hM;GF3o{_Qxq_1`nyOFt(L)(6Tdt3F?x z+Z;!@Ci&iyur}f(@F5>_KdF!ESkqcZ1!}}IM09P;P32N?h9qaLpuY`ssVZL+UwOyh zKR>v4mXlR@E#%_mL8jKVy5-^LFpt9e#`5g=h%nL7n%?t3X17Q|=tO4&1%t#kfjJFK z%2ZjmOU&tx)WJjKBW-*)ReeKp^4L0?je-YkTmlGqyvanDUf+4#)>%C4m5mtYJxpd#vFnYq z@BO<~>8J*2zXlVeelp@w+NmCFc|(Zp!GIYDqHoMb#Lfh?NK96KyuR~PIQ?5i_ht}5OA7QdQh}6!< zLT~fV-FRNq#zv^e8Pcc@!6@~L;$^KjF|$0JQfX?3;CX) zcsG-TUP(RDVY_M|e%gc@-PUMPzA%C)ew$W3?rC}XY*ndbWN)6BaQuBY`GRR>yWQrc zjeyCt83TLzB8E*0z&*luV2tUn*=U}B5{KS~v($!1l}+F)Bj5ZA(p5pW`^c?YmYk5j zyVo;MIy~*rK*@|;Tfe4ge=goMrhd>$T_`@=j67D)S9)RW?Ly3uMt0M83_YpDGSGZC_91PR_ z;~93V{k_$m{C5*C&`;Rh24$6IE7B@h;X=nC{SqtQ8i>-ettcb@IW2(ROHFMr%z1pF z%Oz7vSw^fOP%As;C;Ya?bv>Yl`}b4G4fP^f@dQSes0Nkq)PBAT0Uiv7=B5la zo>YAy+lah7KNx2sAUcr~+axP_zK!-LtHuHR>YuIkk$tCkHI0mn7j`tL>|NH^hih(= zi~p@_-U*ND9x+JWfXXloKd;jMrhCYDrdG=5+Nv%b9t$G1B_68Ezfmf@keEOO5Rf62 zfzRp|j+sDj*%uheE=7PUHeMKF6XBtG6cXlnT{{|fGYP!eJcJW7ge;$u`fGJstJ zJ;_%GK8Vm)|EeoGzogFij8mBqq!hSTwn=U1?u~xAfvMR0e!NJs_PVHU)bkocI>myW zDNOfd%%K4UNApzaa#H=&qW~wV{nXNA@tXT&Alk`FDF-zyhp@Gx&L%z zU5z4q9LUo9kKRwXAv*-MpEuxrGFl3xE2gJ(GKvvQ2x+c+S_^k;whh6#Kk_!!o(LQ6 zOjHokig&0Ivl5Ib`*Wh%M+dpUN>DpQheV*kAP6P;ZTj-G(>N9)-gdRqhFt<*=mXa&tBJUhF{Am|;e~e0W&t?(Ff^DaSLchoj&QxH(7m z&iN0VJLewg)C8KABL#^9X*wGU#{iQD1#oiJVZjxb9>EH=H(V|!Z^7hBmOXe5V3L{v zd0>PGBb#Bg{@E?(I!*?Et;ob`qWg7iFON}j+*ign!4usZnBD=hUr=hR`_D0Y@V6F; z8P7;Moe7jo{7YfM3&Kj;wtG}j3*D*5Wyu&zj$aNJ?XMq*w~5>Td;x9J=E#0tJ+J<^ zM;)6BF58{)5C4DeQEA7GV zC@`TWw(!@8?lr3%uk!+Yc&?-V_o&hP`rYko>Un+T4a@^I%}ot6k{q;!_j*8O`Hx;Z zAs4+8V~vL}EfOZ^?YXCL8XA0^KO(eU1br-(=lphj1KMrSuxvaL7kzcX%}d&6QNfAF za0(M}t3mB1rIf}1zpLoI;?7R=bxZPHDrUE*Sic3AdO7%>kK`JE6>PwtL}VAt1giT9o&;i2BU<|UzO=rFPh6kma_qd5r>Efb@x=5R?Lw8eo*o{ zgGy;&pYN^4ZjHnf9hG(p@)l~flHU{cX#88mIBtpjS(e=J8neZ-QIHcqqFG&ij=oVy zii;~lDJH`Q6F8=^;#`5;nQ(ZJ`zCV!+mZf+oKxyghx%rn{SP)4&Li{l^Irn0&sR@l zTpXJkpB4J&CuNov?jdfxxZ~*mj6U_Ja9UGC#PF)-H{kVMXlL$mS~(eoY!_{x&Jr;i(Uk+)reP5f1=6^DX4gLc zZBhjw!W`>XkHT11)C`&k2!%;z5B5br_BY0U%!2 zn9ywXkwiWc22q4;y%+CXJDrAU{_rp~_ySpXeczuZ6;MZWm0Q~A;u|HcX&>`9JpdMZ zJswy+Gula&KrUz#T$ij&@E-(oR--!k&Tx*x`Rv}C2JQqiswh&RvS@nfy{UI=P-4Y| zr|X|>H_4dPjn^zGUiWVu`%`x8&$P4acWvCVIq}?zI}fv7ciw-v_16V%dVhpeYuCVw zRGnb#TLSwEf1%b6JR!3qi*iw`PQc)^2((*lST#_YU{6V zeD=I}(}tUV$a}#rXxLTQinzkgB~;Lok5)y=*+P7F@dm+7HRYOMVFIvfzrB=FjQ>5g zGs3ei(qq7ThYBTZWM@~Gjb8edkFM*RadsGLp{SK%Uy}Y1ol0157#HB64hdm6Pc@u- z*l~E+6F3%*C=NabMW=d)t4!OAts(Rkt!Fj0v!i(1pc`ie9Ma0S>YYvytfVsAtUkw)PBpk`GtI=p~?`1KscjKq@!wMhx!#TD(#B7vI=@y8U+iA3X`ET!GmvQ#IFK zG~!%v;e>s9RUG-GOt8eGWXWuM{ZqD))>+z>d{Ojw{lG~NP#RX*BH}Qssu_C%}K2*%$B0W(VdZ7q1-Bvhg-tt9xNHk zglI))aVEsCFE*Yy+$HPIw({W0S*s?-)ktFqBKu+#uf(3dt)`iMx!>)bW9~!CI1(|cdeFo)Uo|Drfhgpby)eX!!Jijyn z^$+oazHp(wW*yi`y!KKO)QK20gM!YteT{`(VUPq&JNFxdFMoai_tOz-A^zH!Qyuhr zawCCvHEOUr{^QLL+Xs4>&-DJK;$(850G+w@`=F$af87lD=jNr2UxW1Q&HnMf(Qgtu zI(*2)thT9U!Lg$YL;w8U{HKjK_108NWD+4N6KBc8pOmuGa40QNh3W8mT(R`F46g?h z!c>VFoR7EKF0@CN#8`yP=Llb}UzTm&Wci!Pu2T#2WRFNBQua$3x2~aZVnXhhWk`y7 zP<-h_+_l`*r-E+=tcJeZ#VZCJWeH*ctnShrYO2DswTT1j^oP1FX@NR+Lbpi-QAD+r z4hl)rO9ZTVfzh97?E|$c!{Gk->D4g^x}O_Up5^S>N-BR-72~{uBhNU;UEsj9oCU!2 zVo*~^0Z>qjD?@sRzqto%58O#mm3AL2k^vINFWPgkMsCf>pznD1p!kc^GY)7yBF^K| z5G$B)qBh1ZMc8h;8fKJG%ifB*xva32W09qABgI8zzi)2JvcXv`X3lczLx1?7FU*86 zS}RcESP}@QK`2>y#~sWuKwV(10!wHOl-~)I`U7QXSlGh0;H?~-j7z*5Gq$0j;Dxy0 zT9tdaZp%*VJ0U@Q)t;iqFyT6Kz_C4>JL8D^5f=1um;j>X{MphXwC?u>0 z)T_wOXZtWSlG!L-72~{Q*k5f9S+G@>>PPTPnIz52n41_^PCffdEeRRqg~*kA-fsHH ztA*GpT1+`9(n)1d6P}G%2EOt1XCLVCo4wxH|AoEey=lOAj(f{1KmkCfV1J|;LepB4 zIdvSoOTiJRf|(eN-a8cLAX&ffkC{2Dk_*tpp0Fr5AQ!U@;5Ki8&il0T-PJ{`xdmam9BbM^24!QPw4 zL%sih!|f_D5h-FSiLy*(i+7Q!ysnO!iZb zEMv)t**eLV&lKa6SoKC1mgK?(f!zSu5?jmlbL4LgN_Dq(Bpbo)+YYk0b)dK}o{Tivq$8@@m;$ zphhBh{SlFKYRJx@h^T+`Rqw)>Z>64p7$g5`i&0Gc`OkYUYF#g$9eZ1F@ego2j{3Z1 zzwo)iGA8YjC$ociiEQ0VrWPQrP{CZp+@v5_d%`4sO%K?FFcEvT%pB@TYIH|Ph92%( z3M{jo;s*C{=c3E=n)b$3ADYZz)~*?!B2<*OTCD$nY#02mTMd{z418#%XYVLXy9qSuloLhyyG_#r7l@hw^wT7<9Mb)Ix1E-QdLitpRn~VZQ#>?>GUIp-ytxeq47Ir;n*3F6@d&$Y$-N6yY38nT{6C<;;!24D6 z;QOw(O3Q!@Laya94WFqTnR={Z9abCy#6iWSok(RelGu6_4ifYvBN20r5h7%Yo?+iY zM2I8D?5o-Ne$o!XA0{=0n*yfwb`OPwZ0w2;mS7QKdiq1>tcPt+YkL>UPbuE9p^Lm$ zoGQ#bBF4N z3~L=9Ea1A`>nHh_1lTvpYc=<4gfX{z^m{L!ikN;r84*u0KX$6j;!#la7(Mzy-q$(! zIIhivAifmzQ&$WFpJyM~8sFW~v0(k*Y7GITvI)^aLhfywLRCsRv;T}l0G zcDkkL-`(kb;a5yK9oF}eX)~oRie8?2<5qDsNaXkdv65@@Ts!l!5AdxC{&1S(P=dHY z}>lK3F5SZopcN40>LJa=iYN2O3WaqX!QIzv?q=e{mBsIXd(O*EHN5ckCxgd5% z4s|x;)&QdvebCoXQD~9u`yyl*{@fbk`9IH0YFp(+_PdTA4l^UfC`;=iElu&h163%? z{J1lX>>)y=x##A7^t>?;smwr`Vhky7j7)3H~U!cd)RO9=?jp)RgF>8EsH1emX5-IfNoyXvSvj>D#WW6%PDeoJnn0h~y4gzSp=kR$v9fHj=MYT4&m0FeVg~TIL`%|xZMayV><4ydyB+#Z2+tC&oT$N^jbMl zP7$aMkvVr>2V9n{Ox!=6a?l5T)~gljDcF>39{Vupb>ykP_K%QWnHjgbsU$x**`}d* z@{;pr&-t)Mw$6<<4nU>9(|{+vV?qK|Gp>6i;)1*VXuI%nztb3n27C72qha>a>VJqV zgcW&;rO+p-KRkbZ@?Ukg`fv0+`UF#<3YaAKwv&h10E-BkDGN_nn|Q`s|>AgQz2=K5- z=f3T+7mjAh%jVIX@QE>g_jJ^LF(UZM`aoK2U=E-PvFkMJfU{i(p5n@TV2p*InI5@cZl-c~sF zusHU%<*a@?T0`t~w*(yU3{CtE4TIrc3WSyrwe?nH0tZ#;}j zwrucyTrheT>Xy&e)F&q`G+e8!99O|P)N>Y$(p_Fn{?hJtE+%VDd?#i?CjR|k70;v2 zE-HJjXI{aWuQR*y@B%zz7IN?6!H?zU&8NLyT+8%Nqu=N}w|As-^xR9L-@WqQ=S6#* z3+^9#YiRIS_RER$MlDzF+y0*6`kVJ7Lro)>=*A1Vc=v*o;-DWa5DUjB&E{yEP@d*b zVA5)^EN}g)OjQ9Kr|S5ol5#Qjjkad`@KrX$xg}twd$QYLbbx=WnP1$lwgV~xfi$oXE}5Y% z$IvOURg+xMDn3z9E`1`~$%NWji6O#Dt=2hACZ*QSvxHa+TBA<%(K&c`2uL0zZs6)q zku1f88|;e_hV6^>b8TI0)o_{?+DPUoHenfn(Ate_dBA0`ZpKu)L;(6il*=o?6clU+ z76yTL1vJE$+Rfkae(8&9WFrC1ybBGmwYGDNee8 zPirk0DV=~j|5^4dvk(V1{MR|DFG&r{#Yz0+Pcb1rtlIvbPRdO{@)Ws1%`yj>`jjv#;xrJx>zdC;R1sHdrbcx1oJe5Yg5HxuTEc<{=j|i~> z&I^E&Q=?V95wE8FPZ^|CfZ+|lV4MIM54i!P z1UPUTpDNmcfm!F8hw*%S2hp0m=YgS1+)=k8VwxYs$}5A5S^~EYR#XWKMo)7{&cY_7 zQy;x1?#@%@kSYBFC-UTMRWaaR{hAm5%O~{_UGqmsZ0F>Ms7C&e@R4nkBS;c5C`56C;9i|lhk-JO36H)QN|pp-W2@2odpy~EdK6{R|7 z=DTXNW4|-C`h< zkve2;qL#4d#zNxClMuBHrTk^wwD;og`cHjaO7Y$~mq!77Imjib%&)>)vMJ*AtGZj! zE?U4MS{VzX_?1BlpvKFL0~v!2tV{q|dtJ-YNq0(An{O_goi z3bN7Raxv&KfRXg7NjVi@UwhOPe*v2b4TNx*+K3S2h0L24uOl0;323XnEYVuxSGo;Y zxO?a@&+qc zC%3(v?gd+Flp5{+=$X?d1f_UwcI4226i{l5!G?>0*g%qyA6WgAxaf#YMAkBAencT} z0zHAA8%y`|#}l511{!lBtD;>RuLyt(iE#xNc4+gf%A?@pi^A3Ng9M9d5e(d_>F&#> z7C@t##$dqT!HswgwuzzQEyyEXe>4V(=t;KpHfkEJ#;4tVLnvi4JCvHH*qsO50n$L* z(p2VBHko@hBUnN}5D5+gc0ir49IMTpV(n%>=^G)7%%{{k%Qh=Xv_TX}r8L`NLU|)`Znv6Q+le_XcZqbq{BJ4O-#JJe^~QQg zVFDe+aDDE&uc1Z={pD8JRnxY08M&>kEW;s#!v%QRX&F{P+ek#UU0=Ly=UWs`-UcMV z7c$&h6+3$^8X-TI5t z0>D|Wxk2u&H@pGhrvxra5;xeLUp&!?yX}Vpo_}%n?|zD0-eIXs$L6TRk?%%wNZ-a_ zpPynD8daH_jS0%w&*hu%(3kZ6cv**4dRC>2zAsF|S4J%rrkIzgSsxMdLz<)vHkF=& z)jL#hIM`X(KJ>J=DpKX1-Y!T{T*@QHwU2MfmH%3og9TPD8iNa!Yw%xoWZ};+lpa|8 zR;(k(Q_fKVwlsG3JEYV?sX@LWsFjpH#$PE;PO-C&HBlDQlF0{ZYngfekac>^m#0@b z3u8OJo{zaw-{r$GGqIf26D~Rp%n%HJ$olhLy#|RTlLIGDuPqvE9~rmj#@5e9d?lxx;YCkyF854f-bXc0(9Z?ME*`U^pm1H2)V~8= zg06ZDDJfAM7UnqL6h|5|Y$py~6Osz{iZ$Dl3Lj6R?Zg_^+|{+oeA*psNby-p3#NFQo7{$1Ic#SrR&F7y z4kEEH)4Ub+(bKoC-og?TOi$hez350E8kD!qf6}0{1g^1XUrpuJMSdl<1$#?R zcZgmy?d~LM)qWCo9n)H#(?_o=d?0cj2;rbtgx4Sc9`?<@%QQRO3ajt$WoH*@$eU90+L`nI{2wQatrb{pX4<8t^Oj8U7hUNkf22>=u&t$M6NgY9$whAcE)1s4zlt&-MO2jbHXu96r9uQcfaakG`BrtoUNMjc#``!AR8`G9W{F| zXq9iwT$7~1AhU3;_Y9nJXYN38Pw<^tMSV;K7co=$jf3&Vrs}*_25Ia-J zIfHAXdAIMOuC8mnZB|7^#d7acYlF9MJiO|Hk5uX-wg*wt4@`1^P8NdvCr+KbKQwTo zJdd>%ctD!SPD0~g$=OnFk`4=cmXUsib4rLsLS#|}d3zwRv5N%}Y`e_yC=^*#HoZtT z7vpX-DzQ!p9!YNQs0%We2Zi~}7E2v~Of_aRAXNfO;Ooh0m=NA_lEEO_ z3|=qtO8Wc_n>cQyhla>EWBGFBGg)4lovmog zw72+;p7AGnLKchF9~*vzMW|HxO=B%vUA~#96&-tjHPK{$;BcwT4?cIlr;WNC^gcZ) z$xZC3ownD5GDXD{LmZOL=ThLE$<3HVJ!^POj5`aOLdig4(_YzA9FlJ57A($>+)>+K zdH1OIiBV!gv%;``AV#@?$?TV5o`#)xTZzwYUwDvxAvdY;8lGSXO-nI&_De(}8ObG5 zfYVbp8bt~bzZI8MR^ViySt>o_0Acl%>-uW)Om&*C7N`$WLO}S+wdT&ppXd1w6J%J+ zt^oX%@CSYt-nJHg$8(d)7WB5khdUKU&N@CfiE+C5ywG^~-$ua>_9=E-6U7x_!9C z9w_n5Saz#UpXA_9^)m=*3RFKM&ZsK!eO~#bpXA~77vmqs{1G!p*tvArYJtM1vJ7B^ zhz|Cyg<``rW<7e(q#6<5LaWJ7Zf|l02@i7SGdsfV1oUCJG3;8|RK0f*AEOTHAjV=c z_Qr5)ffkq?+k6_385F|Q<>@7p!-bw% zmOHw2Ucb`}^T-dk40{({-dEcb^cj=RMS_{t3h zlf`dc|F%KD{-0$S9&F>FP~pInKtaDWlg)q3et~KAr`)+tX;TK~0bFCg^DWc6WPMbd zf9O{aw%AQDar;t*5N$*4+}IlY3~p_$-xRI~9I?lS=t6t|Dv&8&^JEFJv>Zqh@b^5M ztWTm+t`&z5S?69XEj`;ELIHe9jxWzQV6-kX4&UH^J2Nis&INCmbNAJcUiP}DmZ~UY z^M??lILS>2J4+3cYJE66r7)2f{EVgc;>d=_WAV?W?y$d*zJ_S-Db~!PMu%-kLLoYZ zzBLh;H(c43=f2ZSTXwR_Cr@0M_R%3W==RFC-IaooeY2^Zn{>0HkGF+vLv)i6AjIs$ zQkjX^IhD1XI4L^}NUs=O^mdXf$5fku_|oein%kM&=pcR^-tYS{bh40GgcTR$2di}d zEiW!k9Lt^B%)X6;oaROs6>FQM%gNpB4WEY?Ke?Ea3OIqEVb&wb6hFc!lg)`7Klk%0 z7iaP{8=aMsEvJMyU*_Gp=IMuQFLnES>_3Y{fAZKznWVV+dcu;6p9|OnUgy|_p zr+>SBWveyf^TP+)kA(RXCQh4_{qCLB?%QKy!%&WE7l^m`ow*|=MIx2ohGFo2`PK%} zkJRto|B*ekWDwh{5X?)9?oVWzPs*K3J>=!Tr%7_;1r~Crcs<}xGTa=e>(~Fy9X;pP z2wV>00-T0VBn1&HGurPhO`UDNronGDsCij2dEk#+%gevW7GNz<_QZq4ESyASz36A@ zl0SeWkLELo(&x-~>vY-{ltbBm1N6UlY_I0jP}2N-Zdn>nmnWaot=L>!?G|Hf;yrDW zuAzvRodikWIj~hTzfT5Qz>pX71+HPkuVxUyck*Do>_+?5Xq66dASq#y_)h_PY9Y z@O}f0Q=2cw3zFYqLFm1{4Q98>u1?CI>{n2bpLIYopTI|PqZFz|;5M8fSwP54(hl*a z!}9!pmVtnnj@(GP465GUX~J^M8X_OYNfHJ{kfd?R^mCzJn(z-ZfZ>}`fQ&q&q%^E# zI#=~Ew97|=vIavGQ1S6;JcMEgs7SO@*&$RgrmjhDJ;ct)Wy{QmGwkf8$J`;Ewk54M zOjRiKx{)e1PPNqmnIX!in}2mLA@(;9(F2BqczxMSa*2A}N#t zyuTlV*mokqG!-1EnQYyU+!)aBvl_B}f42;ec{Ne60w^7Rv{p3}>Z$$(ZQW4TU*@^> zRLP3U;|8|h()aWrZ?*;*iy*bAM|--3(14eAR^r~T`A$z1WE+-}rmx`3aOvJM#=E*o zF_W;@lFO8G9octaCvNg6G*Tt4?#wb+M!E z#B^N$`Zza(xuAx-&x1~~VUyUI!yx)`$Ft#r>B`t#ei>69oFG`96ep!LD- z{lEQPc(V&y*l#(3k8u#L&oV5yU8v2VvC{l=31z}&CA zql|bpg(r>KBU5XY$id1a5a*g%8{|b~6&nuO2!2IbE!z$!K-2ylI|`qWTCm;iP6Tkw z+c5T}+Nz-hU!Ql@VRxPYyY-9SW^u%)4Uxb07K|Jpl-E<5nH0!_PQ|>io*5JSw;^i+ z8d++9J8=|lX!)t8=4LuuTNneDM$7nyenvdzXrox;fq75RIT3O*`woF_8Lv7X_pl~H z#~m6hd1=qQgJH~@Zu$XI;aklhB9qZOD9xvL!N#CLA|XMgZ|KX(J-DqVWJ{8LQ@uE{ zOa$uh+?+HEf1P}DH_FEFq|O$_CT8zMCVxtcg{+2(XFtWh7&)w!TFR)cSM-1HBESdB z!Js#Sl!F&1ygBeoj@#wvw`gUVxpc$t6)m2>esdPU3p>KUpx^iC73zMu{lLHdb(?6( z88NTBboh#DfNRr+^6l>?2h+m`T0dVa*;}$_r0mF}kbA*(wW^CQZ#&h8Qlk9x&9t|j z%o03*=M{3N%z)V3f7`}u{rxuujX$9ak!&m)= zwVe65hKDD8esIN1*|wKbB56A`R4&4%A|V23S%yJ#&GoApV-f~v)9MoEL_jgi_^Iz1 z2h+h>NwLs0PGaz?Z)RrJv@rLx@T*XAh!`Qt5`0oKJ_uV&@Zi#3k}Y9^H)np1+bIcy zv@%I%9XfG5O_BAzw^9fZ%)xP|;v}-GzKe^2V z(+8|Vc~1I&Thx*#LHnN_gwS*r(l#lEt||;5MEe-3Jy)gUt_YhzQMqI?pDpZGYSYsh z*ipw3h_$Gc+Q-mP8a?cKX5C{-kRRM3ZLM!yVr0_F!g$LuPY9V?F`GZx?Z~_A9`5xz zd4H19>U<8}&fCptzSOVyQl!4ptTDf=zwihn)U)H)FXyj#sAj5W?yQ*f%qc|gd{=&M zO86}3(zDk5-cRY_Z)>woda0j^c)MFS_=)fK}lxdW~j#v&W7j;l|}h$ z@{Uln*{k|SI9`NGj>TyK@hW4n`>VzWR=LHrHeH0owU$GuX&-#tP{XFVxQ)h9UjB6p ze!n|&bPfYs6C``&FIX#(un~6lnv@_j<*tfI+kV$Fs8_zOKh~(D-tVr zSoPtJi^FZ(v^5Kd6=p=LM=!~;EF1@AO=@4w^1*SU%Tolkn9pGXY&uVey!9+G&J#j* zfX%#WfMlStk?6YE%-(3{FG%ZAam1T{>rq&cN>KRdrpjGi>j+M55_(RW+6 zq82#X;`YqE9 zap!tQV?A|LKa0!rZh&S2bq4Lc))PDaS=MGFrRuWMY~ zt-xNXdEBVC(M_wPM=NyP9w^fGWcK1JYNwxslGlktG8q}l?odI>vky8C^4j#&2QJCN zbDw2u49%wyNQMAD>X@3{Ck~0j?jL(zv_p@(&M( zma{5%T3en@3B?~Cm1=IAHZR-DDPQ+_Yoc9qZ@X)Hc-%Hj?VHo5Lt7s0&-14m?o5A(;iTDVl76>{(V2;@`dmX1tIt8{XE~&uqW^9jO zPLJs34;9p{SYzZDppSdOVHgeAzEYPwUgLfC>na}`y}Yoib+=9j@w&Djke>kB2}?fG zG?E8w4`qu&l6OPE7ATReN;V@pi6}GyE@gZz)I%lNVE!oF^}Pn88J1i7(D0Av&!w~e z*Kg=-UygeZ!tq_uf_e`6ne4EwRWdioyR!r61X925GG|P*D8Hc++5hnQ77!gQ`9o;2=O2%uo5TA_`P4I-6^*PiCtwzpN-aTQ(C}SS; zey#(_be_!?Vnzccs~qa)&x{lL31DIk`y@$Z2_(mP(G;1emR+q{$(y< zW=&PsJV#)ZfS0_xK67ZU1C0^yzvYkG0Ef7%2I9MJntm;`Ex9k4#rO_a%|tiQ=0Jm6 zwsj6A}-tWm98etRsevkmkSeEDhoMD1UfoD*y89XejDd+3X~BHnTQ zvW?^JUzsbHjO2Ggrw=%VAmCtB3u45YKamU+@6_jf>cwj+gNp<4Tt%JG^tahOa=DPc z6>jwrBQor~;#V15ik@tA6sjDb)IM!hkIpiw$#e5JB2^t&x=i$JzDC&^tXOv0{?caS z)Rz`l_IF$)YHuN}QQjg5EWjK{4E5})UVmRaMGOhdI9q?_d5`isJK^mC-zsGC{tpiY zrMbeoAy6E@%)a-x<~xCV$;9d{z9VJd%11}u24H@6D(>MRH|#AW^=0h>sHuclbA;Ug2pPGJL7uSbO-}$ZsFfX#mQefj!NUtrHxqGDvWI!5XkseoL3R z(1}#vLCOiEC+x0|-|f0z=UD$`KC`HcJJ4Ny{HXZMOyusfgLv=!tUiPF(UUFvijO27 zY{}G1w2e06l)66Jfvhg}&1+=9fj&XMOkGhhpE7-P~zV|}ZyUOL~ zw?@V%TuV9$*tLSSq8F}}h>BCUpFZ!%E8kLI!~ayQAuyTZNNOgq1;yx?5ZhCq$~0rh zzP=+(^oTr0LFtceva29uZUS+XFkczehEyk;D>^CLK5-h^6k&JNsYl#DhYx&gk0ljXcK_+!!1zuvA{)rI0gG3+vjEgP0woAT zn&;Fv?FEx-k_UuHk*%w|h(PrqM*@xeYch`hakTH+WB*OaJUuz<{Dl0iahACAhTS@` zb!*PJDxA%yuOl6XGM^R3VAjK;*6Y@3b~ewRd7of%^_qeyB>~TTVpa%;e$o68wBfLV z&*zmgH(%IPvRg=P(8?%SLla|JpzSZL=-Ys4?mzfv7c-4TcyT6rPUm8TEnEZY{cCRztb!n~@ zpl`9W4}Z2g2=INWQ7BE1bR|Y=Lps$T-GpcZ5J1c6*Cdyxll6f)<&#T|-UTYJhi8F@ z*qf`doJp}C{%W8ytIt9n=aCy15dKbLuH&Hiol*-hN}3=DG{~Qe24a9CqSXN90wG0f zZx3Eb8+qrCQGpLvd3IZgr)WwH9gs;SE0^rbHt$hNsG+W$8a}+^&h*Rn_1occK3%r8 zOR5aJ3RN6{(F;QAB)ihU%>Nj6U4x|xG%wx$Jbt@#O;Hg3DRHzt)7!-9+a&8?|3e<> zW{~%p>?NOlOW|^@gS>=mui34QUF+Hpns{jGJ`jZdx@Ra;$FkYu;3K`@m^WE(tqtn@sax85OwtD6?t&8iczhBtZ zS7wU%{sU-?p-I8=G9WvL1y;ENs0ZA(%`9WMDa+otWnA;6wdY~=KfI+rL#T_P7EQ{B z`(=OA{KR^7*GfaRP3x@vN{aByOmS0T;D#Loi+7*w=c#{+q?LDaIJi$1Cp}9#-FJy2 zBDCX;CbB*HXGpN+L9~YKApTE5T{WZ!vhc>G%u)kJL79lR(brLE8qR(9uB%mV8`J|% z)R$P~2TGIM&X=7k)E*ebJ$KMb^}-*?zdl&EM{xNM%;UG;_WDLl zpU$amd&_*!vk2pnod*1`Mnw6Ba$Ucky6dRnBkr63w{!00xGYSqk8C6Fr)=9bnfD;e zR?&xB&d`k#?PeE;=9)3VSP>AmSbK=Zrv_{0R3`P0_67}DU=c)CPxG*kZD5{m@l)GJ zEbRzyd<4d$x{+1)vN~yy;{S{uq%YhiP1;XCAoy$A^3NzGC*rlB-_ps(W^Fx)Qz!C| z+fAR_+h1m@Y3O=y!s)mBHt+LoT+L3T`3@Gl)?738Ogs}PW9%%cs>*hC87XhDtfJS+ za!9{|(hbz}`RM(%xgvp9`y7|RudNNrFwMekBANp?>7u>Q=$0KRLWEKnFu%(^l0NZP zJoa?`H}r=UYx^);*`a_U)c}akBoqTPk|RlhjinA@n_FBW<`Ft)@U&Hp*NCNYj`*Ok z7~sdMcIANJd|ZZG=eS;y(@w8cP19|ZFN2Qczguvwb9x7@n)Z&w>WakoCTqr#R>clF z+O>Gs*NpWxd1zXyY{au69jEm4Cti!`5zl=QIMF^%_mN1(pkm)Y1dMAe1 z+VjZvs zjOx@MRlzCw&aRstDavmaoho_;!F$c~ojqvg9x;18stnb3Jr|Y_SByE&zqZpuu5_~4 zH!!e6gMOjzX+Pr=F!C|#41HPyrVy-EQ~!Dv@h?dR|J%Ht?9)$p6Ti?eWHwBC8u*1^ zE-IrEa};Zuj7EGq%ZPoeskye!Y7b_j)D4Qvn>+jNy2n`#>xI1B8} zp!8yQABN~tta;a0X_mq zD5y(6RUAW5$z&9qBDh1|4!j*7Je1meSu(+FD1p679O+>!E%C+sR>>CJkNc+l5+4m+ z`PhCDW5Wii05?rUmuUcjvrbTbEy+SSjj@HKeL&3t7zTQi5u^h{6h()9u{)^waWrU#gJ-Rv$g`~kUDW9KzppPCSl()%9VRQ-x`4E9k3t= z=PX@b#pdU*H5KcDsy=U>={KLkz2V`>8)tW{eLGe`S@sBk8?zz(s`Gv?H_#T!~*}_zWPZOBnHNW7n3> zdO@)g7ucMS`cTwS361hG9e_z`CpPU&wTp*#_8Ubbl;Gku&q1n1*2Q0dCjwwvp$whY zvZ%m_9T$@<1Hu*GM2^hY)yot`M)U~|a0e5D&e5uWmJtx}UX*MTt`k>d-SJbz*>EP8 z_lnwvy+KCH3md&zHo(-GphE7&C^N8|rrD}7b2>dCdS${f{nlaat9K(F+U^`><#$;3`imCla7OAqn_j?M$OubX_B7+AfVxgA@5a;2kw#QGSX0M6}@cr zN(D-04n#+lWOi`kwIH)1RBIaY4sX-BfnUY3>GNmKWevCaIo&UD*qvQATQe55G50Id zPktLL!?Jq9tdBTPh?%)77D+C{MN&1zC}n-4&N;Dp8xY!W&kt%P?@MmuDKkvZhMol3 z{YkNTiz-VGP8PUh&g8bb4iO4D^C7kDfl8-5)tAA#3k$SX@_uiD?2ud@GJ1x>{k{%@m$g_c$^(>(|&x;?CNV!OJS%aFl$Ucdjg`$jZ3@4TtOswE#!;GWsxaq(khNyc zo9^E_`NNrwSj;V!*%#gxG$J$`hX?8>7|2<20~*R9916XfTM=j^K0dzTmgppBXMbhP zwJMV1l`02H$qS`DvbI(j|-v}uexVeNz|kPK;U;2nmE}C@m#Yp z08(f}$n@12lAC<#eygPME!MWtS&94eO#e4QLmJRL;Lf}7_4*oNF zGs&kBYNYPUB1X;$Q8#N)S|3BzT@F0$M^mj{EJfKp%QE-h9OJ!f86y1OvR~-!JlKRf znK(rZpz z>=oP;PgQ3B{L5m!6D_8AsqXN! zLw!BV3fz^Q&zx%-s=Rm^aVAf(b~Rrm$gM_QV(p+BDmrC6nle7)k3Ae zsDg_~wZK)we*Y|UZ-lPq36ZdyPs@Wozi>ajN5B6;U&l1RL2urYJK5VPs~yPPYSn8VF-`5Z1$eMMiF9@hiqj` zE+W%petPFpQc)=!)ux?%$3kv4d}46UBGe+^{A3dYqGeJb9P)z>->@B(f?LJ-fj&eW z7WTBD(wqf`RGAV~&F{Qp7fUCS;{uSt;)Z=k>u%Wae*adewe>;Z_P%Uy@#)3=E~c}S zFZ<@Z{ieE3X7;?n@M{i)F7akT6Lys@lyh?hkKAvCgHCi~I+lVc$h4Df9rMnVz6*AR%!#G_TflvDgY3X#kr$ZGXNvRBXL-dYsoy|8W&^JbO_Ag* zsMpNHDbZ| zp!R(E{l9!3a5b!#a?-hPg)GVjgZqQ`+j}Tdu)&*8Vb0Hu{!Z?V&VpTimdF)E>mvyrtW869LE9>y6I<-{go7m0k}uk00L@fe5uG3~^>-PFo;~es zdjDW=*iMNaavVrPLn2 zAHle}xiW^YGq&FJI%Qoqepzn?rRHmUmS6sHM?*nQ+n9eVO#=;BO?>X~Oq3=2j?AWt zxG1?cpwEUwC=q3o=s(sHZntny>*Ys-s2PSJvH zw@2v~FQXjw{{LrhV=m2iW8H^p#xQ647L-l>j}2B-TG|_Xam@H^PK>>kV(r(87!~x= zkV^#c2QUpz{oMbR>%0B+tz(~)oHGWg--5!RLy`|-noRw?Zk#VTzN^VC1xWB1Lrj(& z(7sw-#+GeTs5l>icyIiBl1hIWjDA}+Pv>s(jssI-iU&SpeVi`3^`zKYW+=fHkA~%- z_pSpSizXg)9Jwltrvjv^aW4VA1C#ETGrLki-$3r6samktl0pVaI)E?EM5@`TZBDqP zSSMCt-;zb`VwpgaY|QOXeUE!9VwlzrM-#9QkBkERo6=a#e<0ES(+r%vQSkw{8p8)w z$#Q`1>yi=`Z+Hlqmedbc7M9LTmdKRgVX2YK3!VpMQzd8&u-6l7O(`urEwUEgb}WOJ zaA`NIFUxdgTW!vXkKvv=ac>hDi<07d3S9yFTn}u3=ujbm;fB-|h-u!b>nwY;iHBuARnz9-55DulFp;%= zfuo-6d{os1MdFvo2%zyGC`bm|bVg321dnZ!tOUl$h><8EHkFKo*R=8p&2z93XjIm0 zBd>zi+=ND5`YIZce4tG+A-RNAnxbixzz_MTb^aa0eZ;5}gBJEdZ6IZduRfC*aAN$U z7!b%sLQE@Sq*}4IP--b#HdHfrC)Y=|i6?Z@=cjFwrjn#t9FvnAp->?YqSwgNWY$pXR99e& zemRMa#HFD`4fMriJicPgkx=Rqd-=jZ(b$y@JJlK$)qTGpvys(^nK&^`5F*n|Q>;&r z{tDN3wbp_)zz(OaB)0>H5yzNdx%Ea|Z@vDDnxD^R(gh zf$)=GUni-p9jEwy`CltR{8yVanA{#^au*Kr#=r*Db{y3&R<>zaK$!V1jZn1V^jMZe zwuqgC3F%2;Z?;6-r0HNnuv>^`nuNl2Y`AtfKGofEYa%HY>AkXej>% z{KN9V3n0NjlwXbm9-kS=TIZIY5uD^#fI|zTjdV#E^5-l=#Sl_iaRBB?Qkq~)Jfz$~ z*@l`=5ZP}M`P_{BxeYgyjzNpbInJ*dIS~pigM3M$=A%3H85K){YG@cCwWBm^untYL zAvXeSM4#Cz^#Zng3~DTRY~ynTZ!nr`N=~%1sU+r|7FSgZKmqyX`tC}R>2E6NQ9p5HX>r)q zS}n!yA=8>Xf789Z)5BGdbG!z&b4+F};Hs~mGv5!5j|8RIx*9F}w`ElYLId_}QP@DA znfy5&BHDkEY`!s{bGne>jG;c+PmKZ|CWA{1yck(Dy0UM%+emWDz+SK zml60Gz$vjLjSz9g@>6E*)qQ?`ekNC6gh5>Y5pJv2G_?gYBI0A;U=y!X-pql1*jltM z=>UAVWU@MJAISzH+^z}GYKa;d`_Ne$Zg{%i)Kq=cziH@_@XW$2*4JN-#YOE}>Ki-^vMS*n-Yfl&;#65=xB{e-YQBc`xa0)ybpCc04gS~HBhG^ z6^M;yH!m@hHP81luq&s>)mgoEF8p{mw_4nb5gyU)OkdR#PCm|cnF17@O_a4lM zn_^vnR8`S|*=UJP1E$n!I7-XkLw1J@6XwVVNLV;cY%KC=w}9?3DG}MNwjminn3dUK zJCyju?I5=1!xtazOR;?p1OhqoF{3sURfygQ_AMwl-r!PbNX332zZ9o*o4kuWKsM-v7RT{2&-X-8v|^wWZ(au9~J} z{lDL$|8{Ht=kNY=&;PRq{<8-Dvj+Z;SpzHo8Ch2A45mJyJ^Q@0_4IOiRiC5AvXlS* zYq{bI4Z7x&0YEbVSo?Uy4L=@C|NXM* z^-I`YaP30I#M_#zzdE*WGVfO0!Knf)9xoBp)XGp#vm^)$K(=%xV#u2Utrt31f&{#^ zUux0FK9i32qwTd1OGl4bJ)yt-_B?&LA{q(#mR|(JdYLXwszpS9Mp?Zd7KO+*;ajtv z54826v!leQX5g~)?&-6;UD~75K5gy#?i0u88-wc3VdsB;ydo8N>3#>8vReyq9>^b{ z(@6LrjZd3dju-CfY(*vO9fCEbs4$nOrehz;y4ga@;Up3P)G<9_+jdb1lfM&_COig% znfEIxugKfJxWrmMs*N3YUUDa);2?nP;?rAct4R8=r@$WhhNeQ+0gW>#Zcz}v}5RrvpX9VNzWc5k};RCY2#Kv zIgy{wLy|3tZl^GstnK73+ACffDWq^3r*)iL!BZo-LDd4;@KtVSTP#$;Qh%V3w)=2@ zVT+H}uPV^nG>!{&E8YDlL*pbxA9QEIUHpm#l83;BjPnG>y`j8Px=jfMjOf^QLOSrL zrnm5J@K(qS#C{^MiddRo(Ayx(&lw`xwl7^mnhtTU!&bo%D)W%d$YeW|5UJc8@%W3{ zwBP32=|wlKx@D60Wm zz=@Nt#RHVeLD1;?RmS_5Yn zH3s?)#?s4~{5QUZUMKc5HBw&4-{fP7^XFr!F9j+I}KG0;}*k}zKEhvfxl>0 zwqCwMQHE+5?!^2?KMB9dYOPK!RbPR;m{QroGmmPkvq&9yWLp9!%QTVAPr}yB>kq5b*zKyu;Q=Lbpljh5c){vg**MRT zvAm$VxX?L5MRbCJ3!$FX5Eb1xzY#AkJC$Qx`?qxq7WQvXHc3Yph*=*d=dL8|hRGsl z$y@c^w5+4uVavg^XHTnMmxEy?KGY)k!NlC#`~z!dF}M$pTUO8?{H^@!h5nUNw~s6b zf1}V3^!!vhO&rV?tOc^8GXEecLBFmy%++J!JJ4wj4INcaaf6u&mc#hw8mq?55vKBom8Dq02y{Avz>>Sh%eVu;NP12 zM_YW&VMliJz56ug4)~r+&VK2qFtRfJz(C_BWKDoqcpDr_DA`0-yw>c4{=$thxWYy4j>omVv@Xhq)^dN9;DaNPKmjJd`cSlrMwOB>{?fz)~Z= z%*7|xZx*B{@r;NkZc)K9KE3aAuGtp{2036^bM9t%xZ@LRZb=2D07-Z1UDZ7^IS+7E zlek(FqeA-%$>REcj8pu7ap9`Don}2I>ij0>E}j0O|tq)i^j)`ApgcY*YanXf!>AJm8o-i zveVEjJQT!~ZDOVo=Q@4Yu|5uY_BdW+dXuhZ2fBerSB#%(_xYmEr~)G{&LJ+DNWc4 z&8K};ta-@mRwkyL9w_8tk=+KZJr=TCa)(joa)j7yw4wf57t6F8sP>W$WdT{06pyOt zpmx{H15f1ug%#67*MfPXwP0I#(6ss3D?)3d#YmkbR=Nn*yHa*@DOcQVv;N^ItU|fH9<_VpKwCsZMYAe|Dy@zL$MnO*gX78cam7DLcQPzzc`TWv%$a|zwyUA?uiGj4WHt<}ftvl(^?O?wpEOf?t@ zn$5z^UI{l-*zWDFKX^aL%Y=EV*f&sfgyvk?;p+qKMSEv_++E&PHn|RVot>m*%=yV#%V z7u55{nArhdT_$2?Enf;an`31fO@huV>=l-z#f*NeUi~Q{c?CVJ;W4sM65GPPnVOSU za_%189^OU3!{V5kGWS?j+kMu2Cds%-hd1f-{M9RUZb-NVp$R%J~;EkG8 z2pbR}tENF%Esq+IpWx$-kO+4;^NJm=4DDc6s`lStA?JJ_XYcQ;EJ26@A2Z8_PdP1* zDt1dcmY&$C)w;!L^+g7Ncl|-+=Rp-Uavf!9@VlQ%fyBX~!@Z}vKlX;$;YNA$Xt!~= z`cSy0of}?NFjCCt=?=dCFyeW;|1HXK1B}x)6Jss|VK^sJZ<$G&r+H~?~oD;4l4t_lVSmGY3XDpvx2DUYs=q!$5z){5Z(Zoy@* z_-oJuIZonqh5OZTxBZ51*6ivhy~3y=Z|?whH{;@>pGub0WahWpv`lb0Zx}c)PwMj- z^rj_xhz%db#}k441QoH-8)Y{02Db_|k%KZP+R&(fbB?wE+j3ag5b?aInXw^T8i1VJ z=yjjQ=IAp3w{+wVKaGz|gS?K$wSLgrpRIDd^6^Ee@uS2m>MxIfF-?v12`DV0{eOe36gY-p1au5Wk4A zT7nzSH}SB*=}F~^d;Gp|>%}F~;1%^w0-W-$w3Ex9Qd~THMAAIvKmJPQb&6YD$$+VF z6o3#4$Iyjf5?M6}xV5W{9_*8XxW*Di%BVpnH$iCH)|m_~EG?S}iz^McciHn!?j3y> zN#PioRC{|fHIU}OGqCD#%1bCp*kRzRHT+`U95xgL@+#;M7LK6C5LPkTZ78`_{LMfZ z%|zlikXXOUzOm>oQsjip1CY2y$LL{5PT+-;orS4ttxn)zO|AM;^~afjD%#9)ORaA~ zkxgJtdn(!(APwNK7to5@Q5(tpn*yRL&6ra=kV5wnrk?+XBRaxk@pRjwV-s^5dQN3}i;4*DS4d zdnR*2$~&E^wjx$ifhDby6vr^?Hm{=CHW4rbG2=Gz4=MFKgs$!_-Q#m-=Ms|4r-xke zq{m1NicwPB2h_blv)YLY&o)P7M$(y;ouYYs>}8`3K-*f^(@wqMi_vBzuWX$4=fKjA zAZ~RDAG_rJixV}^^lZ*%<=kmoc4wOsqAUcGIg_#|^aB}7Km4nMwFElpk2Dk@jCU1s zZzN8D)uiRMxJS~nuGoYTZ0g}oDKFMe1BRNlefC$|_zkq$M;#6xx=k^+T-H^tPf-cY z1y${suVBOcN*-K2E3t;4?+S1QMoT58&%xEw9eZZ5D+2UYjFvv_iklp6Qp6Sl=5%i$ zmkee`Er^;Efq~&s`2oJR?+lLioqiHlk!7%6j`)C8UIwoG3To0~^KbKD5;>l|&@M`n zeHbnhtsV7lIM)v1#*Cr@Mkjl@WrM>}Np%4$SecWhGnN9F!snzt-raHyerA`QeZFJx zL*BSs&HAz+{irqE{=47NeE9|Nj$bG8c|0x&I7IP9z$8lm5_n7v6i3_Y1N>(g&Rcp0 z5kr*F!r+WUUW)SNA*lZAmHLG@{_3A=OnA_y^?gr&sfEIwlskgH;tGGq43pkRNrwKYYV1*;NHh&tLh!9NF8PGtVPcwvcW}o}d%f03m2b`X zv!e|SPwJik+Tk)l40HHhl@I=Kz5E%l0S{O=k^us#rr+sX1!R?RR2@nsR}ou@n4sKM zuo`DxF&G7^s&O=1yfy7Ek^isBAy!Oo;aABJCt?wFbX$-UU${**m#y~7$IL=<;-+%T*7Wd5% zNyx^~cfA|%`YytSgt&8T^s6edC$F#l#v1H&>4Nx$OgI8+EhCQxbkiX|_7a#OwHXO8 zGVGnY8jv}sHkAONY)crJz&z(?-Hgr))FN6P2VEvPU*Zo@7u&S<6|#WD--Wf4DSQkd zj{}eEK;(m+Wq{zIV4OC(a25`d@ylMMwAw&T_6`Cm6THy+lLXD2vtG-=k`K8dJb#(f zNL62saAf>R^z|{%#;~lp9+$?R46_yV9#GgZvXpF?qb{i)&dYYPyr!>8A-vgli+imfVy06kG_N1$s$WIdSufo%+bOx4d9C7XAG4O z_vE>n^vdN`dZoP_iU zMsmk$cr01&y3#F+l9M1Ud?ttn!`zHjH$%kXIHzOQTxt30XZ;yML|OI)Y=K*h9`p}I z3Ur*!O#lQql>4;9EsSl5A5QMouk@OjZPe}yS2uLnUn4VmM!7plFzR1Zk)d9k zFgL&Agw5X7H#)+0CzFzi{EOg4Jxojma68_6RvWpprX5g|;9zXZG@fhRj$LRW!U#-m z3p?-}e-1+r#x{$!21iEPN3qLkJ%^w6f3+m)2QNH#ZhnFP@w2OXD8imoPit!H8>h(# zm{7V092T5{^;ZZ)Xm6aH| zQdiGw;3B#L`Q8@kSYD#jGUxNQSOBR9)qZBqFHp_tOa0KV?em^v_|dJVIzr34GD)?s zzTgaLtP}8N0poN6J}KD49xJWsV+R1nU6o%t^Ubln=z{SPtr}!o9wOd{_Wo3gyA~|* z8uQI=`3o6pN3CU~LAGE)*Wo?s`_i3d);SL?Cnt!3mEM?PF}DbHg``&j09RcS3#vKd z#1iB_a2N*AM-uqLzoGZ9WdOG@&XUI6#f&r2MIbg*O;+4t^RKe!S7MCho;_8hB@lhD z)*#^tM-obqZ`=T3n9({o?V6t;XZLomwznEDHRZrP$22p5Z09_N6?nDY%p-=B`5yu1 zJ1PE!=xOG~jo{zSP)vb%SPxs^L*E47%uJJe3R+?2Wt^qq$+6u?p2Xqcz}avEipg8= z>Mr>K)871e+rX@WxsTv7{0SLn&jMUeaeKqC=jcpCN5AlXe%I%{VD4+)^ydS?)+7$Fr$^zcOp zwb^KCxA_cDbJ9K3b4PfTo!9xBr^3T1rIZj>`M#4QZ~KLx_uPZNhk_dgQ9QqZ2S&${ z_5RJ`!AU@J87HA0l#oR(@VRz(!pg?nNq$W$W?s--O%qcSFcp;9SE5@b)Hpl)Gg81& z1yZl#058(eSM%ew5+KA`Sa#Gne?v>ZB~qjwarEKGXGw8&i~pD*W{Np@vy9C$JwK`2 z48UsBX1;7ZD|gBdAj20YYUVG`nmp2FXDlH;2O7f(aN1bQ)j$^lkbrZI+D!@hYP;=r z`P5n6&tC_=1Ie&J@Gkx60*vKGsz|YHAqr|xPsUpP-cbnKCQ+N-@bvkCQhj0@D6Vhn zn+B;SkO$V2v~$SV38Q6I{l_c8$hGZ`x}Jjc79*p6ijw$=#s93qt*gtl`dtaMzv9{d4QWZ3~pJchL=s8at8%%=p}Uh<;$huHeP>Lf`pr zm9U$>OVp*2NQ5v)`|z-kr`=j&a_s@4*J`r_a6w?mL&3H4OZYDsZ%fD77FXc5X+YmEsZ-UZiflPihNIJ~kYXWI z<2`5-^j~9r;ZoW;!?q6aO>qMK07dRJN;dNC2z?k-%S8~be!it$A}!-10ADd{{#3EgaL9FRd1Iq8W^4PHwmWA(E~ePI4>j6)#8l-GM7 z9>w#YBAhZ(QHRO+8lP?N&e*O!3Y3~^Q4@$iYyX=W^SxNQN#d|(cQjU-A=!OtgQ zjlOFTpVFYMYy7UhT^#}EYivq$OsxNN#kJw%0ndq##249GBGiJu7}+dI?s+LQZA}N5 z>m`kAhoZB=U;z;fM&u#)vBsgek3S+G{_sw+wfgwE;UpV4UVfTaFLze11)&Ss(6;l} z@$Z?^v)$Hvj2E8*kv*jTMf1j|hsE;^H!rGCHY?XZb0MXUaUIC1>U^h)u(FfrE9Mim zc(wqict8vwUnc)n>4__-eFU7~ws-Vx4=0P2x4hJgliOaPHLjNUeDV9UHX^>V%njXn zeSwJiI{Gfw`5?KSDvAS}^A@9tykLI`h@l2e1*BFom)EM>MmsA%T-<8jkXAqMbLIN7h>9{WbLRoW#GyeZ&O07x-|^S=ZLF%XY4Onc3~6n-XCd@Y3YgfN~reYsp>JS5}GB-U*+% z^l-w8+nF3radEG&d0hSIT`Bdeum85xL)(1ppRhS~()AK{D!~{yJ16!j;tGHugp%zp zjB1>dwkxu}&aicmPI6!UL7~sdaR81t3x8W&KA9Q8Exh+9uhnw6x7$cW{%ZU`Qy_+z zSUGn6PbDDrqjB@ihdwU$tVb4CTgFuL{fGl z9@%1my826jU7r9yD+a+xUn@kd{3m_a|NQPh=vgDc%c^Z@U&DqS*h38`Q$L;%bjEb+ zqRWaPO#wOD`a<3;)D{Bx+r&MXiv@~gqrRFLz=(1vypg9qF? z?i9W|_b8$>`tr?z63x?Bcc}C%+x}*Cyyt>JgGT_*he5WfqX%yOQsNpUBGEsUN;rb( zUz1%fLnj>bHu)fU z>76I3^Qe}7Ju{t`zQ1`}@Y{kx%Rf{cPoFvC9D84N_l+qC*qG#~s>k%;aC0mmoe$6vLiAm}|?1_H+M zcIi1*5wPtG>JLliPy+fIh;OkK?(WOxDH{JVK7qo7|6KMGwnmpkDG~^qCtp|$Ys`SS z%yg{Ht#|Q(xEs4+eEaKk%!Kun=g$2UKO`JdHi-F%yWi5X^YMe9O3pSvl?Y_SzMXGX zTmV4vOBH3)J3?1RL}FT~7e`k0^5^SyTawf#_5E8q6Mi#$@cinBATT1FWW7{*wZW-- zKd#0Hemafvi6?p7MI10)d5s8$SiDJ5Cz&f7=hHVM+0oR%N{7P9TpS#K^C6PHYh6Lc zwrQ;9v7ks??}oRJhCN)h>Mk$dPRIg49A&u>*lC=3iKqieaCpdunkf!Anf(!GKU`bN zXh*QXKQZ(0vo!0jX)0G;`WI#G@9^9EtFJrbEX4ZqUsN`6Vv+LD4wHh=BHHID&ojAg z0wMYx0@FcyVBTy@_6OZsQAG6T=?0?fK#pRl-~Uz<}{Z^BGFu=sd%2^C!Em7J0yeWqZtPe6zXTQpB% z#$oC(bZNOlY)Wm-lw?%o2C}Xeo&o*Bg8)wx!qC0Dq*l0aVhO}`}e^;TAgi-)!oyM zu%NCXTAR}qtWFWGQBuqYm%T`H0-`o-{v~pie=NZc`TAW55DFSllQp_#oTw{@7`1yp zfFWZ`R#Wh_B&{q5v#+c28Rhc;`XM7im+N=$Sw)(9QdhZOmXkajyw{+N_{oS_Wva+M z!vX*!2mU8$9~M5yx|D0HCN6DkC=O z`=`>QWxFTHT5=+VPu1HoYBnDRDa7K&m5A>Ov88&j?H?2t)(!q#f_*Ppld|~W+fQx* zO-ge;s5;0R*Zm1h5eE%61pic;JKq)jEf!te2BhRjbZ|^maDOotJj~>4FebqF9tB)) zN#v?_xps}T8YIO_h5}}p+Krp#Yy0dB_;pmxpsoO~joiM^_X;}~O?cIJb?C%BO6Iy< z7+L?@)n&%IzcqriE%q-WnAxgKkZapYmq=Jsv$BJMxmw+gt0#$-?dhoLI1K8GZn>Mf zKEupJ6e~A*XE7Athx@(SgSDhcfD2kNRS>r}X;nNm%PjHM#q$vS7g2uCSSkgrQ;eV! zWs&1<{s6JY{D{De+5sTy+MZU_DI?P8p2S-?7H?;ehY)&NvU-gS-+ij!I4;Nu7@$0JXJXsh~>_iCRiae}2sQN?wg>pIEXwsdtE zXktFqpc;E5r^$d*NvA?Os)$9myg|X#`ISoSGP*TrUlv;m9ZKkX6}g1flUh0Rxt=Gp zR(xVM)n;A^FH<{I+vri{t!@`s5wvBZKb|ywp7jYRG=YP8{@5h3THHo4lYUdgplip; zU{JN;?#sNOk#z}2`YhAc5t6XHh99+#El`&f-VpLM2Fbxh3SI_#b4+6b16D`>>ICLr0G>+AgYZn5h=*@sO_`84@XL(BiwpIyHAk1Hi4PI8^6UC@Jx67m)SB2e*6k!!dKfV5 zbK;r0^m#%3UY}Fp5^0{|YU83H{qo#z&Q$9Z`n-n$OA&6%i|0;j=G0Owai=&Dt|7jd z%7AZi=R-}1-5KnQ*cc)lOcsU8jG?CeKb1~U(|C&jb2~nT{9B_M@?8oc*^)6KE5+_Y z;ClV4)xE5YVe`BPB|@gMqbRHV&-daE-aZW)y8 z0%XrHzFoPUVP6d&>z^fT>uuBLs3x?Jm=v;nvccN$*Gen$+(VV`>)oo%CP%UAT6$cSDr$xjz_Iqf7d^6wPtSv$} zcj@)6ul^bRVu#+i)W@}H!3f_yF4O`XnvWkU{Zz8zUm$=+E`rdxIX|Sb?z6fCh4K#9MDdm1(c_Eye4VBUf1LpUWcAQ$0fN2Sk zI~DJpKLjSOdqY00Hn^~y{wpb60{o;7F@%Jy$AY$mxty-JZGnxSE8>VRlhx!nJ|JHr zjbalcBQA%Z0L=?Mx&Nr`>*%ff>MEaJm#*!yOi^>mxW3@@v)g5THI8PcGn1o4HJZ23 zG*<2j;+J+C-E1LKl{U%o635n`5agU34w+70ig<&HbAOUhL;YwKbbZXVZ#sLjfiO4R z=vfxBHudSL?buep0u?M<0B+8h$iIt)T>&XUiZFBP0&FI95&;J2R@4gdlZ<*w)>hV_ z=Djo9=&mKSlIOnbdPNk=MDyc4mTaZqER)Q_kz<42oD0RYQ!TpZuO9=HoyYdD8@yO@ zO#st+b}uk8<5@WaENq2u>isYMdRgVH(x6(uuQ>(dUcNchwV&@52YJPPZqO)5jZ4kn z>f`I{d zhz%lj#C8B(WF}a^K7;4_w_q2d3*>5&%1GaI3Es2I4w4ZN;?99ek1TbcL&TNV&vQs1 z0oDR#z=_B8wP{s+#fBz2uB;Ms!s-UqZ;jj8d2_$c3U>sA)G9iPd@%Qzc>Y&0vGe`p zA!M6`$DiehwS#KeEh!-O{1b6-eTqPk(8shXGC_(>+DP~vspbkX+QSnz;cs&agWMv| zmqZmfg+`tg4ef7C8=AYxYVSmhoxm~12PGGbl-5Tu0Qzs%WG!@ye1Bs%-B~g^sxxyV zaJ_IiVo&9>sxCXCfQ(TTis;+;q=gO-f({0p-#W|-nv45dR1)mfcJ~{Z8<^!ZWvHZs z2Jv)=pTZ6CKv#x8hCbn4npA8EMmi;9@}(H*_yENJ7>q^Mk7Vc3H)TsbV@GR1g=$L> zRif36HAu(MBKXuex!S|0a9CF4u)zyEheF?78TY4di%xkz$hp2tMOn6qHHJZ#>;x-m z_a15jxzFi3RuXw3eTzB!EORi=u^(_`q`4ZCs-w>d+hg;bEx~!QsF4}A)wlYImQR#` zyYG8-_Gn#r&soRR^i(_NQ0wUnj!Mu%VBvrlGZ*Q$|Tk>I9AEMSw{CBe)m zAvG+g-)rk(r-U-c&J@OcpAi?+9I05vyD3VT{u_6A$jnhdt9QK{U28^NLZj{_QlX}g zy!mt=2^L-G$4^i6UEWh=NZOBR$PNMAWM^7NTWh(n8;p}v=gS?C087R0)7QLSwe_?a zZGq$e?zwg5N@rQ~lM80DJ_LXUihs@VF$Obv@S$j1={NB3t|N47cq^D2^CGs}AKueP zr%>S$u>cY+%$pnsi^7Y$f4y7Ua|9ksvs`{pLiuz#m`b6PP7q81fzOhZ@Kf5LIjT?M zl(|dqDXxhKx{MEGH9PJ3&m@q*TE2>18PPP1_aWe5gP^@-VK1&IET>s=!8$1Zcc zn-96!rnRA!nH09}V|RJ?x7RIR0kxNve*pAG3@{}S(&ML-Yd}z^Ej^FafMn!7M|uGu zyqW3&5zIxX-9$u|TT!Uc1aOGD$a2;KAT96-9zl(p;8(P$wsXhUKJglMX6>cq-#&F7 zTZ)2&F=jQmtR}jGW(wY(rSfP{0`Fkx!qyk*n_0A;HeZvzFjrsPR?dhn+?E+$7Cbj{ zZ*|Z&8_)5`qc{6Ruh;ywXVoRCapN)oG~qJkn#!m;@CGWGio5hvl2hn{+p^u`&{Bd9 zO^QbOBN3x`5c$HqE2HqY_H&AB`EoZHYOOOM#}Jp2IqOS&hXMdCXO+g*7EI{k+A3z8 z9nP|w+fBcqhymTsPGo}#6Eig_J4O30Ac5I%P7CgoX&P>tWkA>HvePJYEFk1WtN5<( zVknK!+J&UF*oisE!iqpaL&zujTHR+)B&T|`xNin8GC-q$5d(0cH!Wia;**Ih?7YEE zS1D5K$ufd`2kg$NGgK31UYTKE9_Od5urn6&Knphawm>~yGmxlhcTOPdaPaJxZ^|mm zy=v;}cn{7fj#`oSN+*gphLw6Wf_}msfEg)j?bM+A`QV@DY9Jy>=jhjTfe-ay@0lBW zD?w{4*wywZk`B9l46?4-H))zECzaD2nliWezFy;IQ~o$&Wx=W{*)B47K(0ka7m{Hj zx^S~>i#J^rAwd^L$-1<3H&B&lkD9h=oO7+{v2&8xwsnT!PS!>lSmoMlH#ZuBL@db( z9MhRKllRBlgrC13=nEHlAL|F?OAx=73GZOe1130ThZFo74hFo#bS=PNuN8PB&`1f% ze8`=!_FW=j#-;Yo<%17yEV=Sz(kA+5e2qmGGoDVlJ2;Wm8E4Yamq$@+IUgbM^i+a& zR-qxFFFyd9y5vMAe3E zHteK)qcsx8hFV&{M1=3gfvGTxyLWF@RR0afZV|4u%ov0$&O`*Sn-dMFz(Gxwf3Ke# zU0HLkRc3{>AH2TK{aiuXHFIW3P{f+kC(UqHufBdek1qKA+N03qU-Q+mxO#s<*?FeTmguIHs*c<)K-vZF%?s$;F4(yy5jr0_3!=kuAv&)0PsVGm zwwrYZhcCnSN3@WYXfDEbj@S_B97%8JviamBUkwTIqFvxU*Nw148O^25Ywc_T-6{XI zW#!cm9NmJv3HOY@T&Nolx82Z7-gVUs^L?rZ-2O!-?30eHVTld`H8E0Anh z-1ca~>$IL0tzD3Qc}AV7`Z^j41qgvrQgdfoW;G8}f3y@ixotoOT231MYzz16AA-I1 zB6~*?xyGiQJpK8e;`m zOvx^m*$9%?T3enZDBH5&@7L#wgZeWh@^8gh(0h_EANr}Z8j>%|L)*xMPJlrPs>DV4 zbC`(ntXy{#UBr}Z6^O)gJ%4FjPrb2h10W3`qzJ1Nw+PFF*^BQzMQX~v3{;Sod5vVk zCa>@b{rb_OD+6|nlNzb07v>9PN>Sv#NcUqlK z`|MV8>z&$nOBqcLa22v&o~S?T6HC+u&o%)TDL*z>8Cd<2+(BqYOq`3Jqiq;{&aBjH zmSGkIj9Ap&aX#7gp1ujmiLl7Zs8DUG)^z1l$f6}D(5AkD< zf?7x41+rXs2we~d1I#8klJj<4u-g#`XG(LC!$m~E4|)$%cMPzZ#8_fQg4}4EOp~?;eG93`9pKla zna!AagU$kGJh~np59-Lk4rtgodUYlU#F1zpAv(4?{AMvu#ed;_XS z)=2PaVM7_Nhuvau>*b{2(BhYc^5xibIj@TmQ z?k@5V4}~$D-1XgdBq6jsFcLQ%S$-Ry`>AvUxv+!@*9h>pD9IN)RwTRhICpm$c~whdoZ{`Ork9|RIAIl$Y*68(nFq_+l$ zeqvQR84hUxg!0d=^#GKfK2N}2OsB)X6@1*@;DvHx^eCi6OF1aXyj(w|K8e*Fw*q`D z$;F|9&ZU8R741~qD0hc^7HT51`MuQlRQ|x!HQ6pkAMXja1)s=Mp&x`z1)V7pnh-TBFY)_&i3gWA&Ca$dkSU90#$| zvx?ht$8i6s(G(5SR`z@lpUt1N%A#}>-lG-;)tXNIR00SP(19%4CY6gEg~g$aqm z$0S|a*x{ZszpT#P$ukzi)=uv%)IG&|98|l@LPKlp=m5Udd}f&R17t-pAO}zeDq2_o zhs;j`DV`Ph>^QBH^H}a6IYcvc@2d^D_Q7(Z1kY}g7koP<^q zcad2!Jw(E&%bNg&Rwdd!A#x&0z*6DTqV~BVek&PEx{p^5R03q&hoSMvlRHnQ$zAL# zx4i1WPZlWY$TvexuG$(;ON8RJp zm1CqLxsRfRv1ahcJ7<;CTX)*&GZ%)dx`&;S?fY07s*iBeETj@?2qqPz9ZuoAIP$f9hC z1IO2*A@P-+vFvCPV-w&EhM#B5oxhD~C1cv}0ceAl{oe;2RklnLmLu5@+Cxe-Z{E~& z{}v@X4%V<93%fDjKcC1dN`?HGU=9;ra;tkYk|VR;1F)2IGhF_L&d`TGUq=_sfwEB1vbD#ZhYV0Tc7zY&f@<53}OKs5=bQO)=YGfygfA;4< zBrjR+&zcxnCoz8<#)}*~G7$c{PIUOdQ>rlQ>qH>teEE6di14j2%H^p^Rc%$4cT{$8 zVy=2hRHEVitc8M`ZhOweI|h78c63keyVkqucZdJ#(_?p`FNm4|B9|_$P-Hfm(LPHl z)=lKaPyjOq(yTx6U}(@M$Xy?b19dPn4q=T%bxJ?{@?jpiBRGE?M23F;Dg*8${U++N z8+#I=-ga~~D8|IBg}U10+d(TQ0ulUFdgBQFjcM*oFfu_}CANajW}<4VKk*7Eefo%rgq>(1Ui@VgCW%dB*^N7e#B2S01HHi{K^bhF!QYX3NC9rSz{#43K{WU_7D3*ZU z-K}JF6I-Y$MmA<&fihamOX%HASQ#LNs^fkty?UjDc0$697-Y4( zVkNZ&BX@$*u=19JAj3TV3u66cl<_+xw3UN?#^=@SEBJqPZ{TYk*!(Bt>9uqaT60AA zpl?qu5-WH}iZZ(3=1(Oj0}1%PrM%IEpGupTK`kCwco%E#4_+b`N&Bg^lK3N$HLCtE zZ;tr?dWgH2so8&Re5^!QO}^9&O|urNp~mcX40_TGY5rIDmj16E;y(ude|E`#4E%p> z2SckMI4o1Hbm_c}M!f8^Wh48%Bafrvr7}YOEqR>bI51c>go})Ut#B`TzeW8-NND z9QslF7}TTFEHyr_`z_1$|^?+#sI-|Pz(v}%3TUeQ_X<@3BN3^2msLv zx&6ap%Z2r>aLmxb-g=jU@r;9*O5Q}HDdPD65ET}tpE$d3*5>WAq6-Ix_g>?6{O|)0 z5dzQ|plNubGZA`BO%89#7OG99k7f^3nbmQy+FrFd)D&wp z81$sAjFK1FXbijM(r-Si1W*5Xn{ise$7GLgkAmH49{ERUA5(iv+E;Jd@MU7}lSdm~ z?fE^%5z6GXsbfIc)AR%oMeM`sgyAt0OVM`r@^FfOiS=Po z`@EP)eI+37{%`p)laCSkV%|bp2^p-`eMa*DR}bKr$~-;MPG41**Bz$2d#en*-N{@7 z;l?+a*1G3=9F;fj+Uw?i$avKY?m6-oKqUCDn&0Q+;d(maP z!yx~Q0B`iAMi7TKIiYI;YQ7`dd~p(;BHNQq*=O08n;<0VeBc4`8$j0SbF8iBW&=!$ zyO-??PpZc`fs|tF_YS+D{#oK99vsmr*ICrVw+75BP?HU+8yvvc#OSjLuSq0qdRcVr zUL)*2HJdjXbi6ynZN1y)o`VQS*sylm)=zz71VJ5cPb;-ta4P*+_owV>8L9!Y?U=J* zyEknk^6!i+=tfr~f7z=6{lojL-;ps%8~1a!cXR}&MqiqvLc}!c2W$LQ9HBbE5v!^% zuPQU_wvEQNM~Yh$<-esvwG55epI&Lw=ackaoCiDD*5( z6MBTX%rF~jj-5T|33|x(?eVCww|BGPw97jAzKw>x3(Nd=zTmx@{ROF>e?Y!WLX@%+ z0VsmS&2Gaks&Xduh5Nzn(%5bsxZ4fhxBu$r`esYCo2^C+CFyZtQRgn}>9@xo8lH${%P{p=DLQx$Z6?UDDh%ygjS`$VR}c^al}$K>Z-H-bbNt z0c}9wPo=(2{==V2=dw`CdPiU=0PiDfumhn>%u4E1G~k)pg+Ql(+$R&n@hMrEP2wwa z=GhX{T|nXMH=1m_gff;u$4Qj86EG5Nym1uHB-qu4to4Pd`Snxoo;Yb?NuvOIlJ#$S zRsUVD{C}?V-@8tE0Pr$#lp=tTivgi%)Asu{S6;5BFRy&&r@BMa6Paxq$lT(We8rKX z^5p80&kK$!Z(anwbw@QKpTz*|U9oJjOX%;()=cTuYPVI1TSw3rZ0uRlspj}?8J}tQ zRZ{OhH9xxJ1bUSWlhc1V_{*1>qA%uy+D_5%e0IkTR3j6h^hO!YMCm3v<)>1=AJVfO zJ7xL*SLyyA{+=4F1_F+^&|7o%S1B~xHTfQ}H1K}>PORxvWME6(8bHjnfgeIAg$pI zNBo&aJik5^N-7Xpe8T&aS`UoyT&c_KeG2r21Gc%IAN!FpM;Q)s$b_F>4pNC z3FCRo#gzIB{J0)U2)a7e_*H#xWrKN-(C}%BPZtoS6=J&Ch&2mUALfGyaXpV?hPjBv z&g7^ylWY7rc+A}ai*9Mw?rfO@&3#YxkTsa`3mknJLtN(Rz0(8;K<8NQ9naiN)D zV;7N+jr(dVp;=%4P0_@E=UU8!&@Zj?QP>Gx*dQ~b?^YC}HWS6VKvBoI2ytq*8u-&2 zxgkt%iQdnhLL3CF%d4_4JR6`pMFekkwc%J+U%KlV%rNU~(btWA=;JLb5-H2J%MBG7 zgeA!AsJNYH^aau&Xwxm;jSYa#u846yt@~BC)gDUel<1x_wJPDfySms9`$T3Y+1vW>%d3nHrcawLvVQbA(i*qNUj! z-fxXdctMZ7kXRw8x?KtmHOUcT(ui83Fyt)VUf^vuxazueU>_K9y?}|I27}2H+cCdt zy{t zw#dzqwSrsp%@2aPN51YW(ZwsYH)s<-&H%uQ| zncf)s3d988nBTxtH(3&=j{j=!PY=ts4S;gPd>iPc88A^2u!7 zzk1jO+m9c;=y%*pbevjZ916wi@9ErQb(>T*t z$pQuRyPuZLlY{W5X+8kyg1m_hA1YJhuq)C1=2*~oma+}7R0?fo}b|MKeC(1!N$GU~8J;|)W z%C)MS2Lv_kKKMgaEHWkyg&V~3Rki+C=%hj)4GI8m5-0l4z4xLMkR08---5OvHru8F z|4!_|BYpLofp$|%zqkmgc+KX6%pMN={FvWyrbh~vCZ@Hqflo9-z;%@Z*j1PmJD_tm zH}@j4q-n2HsK+$i!H3LddshiF8Kbd0tZ-3YQExXEJbOKhBnj(gyM6Q`M14+uGTOgkN6q5CU zeoN=YHQdHxi*r_)0_viGYQYR~8IA3mp;rwC3-Y{&gCeiIHg;Mxm;If4$Ni1F&pqGoD|?^w8~YE(a0If#dh@RN z&iTw|K92@fp=A$As@3L0lSy701vgRH_MlPCk1LgacajM(aM@(u88GuC0*9Xyq7YSU9Im@)=B`i3EI~&6lW;Xd4 zxHbT-khr~>9;wB&v@5NWO-+qZ5_)auk^p~as~UNvxao4f7OOF_#kvT+27mh48bHFE z1jLk;wA2fkLe}wq#rUIzKafNKnGz@}G%?36^zpl8egxK3dd%kju#!I3J9fg@g`7*q zN8#MEKs$C%J0e;AxXIb_@;HI-ZkuH4zG>s2m3eVMf$GEI3rU#j`~R?;1ppKNdhegV2K=8u)}G>L3b<%8 zvVg2EAjsdt*}^^i1ZS97m38lZW{^2bsyre;%5zU}vdOh0HPIf>jR>wvc9eJ=SAonH zEcoTE2An&mqWNjWIC2tT5%E959&e~giKZ{AtNZJ$JuT8Hh*=Z|*=q$TApzjkZbx1k zu-OJJ%06)aehzAH!G6jE>Y6`Myc}@=r@8j0yyxfT-2VfT|KBSNkTrh;zU846;Cm|( z0N7HH zG@xX4W_6cro6^n6c`@z3_1>EHmsmjeJ8O(LM?_qLh722lZEMY~&V%dDI96V$nv~f7 z=3w&297`v^(6jM}QODl!bH@Hv&q!5=3k)Pmq|of$;1H?|P>xHkx74;-jf`0kuj^TD z28wwGwhcKP53_Kx|F8)<*+wZb#A2N8AG^VU!KergtpX$>C73ulGc!eL)vg(G`(`^r z4hNPt!hvFKBLbBr$vI5 zDqKo^&+x0Rmj-ze|XO~U* zdKpdoHg@@^dj;;`=e)T~etDP;80|4dzfH}!VB#}~&#_Dom&<5rq6UTbu6ig8QV8}{ zN}FwDxYBs*?I)JLUUJ_%MjPO+XVva4csX%u$TWX~ySW^nw|NswaJ-WwXEF%Jf*aES zbx`CiM{H&PT5T;*<7GhsSbna<2yJ+7Bn(uahh{Lbz^!K2CTZQCE{vF!T^=s^La$|1 zsdM~z*51d=?mJ;}g?8hn+vN7Qp(FpvN%8+RdpNqBM8M8sP;js(i_P-8I8xx#!*OX0zU<{Zf1c-iET0d za7WW>k^;X^P}9fyFuxMd?w7rCyMnI;zQfb5oyv2V!_D>}BA{j$IumT8!xHaVst0@I z0yF$E3yj=pjjAy(l`srSVZ}q^_10MLk<$upP3}t{nAihA;t-Mzb{{{#JMbR9uqRJc zV_fHKa3Nv)=+K9~EM4d(S`u>t4eNt|y{?)iM1j@ zVh6@ag7*{hI zl7KjgC1Y1-doaBsqD$6mHy(w#0X)&hxytRp7(0{oomWq$)vM~WS}V%&A6xc;1S;Y-LbJQZ_j>F&R9g$+YgyxHRic zUG|=(QmRl~btdK-q6D%j|5!i^C1xmF<-FV!~{ z12Kw4cd&pvSA;;uT7k4)52)omsIaDDT3!DHnCmR$*x)I1`>)-57`u^GX4eE`S&R!R zj%u~JZcO*?^%%z%W?c7riHQQIjLHv9_Q@Yxc88y=!zQ>Xbe)R(F~dv1(CB1X)12|t zo)1FQ&*P~;vlIrThv1?im;XbYclRp`BYnMY$;N$c@JNh~1BnZ7MAglZG zX>r}FeFJ4kHTMFT!WSS8xAKkHC?JFAH8$(G!Ej{h!yG`_mEo%2g~@hKb#@M>Iw~Qj zUw+^ic+^gx8vc6Au)@^G;#i}CdCup%4&>+Nj9RdGK|~65ExDfH!cpQLg0FDY@vU<& z3Z~k`tsh3(m`%4S;CbC{3y;B)r08fOTAV0VM?uuc8!lEneOy^ z#U;KAq9I!pTY9>!ec#BM{IZawv01JSO_i7bF3r(`+VL3Lb!>y@&;{yMYJ&kqkH5RA z#x9*}3in2yWxDjl9ZX*xqPTz9Mw2r88q<|mzSpVF7pQC=4rTS2G<-X3i~EH|d`vNM zm$tQ%>9LaS&f0CFUt?=>NE`n)agT1Mz1r3b7fw_b8D*|9;nVzwkT~~#Fiykp=WIou ztFFT;tVmVXNrDsniiOxfziA4!%OGA=iFszSXQJK68;;sPRHv+EeG!`1(gl*Rxse>k{U=v`@pcTC8s=a(*Z^|%N9ro7i6dVe` z0Y4tI?%wV-t>+$i&%DQqa2@Zx?wUW^evx_URwa3rl^=-1AHnwkJanpvnxn1qAe3xJ z1i@?(crmncf7X42r5;)@7<9%L+BUQtd~45=W&g^0+X7}XqAYkdgLhf_@l+u(?E5%| z&b!BtP=N4jFbT0Ic$v@%st8F58O7FNI;yTT>E;ak1P6<__XivDl_Rd1=$+z@X$|Ymt_gGDhuXIjg+5?atbEj6j-O=Z z&)vIKhS!?C^rY7$!z)Nt1@w6 zaj4EEkvw}2+~NT+4*8v{Q-RN!xAyMxUCXq2a-VP!UZx9csx#bL^=)>f3(&sHYTnuTqkFVge9E&T8mW(il;7?IcjF2;ki9N=6| z0AUxaq#buz(c)TbOd6h zS=LuG93xl;haUwe#qiv~{aKJ{ufYBoUqXk?Qqg!yM=q)UT~CU1$j3rBjrGoX*q}w} zZt%e^xg!K&rki8~UT4>*O7+BV4$Z7HWT_VS+rD;<7A{F4#e8vJ&f20j@a=HrbKN0w z8-?OWp|+tUQ0w@d{&AVH|Fxj_>pFk-dhmS*yH_xx`VvUY06HM3)##{Tj9${@xKkC5 zIc&a15My;0YaEk^*qf6gqnh}!_|a3UM;d04YGAx<@ZA=e&D0_<_}NbZFCh$0!M*{(ML-vTO`rg3 zw}h77A*#^AnL0jX*yNvV_5WuSfdA@sNDRv)NTHdxfiRO>+q8`VH<0h45kKCz-S4vX z4$UJO1K9#!N=Zs<%Q?H7xF?D zeef#u)iM4XC*+!Ps_8hiPn)sD#B1p{=5G?Ab~Oy`eHyH3+ttQPu`$4ZrsFzzB95Wu zU|?sWcJ3A_nXr27J>AJM6gu6XLfNUDTCL%cx;ShLpQINxUOzf%TRJmQ7h#TFhy!{L z`3A870bHQMdTC4c6+k;4d?A;So`Q9(gQ@2Q2jFKQOEX`pXE}Lh@^c37eem+Dyh)Rl z%P7h$AHxdc=;?Bg8m~`@XA;{0NXWcs&XxU4-#q-Kf6|=h!Vuz^eK0>^P`@~-YqK7H z$9(6caqU;1;7I{sPaLyt*Yg1t$*be1@tNV*y`HC2&hj9@l4_Y0b z?epRfe(+vZeW={@iQ?gg6KkgymEB4NM^lyrvpu@b3uI*8&VDNYUFf&Ly;g{6PoDgR z`WmLP(eObUSqiIkT2%{{SEy!+9*IVN+0#^%gIQCLSp)lfkh5>j-{LyW@6N`!z%#m-)KFq(7n61kGPxc2mD0K4in7jTz{P zjh2v4AB%H|ed#m`W#1M`rXk z3K8M&%(*TP{4BW%4rT!WPr|QpBi$)K-lR&Ni-Ae+TDIu>T3dv84LqOM))=YiaJ1xo zqZz=~RcyJDkcuff1V&!H@HULQebcI|a%sz*Q30DcR$}UnO~9ny;M%#@w(N$-aW8XT z_I0&v^@&{Vl%S`#15=l%^Zh8ZimSR2MBFPZ5%nOj*dwii{;@CJ(z>t$U&?wCKF0bt zo(DQ7HipT4CFmShVWi&lPt;SJsIT*_`OIAI`jz)uu@3j(3>X<AJEh5^41Hy~Q1aCMoY6`Y=60eEtA9UOj4R-=O`a^y ze!^?y$|=&$95{L>bK6%Xf=`j}gVq!xgBW`1DXl5lN$z4C*f?T=<`0T8l2XiFn`=kTym`7k_T!lFj&^N6fy>0= zLF_FH1FrlxMA)fn(wYIbSUy`ZTJ9F(c`&x#1t`zIzA3GqaZ}gIcFtMU^3GO1JncJ zh`qc!;GNRE8~QdA@4jPRVWpN@4%RLqE;rd#u-%DKgwovaqRt@ubKE9c`{sECbI(yc z>|L@n*BjJis$dMFMSk5-FyPct@<*dpBB$|AZB%6|NL{n9ts&)jQX5I!%2BVgccb`O zTxjOBm-qa~MeUHCHweA-e%Nw1vZieInT9iZF0IQ&^}$gWk=|L%#5a2VujlPWver+Z z?6DAveyi|pkKh%*M4kE*Hp9W8kB{;3HXp=3^-qFmUm#esH8InEmK1|&=uci2FF!o> zEQC2lo4;0gb7pof^ZBQ5p72+RGMvc)dv=yy!xiWZJs6(@8CKf#g1P;E*akh@m+>fu z_6}e(-7^}J^Tqc{SjOzj11&PgzTcCYJ{sVw03T=ZGa$98x`wlGVz0RF)7;J-nMY9& ze#HIzqZR5j4R{QtrHn@?+b@wh5(FV*=dFznb^UQBCGM5$uNH3 z!;Ktl?8m{OV={vpIZCqN3B^%iD<^hY@++fU7+8F8XxM zC%<7Uh(&2LTpUxlYv-uwI>bF0prq02SXD428eh?d8H#S*3r}zeL{N0pp2zNHeP%!5 z<=aRtDqlzud>IF7;uNf;l{p<-cUyNXd`5AEuv0R=sw!LljhUH+Ul%QLyXMx&vrl`u ztI1p`dqm+HD=R}*7Tth>uJTk;&)Nx#GD)4|_lHaUj>L6A zazX&2S%uxvx!U}qePYeqPGx;J(U-(jHl7O%z1h03bOq-2iWHi^9duhyk&&H3UnRn(;(GEiH~H%}cUEhuoVH zb1B6`^yN+xyo;#u!Az=6IMD}9cZm<}-N6bx&{t~Ng=yk~;yA_x6I7L=s)>;S@$W9W z0g1cw!D4za$VP3bRjDMg)so;cF8V!)od?QEbiyIn#c}X!{M=`L>Qo?FMmM`Kbz8w( z&A?+nQM(tl`9a|c3fY)b5G{>bQ^NKw0WlD)vAEKlweeFNypJ5@e704WSK~ko`36ZK zUAMVwvo4TZ`nftTCdUu=X+Od=x5_iqu33rR4s4ZU+Q(X1)2O2FnW8@G6r%j#1*&9# zvg<>Qq)X=ZLwe<3KCI=_h_48_n=IpSG|NZ)pK?e3vrC@8KL7X2Za!ulSKf#MtQUkq ziX*5rY(-V)*t4a0Rk~KZ0$diGJi(#Mlk?x=(q4Sx_HnE_sVU9lmT}+JK=_qh1|g43 zt3osP=cnns{EPt!#?>(u^Y3_MgeBg3bN=J2@1JbO-z`mAQ4O9`Rly)NG_He)1Ouf? zkYz;2KojZ2QNRbk({c&>#tA(6hd^BjjG4DXOUsB5(2Wl%9uVRj{g?JLe}5f*Z-4M{ zje*>Q;Zw+e`IXv1l^a=e=x&dh1LY?jHJL6~m9;0@CacsaI(9{|VTrqLUp--U5)nI< zrE_m{7$uH~vRg8nb<2w9LiR!?NjL&9k zc@hg`h#ly}V1wNGs!m*Ns~D^4*;|#}iZ`sB?=Bwa)|R|l`;GtMgqM8J#GMJIdkUEr z2QeL7(9_m00V7agk(aHj5th9dNZ|O!F~_EbD`*Ypa!3*xkwnxx>L%hs$PFz_9-bii>pv!>YB2b2d-X1|r)eb1A7 zTos+Skes$I*L>1?x@M1?`Dv-C?^rW@Eh4@eY_aY5gtsARaB$VRT+VU2QR2YWVhtZ` zaZpQLov=i=**;(usP65uQPHSn*_3w6^>|b4R9G`~;42f%Sb(CprIr^1PyzI03Hw3L z{20|6_GFTL^4kn&B11whvAny)aWwMPA7@yOB#W`1D7txki!qK6pV<_+9I-WVr|f2M z1Yppw%#k(qU?Cpx9+7DeuuOotyppwt2RUBvRV8_Ovqr?e8NES7ovY9~)^ZvI^{*dr zvD7;8{^U>`BIr~M*E$a)n}WUcsFbG!&A{16AX+Sxh&X#69{(uvAswMZo#1$RXuw+^ z!GHk9j9!iTKJLx}iK4&(UX20yB#=j^F6DD};qyBcon{&S^fwqu#NdPSt5m_M zKC*kv18UF0GF|Y?dZfd=Qu3LyK5M_j5y8hFaAk(8v@TzQ8C8`wl8fnH&nX}K^l#x4 zM%s8f9HYCJF`4~hQ2R|R)A7kFn)3thE?zwWx3;P75l1TlG2rUfH5z(9jx2Y=8_>+J zhPL(+i|oVcZ+!2f&fs*5Uc@axYe1;Gk74d1iO1X3a?D{)&%EX%cbp<|5WTKE<=M?% zyU$RE&>TtNTJVlN;q>Vp$%*1a)2*PVXMg>G(3~W26`C=C)u_=eJ zV+6KUs>eN$L<)T7wOp9c2*^6fOC|@YC}yMlQ}mHF`1#jG2c{KvZuAq1-XR16@&+`l zZvA(z?;H0N9vr$k(-#^rB`{z{_(4C6^J7;lK zDL9?JMR`)$F1!Sso7LnLGJsgr=lMJ}bBSb5%r~SRKdv5`@#?kNTf04PK@Xta^^_Ao?5bRyz zd$&3`#kiBWWz;I7tVL#vy=pO&v-k08d)rUsWYc{-*vfmK%R6RYI2P#HT3e`X2JzC*|)<`l`qDpUhceQSTZSUfP={|2V zji>ur*x907j5xB%XS(QWAKz$8;H;0ocU|JSPiFb06ZGSJj+GKAuv^ihQ1zwYslTX zh_EH_Q*s8^35X5ltS{w;S)F~Rl49I3dG*ft^FAnX6pF$yqZAo8!X#w@o%o@7 zZ2d3nR`kOWz#ElJvIuLTd!*KuLuY`FtEGRq+O`Jr(3d1Av8lK(<(aW{TA~%^{+he7 z-kz0EbP!7Cn>r%?j91aK1j+!xFkTM9`9TnNVrC6INEfY|$ft{1_2NRzl2~<<@cE8U z)agftQ-MteUE5k!@uUL@9Z0P64+Ui#+<1N%qu9oyXX~OTZHuiZl76~0!tSz zW+#90_Fedqa;-DM&3!Dw(5M#|7I`PxrzY=O5EXSA|q~!8&jSvFMVLu4E>UP@P#=>+{#(( zj>oz3gRgWXcD6IH^siu}wY21eCR6kcsNFJ5^u%3y4|{=^X|O^6&VX|Wi!;j|R5(o0 zzHsO{{&>a=c@o2}`H9N%;1z=)LPbDST5Mvihk$*6tPXO>9$fA_sH>>F>zJ z^y&YA{kPhx#GmWYb54&;xN0MT#bLZY*;j6BaNTI-}=RD$#z)N)2O}C zQFVZF7JG$E>q&Y)2ZP8dxMq0o_MGL}+%}ar=^>xStTA0!*<;x%TH)V9_HRlPaX)h| zbW3B?saiM#}|%29X)Gk~4W60ysm^umOm)CCOm$NAdTDa{gVf zZQ1-TdQ7-7^mO9PICXP6Ki36hmF46H6KW%9RXVXB@a}JD&HL6-U!^jdNr6!F^AgP`*1u&aM&qP8!74P?KvI1*6x2V9zCw~FoSa?2%&Nhc z=ok@vWe*3_nJ1BbpD`RZWzx(sc(t4}-w~DXNgn7u1h&f|GA%x0d|WYnMMW*SE?|>i zz?VLAb~5jFadn-j@>l||2&?zx$JUF%KI_$9eoAO(d1_7c5mlQe<@upqKV zdVa7=-oY_!f4EvGwM8Y+YOxC4K}`)GZ;A>05mx&+)8hgzfhf-!t9UTI>#p`DR(I~Y z=b1ZT3qxiSP^uWl)4aX z$2TuvCxfY-1`7W(>SHEnq2MxnluH4{J#onTP`AQ}E+cfVcGPsOKH2@Rp~=4k9{xXV zIFuhW4Fg3z49Naanw$H>wnbUbn`(L0V35hOEHJ$A`}ryE8RnjWXEnBX>Z$mOhCQdw9M8U< z4o%s!0Ro!I&+0({Bd)Wbx9e*YC_Vw8(#R!^FIj`V4RK7sk@Sw~ih>^hX@E#K07M2s zb3&A(4G70m;19Y17h-|y$jN1H?()>TmA}3n;yGLSWACAvQS0M@_&t?l5JWsK9>@s@ z2v=KwJ5D*RZ8^nUeB-6I()fj-JK^c{6HfYVDxWxrSaho)GDK=Tee-Q}BgEe-PgQD^ zIxM_wA&s4hwi0TOPT zqQdWm)r*Tga|Ho z7$oLI983tQMAqTylXNu?;bSCz%y+G*TIfSV=*EzQI zTy(cFKD7JwN?>}S`2PeMA>J_^E`YFT{qZZ2Nx0;hZlL-U@da7sUE}EkUP^hm)!rr< zSY4g8!7Z+kXYd{q^&IFWC9dtnYbW<7gF<5L`1!H_4lsqj)XJ zQqB~{QNn7i-VOiaC?@VP>ZY}2T><7=V$C==~VVr*uYBGu1g~^ zpj87O>QIytE7B7B;5j9|P5Noow<9bZH?b99`qhvpnE4FrgRYq`k$$4IoH?c|Obc^h zKCei*&W9|U{7Eo9ghn$j_WcU#Y5_8@;OlCjHierSpY8f}kKvk)t-Jzq;Ik63CrU{R z>?!XDmuZ(iV^hh@Q+{uragHMsY!RR{i>YRU{Apcl;l@GeuI z0e5|c2}qaQ6v1AooK+~f5JbL# zg)LhHPlA%#6khl(0M5N@i}&9XZd;(qlDP9b@ehT5*3VTU3jma-{piZb5EbF9shCL{X0M}+iwi)1`Ogj`o7HWOzXxaCsz1> zz~wL_;(>tfA4iqW%QVwv(*jMJyd9~ zduYqbJgw_o##;FcVIZ@T_tTKtsYY&ZWQS)uKmq70hq~0ebg^rZwP5zPRwLtSf!W~7 z^lhxY9bfkKST~^7wwgTSMs6puFRoy4OiQF!h=Soco9qRxj>yD6xYhB0b3riaU?9O8 zIq1ifMEU`%#~-P!Vz^?HG0amD+=93j`{~o|w?Uma#vCP1L+$SYE(O9{5$vK-UG}7e z(aX%CPvmcpJq;UhY$xjy`o%&%`p3UhKUHq{tMp2MTJ7Xwgv>zo*)>n;GuR3Fk{qc{ zdkw&R!oOx4j}Nq{baA3QQX5Y zXYSfOxIX)I!ZB7keY2~BjGb1jQuH=InJ&4@xRf=>z2NV5;7w0YgEwh}MrZVD1fJ#i zaFt>&*q`#GV}%G_w@&;{?)7;k_j21UM`9lNiAr78IQWze_+46Ej<+Lh!=v4JcObv} zVh?LqolI}H8pz#Mej?A~*@Qaiqq+P5x{8vpQS91$MC35XasP?re)Q7zIqj1-?%q6^ z2&F-_bA2)nc3)Ec8?U^zJyv5PRU3VB7kPpFmr=!^L_PUE<;FksH7Evi6%)bYbRgJg zwR5WzYW;g_aVdHYjH#67U(Dv#w)N~?2|Hj%LL$iW+_yNZ{w)7)aWwHKD)%&1+56+` zW|eT-$CrjNza9|cy!fN5qW}0jK5hoo{*DYZKmfeP1|4I-+WYE)3}nN*S3(PDl=D`o z#_Rw^^pD4gqWi~hTvhN}+P7is3I~aq1{@QAq8P&1t016}0Tjmoub;kh7Nm-#*)SAT z{Q8gHACLx>89DM>*;W7t{VjBaaMk`JbO03!0s7|bm53AlTg0KPM4Yvdw<$LUYF|9> z1avYfv|WD-Q{R}CN`vb&{1YJms>z(Qa?gu|)n(ho+SeH+98cU~-kprXNIJc|l^_`;IHks%kS^qv|%Rg!B z{ZVbv|M8#W4jaRbh|{+O^3ua#%#XaIDpQFpDX+S^driW7NgVof7xRcfj~r z7IF|h+t0BCNdorARW7#bT^u3?KIeJ{GDK#dQU$t6qo0|e2|XP8N296aR2*!5G0+_m zuHcxpX{PVgV)W1^imqC&Daw(&OsZ4fthlPUM;mRk6s!Sc^rq%?k^zUqV4l9^Ff znUb$W*@v$>(6x9ED>U#iTT|X7(Hgs46YX|{*LK(LtF9Kz@Jrv!_ex5#mjoNBYUi`d zd5!*tcK&JXD5luK-(rY)=ef^&GNqrK%x2Ic4?1`%uf5rBcnACCa@A*0&BvhcB#;9z zQCfnHQb6Zjs!gBbagf+OM+8@;^~_e-&tdZ$a~a z((mbT0iA=^KxSgmu+BHGtP3Jyz^Y{jFe8!>G0CeLJ+q{EvH&iO8a-)NQc5N0v*uG) z7v5_RD|Lp%CbN5D3AREBEz%M9on)ST{dTRgGIe%q_=@xtqJW`&U4BKwXU!@u)pEk| zI7gWvk{H+J0rHY8SH-wv@uzZ^>j(#9&jeW&ReNZ%{$k#HOagxOeh0#(mX>OAp4PH zm9IZE-dM0u2PFWj=wu@YGhH@wH#2-vm_gaQkvv*a;4zouFc1FH9eICn9c%+)baeD0 zR2Lq@ktGfnXz?-#yA@-hK+I~c8p}PEC`Ij?tq|(^0MV=Xy(AU2t{tYdZhAQK#d;T{ zvhYLiTZ~@oYyS;(kH+b(jhT~+W1=^p7g~RF1)cnf0-mgx-#l5-zj?A&j6#X_KmdjvE1J1Ekul^@OK(!`E$~Ek`!c96)$9l zPqU5O*qF*vZ^(STN$TkSG$v1ukB9!vX4M-DY*ym{YMJ^I1@KE?w92SL#PfOe7%;G{ z?XpzGDq)j&jW(Krs^)`0rXAGAVy$Zen_MWkRa~3Y{{2j?xZ5KHh<;Wp#$>(H*IpN? z;8uen?)K2h>y4XBuy|Rw)R)@gqr=ls=CqVcn3dVsK=0Y}XLWz^o@!U=v-Li`Yv(o=U;Ofy_7xgSBegkfsdR zjRleBNQvJp@91iz;^JF1SextpGyT0chR8Z^NH#=q*ni|HPEN$a)*X&QOYWTYdo<3< zA|qbYX;P=9TQUe*ti4_88!SENJc~VEML(^1DtCEFl3kDOG~lYTG-BAUy!aC`+rUE@ zU=S~3weyDECtK#c>3^Kwd;edjcPd2d@i>7?!Y~hfU#S4Nmb_bcQcooMrEcaru>$84 zG}JHJ8&9=;y8F;md5!09=l71P&r}fdRE{UKV8N!;{;l2+N1jXd7}^^3$o(i`i2Zi% ztKVEX_dRq|1axUP2C9BY{iO){M+5c$9X<~$0KyCypE>1(nQBIac^p^d`6ut4XDhe| zVdu&on!+RLNH<8Hpgo3|!3QCo=Z5R+OFFR<_|x9=DYMyMSP>#F!q=aYqYmys)6fxt z5yvCWABRHD`d#unYg+vF*0hng&!o?k!@#un(Ww`uCW=z~(&Pr7Cukf}SAS5Gt~34- z$6bfxtRuYwqQT=FCpdWT@alY;WU56Ax^9`WCzU@WXa)`Uhm{5)T6GT_k2vUkVer$7 z(>h&57aG{bv^bXmhO2;<*#M$+fDg+M24|}#FGFYv9I0R?G{Z(J$O6ZXNcl>QGb1b6 zX!V$g;_g>ojxa>Mx!5m&x?y$3S;QB+3i}(%j(F zh3$@*Q@Dwm5Brr#*Q~HCe|wftXLF#sdn0reSmSPd3@5QHW@d}+8{-#=BFu*RLQm7@ zRz^X2*(uej5w|wv^6Gbpthz7q0Og88q1K}K+EBlsXd)=DKlqdt`H8|*!PX?UrQvJh zGfZPioXMTH)3)VEFG)UZ?J=E%S&RP8{+O(3lO1I(1LUp`qGi45m8HRVJj#a*CT8P! ze8Hb;5m7&msySm2EN>gH0$H_i(J2VA%$ltLdJWeU#@O_^L-}-4hvO`qM0>5{Xz597 zVTFB*Bee}XuhqRkl?#%RI^-|F&vjdYn0ZAYR1q+zHgf#IDg&I_PcrUDWj3#rhKZfo ziknoV2RVsMdl+1`5!NLdS67cRCOrLz2(EKw*wuLD9}0(RZ0WZ$<&&Ew*+;5o!31p{ zFFk)Z3{G_7^vuSgYy+z(y0Z{F1U`A+WlicLMgp%dYJesjc~lpHHM*n)rgF!L>jFPm z*&ahin5u=L`)a+nH6kDXID3Yw7t63tE~AfSuwud`#oft!*s9Ex8$_%cJNl*Z$`}>A zjt48{TlAs+b{hkhCMhPg#o7Ns;EXEEx~r=@g{~Jxv73Loa96tM=;Fnz;|J_Z8kUEZ zndr$Xnoukzh&)j^M_n49lU9atb0r-ZG(mr)vMBlaB$m6$S9#=ulk>UP?h=iY;=I?D z^(DPZGVPa+<4pEADyQ>tH-MDsEF_OvSWlMxiDId@*#AVmj3%$c&>A5oc8Ub+mQbL} zXvukAw$Rdhs(=-iPF0*2H3GW;ud-<26@gSaebNSyMd-uS+HAZbX5Yp=+xLNcaSjOk@8 zDJ1TD9WL|3ofSoo-E(Al_+%R*GxRa6Umou5v@p14Wq5buT5#XVP8+q6in*mE1k7v=}S# z+i*-byTG!U6&pt@ytj~7k`iKSX&{Psn;Cb8)|w{}G}q@&gue9#dVoQP9nUFv%%**r zZ#~2VckTyZ5Mmdp`rXoN&KB5zsm6mV0o%_?@-Hf1`}Kr&7`X3=z-k}-yROrwsq)@w zitx}fmSsCDZKu5KeygSK<>1$lzepl~5>4TsA$a_6z1N)uYIGqfsJ+__s@4E5$*vr6 zeAYe|nvqquC(pl=l;Cwn8H)b`PPmsm+0c~Z{%6IJ>?*>U(GA%)y@ftgz6*#z{Vh}R zb3^ZZNG?OSxto~wOV7YH!?|`$D7GaENOS!})!QVL;Ct*IvNU6}=+r6MolI;(`I-87 znIA*z&vz)3Q96e*gb8!qeGiNKf|Kc1hCG;Z{D9qgiwFaOc2!XkX z!I#7VGJMNd`X}mLl0DZjjl2FcD5SS@0J)zYh6j<1 z>x)rv4B+b^%3AQiQ88c6lj;l*E%&R?clvEv}HW4!xe(hXxj1Pi8AL z`YH*lS&VP({R!p`4`Yl8rf@?7H0$i{cH@)DvM6B>^NW{iL414c9hTjRL$>DtFp%E` zVX-5C$?klU!q`p}LKd&laW?Klpj zUwYsB&U|woHJQ43rfSJxL%LA(;>(XpAs;vXqPqR$g<~Qaz&6c*t7OjExc)pH0@};x z@iBX;$7u#)?@6^diuEqMms6yb^PE!(riUsQs{1pjgvx3enz@uafw<>&P`)DMW3qC~U|wqBy~nLcC==rr!MuqBP~} zG6%A(_Yq8dk}vaD8}s4HFF#STK^B6o*Fs7u<&#|%6SgIjffiTYJ_Ys$3yq+EZ$t1Q zIY2w`-tX-Tkjnh-wgcQ9e_JTe|Mz#_|HMzZIaAG12h09id3Dx<;HG}YK^ABKkcp4Y zH`h^9(W}Txe<*KvW_gJL)vpJUzsoaBl7Nx7|0zKjwy9D)D;t+GlZS5p1ya|B^1Cdp;KnlYwQYBn7R)^@scCAW5z{ z(3YW6zO`zz$SK@|7aNrIuiSm5Z8Saoe0QI9=VvQ3uh4uy@v+kKpj@|$`?LI`N@pg$ z_VF#qfjxtaP?=t2SjT{?ki{Npg_XdWeq{tPxr62v4LZ)vUTJ4qN32EumVoTJK*A(+>3U}>2O@P`yuMI*6;7E|E_WEABvvD z72lm-FefP-aqypmP%!SwkMQdks#d)w*gt_wnfKPtZ7!*zpp#f7od%){O^35xEP6Yf zs(fG>$GvZYROMR}3hWY!q30U*8DrZ^rDYz;73U9# zgJFtRegAY4xi%Z@BQxACDp7@r*Z;bL@di%X<>e>_9~aM`o{oJqs{e(vO+7Q}#=vE$ z+MO$-!`=ft@qlzgG>!;{JZhak{+j2XL{Mbr+XG9xsrbRu*d(;HRugcZwGf^^;;7%9 zKZlbG_Oj0I>RBe&)J?51Tz9}$3@RM`Z+P(iLmT7|JjC~{P|`b>;q7QdcyzXkA11TQ zcnx4S3Fu>>rRq6U3HU%Dy8eZ#c`JG3%hc*oeJ;bGqt$SBH0{eB#7Lu%i=U!$k8 z+XQw2B=iEE@Zj9?qz)oH!I9yfhf5#WS915lB^)EdI#zC)>3hcU8idL^T}8X4782o` z_j2N7U47G7JsNYQCHlEa)Y{6A^G-V6-Ur3&cAzf|{{7-67G6M5XULx9ASZakc2+fSuX-$t5I^ ze#x*n>G`9^g1PU#RbN(`Dd4y~6~j_zZ-s?8#{w)oNWQ$Ah|V;u*d39+OTnR4k#N4T zdbp505-MXBd3zAiguUA)-Ezf-bi;@7)WexF8co@ta51@v()ob_*Ym)fH0A&SDo-1E zxZh`J{~hKBw*8=~%jDrMqdti<`M^=2V0bsY^H`(7y7YTxlAVkQ3SwAWe|-M+!i?}( zo($=9W&rtD0B#&0!^Zh+6JU_K9(*~USv`z{^z{Zyt;W1+WYbY8F6-kyghvKn%KAj!%ItCQPv`%^_@k$y{`$N>4s=;r>#DhNL}Clr zeu;HJID_KCGGD@QB;mY%ATFX6uS?wnjL^*Y;7xrn?4aSV9s>RjLt-bQLY(FbibH<6 z*U?r!K(LAqsP}Q(Y-PJC|K1dudDfk~&b0*TR)!MD$H6tB&N1Q!UbWTE(?7I2sR>Q$ z?GF|}#CIb-Xj8YaOFt<)6@*>)RVVnGnS$WynAtb7whtDo9qF4*UYkwP)8GveTq(-~ zaNvDBgop@&oNE=6_b%QB14lFQ%QSG7{zR=$_zIM=U*37}GAy7~m+P72X0B+F+pV{E z{(M=0mqPhzQk(1xQwH9VrQj3=t-6e6?pTI5gx_Hf9{{O2S56dMc$U*IMSxg>G!_+H zF$S@eU2KY@+}iE>>*64=E3Q>257iGdNcPHp@y>0aPS2wcb6M*~@FX*fuA9R~1d;B5 z-%&JMA(p_}B(6-&yH1FCHuoy|G(&68>I!FX4-=rwA464#!vP1PPfDDS#McJTi@ z<{|HfAiYjCEhF_hy*`-zpy~F6Ony?J<87&9>5&(e-`sq2Vq;@N<26;!d)Y(V3hg5N zTs0KGvAuk<=Zi~yfo6L+DcG99<1MkgCMG;5JBO!Vzn*^iO#DfGi}t5Jlgf-PW#i^8 zrRuC^(2FZsChE=fgKu02c;|9WhCg1fmCqUb+#Qiq6Bl=QWcHUV@AnO#)%x$guYKTf z%Ar2xgyu+22W2Pf1+Nkc+j1i-i-Ho-t9jJ_ zi@o;_XsXNBhoh*7sIdVGO6;fzQ2|lfvAl?i7%Nc_q9P(hjEV>dF>8M7-Zx<{ZbFSn#*^h z^*nlv=R@%~y_-nm@B&pi(6ZjbC?Hm+J}}%g+@1zs7t<&E zGtVu89UH+tt)+*pE#AEij^TENw8l$h{}}Cz83Lij z_!HPYlb&dB5FYlNIp&}}Qp42b7%6uesf+21x#}9V=q~XYZB--wlt=gOud&<~C%&GO zY^|IYMyj7jwy2oDeC6H`lUbkI&^dL|Wl6plLHFI78a$-5IF@ElO0dhhUTw2_yLR1t zduY0mdg{vSnRL^h?aht0kr}y`M=ilh)k(+i9EjKorM%#is$+x>peLjns{KZ6#&M2i1yHX4(Nf!+>uSs(<21o)KF_ZVUh58!ks8fMEBiwi zsC&5&{w%X~4JaVrw4NhQ<3_BkD!)=*#ZlKRd1aw}bdCC~ zOLg)yEVi}Xp+Aof+eK{S;!`Q3TX`%Klbg7To#bZ@HGcVqU|HCcj(f90vcMXXwMRcC zhj>1XH+4U^a+|$c*tWQ+FgG}zBwiSdK80|(!V#S>AB1_k9%&+8Jn_1{_*UXOu!M_p zF#hC163m47#^j-2V030M=wjFa<4^Rg2u#-B91B}7l5W9Ek@3he` zFlV4TrM6Y*{-)Sd=F@Yg*AJ+r{d z!HQ|nP#+3#N$(7}ArhH`uM!I>U-X3s#ZC;qZceJVg{vK)DYBs}ATh+%lKl+p1}xOB zFc)@OPZDH4YV46U7feYC{Tl5z@JJ^STevlD;JprSrp}F!ymzvaUjXh2%aTda8TVrGp*gF z&Kf3LD!Wf57Flk~Oe@zhTPu;%V4uUbWH*^w)~XcR@3&1!O?-Rpfad%_^R1Eo&CZh} zh~3xmg@BIrzKFC}8Uc)^X}$*_Kw5F+9=9eS7i2Y_0#OqyFW?4u3e>m<-6Sq=4p2Z3 z3y9v1>2sRMg!c2PtX$Fzhnn7a=SGISILtkkvV&ilpLQl@%9=`sX7|h7JsrY5FBR$I z=Fg>Nv3yiliv!Swt z)DFp5hm;Rp{3sq?6nC5nQFebgYLp_7pxIP(yu3N>b}C z;-4qqfM#Tlb8u0{eWK~4DX5J=D_c)BiQTRz@=JVVM=Ld$=N@Sl+j7mSQ;+3)EVWYO z$#u)J24rho=*|;<10d%5*?0ta=xt;80rK-;>Od#BMYCPYVdDLWKh@`(ex{+%NJV^hTcjr;xg zPCJ=gkO`iIPq42+uVHjXim*&9;!~{{@*aEn)a*2OXo73hETVg(CnXw zKN_=LnT2`O3k80?w%d**R&~GBjJnjc%vuim%)=+rrIS6F;)K|*VAsYTvN3pJn4zHH zV&+OQJ4a73Kx*x()hwH*Zb`a&UYT(@{m2=?X-n6cO=PdBG0pOB>6Wc0Qd!q@DAP1q zu{xZq3twH&Js+|(A=-<{fwN?ghKn_RIcA$dEYIIX7B5rs;~8uO+(E9AcL6L5)&lFM zyyzrNb)I7G6k{x~Yx-mn|K@E)=4uD3)+5VJB}q2gI3er^o1RU%^4hXitR$Vo&L)Wu zA$$wAL=|obhQx;vIuXArfkxM+bU@nO0N8p6)Kp%=UP2PJb{WxyYv=-i5rNQT{EK12 zlQvuqNPK+dg|CJQ?W}NI3E=!OxF1Wrq!^z~t^p_dl<5VzJp1=?{?+c=3of_X{ya4# zDAJ7{4QQ{Iq7;!RA&+inYe%0+adL5S(qFt*Sj7)7)GkXknRViG_lt;tY~*L7ZOE^Y z*V8DaGRxN@iLzO|8VH|N70hOWm1@_%<+BdF5szu)&2u%)3e)%7%6IWfw~Xw(aPvX) zFk!A(N-Of=UXA<39IidxQC2)Yc0l4@UwxqXy{r^ z*NAPq!PBrMPK|z^-MBQR)Unl=Wn5~v@7DX5`uO)bSq2e&oGSl)J0yz3Mv z_U$YCVr?yF!eQ|#ylSvI%Lp)CDPFIeRa~929~dS(W5!Dd$2iyKCXzpM0$v`{2I!?L zl8=cUN8FofGwh*6lHW=4f|BlNPu&e=+Wy2}O0?b0--Z-y_1j&k6Wa5O4CqSxch1a5 zsx;ri4N0^C1KL_# zJ(q5$*$b5-9Kw!KsX^3Rzu0^gCR}WRwlyR3#H=RD)j^V?IAyozX7dVC9Y8I1#s3Pk z*s4(yza%2eZE0$VTMNO!r5TxtZW9=V{|V~wmPW?zm{;Dr;wPo7!F(zpTWbW>2O2rd{*u!Id_@Yl1Q!5c`?1Al`wO`!=& zWb~-;Erq6H4Zu>)6kZkxuk$&L9`x+)ZVE33x_uzF9ASZju!%I6TIdi4~8UIzqrkShS1$`~(o+6cD7K3ngCtMAT z-Y|i{B#7bes zeJds7q~!SI{Wii0*hRz+0JCaPHwa0G30@Gv0}|3z80z~4#W$S~#6}3%d1U1ej5XP<%#p)9H!2zgP#{VZq65;@6A3K zTM49m%Hq5DBrWOOcEFeycOMZH3inky>zhq#YqIk@N%S=^abqm`q&io?_HoA4@^E6+ z;sa3Go*Lq`6Xw$#G)}5g%i_oleVXARrU$*CrBDM0s#d}0l;d*GpP&ahiO4IlNSG|R z`CI_+y3=GBav36O-&OO*6*}&`8d_Z5o9M4)E)d>%mxhHl%^Zq*$t;!~@Hu3c6F_jK zHJapb$9ib;jUI-WT*6hv`Z-2p`8V(RY`4#x!}q;z+lWod+8*JMU2a<5bNK6Y88`Rj zxYPQ2KWQJ7$rJ++4Lnjr2Gv_~Z)1#Wq4_16cuTrY>n+>)iFW3dH%=}|D%G$Zr#1F& zr&joZFRx$M)qU|Ik#I=t(g6GY^7@JnV~#_KDQJkm@l7wA9HDrlvnJmEa&qdDmydl9 z@A2Gnj;^9;Zkd)w>MzG$8CW(E=fPP8P(0%ry8Btt3wlUFkB)TQSL?dMq>bu=;H)#7 z$4#GqAU0NG{!Mkd{(=4TlasG4TWIV*i`Y?^hm!?LH&Nr~;+Q;(hmz!!n4Na1sSmwo z5foMEC-zN@J=t?qZhU0K#9NjduFl%}>@l^kQo0TPn9Y4vfISTEo({S#glB=Zz!UyQ z>e0W!1pWIsf8-ATUiX9v)cINOX?>U3wcL>f8*+GVukzL)l`jT*Z)`f%f6Wj5Fc2ol_+~8QlqTkr@>l)%#ZPF){WT`wrabE-uF}X3haj);vql6-67)773y;nGs0b zOD|=Zpm{>3%R&7cOKgn;>BnP+FU*%U2X~-|#LqzOK`|>AVCoH1V}}WaAp}XyzwQZg zswxQ72A7egJC=Z@NhX1i+^)EZhwUTDs*Caw8N`k>L{zjuQBD$Ri zVxi?R*mb1446=hTI;qA2iAlwGLk&FgkQuETc)H`arR#?YT?g>8O3bQcjPc$IP1SNr zX6g;~ZH{(MXO!P8kR6_5;d>g z?WB?>8W=x!RcSIQfM!&?0#)7glJq0Zq0L8PO&d)$Y;rA@1o#<{UF;kEmCZDPcMzP< zV0$^@|MW36BOm&r(yR$mt#NG_3x)Wy4McPXN%$26+ESZW-OG*Jv4N5iq;Mq z3xLRjCI+r;Yc!Su@n2wf5y^rRfRAUR;5A||4yx$(Ge2;#X^CT`>y4SKfg|VO+Hl$O zVS-~Zx&k@I!>?FCja|b8WvByWj>&Df0`0X6fKW6D%+I@NH()-P0sf?cYjCX-SnQ2m ztq<^O#eZMH-}@P4QN$~g>R7p;3&KLNk8~>NAT-wFh``BI@Qo$!%(@nmk~PmHt0t4( zFl7=qSGgtbIXMTuuDY7DvLT`T)RQ+KJ6|48%l!TuHi-5~m*+ zl?lL>eKbVfKv20`pad3;OUX*b?;{Q0$U~c`J-kt@yitU+i9UnqXW(b!hY5WFuqs?# z;b9E=Y&r5zc|lLF4Uw9Q(VHO*?D|@o>7HZR4V-tRA2V-V_DteH)(09Z|TXe`T*B0B^?(OW68hM=B9VFHg z>^70oh#rTKViN^QgZxQx9pABFywM5|!;=z6!TCFIB)px+Jmh$M9L#dyWi^B?-@Yv| ze2J2+XAigHfoV_;17syQJ+whB-&V?&A!kpkiJJc&S6D&EyF`6J) zSHRKR4{y~yIcLj61JXD8owN=$j)a=z*C^46ExHn;64NnJWQD!Q5 zsG{BTVP0B7-|9?daU)Z7$-^9+l?#>h`Taf(z9?T#l)j(|M9=PA3i3N8NBF!KRQ`$I3-$lf8!?KeOCvD=l3!Oh}&yu5zc8-2mT?+tlBiGSo}2&+$}P$IM{oxqUjtj*=fI zi3E=aJ_T+P3;1pR^0_gVn-I5fnkZs7D%cvtFMP8_u$8KEX=^~r`7>$u>poA_;>gaM zV(iJqxcuj+7BNKPCtX;csfR_Q>TL!)B)Z=o%Dp?tQ)*mySEr!qEUUZN&O7(Ws(a7O zCHR@3mb-0_mUfy{mly1O>E`lqAuF{yS$^H~Yq@1lR;;@8yx48a9>=rg_Xa3thaB6{ zBuR8*fe*UgrJ+cRN}C_hHBSH}o03uUusI>HO0blUH~d zI=o@|ZEF$hnouY`Q-``Ow{vH6XaGZ$+^l+=!QsHA#TD2x#Bn%)I zOEn4tk@vw23vL34OhKiRtkH6^luJYpKw-Dkd5CzaS)pZge(Xyn7*+RZ%Peg!w!u0v zv!HfU|4(n{0n_~=V~3{Y;I&;ZT9)N|a3}5jn((bFGh%&WO){4c0dcwIFk!6*pVX}E z$-7LOsWi{Ub!OfoQrMjpiei%Bj1h=+Syw*bqDuxlkYZB>Yz_@34GqSb?rvwY$}-Y* zr|e>%I%#%*8+>B3X)Qv3pjWi^t$7$D9+i+Tfi46}JdagkFN8Khd;>OI^MrAkG!uNJ zD<~VWNu+7SwrDDEJeN`oI={sk_49Oxy1{%ptvVxv9gqub$exJN*_6KTE=oJ$`;6a+EEBDuxLQ4m}dl z-VGBLSqoLNKV@dHLz)+PYKI#SP-6O5yLLoz7N0v`$GU6RSm0092ZF%Bf8tD>+!tS^@lUY-T&s3Aj@< z?~DkewZr$>?o$RlKXlcgn;(oA(bMcvBUUskiDOB5SP6NR5aEB0v|oynU|6WxUFE3{KHEIXkGaa*L(P4EM@ z3vqzb*!e*>@4+XAU6i~`Rb5N#@&20Uy4}k=unw2iAu6`gvUdp2bT)pBy0OvIsJ=Ou zIq&9)BX5&gmSr;zL>y?n?S zc~_$^A1gqSb|ME4(@=>XV1M*D$v0K!vzNV3Scw>X(mL2WRQZa(g1xegu*#=!KXbNBmc4b%~ z$p4itnmS|yXF+g_Ex8uP3rA{yMgn@$^)75D9gRmCSV-UN;2vbOqX<4}I=H}qyn!*; z<5FWMV54@j8ZdP!ZLe>iJ0mfN{yKl%64s}OTX!daJ!DbvYGUn~ewWENmbVe+X#moZ zpE;tg+E6?bZcq#cnCmsD(Nnw`zZkX@Ah;T2fZ)y=(^EkOV(Dxt-w%^9FatQQ1r6Z1 z%RsIzY9vY~xQ2r0D-ppD?UGH|Z4J&;o|jD;m@Lr6$JoQ@8vxC5FGKBfni%L zY*4K;b`3zu$V9kAE0-%C9|>YlP(npxP6L3j-BpAy4<0~E9>LxDU;6~rOVK4wMf&*` z$3o&Bj33NMr{1YvL(3bt_rZ~Cat|)meyv+@BQ|^d(hYgum%IoEWTM&c+2UnHG>n4I zbiy-XIv3k#J)6z3K!J(5Vw*ghNwW!r6dj zxPx7S2G6;!MWQGD{0lFr>gZn!oBeyc|MPXu#{PH)#9Weiy*$1vQ0hKX3NHo_YfMg~ zC#;3=Pr_(8w}2&dCQXM2Ej?7&9oO-xn?S4OB~0K1g%q3wck;zF+9w{W0u1Fck)REG zWRgq6%()ZVWhBekw@3J@_Z?9{aFt*?Oo%o+mHFl;A9|^yQyf{d@fn$CQkv{8su+cO z?2Vd>2Vz8^+9D3ML#uHewA*8jBTLap2@x4BbX|_{STi~bBvhW7Fjz{9_2kGM#uNPV zK5}-m%{=*(zPE)}?%W7ZO?7wE0cc%c?*)<&jQlX~ep2)P=r=cwPwMp=43e4;kw-Tt z)zEsFNrQsIC5j4l-H}1w9c3G^75G8GY|p6$sGP z2V6eQ{x;W}^^IPgwP|0A!V){Ulw9)JOxO9#H%2Py32u7Gxvh@T|CM9oS%MM!ESlLy zU2s@yW`SmnQ9N3DS!B(MS+By&gp1D{at5|rLTXiy|4r7FSxc@lV!hmHk5XEO7Tu{Z z>OkQKaYj-^%5AjreIlYXqeW*#;5@+tnIt`lxJvNb>)QlS@VA>kLatkx9b#51ot-W% z!rhg>kyDv!Lm|=~6AH^n_jc~Ro!KP@3GS_QX)Nv@(#1=~Wrx6zzMtlgV><<8@^GHm zoOkg_Ka1M%D4fzhrc21BMINEK3=>>X^?0#8@u7q_u1A@67=WxNE@0{nMz?^uJ_cC6 z9xmq4Cldqw!0nqPyczDe7F}6CkwVUvSX}T-sf`Oi563z?7rX8@$QYZYXVYU{$2dh9 zjPpeb_x4=+DZfR_)Dh2io z9OX&11FAu=Y*8Q#Vt4j>Y3Zk9$FT;D(2yvE#&9LGlQ$AhMR4_-7RC;B>J5JoagUv{yRC)#Xj>bzG31 zka0bOBcQT@+8#VQ515Z~s^OFmd}lXC(V+i1>ADy37^zOguL{|}IIa>LGNM2-bV2np z=i@u?`SUR-|25;$ylv*PH*=|KSR~HrG8x^CdtzTl`JH=v>((YFKV+kLvypQfW(l^m zO7VmsY{DC9aXPmxKJF>~@;zrlKN^21qphCN|Am8GyncQA_96||%N-HDwRrNnE<9im zdQVmJQoP%&F9z8Va+xJl_J@kFFx1od~m*W zFrf9O&F$Yt<8c51h{1^c3I*IzgyYY1?IcF}c1FZHlNk$O1uoR~CN8tTSGO|e*7vC? ztY!)Zv4u{QMwnbQ*N105@>p(vpJs6xo!s4M(OKO(XeL_Z86{)eLHwv@fGhQ(rSGkQ zGKE%^QJl4wo5{a%AL`PU{VXerRbgB33%R(ugi~fT5+a+7eAjtyu@tOc%z z1{l02B*`0;wt-YL#g$h;9s==g?`n{9w=3y%?{7xtBHzH7y~$^RVDV6i?1x=mUu3yM zm09%Lx6x44Az~gC1x26icJGFiY{)w#I19O4b2FMB+!z-AypuE;m2-dAsarpmA6tiE zgIu4)=e;}zL#^!C+td+=a$D~yyF1&WAJiYTs(TQy1vK~G5jk7?;ht0YsXBuX_wG5i z!iMlM=$j5{vs<~t zhT+`R8juaYCiN)?jJzQ9I9v(U+V6-=MJFM>V$%)tilbPEV!1i6pFY*Dx*?Hi#!pC}7QkFwBT?W7{+Hm4*o1y9w@B@tZZJYF{sNSKf8Jv(1kdKc3BNs{;q(u;laCZ!|0NZ=4(0-g4@Rk4w+BCHZHrRMWo=?C})#RSlPYi@tdBWeRw$Z-pNpVngDhQ3k&4)vO-+0>!ii%?+X#i zyPkW?+ueQ4clnLdx-aiu6rEw5^<5LZX2#Q1d6%X&OfZ*(XJ;yur!R>55ZQ6p+Ki#` zE#;-CZ1c+OgGVnZ?kcgYoqt0kRbj%Ktm`v!rzzzdP0MO=4H}B)Y<_F~whN9Tb8$4upXqoT89af%u)2k2TFl*GedkSFhweL7M_ zOzkKAQ3RKSt^4Wc{}#vn?+MoaUc$G(WIzAg;y8~0K^tyjriV4b$T1z+AnS2Y8^Q z4vR9RU;|J#V$08w9eg;|PMsF4WPGk`8}zw=<$w3a zZAgs`fH=9EQ6N25jm9OUMq2dC7>4se4_3=$xeuLj$}AKB&Z7hw-C)){GYZvda6Tst zr11g3mUt5R;nE!!SkiXbM}9?Zou@v0o*P|{o9K5Vq<($V2hD*y%RdukRqLP?E#E`_ z82C?HK8wt_xqtqkVV@!963%UgNin&ld;MIUM@LApu&1$7d%OiUQajGsEDSL(HwsJfnU% zEdQ;1FY79PJoQJUxam7cnfUHx#)6?=TGp%dA{X&3Z{>?qxovObGJnI_^t{HxJ0!Vn z>!iIPBmnT!1>Nk~9&G|OLXR@Ei$d$6P$J!mM6d|X&R{g0v!;t`z3b^%VPINfz0+)iWaHMVvs^H z3%bRQejxeSCaL^5OwgbtPeP|+58-bkAjlF@oxwtCanHPd=_i$m`t19rJ8|k~*^?6b4Mk@U1D@i?!-ES6ejP6i2 zy6yJNftoN)Zn{NvZIQv{v75H!%FrHxHQJ_Lk1>;);B}b`uJ25{ySbbx=drAmF?h_U z$UiDJR?M@`EYt4jCG<6?Rh&ej*snF4b`Cu$<5_JyFxQJXVE_M{G-xAy)bhzm){NJm zK(3cPr=(fmsP%%<_dN#mY{W!po*Peie-ILt zfLt6DzdRo%WcrDzRllCIM?Oo*1!(amyn@7qKkZf7<)1bAK-=4_IGI1qRMMYk{lUHF zi8ND3j4LL&56$JhM&VEGyaQ|EW#`{TWA2S6?haWM_ioJC3iS~AFLbpca#ox^?OGhq zHj&?SF5=$ZBp!Tk4`$S-8k^as{u^@R$Z!7R*w&g()T$H z;DwPny*@Hw0KQ#N@0l-2I;+I>0I*6En5S>J=5d>(JXZf#YOSrbfHQEl2=YM@?Xyb1 z<{9ErMKL2}+I#Lfl+v#wygwy40$TR4Ura0PH(3CtU>_L#mye@e1A4MC4T@NF7rF!GCiRmU7UC8Of8JmTg9A-iqG)EFGTLgjI?dN$r| zBxUDLM$~dx97k<{CbnBEealt z9uy6QTnN}Zh$p@Ofn^;x<*hn$jg{LP&)7Qu*4eedf4I+HaW;C{bM=#%-)=vwIiCPr z{ge0Bo>hQ?1K1nl$|QYE`{9X|^SnX$l6)M0z=>eJ2Xsi^i zNs}KHi5Vpoa6QZMcHEKX7(9R@x7wbUw@K#u|CTU)mDT~MzIGij-CDu%{5|7joV$ph z)mGwaedz51+5r3<-}F|0)E%Z4{st|n`Ee3$qds_DS8+26M*>)*0NkqW-DD_0fCm=T z!=z?Rz2_ClM$+w6+ys+9%)R*?_3Vw@>T4q`xxs8epGg ztMQj0fiYi`Cw>lH(?{rXKJMrpF!3ZD0anZ{-`oY2x4*ocI(k8jFwP4qJ=Mp3Gfi7m2{C?D6sQ+I6#?xf<5xJ zd7FgFrPW-;HvoHu2kpBIZkh%LGg(+q?5}jEEyp8?8kw7 z443YQ5wxoqPMV2c1UlsJ8z0hOC4JFrE=gh!w@&(0fwzrjJ2k%^l^ak(G`#-MdDUCrKIX8!#N#dF893Y*)yKw7|t*_j%u9 zCVl*Il<%ilSzZ`v7Ck#D#muOfg7o%H{SzKs<5vd-^y?IGU4XO$RgZj=e$#f*Is?gJ zJgLYUoCf%;{#{;P-ZpJs!obygk80tn0+O336cy^cr;SDsoF9hqtkQSHHS9a$y81oW z8721V{K-x`i|Sw*)c>p!YeXCeEN%~pQl++j^uU0@s09MZHy#{8fOJt#oq!{CLe+E0 zX~TpxTwy@1yObD$1G?dut6e!F944^KGG#i5!-S<2f2bGSw<`90z1IOlvw;Ke)y40> z@SJ$HKnl}BOsv%t_j%Ljr_A-Mp?kh$@GC`2W*Ky2h%6y_8u7s;*Vh`)Kina=ci~eEu;r9-OP& z`vhMBGh$UhlFQ!>@McPPk5m@b%Xrk|=wZ_UU0Nt9@qEGE%wf*&tZ}(wv{KAD{$ZA- zST}o19%#4rv4ri#`*85u%gvZAlO$Hk=E|qbHn4n|6UrP1NaIj_et<73-+)ajaZD=p z&}f{eT)2Uc-#wY`2K=}t#3jl(me+T49IIvTP(71ko1Yu zZl3hpMjRJv2S@^g^a>1TwQ`%iZrj-?)3jDlQb<9{F+}?DFhw za~lTJ{382#5(K1Ds}FhpphjP&1upse@q%|&SIt}f@2N@I;hBe?$LOELj@oFj!cjna z{)(spw&7Pf3ZQrDjgSaHrIK5Q!f*YCsBz}}I!R&wz!}MM;$)G&0K@B z*Vy2;v125l_FuC!72Yo11RgGsH%Ukhp=UrcNxXb)nD89h!1TtwlKnwsi53mK12K{% zkj!2@HC{mr-y2YUDTjAqE083V*=;ay6MWCDc(OI6%X0sr( z8ar=PU568Z*b`^HTX<|$XQpb&$#mPp{%zTsk$h(0#IU*ubIM?N>3}~uN3t{car*_& zsJlbT&TXnujovUQyC(p(sH{uU@s80Qe5H`!i!+5WLe@m_p# zHg;!_a+Ng?EIk2>mlfw8SX!lNu;4gIOAKV#4!h+(M+$VCoGYVqiP!N3zD~2Jb)}Mg zdQi0}Q|+^-g3ZKFUi>0>@%|-T(&shluU&d=n6Q!|R^y>BBc%*LCxdBz=`!5eEbxS% z7yW0Xloyn!Ea2Xx^(`7Ee6^tr6G}AcUt$WF>*4nS!-O;b5YS73a@RWzok*M5hsi~k zS4{HI6rT(`tYRS3(30P@IwdFdbV1`abb~jTTdXDpsXU*vtD4{TPA;w2RV_P)BTZHC z0`MWt_rBfLg5zhlqi-zMNV{qU>*51~R-H%EISuXw!rLJK4#%b1usvz06XGRUyZwrZ zWxjhI9i3JiEjB=dx!+E^Q^*DQn0C0v1iugrK1~e`WP0* zJH=|~@1w!DzHdsh|Ck58K5B?y`&_L&3Ewen=>&T2670ktyc9*N~m!? zjP11IT`3CD&peyKuNUn*r%=~aJcTVv-xyAAdu|cXWt|TO%wolL5!q(r(@7M-ZV_gDCPX~~c`>3x{a8;A=+@|_0e z|1`n*+eBN)i9UcQc>BUfB5wFOW0CM-*Xn7#wzo62Tr~nG#ce%L-7;xQQ(VO4Rf|ET z&xwEWbm1SJp8Q`N@1G$hy37yl7EHZ_t)=T(wv{^$m`JD`E+96V`xOMmeyqaE#OKi4 zXVnnaf7TyUZ>RJe>4Yf)b{9MR{FJ7%I~U)fwiKPd&dcVaw;Njf93oZV#faRZQI4aa zb=e4RO1jTB#H&=J0sf;5CHXxEIus+yyBsilomABQ3QyAsd;TK1LFCD+oayS(F@)cMjPjJ_tt zV@H^E%7sU(g^W|lL2Z&M1vn_WcKpw?AjE^t*B62S>(0ok>*E&yg=(9?1%E;GzBfiV zOtm9nNuIb#Ve1;Y%0aTkWtb2JxDgs; zI!d32(LB<3`vB64oRJ}HB78LnMT%CSno5!w?eB; z0QUkm2BgdA&r7kFhLjFmd*$s#nkr{W(v zHIRd1^nsKSa>dbL(NZFMB~_+P2KQ3*=fpDYtw+Ua$X<_`fCx|w1^f#9L4SmO8PKrM zwZM-{!2cRqu+ZQWG)R-Feh&p)0Z<6YRmw^Wb{i|8lRUQ#0j0+H3m}DN;FTodQdba1 zo6BBa5oiVC>7SHZcuhhzz&BE{$eQ2Q9o|45-?2B;k=#VitJ5scZ8_kVQHGMA&{Y?_ zE~{Ie6#I?yxJ-v?<7erwT0#EUN9_%SjQ|vJIvTx2^5O~)=#a5ZgAN3|5pJj_Rp(^G z2~>1CPq?DgV9mleh&Y9xupJ9W?O(1mGR{wEDh`-tEEwb8FMUQFvro}&R+hjmDQmg-xlw(%u*>_d2>_5iBGu%Gp0OnzXYRczyxLOL>e-z z=VLMG-l)Q7=ql`yGifHXEYw-$tWYSjo*Tw_M@yjie!F7~?kY#yaA$K4Q|oo^>IcO+ zjuyM--vxbtfDLjLqs(#4T<-Lv^*J!4()t)(zX@Gxod5(Swz3jx1Q2*Zz})JzR29AQ!yD@Wv(yWfvBAdx#^H#_rTVgi zVX93Z&T@nG-sKB3#p<{Un#p@md@P(wbnn_plk72+mu>^5lKUY1$NPvm9szX1rKT9W z3V8{?M&8zfewKcH!-QHUPyW$)*l#Gz2LlJMXM8IC@jmJS{)`=&2&p1{PZl4iNf}?A z;)?mB>a;|bv}h;<`u^6>?I8;h3n&q(!A{^G@is;RPAUWy($bsG)i`NHQ3K418=__M z;~g+i|4~^$s-}JI5yCk`a1V0ka~hO0BBd#VffqUP{f%A>f8Xa+mk{ z!qHbeqb^c^1xJ7Mj5bzWoUL)ov&-RtT$z2IzJ27&l% z;H&Efh6y3;`TA#nMK4|8-Y72=XhwvwYzBEp9NaN_a9m;2SU09-WqEn`ynfD(*HG1) z8lXcX{#81(Rs#?KF0=zZ+719WJy7XlW$2{37L@O30bXP!a3m6kw8z9^vUs%!Hx>2t z17ZwrK=7zAE%v}6c0WiIyh%X%#^gdm6C7CfPWxtn)fs>MNwdyt|D(^q@{+}3{X%>% zRjLG6M=(Fbk}4=M38p%uPyZ8A=MSYogDLGs;X@(Vq$=dwloZe{F8_(_VGss6Xv34XVthgMh;T{IG z6}z>q7RaqPBUAPkiyRlIW>#V<181`_x_;Bv8uYF8^ntD$Da|RL`n256mWN03)-&Z) zI`tcnPDsgJ61a?0I-;rm5BN|YFQ@Ds z0t{RGpZ_?xv%a&V6m|jTtd@1(-J}|(mE%E~SO2?1Zu)p$MBRB^im>pnXikk$A*j?D zNE)C3L8#!nl)Io+{qry(vAhC@u?x_Tcg4Lasj25^btouiYHL+371B7s)JL+P!lMAo zUd!EEW*^@xz6RW-_8`OpWMROc{_we%L^nw>#<*Q=o&T}n?hBorc?YI#GnnS79ypq5 z*mG;_c_SCu8JSn%bZ*@`L+1U#ICQOQ;u@^|!9nsjeidl0q z{4b2DlD}KqVeLk1UN4zO{b@A_wJY}L&QDJ5(y~s=O}= z5ll$nH^0lABc)$?smWBsOL4<8k9pBn*2Fc-TH?#Q8~HC!7i+(Ftl?Q~{Uo>YwHY1* z%NP+|wS*VM8vNh}C?|&8%^QQx5i=UtJ~%O9!MJ!=r+1n2%F}fsRDw1o3i_R>@KNsS1T)>dGy-UK`e{A;+s`LKRm7xFqyMK;* zHAd_Wh=&F6#~BC%ma2iM5gBy#AhTOt@F~Dt-*XrDOCdi(>l_Q6eBkK%#=8f+F35L`6VE41!XXNN*yd zR0)}aC`yZff`AYe5D_8LJCQEZq(~J)h=7zpLJ5H+(>w6n=bp9K`u5%HtbLz*&biO= z5C529jxpvt##_ekZRm}loY-knX$N+&d`RpN)M8`?b9X@y#;jdG9>6$M8wlL4<_pCdr)x_G3)eVP;cRbP7%UYWF znpkiVQMv{VKGh;EE}lBX!SN<)<7pr6;#YVdOoCOX_Ec41LWj4b%hT*&@{;!S^u0h= zZ#wE>*X*PpsAWHN4pbV;pTq#5MOX%u@f5ubI!}3okXCBPR+gPmOkIFKo(3gFOi6$b zwG6^3EuD6-92<=P@IfzfX}fgcYq6Trv_{UAB=9U`19&DZUASb+(yMJ+_Tt#VDRcOY z?50H#8?45gjG?>+)a2;Q>bW%6=gBX$&Mfmm#F0c=>cOWk!K;*U?NVM-7e+Tol z%5mDvfAN(1Au+3w_zNWD%%o~4lAc6;34Ja?WN`IDvje3*))BltL7t)A^b|V#QclzI zB@_?zLhIRoKE=cflA{ZtlGz%S;?_;$S6}dEJL|CJTlb?zM`&%R@vFQ!s3|+?!nx*v zI|QF=cHGD98Sjy2OvTK)O=iE_As4@Fn4A_|-0`2!Rb$JIaf}5us){C%3kTMANd;l~ z70*1Gc!t2T@S~#UBcIE>*+fse-_n; z1Do`o3jeqr2ug;mrDa)t%aw6z#b&3$rkD7{K*>Rwy`%9_U9;0A;DKWVl#&uC zUo(o9#9kVmiI=Rc(5`Df+O&Yo3cUMA-lbhN0Uqyd=`7G8U6=P@&1fcD(^T{!X2>iQ zB`3R**>*eb*_|1YEzwyQ@cYhV;;cJAWVecmrMgIAo=_RlZ2v4jrR5RD9Q=S*eZq zC2Hh)35j|AQYLo@HL-hI^R@Bs9BznH;YmqPj3omC z{Ja91_NVx#IYf!uMq27SL_e3&aq`*1$Acsw>2`CR__@Ie_%lEqA+DjO6Xs_rW&#-f ze*he~3CFF1X7C&_a2&cCyy5U?|5atX4t62Xed9TAiA>-x%RkS32~4vOkc%svGra;N z*nQJp%T3gqnfS$%ZO1k+Tk<`tb|!IJ@ag{3vGoaNo2K`cRhaqA4V$r|v0Nc0WwfXwsEW5X%#=c5}!;}#{jj>m(A+MJk`kISheKIZA)oY{``~g(M-IOuZ z0UkY`%INB0t#09(J>s}CPKGF76BD4%2`iA~n&JEJ`XPF{>$CA2_%b8vHi7v{bSt7Eo!5Lj{f8 zitES;rBryo9wwKGw{=kh?m5%%RQSgI zrADq!D3Gl5L%$#5)*kYJhq`*%fvRP?F9|+mu%@^91hfVERTjU)O@fbt9k&#bkwrM8 z_g7yQ`gbbOJ)9YkcoC|1DLw|;_IR!B@Vk+v?9C*{{x$i2a!tMm0r`%{Tdp3wY%&{R z_nm+?4%}pJ^(1z-N(uf#Kws4g{!fJ+b_F?8t?~=BtdCj;;G=8OuL!H+q3IYr(->^o zJ3pYRE&^4Rz?&X0Fr^W`?AXZ72R=nrH5>0rLoHeliWXVAwDfqj(kzg*^UkbD@ricIMfXkh*$2Zh2g$`VmgU%=7BW=b_~9RT4E5_pzeWFZf6zyT-P>C40Fr# z7mxDMQ{aD-`%A=Y7%9JWt=Y^7s4MPe;3;&TKBLp==q>Z3{CuAWb;TinTYL!7bdj4L)_gI3ZP5S4AP$|9mJ?fgm{9p^WVQ@X2G znDJoFwyY#F>1kdvG6m)yNT~!k5qd>C?*J&3ysnGn#dQ zq&L0r0N%_1^{rm5IfbD9r??hqHG{Jcle+jXD}{fxc3*E4=MLZ{P)JDvf`jf_*Z*A_ z9uGF2_v$YmP-?h$k*n?SFzgmP&ZoJT{qYt2{Ks~0%mU|ZnR(&9sAF9?^}YL#!y+42 zftKme<6lbIGx`|-FY23Ufs9tnSSiIRZ!BHg$~q0CK^&U7_qT+3VD)=3Vy5?2*cWEg9&an`n z!)a##|HXd?Ljg+iH<>O!|D9U_?@UU7;8@c5u2Xuc1^B@C%UrQZp0&s2ldf;>_Ku2j_b^)Ho8UzDbhr9Lg9ZQ(v zlk6181emy=wsn_qTV=RBlfCc=eZ3FjF=MNBD-S8~=WsdOM4w_vVH}!CKT4jl7(dF-0 zMOGL8(2#iA3oFYH1ObtpBU_KQ9(c5SUi?jrVfp39`Tq|?f?&7+JV&?}j^YQnhF-%Q z?DNfZ0tPV^;FEvmsWQwiA@lh8Nq0W-?`?Ee82;QhQRS&Qs#f(?-ei zX~xR0uh{aSF1#bQ{>6~pQ0;eUB!#qi8?zFor2#Dzy+U+ukzasdX&@#x0$?E(B%HKd zW&rV>T!^GEBUy4pu9O&Ti;pC8Xzw;G28VITz`YAhq3To6&k@qX4(u&p3cKrZPBp@O z04~zo1ZtY3Qhfos;>yF_{Xb0G{}om(^i54NQ08K{w_EO>!p@l^OOnxBLZ1!xTQ%K1 z`ebLcrM8q|uE9IUPYSPo__cFX74HKIgg!>mF#uI*-m-!Ok}NnEQ0SzZ0Y6Q7nYt3w zemTGNV|gS_gyR{RR#g6wHlY)(C7JcwIn7%jVyFK1wC&scJvbW6pl>@Iezg~t{KYfC z3-*AE6CNFxoD{?;eL)77;zPw1a*iFEJ{c-mtD17iZ}~AAp}^lvKUSh}qW1a~W=u%s z+YZ+j*oD!{yO>=!-oLd9zmphJiZhfd7b*#pmN99|h|El=xZr2uK$LIx?CueG4F2S9unBze{3Fx zu^)h{Em0%MOw*zsAHa(13<3=N++Uf{v#1ZAKib@L;8{LaU7>F80fo9;8QB;&;4onx zVnvR$heLuSkM<`>gm?5PViSwR{KhcNr3^W-QCB*nUS}A9+G<;sK)8_Ow9koK*^UzD z9WIM-AAX?P?BBgvjyWSyudrXlF8QFHsdRAtmr#w5e(4ub=3f3W@p<0Swk z_@5Q)8x#ci9Ffqyh88Ybc#%= z`hY`Q&?^?Ss?D-tB#nTA^FyL@bFuONf}0vw&*YJvW!ieGA=6 zK60}8S(k^_ChSpC`A7X9Rwt8a8jY)kRRsT2A8mFNs~>eOd#N5|MC_fkKPrJ2WSWNccL4#n)(Z& z&4YV-8I#xpVqDGUSaODgLwas(U8`$N4!7Ms?21_!##^U?GC@71qB3(>O2z8?X2(Y+ zTVKlx8b~Gil*Kyz$73deA^%Pvye~lgIZI2gDPV;Hhk|8Mx zmz)e={qcDyMybs=1Xf^KHQf8yQSf-lzFfWQy5oH3W}Ep}2c$frW*y1w`T_MD3o%OV z6XY<`$Xf#aA~409926gGooM!1y@%eFiKk>Vh8u<7?ofRd?h)!4`rQV&+24?NYg&)f!<}dZe!ercve7 zIh9E_s^X0qMR9WbS}UYO@5`TTa&-XCTms)(M=Rt2hC`7J^Fc*0vHLp_Kg&I$HkSNzU z7M(bwt-yz{p@6fWWNd}2Ro|&W< z*Av9x1Jjp(RteJMQkKoOs_VaV?VWc#rBb`PE44|mTW#ppV!cgzzkEa9wF|N(i34r& zN$;Qp#eM{(K7%8MfVb=(KEx%GoRJTK$P}B&ppVU{K|5O+8%F1Xanj9iI7;{{$&08E zBp*ak`?eSanxQkg87yzloV4F=Pi_2I4t85RKLw8LERuF4B8hf|T^GS*M!_MFR#e=kPIwBgHBT zo;ujX>q~XndBY7x8ru}znNv^3o*NK=IZg<=qzVz1y1 zkQ5C%E@Raw$wwM%aXaX3_?uV{PhW)-xC95A^uDv!0STf9qEjr)^8*{Vc3jihK{t@1 zqa_Ttel2U-(Q*DYHSXZ{JbkIh)g2vSWlz7H-`&dsVH4H%s1ZDJ3p5g6g=BgAj-UqI z>X68aGo!?vWR^+&3EXQs^#a{Go}yRub=Et;?@c!du-NIBTxfVbv46B%L8r37(zU>| z`R=~Sl7a%@OCYjjj$~%Kq3HS~mYnYEEb`El?>yWHt-BG&TpcyY%Cya61->yKkd19= zR|p<%8{qd_&XwPBKA|RzhkNv2y_5aNMV5M3U-te79UgveV4orGVvPY85{lcP3TiX0 zAFm9n@TJCc0D!tI4$l1|eACYt`KZV&-)q`)>%9;YTvy_f8|Ww;m;eLHZK3@ z&BUTuc7RDzqP4QQ#oQ!j;01RS2z%@&-6W4$^?zR(_86r3%(6|}6`ODR2NWj{nsw}D zA;+ljp~${QPPJzja!FL{`EtdkQ9WTlkBCNUiR0TX$=K#?2K-@u-=&nEdO3e%el?01 z6XCl%x3BE$SpAKkRGG~^#;)7GXy$o-%Q`t2h}M_(X%)I_DrHwG z-OY$tZT6f@>3LKY9X>J_k7vCrwnHv#ZoCL}8?9bt!6Gw_9CSW@^&zzg^>eQq*k4v6 zMg){gk_YBU?LLI!s{a_PYC?>;pLfL-WHdFxm~Z6gRc`1_tl&%W_smz@z2_!v0}qX2 zwHOM?ie8>eB^AqyUHJ4sMPw~r5w<(N=2$xgO|n*dLU6qEzj($E64-YdiQH2JOVp?f z)(n97A3}feY<;Flnw*FqWrzOVRQBjc(uZQx;YV7<8pCj+&0^LQI=(^3s~fAORcwhT zy^RT!pJZ|5!A>^A5Up35v`7zhJ9|pF8PvD>078+Zz@@dzWLl47s2Bz|Rpco!@M1#a zxtjAF%x$>t6YywbIO5>#)$;k`=F1#0M%s(POuySsJqsqx#g6vuYxohfDEo#XliCeh z#bw0ew~qteru;r=R^XO>lsMv(cxHP#aN<@#qDr(bB<8|D^;uW&Oe}-*Vld0k$f<{I z#$5c0&bC7LH;zgrtKl~zvf=!>Ny@?`##{#)qb^(J;;CTIJ_a6Iyhn})JwZx=W4jOB zP~Wb3O#foXDBLGOuqBD51c=KbU?uje}F9w4QsS96X+Mdi82< z*D8xE2h*iaVyyH=iHh(wm0vs_=3w}YyVI6}EGi)73u}nB3~2i^&>8qt(-{~zI1kBD zHUc}Ek%9@IMfSZwj;&dv$Q8&8XSyPSvL}Sy@#?MEtW}mm0)yY3G>v6yvOZ31sFPlY zylzsbHJl=%Rmj{3335m)51io9V`~!%Pn%I$nPb4SXX6C^`)ndjSQ1$Ct#`73o8pWG z0^WJRAL?pXtY1dVTtv)&17Zx%A!U)|vHr*z48!N5I|EVMLAY*JhZ{~2(kkrrlVKib zrBinHA+Od#dlatrR)x9y1AHa-ELGG)pNW;(S!ech5GDA+5BqZ z3s>}2T@8rc(OBDg2Iwp_J7WjivL*qq5H5TTr-lM%N?*2vQ_TU>ywhLI!D2e^)@$Ap z76~SXedaz=*@oe>irt^eS9UDSIQ6RTh19XwkaQO--PtR5ZtmE(^FVineH!udoM__Y z5Ki&S07?ADe1(c_-*nEP;`ErJ0Kww3YcRgqZDnTZ?7x_E0=vN~(3j6hIvT}3smPLpU?O3C?`nnF ziZ;g)1W7zajif+K9}N8x5SAFNO~fU?|Lv0#{?(+0|3AI=A0=oUMbd9Ng<)0XW;7VW z)w!(jv$6F|QUM;1J>yNe@@Zf2Af zl@y0LChimjDFGu>NY+88nPRpUTtg5=*$_pqqZz!+aPE7Kh|OD;9fk1FyWDM?Hf1jK z1J*Fz@4+QjF+-GP<;tr|AB!uelzY>cLmi5)n~m;T${vo-D0~#|Mr7g7!FNz2WF%|1 z3^w3|293zRi$xWda0!LG5Lb|!aGO|-mr4u>5pF~mGfR7X)q@tBcuu|=j5^?0& z&AaC}tNp;K13`BW8hM_wH%cF?#6HSW`4m59)qLX4)N0$aP7O{$X_t4l?datFQ+xBi zd9gly-XQ*ow}n(3&xu2@_WN=Pj}m_Id?2&WnN4oOT?KwXc-sUw5qqL$t`=Cgyfuou zK(*Au7CG%ejPAFvG~@atnkd_{;1O-hg*q4J^6D4WHwQ!h@Y~}D8}|aO{2Y^O7J6j7 zQ`3OGmnC1jdSBN6@#S(PmAcq`)-;^CwP7z_R{!x^LmIodQaP! zY?UM*z%WK)$+=!{_X7J>bGv53xva6AVgZY*&-gZ;2;&>Ajk!T+dH6zJ)ggN4X;k`@ z{7MlX=F?z{KLvt(i#DZn>js|AYpFcCw=Xa0ICvO+QeND>UB{yuj26s|-gkZs-9Pau zuYRw5<6bLQMXBhLtx-{BL1w1Ln{V8{63jP!8rgAGgtMRn{vOADS~A2w?ze+`b4dnh zkr$zt?lEbJouTk%Hd|Wi7B{Klk9z_)myQJ9b44bse&t<6=-@U1Qg004Mn-AiTD5pQ zvg0!P?gF$~a5Z3gG84fOoM0X~HpEuOp7O|!0wijdP5=Ny&w&lg0QFS^Bh%QzFK=<< z;avpqJK-&=;2vSJQ6!g_BiPJDf;SIikApWe@t|(N5x=EY9$Y_YOZ^e5mL%CcYMARc zqvWpkxf0N;d;y4CZnj94jML064wM&PtH=_GdWX`SN`UoNwj=ru$*Jpg@uuOT&%i;sl=GvJFLeNg`AR-Kxn*O@W8+T zVxXA$A}%2O$(VhZ_?{=yAYLu*7}eC*;i@cjHBo@{+Al+|i=$16^f=Fu4$86$&sMzl z)m#1)Mz1^DBEfV5-MzSyqd+{||NNPuhh}t~>AJkl$T~7dteN?aF2@n2!dp`5#h);A zw8x{AgRH_)#Z4^fcBTWp7w^@anbJj#NR*4j+EGnL6$O6AU$(OFv!WC~AYw72)9H+! zQhyhLOv-WXh5tC93uPBTh$UNDNr{WYh3Fa|ifTp)a>Bv}xv&x+1= zNh~+l&T-S-X;mXF_>1S#$I{puE#cq;UfIep+%wF!Dvbi+y25>vsDZ3Elb?|O9oN*; z0Y}LJj?#0DYj@WGF}ymTi-EMbW6>4sM5 z26;sRNjE{VQQOeJcq~XvUt;GLjs=6r+1||bWZhXVmLSepj3gUe@^6`Ulh`QH{$%lE z)>hA*&kw7)B_&I=D9FEyzf9;jOx-WuisKEO=0F-gC&@DAbQxXe1NEyo1~X0#9P9wwR5} zL)8aJwqrJ993i@9c$B4q;U)E<*yuvd>!bR4@5r?TCz>eYKs(q@(3-y zb12@KR{wo=`%&XDyYBwrS#RUIOF~h%IQD3H@z=aAXfRHmlK6$7-NfOA7V0E}AGLoE^QLEjR{V0>x3-8A@;^)0zul$s}e^pfACi7 zn?NyCxhP8>?!7l8xjo=_HDFhvP-7W0sq`#Hes|Cu2*|z3B;5FSLX)Xcy+gd4&paGl#oW*EK$O@n+U{ zjV~mtTYV4ARY?0D6PSL(LzM?t(Sdn#qIZT5P~yuuXO0?M04w)=;J_Dax1Pi_w;jX& zD!K!=2s!6(?Ny)JXtK?;zsE)2dFO6^&mYSqx*@(#d46_u8s3Sa=)6iXjds6TP^rsr z;3fAXcv$8A$<1%B?M<+cbKUXii-dbQHs6jT8 zF0V^MnKmr@*fR=uL2PUP>VuncatKLxcb(@&We*-1XNd2P2xEuc-!gE{aG#KWhkf^t zt1&%2Is$PAMpS)$JwG1m-I{hPJvDfTNNK6@BXOsjXHOkI*R?y=c#q}4u#Rci=c8Y~ z56Re{`Yi4iplAH}LhIe+ERX#L<)7zdqdN6vRGMUReSPm(B=)(20PVN&rNclWXHU?|^OxK~setqIPw2u7uda8duC1A-p{eNO`;+K!u) zZ5de9+!jS`T9|bw1#Y*IT#1W4qxfZ46-LFSy(MFVI@9WFie)F?YOMTJLf5OW)k;B1IPVy(*oa3M=gYqSk z_RQjYJxT6&`Jaj_d^Efh(@<}&-h1`fZt6xeZR-uA#30d>0}O#oBI(Hy1+qzraABTH zbj!!+9iHw&Nqx=uq1h&#=?Kr*6)3E9BFsY6Abu2kVB}?1)ZMcFv;&SiZ@H(~C`*W6 z%%mt?m5a)F(qK#Xn!Xx!MBH*qi0g%0A`_R-ymu!yRNNdzNgQ(uy;I>7EWOJ!7`H=FiONN&{JX#rv23HG}Vm&sI;sl*u9Mqw+e~mm;u{eF@!c{2`zk;g9 z+Lq?XyEuLdr@78r>JuF=X-mJ8S*LkCZv2W$gn&tec_KXuiAb=?8hvL=QBZX@^*(5O zsQ$u-*R6i4m7-O*xxmZo0?bZio<=oE5PO4#_buzagaIJ&Hosc~&Z#|Ex6$U3x9-TA z)kFH_8e;gZvBN_zempqe=}ueJWIdGl-|{g4al4&#dK*$HKV_!cnJEB}IR#tAbh*$4?#3A)G!$~~+=(%s-KI~kJIB!nt~c9NZJW^zLA5l3v)U8T0n zjqDmbbR=TgZw@N1~fUeG^O@ZaT>WKcF|Np6|`R z1h)b}9UdnV^iz)nTY zy~TwM0O<6SLTYmoJSk_i9zrGc3e)&15)t=3kC@8g*_!2#_EmK~;twx>); zt)TaW>2qIbqZ$8>r(eG6A1;%vIcWQ|!CdxnpG{XUfnx!x&Y3Y3;U*B8Nm`QXez$=W z4%j4-l&?{b0fBBbJfn*l!s=jE0JiW0YZgc|U)3pAhsL;-@h$k0T}jk!z%%IFBD7O3TnHc z^@jksTpyV2Kxc5T{o<*-RJ98%v#gP{+3iucdXU7B+_ymF- zSQ->^6y%KVOBxx^PsJKC)H5lH2pxJevz39X3>3&noz(wuT|cvS6w}vUO}83Q%i8qF zz%8lY@@7`Ggst|9F^TSD`Wy1H#NQ75jTJU1=z_C~*DXI6&{oa4U)M*lvyM%4^8m%E>zv zYP`;L<9bxG4v<#)Oz`cs>n0+grf!h)cK=O8SGR^7a;7AaB02~Fu1V1r!&<_`DNyR>Ee)B}*_mT2?yfn0gYDDxKF&WP*IWwMcV2^?K z=N%)SuNzI&2-Ep;J|ZOZrx;TTtTjOj-0xr0f7o@wd<+T+eiAf^xr|>6(=w>$g~f?X zG!-UNM*$dUFr1D7A&$REfc}#N3%^M)cN%$wI0LRan^a|B;|%(Y9ezcxF%-4B-?au{ z-*gjQu<@bqIVYu@;LCHAcKFHr)j*sKRBh002aBgN(O~b<)4!(}$N&rsrT|Co(LQc$ zyEr&{oZsKL_zLm-7U>4S)O}tHX`?tI9-lg1c^*)QKJJgtXfBa?w@r({HnwbGP}*!= zwqC5h{=P#JgWxPuQ_4|qnAfB z-l8U@y$^BBz#H+I?HnbsnH0Z94q>?-4aYy{!G;j%m%#k;cCdOQB?$0KYh>vpVYU3s zq;S1()1H^m5t>^<0VS)6|Ah}DgEg6JDUpGY^x*Q1%OL(AaK*2x|3up)v>XTyQ**(^veP zqG^(&ufQuGd;-8UsW9J8w*O10;cL}SFiB%kG8vw&DP61VUM)}ZKyC6vHsAmogeGB? zfoP&^rxPAmT;_s^EdjXe1E~P@Q8m2PYzB1`5bAw7TW$bu`Vp8hQ>>+zFwRYJPqD&< z;_=`s8F*GTx*rt*wViXs)AU z>a=9(VzfdkS(-(xy#s$G30CZ*i=AbNCX5XEGLfvi>3%V%`0F4c)krhr&RMJ{rKqIS zUGZVjt0Q&WzpPs!u~o4b8FCGZ{pBNKb?u@#$zqQyeCZD;%Msq6y#g#0yoj`RA(ol` zH}CN9lgDozNej9~%!s)jbM0bB!``CEo`KlIOiZwj1vnp=;Z@=J`gvt6KjR`_OD2 zER8pg69`Odu;-x1Fr~PbB%{u zdi1tTqISBg<#^)J#4P6`QQw=ZcgYsR5Jru28LFN|%{zddBXM8CmB62qQC9$Vjw9F$ z^Hs8Sa4qEZoK0*)xD{ELQB4S~I>ybw3Hohl7#Po{V*2t1M;jwJDio6F&l8w1TUJ1- zUzaKO_2On)0l!uAJEw0}I&^YqQe7|8N8{di4TH3wPhAKF@&H_~wj4w<@}QsxGN}0| zu2>gp@$R>IpynQ2=91K)J|y#08OxV_7}uI2Ls!M*H`WZP)C})tpnxqUStGrtSJhpt zCea7)l;tKZ+J&+uO1Qw%q;8>F+TEYbl*0@u(QS(TUFIY~?rV6!bd7s*P*ems01H9* z6)jR&$_ro({5DdMz(@fS$%KB?9^|17y;-HgQMk7lKy0u?6eEum^Z>aO)hY%AL@;SH z${AuE*K2_C5sj!YD1WXF)rUG;$!G-fN5V~O6T`6g5z0+|8iZP2b2}l@R8$eKR|&Yz zfh%cL`q5?GQA7W9^Gnk$akdN~)D(*I(s{$`IcZU_*utj>j%NOd z6KW}{a&6*A73~e=d?aUw+34{&v=}@L(DVk8CB5JD10A>T`7DVMyA*_}GdA@&&ha2X> zlEct`%qsySY`J#xw}cnEgpUqE=QhR&UL+K5_GNp;)(?kzNs3uiOaShiwx z@KR$uruT9$>5hpj!o!H%57lF$W1d$x{!a$phie1rPCS>OY;Lgh%%Q9DdOhae=)F>jG4{B4#CTu3vM3u!8ak zd(Z{vF3?PfQEMubz^ZBQ2P*sDtb)<|S{FY8ztc#%`g#)&@aAPYXqmksu$Z~EtMG7@ zHLc2wiKpp!|8Qz?htculHkz%lrF##i?=HN>={~T2^eA6H)4+0A^a&jFb6kFEA7lAX zEfPjr(;`1$93Wfe)MW+;h_vQG((6%q&<5l@gaOLMx_jOHDqIVo_&<#d>~AB3@!QBq zhHQYkP6f=7O`ov`wg-*Uei2Z)wYO`UAp^$BK+%D#7CZ&eWd!{-=tgC<4HHoE^`ouzEcu%JUbG`Y}NCS(^Ox*qTwZN%1nCWvlonmFL0;T*VX5 zuoi0zh=3Zl!%d)u2q5Tah6lS5m>=HWVEzv9YlN#$r^OVKewDzBv1lwnu}bFUDOfM`Xr{Xe#I zH0LN!Cy?{_>^1YFC=Cf>AO~CSLzD^9R&eS+U?<63k;3Ie9M`pPde^vSeWduebJ7sM z&5yD)kY_cLKC;HzDr`9h@S|+}J;&@TL?|yu$cniji&*p7)gFPSkrKd$(DA-RSo1*) zw&nuE{S3=qg#ZkOm-n3DoKN@6zH3tnoE;V0Aqu!%+QX>n-K5{<#|bR>#NV5`39?@R zOx1R#>X5+N0=)^AJS=Z$BpOwmv=z+r72YY}39foeU8Ge7w;ywnzj!u~+)#ZG^C367 zLXLrA=F)j-dq&6uC<|6EWk_4U6UfGCkKlXp4{zZlunHB7wuG#HHz2SjwDv)+2Hx+o z3(E)48U3{z1zW~)_9gBw;wEASHh{yIilo;-dHxfq`KNfW^+(=wFx+y;1rtFQMco2! z77!cTNC>jt#5zbNhn$G7n?s%@3E+-R6QnBCMm_jBPAox2s&41?CA=V3fF+#ioY)qf ziIwa%WnYp;ew&#n=q_k}tY)JzkiCazS=Z6W$w#5ZKt)NRrR9#rlcqIfJ4U}L4bAeVQL#stC5<`RajiUcM8dH4#&h1FV7_MHh!3Kpe(@M3AGwc0|NIx*hRAc0 zP>xU#5-75E)-gBcILigVf+rAl#C1csPE>J2t|tXAq^-GXUzQTfAT%gX787?>Fz5Yy z?wp_F6XM~QdVrZCQ<35z0e&Kcqfc#@_KVe~djwZVzMe|i&u|LYG<5dT$=Vkh z1)|X{G!J7GQ`{A1+$-4Wt&vo?r zo#nck9!ajmK&QLe>HKeMn=6iQ=UoF51c2!m>nU_C0f*$8!_ZKLLST)G2O)>sF4r9cnT}Crw;B#3SqUl zFNm~TlDw{x*@`1?lMcsBY;0m0%=7F%=ezzDQV@F+tkQ5znR8Cjokqt=Z{W`aJ};V+ zHp`9*aPaQKi?qwP6x2?s)RYy{9?onX$U2bNGqpM6ao$ISM9k*Vibf_tPJ@B;t3Fk= z2iE}Q>ESEfKN+XE&41yf-0|pdKFm@Ncwgy8t zA`Jx8riidSDHwzwqE<_~m>xgF-2D}cqHNpSUOS*3$@5K1fIMZpOVO|^`-2_aQB_&h zzn+^jJ5Fs$_@o8wpU|^VwJPdon-&6qD8)cvj8*W=OFqYC{E7J>((m155U3P!^n8cNbd zy~A6%DSJyV?Zw25q8^M62evpw7dN_$>Ank}C_Q{Fd%4DAFyFYKVy*ly0C5V`T2o%T zdD{8k(v)GJdR9_c@o*$%gDh(EgLMkZs`d?(25RB+PrEl%ff=x1j}*aM1P~yKvYG2Q z7?rC-^kXQ#VHF-!Ob|w(LVuJf=;=e@?;hJBvzhXNeq6iGJhU z6W!luZrS`EE8o2&R$LssFG?oxo%PU79>cpT-zSuu!{o*p%a1>qs!0j3)P_Cm)FR+I7@msqM$ zdnZwk?!g?t3B+~qW4Nu4i$ z{a+!}1Kt&HY9XlTBF7xKy8u-Kpw6XZkq*s_qX^Y@D**vvJgepKEEk z;2|qe8)gxzeT^DPL@qiZd4UMG%7W&HtL(y=f!xA1otug&!dV1c<)(Y>-JV!&asi6F-p}If)&hdXLH1G5 z--|BKS!k5H?*=T0f1vtb5$jX{doR|%^R0oq9lRxN?)b3&UASZf#=$gCS5n5f2tuX^WI;lat&;^t}U`0w{QgLnWr1|AtWoOf1| zW_Axh!-~mB5dK0BjQg_e^;CRZe<5zLlI*h}UM;T|#HfO$SEIa9{R<=U z!BsoRtUUCtBAJWQt9&c+YMq>m{qA^pO0tvg_`y(rBz|bMHI^9)6{dT|AwR2xT z$8WFQ+ql0%#?d@OeuCu{^VLAQe8HLU1w-5Si$|UxSBqrc!F&Wg%JF~nE>bWp&_6!^ z%DdX~Ux7dV&%6KgU2A6l=<3S)D&Gz(fP1z)EB-gU)__@!a=o$+HX+d6iPZt9aIzTW z(xp*{35NuZ0NaN8=?&SKWk3zbZ*aqGLiDJr|| zO2XjMF6PnVZt$e(FuqJ&iYJ_vDLz#(KO42&6V(ZD2V%5e5d&(C0$o;~W?62i$HK zY2j~kYJq=jv!gmx<%%Y~N8cK7U{_1$QVtsr@3dA^UwA=@@^;3)jXoVMNnHdkA{W%i zOBD>iq`MU}zSQ{z|77nd6w=9m^vt0M8ohfZKX>r4%*OXIZOsK?+!7WqxZk?9k8ayM zFePy{Dy4We#LO~H_t=Mv8q#Mir_N~PU3V4OVv(NL?yJdMe!3#YXS;luW{_c(^qELq z*jxS^!{ z`bkY0%k}-@l%|$)6jDSl!L7+U@He%JUty<}DVnuCBhF~5%T^Bcx>GmI01w}Go)@a@Su7QfmpH083Y&k_PXel~6tJ{sQNAjf!eb0LT;_y?B3i-slBi0m8p}ZMmb`bGrm6L(0 zu=R<7nx<06Qk#tzGoCifrf<*Wd3t6yHT$mV3371u(!s>`dWjuoD)`0Z6T5sb&Zk8u zpDzngI(gz6_Ctz2&itw9f~fAg_)g>S%5*XJnXba(sxqq<<_9)1SQ)C$MbQl(+r*7R z+i{(_+FLYZJ_EB1yFXLPGiSm_j*_wNiV*r0@hcfzBj}e%JS^c)DPnZ7AjlatXi!_V zk!~HD?&nq`s>H#T<^_2f9{G0I-Lw~7Kp!hm_ld5T;P;N&yf<3X%apDwBEN`39GM;Z z5$@2MEAB5dsJaQmAL>1{)2!{g@T0clt$OK`rURIlaqcIq+V_5mbKlRl(w%4#b#AWO zJZ4u(5$KSQsEb}-{nBBle8UC(lNzFl=%QroeO46keC?t4#Je4)t@W{GyHvYxmrQ~s z_^&uR+7-$~ik(%IjZxb2_*K4e7f)?nMSUQ6u*t)#=6n4eljejUpU*!#zAoKyXQbJ3 z(*1)AJm*%ghTL;_m;cBu;dNwE6Z=|&|DMM&#zXrY1isd2oe#;D4ZD9Z`o8!Ltjv2{ zowZo!o-hOazys!BNVDoV`ArREk6h2D6?Y<5ua%Nr`@yNX)Oyw;dfOLxu12nwXU_+b*H3=AhyKMHj{LtGJ~ zMX5_-r=OmDL(vbx9lY5vdwDVBGpVMP1rzgnhLoQzCTsio}O3X@3#x0mU6rBW+fx&>h{uSeCPghmYzC>gK^l zWiRb>k$mgQJ|hpzHqTR@EFt%Wyj5Q`BV5|=$ZE*mxY|6m$?C$?z%rfUVarjCW5_S` z^|4$B{UrG1d53gxp1UG&_L2`{R?{uOXADGQLC0H)+M8ocH?Iv3>mJWbc_O_Ph+tc5 zd^(-=H8I!0Gk~)=-`!0z(Qvvjh^w`&ZvN9;%_*E*{)J)C8C(DHnW{t%mLtYXk)1V) zhb*(BofA|WJTJn*zWQlddH&_43(Uu#xG?Veb)&0w!l<|A%#_jfew#x)(Gi??X64Xe z*i5r_3M^l1YtgW{m$h+^?WG5(HQu8{$5TFhV=xTF&0j;+DobjL)kz;Rjfg zHy;)1r`Bf6Bg(VBH>;fLmTa((p1}2V#-VJ)vjYUyRG-y0TS1RZrz+zWf@>EKgP3K) zNpyt6!|+15(~Uv%P*borg06r_W`|VVYVX5Zg%#Leo3PI!8dh83=@SQuxJpG*ay@U8 z@<3kdL$PkfSG8$hvP5qNoaR#&p@K{92pvKa0!9C1D_r5YWGvBtqn;vwzzUdsm{u~l z5fm55rZIb!Iq28Dq#JUkN|Z^}voZ*+Hnlt~3(B#VW_?y0&ut{bd-WLDOUs*?JXYDgpMm^cEJ(Yo#YcI5s;ZJ`f6Pw7rpZvbN zt$$UnZ)K$6!?jR<=lDa{;72=wsfS4-y{!5agOJbcx5#~y+6bhe>2-nH(C?!Bhog@5 zynB+Ttio`pHvrR@3yH(J*hx)&miXunm5|vE^#+o7Hmuh`zH8|Uv|IY4g9AfGcgfxu zBBGzm`7H8MglBy8WkvdyI7syR$Bu8#HD}y;ai!_zXu`qp6mD%cMs{z*^E&E`w9@9! z@oObp+G5SGq92&04StD8IFT5x^~rK~`vqo4lMZq#3ldNqh!!HNuE7WWHUPXNZuW;} zeW5MrEO5j)HLKoNR#rTA_duaM9@N#}z7IGiX=6Ia86ip+%9gUHaVV?CearOcifwZEeX?3$(JbLNk1?v$p>H`St+U zsp}l-bq3Pqp4#PGgQdDuFk_wif@gWH84PiJy_VAzrs zpWMg>cq0B(`H4+JyHdU69%}-Okk}pVk>B(osxyS+!>o^T%bdsNW<#!^TdIE5dgFob z8+StP3CNk5e#{OmA9)CDiX$%}ZFM8!^E|3PtzJXyyYhI@0|>)ZXN_FqnyTbqIxq~! zusC)!MgF=?lFJM{0jqRNWJ+%!3s}~MSWH&DQPMpU8K-;-q-ybypRZh@!Ynihd}lzocd zj|Q4^3OQ$KZ$dcypZ|m%4|2my07Zw-KRS+2D!C2Gvg#QKJ5G5wS8j{*`0B~Tp;*k* z<|UQ(b*L8;vLX~hz;V^+D$QjuLb>L8ob}US@;ix}jNT~{7$0X zd?J0sZrKc|N}Qal^xw8>X&L`H#yudq)0HExs+~&+JUyK8l&SY+Wy-p(d_=tA(IVed z6>rK7Y9}Y1UM!I%KIE($N~@3VjGh^+$z1Y^@xNUsU&7zqcKZqR`d$&sOo_IFd63{4 zZSkV@`{C}l;=fc4PsXO&O0bi847&p45Ycs1>*^~SX1BO&=9>wguNI6-j2qO|MXQ@n zXh`!}?z!Ddls$1(PfQLec5G08r;LcE3UpJLs77QUZzUC^MXuUZDTC_zEJwaYQ}0#c zgG|R!at(r(^t-<@np9NqFwpuW@!{pxqby>8t@cL=se+h4-ZtRWQ2HtRhi06C(?|+fo}<>?zkC^({*uoSqCBDg)&EEd;frtw<4rLOW@tFM21c z7_F->9{V;W-xzS<{w0)mTINgSn)|eR4BLpb0Z;U7IvSbDw?Jrmgd9G`DK6kyKX~?9 zJb|z9e8#y@2Ht6RQJk-yP19RFS6s*JkKja<*KYys=2BC`wAZwr=QxGd9qpF@5DxapvHNI%C^q#8MUOY8 zT2kKwOqsLl@_z`yo0}qWx5+$j%8%Tw84{{+cxNb_ZvVWn>E-W;>&GfWdrkZ&7F=L6qTt;c`T zj9D743V~v|X|YhS2T#87L2@Fq-hAQZq}57>U(&5*xm#~|AOhPsU!*FnPq`a{MV`AU zf0~x>BXR7DQ_|(}mzVD)2{8I9FpNrX%IfGfOi!F^dvP;GoC9ZlbA;(@VUecj{#2!q zMBnV`LEU`KvJz%{Cdt?c?2Xb`$Kck_?>hbF-?e57WV@Drc`X&sFl8W~g)A#>Fu0l( ze2;Ni4KrVMBl3VvZfZY^*4V(NeeX=_b=W5ByMFGp>?I-d$SAR2f#%Rr1&5I#)VSiqy*OsRn`OO4EBMOG@u&hCB*twDCP;w=&oU|>b=nXe@9da1!TMqXtq%pv-9X>lCZ8?b*@KJ+UmDj(~j=ZPvA@NWET@KIEqwyhbTV0SL_{`WY*X-^z89Ck{IxkD)3Dm} zU*jB8aFJdf!w;Q(Z@4x^BOc%nK~DvYR5n#8o_JTwmkFpsBoCrqxlES+{;$|_^2gly z>msq-K7rYpfSfuyD!JAO{?kxvic@EbI7MvfXp3AdJT>snnzif|!C6)ilUpOC>z)+- z;1ZuWyRb~kr*2P=ib9th$);3;j-5)|QS;Y&?oULfy|cSQ^#ij5qpdc1cwLJeV>*q$ zKlvdl_vs^?WghqgSK6enR@674>oJ>6NFSLC?IWf1N&d8t4R2Povp!o(L`_JX`eb=g z;ca=gU+J@trYD;yOc1aBbooyK7*=Qh>s*jlP@E@Esxw7<-H6oStIi&Pu7b7iZObg3ZVK(=Rh|$;nGshkDN#we526in|aK z__gh8-X;}D7?bv+h#7U$JcN~L#Sfp&-Lhmq8@Vd~b^W4#wpOcF)|Io)9Gw9UUT4NR z94*dSXn$pZkm%)XTNV+C{ajj83kXU2Ts!c4hbpb;rv*N$2#MISxd2kTsLhdb8%x#P zQ%l_xo%JVcOG1&liv+9I3=&1QFaL_kskO$=;)PZHOCM{L(T3EX*z;m(la~ujL5lrB z48QP}pPxOq(S4pr_J^P8oju;A!9Rg&2uAcaDhwgtMJElQ3$6Rl@Neai;Zisg6Fv7m z{i_vc8YinMH^nboV%HS~beX$A9{9*4>HJwZHgF#g`d$-ef)N{x){SA{IUNch+Su=m zY0(NW9&wAXI~L2W)>%F@ z#uBkhR?))EtqO7GqUnlRU%L6yAEG!f1jHNBcZ~R((sR`a0p*MGU8;A|s%(B>En23E zrisZu2FjC8iQ3b03lkalo2Q0v$CL{9IjHznc}x|i(B69phP`9%1UZExpGb%J5cS)e z-GJ6s%>;m;@7O|a(($=9+ugYfsr&7~Zw-W6yE^2c4=Z?H>OFE)e+6f6$ax4uX1zeBlpvdkTWa9PD%&|sktt_l`4(^aVvM`>y0mg zTD0=}(%YB^6OUDvmM{m=atI!fQpyJS97v#Jhv+9|Xyw4eMhN)`1`_}6#iMPlZ z@%8V%HciPW4h1}%eha}!LbA8`${P@ZuDx#9kpufT)>ZNKxTR zBt|+5222E=R*qRFj$wz4pk4Aeqqz?LkY0f8D1HA~#pgeC>xEUYLig>abirGHsB8}r zr}MLh?vZCpoRk{I;?*B|xd%EE)YRswM#SjtJuAS>=C27HdE?xO+!H~pqBIPARA!RMvd*)g@W@=24t^Kqr86YwF!d{sXAgYSO04RJ zWNL?`M*`okW8ZAdsaCdDt~aT)%tb4e;b?AF7|aZ6TV8yx^gpB$D*1mYNC8w5a7ayM ze+7^VT%<367XtH>1k!Hum5TC_8WNKste^xTLb1YfPO}Z#BqtK_v2rq0mV5|f*U?s8 z9riJV?+x`5Eiup4%+&HU`SBcGnyl1Io(z(fh@`dBS#(IP`+v$h%!1CgkaC|RmS1`W z-GruAT$VG4zxpd2_6H%B0w}8QFP!d_IfT}nIvQ4n30G$-xPmYCx5b%F^RH4yW|b;* z?oAiQTA{`gq04G^^`u~MCS4B%urZn)nE&Kh-Ee(+W5 z5unn|OyvSe+`~`Noe~~s@)bJM-weAF#xO{-gv!PB6TFYXXHc%@9R$FgRpYkQ9z+wq z3&RZg*^!-NIymWb{1Q>>7vHYu2+!@kG2GXIC=SJI{NgbVp6^{-it__~mi^ryZ#vN`d0=G|(-p11A}QpTEKfmX^V1 zG|`*-Z$I4(=cmtN8hLxnv*f{lb@D5=`_mZe9 zmz@?q#v!2d*8zxmWW5epw-XWpj9*Hw2Z?kKZ@4Wo#{n<2`bk|!cc}aIz z7*?KJn*M^5?n?*!X^ULWA=edezFpbxh0gh~d@ec($(0v}-aabk*N3w@+3aHqe=AK1 zpei#klAQN}OWkvs_Vy}z<60Tx{x8Vo7=l!cdHmuS=pj;}y+!|i2HelDv*24x{}gr{ z*_{Nv6^niwy9eKHt~M!d^PoG8H-%kR!RxyD-@EUd^hpjBnYbB-dJadMaNQ^{uu+A{ z-pXJiAmcs}iX7SB2G8d<;k(bhrt;Hj1JNVd^a%~XfN<6XB#6N~MhwMM1!%Q8Ly5mH z*?0;|)031sSHg=QEUy9w=JNY);8J#8Xk#9|fF@LG za5&@WcD=z}7}8xt#4|7r$G!Fkz)LFA-nIu*8KgitW@uQDlzZ+~C~th0C}+S^w2 z=jYepu&TsA@0mwHFTV<51fOYwv}maH!Jr%9GnJs|m&(ce(c!fh#fo~C-qa5y7?wKl zpl;c)>)CY#Gv;wFRnrbDKSdv)nHxfqs4l0z5Ln29jTe9^kQ6NndIpgPi@rycpmM(q z#zW$WXMKqEP5Mb%;L=P41Tt4lk5^DsPKl2Nm-`TIxnTuslLsBk;F~hKfVtqb+E(;P zCVe93k5b9{Ab4l3j#F$OAWe#sXFOnd<_no<$86<--i$J`0R8y43G{Mi$1%7ec6XTs zuJBy4HHj=wzobyZ&ZzgqKVLiAR*@5a73e>;lU+&tO`h*?O7+% zk0VG(&)jgkM)Thwr6B&0qT{nNMc8BrdA!bxCYc+U-|#>`@QwSltBiIISpYYXckmJ_ zqji-2siu~qt=<4wcUFI2NgZ5R9l|i*C4HlEmzh161v)DdpAX)&+^Q?&kJ@Y}neK39 z8cjShuQa}CVS2?;D@pQ6ifSlhHR3(+MVLgO3rAsH2;8MA8XpKFK&t8fehD1Zl7xPQ zLG|cH=j)(8*Ny_9sonNG*uZnr3jKK29cC)0-yJZ6BM=Pi(`hnkiheu?G;MMEfy?^; zcsg@{#gL4vLLY>NaL^ZWp$L3^R0%B=TMeG#L$9F+#AtP@z`M+#PYNwzU|I|zIuqdU(KTpC53~!T}!z=QpPQLYS7Lj?Yhto%P=kq7MFi^osuP(w@ z%${LD&wwNX*o%xNq^(v3E{|~|@wJZpUQk$f)^@lL4mboS05v*uxVl!+4k#y&_N3QIh(3d}P);TM(@?<1KqfIon{i z9=9oRH$pUpH@)AeA$@d9DNEeK9(guP%RB9_&K?2y@rNGTo@b&kxtv0eK42gxPDM;iIW&Ck-gH_k@&he_zCZf@FQLeZO|nMr+5l#?@8 zj607C^%`grrKgAi*B7XT_4lBuzrHgbFp?fMk$!Y#xh=@|*s-|ss_=5!*x>buvK%}) zdhjnAcc+V`jM>jT-j=C4cZXliyQ#5ha|Mb4UjiHv?zu1!sE&`l1#!pR9{Mtd%vjQE zc%YjB?o_G{9Bigtd;$HeHv~=*Jq`#Bdzd(D5fPyV#M^V~ z7wXUN?%Y_KXhJ;o*fP*-cii+%om|rV%VRFk5{GoN+R^!0^OkxI3M@hzBTM?1PUQN> z2Y_F5Np=|W3HpsjrK&Zq&>r-8jb5}oPlgk|4FOwx3qwTFA0@zW^xU%F>EKlbHKfp! zH#^|qNWBb!J2;01{|Eb&72HTWHmF!Iz^6A-iM+Lg)M_0Vs1Qy|ZOryy`=HhoEDJ^x z=d}W3f^lG7H&+swT=(r-Bc+jTHhDb<0!iH48^qx2?v*BuTd!+rA1+hW^y+nNfJ1+( z?abPia79qxUSnvrS})yIG4o0Y;*hrlb@#(|%CZfWA4rNZU_`qWo6`rujvZi4yjITs(eMw=~OV*R@XR`VYoXCICY|`)u_=`|P5o!aAEs(FASZqsEt&-7|v;3+I7a zJ1xJ4-<_vwldf9@Ml`ofI+J}9QBIZDFi*R7R&jUrUl~>3cCx6z-e4M55T8x_kBz86 zA4(-{AoriQJzRcsp`z`UH-R|D*0R8zptE37;d8XfDN2AlA0<;7ns<`<5)s1#$={v! zt}w;-YcS1~D|Yd(bY+BGB+)9`r6G}J%r<_WOURsYZ=6vhN0fMq<%V8dNKV4!!1LusUSmq(HEF&f7k z(#@S-NWW?`AqG#W&^pHVeLu@N);;MQeHhbTKb^Ar`MmKyAwFH<&S2C2 zP7jA}FtZNyhBS|CdD;|DWi3>gw~U<3=%w>IP3cBnc+6eCCjkU6L^jrEhsW7ZUxdrM zqlNzIxcR4z4d%)f!uNErO@rE&lTakS-}VssPIAGk*&m&}OG>1#Z|XiPF4&zy?&z0x zmn^O~>VDL5uE?mnD3Xn=@UXsW0d13Y2wBvV(6|**;`x18IG$Zkq8H1+06XS(B;`*2 zGlnm7JDUGIQ3xWlVQlWtWceu2J@P_B1c(=50LxV$hGG>m4)T#z-RK1sI!h*n+h4j* zNO)#1G1BgM2d{tGZPH9@H?b;qNpVVDu*m912j2_llqDEwD<{=0*e&|kS z=izMIb#w_NaXTkihX%^Pm;d;?Ao>YMlk^P39RNhd59`6;G>Nuh?#z9#IFgl8-y4E~ zDxqS#5Yi{5>;Ldad5;}l%c%Z%u-Z(raI<4?D3eqg`HPiJs=JHMOBw_2_Yr##aIn)y z@g!ZtqdyKtGb(k+L;E>o+WV=t0Lt|rAv{f_#hHvq^?A96P#_7{olpuz0v2~AkBa4v zIcqn+1AKx~T%fy-T?fB!2B8WDdHuTeKcx%vZ<;c6Kw|d`{#yYwapCEsXj{;ReUE5K zpK~Mo(OI8^VMo`4ad**2(6EjZ*dPZGtO~9mA}~BMA>xo(pGpWvMnAvch63bgO=^k; zJU#v)uv7l7Uv$t9D>UDmyEe8Y=M3GQ*xa<8bu-XjBQQ`wNEwQPzI7@Cl1BV|>F92s z22Xu!11&>?`MQ^z7m-bzH7U*gPa8_%UL?Hrvp7H^J+$t`VZz0zU5+TPo~h{-r%7mEMa zPo~qFWLCnCs7r?`DP;P1SuU5$ux}Nk%v|rQ<&)+qPg4xskr+-o+HNbGe{hF)m)iE;qfJrNcX#YS55CrP z&{%dBeQ!HZmZpp-j?oaKs;A!t`{a%P#Sq;oIO*kgXPULNkk|giuSSo)%cBaODGMFv z{Er5@y?O{mEmJI-NNNRAgQdTEEeh;UGsds*F(*@p|K%%dc03%}#kE{< zO#4D?t8ctIL9`NwDtm0uhzo4;TOV7~?^;qOiDw|M7R|-0vvI9ytSLzb*}c8v z6Y1H!G(5R3@0)StwSi!#-6?KV!1?cf{6#V!`eyx|S3`C}c2nsA zgJkWUmQbS0t*uD4DusbBxNmRY+2cp-{=sJ7|8ZLHCaz1(5p}vTQZmp==M%x2RZij? zhF5oHXcHIL+4y$o-?kP~6JMrvR-#PE=uju}$?S44!Kme64^aOJ+cy}C`*{peyXwsF zgGXBYZFT)ejV44A|9Z# z3qYiQx>)~diCu%wd<34)_ee$}bLe>5*$ZUTur!GRhPmOw7kLWF$)7}o0T6Z+s_V2o zSyU!ovVuPBYP?j_V#o{UbCC~z*Ef3esSm9jn{9v;d+RWue~$KQJP%tvdK|N}{T~^{ ztXw{s`6F+O`({x|51tvBp>je01TV=yw|mk6DN@PfGO52={^^cq+GEFpK8no~b*2@= zL*G>$u}*(8#A(1SY&v~!V~eU1yrmn+oS*V>W|`ln@poUW-?(M18D!osOkBX?>`Qh4 zRQj)5W>z9j-Dx@Zl#9yKM`>iM&Zk;mh9jKqcwEZ3hh4nHBBmP;;{B2=ZDEVn22L_& zl2MX2tle@M;UVX!sx{-~GGECO&(xLoYua0procs-nJL%4ViEKsFj|Rc*S@tMo3?d$ zF;|gRq1FG&aOd8CWMnhHdSG1smwJ487!9E0Wwm$SKe~Z05{KdYKKm3E`@o1#pB0C5 z*OPeG^U1Nn;zU+ZU2O%G<7O%O>CsHG8UeHK6S>XrFQAXA!FfzE$F(VR8)_Io>QO2# z+%K1fZ82J@StScSbP$@d`vm)!HxvWfyhSI5Dp*LpHm%h9H-n%*YfpOCrw3$!}g?af>dbqLtw-AKfnr9pfF18sb!C{{>DWB2@U1j!Bxj z1!2~IA?;bRss6Y4S`I$Vg8Z{bO|8SIsDJrT0)F%d(vKec2~uNz74Cj8B0yH4PMNBt{ZvXo*8{E1C6!kPsDX|B3+>Yu*>)2@1BK7rK)V7gxqcq?_EN!H3 z@j;sTxX~26%TwN%27FA`RtBML@eX&O?jEHI<{#l*b!k&Se_bZ@C<7G)u%W;}ohktK zmWM9a?bl9+s8a)`eYPWWYtY>WPp?Y%*(yvHq~mW_Psx6bGLBnDeDrUSd{jG4^?AQw z;0_|z)7CiK1?bM`K7z~tts2b~oO?p2K$3I%(y|#~0!P=WbRiB=k3sCqSD6VVU8u+` zwx%d`zU8r#R!SXjijcD#J0Q6>xNE!qw6P>mX|$~JM@z-XRs)gE6+pk1v)ds3M;RJO z{QpU&?oZYZh^C{U%i8!JKQzfpWnBX~6s*$_&5`yt#DK1lmi?Mjn_bb^v z7OQ`8T%JpJNVyUD;1D^g7Y{(*PTdd%syI|Yit2&aGjF^{=27oMek*p72aB_BNSwsi zsu7LJE4gg0A*ZKHYO+>V8a}hzYC4q~ij}8Y+T2VnRZQPK>yrKZMJ}Dq92#;vk->{- z80&g4{X1~%HeCkv4_$+n=KDc{>76rT z*N|$1L+o)>&DcTWW>k)nKe@6{8|xs)I>?A(OdsR#-R|#)p0vzV#nMj%X_Ey$=AOvt z-z*$?NJ}J3cdW$dptcG}U>C#5x z{iYA`gN`p0h0n`;gSUs?l*-S0rjDFnr8iF*Au2n(oq3n3j2WLxb zRj6Da_Kr@j&mg#L%04w8}zgqN!zb0D*Alyn8 z;fcFSiG?nyE_1)^9CIWlQH3d6`T+&9!Aw72guiTqD~;60+DUucy4-r(FLM4dL(sbx zGb-06xW)NcDIzD(GwpwPPQE`coxK2P;FQj9%Why?K=~~PVMFM0C02d`NBp6AX7Z@U zt)OEJJ3n4~9U z4YuBc-hod%NhsX{AYnqaNk^_G5iiSD5NLT{np>KaXrE+>DW+c{K8ik;c#c^vsVwc@ zPogDXdvJ`6awW*xScO5`1ias1z?Tb0gpM)CQl#xj!`L_bxj^;C6YF-M@TpNYkiBh6 zBJ%O?{a?x@vW&M_jL?quZP8~uGPy-;x73MO=NBUu<5w>8^*!;-$^0e=x@d|i-MJ*= z3gzuP)$OzrwBzY2AGh#^sZGxPCs3!SmzT&H-cwfeabw+d@`;At&!y>&>na8cC!7O9 z&VX~>5J&g8x338}4h{N=3m^z6i~3bnS467l5t1?F{yvw~?e#Q$)^e{npVLOuueiHG zb8YI|Ccpa^$-MHr%{47PrXRvDBjFSFoAmGWmxq4W21x*GN%9Zd3IS%jd|Hy|tW#qO zX}+7H67(QTSJvV9?O%zoO(s!Oqk#{mjDDixgjX_$HLbb{QB*D}AmJ&(IBIC4zkKeH z1;kF8W;j)Ncx8|pQPA-2B3AP^d$D9J~I5JcbPHO&I^(N2<=I$cK)S(>~{C9)fKIn zO=CgJLH!u9lpXB;D$@%=?Nhx%OFQ>@`@MRfOqP3o_@UI!(chCLSCH>&i=OvU^J{2U zo@%cr2q4+G1_os%rtmud5cZX%=9BFwgp$C#f;VR0189sJ|oqpMJS&-q6b~4kovv3HBUtmqn{jIzm zho-SB9WXC1N|=s&M@;PL?WrwmOrJbsDfUx#wO3?8#&`E- zvi?^c>A`|J+pzD5GzPQ)f?0zZO5FFRHKI|daQX#uNb1TM57G}&Hy7JxKx}93{uRPA zp=1z9L`SNFM6aoh$TiGtn^s^6vL*Oe{I|)Cg4Wrptw&9@FIJJJdRdb6u}V&2Kefn? zeq0Z5QjX1IK>Ay@H)xxJ48hv5BJ6V`GKAE~i(TtWk~8UB{RDh>t3Y%9T$^W=CPz&q z$IvBY%h9YUt1+THyCF)pGSyASNQ-;FD!2$~`RvgDf-i~srb9i|L}wYHSfV>^0hhVP z32-34qCU-9>CCOee1s*~9)(hZtg(&-6Y7 zB-(Mzi_ONX`zpE>crkx8e3oRLnPD*DJ@cw_1;N+lG1Wcb=O@$j6qD!^P#j0Q9>N_Y z1$mNG2>qe-Q`B=LyC}LKpgQm)P++Y7(w(+E)p57S+tl)r$;T>W9=i*}x5s>Z)J6i( z0ZP3zTJOl_NN-P`(DQ%qLHW;r|L1ym{y(wbgY^(8Qb&v*cVr#WfLXUAM>L1^s-gMR zhTLkTg=plJw@oe0u}I+*m!cTao02C;Nr(4W&B0g>t*Gy~{D*l(TFbir0CCPcLwQBs?THKkXKb|Uwd1Pd*Ru#LYgdZyCcFq<&KWA zI>${;KVwssDoUV5zDKgrGXs(D=0BGywQnX2tN5mtwrY>CjkFG&D6JJK;PO2*^D=4h zTGKTqE~EeYhdTyMM4d$X?PG0Ah-{p9rnh-`>ZYpptXS13Ma`L;6|eF`zA+f816E~N?*D)`_ujD;>_EKOMzD&#XK@E zj?|8MrTbpSmZ>Rfl zD(a5xlr@{keiS8J)iW5GA7G-r*$zDj4z!;5d9ww{v#=L@XUM=RGWvYl1GfPJNBzU` zPa-mRRnr+RjI<++ry2q;`>;vThwfE3?`n(%mn>cnbVj5}cgm-Fxrk&i*8iZGwnhof>F?FOdDsmML{X>3f`7p$*tFZgEh0KEEzfEw`t}fC@{!03 zove9}dHDXDfy7Gw4_3zi<8wyp1Y)`EZ-$+E)CQadV1a3 zK}4IMsh?-P8P869VRXQmP(H!aKvqv-fGajddrepx1~lg`d(n@9Dk~-(+B@`evCs)x z6Sx}_BM;C$6jl^NkVxw78E;f9{uo`rzng&W*$%g?%va71lr8DS^F!ieM&Wv>Jhua( zvi#5M6cpRn^rDB3G7oa^Qa9j#1VbV9vu6@mS73B8=M6(p8BzNT@m^NoBY#m;f;UZj zOV`InUb8Mi(ZCk!RxWZwp)LfnNjXV>8E2TKX*S5H%1`54E9}N%>QnP!D432f`jj4+dEc$ghej z#INdtIo(fNblw0tv$4t#-yc3{wnOb%D+8^%*IKttCFYGvbbg#JEtRSi$JE6KR|>xk z=e{*_rPM%M$~dU>^Za8T7}H)NSm0v=r(;qL z>s}`B^0}`xaHBRnM%}!pd?X#EBwkz5UUNNisJ8x{ZGHZJr~mGU48PN(Q;F$8KFf(Q zs+IX%En*GSB-Zf1?B^cKGN+$TR}-IJ7Ij}&{_?Z4Y86C>8i-{Q`QrrrA^>7cLx3*X z!CE(+1!PT`qr;L5gB$^(SWb(<{N9@`7gMq=7Rf2OddI3iOFb@~J+$^~%cSa(p_$x^ ze!ELwjcRK<8t2T=kWkcKFRA<1ocBWjx3O>-@ENyKDW9rZ9Iouh$$5?c9Ce#4*oM%S zbfu_2{j@MHLB*>00{EU!ZVkZZG+O+q7hqH&8C7I1U(_G7>i;oo=?i`?2~*EBJ=M0h zL3bsgJ8pj!al7rREw-mG>WEKTA_Idq={oyjL_Qz?`ep2mOIOlNw{(pjwTBL;e$#)5 z5)r+(C`pMCAt@MNfipZ_WAniJ#95Ac`FK3klk=UHEuF~l}LHwHUj%73ZLO&xl35#8wlnnhXFDY*3mr$v$gDSka zB&7Fbs;{M?_vLw?a!X`5y3nv-;WoL`>uFh1=?>p1L6WA-s|BhlY#v32NeiBX7Qhm) zp>Uvg*GgdXfpnOxpa_KfW0vjzIzk3C%2votd*WwbbxUhUo!e=)_C;#yNU$eQFHZJz zhE!^H)p5#Jm@;Kk=x1v*IER6~k)~R3TSYcmg>0((pB)CPgFG zk9yIND&$VvegTKlKx^i|J!GJ-S7rT{seJ&d=QehKpl~DFKA9z3J{6- zI$x`B{y}(h*ryc@ervpqrR$_yQ`VObDc&bl(cUy$P9(B$R0LgT<)WO_mgPUOq_FKC zh`id96%9wWWseF=Vxp@cd$2jaQT}E)gCI*|>=E=}E&_!YAO+tX3NdHU|NiU{Z35~- zb6NR&8E1FS&SpH_(av>>-3UF*)3amMv(3fJTXWN5Q+CTa6l;-YiabtV*9K|gSrqHS z0P4Me*!{HP7W#NIS#|7WgV6hLP5TiQi?7$qO5)t!khL6KF%C^JChkj)8s53x$7u1G zSsU ztDcs=42lpEl&yq-^qtPp4=cH+tb!Q)0{!_?97iR^!ymXvyeRI=`%P1dr zP2I@`_{cVpC!lh8?Pq~6qlHXamTY=btX*@2Nr@Yt1US1`R7{ZmgAB_HfZzAwH>+615JM&3TMs)u|b~8m~h0gAYKCK zR6(EU5QIXyyOxfV%p%H#rxby1rlsQzX?+0!-6+cLqRpLMTe~F#Pc+)oE6~f+Q$FuM z>!1I(uKz|e0xAi+jR9X7w0#xMt{ZFmwhqa=Bu9I#?niXfTRb~;WYsPPbk%JN+Q$zu z^<*Sbt(!xCdz{rRd~x~KkmCL-d;>*iS)|;k+O(svgnYu(JFGL`pV5#5`bVs7R>Zib zi}ksr*Vg{`3>wIL<$V(VBYn9mRyKi#&DiV^C0~~m*ot^jwg^&R|k}e*{+PD9-y)u=!9gTq%Lx+|x zJt?1Bgc4VRa!JqES&OC0*h|-Jk55yrh*o^QwN`rh0`Gl$kHllXnE-GntQ`XA@{v9; zC~&-VN`gMA+*nzMd_d%1?X7n8*69A$&erJQ6oI*4J{rM^ex(2AUXR|H(}2|4wMMWxIArWPS&a{g8zI2Zn2kCu zsD8b!8d>wcq*FdrpX=^YQSaW!emhY<)|8Cgv6N95L%Na{f3F*BXwpv((n9MEpZ(2H z4SIhJiB(-VH-SSG_5lKFLAD`VnlFDwj(RSbM7f#q(K6y=B+I7h)D1E(;Kd=TOj{}@ znvg5H%BO5x4Y-!!OATlS3@K2h-)$4!lUuE`;Y0{PZTkeylK*Dt2U2vtOIjCQ{jfB8 z2C0l7Q-kALl!ZRjLRAi^bD7*cdn@y+_GGjZnsg@@>oK)I2#8Fa;8Nz(k- zAxgfV+$Fox1ce<Ua5)cBSS!VQd4!X9f7lj>KNtl@+ z*>%ebv$(5YEgZ7%5GT*YDaCzKOl0xr?DGBfi)Y-%jV?A@xmh7Bf3|7zC;6S*LH6~6 zHU5NYcn`c2gT>HK0uMhd6tLB7!@=e|^)qo^eqVFEfsz0N>Ml_}KdPc^Pgc*?{|L0o zg>>?vsv-z<uLkAHirbR;Kq=rx5BlCRc^Gag)0V@n)RY zylHAZOF}a;ZD)N9G;cM2=)iQe^Gl;2*^zE1Lg#ruRzXr`8#M$wtn+f5_4issFZqZL z$)Jm~RqiN2K9WfE$Z$h?RzlJ?&GoturZBmPA8m2jQHM+5JhS7{x_iqg_wt|ig2!$KJnY0o(~Xd|K`q*aC%kf?}=h;c$D zF|DW&(lKoV5+tS+WERtE3oSH61wllJGDQIsW}2okOePYP45-KusZt0fNviMmoTq#5 z!@cJ`pBFwJ_=;V-)?RxJ|N4K~5Y9NVzzzv)>JD$h>oeDma7=K%)w%yT;M%}EL`@iL^aQfE zGPgEMrtWmEhm6KC12qpbv;l5$kWq%|9fs@WPzz~rszpM`gs60h2O|?(Zlf@8q(NQ) zAZoQV%-#1T@^Utn_VKD_7w8t95Yb{d^CAPSS$CjPisbb$mzqF%Xkd3t2fO)*E5|iU z;RA&4)6&;_e)0T?xexrOdt-tsX=XzNPP1;SW>p>f4l!wR%SryB!R|;+pEGGOQy&e8 zhjVrcj=xBV=yfVTf;o7ulsz4&cyLRP=J0*3-){yQBr6d-1EMkPCOqs%L^n&`Fkp=j z;+n``jk+pcx8Id@6WjPJL>lNzZ<$ntrg7-|xtsZXKXG41alLu=gLoN2apSN3P#8Q7XXNJ;ny5)k5Wv zdJ{{Gw}PU$nVvQ%53xL~ede6yCO#y}mGt|sr<63+-+vQBUdaWh_K$$00TVdj46;eB) z>O#X6uDv4&8Ibq1o!WtwQ{{qM%_byPBaD^(8SAj=-DW0Trt<4F9HbTfP8NRw2VwE_ z{}?O&pb0>6$bwb@e1Iz{lM@?-gVMd)LRmlNh`M&fSzBj@Am%q{8cX1V4~d<_wmZ|W z3QW%D_DwX-dRq5A_>qyi@A|vJf6dzU7LZTqK##PhXe>w;KpL9Z5y^q~;Rd-ap4;Hr zYcZsBaFj97UaxhPcCX*nJjxYCeMb?hu=pm`f9m9xUc*WA@YnCb+j zTMS<<(nO&&Mg3*ZFIFKUX>jFJUVvXPt(t2*X2E(GPL|AekkiQ3BxMWmFamNW5k4SI zli-@!Y-u&InUo|j7DNER9s1Ag-rK(LX*93(gF=4`Uw%Y2J}GZh=VN(S7ovczldoK zJJfWfGPfxfqY%tdm?al61d45!Yo`t&(u_afSRGmAT;Ex}L-%lfeO|M~`dXD62QYvx zJ*EDUF{0|zg2rwXelS#C%afA~8Kb}>8+bJiTZ)+%V#@{@r2PYg8UwcxW$uSpNrjBm zpc}6?IGiJXvT)W+ZOkBUG?D<^R96R3^!5S^4HFAwF90_!@RY{d`<|94=tU-kBQ~n6 zehm!~A9LHvz#T^_Qsq;o{>be^0Q)32t0~&d`k~K(=|vBBf|fab`gm(}b~%N%^5gk@QwbNAH2jPM`0GUA1e&&aI)k4Z1g8b7SlvV` z4F%?P6cEHvBzrY8w7*7pEtiw&Ofp~_fH^2gzri`;Y=Yf&CSLaPpfr>>pfP{4qr2LI z_TkFYJpbpJM{X$;4T_Vcc~^&dxyUx5*YG90=iy$rJUu!8e-Yi!Nutc`U;$37c$RtHG|XPdp1^%OZ8lg zUZ$_;VT>qV@k*jX4wku%BT|&w;u2DuM~QoYCI!Ca*T6E}nSO6*aR^YPmtuxP=uJ6= z<$`jtrSVjFO5vb6iWW#^*FaUvHOnc?J68WpsFt{^9wf@_lq1ZAy&ne7rw*R!EsqUu z*g{*d*&?q)uizB{)b0{b0xcP+me>gNe0Bf;!JlL}bjomu;ic&A$gG}99X3Iy%0O>{ zr3;Gi7F5p9Jox+_#DQsJmIsfvl5rjvZ@=l52{JLGr`7!tZm;soCfrh9$h!j?d>-gP zzy1;UK2>8*Ri&fd-$@^vHb7La1gAb=W;A+A zP7B!=BQ+koBub4{y;~6Wx_S^4RQ>9+PB+B=5cJu2Vf>5#_;&Ve2uiG-EPce73wyH&A$B#{a~mmNO0OpSCulLaV|n_6ufJi&@zo3IwVsrRaQ9da95b8 zI(3@_)2J;!zB_p`N>yNrM5JoGA57GFS8&(yF44MQmEOzKhO13$5_)jr+ztOf)ZYJ} zf0Myj`yz!({qyv5-t&xoc8QF>poo-XTe_C@?pw7kW6wEXo7gikr=KiYcdYu5?zd9# z9Sxj1fwAOD;b8Kv z{O{*jCkNaNR}`R|QQ&}v!X<~Z3Cmh&`rf!U=(<~&u=OQVv13=r_y(2{ij#{TuHzk+ z_?EO?V5!DDjy&T`=V8n0wZ)+w_-7=giQ0~u6KK;4Xc^kyFi#bZNwV*yjCVCkEvvik zia`}D{&dQCbH{#U^eU6~D9YntW*OuDFlBA_*cxsoB$IC7U`yS>!ZcuE8DPf`PW=*_ zPiiC^o<&m=V8Mxj^9>*S&U^kyjf3Q<&uoqLloIK%MGImL~H8hkYp~_6N zo*b`qcsveTA^@!IPut&Jaq9MoT#=r_@7uY0uytO8qbZVA73a@o3=a=)v@3{zzwdtH z%HO{WtgbzFV?%H7`w)%ugf<&k;T-{w*uKN_Vy!qvdc~RmM(kjj0Mo$aH4@oviV5jRjy zJz-7W+c&u`JNOjuMsT6#`+5k_*)DpD^l3x~UXpY8+4Iy^LAA791i!i+eSx~vD;gHd zJo7F*h~&A^dg2O#M$x1ZT)N0rmiEK8PqlyB+$`7h&C}jIO#UZFV?K%*8Y7^#P}V+d zsm+AK+4J8pk7{f9Yd&2%c>ht9Tg9COMVP+apd@S9-@Wm;MO;!$8&!bU1R`f!+Dg%c z=IE`IOBeGUQ=!Li80j89;f1`9LZAVa3+|GOqsq1NE#J!vfY1V5=@H!3i~@VMaS(H< znB|Ig$+6NBA18eq2h5!8JXoaL>|G;MtcQ7f8S_ThH`=67N#6X#tb(2*`?J$ zE0}u*);$x)MHQIBf>h_}0cQt468)1Gwj>=c!StKpk`QfW!&+c_C(+v@T1c*&%wJlj zA@q6LqMSyKc`RXtW;eoCJ$yU79%i@G4Nsu#;1@m2jdG#Ec%4sL!>UJUt#|(nm-0+yN%I8hQ@`hbA9r@__bF!SpwzUC_f92)(dn;Pq~SirOM8&^6hnI{lwc z|7eGW77R5ePC!kKoaHQ>=3qZ0%O%45lk*fM$`2zXQwmEL5wJbX?~OsreVU}p$z<P&D3DQ0J4za0?tnQ5sNL>|5+VGKZ|JY{w5(q8T>Sg+X1JS~_;lUrPkfW^yMA39UsP`2oRWPoet$_*G_|^;--fY}$ zrTSo@!%TPgTemg3vsK=Z4hQs5!a*<&5NF-6v{hO|P7%l+l&S+i36?q0LR*L3(fIqC z0|>AE^6`$QIZw`x;VQ*Xm<85@*PrfQ&}Qc5Qb2Ha*ez@cboI2qVej<3KPKO&^AugH z3pEMIa8Q;Pwsb;GUj`} z`m(-Yu$A6scN|2VXl-HOH_&eb#tyeFu8OEy_AhMZc(U|sfV!G5e2Rg8*b{ZzgD#mtS_?(X@f_p1Z) zj=eVaRhd4`Dpc>*VIhafEhS1duyb(rCZ@4OfS#MY5^8cF%F;0l_+@HFgQ)^tyLfBt1A^yRkZ+1}6{Wp`>{9Qzsf`HSF3NLIzEDQV-r0TOq2)W=z`@$;oE zhcrQGupC-MOmb`pd%#PJaqt9OPLL^67ydF8@VwVsc`YRQNurrLZVd?#7AQ+#FoZA02v@Nw*Pj=oNE$)O&Cht36t0w zA{gd1yAut8O-tddh!N<8Qao|5CqFk3doe#^;g+E>iA#FXV4G=n*1gEukc;VsZ@}+4C6bW# zn}lvW{Hr+tqpHBWCq82`wSRhU)+_)_0Jz3nLL+znM1hsKdMa~ns&$9mF;R^_sr_Ps z?bYh5GGWT_Pt~CdtE+-+Uo^Z(l%Kc#Ih(NJv@a1z%}WGVz)c#;HqmU-=CC%hUID!< zK#m7BIXjUh8Gaz}S};SIUc!vyq7l2%tA#@3ar-~++(b95%%LN_x~M+W{|jsV82g8= z)yHQ;+~W=%7iu~rpyS`TstUAVniA_IR&Ainu zpWA9>zlT>>Fx$>DMAjEZ_Ke-?q^`ac`{A!m>*DvY5>`bnPzOC|;Ev?9b5ikDIZe`Z zfi;W3$PhO}%YbIKU8^S%;R`Ny)^_ZKMfsi&FV#NXRwy0M$(;WtQsVzCK^YNPSJ_vM zmhc;RDI{5{KiIyCw(W;pD;a#@XJnVH-5bK*hcv6|y+1^xgQD zPi}%XeAaA;=L>}&9Zz)&b`=4?##%ho6j$Q?%bBxc2d7R=*aqFXRfox=-N1)f{!hns zlVvzSj4ILAIuL&W1OxmfRbdGVZmLofrl3po5B4y3oQkeEHzgT1Bhv~0{^RT6}YPVsA@Ic=aY&ENwxOB*; zM-WL&+9J&xx~zZvwZkY4-*Hm+#q*mp27YqR4U3i|mknmtYM;s9k^Myt8=v@^q61GIh1*lsdroTx3 zCy@dwR4W^7LF^*~rK|9Vq!Id6yuJ+iEP&aq)@$Hb_oFHqXFtyn7BTRxQnQ&%-16Wd z@5e!m+lqC=_&(<}=RAL?g^y2a3CX%yrGrgv;C{W2>VlQ$fX~?uHLVn!=cY=ZNj0Xx zkBC?PH5lZPwxOAoKvhV*gKz`~U30|AGxfdpg5VDr+t3+Q!#GSb1j8z z@EQ}SgLd|8S>)4`%X`BZg-RRS5wGCS`3p}znBUB|BaLV`WkP|w%iI)UzS-&+~-hBcD#T>CC`2L=?2I|WOS3A#@a|hFqVW; zt=FL5>V;8pjbXMkFWYibgp+rQeB@O7KhJ?mGlb3dTMkqSt*ds7hq-hr z@FF{nS%>NzT>`vru$rbZtE}>yGJ2~qL3CRQ3x~3HI9qnHS8G=1_AA8Krj;(+7Pa<} zsdK_aWrItO%S~;?2IsuwYx$E|8j$3GvtK2usQ~W=x(A7gC@7t30+2k#$vl|aa?-ZG zJ~V%?yn1hrqNLqE=j-|Nv3Bg%f^`VDWzeo}U}a7BXv&!_{uh1-bNf%{w@RPYy`9T? zi=ORguYR^K1L?FZ4ZN}E=YZaUJ25{8{PFv)g+P=?g&9+twjiJmxz=9>8KYo*FGIspVY)6qAw6m2WHz}n7xO6NQ{{=d>ZG> zuRY9PaV^)~l9*||dDE@M0l6Qyub_LM#U2BmQv5;>f_gjDe#9f`mLXdcl?NfYZq?S^GHDiBugUm$j!o`LQJnr?J_Z}j~wJY((FYEa8)CD0e?HX zHSAO@&YVH$5Ey!Fr^!%mga5*tp<$Wyv=DOYeMFdAbLzZ=ReHAUL`YNY`jWzl;}>=C zB?2qVfMExU!}dt1jM$pBNf-j#wc}EUH>0tNa<{oxmY-~UHK7lNBgIE;8&Fcspu-z} z|7c)Ff}fg7t*6MICxTk=0!a1`E`zle7Ao!4m#}|7VpQqUC;)+MScM$?Y51M-FmAbK zD>5U#Vk_WvcXgh1sVUa@Tnig&&)N7}u6IsevOwIMkik@ZksD>)Ks+OKm)Ij83v$eSBB?{KF5^#B|yuigTUz- zd2a41k*>ToIypysCu|8?pLsB-`tFpYh)MS7jJCdWVH^S<-OqR{vK5|MjOV$9VK#?@OFZIIs1l`8LC27$`=V(v5S zA5OJcV>aT2Cf{fM%~=_%bq|7t*UABBgDq^=Jf+7H@;uid#8l==51j3AzDr@LX4x~8 z{lFO{Rx21M8O%V1mT&-hRcf9A{_J+N?qr0TXc#nPeu7WSF$YWsBM`Fl4*w%@xm@@qgEcZ`4;exGi0 zN5O~Ru3i>81}*Y12RdA;o7y{0RIU-$on2?0zWAmEDg3882Eo$}tD_R-O*;q040lN=3fal>7865>7;Ea~+i7E-JY zdK+xd7LV~w`#kK6$7)3^>wb@IC6S0%Q4oXGvM=S8cyDFj{Y?Lw{?oOI?HiQg>AHv( zTxX$B(>fxBIJCY)#nM&@!lesPwnES*7*y{c@Wi9D@|+}*l~0ESyX9NWU_-bZZ;qO@ ztjE_f%8tUEOvjnBFlx)3Q*LS+;TMfhv6K>GI#Y4`Nq3YK1-%atVJE14G^;^2mw18I zTkr#)4m3a_d)7fo5@BY|hW-=iP1t>JevA~|ecdAUc4-)+UKVdQ=Tyrc9e&I7$nm=K z^M5)SUafH*pGl}aNweBC0tCMIwOsx}teay*JWD&;CN<=5i3H$HI$#9jwi!M6B+*TD zW~cFn_0d}n9wz23?@o>eAwDhL_WSD)6H0iS%gyoS)ZUMDWyHx|uoHK& z9;xoRUd$YP`rX9Sy^8LHmfE@V$qJ4&^QYI&z&uU-+wZJ!JNK*{Wy|fdrb9vL?d19a zLbUR@(9nRr2*B#w@k>uqfTH}43hcGW?H3EX zD;k12gt-Vd;B;(oqA!c$&3(*ysM&##)1EYh-;?V5hqhj-een=YXv92UijI6#A0O!V zy|It}F?FV2F+16*w!q$6FY0k7WDys#bXffbix5IeO!v_yK#`i0$TUILw@)?K%{-=t ztgn&L&EEx$2Oht4zJ&5`xe5bY!~u?`6K$4f?4 zCtpJ{4zI4GnVild_Evbrmop7vb{f-Pp~qi|vYRm5o)MIW*fq}8mOYxV-UurJKX%Qt z$NB^u|Jg-{8EC0vsl5L3R*`IIId1y?T@vU(vQEJ_pAw}n zC^_FP13C-8qua^P$?&l<>B@03yoWtRK8&WH1D2F#0VXx1iaS#`o-AB0$;7XG$28}2 zT2-ed!@fWB%p`AfdeYWp?>jyCp~L1u8NP*tZawz}c%Ywg5-!QFEJ9GDeL6*V{N*?! z_sj4r5GZh)(>9+ksWm*8BKbmdKa?VhH#+@1yZOscVZ)+duizU1M>=~j57XO*n8=4Y z$Sk(>p-h-$!obPH3emV-Xcj_kv$lo*l2}Z{(H;YVJ2_Rjl#%Ksp1B;}YJL%Sfi8z| z9i`NeiQuiFW8(CvBg9U61_U$c8e@|Dmt0N?W{F0q{zTjGsTV1iti&zCWPm&7CDzze zE&r@W#rx_&H(&}7lZKo)zEAA3n55U7MO}EvXW`yG0UA(kNooM4VYQ+}W9qItp#1{_ z$#w|rGx<(Fl|t69xoj;b==6pjK?TURR&b8*BHj+~b7@;@!p!&d#0U*{$66k~P_j#Y z_v4by&b0~L&-$t};2VL#hbCv}tY8ttbth`6w!(yF&(NsmRWbCmOS3KPCCuGuPFZK{ zsYMME>>+@^)t##!*>Q3nrcD3#&mtmxV;;( z%ZT+WclNmOWZWXFIGyI4*cm~2bX(g8mbyUj!4MCyn`+l`(9%CBc@4SY;Y;SqyYTrg z)nzRQnDDSMf=`JhXLcjdPIPKSpc^S1m1?%a=GU0>Mu_^1O$O&J&HW7KPqfE*?#P|m z-CJ>|Mz_azQ?s|UD+o5j#E1w#5pfzArjQj1xZB;r5u~W4I!OUW`g#0E@ zW8Q%sl7rg}v~4rY3wI%-t@P^_ zgsaAie_Wqttzbkq4gZ9gWj%Pev-Y>(y{f4dmd!OhrBt&j>pz_h1t@^mMbseR)(;>w zT4;=bC7scD^$=xg9QZf9(p{ z%!`REtjEniZ%kl3GeifA(Vyx5tuHWzMry|NpMy{j>_VHpjlFlY{AENN=9t?)ZhYH~ z+Q07A1mz5fpLV9bzY#f-yUIA|;htL?!($omvj(>MT9$75H11_ny6K-?3lj~Xzjzv} zF17zBPAykit_Cgpp9JSFP>@gsw@C}N*6C)(0BHB$lI~7)6qyZ%aVZ`IID7kMQRoE) z-tY~2`qfD5#cW~p`us9Le6M+6-(-J08AAEU+@#(GLmon z{Qyt!-D)fQ_XbQa#K|Kar-bc@;@>xA^=1Fy}T2Tr50NSq3Suf=s) z?21a`Q3}f&_tf$NjA=n@b@n#kH5N9&9HL;nHiyf`&)o{@(VgUIR(xsh03#-epm^7` zYjcggz5ni4-8yu0fDzg;eAZp{b;kbCxV~cR>%^DIKS@J zNDdZ4NXjfUSy+lD?avLJupf+daBr2cUXDzhsj7F4mVEkpB3Q4vovls>6HTeVqO=yIE#g@oHuSJhq^mh0iW1N6)00N+CWSG5AY#cU-{yRXp>w>p*r1Q5=xScTLZ`4+> zwxCrVqPnyMUxFjcg6VD#1E%O$;oz|E=@cztV5_6%kB4npeu7XMNE80X|=q zW)BkpGbH=nya6COy0Cdl;WJr}So$CzlcLM>(j~7yGk3}H zmK2HMUuKrUAuUZg3s2 zSX=sp5!fs_e^+4@qi5~AYdMrNK zLG(54)UD+>z;*-dKqA;b0Lk(Mm4p^tYFy4GK3g5mJki775Sg2uM*!~wkI1E0l^%yr z{i32Q`$G0Eo8XvuT2sw_-;KDvqty24ozZ{quiXrGoIpHBK*PzX9cS{Y+F4i}7AcXo z5U*l}N$t4-?|V<2c*ER>>?lOVirDJgEe6#`zCp`1zKWl3{JzIxN723Q0EdL?BaRc8 zzKQy`?>)#$$HGyR(=4r=ZNw#Gi_oURs7uQ7nfwo_O++h(6lAZ)5B~N?gbMr>&Qedy zG1uBJdNtczbLiXkBUGdR=^XzKe{m6_w!-9chz5|uM^&Y0DdN&@a00LycdCD82KL6} zpH$XtP*(7lqtCEKO#7gR)P0KvuqFP~W=U;ooqfQwI?#cLUPTCB4I0Wt`OsZK3iC1r zSOmyPlb-N5qP&r>-e4nTt{qS&$E+rFYGoKXtW7>aOoVEb!{rL0E@OD=NZpHucn?<@ zly2_k%*&c0yl2F8z=0XXdis>#ouIL};;2jKYl_F_+S!kSqaxBw*dl|PC zpD~O>)8oP2RRTbxi;ADd;LREAeNd`kNrRqhVtdI8>+J)l#F~i9WsJk8Q$DtL9EI<^ z9`AH4kFXL<`>IJSVUD1Zt+AvcC93nU%a!H8RzoqpFa=kKQE9*Zi6f1a6@$ORGaGg( z(0{pgSoF`FJJ$^Q({Oe8+1oVnpGxn;m&?=E=fb-BN>ulVulA9 zkv8F%t`kJs(Dn}P1j8Yiv<;Sc$Mk&b86-y$SB&N=mQ*Z@+cu`^BQ2r&MGz%9t@Y`ABThn)r!93Opp4Kq7~2Ve!l=5PO70;-AiD5I~)buyQ;% z;1vkZ7Foh=@9UDN*!H@b*99FaCbB7rGHWs0ydu~1#nadHhOlVpfrczPcWd^9%Uns) zlLrIJYO?XT1x@%n)bu-R0erFw5vsnT$z_bdT>}l#GAQLgo%z$m#b~AaKy{tC!J!Bp zIf)iwKR)eo-uNJ)d=t)?vlNYWz8SI30W!N8v1}4}kU z^y-*C&m~L%FNst5hd1Y*Eu)-V4D;Fyjts}MHfwN*LqXODFDg7blA20BftaWMkYRbD z_#L1IC-sz1k1Wz@(fUC#rvH~}cV!ci5VV)3P&U8hqdVIo)?*g2?=NwiNhVRn zeHhLV&GAOEvV=*_^`lQ;R#(?217;3qhfSf|0a*73qU?qg{*~7Pp_^tqIBarNTqa95 z80+E~^TBh@ap8$Za4^eDP&GhIROhX#dAUuIU-T<6sljNOh4e~Axl=;@y9Itc@rM^Z z&Q$Gj-v`y;0|4T&RMk0fAVJ0wPEX4QDWD46D3~J?S1+inc{Xe$J=bSb=_AgSMlIF=#;)gIJdemQJ9EQ;o(khWx*`=c92XnJR= zUS?0Vxnhp+R`lY~vHIR@YSs~Z?&={%=Dyl&qYF^@1-gk3xxGWPN_| zb`??UG{?(mQRg4-Gz0}D95}_dd%zxmTw`z(LdVK!7J}Zrf>$LN?qIGZeaIeYGF-Ue zbX=tZ%n!^{PG7r!@Lu*~8*0w`;kDDp6Xaf+!n zq7FsH{}Itm!j?1C0LId%EU7K%YP0A7Fyk^tW=rnnmqJPT5KNELEDs;yv6+zJZ5bg6%utoEH7j#VF;W&)4JZlD`j>Z|oB*>{Agzs<45VJ5 z6|!Cda!2K1q@z{*&&?nTf1~F-G>~$cB?h^m{q~q0af;7^U)x_1Yxg2(SkcX*8*iFc znfT%4g71h#wJob#3Lj&G0kn3sMGcKz#8;<+HaQx-3vDW5*eNbPgt!#U1Bq)TT2J6c zglYWE{f;w>Iqfc(IiJ_#)EV7!MI5fMYjzmzR?WP;7+eD5QDZ``fr+xx*&q!&O^LhXjmHj%5_hLge$ z+WNI~nBL49+_=r5{z+%5z7hnZRRhy6555{D4OG9|EwKc2e_)=gw&5AS>V>bv=gH5? z_qX)(C?mI4$!{v|SXqHACXcIGzVXy|I);yI$&`wSUhpV|tHKbxvJf^F?l*gs{S?gN z;j?B)X`YMk$ZvhwTl=DClE1nlix$5+=eKH$tjWbIG7sg3(x7t^cHiW5SFoeW!Tz-ck>uluVU?TDLq^=r%B|ySfPKyo|Gf!>j4PXE=q?15R$Yun&uJO5t(Y-i|vLEo*u zzP@IWq`6r#{7?Ho!RyxR%jW?W1H~y*6drtshf5|6k zR@?ojV-Et|CKtgJUOFclcKM&prVijUMmA6OE8;XJC5QY%&j{*ZywWZrn)&-4UXIyC z&~10#k@aMD?ZW=h;OJfMTt(ARP#1 z#%cZ_qITxmbWZgY#_`k*oc#v$1ARxYfD?8A7GQr)MlPuOzp-%rf0JJ=_yQ(V^Qnjm zkJ_@8^bz`Cl7ieSy(YB;H;|!p#OIpk6wGPM?MTubHM|YCg!VH7kflvi)4O@RxwEq^ z?zoj57mD8d#Qs)$B;^J*@(ih)j5>V<*w-pG6>Emft09Tyi1`gqXXbC{TJ^3d$+>a` zCoQ*aHcfL#YM@B~oumnDeyDJHRG^qQ_lG@qOltJY=C($j+40;nE{EFT=3B1QgCo#l zG(kZ~)R19yo5*OKzXA~_&j33@Iet0T#~;e4OvYRJ+4RjuV9bY-6j92eP z7pMZzP~;omlCif%PHxpkw?GCOSGk|1mflES7W7lq=gpVHXCRpdE4t#G@^TvA0bOwP`HepJ%sbDQpYVxaarj3a zOa;hgB=&yV3Sv08IXJ4jeHGJP)#y8zB$u{Y9g0-iQ0C$?oTm&Zv({UBPMNGb(qWgP z332A)-tF22jEkEdk%Wp#tf2V|-11Fb8gd<#)e&+39 z5{;Z8h{!)0m>@J|b&@R_{O*NNZtWeKo=PE_505CwNficNZx2NH2`t|+gDbCk)Ss>4 z^{wUY%XRX_3nAGwNwN(y1=}_yizUWmNN= zkx5=T7!`n8!ghnjaVk}zUm7!U2H9JN===76=2pwyg?348MZ7I9 zi$q=8$iNRJ8Y6cAvl+Df4zn?4n!T*@!y64TO?o`nG9+)u#!60xqN5|L#gJJ;a7onR zG>4A`>YXAcC0fpVQO{QX)>Ih!r1h6$Wi`DKTF z7C4+ypqlT6hxdZwh9h}W3XTO6KKSyi ze23YTOscekXZI%sqIcm1ts%-{K?3c5*9FSYU;-I$+;p}GZ-Fk4imS~H`&_s_^+D9! z;T5(Nu~|Ya$;_uP%f1 znC|e&7x9X_Pf^`Y%ENGap2|A?Sv6-J9$um=^3fLKw_+mxVKAn zu?>ubHfTQ0iHK+JKo;MZL#AUclBss-{K(vY%`8MYfONAuq~PY|gyP|%Bafa})fEiy z?EU*666(_b{dp3v@6YqloIt;Qf8I*Z4UE(_$g12~h-dBw&r`5X$3+tJcB$2;TqpB6 zQK@sA^52kiH^a{s53U14$W=9o!#_T2e@t8j$sUqx*&S2p27<;SA5b-v1GF>_-6j)c zvDPv7qXCAuUBh9q1O?85do`NSxY@WqRO|B!zh+@OEaT8^DPk;(&vPMk}UL1qL(hbg0^} z1b!fHdkTaWE}tq2dOML@d;4CuA}qYkdK2P@#vROxDITCDhM_GXX<%M~DDR3G*zDA< zGxsV3GoeGW4?@)5TseX)Q0xVzqJ@{hg+l2-bY=hPTyYMH5ur(lc@yS@W+_OH@#{GI z>JTR14B=e$v_SO(ko9*d@v$@+C<+|EBtvc3Vpy7mnWDMWP>S-5D3-Y71S7AFXaum_Uq)NpScN5B@p8+s zqb-tam7YUFBT9L+apOF#BJiteAs9qrZP0*@SJG7vkWnnm1uOy5J+NyNJEd;4-N9xA z$9B$31J~AS5G3Fdi4kl(3W&~oT>U zLVHwONTd<*Q3BW>T8<5YOPZ6alGZ?34Sw)YBgY7e0KhmA9}keuKCPj2GX@t_%e<{Edqs*qhuJcISA3+)5?qZv}^%!a^ z!$dXfL{RuwZf>YI<8R%$*~8>NLCOT??^Sm}CX0)bR(EJtB*;?o)GL|Za7roS6M!bk zUWT`vbb<|U%BE8AxN<>FIMQBr7SRt7Ilpllbke8!1oK~8!*4EUi;gbzRmI0aE_l!( z9U~8c8V&k-gpkC6z1gVUjS}^)PXz`kS^{RP@d45vIE(R|y4W`D=dAna_(&tRQ0%el zA=44=)+$1VBX0qXhpczQrIyYWWksI?d*4x1jrg~*rWze4C}$@E+)dNp(r3=e>^A(6 zAEw8FyL?aw)sVYYL}(pKK%$%LYzw>NEHi`o>wwWqvj7IjD)4X(uc4-IsCDk_`>hAS zQA|A`P;U$E)SiZ#?7r987J)0U9ACAJTuo@sS`Y{?t_NwZ@jKL9!D|LQAs5|5FW{+m z(Z!3ozgLC<+4X;LMvUlSz_9B~HelS0d1*D{w|#xeSD(IEKLeiw)jDV(W(cNx5`~&w z;OB0Q3A#t1%QBC&-i|h@_je&ayI_2#M>w>N;VuW!fZ?RZ!12Dqirg%sESkDbILGeD z?u*O~5;=8>Lj|3T*|E=RXZ=V5xNuys}{Vc8;a&BPSU4l#VFqPi25 z2GL~reAeS}u{6A2sod<~DTd-M5C@;oXisEcJ<6zzQ+u+Yf9){C%UUi`c8^mX*fuqf zxn28|cm|I~5R#vs_%1 zK##QrYJQIL8W%uJ62nFJHP9400-NVJs;R3r{`5A_z3fx83kljh)r2|d z2fRpb^!!D8i@6qDfR!LTOiIG+(aaHVsq#0lfCJw!0_*i}j7`$E0@v3B);vPeXXdt% z8g#dlRKJV+HZ=58S((c zY?Fr%49QqGVQM31g@D3Y02(MpPJVQ2ZtGC@HW??C8S#X1ar{dtsDz(n1S;o>u+^2B zAq)?9)A|-?@f+G?z9JH03C`n&AXPjtf_O~mX|FAYC>+zbjBvR)#c^|WUTE0*&RDuB z+R=8Y@@;LZjJqh37Ja5poDuu5@@4j`SD{0e_6hb@UIDLT4cvYDYYcV|ESUJe*n9J+ zrn0YX5X%7-F(L>GLR1h`hLj4T2#G@>AYxQh1ca1@$PgiA0TLnPs>m#))DlW4h>8r# z5E(+0ArZn*NGUQ42$@QeA#krsxRT^nAAh~NzjyW5YjwZh>aSP7tABss0+(J+?%j8v zeV+Y1&rY>OExH8azbU9SPcD~|*M^E*jtdGhqvi>$pHcv+tiZ$oUE3YGUHT;UY>n@4XA$mc2>N7i)H$4b=EYzZ2%9DVw!8BkF z{G+j)xbCA!3%DFpXVUpN6j#95#6w~j{-_Kgy?{(?XA(^`;jr1UREA#(o$Su-RavM7 z(oRGAHQQJtzm=V!CmpGzwo5DE*2)FyXU8dxUO?tuPlA6LNR3>he!$cR!C)FGSiyY# z-Rqtf!>GIxr6T|OXnmIe#gy4yJ2 zcXUlMvVtY$Xw&1Hy4-`JN>QUTy^hBtYstfQlX#uc;WMF%nBAIRK!5(fph5h<*Sz4{ zbE9-pfDH$>)E$%wi+@W~ME2VfGP22FESunJH{)PW5Mq)K1O5Gae*&Dv%4!-o_``Ev z6;c#`q2i8ks4XD@%i8%W&SWIoo4if`H-K0k`0beH7^GTVrC0#aCzY-#z(<^w8$OP1 zt34B2Rjz9*rZ04pTRVoi^p3kcz#_!@ux0a^o=H(Zn7g{#=(E z3s$~huW(#2Dar4tx?}yKZ$irycK)MrX5H(bIbtblwt|`X7Ab$sdaBaSh zYDbsuJ~$}8+U}3R@k~XQUNKU$0aXl#=^MNi z$mXzZc(8Xifj!wofDn}SIoMrF_bCu9pTVZL7+@~fm(f|@JdcRCt2ehd(kwuIYfu? zBhTRU=q^ONv_f?lE_jruhvo!Y;@8n_=G?Mh&X4&Zk>=yoxtFbX9#u2JlK+4jxA7MM z4;az~wUL-3HxGJKe@U_l+ki$ONiXy(4oY!}OhPo} zII--qmrsvXq}V|eIf$V=5lp4U zGnX^FM%Sykpq2wBLj$S*XoS;-T!eXG6YRfJFnMee50{q_H;&h4DKZjdINeEqLSGce z2%H&UdGD^6y!e0;`^0)uNQrN0ZDvB5kB}OsIa}bY@iS!&(-0-gNKL>s8?RY^1q_T4 z(^9eTkR%~x-CLg#w}6N^3E;E<(=tJFseI;~diK$r1F_`F3a{hLPopm00bhUhf|Kl3 z>vEJoFm|B%oKSpK5b&!b!@wuY)3b-cMqM2>Wx2RE`#i%MK}~PgK_s{6YCW|Ojcpk_ z^6ch~^9^rG?VkUb@g;#_@ z$9)p-!7^D^qRK9WbI>H`ai{hyh%ZN~#HhjZXdObj{&(+f+41f#e>!>>gc6N=qqLNA z(#afo?cMTxlmzEv+7sUniA22+|Mlo`F$cA70Zb+KHEi9LNDnPCOljc^g;E78=x%{I zQ(Y*namQeA?a@5sz@(XpaV1ioQy9|SZnn#(*!6Y@>G?c})779x5qHrBqXBe??PTm= z%jTL%$&?yis|UF$yT3#kuJY?HLQWSI`qN`u6+|=8C$W%JAP3z-fLEq-%^f#HZy;}P zzayo1A|>E)hZJDuC7zOYC<0vfj;O+-84vtHrbuv|Vh?%^iQk7xMVefa(JxXN&PN286VO_C|H4hKnjjb&*32G)V_w&~M=)^rYk6j_xwOfqE~e@;Y@@Y;>&o z^M|%$-a|DX-1pc-;XW=;)lh+U!gX^H5H?&kY6eZEOId?SXVH#A#xHeKv=p z$LqRH{Q_DTP0GcyiAT?kpSFA z+P%(kPa%+=NJaAfVhK$wKeR_a>+23B4O#irBo&klhZ^3XN+t=0#GJ^quoc<4PnKqP zf=I`f8G+|EgmnNXJ&ut#)@8CA$*WSH#0E~cKdHHk&{_>Q(IcAJxC_YQ+e4Gll$EL_ z^MaJkJj7`#+ORBPj0Nb}#9kjMnD1cKKM`3dFx(hkNnxW35553qh?#82TS-M>s z-rV=^+Mrt-f;WGfK}`iJ-7HV=(9SShh{o#MTqtc9$&gb`!J~ki@d=fAz=3VotSu;p z6BU$}!nXS(^sJT$%Mj#6@m$yw>HCqI;RD55Si$jqChL^P7$(ou;Fexs4Vj7;=QZFG z%CS9+6(|pm8EBq!U}SRzNj5}tx@NP<@_7Ge-4E-YW}QfK&GPj-;78m$xs|Ybr0n+e z;3Hj&_uuRdu%%-l{2SL}I-va%Sk8#+<`d5{0#Md{blRBWhDJog6L_5P0ea4nq|V)3 zFm_WFXkK95GxJ<|p!26+HxEYObI=K9uvkD zy*+EqzY}bcx|CEgzeR(y4V8l=EW-1bF@8kXNXNsiRn|z3B#A}TrzcMQwY8zTs~(cX zc{bIi%Ts=OK$w!-d54!idY5Qf{NR?~x`Vh=pj^1iLN`ULj7|t(H^ER*3x$hI7lhDO z0#Bb;Gfjk5s-5s5R*`CBFNXIJkC0n{xHXYB%hGp#Jfd<%BE6z_d^FUaOFYq|++m@_ zClmhm{qx25oodf6XAP!1)Cj0L;&ee}v}z4cQj;zbr7H}x;)z!Dl|84DB&0UZB4IFp zH|*ADdO7D%uRQ#+H+|s6fMGJPkHEl}x_@W=_YS{c2VN3qhQ=u;Ws9`I-KoLDXDL_b z*MJ`RU>c^iznD%?M5an7diKF1>=x1xMe^HFc+wTq{kE5hn(%e>4*2`#%4k+x|8qrs81Cn;j9=9jZu_HImU4{)1SgDTQR%7@(KTwx8cE5bW!8+KhSUAM9u26n`}d(bXS2XOnM8@#~>H&2CH?O1VY-*!8R%9MS6rR?;i4DM(Wkt zF_U%)y>l3aqTo*=7hWxFahLs2Z>l-U%~B^* zY&8x?9l{L}+ts{>6WK09Do%A=mhy0GETQ^}$wgxHov|xH9g}7-NdY7(QxN%0w~Y`7~i;3 zAigQ8hEzJAK(V7a0EMOF6!GdjriBYGS&X_EG>&+_=oHVS_P;7R1Bgm{%0>Z0m~pwJ zoF4k-BXe7~Y0pEJk3)1KEnB*^+Rm4Knr_uNFCC(58spt3c)2}s4)@>_HbhAVZflrXN1;5T3|QFs{!iXFwBzKL(*STQDFV{ zwzPldg~|8O0(w9T=~>={E3g5RuBlcY*|oiWUFzENTeY?&qO<%Rme z>+D3jXkA-Y_8}eu$uw{N&AZcz*O^tDG)`5+{WXWPqZ8{b^W%z+Lo4a-?yG@Vb0{XE zMUGXJ_=`e$P%+r)Bta?zEo^C;7>8QXgoMP?9E3O{=Cq2g@t9W6&=6UVlz=SgYS+!A+z!Q@;`j7LHOH>2vu zY~r25#XttP)89QJ_@X1&;>RmNoLYcs$epOIptaUZZv6!7W5b6@!#hu?e`N%g)2qCZ zizbWur&S@6y@Vt~Rx&Lc$aZQ_FT_`}m#q(cNk7}zh@ZkoOC)ipE`Ir}pE+v&tmpLJ zGqx~2Ic0d^SBS*dCG<=|USfw)4nmbqQE_~}6PE3Z?iHQdn(jH*>3ni-P?i zpz+dz57$6RuyoY*j=yi&SM-wXo{nQmz7Bc4`GC>`q~Y80AWWME6Pjq~7S>dsGK|?o zF=A{~+M+nc<>x4zE0w3>O;Q|XKMQyA^_L>hcf=0+jbUl%B7ChV<S}ANs{P#ccMwLwghtIU+54tVNj{DlNx#_P%KjOQM^%k6-Z|(4` ztnZ1k-0OGnXs>m+6>W-8CatUFSJtJ7Ypd$Hxn|ct^nX}pmM9$!9^LvdI^&1)7d%x| z=35dRa+$5!2E0_0uw^dFGiaJ(Eqcei%03xX&`E_9{c%hiE_@yv0YMN!NJzosxKv=D zyBGdD%f%ygppo@}E;!P2UwP3X@O@(X%6pPY^3-b(lzP(waX+X6#^f{(N_7RD*!gA~Xo%F}5_I9&yq#7r2l&g67zAYs++j*pr z1D7kM_9`>b3#j1KFOFT@Qm+7jHBI5A@KSB8aL-4uf-^O|){5Wt_m!$EU?cl*#XpkoFVDM}6P=s=e>r;ZzZ==d z`3#uL*RPF(^s)#X-QszIK+4d8Q74Ng(Mmz6NJnLam~$TQ__NlzAgeXZ^rZSO(?&G0 zTJw2kzuGR=iQMM!=uJWCzRA;{|6&I|uLWG72Vu}^0UVPxB_}>s?#9|gHRL26E0r5; zDci+^OHTKeJ-ExsaYi{08>NDkey_)Aj(9McCzYu0;O+GJAZ@WIH7LQvJq;EAwy+kg z=nY6nj~UC9y39^|Wn$8$pBej=M^qO9b*d$UeMRRvA11w!JG6fCu$eo2#-gsix>t2Y z#`$hS-`q3|SirlQW(MfhbgE53K<L^(S(2l;iLwn;`UAkX z6I{)kL8?v?j&4P1VirIF6lC-PV%YQ&({>T=IOE5#t5H`IcZ3XVdZ`X83(6BV8$!az zrN)2Prg>L!p9&H`ZFXM-#HiaefE^7EwJ!??iQ=kVVIXCCZ!WMq2LZLn@j#-K>xIt z;eZflFb@71W5n^#|LtW*`6R@Ai&Gx|F!r$dkS4dt0I?Ro}PO6o9N^LAzF7#-h~6Y}fY9JCO`-1nqFiWcJt5+qLoA9^X8U%*QT#eHn6 z1G$NuWU?Ju?(+EE$Dz5K)wwQUM>KRi9(F0N$j!|E`+=!hj^_9;)Eb8;p*q2}Z7E-d z>!PX=U8hb(5V#Z`J;+ggL-3f`5crW{Q8eMxmPWK-b^wnxBZ-H>A=khAxB@*YMZ4aa zDjC5Ybvxnu>O)m2H}l?-kAF939X_yU0r?Yn0!v)6H~xk!$&9>8X&O3T#?O(-P1{CS zp=!yD*2tr$6SbTqZrBo$gu{wjyNS9~(IWAq2$|ni^h%%gngov_&Plh5UM&9mvj#-B z!-7aP(bpM!K#+h!Fus`o9oiu#+=1I9q*i>2;5*DL)I=N3W1lOc#~wtZm9CS~>}A+4 zbVsLf=*a09KVHI26csY2I+&*~`2&sZQn(Xqo3@f zG`H+St9yq0UN`gMK@8ms&bJiRi(`=t=r&(#@FRS{N=6K1OlK*RyK*bq?T$-}xp z6eP1`CyQ3v_M-x2=m_HsJeP_&h(fZXlk4N)A(fM;aS*1c%PF`u zS1R{(>n^Ii)8^3dI{%zhPCoG_F}*POD!u>@U!3{Y%{i#P3&xiwu(?G|(CT2kG3eI! z3RZsM=%Q`1`j{&$Os8Q7ILAms;DAQ`=q|OOX^3{6;T-r4S6c7lFg(fJh-4b?Js)F1jiJy5b_x(%fPT^H9z};bkH2Ey9FUerz)i0~8k%=JG2$F0^>BYdw1s)i zR~V+}6uWIM{><cp9 zjq3#AlbJAdm2m|*O~IiCUIR3nJ3_t&iWIv9hKWa_g;~6F8}Yygm4IuP$O=P=XD{cU zt%Gf9+6#s!FsoY9NHj3&xu^nZ_Iey+0f1FQpdn8$#3mjt68i8H?oZSfB{1+DbohAu zDa1+A6RCB@R)8DwBU6_YJ>k~e(Jv~t%e%AWLEJf7Y+RV4`1h+2TcBFyj;%C9!3)2r z2-PgZgzOr^6d3gv>+-aVXoU(}`q``a>pvPgkEML^Be%E}2+M-mx_lFqcLu&(v8FXG z`cdie&{{y*D=EwitA5Vd-A2v`A(gO`DO@krsxeG!1a`m=+WV+Wi0jcgcZ8hgs4G>Q zN?JP8W|)f^;jC7oQJxd|D66p+*Zc}E%-hy$w=)PTMybAcnhgD)Ztji>i$1~=ke#MY zzu<7pSD?IcVW9-h)VNqQ^x04SJ7Y28wGYi0-PSE_qw#T4DG7g9kd!(ZZgrHN!%{mRKQzLqQ#=+`?>U8woB7 z-hP%vw@@_w8H?cD&gM9pgwi61guu5$!F?1G(^8+M7+s0f8&cu{@Q9;cHVkVwG3kj) zsi(wAolbNM-KuLnD93KR(||4k(alsY?5i+Qvw;ogU-+v2v@$Rc9w*$w{aC*l%*JTR z+XQ?P&pVQ(aY3yP1NX|&{Nuf|drWH)T(X_-$I@kzX@Oc z0;C-EUCIzZEWAxye_Nm)#s*21ylYUHXfQ`QF!dO*Z^5rp8N*5$F{ucpwi?bfl|>&0 z9Y3#q)yT?p&V=F7yQPy%JaRNEEKuqinFx!LAJo0!BmihEDlCAUHdQs^983G^rNlG# zf+z?0QoZOqm4|XSl~O})5-beO+l>aaS~3N zU=47wzz-^4iU#r?A41)o&XWx#SahAEtX3Dgg4V z4f6j6@QZZo-IN3{;&_y_7!4um}9gR_puYqejo(oM=^ItKDC1Gu}qx zav#}!n+mjK*FPG6vO&cHs7Do9isL{#L6K|i(^TO%18pUw(xqU4_FDIkh6hyD2T=u; zJXJP$2}J*BoHq~y+`mM$asY7#_oyBEc9}&WJA%uUf4j`oEM;hBs_Y*PbAi$xG#3`5 zB(SN@39A=iOws>|y^7zM%6JYjYGYS}=~ABm8vekb$9g{}uXrT^GTFp|eb-0NyR%a9 zfI3#@DT&J{`{m7ogonVidp z6N(F@n<5xc%BE;wloYse*N{!u$(sW=Pxk;(%MbELGE`N#ZB!Ux#hJ5;c$ z+THlHbYZdY$GqLJer`#KPyel;D`C{fp_xpBJ{w6x9sD87kU2ruYV+4YABdqPpb@>K zAwbIhPPHGdxhA76p=Wu77#&446BwQaK2#>Yzw}6#(05J$#J>>2OI}QO( z!OKN0|NmOvF*}ZtL~(pQesE`@UZVVaF6}~*$ZG- z*5I8Nlxh%1T1wYJ#t}FpUB=dAtWoEK?li>8@;5@XbNLS*f}%_d6-wi@US#LW6JEWq zj|Q(Ro-Sy@Hb1>F99oe}cB!P6?Q-Az{5co928lbsM|LQ>4(vqO>-aVkHN5`Eh)|J- ztj{x{7W){5xd`#vs&x{=^1`CRifH$e)5!VJ@4GM59oLPkbE&Het~g&s{+G3n0(uG9 zZ6_$oKXDkBcRhHadTohnguf1Wq>D5m@ocPMxyr6b0z6b__(at{;0Bnx%(Tbqd_9`4 zG@vK7<=K-f-xf4H;|_owE0=cSdCjN#!c62|yz8R7*eyGNb$_lCsQYP{=Bx&kzeex) zzQzCk`u9EfuMh)FD%fOyBn6~HimxE^g#!S*QgnUlOO7a+PeNl&9>cZTfnSuq?yQtN z4P;3=qV-~)rYJ8y(Xl@aqQDL8#DamMyiwzAJ>Ji2pXcrU;hDvkhyN?AE*|8d8}V{* z(pdCW{g}9$*(2gZb zgd)eo2W7cH#JZVQOm7qNHNikff-Xuu)5YH~I`gKFr|7fA(FiU>&6>ldqM%7=numnP_4*Z;u}p&U`4uQ zCTaeZD21^TdNQ7UJnd1^7^4&zE5Rq?JIy zr1g&my46CZLy*?6;XAU^CDJ%rn~5oEp0}}wXyi9e{2n0>-D#=KY9g(k_?4Z+%QHYK z`s{S2>;#IA?g98WeV$JlH}7#t@cdogjV~hk6L7;mIltZO9U?@+7mHPwHmcV?)^9H-s@NmQwO8t|0? z;D!!*{q;ceC;{Q5ZOYzDSVNyVUe(1L+?D?@#3#ag!mzLeY_H^#fLaJ>{hIrrMqAQF z9J-bDf;X**zhhVUUz zeG|G+K~4GThmZ?r-aLRtNpJ6fsuo+pY$a6^8fTJyA{|^q){b*Tc}O)WKx?}#;#zU{x)*i|HeM#SxH6d0&vO3$4AGjpJ_q53wL`yxZS)s!-L z7<8zQg5C0tK%79d1MB{c9xEx5Z&u>b2;qz}P-XMH$8lZRcvj3bOMD9nk~p<|nbZG% zgkE&E_e<{Twif&fyPW59?(7LB+IYwh@VV*yP|7MEBj9-%g`3Kr-_diV%k z`?$6PVSlHNwej^>Er+_5?)~q(!$_gQ!1A^q^#ivB+?sD6AQf80v;f5KW%*Lp641@8 za!GS^v8g!su%nb=mF0#kk9%n8g!e@VO_a2GP$tgO7BKc>?Pty*b|N87*Z zKe8k)`T*!iaZ*VJjEF~V#T_Bh`Do1CG1~HXXwsply>_u_?jt5W$CH$w9$qDaxB2=- zsF8@Rf*N;#CRY+v2{m>!9Q5yDSkdYa%4jCk)9~P=Ko{6gvZg-M1#K~93DpDoL&tiH zYN~sxomLu^yoYBJs&h5>#d)5|IdbPTA3F+$--6Eh47#l?i6kyD6shz?pg+qAnFCf% zx1J2s`-X&43t{I1bW%cK;a}8%A(`BUzM__#5zEFrTiBje8X&0KrM;`hjOE{7zlTG= z7;?JbYlt-$1~R9ui4IuwWlV&JJt5R|pp`TTh;;#n#8;| zZ&BP5^j&Qp@s=*PUS_!Tn=7H8Z=T+ zz+)_h#2JR9RABTp7%h%&!Vh_hHy~cgT#i;-F+E?xNym)$#}#)5g10J_u?mP04~oZH za7%~K)H9>g6foJdHn3`9^+av%K+@{@@hD677faAiOuYu8qzJ4Y)WVH{cA~|NAQJOY zk$7e>R&fx{O1)Sg7_Hojbw>NIrs<=@$1+CuSl@pRX-@dt!-YADErEHRM-dWm>tMJH zs4Oo^1Am2m->?0l-`OPT^(Z zwysi!`{nea;8E+R^>9!kt)66Fw~H-N)=}hjPA(^V3bS8I7!lagAQ&`H%z#}?k?>oy zSzNeFzm_!>D24{-n)%FoP1Ee$jb}&@O<>!rI*$KSaAP_SKf(ij(@F~8c`$5{?#P|t(fK>WtiC5AW!JvC9 z9`HA)pd0S*zypfy8P9&3NMN0%k4$HY)7lxjMd)o=OaH4U{?wTB5k8%dHuwL*TjFuR z=zQX3);#EYTz>){=l9H|P@@+qQ0}FOMjN6oNt~kH`wUv~OEK%<`BQ}@QgT8biCaiaDE~VB!dSVk57K;6&;aO`P5pBu)bP22yG#{>m8E&LXFi=ic(W~YHiW~u+ON4@0 zHwEwj1-?q>kU9*XytvJbn&RcUT1C( zf)ugOSLbWkfs4x zkw6{;EChfLEYnn($AbP7pedyU-BPEGUBM&U2Nk$4ssFlcD!BB&zy5s>{v8AVj)8y2 zz`tYQ-!brCC0Oz7!7Pn^~HCIXmuByRoI zpTm7m`);n<*X{Saj&asB2N0T10Q2CsHerZCBL~s>!} zI(C~KKUDh3;mK_c>n=*>KN@QVVt`q>L09Jn9K4zV1yysZProc+uTQ8U>3TgBgiy}( zgvT~1kWmK$w$#(8K1><0F?i(G zO{j`2zqW1lqsLpVC{@a~O{T?FFYy z3cAH*s=;`q)ww)Z)|paEYVtTeO|G`CYz%s&@}@1C@X9*za^lD88gAwXsqw(~&YLT% z?=PG+N&Vnx3R*UdeO(U`{8g2ogk?mJxpMxxacq+Vc30|Q7$RBPE!2OU?>G9k-OAl8 z%q(P2(bxh2(f?PzIJRJfMULbfp#h$2rsy+wGu&XWCR$>iZnZsVxpy|> zbjF$baQ|JMz2`Dyr~0PD!m>Yo`l+V&GFz)1dOX^$oxu)^h<5y*1owcmH4(fk?Y1N4 zpUOc=6zN?Ah#$SdH7V*xoG5orDq!);Hz|BxevQdWWa1h7dzspFlj(PE z9|}sbMJV7PEhWT%;H5V?wCzeU_uq2<*ZPV>lQ5HmbjmIWV&*gJkR)jyulUbfZJRL; zqS;b=cj{L2gH7qv9J?z;=TkIX`yOpe{jPXns>VMWF+P?m16wFVWTe`AMg0KabvM~- zBApJtq!Yn14*S@?8J@;FK z9btn5qskbZ#}@yj{=+fo(I{{$c#w|`h_rsX>v zx*ad&PnPLzU2|8IT<#bq>DYCp`~sYy`t15jt%Mq_AaR}G>5OX4g%@|al5nb#%oS#lU4KFF7{ydKd45Srr+x03ti|M}vtM)${?+pAluyz@G zEOgh;t_zgz|F#wQ|A(!D|2c#uqcjl)$0P&Ez*spFy({M2V6Uj(987aOC<9JNX-8l{ zmVW#Chws~qCwThsD=2P@e(i;<(4bzf@A$^2Jz+L8L5a&q8^GvgkZJtzg8-jg2TzOK z_`E9McjHB$amiCkfCP|`8VP$%_AQ*Kr>ad)3} zcqFvKryw^erQSnA`P8hg2QFRg8j4CUOL~&MnncCj^chRa-)32dt|b zOd7)?%gkTyzG&NF>Aq#-b&|!y|9!J3MFWuTo(R5VpwhF)mT}Cli5qZjWy<}`*A6E$ z6`z@%Y@;Y+7YdkfW{052|k-&~vd7~BQF=YSPb z2=tY@fElXX0Q7WBefxAMh+_a$pt{2Bs6PM&o4EjeNp&JgIYOh3cs-nxB1^9mm%=cL zh~3oqkPZMGa-3wuwcR*js@6W`Tpy66La83HV^c!qQAU_LgTIGC0`6Bq?DZ31I*ld6 zuKz*5BdL76a{KiO!mJNFNwvQS$SY%;_?vo0`?_9v=I_NkNfMZAMkN5jPP&#WW;awkE(ILIo!-! zz3V|S!yf7O4z=g&^wO@?SJ(D%MOqU(M<@J^>Us&#kH!BT8UH_NG5UY+|JNHSvekwB zrOYF3d4WFvXxNZYE1YV5qw*}&_`@tqkFf>Cm4Ja6-fEVGeasP}2Hg6z zHoAExb$+kyQ@goVno)3s7N;Uqks+Otuo9wnt8^A~J3{g{JV&M!mxsnRu)hS~Y~&*i zrU=Rfgk)^3YHhC-bdfYfJ!+60ftpG<$z@)WpsUfAw z3+bWRmcv^kAszPOhQA#CfW{7jr?d*dhz-~X8}{Vrzbe6cNCvaZ7>mOg1I#t3u9#Fq zKPb+MKNa{nEF(vp6RUhs9@<}a%OK4Wv|vkGiM~0WQry!vS_tlg%U4zHe3c(XQfmmHPt=LwG}2e(^hoEi$W`vMyunl=_9v39-!$-yf8)SDSN7&d_(@K27JV zusFaM!GLe%45kfoN&F2il$-^`9O8R|nt@asJF=mp4(g>iiGG$EA9e?6LEriht4QdY zV0D!BfuPLEW@M;2Qk0OlHTyzV^CjOwy4Fe1Ekj*w7%pn*|NJe7@>h^bpN6gcQT+!= zob5F~_kwaQ`*ecxDD&-QEvHPLSm_t;%G6is_3qknh-ib}aS5vLWst*&o9{>{3Daec z2c90Z1y1>zS4YQOb39=Vx~C1(sw!u`U@qf*ORj+=oso~AVIl%xy^sU=J%@g4wE1R( zcVqr<-cVHegZra*Ej3$c#&#JGXp*$FUj{~(z6qOVoXaJh>1F7|S$vI~aL9O2GGQ=O zQxou(a@^%;Nd zBZ}0uV86iEL<2`VsS)FVx|DB6XSGRRewr9-@?TK>+eiFzzRCWG4;w3CyztfbhcK3S zy05!+%;)ydT#L^Lx1o-Hr`fRY-Gy)p~ELGRiv`s)&}CN&PHb)D^aVyIlE(Dvx@s<4q(q6mY2LCy{yXrI7GUhwvPv z#I?eumfvmL5o}c-@o30zvev1fyy#rXThi=58WgdM1n?@88kuHb2e`>FEt|+QM1pS2 zHdr?E*CHlKv6l)b{qe}k1Zc_Qhm$eHf)Fz=ALiqN(9<;o^KrAK74Llfz*v<N4cK?Z2zSwfm{#vAlz1nDoAESK;tvJA>KaLtB{K)Q{qwrV?e=};i4{oivA|o%` zv85``vejfgLP=CPLxn@hGQe@=8dHT&n-zwE3A3=~6!*UZ4d&l#{2!-lF#uSz*g?&U z1keb1%367VIZ#`EuZSLh9QBf{B}taT<&UE-Q3Ir%DMYJ&;P%9DglDHR_f6mjv-s8n z)sO84dS29XVKu)jZvC&;CGQT-6+p`P;@1*-qUhb42>JEOeqouJ*3n2^*jVa zf}JGly+_`BNpttuNv^I*YYqNyRowC|oPN+n7pPVPv9xN<9YygQ<|_1r=q&L3CoStN z0)o&Kw1-;ssFo(AEwHW92`uvSZygE~HIodTa$hHjwU;>8XUdKl@Hg^%3V|!4$|LIh zg0I`1Avy(^)&7Y~$;@u)Ynz%=(&@VXu&Rq}mvI-#l2{}=nwEz2SZ*2`pJQ%-$tjMs zz|xj&nwc{1B%zPif%iYA?aw{=der3ssD5t=YV?M}dG>&bbx}0PJKPsmJ^8iX>6}Eg zT;+jm&f`332@`lc?c-W|y2CeXweP+c%{zT4paOr{Fyab7=fa7V9#5_^_kc_BNJhX- z6XbRZ?8P1=0ZR(*yDU>#bh;c{!w7;CI^Gczq?~I8#h4jd6!pYse}@RwY|2~T%iK)= z3AVKcaNg4Kl1sK=y3gZtahR|V3jimG5c~}l9*)JdG?4)?q>7MGOH$bqgq#GtVLd@C zSbKJI{=a%01;#eCaqb!Ak}g3z6$tTjc= zqSa?uOH_n=on8y22Fdk19Pe)Ve0!tYrYri+pfuh|M)w$C%R3NLY`Ko|B4aUk7K~&nfhliJIOIn(z*6y%&USTFeSUDi>BHa$R}`f4_F$-S>~W&JLe#(R<^1`{C1nG-zP@KwArqg*PUNa|IQ=R`O8j&})$_sk!qi zQ3EN;u&CndT6Gv3~VKE6UK z?&}%wS{(sr!8_PiP-brhu+V~fW2LJf-9_;PHIgNNA*AR>py6a))oyai=rVXLxzrZ) z%|$>;zE_(;&w9b%#Pc?3?K#<{A{Xzw$j#L{%k6dU3H33_!GxnLC@U=DRpxoit~U)g zrbx<`F-*e>4%E#12@(z5xrU1jFD`xZ>lx<)E!QcD^QnxH0Ow|3e}4|1@;D zb=h*Q?aQAmyI`iFvCizy3yojg#LIn99Jag&>7G=Cg1ZpTe1|uR?6?!eL5&m_P_F%r za^Xd#eTH4X^`0h>m%t!K zWBTo}SGUfw{(Pg_UI@f358g+nANKv;=H>nA4PLXLVq5eLXK*D2Q_`7R;kK3;bgP*? zR3&;bY*gasi|G;Xm3ns~!_u(&BULR0==DjLXAM2fRrG*kOLot?P0y6`H``TG#xIYq zhogY&l(a){$amJ5eF1pF0f!7{K%`{L)0Ot_%G1~@N);}dxm9ICUPB$#<@VZk>d_5; zK+JhjlHcV9zI~3C65JXzXFphP9%C6u8C+u<#fSdAPM+kvF?AFqj>lIe z0iqf(Y;aPV0A+O0P20}oA$&T~NQhLOMAHk#y@t&E?GVk@v|)C#eb%S(m^?enJ+?;1 zG!OWQ{`}2dwKw%m^eeAp-Tj}oidmoCF`N$S%3ehh->RTb?>!aExkjq)1SC`(y?90c zJSF8~{W&;s`dxc^2Q~UJ!?o*t_V)+cBSOwkX&v}|UVv%GqdY*p0j{7$%1uT&g|+=m zLqZGfM)qqN8{lhA2EfKEO_FnJHzgDQU8u=wjUVOjJgp2aX{c5I`OWG-ruu(>|Nr&- z$N3ZoGDa<*;Ky(UX|Wx61)UtAIdNX#ul=L3kho^hcgEc4hXKbeU~)tG8r@6InZBH~ zPxXJ@-??D*9^x@A?CB|N2dGMZzQBP1y{j<@*hXe$|0a*Pglm;Naxsna)n3@h)O}N3 ztVzinaPPS&_f#i)6&==4(f)O7`19%;-(X6p#$Kft?}Nra#9rc81Rzz6@4IYY0mWl0 zPil>8%v;x!YAtPdS2)AJRj%d zd_0Wa)I!nHe>zo~mQk3_PbC0S-3?|ne++R>yf5&hZr6v$!~E*cT(y_-Ts#{)ZhPm^ zwxDOF_R-%nE}XZXd!spv@@uK{kW4xK-t+ilkAcC3FCwq&ac&7OqP7)ghZSDPk5lzi z%e=bpsHMVTt@*iguU@Jpi0z=y;~xMWQ6Th)?C{vshCA91X1IGTt+*@6!DSlmgXllQ z?EDb!tx~Td)X9p8Q3M;l6{u-X6iU4lp!Ys6EoRjV8Zk@1sp^WQV! z{3K(&>4_seki=Qm4h4XtT?Rt6i-32d_V?(uz^HG@L4f6)2h?l=^9u4@>K9#evuD0# zbE^8+*IK#~W6BnM1=^y}&%edY5MKzQ9i0#hj3`;~EIEsUvlu;AXjJ~2+NL2OmAf$$ z55r!dy{27A=}IZVtaK>YK4zjfj@K{Z***n+_&wKrV|**zdXMJ~3TW&9fKfvjpnWSs zx}ab709iHF#*oGyg#w=6VotC9eedgx=pd~)@i4i`ZW1ozFXMp%w120nV8ePVhVW0JUtZW&CLA`Oo_Jw7kQHgRbq` z!^7R}Kl6vR%w&CWuskbmwPD;}z`JeqeBJ4qH+92H+(AF>Um8NC#{!IqF4_n_N?+8i zFx7gT?1Itb)4B2wf*GShlbNxy=C6J#z>;vgA?vR2(1%+K3Tx~zcXU(e*`$PkSc@r-ta?f2vtfvap{B#N{{q*is!oImaoe%d!``Jz6GE;9( zYG}4yKR$do&{OfS%d@R#Q^;{$1Gy#zyPqPA+CJr}#|310*C*;EH$5}+F_%s4{LU>( zh`p_J-RLp-+!eno4o_a>iaTV6+<4|1BDcp7Q*Z0{Rd@@MpIq1^+yG)j_j0cn3y2YF zPQo@~5$j};mTzeILbTAL5F)gP7gr1Hv1&Zk?3RBTx(N6%lDj|9IW6JI3yhsBtZUy4 zVx6CR{9H?EGD^u9e}bPTfxeJX0USD;aqACELb(MjOQBrdWvC=fV2&F!+seCDDI+|Y zI4RiF3ab|H$W>wUm30Q)xGxh;@trs`LOVU&Cs7|EgIbAh{h8gIS;C6kIP+WVVkPge zXug!^)&C!-I*9D4OkP@29aS36Ob=hKmelwO3Bdd%w>8wI zhz?;bcsh?+u&o$`d7ZpbaU)yGf8N;CtZQU{LEM4G3o7jSc~pd=6mz z&pM2$-tai7vFLO-CD`z#WXh4{)Wp$w>A^+3A3 zmFpZYIwX3>s6N^JhvUA7eh63#OiK1C3N;}=*0ce?R^YyNp709m%A-Hj#yUih-1KE; zFGDwMuGjn(LDH$_iw`X=zY&q~*Qt;m(fqTCjTf5%jCMF13Nee8gDrihAVb0F9=5io z1^o`~+^<$PGEt=6Z(_>!{+#SJM_2HPGm&R;?##Wrb6e1M$TM1g@>%U?D?l28Eu-xav6(yc~W8b>QZJ&|F}NwHkcw+;KmX{z%_7 zFN53N#yiwt_uJ;M;rnOTuuip~MXhH;20{D;0kNd4Cn9;0 zNC#EPvn8>_!1C`S{1?br@_D(k_6JnOW*M{BVA%zE5T=QaI39TDyLUxZ~VC^jj`VCSp zTApz8>{Hb&o`L-X=UxO&mcs<0T+SqEE(}~3U;~1)SU3$EB=ExZO0gpm%$2#7S3>Pt z$f4vcxtoi0;ORazldp>|t07Bc(lG`Mg`fAfM21gZ0j8rWC2F~OrFRRgYok|8vfh?! z_F?4!DM9(}Q^qFCSV&j@!@bw^1K6t%Is(6up8fculqH##wvD-0?^w?-GWnL=waV4S z_tWfrg~vUaMn`pU7VaNnwLZi-A9W$3<72Jb$Q9{rqWNd9F2Y=hS{e@N+Q01isYXG+ zQ0NZ}kl7}4u6hx8g8Xf~6PVIAO`hH#AESQOG*-^Uyihl_94pI{Jj-=@nWtv9N4~(L zo?D(%CNxk$p}K7&k9IrK4ptccGd_co>Ru**kogpP1JZc9NFq(J8EcoxAusFh2I1v(QII=CWngWA z;7n8|jt<{dgHFQfx-sJWAsd-NTPRIn2|d1)%}2^%q}%gO&;#y(nrf}@Ezsi7zJ=WE zr?UUeFr2lO(lpQ{4n!${>eP??LVPKyRX2*UgX#m1!S8I@EZom6BSx9+5gy^@Jz^3! zV(hyjdS2=yrF}DdP~Nxc4H>;Zie8SNa;m2eEgSdrT0694gXeO&@lDg4yfFA)Q`w=H z|3QR6a}{ja=vP=^KD7WHL~ty;Nb;#j6-)sW7Nw)(b&ZU^9>nW;>v?c!YtRSJ5$I09 zQSbJ>Q6jsCxjpN$&WoUDDSjJRamkw<0|E%wAaGEYD%w7m{^k8At1T(2t$#R~iVDk5 zcX^(D_nT4^k)24Hn?OAnQnTWdcJ? zw$6M^7WjhXN~+#y+Wd9$Ou&XnBF#zKB*10kkH`BvA5wUb-BX4s1Dk2dTP zy%+8Jj5TDUcVL2AJ$HUyB1=N%ZAL1OS!t3O)Y;0qL4AZYCiHAY-9U|ptmR;UjHa8E z?#02ffVtaRWd*5@5qPytEo&{-Nz8LRMFyWoj= zSlCO@{O@qRt#H3YJ#%cPg zY+f%0_)40}G}jfg$hJ&sk|+6#c5++NA9shhY=s0YcFXZWnAL6JQh?-eLmF+n;PROABSi)@*by`fV9lwS~AmFsj|AX=*c0PX6WKf z%lGlycPeHS7*E&SfMep~XF4}64yyrl5B}61`?w1+!7fi-wPEcFIyuk%0_Eb2gO5+I zIhGZ@l8f4R&AuY4(n8MsTgvW;FFN5tB7EP{8eL*bw=I4hNk)7hJd`+ITR+VKKSz4v zll&dg=vm6z0<`<^SO8U<`f&<(O7s~ffxm^0Y0(2FGd*2SDjNZy@mtK=h?Uh1?)+#tuCaa@fHakp>q!L zP)y`od@PUzJk9EhWk~&i2+}|)e#J=TQ6&540Uj=vWk|5*o=-50SF|0x`PiLc7i~A( zWEb#CF(_b5A3^($dV{s}m&*s5>hcEWx7{hz$$cYr+f{b( zPR5pDa@tyS>YJLNNRqXDG>AyMiYYA3{hykj{~WatJ=YDR`4?l$$570aUeD^_q3n-to2g| z#V4f#|szvIe>|=~G>XqCK_T8v1<|xjKju#?5gccHsVk z-t*ErR#0ja8Qki2S7Kauf>4pUpnv{)Kh~J+uL2uxIXM2)RXu4v58t{2H27N#!OgN0 zwKas!_40JtXoBdl@Eon>Fs2XKWQHKNwwIC~3gNFYb++8DxRyO!ZyMF72$fw=(FEby ztHEj8!RQy2`!^BNGHhFbGzoLPXf{Nx#3~9dVRb13!MdY{%=L_(hc8g{!ouMUs>qq$ZJ0g%6xB7$EOwnp@5ZHhoz!$~s?}qw- z-Y^tToTdAa(tCCCy;g+_RF2&3%RRzW&)z6}F17(O1*}r>wQ%siUjZ%?IvRLhPRQ^= z31xH6z*}9UhrPx_q>;5(nwLgDJp`;6=TU=PVU?af%7=y6=9{%Q^X_d*g28_D-rZp@ zJ$vCx)lwt--oIO!fq#ljQ`Ur@gEYa6>JR6D_Y_&fm`ixgC>b)_56zlGBg~|*aqKFn z!(g^aZ4<|;DpSs%C$jo>L&oQZ?pXg8>sd*|+}IZ@>dSBVbo#d#{M~Oc-Afy?SF!Hj z5%R7lsNEr<{F_e-%4@5#GRjcFrit566xfizUlgG%Nj=Lm_dmN&Z8jufcQb^%u}2fN zW%EJFL=uEwZa%HF%(_e$ z-(qgLz0Iwg|C~xd#nL^R8gLgg_2*yGTf&vNH2VH7#34GFy1LxDV!Guum`xQ$8V4;c ziK)yuo}~BvsunfdUTw!s14=M5Y}~c)PmO>Fa8ajcB#+9pY||ZeV|J%efuU{Q)_#)+ zE`g1XC7UQu!nXQ<-c4fsth0O35uir#)u~Bwu&R%vJ8=1Rg?-+oQ0UbP&w>wl+ zHxOP?a!6)*<5W;5&L~@zlMB*E z#sYK(A__-(POX=h;%C={(!fL`G>(b_mi5)5?F272dHZ`dJ_%W=(JlE{`DNpy+e?Tv z#ulv*)f`|1MOEFFtr9IT7s>~CFfP!XCg?p4&9yXURnlAht`PFhulPdxT&utaE5BaZ zvPo#TUP$%n+wTI#=q)?p(yc&g*{w!XdAM?)RlFL?SlTCOK#@E+V;9gP=Y(|XGYf?&PvJE(Oo2g^G z+(0uS(y|K&Ys?JLbOoY#vY4$ilP+9Ryn3C7C8CzT-0Xv-88F0wMm}LVjgV~;o&pp$ zLAAHF2;X`N&wTM)tiq=qofh85i3|wg5?Rt+{|fhzg~vcvXHgF167@b~eXg$;!Q#zS zQ7tMJnF0h95K<1^w~;n)`m8?W#k*Cr7C;iJ8|_TcjSY5e`ub7S^q=hU{plHMLZGb~ z&DPa9^k-~*^HQbNaa0Ho#*cZEr~Qa{A_Bfccr(!Hf1`rcH;>#4z2S{U zB5gR6kBkZRJh&cDz1f$NS=n>gc<`N zJ25)@=8l1zuh${Y&TZnWC3Y5a8`h1%(5Rk_FE7Q3lwuk9D zEmGpeuEb*9!tQ#=oH8+)2->$JkumEbH8QCIs`<3p!F@{fz8`n4@Qw!vs!59XodOKd zZl(r9OI-gGg~Jqw(1lk?2yWf`6=PqWhi^Yge;1kZIDM0DCs$OxG^ne>mX^t(yPv=M zY=$i^h~Rma29$uH&$4}9v&$>L#g1fvF0klbf%BI5ZoMQ#LCYLXY)+o&sxJUQa`Mc?`Ysrwmdvv%^Ii z`mDkE`=aKZGc?>&c-AV2!}O&+%>m!{E7&RB@RK=}cR5iF!}jysH*vw-bjdp(245P$VW13ul1`u_a;Q z1zr1(5knkzT2hWLzjnXCZ*K|jKI77es$n3xb4y*ogxRn>Um%iN2iF?Mn2WYIa^)Zm zwznvOMIQ5EmOu1@bs@HnX(@Qub)coq)oL@mdF^krZ`@A)nGM2MCeE-w_}j0h-IJ1E z$`mI0+C_hHW3ohBz)2bgHa|F@yz!6*DH->d=p%AFP6?z`6lsnnYu5yfMU4LEcBzSP9{+qE5^G|+2<5St<0K)M`I22D-Py<3_zP$ zdxhK^^SDtNH6JReWOERIewjxW%E9?w=rKdS!ul(4vwQrspVM##*j+qEv`{THyuU1i zkanw|M1>+tkoxt2_lue6ZeLj;n%MABe&-OIIN8Ufx&1F{LLv`c`D_s2aF(_t=+$uT zH&7fuU@SSESAuoscOa!H2+4Z2bDPQCpm&=C zXLg=d>DU!PUmL-<%2H7rJwBN10lHniX7H>ICnH9ha1S%Ukh##=c(bMU4Xp-Pq-#Gk zIb_O62zZ`pIk=2a@Kv}&;K}T}=@PSsOuDVZN<2lnP{7lR*MCWKwlcIu&<0`!nk6$! z!#WLXrzB3!I1G9ljE`C2In)G!vQ+maSkG$6S}N6iv^HQqsMQ2Cr|{j&`=;B+#)JRF zh&*D?femlaW@r8+Rg=QC2zr)n+3Oto8h!BH+!JG8K_nya>zI4+{>i~XVO}1A`)Rr9 zn-|fRx{hA#GS!Tu5D#n&Q;q~L_f}6S<&AgOt*-xxBDbddNP|NBC!@#i+J)n%`#{iu(Nm)u+^oWbmZpe{L%3Vb zM;)4}*63mqphLNl^}4D4eQ7{mb56i@=oIOI#6T5r@I$%gPW=@04(PSeb0`=C%xXD$ zV6ZX303mL{>b1M$!|R=BF187|fmJR9%8#V-RxR57H5=xW4#u~5`0cbfa9)G=lkB(- zPHtu*?kJScMns=%F^L;wbw3nFvTP!35%Dd%c#-4Dd0LQUD69IFCx+ey@Iy-E-pzk)`}vG@I7}u_8LTM%#h^1N?tr9R@`Xi9l(f%!`l{%B<3njg=ml7ipOL2P^{#R5`QG$%RyfMypL@9!+M)TLw7+SY;G` zQ<(D_tUXe(6{pm^J>A21(f!WT#>|rNLr;H>&f)$M8u83ZCXs}R&A>mJ<>BzQfu01& zV`;K8A_@Aw77CN&;MuPg0xI_HGxGr)N&h^R#>T7*L;FU@_BPi1n}Z4ZPWHBtd;5Qz zd-Olo`)?XW{Xam5HH8A8y}9rupF<&zpn=If%i~B&O9?7Mpo_iUSeFnJh@Ac{Hb&e9 zOd6{T|H}A5t;}KPC6)L)AJkKF)s0!|*Kf1-z5ZM5?9o*{{uGex+bd$gtFpS%MvU3( zuj*oOI+*hd=WA(`HF3Zh$~h@O#nhqI6aM+dQ?_|IMx)ay+&O*;@^s-WiUgP|blC~Lpj;lh!lZbULNH^aWo&EzYjnxAt zAuHfom7qVD=q>dF{BZuv_$V4kBb@_sH&LqZ3$II}F!J<;&t@VJYU z__QCigTQdYM!bW%-R#84Sl_j{A~X}f-`p?IugrpEdC&?EB)AoSwn+2tkbDA^KNW=* z`i7^JKk^HC)#nqSyY?26@5aeONi4V&X7Mdftsi?GaAke7(eXk}U?V-_)X){pRu*@m zBm%89{Yd++tFIT91OlrU^%QV=u`@NCc7%09$@FDV$87zKZ@nVY7Amg45UJo>;n+|- z9Hh8D)YQBK^xN1?=$AbvYgw&*35E)0WVRwIP4dIutBP%&#>2rtHy0GuH#{=;x{AuA zQ}o|2^$?+p`8>}$C$O*R?}gM@{@gxr*I`pOIPtn%Ix4MudwVT1Gjyl2fLA<5sp z0llEvRAtQ&y%bnm|5HyCiSI*9HKWb8{egp*@p0k7OXUhQ>||a9K5doLr?mYiNBxs2 z6+R6`Oe$-(6*)Mrql)u#Pnzzx3>G1TsS~6422gRp)0FVK?#`A0C(r!XNiZ9FdNQxL zKEf(&6P-j_6^6@b7FTEeqc=@oUEymC37m^AMu3t_r}X>!?Rz$UxY9Kt{3EAK?bGQc zP*UfOzfqTxCYW|=D3(X)W65zU)MeiRp~Y~bwzNvFh-BFMu>LZcH98<-1G zu3wGC^TsZ47dp}s?p3DPdTzUe-Qd&wZR@@t5!YO7R5vtVlxa+RkFx;+oZWjYJ`hn? zBYT9Od_+5j*K^HOAOMB7P9jHD@M4ffi#FD2y+EWf{SOwA)oyf_#{xD4XeqG9i5w;d z>qObD_7gm7u zXBgK>U)RpFZD<4UK_7-^P0H3sJ`(&CqpyL_`P zSNGHDjEWUbBX3M3u`W1+apT88666#(vj5-d33YDQixnBjl5H4$=SQ3k>a9qB@IW?< zBPD&s|Tce|e+E>(x8XjE2k_^dGvbIc!rZQ_G!0riILgs(l@NG zy1BepCx#S=s2@2TpHHX1;dMoI^`>2@f|{IDn?+O-qJ4QzGp;skv(kzRaUK7i9uiEe zS|2dNnZiF=+O|`|^+~l<5smn;=hS>1%BODm3y2^fD_!ER!QS03&mQpn!+pXJY8Z65 zz8@qayhM>^FUT{1cPl*3T1R&@)U+`!a_7@ZW*eS;dr;xGW|6$IQZ0kosEG=5Pe1eS z^gi;%Mx_I`G1Mo<_lhqxDVbLdAG)%_MbL)RZSb{I^d3{wg$ zL{Iy>m5z7I*%jLToup)U{?(&yg~L};^)K()Ew{z-@}4VVVq)9Dxd~1+y-jC_%npGl zah3`UspXwxdP`h!eUE+DQu?>vOmYM;VQG7wUV{qu>Eqsdd|Xzn66<^F=@k`FAr~r# zqyBE*dFDmpkq7^YI`e;DbA!m9!41(6%BG?qqE8=-GFyT|C~v!%C!zLEuMZ0*pJ17=)U!(+9YwI zP~gUJsT~*{&5fF zD8}JzMLcdmBtGEjjL|Y66V^hSPEzGMpWJi1s?*4=Izr*xV!F@B3XgGHawM*85I%OE zmKMswMBBZ1zyR(M4}gPY?x!F4-9k8hqT34U4-7E5V98B z#BPMpf(uw_+7#VPQE1cuW2h#K%uyYr1e_4q4(aM6c4N?8HZO?5* zZ>mw_W>=TS#?3Efe-cg3SYPc^5>cl};-xbLXEZi>a?8=S6vrG-mc_)hsC%VxkzH52Zu)>+l z%3!nW^V5aVhBMBqVuLao#sgZ7#(?a6~)8znP8`U#kIn?wv5DV zjaI~{E^wG7ux%?@k}xGIp-Gad?A0ZHo;zQPf9W@B&{c=O&2I?Fm?$%QTw?80+#M45 zQZ1v3;W;+y%9y^#z$T#o05V|f9V!qrOg1yVi$FxV(h=2GgI5JnfP*j;89+ zu;smG6CU8lhti8K20pe8q&kKK3+YeS&y4o0T!2q;e+I4ITywVeQxAmj1hi)~c|Z9F z@gk^MU&5IwL08Az-G!l0O0kW;m1?FuQLIJheaNl(=alC5=@uY18umg1MJeNa+!E)N zZPD(CP{6m205e;vI0oTN7aH;oO=@&Xg(B(}^%pilaAs&qa4z1X-m6l3Zdo_}zDRLcj-d?7`rc|Q0=~Xb zv@Ge?Bey_hW_K!&G<*UDbZ38kVB#LCE@_()a(Mg#3TGoQK@_e^}3V zMBP-bJwEn5m&~t*tcx)}`Ys7Px}$`s;yw)mPF-j?j6Qhfu@e`Ts?TgFPhC)T8U&3y zO8^eXdw%!?#3dr!s~&1=Lc7+fROTlDg)+-l;+uQ~lF(#3k&@M}82ScG5y3E7_5)^$ z0GafBmN1ocGTQmEE#x!#=Jf$9dk^aiJi}On+&fHjl{Dt&JeJnr`bkYqcX}{l$22g# zHL-f_=c)2pKf;Q<9Eo-x($>#CWL;5bzarm|T7)-f&PXZTCfOSlq<^{Nd3D3X?7(Ew z;kMoPo@6I`$$dX^(e}gfi=5g~E7h?(?@oRN5e5%3STT{IsE;9Q0FQ<%naN-+ARC_7 zg}e_Syb|!cO#UjzxOMVQzTCZ(kpGzyq3I^*(e^)bu)g0w^3)xzkkYHq8wcifIHWjd zQ3;&+l?rsMITA3A9uqxA27YfLY~2{DWXtZWGi&rVgcKyO%=w##=+spN#zdoiaZA1T z#@g1hMkpM`HkF-K!{2!pvvabfrqGU0T9EOS_1W zsh+aUDdj@%Xpx4U>%6gtr>tVeU`by>H!J_>JzM>87vuLH6HWo;PLC;>wJq83Y5xIF zOK_Sjt_OW_P7=}Z`%UoNO?)K8;gzzG?aGKrcn2~LxueN}4G;pXZa<6qhRRE&7WxhJ zT`w*wek|2RjtEeC?#USO<=VXVoaSdajb1{(?7b~LY1s}0+dv^2I2qs=Ox=Z9VUyc1 z-+~jBo!Po=h+T*xYt?_RIKbNzTK;k${p_{J|6p*csx5*oc%0EV#|Jn{J-Cx#=&$c!-L%oFo(v5_i}y!VM@@abxTlJG z2wkjKeK$*Jq$9JWw+zGVrrVrhSjVSouwTRRt>>CFOq?dv*3$qfoFVPr^JCR%jUUZx zypV^ire?vjqyT}=I1KjwpZM>iq#o=cR{cxRkc3Pc`4b@Xi&z*z@*Nwhtk|f=2q)-` zcZg~I-CKzhzmW0rJl9^;h|DA3DjQQZt}O(dAZIduI-+a;{xVaZTC`u;T(dx`=<0>P zGM-diJG)s%dXLZqoX?}b#rA`hm^%CoZ4n&6NZWEhB2_>W>ZRzI(4wB3MKCe-$L?pA zIkYzL3C|%!hf#0x-3iy(iK97Jt9dqcgdO)*753(mPz}$HS-i&Fw_dP(h(Va}q~UFM z`?lY7)KjAGQOJnQHR>l>OUD)YAmx)foP%GoDr&-BymBhDKOU5O^vde2W05U(-%RPI z^Dc={xr?S|z2*mm#kRt+Cwb-X4R4P1pKQXxAyY#>F>N!h6TczV6pQC=!ygV`o|UTkq=dWJL{?ZlX+*)C3MkGV}A}3&?kW@QPL>*9kqcz#_CVqQJHV97UA$ z7U5w?nF-&4J;jAb-GJ1%8puksz1*@gN=j@Cr4&PE)i$ytUFX?d1CrLwXP3W}ryHbO zOK9n6)aN080>3Q@kn4#1%o2Cu4nc>QfLEE+hLhwjQAl9v@gGb2NfK8|j9hNFYxU$OXPi=BPu*Cnx!X=qb9neZwa=ct zWADKig;VyqqZb{$wn$Fj{HkH7IQ{yGvs3^2U9drb9Jh~$Gv9KZ0Q%=6SsjK(9>5V; z9M{L#E>eQp1mf7dbAE!qhP|ue1`!TS9wQp#q5luUrx5Bh;?Yq zP>n~#xna)yQm>mewKm;4qDOM}DyRIf`>8HZt@a2J_*QE;vzHOWkX=|NCc?Nj_ynWS zw?tV6BJ?E^*N2y15S}d)!W#4LEmmQq*-{xGV1zLDbv*2Fm5vW4$1B);TshwARf;jR zsyx#}LZ431F10xsk~}g36wHTGp9<(c#gtQ4Ey@3=FN~%jaum zr8L<17##|AYp$E);GC|3CTyp_0)>z$l~MLB%T=u=)3DNwz8?F>0F^=#%1N^|;)H5k zOBNvk&XS-Gzh;85&$0*$>R^i;M)sUh)|Ze-v?I@B)YUJW@*_*hdGIVtdm|roe}-uC zPT5XbCYdXU08t#0dmq6$Bse}p~Snffsx z5oxpC12=2OEzdO-m9ZjFx;rLj;Sq5Bnc-W}R4*_P zgXC|4%t4bVunOwL^Tb<4YEVf?r1*oTJq5FC=W`1o5?egp#H;ZANzyBb)b6J_uHJaC zX77;_tIe#~_SXH5X|olcikMvDHNQ!W*MoY}>3pQ?sgGt>|5|T2b?SY?lgjk`9ondS zg$3I=b!si2{BGR)IefQs@~~a9<7mjW!f(%<4>{jU84PIj&H51j|pi^W&4tfo-ubJ z`K}ALhFLZ67^0MOS zh^kCRcO}YGg(n^-JRwr%X;oX}U#!PXT&b$aR+O9-VJ}P^RBt(M)OVoTsTNI2JO&N zq6%PB1Ivyh5RZr8M-7;0;x!UYHn7ogD&hm>z@Z&wru9dwbRUFv?iXsX(xls8S^W6g zgEmP=xu*2xeEX>a8=wT-yY|=MQ&{`E3YXW5$9sOclMa{td+SZAhV;YM>&MDNj_-Tz z=yxu|D0up1UGI?(^S|t3(5F8fw@>fhRdjKWW}i&H?RJO9PwbNqQidmJ9{P=uDx_aF zMc{V*BvL{sxlQ4uxd4GF!+%?hhldNMn|=PNkA6;q)RvT9TA69pmM`0_UXIIraJjH) zv!@2TPpEtqU?n>16fEqO=p(#ZV=jj~**;+mQ43HL5)Fu0(OxKn*Um04dx43zsvlU^ zhQBr2ml_&1=$QFohT}bSi%U-+MIG6T+7>+fQf3LKifaW4s2zOl5a1Sg^TKt%%(B1Y zB@J{_o$@3rAwoH91J;yRoF9f_9>Ln_5?%~gc1TNN%KOZZ!S>D0n)b%N4y{UhStx(J z*AJx%e?zTG6Dt3~a}Gil>{;=3k~U5WMy^&}-Wm7h*L ziff;heXEs1v#Gy0(p~w(ljEoU^cOjvYhN<=b@pmw1j1*Jq+i*S=Caz~Vp*ZThSvE2 z6|ki;Gw~Edq3kS6nuFk3@+nY&Zkw(GZlHnDI;*<{Yeo-|I=IrcW*OD8#}Dw(=fBaK ztgjbL%-BI5LaS2j#q?3>oilY#6RH5WiSaHsC_7}E>TwEG8}1pu5Oe1L+ebJG*Qc^; zy@=|A*zG=>x0z>>0N3GLMWM?S{E1upamGiwZ~Xe$ClUYl)~Ov9?&^kH5{r(WdZwcuGj3Fa zvd^}Uy%>~~{DL-XU8+ZmnY2ItOtna&=yI<>j?qy{&74F;HwojI_7O_nWC6>R7u} z;Sb0|^IoH36z-OZT$Xh_X)zy4y)!qjPZFr?VD8l6iR)cONZkClX-wvs%E7Y3uUwR* zPSP7emo8TD7?-~>D$3neJ z)*`?*e!HFozl9ui6G@B+P!NMneVZ0%W(1XTNqkFaod-lhJ9@p^vd=(|nCSi3Q_yub zSqUF39E7vXuHZgO<_P}qLEjiQT1F%Py}PAqtS_U*!3u??S4 z0%>%GR!{lZnomc-gQ;D|XZ-nt*dfbq5@zDw~ zK~#IaV)#i3tGOzL!+CXHfpT-zYkTd_;ZYo64RXQtQ>%pomOGHjW;=Vd${%&g;boiT ze)Sxx0Pw%Z?drzU+v1;BzY*JbVb79IPKeaUWA0-%dpao{c70nqWK=6`cD$^z&_B_2 zRW0?xi0A3ui4FgJxNv~TOA%~tp^|=!c{UHSU>se6t*Gx;r|w%u8e>0BX4@^lPI5aj z0e6fW8}sQ`*g#UV-rvJ>!LvVo9%~1o!vX@gyw}o5yxgH&jUK7&ICWdM=6(I-9%ora zisf;Z+vSoMl-cjAw4)S7Kw|Ui2trz@I2F+mX}+J3&8-#|%cS^#rD^-*8^hTGyH(i*u1|vT^#*wCU~7YOY7JFMXbr|IG$S`k=FHAY8ozH* z!SuAzJP#vzfW68iV^4>_Ukb)Cmgk@zP;7%T(=UAj?=z9RYcSHRdb zZIsZucVP>Kk-$vsW7Tz8Ywoh&WP~zO$(xYi8M};?WE(MRRp3<_SK>*}nPxD^Trfi$IQvm6YMeYbTJ=g(dwYrM3 z^c!oWoY`9o*i!JbNgWE>^KYJ`aQpj(f!Cj-3`-69Ddfd+jh_XRI#yj_(%WvG#(F*c zv}l+$z)o(U+{T$l7oE>=chcRR?EWwI&c-Xpf;H(yaUbvgD>3QsfWd0L8Vcwh5-~n# zvNUfzA1t}Jy&(To*Pb8WCilIhhU+yI$$gjTdlWHH#aIRYL4YIi#gZSk-UKQ)DMfx9 z1P8tBM*iOrev+C7{NpWI$PGd-^j1`!PEtEK3edpNt>wfUsDQO!`#?*-2KQ4IzlY;y zMQNy_#T9KWxL)Y)W}*q36`z{~txYKcPa{=m=p#4_46(r{;G@HLWIvG#Wt$rFOCU0j z+GZ4(z$r=*Iu`nQszZU3lDQkcdRV+nlk!uTDEroZr*_TpOJnYBYMx@G@$u4kh1Ftj zL&SFfWArOTxa4u0O+i6}Oi{_KQr* zIDPmIjF{;L^m z;Zo6CIKVIF0tFzofN}*ENU%*teK;1Nb=dW1jCA~71f*lK>AA1bt!5N(RND=O^PIp3 z^{{g@%bq7J#ZN{%5!xnXxQ!U1!jpgeRA8c)C}AN_noxz4o3uU1`WkIQ>IggUkd`Sg zo->#%(_!I2GO8gR4d^d*+vFK?cMnfao6tdPzd|M?`wqo8kFkypkqJdkWg6!TCwq^g zi_3eFR|?Bi^D6DF#!IV!4n?xj#{V%2`G3eLol7I47WAES4!5fTAq9G)gJg^~h7N=_CPK{7U;Q*fE1l$~|p7|}{gW@Owwdqvd@3Q;JNB$AjE1Yv3! z-GK6KIQNE$NHjC4{VX2ao$@JvQhE!fZSri2x0?<8GS9G`Dg#-&4GmtpTCAWonzB6n zDNgONKqW$%L+m9hfVGvvx#z(wNz%i7>|Q9JJN8K?zAvi96pNsqjI+MJQ4tThG)$#M z?^E0QOP7-LoUU5PMwz&#aS)q@-dyQ+canCdZWNTXes_(c_Kc@|X{&S2tNJ1|TB%Xi z!LZ%y#N3W=kK+`@quOm+&))FAfpT!1S{zA5KAqGsbGOjF9y&LD#CC7$Y~Iml1*b#r zJ)zK!Xv?p9bW1gcWNCc<=gr!tXwaTK+MvI5$NACBJ}@?j&zJ8)LO^OTp$=R{hVLvN}3wOF^erjL-gg~ZL}qE8DA#$nICesw+9|Ma(5O;N(=Bv9db z430$bYcq=&;*(9oz!f5kN#$7QzfCt$@QD$+0J5y;;KhneA5_{7p^b-P>jxF(l+8x? zjxyI@VSm%upHGZY=qvE8ky;>NO!w;YN$CIFLiox3CO>N!m|z)mn_4;YLsZHNnvc=?XwDi zXLqFBzeNSOB8Tn~Ju3GN&-jt{dfNH($)CFVi1V$Xv#LwDy-*N-fZ-(u_>o?~wm2Gvnf)u}Vq zyvcD`+r8E3^+)bS*;Wq4!|b-!^RYMG(jRkcqw$w0%$MHz-#B>lL+xoRZ+?CuDN;)gLZa5kuc~&XYeB9nlQH z=)clgu^VhjWPDzklK^n1mS_Z7_4*Nz$oV%8JjgcaO2xL@g*;+>R}wF){lxj$gE=-( zfqK^KKKAU^TVd!qlL*r6TG>$2=5Tc4k|w4&YLctV4((tl2=P!IXxhR%a-BRPy797D zFE&gCQ*EE|{d!P2M09*Jth=QOp|yY}jrQCP2p-$>D8FKmMX8FuxtDj~d-?p5letp4 z#_U5G<2|M)(gUuDeRh$F=j=zlMy$?SLjvE5zn-m6aRTe-l@Ggh!;8<%Xhu z&x4!Sf1Q{9I5Cx$VR;W1t&u2tgl96+pue&}Bw@Zl%ITv=o%wOxdD3%S2VTnLT%G-T z)sY$jaa5zrXvk)TuGvakr0&mv&FvHQ~Qj7v0hTkem~E zu!W4)3tn(TIRM|?B3I25Z$~~1f98r$Xp$G0j*WZxBKP35rymzOkK*!q8`s9!LjZM< zyb@cm=|UX}k)W_1^SDNuX&h993MZ}Y!$`jM+zz=h$&#QvLbE)?--u%!i(RcV)`PLhMT9{`?T)_V;3^un znQBH~v^)c&OB8BOga;Toipa^u>v_gz&vdQm?LpV>c^@&Cc5S(?KshW}m6!f>bYF77 zXwA)h)xnnoPEDSbMKA8-maRRC6g6mKa$ElJi(UbS(J`@255$-o-pbY3A-~)4S^o`2 zCE{-xTzfcl7<7Ulb@{pBm$(6!^$AC^XinebMM9z$o z2qvc|Bc1t<&<0idv1zKfXd9-$wLiqe>(WzI!Urgyckb=aiK9H>;91S~h{9G_99H6P z(qP9}lbLgZi|KUr&?B1g0@82oSOLslpyLCb7lMHt37a+PZmDJl&%czPY zF&k%z`tKu|eW_H~?fcbZNtkRYO(!U$3<{^ubvm(CYy0yP-Tv{MH-3`t@=f$FSGNPm zJ8n#_-g%Naw`js1PA=6_4?sv{{8+uSNxaAUOQgeww2u|L+iwgFJ3H)IzU_HbH){sU zzA|NG$6M4=_e#XpV=at=PdghJ9vOUAM9;R1c~d~i(svG?Ag!8VtKa?l>TZ($N6yw* z&-mTk)o)>{J;r)6AA1+|WIEFQN3WV6SlwsXUoA}4Tvs>K0jFm!gKLZ5XKvcvhEIlp zw6?X^qhDrJ0*hec<0oWdULCEe`UyH}Q2?Zs{c3&a$o;X&QjEFr=2(Xtq;GZIL`VWAj*(xwGokqhzQ6SXNCy0A`&H}6hsunw4&025ETIdAu5xMky)iN%n=YWpv*%> zAQYrh+}FMLz3ZI&oqN~!t#$Uf>wathA+BU4RjK!VpZR%yKi5mXS6}3a6O;Fx$UXNa zUjK^hH%~K~`|b))MuG0S2Hg0j0;$hO#V|J^Z4@iDXP_Hyn4o&nijQE9{pwZc?q#QhNfAZ;=K5EU<)z)5-ynN7R z3_zAO#S68Pe&d?eXB^3@UvN9+X64B@-}XFjcFmzs)%L~Si|eZ?-D*cZoisjyM(;^m zp1<{ie^{c8{fCXPw}Vw#wogVNOlcy0uELk_WLKCKW@(YcNIUfo=2(>1_X!;5#2f-1JFsi zE0`*1^7y-W?xNDwNUo>}`4(RVp|-5s#(y$@rpbxMD_QXok?1G$S#!Eg=JeRQ!^cKU7B@K59DV9sYF}y?^e4aS=sTy`YM(<5TWXm# zhZ6g4yX3h6=a}M%_W29D?C)AY7sc<{$}w6dPKVWoW)bk zDVNQA57&ABk!twcQmy+2H!Hrr&ahza{Cwrl&qAc_nMiQ=^zPfgrrfM4)Cwt0!VQX2 z?14|6=={L;f_2Zg3-`<@sGi=45(49kvt;_HzVtY{XQMRxOv;yTG^H-twC#_omzWKj znA7Roj&GH;r=Kp=+|}vO>UgN-Vz=I}xe4z6gsS2LvCqTO4<~%|KQu9xxtSwzH)#l3 zJF&CQdv$?f0c7--#Od|hk+bVP+FtK|{A>52{Ikst_DACK)~%{sp0}lT`|XoltLy9R zm!~g!QMugVw)4j=t{cD4ls~mv>5_c-pV=Kd0gcAVcA$aM26;Sl7hcaal(Y4PciVzJczP{;1NkZqCdE$LdGH$<=Le*i_3 zKnzgv09t;ost}}T00vxm>94-E8o?%pp$VZg7+*bW#SC+?@ zyocj7-xUUbu?5-cvmx`)8Zq(4TMsYAh>3x3Z&jFvyGPD0&2hcow~}@;< zRkx?;9Y`{}aO_In(bn}}6K}iqRtvZP`QZRP+T;7}%cnN(sd&*{68gp_t6<75tHiB; zCK)>l!U{51n5LurI{pZHUP6+(qt)wOm-r+=cQAh;ccq8fd-0C>IwojXD*c##rS2sf zYgN~ueV5mLmedxmz?~Q%Pz=`wPv`-A|Cw*jCE`_VttkVN&CM@8Lm(=d9M$Z77_WVE z-lr$i@WLsU&Z=|0*X7T~Y#$Vc?V6`QbiQMip$18+`HUMP;--zq%%~r<@Z1(2 z|B_W$%IXaX(RT`2z4w(ABiH%|1AFE2O){{f;zj{#=#!- z9lscC#H*2d>D{k!oEw$rKUujOUVp56q+r)|qsQgS-A6#~(jtox-Y{rSIc8trz)D{v z=&bLKfh})~mfYHN!f(RdCq>WRrMc&u%^_9aHyH~@Ugp3_af3}CQ{7C0EpunX7w(<> zIl`khJxzW+YumNEU29gyH|*Q1tGnmS&uhtzO7n_-W)I#5NVT8<=R!h{g(NSCCUl6Q z#qM8>odp_wXd8e^3w$zj$SY+SmnBJ^4s`gOfGY&<3V36~O^{IQ^*| zgyEL5z|ME`ZVq`ZM}c2WEBIrD*7-A%HDZnY)9Z9q*V>&&-uzy3{ZrPfcXJBGXv*YY zvqpWc>V~f0{%9CFG3Zp)=j!b1zPt9sRxdSr@Qn`_7{6w;Z4wmi!a)u z?=!v-2;Rm+B}K))`J3$%(pPVM7Lw&~fxCN)&&xz>M3!p(5>0eyvLK1HHJv3&x2A?m zREaiCdH!bO&g~}t-iH(P&9(Kdn$N8-Dg%A!!$qeG-sI(n(_$WdyR&NDJr@UR_cXRF z23JkyGuP5{E4T&T*h;vv(?MbwBc-S@bmR}F{`8-E*l5z&*gb%op*^=-1_QHwzABF@ zi|*w-DGo?$zY>2t#~)z%2QJw!1BLtf{XmYCz_c0gIUn_yw}K%Z?Z)zT7zO19r+;`p zAW*}}^r$Mb5kL@}tbdhqv;f%{ABkxSsY}oc<1WXVmB*J=i9TR1^2Y&rG*4#Nf!3)o z=&(G4Ga(x!PWFi5l^JAhA!cu6_`dw9%0j(e8CJ)4s{T}*LRqbuqB=T@q%yATlt?Go@xt>0%}$| zP~Ta%F8IFz+vi`u@jnH-Y3+E7#X1qBgN8UEQFls7B7qj2;#oj52&}}nH5m)QESPKV zSr?j}%dhPQ9OLeN9gox#luLY{ZOg3LIgj{BFbaBJ7v{kg+hO0~dafE{7m^N=1E(o# zyT2%B#R#s(OMj;;9)6JeSH@)|rADKe7ipa6plE!l9VUR$rul_8&V*k+^6iL^A^hA= ze~32>vt!I9IM1yHXCCqKGZASj((H3(QL_1?v6CmX@Hz}+C&SMI3=U9%tZfP)FU4!1 zTvB*2yj0-omq3ah!I#o?k4V~XWTd?gGe5$r>+EA?(%Ys~$pzy+WB&V2AFEBzLQ^WwckJ(_z0f`Uuo1Iru{e?TZt;%K3l&(c zo%-z`f=0%pcC=1dM3@4qv&x5m)SjV#IiR#G<>D7@50gU|lJ*!E4cdKt%rDvf(ICa_ zenxvtq}%TqN~PZF8;8Ld+u3|i!q*5<9k#jwC|CsH-6Bl|-h|dde(GV`uV)wu?~I1kJ3wxptQg{Inaj+_3G_!|h3#Y!0jl3nPIAw4rv4t+7H# zBRddl&+>e$+FVKK0WLb8_2Girx%kTAUUF*h^Ebs0>VI1puNs;EGPaOXkb7hIN!HZ8 zXqm-CEpg@BJ395Iy`MH!A3pE!Q0s;_l~j4uW;2I&y*~ANmD44vx|?I1f5G?`Q=aX- z@Mq%`RV(t?k_(;}@6RO67{wz1Z&?!&RL(--WNm<(36fOQsO+zKjTY;KWr{X{G-=Y@ zHDFW8!!GMaV{=<=z26G&jfus52hU~y96P3$aMp1{PD87M6*K6_^P$!xx7?&d`FdL) zY&{y6bSN(I!sGrurd#Zf1RS}ac)`D*%iGNcaN=w{{qWYtH@46ATCF^)@=tAj3r_MA+*TAj{1{!{O! zypS?J;BOjVNmp`_{B~niDbW}7(C}{rXFc@a327VV?|PIr1h+i+=e585F9i0#T0#8l ze*bZclR8!aSMb3ARmiBdYz1D;%@tG=I&6C>F}M=iQUm9c$SSFHKe?xAF5PJ=Uh{{LokXW9`kW1?o<} zD8As-vQT}{>HQ2$X;6VKZ3ygi{H^gE<{~ja+Jx&c4ACv3B!|;sgu`f3FM(iL%YH$> zzcAP*4t&0@6w)FpFd;xT6?Mx%u$R8z_s=PQIBH^h>I*tWuT5# zv7al8tVKsXX1a*(7Z1nueVtin7noxFD(Bsr+}0uRlN|SAR9JGonI(xC^={ zpgLPH9gY*}QFU9+0sKP24SY4qb#A(ND_>3#aUzXJHO1O}98nnQXqcI{UggC8^qzr1 zn3npbUYKpR`cz>v>;enBVRSR4hK+8~#MDlKw#`-L<_$};B{3pyHC2nr6WZz=kpzqv{1A zBx|Gr@*MJyRuU`7huGOGwLv2#usxzz%f8OdBbSJw6`e^(22BX+ew}BtQ-%_zWAgU+ zW0yt4B&}}WatNu3inKT(f>(67*Xa)L^jp@jTWNNY!_mC%eLR1J#H(DBdXjq;!}nJ>B&mx zI5)wNn`sp3Vwj<{&aw5}m`J{tQwyOa3Z@oR3jhNDh5-j$WL{)(Pkah?9Epl8B}sBc z1_j&@Ia@TYhVtLdhTNQt5O&iXf38cl60=n3xz!2w4>G&wrTdoavaQP6o)5)ZVL=D`9ka}{&ru-q^Z<9-B-gD zjPO|O5Nik4_AHX5#rS6X%pS;1rH8NXZ@f(E7{`~o%5xzQ^e;h(BUxYv0NZysm`7lMG76Jk%HHez^r*v&7U=&8d^l-MbrhOd`eQTv$5LGO}Q6E^6POCQ?t z(PG_(htnE-E*OZf;KHD+7Q;pkvmzjEGJ)Bi;Fa`@*#*BQRr676*)N;opSz)#cr}-% zHG6cMElhj&TvR>~-Eu%;L}M&nLRe9?>iq@67`xsr{AQIo$D26X<( z4Qm8pF21NsXh*=6O~Gos2WRCfTwzBh`xe91q)7G~ZuCXYBD^7anM6A^w(nMIL)Z_t zdT$~5$wlS*#r~Vf^HCbo?=Eo?e&6;Lq+JumT|~{*dacgmf8mqEs1rCL`^HmHRha`^ z;R&6j-WR6XI`?^AZ`r*Gr&?_6?yvx246C znukAFFpo#ff{;J_Dw79k!+z8bS{-)*d{)pIQUifHCaB8#c`277f;#TP4!`MH@H-4j zEqe_JUi_B^{*xp%6mr}_XYXstCoc80EH4Sr5FCm76y2w|!Em00jcMrOi`tMvd`X9p z9pRk7uAYae^^!oQ^`-{C1)etIRBiDm`i_6Qv7|eb! zK;#$l$|oT?JmB0#e%z7roy@ynUO86?_-~~b_&AB#1gnicLA=2wZ|oSJQ&4B>(+YMK z@An%Pg=zE{cvoBRZ=S9!7DP|&wrMUUJ*_)$DJ{x!rNjFQic5;#6vm@;@bIb3N*Hk+ zM>kPMTtP{q7PJTn%cyzNwQC4bT6I<3>!}KZJjwnFmn*q+SzWmfV(eHa)@R3(^=|y- zdyh7pVY9}EziHIKt<9@DRjqE6inu{6cooAI*~S z*$wGJXn9z;|7tNJWpM_|m$!{M^Bho?u{1tVvxAG+L8t+zhI)sXUw9i3)w7W!#EzSI zt)P&9!??`0nylWGr#zfZ6Dif&DIRPA28z>%kTWi4()b!7JOF42=&o8mqw%#k7?Y05 z3-jx@mpmxQO*g!Dd1hR;3InChhqwhs>F3s*oho> zH0n@WIfQbY+Qdk5lIfj*$t3-~w>GsYMdv4xhw6^H-!$-1oK*p94a(CM-eh&SVvEIc zp}~@_cM06lc`|40Qc0pU7y#ZwxBu>A!d}t6X4p=Hw1S=xW@^tj2_scmd$w+y^w;41 z)?}g$*OAxl1jSU1)KQZL_q|vV;QLt#;Y%N&PDs~b`E%xaxMC~!^e9cF6C)Z1p+cev z3b#0uL&YvOV!CJ63>7~R`;r{2dC{rInu8rh zq!{926FG&_%5XYacHog*pWt>xxmywQ^GUQrCOsFekRg-i{!uFgwK z(q99|&L*ey#@LzlNHv-<^|ZkwU9_HsNYb73mqWAZH-nW!4;uSx9`nZ5jBf*7(!w$r z@f2KzmrnLRV=U8w(7WOrh%|2P2T`nIb&rZXWh#kS>E?fbJ7J`Id*`4R1oEVh(qBX$ z%afCB&5z2+kG9sdgROrHzWVBMC0wy-4eqR~(3QTBCjbQ&Pt08yBik{2cqn-TdO}Ez z(Xnj3$i5A)C?%M?bq{J5pBc-QM7)a|(qyH%dnRdm!cjdcjhNl8Y~QdH-_EwkQrnroaw8iCsBYV@khsnyw_zh#~;)Qv^=j1Pk@G~5LeJ|-gPi8UZnGR4#tt>m`^QtuYB zB7Hqi$ce=O<6;$i`8Lb}6o6Yq{93_{4;xvxEf$bdn2Ki;aF~<-F|#&zWg;72E5FlP#f7{W+V3}^2!0Nz;K45B(AR5 z`x<5>f)zY46~Gy37gVEV(b->3Q84(#Nw4W0-ES{jc=x6CYVx)=lhD{*?omZWe<)6Q ziSxFF`k)Z)4AwW3UzX6=$>vF~ygxmKkA?JcH}q za1zWq6Yo4N%P(#YK3z~Cfxk|AQgH>T~Kb&Trk#=%xNN|jqD%j`e(Q(E)7_xJK6#?P53D}VOt{9Zfz zsb89TuV?JjSdY!mMFq)w)+Mjj+2YXDL9qe0xR&{jS_tAdo(LKZM(U)0q11G2J;Gy= zNpwg|t$-K#LMs3V`>)L-eQg&FU>o#$!A`nT4O1`1I=n}f_-@d`6c4X9HYYTgilkKY^?kswhWS%**RJ_J+nDe-g?fM8X;Jo*L_6XzN+bVmNzW+ z|Ec{~r3T-8Tc=oJkpFBwz9a-BG!5l1n49o!Hxj1424CzcJB)fu-Q=$}H2UWS-YPa_ zsU6Kl2-UvEl%=jH)vgWBGbSuSKkkb6L~7&H=%;GE)FmMirPdGZ@Xv5^#tU-Q| zA8d+k2U>9+cwTF+Kh((w4X!|j7JQKBmFqC*Nc_843PnPQ;Ccs!w;ORH^J8uIw>~L* z)0ltrIKc4EeV4o(sV#fqGrK9f_e1F(5osAcvCi#?ohY%)yXw2KR{7(3b#R5yOHC36 zB(hMdERjSUW>rEeuy5deS9HQ=BDr%&;L={cjDCN>R8!0HuYwOIZ7fWesoVU}wavR_ zEI2S%BiwxWA!j7woz4!Vu$nRtum>nz8z0T5ET9iplGjLB5mV;tR^_%b48;Cg_3nsV z{^aJLk@mExbm9kgS@?|jhE+}J2>DI2G=qke6Fbc-%$g*;O126uel*CSo$Y%b z;fZ%1t4Mw9*@Z$B37B)(P#oFw`f?gQ^taId8^06QgH8GD=geNl6@DY4>)2Iap+#lAy5YTC>loVA(Lf*cCjfaU1tEu~V%M#OcH?n)FsH_Z-NQde^K`wXZsYCIzK` z8BAV?p7Q4Gy9kB`T`}^TGL7kkT*Io20&@vC7afT*x?b$+Hj#*{-{f;rC%>}Ca zPl>v?Dg{X(>+m$*-?7>(?1R>&tb)%Yxw2zQBh%IJj9xOx-@P`fAX#k~xLUc%drn;37|6>`rXgIR2PjAd8o=Auvbc=&ib*jhAQ4Ug_tEq>8IPQx zCj=6#o}wugL>0`)H|>jQGzN*vY=UW_g84&6UtT)z?2tbk`tYpeo^$eM1!xT{Y>3g{ zjjMypAIe{uS75hqSo*eUBq#9RqKDu6K2=pEj7WFIeYvjI;D3gk`l7*i{+%ZGdN_2$ zW>)Z?xGqRI!Jhebx++j+$+=FQ&0s<<-4L>O9NqAPDHq*neGSZA36vS$XcST?RtL!2 zcQn{5F_R#qvbQF#Yhd=Mn^j+uVPXzJ9X(Zb#PG?6ge|(X`DFzcCroYEtiNS0KuO-1 znjQdYR*#SxAn_wj2(^yAl&nq9{YCx+TQ0dY?u9-zz4a8CEG9;R*G5&QSNu!pj#c=E z))Y%>Wfo|?^za3luSaAxpU({i7JfMU=KN7+69iRE$$H?lUhc;19ta)JnOV5AJSRQu;Bm^lo zV=QZ{N$gC!W~1Frf&T7Ko&?{kAGpm)_eyBBD_Zgy(|FTaR(XwRD^(i zv!#Xlz8Ey!;frqt21B6b*U8+RXu)#xSU~!)=Dq^{1RlU z?OS(S?ZeKz*FkJmuJ9HS`5ng8YCDJD!H6AbXWh!L1n{nm`Ay!%tan^hve`hqw4l?T zo$2N2>ET`_q^@EZqZ2|{rR;g(v`f5Z`3im>Aa2^Xd#vkFuI-Jy{Lx7gY8sC(F_qCV z4@9pT0<4hO$|Jm9ekE67co|w{El|5!>_=nDi6(;NT5}N5b>unoMtI6wH^4D13)4b@OsJgGcuU7iguLXqFheFJpB11F|aK2)_Y^u+RN9_}Z9*sIEj!r1KQg{q_@ms)kB`IVq;Vvod+zu)uJk+(%m1`Dfq7X8wCeqIt_;qeeHs`VDhlkFIMm44@2{v4Z_{oFaz|}FdJ&Y; z`3mtX26S{Ar<@KSTVrS6HW9y-y9=)HVE%;mi#eFKk<1L+{YsvWZ(>$;^6R)Abro)b zm4r@5QOrkS-{1N*aEeg_ghb?YDXOb1A6@YQpS&|XRxNvv7pxxaS5II5NvTMm+R zAYHTs_5oODm>GbAWvQZym9t9GKe)9OZCWu0+GU+2k^F#zYJQ2YnF)JH3DYpe7x57; zzJ!YmzjHZ{PCKDWvcrr;tpTNb5hXCWR-Vjvn8=7@a z%0 zJRXEqlV*Z!v{=se%7&md)Hz&TSavC1A}ixV66>Bq!N zItA2C&)K;|oM}e~zlu?Bql< zwo><_4@A(~23iWewx_nf+KkCbr`H&sX)o2`;Y&eEF8mIr(!339)d{eTl^U zKlFN<+Mw~hE^kWmo2FDrR%nQjvH-p08KAryvlcJRyF*JP4Ju24B?$kOPU?-vme&IO zYl_kxyRSV8=sbigs;lJ2ly7c6((Vr3V$aW$FYJ9+Zr(PTy`jf8)%Qv*SM+(*!YcZiOeU10(t(zHpQ|Osp&J?A=*`r$g z@Vc9MWhX;<7v%Tcab?i_xxfkh{$z%UJQt9Y>q$XcE!-Ebumr1QWK)I&j?5{%M5pV( zSgD<4Y^+DxRikw&rkAB~@Zv5>Syc4Y`Ub+wIY(SDsW^k&5wkg|+`A>l)!)||re@hB=0@?mx99eX2D8m*F@qfg@+rnOIL znZCF#b^hQVe#z_F^SDdu#qOJXv;hQWTCO;jR?XbuZ{*NwVHp^@>+W-3wJ&wgY%=_a z0Nt*Xm>W2{7$HizV1@h8#9nVu&;j{nhN&b``mv>yhOnxM%gc#qdPS#RwP5iF2_#K# zRl3PooH;z}85tFw_xu%qa!tqBqJ!#1%{o3!Y+JB$-V71!WDdCUnNGm8 zGN%BfMh*L^DSw!-5Ic>8+?u4ua1|^9GhW_G8=$UaZ1K-Hwl|#?vg?lz8C~?yn({U6 z*j`cCfniFZ>A}+0JdTM@b9qbDg*f4wv`hq2?W zM1+jp75gi?qNg(Gg1y{V?0xwXjl1HKGTTE%t!{RzrS4BqKTAJ5eahu2xvr#F1UwZD z0j_L3IviIVnl>q{AYm zJCm{GD%ZC+3>-aJ2PNf&sh7Wdd4Pe|f=5B;{1u=bKo}n;Ys)#t1k@}=yiqVJcu98w z!vWYC*)Ft7OsF1JtI*;|d68rQ>RaiLB#Ll=-Wb`b^CUi`z2z9g95Ly?f$0-IlL99Z z<8eSj=2Ez0Xj+i=!@~xnV$-95As4PVNY;QWeg`*uq@w`oly5*?Dm$qWurIaa&8i+* zwHJi+JQ7LKP{0e6w6sCohPj^+Ls6z$fj5E;)T-|K=@=quC89tAz4w>qsdw=OvrryT zs2V^dp@P0R*;%}ei@1Q!qU1yo^(VYVkWYy^j)YwOQJHm#sE$QKL^a*~sDRV2+k+S7 z6PCOCycz7#L0qn4+9HDTXFA87+gu8z`F*E13+d4_=Pq&wXRksm63|u1`To%qfb@H( z2nmvI1}ncC?=d5evc+NRC#nt?)(|5TMtN1-2%Vo>S8roQ2aFk5Kqp?%Z(ZEHp{OLE zY}$;k_Sxn3(%HprlJ9m@Ga*dwmPW5{?E z!}NuXZ1I2GLin%N%73lPzs|tF&cJ{58Gz?H{;6XD|34=`_&>FF`iK&~6m+rOU&;I; zm_8?%{S4=h0_JrWfa$FbMtV1ImKSlyI=EA>K&M-$0=^d(w!$-9iY%WeM~m@y`k?X* zoKR5Y?UO@ed}vxcEK_*$6Boif0T~J#Sk;lUeD36UF!hQ048ZXjpIM8!0Bg9bY)&CP zG*jUzVecgLiTi{d5%Q6}h^fAbIbH=Tk{mJes^&UQK1^<$B<{zCr)i>%gOo@}C7u;8 z8V~0eB50r;vALWVu{o6CybG$|mna)@W$NwPbZ9HtKY3jKq@0^Er=SJpY4WF_UJsWp zR^g!ak3ZBW;6X0KZty$I;ts{91R9=&TUzq*Mk=)x;50eBeXwDE#$@lHN8SJUiv4aS zP5NNrAn58r2>k79@`x+uiixqw0zfte zj|wjdLV}V@2AC-`34MlS6oZp4PKOa%LDzjvoq))i;Jl~|C?kHoonAaCcm3pbF)#%- z>l?cQHO?s*d2Z}KFl#!I6VRU`Crp@3?S`Tu{;ww-(|MD7vUt51oElUZ20`+L#rpsD zRe8EnY1pZmRXh<)YFOJK=mD*W#BUy>0tyeXL*_(}kHha+-*YaFr}xh(JmI$UC0pEV= zV)sA2ZavPOAvV^+YBS7({o~|eIJvJVkyQ>6`#nwiy8|b_@Quz-*Y#qKmGVeN1 zbsXF!3$`rXt)Y|w$k^kZ!O6Lc( zR`~Zhg%5uWQl%c)f;oj3(|2f6TU-U?Y~mKkv%o1IAA_cTV;FL3LDHRsJz5GvgfqN% z)L9n2k9(Rc`~c5vr*?92K(I8YaI~&+NCD^h{1bHAoWgbrYUz$2NkjGZtfc{#n7 zFr(+SZgyZ!VV2oEpeXKQMhroZIADoSFP=}$BUKF&5S9kBe6eKfoPyiTNx_p@Rv&GU z-En}T-^;s3l%>=VIT5A%pzM%u=llQqJ^+&;B_gK0+>ZI2chF9Ho4bkG3;U2bLvSr? zbitIIg7Vd+y`!@ZKmNFka65Xa?Q8@h=r%}WaseHk+|r!-_s_MsO|3o7=3}(mvhz~; z=D+=M4SU%B6B$<+_VuGjdrKgyWEs>d*PP++`J0$%^BgCs{T|=?VN^9tM|n=+u3V)b z;l}sf34n_Q;(`j-Jm^zVR!;hW;4i*y z$TTd}o8g-Tx60SdB+R}fRuP9Yx9_R{yO_IJQitHXIS(g*`Xvr}wDDYK*@1iqHu>Zgj44g1H}TrIJc(96$qZMViJpDDX}AeL zSPLu9Fn#*Qn7wk!R1Q}(!aOxh1<4q190Z78uDGlVnkEjx*+4xir%Vv3QoLSnpE_o# zKLI^=!5-NgO^SGOzwe5|-hluyWgVE_t#HG4G?h3-dspV1%L)mVVLqZ0D7%$Jj z`JcIzd>PwqJPUh6{`|v!;xHCCr?7?nHxwz@7@{&ffsH8aN@IkF(QI~N|-Ve;~Et`q(X z7r7XMrBi=!UzeN(Opz9HwvVD*;fLBn<`h`m?|Or<)Cd%V2eaHb4~`FT`%$YfCxDRi ztDJ<_(jd7B&FT9~ZA}a`Sv?^fL#ar59eb5YJx7&c(P(`$I7@rI|AnFVLkWg)-+V9e zFYQw64omlJy;yJE5t8w_>Z`T+mqK>m27{H37R5=on!62?&+i4)OU8$oYS0KQ+gmq{6X&sJ z6R>4h!YmH>nt<@X;P&w=r^4Zbd^f*oI-SBMgI(~Hp_IRPLg3% zQgQqg2Rq7K0o8k$$kZ-lFl#c$uRlvp`ED{ILY4J0n;+`!1Xum+)D%)t-Uqv7V6SAU zz~m6`QDml!6RdI6LIrNE5S?ln%+`7-$mP}CUmdvkDl}p)io^1QsF^NF?n8o)MfI$= z5b>K=JF#kQcy`h2%f%(srMmMAU54XxO=B|?J@O_4pL1u7CRXE$jKkon9t2q|{o;oAMiA7~ReP7>eqEq`b_aLo!_D_e3Zx2>@1);ohy!M3o! zQvYRdqTHqHU2H`)-?BmiifoM@p7Gk1w|O|M7b4Bl89o9P%!?ap zt%!@Y9B`K#nOtD$)#|1vUxAz!rTF~dWRb~KY~B7T=r0$ezi(Mbm7Mr?C%7zG4_vnP zn=QDk{(9G(b(>~@ei0f0bw?#65-Z#=MtQQ*Y9K6GYIUMLnP9HX%_u5Y@iENFx4>5< zOxOQ+y&TE~9_vrg$Fi4DQ|`6Q=id?}C%NBy-vqaXyk`eNqqiyF4+B-|2Y+vQFW6ho z8t=Nsnx)MfoD6bGw0p#yJbp_4oGj1vx^=m&JR8eOV5h)r5!ib?CYjh~Q@^`c9sc&$ zPN%jIQU8~ESs?JW$UuhC1r*%v!uXN{2QMr%ld!+0Wf>RT+}W%EeF?%+OfF4~o6d~T z9v90ZhH>?g#@UT%kdsxtS?`&P1%JIUEEul}EK20PzLh_Mbec+m1^(XaH&6AW=C#sl z=gs!D9^0KxZhxPivgQ4%{+uAH14ENlkL1txuI7)iaL2r-1D5)IB$cYZW1l{MBCQ2y zBu%5PahBf(Ek5-f+jHe@ZE#5(aJjU6CUqWQ4q`}ENZ)^Fcl?bDAKw_dU`#BAr7&!6bV`|^nplI+w`*h$XJZ`a@6VghvvpV#A<@09=(E+keU4>cR zFPf~>C3?TOOytTe;14KV0TTWr!tzyf3PV|WoPh*x61$@p2P~pXI6vGqq;5{Z$um_U zZ1f*Mh)NPF0ZTts0ZHS)Ovh}Kjs{0c=SZTWaL3m|w#+8Jk|+0JDZ9ul4$u29z)t+v zJy!nTyUx_B&<`W~B;|h?*(y1kHa*K#fQ19&GkdK^foZq|{$)qW08r9*;QuX5nNw(( zg}Za$@fyB#9u*u+p^0z zg7HuX>4aP~!$vrtxw-7`IZ5WA-f@?Dt76oGOGK5trQ?_86rf0Az;xgQ>azMDA5IzV zmsL$bB5P?7?$nY_gTL!b2mrMms6%t;Z`a?VD%73-8k80A%wA>>TnZWCE5l0X6zEKE zY%|WIjo>=$uiRzQWGV|&Onn>t`@yNTOg)U2_*T{{!7s7{p&sHRP{hC1@49Hi)yr~P zX6)h?zrlR(8TZNclt(@7&9(nNP;U0R2!XUgViAZ;_G1lU8U!(Gs%iDn!3p_NzkfK* z|05dy_pdn_730KX_yHr7T0dcJ6f(^fTF}s5PZ;y;uEa)%FIomtVs+-~4U7mKwrcM6(5+(ubYBu{=qrQ!K zczg12%hYMDRo!CGd11lbo0(T4UvW5-vamv_!JYVrb>ANmrnA0Mmd5tzBjQ;!%9@y* z>Zd;%>K<0;+;As;8T+FZ)WZQ)r!*pV-$NaA$87_q)pKzD_+Ge)Tz#~GgH%ac6pssLb>K`ym#26yz zYnj=pt&i8UuX3k!Bn`|DaK$}fijH&hVR@3g4$ojkwxM01t_H3fx2~BxQiK92au{FB zABo3SlR&htEOP-I$AT~L(4VO1FT{?~e*%aTv2QUz4#hn_XpW0}gexJTq%{ zKM4M>08cEOS;dulgJ-wKGvEa-6PN!#eg3f0XZ1ezD(GtxJ!%+eh(Vbx$M*jbQ!6ms z0~^DD2Rj)!Qi)S*1@pPkW(mo@6s33(+siK$4v;|_X;x8sA?_nuC7dyeO{un!Gj?RN zhP?{*>oBG_-oZKwJlDW6tF4%6!JZjM!!jD=P2R)YuCgu+2wh1xmnZV@?&3{H^}#^; z+2e%2!2BTm8|)2z^EA6uIH$0lt5)}SNzDD<>WCEC9)NK^*pknkQAeZlzGJXZoBhAP z4Y7ka<6@tb4a9*{ZJ8e53wTdQbOxz=K7}#ak*7WXxL_?aiT#IKd2ZUVYTKe|Jxu@5 zu|3UKtnG{^3uhL88q5mp2VcwjpR!IiMp;`b=Epr|nlyAzfq-Ums}%7~Kl@VNjUTws zF$=x#55UK<`9OC1a*BcTW#nlf=mx1ecE3j(-*Gaqk2duC-=gPt{UnGb!5=huOu-rW zfnxW68*zh}9ovByr#$W(H!E!8J_w2@{?3Fx7MuUufcw9HXf;OpHlrK=_t={GLlYD$ z|1Gxe|9jLu^or{Z&V0e2tv}S#S!Om4-vmhoK0^G<#Ts%3nzowuxO5mL^yv@%p2)GD z(Or#^zRjd07RgvsP{(Ns?HtBVIQZhq(tl%6;i zM!UkT*BuQ9W4Q#d=(Rv>9|2z(G*O-gc8)tmZljGZLAn3&`wBl|a(xt}fWtv%_2WJ= za6mu5M!*Sk>W|8j1#`suxUwS%*cYK$K=ag93gj-yI!_2vPt*=1nmD3#IdCr*0wKM`~2WNR0#`1f;iw5<<%G-vK>8J=c5BJ@-5J_uT*Y-RIE|!t9yZlfBno z>s{}9w+{Rc#B_FeK{{S!L963lg+9&{KNK`i0459}3`C_WBqTiT`KX~}00Q4Is=;C4 zFW5>k{GBp}sxnN&Z78UKY3!RQfdHA&yTz+T&i|n^&+LWLX(x`67kY^NiQGGFSfsc`}{%$xG-J)Fe-vy9l@uo8f4Io`_tV_o->A_0zfrNVFEA zTnn-7tBZF12*Bke^Y(sK;jGb_Rqn&L=2A5*G)vBXCB1)m z$Kk5(?aK>@BN%e0Fr$egE56*m*Iw8CH6` zyk-JyYhY>*W8TwsCvZ5`iJ*UYhh}sq=6z{Amek?kyaEjd-Imkk6Zm;}%VR#NP2oXq z2Xi0d1QzY_BgYY|_N1?Np|uYg5pP(yp-b`ipYR`!!*5&Ex+ms2z~q35nE^UJj;yIb z`@m!^{0@JkPzA+5QrhiT!2-Tvk3-6&lN122M=p?D}v}7Fm&lpk$4BN zn77JV{WrGy|8Y$6e@o2zpAuW~I%{BDFuYN zCcJu<=OlZiFT_gIGl7er_SD^jre?^yw8J@Vq___3P=?;QIplD>U2<5{?QYz7gmmO< zykswy&AflIFr;b|e-B%Yh9wl3ZOqne-uMXpYi&up~d>b!9TaN|}Mc z>RL>MIV$V`8^iTh2~|50rLF>+l2Q&Ii5SQ@_hUr1j0=|LF%+#`2l&(A9~6nHLrk(^ z9}!d{I-%CuIs)UW2!=iR4SmdZc64uJF9OeOAxvCA01M#u!Ds360$R z{%$`0T|Qd6a1 zd6+Lc6KLnxWMl1@!8JEOncYDT0aIA?HHzwKs?uNgqAz1;^+XcE?yDtkx8oCF{v1am z*d3Vyhkng<=94C}n-78K`hAA7Q|^Ztthz~@hiq5NLb~BRk-mefCk%;ZW?TR8Yko9X z_un8s+jyk10UhYaD65nFefAP}?0TIW`Q5Yy4Q_-NxY=_WH?y<(ys%#}QO~Rt`gn=l zvSk)Ae(*X8K@B_phEM-e-hMY;{4PvX;(3Lk<@LQ&S{~6_X(I_dxYKtGwN`p6{3&2F z$Sd5y#=U{A`Hi8-uf|Z7^Hu;cU8+vL6c3p3sbCEYL5Cq9W^pfrBcDAs>T&v672>RjO)H8kd5wK-jfWqbmg#00Ts{(wyn&ml$aQiCBH-~gg~-h0(EXcp(>T~( zTh4+KT-9G;yKLT{BV!06LW^k(lYA3ume6e0k|cFciwZ1>q{g? znJx+YfV_efptVz;cx+{_Z7naxrDA%gf-P1F)2hMt%%@MGi&m%_q_Cg3l-OQ9OT<

    o|>r42BO)ez+B2#&|CjAX_BnmKd0JD!Y$wgH=Z4xN1 zoXLZJeZ?N}O-;Cj`en|}{Z%h|no%fO?_HB)QCF1z4f==heL0~Fh-lX9^CSR+e-az` zd#4n*z&Ar9GrB?aIzu1{0!k86j^W1&rEul#f9CLAm@`J<9%q2(>(q=%0-kq#uVS8J zF4!kY7($*aa)d>Ywc{j?EoHaZA<7a?hLxJh-Z;`-n>_VFR>Nud6uWF9Cwm@<`G<1k zitrC@m(n1US;sN8d5w#R(1J@nP=w|?SwImuzu1ZYz^F3Fk`am(ts=!j|ImtYao<@n zE%zu>ob#|qwbia{!mMbG+n6NP-Z2#TNBThgYR;e(VUFV%Y7QR`;J6RyjKOAxC9d*Oy%{3inkL53y#K%%p zL>~TXf8-zkhoouAAeoK1k0^4H0@0xikc=%>98cep0WxDTh$$s7*+fFT$p}~uNtFu7 z!T2ToOYKOS>!iB7@Ea-`xRsQll4BhK@v_JQyWFO<@5VTnkPIWa)J)w}$jjNX!@f8hOeQWFqbKyIk6|TkuxGJ^29CA0Z z!n56)%Mr2oV<^t?Zzr&aFM=BysfY%fK@=f;W!Q35F;Y5|vR1S?yN^QVIgLaf99(nF zPj-zAr1+3@;B{6PARP#U48C1QB`LIEO0fGEzIjHpPvm;JY#bYE@?<$Rz&Lhy|CZmO zNQIL7zsv`I&IGEICzRm~6Tg^`5XggS0r~MA(Cs9*aTsaYcfWi^mwgtDdoU_Lgv6CU zoI8uP zM$6JyseVam)@60L8ak-uTbu9Ct|piSCIp`E0~7St8E_os9ez3drPrP*A_6d~^QJ_@ zIcDDu9Df*7-2vEG!kFHf$Q9Ux$WTmIoyl{aQ}_JMro0D!m;s*wgE z3coLZFy())1Epfc3kX!2Nt@soR(#_aFFHLm0+Je|@aVebv{4dMw~hd`-H*Xp|3!3o23^3xan~vD2KcnU@gtHL|wo?Q(mF$O$jp z9f*j{k#Lx^kS;`?{@Nkcf;7tGxf3 zV(E=Z$++^V2B%umoAWvzQUs^zs;<7|p#R3e?)nOMfTm(b~W<1VPx$qSt+QruR`; z>o`LW9lfsRtv*VQ7<8n2$s(baq>ru+>Y1R85=!CiP-?O5i{;2RW%LZ6TT{`2EMzA# zOH5o1{4ywX52L3SOqYI?y|dMow7b${=Zmt1S8WG`$2LscnDlfKUk-ScW`xqo&Az?2 zIVs#~&i0UYo(;&3T*;rx^K`eLc>^PzzkN3ln{W$`(74%nv2g{@DAt93pvLURnp<+k zD`ZZt`!&=&6V#iB3zvcH#zl(736Q7&h1;Z_*7yr<7q4LELROwyct%9C!i^ak*}kO; za}*A-}u>P0NnWS$O7<$hve1(Bxj9h&L5f`6%qe?W7q2XA7Ocjy$eLZo$*9-lD7Tc@Mvn*RK*i; zh_3H8#ByW|WkPSf0)I1cI$Zy8-`tl+U-Hd7eznhg%A%sWI;$v3AhlNnAf;`BF;r4_ z|4!()YefrmM34XZt2!M!z2K5$h?k+HtqH)ArO1-bQTbrjdWdS|k^Of*;ZNs~z?3{DvH{eTx^QTV&>p}e)9GCvyFvWjKI`irK`yAy9Q@H;{6|Wc zht3}{rI+3y;6f1k1OCMkt+WV}MHEaJ7Yjo}1D>=*hkrd1mpa8AU}95pC^@(GT3gs^ zw!V*IW z7kSGI;t=;naPRV7+}V>p64cBLOlJ_%9)G%UY3lsl&s}h=P;wDKNV^Un8qixY;?p;I zaq=cGXqDklxTdy5@D~S=cz)xthOOg&F+PXgPNgsw4N@udHYz@RSw)z}n}Zj6Hup=u zb$oGjS~}Rveim_BM`g}k&wVBv>NM&sZ>m9cg^SHIZpyE`xnt#ZWuL`ndxPc8gZAhw zJo4yx@Q#W-t5_E<&^bj!lXMDfV(pz~@Q4kZ<7i1mJEgsbM23b9BKFV3fT8w1}ESqjxVI zaf8CuSG8V=?zCqL$5^8?`dKzQU%rsM@ZyVaH~Wu zqZm$-QZKcBC|dkN{s@0hEGwe{QkWO;8U8_&A9*Gk?g&~KT=n%Hb?UJr#4m7ZCM5%d zuOyDY^t20sS5*rfK%?g6oPHiOuydQs{sMCyUztfgG%TNTlW@P?({rC`hpl7N zD6^EhYkbGg@Bc$W`qRDEvl2TbWTHP3ac3~3+i8z?zBJSD}k!5{>S6xE^K$5O6Wo8xS8lv$2>#Kvm!nO4dFpS)DKa3*XHp9 z0}<%~VFk@Z#N^FDoXQ$R{k(aILG|x`2en{65dFBh1AT({{fw_ViYzeM)NaVn7)ZK6 z2m&+&P}zkn1~&T02M1@8D}`opx|RMJgJXk|knfqp!#ngJcVgbH8&Agb(M_pZmfpjQ zO6{9xJ+49?XX_4Wr432m#)R3fZdnpZM6~_f&9GS z8cg1)Mq+9i&jN_OB~?aes|JH+Bp5oD8j>n7x~y@5$_1tIg;*wVh)-`_tMaA z7VrYE%jN2$4`$~3>SvfIMwB0IlXvWvuQ6%O&Az@y$Jt;dhFS~_L=piTh;cA~5#0T& z%PC4-Gvy7~6J=O*U#JJ}WDc+%A3UTu(m@tkwW&CtMlCwU79Dgd`9mB; zVkRg@=@>vcqSWvvPcV*VLKB3ei;$RbbPLB*!65i5q!%n~1VzfrnFs_jb3at$%Kt5a z*r;PF0p{f(I*^ojZ6L#)ja^sewLf~ILE)*R!t3mHfjPcqvDV!mucY2^qnAz8+kPI( zNjKSc=(MPQ*k7UjZn@z;#sUP3#6_F(Sw_e|YDjU%601Q5|zFhr>Us0H!-*S4UxuXY%i z4k{obe|sYe&ruSivj8*o?C2#hpj>DbqU<@*(B`JV25>W5T7>eyy+O)N!oW!+^V!*b z`9oJIwgADs1&{)*iKZ$go@se7hXcrvUw^{qlga?<>x*+SamKmr#HADa(Qn`&%(sTk z`HV&i4|s|*wZ6T9QjE@xyON4Idd^cZCR>eKI+KLywCQdQV8NLS|Ip7DcTnrMmA ziT6HqaC;|{P6<if1sKsPnPvYfFlF7T%ZZ0blodSfC7dg|Q7_ z)qna>l;<^5YmOJ9OZOvWLkKA~dnkJl8;BV=l&d`q*cn5j*pCt==^_bGgl>ARNoPQ) zAE&m1p_Ik{WCDo9@B{_9j%V=RttN!Xk~ZPg*4ooar}!Tx6QSkovk)m@#}$aT$leS0 z%tt{~KW_|WssMLpVfO#=d^X~neVftkr49dy^Uda~0gL`RO@VlTF&*T%Ff5(l-f%DU zjAwtm0dy9f_Q#elTA*?TM;EFK3jbBJ7ta%0nCJQ%)LB?r=v@}>o2~fz^Wh~)MNSnT zq?b@)v!^ev*gGu}7bV;1+PBSKZW-)6xr!|nGG56SC!@G&U1A2pu-qQ`qKY}B_JE%; zipYW@X54Fdi^+14F`zfjOS(KrZi4Rrrcb~gFKTY@g8(b#6raL9;0OG>xyh62L5LcU zb7;ofcjDwvi>Q23Hx5Uj+Rwl=YwZKx-*n%L!&>Ws`4rMR!6LNoSfFSJSi?@`UhR1Y zeX#&!m`o<${MisTzb!hGM(-DPU3u4;!29tnWxOA{<47HJ% zE_PWU(imzP$gWt5*TK3F@y@%|0t0fYLi5i*mq#U8lrjg_x#S=vK_C;YZ{$=(-_n8~ zU7%qC_{XIb;GQOAqmXk@6NnT>^~qv=0KjwR0%}eTrUfiB#?JwGf#WHII+kNLKqa6K z5lw;r_4Db)xL?`^ zJPe+0wWM@U0MJ;-O^Oq|jhe{K27e48Mq zJqCLU^Bv&^%9k;e4C`C{{uS5`M39M?@mG5i{(GL?$L}t2={I=*C<6`G62&i>w2U7Z zb|(o8tw_uWeiWi+y6*)LKCTLhiX_$hQo8r!wP(+@=h6R`U^h#tUZ;vO0)vR>pym?* z-;+B*QR<#d_oLr$yWPv|?l*Cie;1+~Z@Z}B z#judjd`{8EIzu>l<_}cms?(0pF=2^=YUHvG=7;d=X81lXfe3^)PAIeTE8=de8@P;-ZHPFt}2kBlGakvI3d;CaL?&#MVgriEc%E*~o z6c+B-4Tj8Z;S;k>%mg9rImPl#E^S&@Q0haMC+sUgMdXm5JJD@K4!f~s@tY4jhGjD< zbA7q^fcI>z7xlW-03}~Mf3lTa<_P&h^@6eo`?lP_+d}Nm>>VNxL{KrKJ5M_UX!;$6 zZ+>m$q_ZGc8p+ccmOrvrYE+jYCG0_Hm;nL?EP?>M_ksA&dlmTW$Y6xIfY=gbSg(W7 zvaY~m3z(=z*1It7#?e%;v)TgJ}uAX(noWA`^_~Fpk{<40P#d{HY zDN)QpIje?Bw>Zg=b{JFEV%-IvaN~=vb1=Et zQ}%eq%wO*aw^v#YqNh_BwjMfH^cad6K<)ve z7{IUKI17Ub3P9>V!W2-i@@fcIjoN{*DCLU-2QY;gBS>W5(_e0AN`UOH`jCwtUC;R3 zlEV7JXm*g!5Ls=!;MG{~=|uy5rv08_q=Py{?pAu@gvJ+Z7%#(xt$R%o=39D4r9L}M z5embQI{+)B_3PA=8bRV#iAy;JrBWCo8$rnT)oY|}Gz=L%sK+6=!{=b-6e{#S9sOv8Q`jiPwz6GBjN6=%2Lj5E8Q;A_`Q3@4qR z`1E`&ZtXpgg0&-Lfh4OXtJqbj-LwM~&@W-!5zancOYCM$VrH^pc8g6q0I5l@JbAlne<#)PCp^8hSZAxc=V2StS8*G4d`x- zhm(Sx+)aM_fvn5cZA6~7yX7;ECC{i-6|zU-`Sg0`OCiBRc%*3sh3)M`b7=0z#u82r z-2{VmeJSA~uwjX&yPVHq2DTIdsVTf@%W=MJ^T9n(&*|Bg0-bbqPcNZOS;8lR0u@Ab zXD&^H(>&c_OE-}X_5VOSDvP)be)FKYU$97J3?))Ap<_=|gD77Pa-2m0AQVNylR74k z-USOIb_~_JostqJu4ZM(0`)gIs9R*0AiNCm{zJ|-c zJ6qDZjsUd*ZiEao79)NE(J7`8x-W*2Cpy0H`x{sLSMJ-K{@7=_2%^QFZMgONc9q0v zt=!i;^74o|^J=Al$WDm&y1UdqD7~s(8U(vD%F}Ei@&l>g$B8D(K~>fz*Mh}KmnXC# z$u`r&k6@h;`!((4iAr}+BR?oymu>t*1uQxKw|e;RV?~n{?Tz@sPjoe>5ed3bT)nPc z3Ee%oHfI=qz$NySECOnC-4c_Di*dnM$58JV6t!oKkj?4G&V9B(B5!dq1GQ%Lou(Fo z4J8y-U+SeDoVZ77laZ^Y%Qb0vZuFZW=cA_&CkJxJP*Ee3GyV~)x6SHbX7zGlhHA4D zwE%w=T7qmkS)bVWnBC4I2IZQ?f}N+Dvuje7&^Tu2_Nr$sJ%e3`86}rK_gwqpnV4aX z5I|n0248m+!(bva``zln~_ix@i`Yhs!zn+lIz5;+!AB7m|OBi`fp=6 z3K8HKzsp3T6b8poCCmXe9vLaMbq96qBL@6QI1&OX&u`O8fC!-tlkc4UXy)8;HD7fb zcF4g8m^H~tc-Cbo)~ZOHjZ6fr0rDEsoYvcTtF0VMtj?0G7I}xZdePusb9S(>Dh}^m`(XvC#!4q8I6FfH(Q2fEq1m^IPG1NVO2)GG@Kc4_Q{LVB9b3N`M-@FL+z`|5Y}pD!Cj$9e>0|QM6?fX1rV~c)@7tZ zCVD7({Mz|3zEAn2R^z$Dm}A1Hf7DX@`<7K-={v6^_KL*?{_wI3ziUj{mB=RoHPiG> z<3EVJhvB#(+nsQ^MSU3Z4A+OOLdbBnC1N~=8gUp5`|dG`wq|r`d5tcmdu0fJ`N!=B z>c4MiCT@_{%SQvW^AFl#zHfJHLel2b#1E+XvNc?h3W6jRq8*MO3+M2B`FTJMh%|~H zLp6Y*^xd~I^2;%PNSN#ZXN@KX43H1|xlhg|HRz z2yh~vXzFz0@+R`!<}zL#a3qGKrcCKzTp2@!Q!|uAM=1?LZ4m$`p3;N>A?^cKztI0E zJ^(X&m+U6af&y)sERN#6Ex2@3xr|vX^4#=~TOM~&AGa)$mZ;2Ge0Te0O;61{8@ZBu zO`tsOy+i{WNp^FyrppP^c+!D{O-hR#Him6J4I$(gq{)5-?wDR&FYblA#~yO8XFS)= zxX0DD>}^k1<4C))OdReSKX8LKLtyIq3>R{z)-Wxr9X2Lwv-m*Jc)2F(zwzq-X1sc# zr_$B1Qdjhm^5Q9__Spn4*ruQcrg3JD?66QftyY|?S*$c?xaL;-gF~0ug!(R~@kvt% z9n8^Fm{VpKVCT~koM zOdN{-Ta)Pre`hd9^}d_dg84GhU2y0{?P^c#z#p~b5x;L)Io{G{LM`Wy>;Vn?ojn)| z9@pM6ES^xb{iDkYi@&#+$0saiS`)B6s6T9a@B5}_&WPJ2`t5{Y|B;o3mQchCiu`zU za{Xe^M8RB=$)5Q~9WBCt*U^#df6DMp*5qf0t~on+CB5ft9B?e>wA4IW zq}Dbau*w#yzFvZS8g9qb5h$nFUHr){F{U`->dzL=TqYjdLp1BgLG*!Fz21Q5hoJ#c zeh@%|@reP7HgAzRaF^$EIzx&B|LkK>wDC6?-Old&oD89V6!1k}S>OO7Wzc|vx02X^ z@ga<(i-i@EVZpC(7mVm*`R01f!6L08!ASG8e ziaQhuCdXT);(^iM)JKrZ$Gw*6THtx$R+L0HW6)H3kZH+2EeVGMD6W znM>!uCYS8G;OML?V?N*?eeo70T{f_j{0mUNrJ2H`<`b>#$aVG zSb%k>e-`OTw`nLxN+Zv%#?$dB8qsH>k9Fc&RfC~{E1CTWH548yncSD$KeAj&ljMj;wpf5n?A510yGAQ<2AmPMf&5JXrdtaL;|S#fSOk@45k(+lH7lW4i6<{7e1PsX z5Tp3b7$lk=%E6CoQpybrjKBbYbQEcm?dM@^BC8qMxEQbdWr~E7m!lmXJ9jfhFSv{1HNq(W|!Axn` zrF#>%&U#ZTNNWH~d{=IF!Dn)n?b=HERV!GABgb87gRm#+7W z0}I>Ku2^^%wImCRCi_JP6j;1Gk-fRt%>KTHp^THf`K8QosrBGg^2Z~WR$I=z+b(}# zDBS6o?5VOQ3f|<;S6O!Tibob3rwO-HltOwbJx`IFGlRmFM};^mA9^O4TF4n9k4D zHiH;K1(rhf4EHz}Vu{v*s9orxVKK+{I^`wQv+KpoWaJH$a`@cJ>(hB#njYy66ru%#7$Ul>ayr}xoOQ>#k1!ckwnY=5P4&UD$dS5=OA+ZhU)5H>m8-%!C_Z|8oq z^z`d0Cz^efgBEVMc1ror4fB6)TKq54b>$~F!(N~k9qPc0NCBT!F&=jjtX~WN)c@1> z5?g2z9Y&R%+4&SRGtZf&1NDdc*6s~gxwo~z!pt`)l&U$MEUQi%kXcr!gDF1Qc;r@- zrRm}m77j1UBtlto{?BHHu4lFQQw<;QGiW~)bm~@2T-Skx&kt;|zY)&P^u4*m^2W=} z?|EduWW^^{%fdt|{Ac;<@9752XH8 zhh9XhX*b@qul}NnuPRbaFa-p;Y9|w;V_80B*s7jQ z%Eon|Buuey#j1>q>uW6UoLpgWe7?T(l&dq=peC&exr+L72aB3iO&ZPGt-)~BUOx}bkw<+*?B)vh1k1|0NSYi+%T1 zUAmE1Qp$_wL%~WI@1oixp%)Hqb?i30QGE{YG*FjNN0NPbEMeothpUbr;X3xTEKy0% zjSbBqs`c#uzFrT=2n5usaVvA{O0HC3iukFTIn}-K^E0@vTQJ zN42qw)JHp?bW3!`HIiEAVCt8vjG^{U)2>3!f06`C`%_{c5bF+wM@Il$j3SM?l5-h9 z-rfuQI9}k;$F%67%6{U&^(bZ10+}coI<`u;8aMiC*5$<0uW8AjD%Ar`UxaJ#J8yuu8ftEiCV^)#J6JHMhvStk6bc`*i=7S)vwhw zgi-O6E^GhTV*qabcuHUlN_FDBH4YNO7d?l$wNV;dS}dvxW!6JjbQcwrhsz_MSY*EZ zRvFUZ_ScRXqF-Sr!m6GGpr)D4!8j$j&ae(oE_P=kFL$RucB z5a$Az!Q;ZuznxXWOKW9R)HKd*zKex1B(T8BOy}Knb3vEJcd~3j_}%OM&wNLgTrW2y z8c3x~=s_RUn88^y#DiPBhS+hrFt;#D3nSFm)RJ-O5oyRa11~6(|Hn7$YW(9sN<#a> z3$a*V87&Aa!Se%Pxq6h=GNFN{)>7ea2s@1j!1qntiM_#q5%I)p6`)3MyC|UtSL=)z zHOsuC2nC5if*mW&r8l0_yS(;;ThyqGFSGGgQhpO3_>@;xLDi*O=dx&l1j~%Fz z$9q#2#Vm$q5o9-aQ?F|rgh!T~s-k6Hi7;$l?jMUEjc8IgMxJPReX0fH-vDNVI1YSq z?TwgM7qu)4`{!b?eoG&L&m0PUtKI02J&t0PiWk{*M;nubk<}8f71dW5H?~)4rj$=D)>VJ(AA8-ZrmVOt zPr7umiIKFWGl z9lw5$ef9-T$SFI|eADm(vk-li1FHHb6GyPe-ifoH7cLx&MkJGw^+Rs&rOZuRli` zS;|C~?BqUV!#yn`BH+(RBMbU@dRnuraMEKa)>Z^Cg}p04oe}vHluqt}S)d~1hI@~e ztiY%)m)VQ&EyM4$+@7x?Qk2o4x5`oN`H-^$x<3VF6Ka(aLf;%kHGgV61ihSmW38h-NYee@5@<ST9=fheaar9_DJfYS5|GZaD+_uad3DnPx5QH0k=aG=ZV_e}N)B43wQ0Pb^7Kd50G z;1mB|UqC|lVi%5WMEpc;T14j59|K<9b)f=hI4-1_>6^b0Kj#zSEria6>@}jaIB@|@ z9eAT-nRzu&f|TDnc~kC8K3nd1OC`H(^8U5hzwoyH>>~z=dLa2&4t!y-uEabPV#ZL# zifkp|Fo31k=RD|CFIOlt9SgKN5u=T z)?L~q(T?L;DuF=bqfsG+w*q)Y1Ht(qC-l>=0sh(ZM=o=;J6^F=1B8cplt8v?t4C`q z@8Bz%(uagIo57YK1?nHJa~F1g(CDN;E8p2@WONGimaxrprcjd89>|n&@w(c45Ia{R zVRDH21Kix^t?!kplRJ&>ErkqY_t|s(n=cGjx8+tD>4jpRkfy5v(0y_oK8rc`{) zg-ueuKvb#=LXj5Ax9&0b1;>sR~3#LO}RvxeaUq1%%K6TxvbA&-UAn0!f6Zq zeGG1$%e)w`=p#FC?GhvM@^$kAFVx;WmT=^Nt(*cT3FML7bt*7Ll-`qqH2`DUhwLEK zV5Eoh9JpYILS&Omh9M^@U%6bH)m}jJBh4LBAkVTMsWRrBuP>C&m;9(wt`5(!&c_h3 z8C7Pc9Q5eY6L}JI12b z`?8qVl>>rK@jbByq=P^xedCblv_f0#(d?{hl^M1dSG(7DKj`Z_rRN`Wo04)O_TCJO zs2OW~tj_A1h09sB-aPS12=-Fjt!sQNjfF$g13u)$W}O zx9Hug(ZIVlb@%%slR2LGAEOJA*Re7R9+Rh6QWWlGfx*$m-I z5X#@p-wq^}@I4`MHg5IHRxG8q#9u$drum$SjD7j>#oCV^cE)WAi*sx6Q|xu0cO~y# zo^gi?Fe96I#-|gf2>1SW=dJ2VqNs|04*mKw(ft20QkFj`3IesMB;JPn48bbo9fMXx z;mL;;QlXUxXWe5KMgk|?!p@`ncx>@(R!Fo{NF)sIs#yD~ zGRZ07{u7cxR7HPW(3;4*D0t7GU+w??Qk3w80SCs$rDWJVE5h1b-RnY7g5JVcq?CY#X6;8=A648F^@@%fTc4^8eRg_{ba?b3_6g>0rwx0Ott$3arrK`~GClDzYf{YK zwZ3$zzdw`zj#F9F?xNEa_6tug?EEP17uMiLtpz(vKfd46z4iTt^D1loiaxry+1>5h zBl%ED&T?Tm{@6RVLPs}?TtQz?yNsK2DXUa^aF41NjIDWw( zUcb`!mF-BkN7K%jY7A{g)SyjZ?JKXT_sP!*%j-PHk*IIq(@g$4s z$+J|uVhNdqE)TE-O;xRetv1*)<_TVgr;sxuTh5lFf%FU{q^BY zV|IE@d^ce!C!?spj@I(!RJ^Z--ec3Er=4#3zRg%k7d5+~AZMNGO}+i80>P|$caau% zx}9ySk4v-@!cRzG!@(Z7ra1;GlxaNQV{C3rj&^zc=Y_#-%4_e<66nl7*|ojE-@*pQ z+g~(pv+_rMl;Bi>t++74<-=pB-Aqt*2Ug#vM<7fhhSa=b1G5yCK(^aR*GluX^5S&QIo@Nm`!1bh z^ebh)aY|jIKX91#ei7wrxTCKsT~t9!T9vhwWm;rZXy{!mU-5AJKtc?oX2jR>X0rsv z+h7?s&?GLx)k%pFU*+u`|H(;d@HV)9!v$47MYzzoMe^Bm(}oM`_o5$rDCbcqc~N>1 zW%Xqwl+}XMCMKs3(l!p#6jJY$$!^Peb$-nqu(5l%P%81WOGH8be9qd>o9i1~yo%H% zGaRPogF=8J`vhylut9B!@B!T~)U`!xOf7PH3>tgtyq3*-yx4e@1nes zMkIqlsbM$rnK^Qi$)}Q1dQtErDL|x>!Th)l{otaIxGwdWIOf5ZdDWKt8J0e!weDGW zq_#vKsHmsPzPK>3&u7nL?Kd|Mo!reQx7a;+cA-(KDsd=JU2rrLJD2f+ZOsjn4q2lR zcY%{ilS;j;UYxAARM~rv#O;L-v%LyE7JgJ|P$;I;m&OGoSijV|<}_0uj%z$wT($ji zEI9VG_EP1*+i#jTm)tDvnI`3j8oaNuw)xRMk8RD9UUxs~(tb6Zo_NAzQ?q>_&%qSq zoOJssx*dA z`7bMuP$izf>>o-zk=ad^Ag}0@e>BG`CeZ1&ghayA{^7*^cAH8aG-Syq54*XgT}}lo zF{#%38V$yh*ptmGD9pqYX)82j@Gq!xiSK1I1jDU&4@;!(ecpL>@YxATtZ|urJ(6ws z;?##*X!WFUHBEe?4xZm_I(09~bM2y>CzC&<#7bgK_Iw0%>ZF=yXg^2PG^g_Ao=!Vg z3AG04Wy*&o=!adximnEB^H(-G)m+rE=+?YY)J&Q^+p^egx**TVQv=?4lUE3~O7!$N z#@edJQT4s{x;xRjC}cQWYLHwa=-qjkvnh~qPINE&myRnH8nvp2O$t*sA#uG6^zdt* zCU4NNJN)uYQsA!HF{1_(gomFC_>=$fX5tc5=0Asw<^xab`h8x1p%y5fg{-nXjL9dg zEJ|>a8he}YbEvM0b;GnpF7f(VRYRJy4yNxKY{Z$ouRO>pSQcPB%eePY@1v?7yFRng z0AzM(YBpR?;il#5+4v@>i|#C{Z04BVPCS#H$D^{_Y<5~F{c_(tGsmZpZ){~!tJ{*C z;}TlSFu#iFwEQn~=dUwr|F=HNzso?ni{+`eATu`!xA`4LF10hOrEbmn^2v< zf}I*A#XHB+x2r8W%yW-%!BUZBoOWMC(PMAEKrvbr#LB@)xhPxk?8vewhC5S=RAuG8Z?hSE7EPz6B- zhi3uyT;B^%!LW7kyfx@juziP7X{PzjIXVSUigZz)&7rUs=h7vKYmA+)Dyx>WTV4mP z-0wv4%U}KS+|1j#_p~G{lf&ucyo;pf58UtqQlk+=5lkr*2R3VByzv3KuQ4;l8| zWU0L~XO$^N4ZQn;4%c1r%MUXD|Go4 zGn_iF9hnZIwkh?s9^ItLgb%nG9#U6cyIxU+(T?c(RyL_2X%@LRnJ0r{BrNpTrCfP+ zH7Wgr!M$DRw^-h$k={)LHIH4uE}0@#`SaKq;8QRH&p{ad&pqcaiU6XV(k${#)4xG@&z#{{x4>%9kt|`A9#P>@)S5f( zgYmx4m3G#I4~poprLKi4pIoviO%E{18%Uo$BzU?TdfyIeEQl#=O(qx^Tt6qfxM+#= znp?Xj+oOh+c$?(J&(>v-E6Kt8YRmxvcoQj5s123CSEXNem@X7Q*LgPEPIgk3Xe{%5 z+E5bP!5?lNx>vhsGuG8zeV2J!nvb!6;|>dFOakdA9g^bRg_<6~$;N@IJpy*fq}$0F za?y|9y)?VC>ge&%(drdRSvp4pk9U-SE*6XKemTqm$^kQC_kCEQAswy$aK7a9bn2A3 zt{Q=dC3@w4I#Dn`z3O`s`yjdwOX+D~-xqnzizXF=q-{I9bY~5CbNvkRPJ-M+XWtqE z<<*#d-5_zBr|EzwPZ9uFc+PFXsxeeWH3Hyzo(Ewu$ZsFf`RSzz5EwR73Pd40Xgw9| z8*B&CFCFp2+bLy(CAa`&#x=njds1CU(`Y~Kn({H;at1;XHHHYj>|CW-)q zfG6BChH864{MmpMMuOA8g4N3{xn{f_4&6@zj`4(1ukTryam-U}@n~z4WoW z5W9`pMxV8P3}{;|$SJ*-Z3%C>|7W#x`p<*It)_rs6!XF@ z_i|LZA{bnlk&;>=FHmWaRCw}c_kCn7jw4Qb&r#;C@hPMb#nD;Zw@T#B!7oAsh+>^TsubA;LlQPd*98}D6&J?L$? zHP+;m zcozAe*IylwZ6A4HrGHF9fb8TNVML*&?7|2?=xZ|&Z%6jSQqtEjn(kXe2W;OMM!0Hw zmPdshH#3p9b*#Q@P0*7->+ufkGebMM^_4@`kKr)3Dlcqb8qBGbVn+_Gw$wF#8nL-3Ti}2DiPiBag68ka zvmb93a&{8HscqNo#(f;szeiyNBY>M_2mC}YXBx>_kNR-s5|NW(CqO>ZI8B>pQss*S zC2C#B+^5mX6tneK;z+)DqWFwlP|Z1jAg&y|HKgZD>Iy(@qH`gM6KdwH3S z&%ZLA$8ZXqxjHlBhNoAQSA-~ej6ds8cQT1JId|re&{8Pa-jme1Csd%EE}=y*i!aEY zIXs{XgRde02^ATYaO4RsN_^Pn8?Oi znAh8Cf53)#F!}A_FG+v-;*X9D_RB>Z+Kq5*s+E2;T`qxYHRvhM4FvVq#jGd&58CmJ z!q%CX%mF-P*|D!sl28wZzz5NW8T`kY+p;+4z>mkP#bA>Lz|_lBDXxCTB(ia~;9mCrmW2Z41G?l- zyXQ}OvJ1@|Z-Z4yybVIrSzzn?)KIzk1zvR|=5c@)vElg^AmROW;WX=|Y0fddY6Tcd z{~>p(NFea8$Cu+CHz;Z%cf0~*ubp(sdCW)qQ=>9VCP0}2*aA^l05D$yClSC>tAWD^ zajrfQSx;Tzi0GcM6e1xGn}cr)G3DT^39~jtPM_vWzEJvG%nI^mN z7z82!sbVlM(rLb+T7fGHi~FbX%MwS?p{eL{5-Rp<@6D(^rm8WCtM*fmloD?tbM({A zCM;4knf91e(nv{68m)(3gU;MG-ynHvxjvCX5}#aN6S4uVZ*P7gc;2#XIL**$iBV8@ zJ3jqL@r%2J2LO8hzhU*ivTt#J&@I=K!njffZ1uPPu@rU??3kJ0Vua;x-FWOs{!QIuXH|9Vy*9X72YOtWeDd z-iDh_c+>a+{a}yVfdLv%-Fd3IU7y`>%Qt$j3XXZQuy6NYGQ3noqU58*DyUX9UH4jR zsS3gD)bz#R8h-MpybD|J@^Kvh6MIS6rOlP%XP+Mh?ny0yw4m~Xi(r7M5w1XUrG(gL zHndF-FrO?=(V_spi!o(SEq?*B`A1BL{sS72foH*c_}N(Se(T!3od=jkQOnSa`BZp{ zVGV#%@FhYM$R&7&tS1%TyNPQ<4&J&0eK;jFMg8}iSW^4lgfVCWT{E&bI!NdRnpZl$ zG~QFX6|{guJ|3MVw4hhF9rC4Vi6R)*4=hNroZI|o*!`z{`TZrpa@4OsVX|x6eW59u z*c#qmB>Weu=O@GVG*kmpN}YoPK>(15k_j%XUxG2~#ygNE-bwnmQQl>M^4g{^n`&`( z1W5q|R=IX&f8*C>&h@{cJhIbhFZ(pS`PL#Fm5kRTwHV$kXR=tn$FG&|e<`IE?*}z6f8Z&jRSxW=SR;bmyNb{C9^ik`2+~ zsjnv*X8Yu52Nv5X&^i*Yn0ssfi&ZIxd7|*^CD4HQhqIl96x_PFww`9A;ibhiao~pj z+o&fV_!hxuk=lz_dBkcy^v~*zcpqxdM-bup(evnUU>A(1a+HuZ5>q=gRepIv7Z>(z zZ_iz$#6O~_^glWi)wh7(079h%eu3+3*@hs65FP3ukKs6(EPTFwW*Dm31=0S}H{9-< zM`*&87^hsF%Uf&ooHZKiiKb1_MWMrI^e(M2-pv04`|nQT_k(BI zYrr5cxWDUx`pnr^y438f-bRb5(cMB9u^1(H^P80=otd34D&t4gi|ifju246{mF_Z) z?M?QAie|Y|QK!?Hz0-;u12g6066US9>BJCa(@ZI}zHa9gtqeU01;14XLUXT-c&v5b zxZi&ebqDxw|AEtH?Fqehn>Nd_Zvw1tF>ZRvWy723-jlFB{kDkdRu7k$Ez#C~FlM8* z==DLmkhAs!TRclB*wFk1TzE{6oqDj#qWf|8O@#ptf54S(4+rh;qzD;`&xa?itl|#Vzfb8NiZ4CesQvs zZ;8;@-!sXOwMjYJ*SB9~zvGk2cZ&JHP!Su}iu=vISNx<>2X(%6snDPO-8 zVbV~SP$*&4c@#<=mE>_%H89gZ)@DP=yG;pd3n)<(itmtc8cGmaPi5MYmP}3RaU5AA z*mCOZ8K!t=m9 zQ|C?~8=*c7(+5dU^1dbbALa=CYjF7g?K9+?K)8>Mn+G+pGVq6CVIbU z0J_TSt*_=66_4^Ys*Z0%N1hwo+%@tU!SrzuNwM43JwO<>IBM*I=Y9|yH;pb0M0>=) zoU58;WCsl;m(R|n4?UW8h<{Ea-6J!@@T8M0G*=bfkLja&|3V!_>Nw~WcB|10xM>Sh zHd95`W;pmPhfz!1n%{x=2|3q6`-QKsMs)f4p5g63JTcTGZKPkrb7vA9W2-ud0_F#^ zOk|QeGtd%VP7X$?M;W`XWo>$qAMo&mhE|HXBW`Kp35&{*${qP^7e6*EiuVX*MrS>I zYPj01?wqfKQiAVM$}^Uhrjee0#t|h=>-wtCiK3Nnew>LuZYRkyJ95C{M0pLF@UKc@ zgFLk`(I; z@IUFi%(odoEDv6Rp%62Y2Hmg1j@T*s*1rOT0{aH6mtma$YEti3)s#$QMAe~S$E^OT zTdnV3^%$DvIt3XiKMT$O6Ava)0jrY2P`bws@|4fbX(`YeiR*CzC?_0i1@49JJ8kw$D+ zkhyQ>1?VY>LWQ?#b7dMGOoPrco7vW^{Fa1)_I*AgL1$r%clHFsS2V^2^TE0j(LUevi!o){h##p=|(DtOnHt5V3uQqrHeyTNs(k2FJYN*@dh?U<>X9IrCm) zT-x9riPP_Cxugh48l-F(GYc7E%rLrI9aXfEB?*eRbQWGI(~jII6kG+-aFW{+3q3*R zjPbqPiGbzu@K*yFpEELsZ3mn7rPN&axk+M({dWsl{5OkY{Lj1ouUH%O6NqVB2&-cr zu2AAkEH3gFs!Z)(m3Ain-G~+GHr*!-L|ENy4ZIAA+2Y3=m#c2=d^RF3S>vrKr`N9+ zs)Z>vJgOTfSf7{ly&>8Ag#ttW;PFKf+}sV=kvYI#;rxh%b|ZBVi^KA*A(aI%_GQ75 zBZ>v@I40ba@LrlKxcgDkzpHD=Ed59EWQD$7@PdWyta(YK3G;KZZ-Do6*x+b%(*jw{ z!M%T9VpsvxUWlv%+T)2X+=+bVr(qG&K3n7#hSoj1v>7V1=EVcX1=v?PEfk@I@R`Is zhgpMs!U!2kFvV$TuEbeH_%zTf^e_r?6 zVA(+k*EROGYca}lY=q4ovB^-m4RJBrjY3&mNex6e2%3q3wL#7E9FB!PDQi*YvJu$h z0PKbfaplxNrWjXJ4G|^~NNcL(!Fg5deyHX;7jG%Vl|&=L_W)J}Ct}>e-ocm!T;XD_ z$Iq})ZWJm=8F0e&tXbYxh5zS=R7cuiO|7$E30 z=6R~1y{@j!Meb&1KzQjfVY3}E4&1}#JaK})mFH?J2a2q147XwXr`QqeMpig# zuJpc7QIozMaGUADDO)v>U_h z#gsz4t-XjaAX!2qLD2r?!V8uemd!aATjufLGT!8!f;~w@M($qEUq%q;+flUh>ORJp+&B8!+O*FH5Ddq zH?QkI>{CG=$2|v$a)+&?e*bp}{W%HHS0)(ud!5J{z`qHKE`l;TcL1ZrntTG)m}1;( z{{9OwK%)d^k$Abd_M0FC6cdgaB+iwfAi@g0NceDaFDo_^2XEJA z>@u4D{TSVrMM>Gaur#&2*D`F1CwP&V!^32tAl(XZICBQuUu}B+CJ9y^=Sr{pL^I9! zPKHl=y{i2paIHO0w!b=VtLept=G|{^o-;YhFE4942Sf;BcYX%@z=S=^H4-w#{KOYd zJpa9g^zYDto=`r7ZXt0EZ?k(3Vra3L3LvhPXt2}t_QhpGHAl!-?fMezz-ke>(rRW= zP8V9sNlfMA33AtF8vMPj?Ym|K@(PMxn{}*@d4A!13sG%RHU-*L{pZ6VZ#tH* z8Qid`T|1<3oLL>0k8idLGj30>diS1nFvpGb_mo5Uq+l=b~@TOT_R& zlLtqWc(H%4<5L59f-p)04v}{jEt1X8Ir!hY7ULi9DV+ZangW2U0H?^6VaZ@@|?mDit_WKx^#^;Jo)qgsjb(t}-@_{gaYDuX-5 z&k!k<4-ycC&N6R}i=k3amPf2$kG~N`&gg6xi9k zT<{WAK^-5(bso`@{8_(|_bhfTS0=+K zB57UN9+Qzo(uE#{<38&khogwdvr%6_rUV}cx&sR_JMh0v7#oDGQxQ|44^$4YKw_9h z(RQzXsTpG9QW6TzLeTM}ah=Fo8K7i$)d43s_doH$py~6;u`T9j<2mRy5eZ>Cta((GV>&RUQt#OtJ?(2Qb>4QMUcr%R63&U<{4Hf8OJn zf8bB434(q_7Q>9Y;VZq_Irl|b8-JA!MsF)we1Z+riP-Erv%1|?* z`J*cv+m~YkHntXq_8fxUOWP24ujN~oSrfeDmjC!;A#uiSj``uO%|3u_0@57(kg~(g z0bZ{P}+RCI+k!*>9jpo7l|agA2v0dtUW{#6!2MVDoelE=ZM?!fp&S-13+Fn*!< z7v9t<=*`TLi@9c5tLFo>pN%hlo$LHGH?Q`Lr=fOs0eO+Hc61R5zKcl)!xLP~%WVXz zC5+Jq^o#{y-neS=JWp59`4B>n7Z3zA$CrQ+paMoBW{mvu`wvh=_IwfwxseCaUQkOP zS+!>XbtwVrVGH@;^_DvOxO+>B%H#GNAU2mJpY3vuymDOPr-McHaSyp>Yl&@OX@aAX zcI7Ot#5+V-zKwTw2m^{xS})lB0}rGC`4Zg1O9vKL8RAOoW$~om?`OeHXT{;|Xok8z zRI>>z0TsSlN&)CqV|n*)@L~6l>H3Ifp7H5-ze9zU!?@B%nXqpr#PW**T1jv-uSjVO z-04M&L06RJKkNHe6To!zDLA|41$NJjxXeVmdF82@AkBTauesqN#dY4eiNXEY(RD{| ziU*d_2~01-dk56`iWvgOG(-GMU|F_V`9J5}Kn5!dKkh!FIWDSJR2gJzVh--T^KLYfQEI5cNrE|rE;Yk z&sLaO-K^*`lGU2tz9+q*MAKSnTfFRfGneC!)vv|s-w(TV149QZ=Scxed=N~|M$Av+ z6%&U0D!gKNcGUje`~Y?d?UWq}hw(EnOlQi%Ta_hZPh zsf&ln&fWc6B|wTeW(TH>$lL0L2pfPmIW>ZG&IK~F9V{J*x77_125pBrG=rzzJfhkV znmE7D`xY@9xyi@%TG2j)o-pM4ZqPq8_(=<)}R8w|8FH2;v&yk@b5ax)0mHmx5TjBTj{m0Xq-g`_{J zr>!DCEQGbN+?UEDg}g|#2(;*czeTRFMYfV@Wth3g#0WB5Ge`^Av_SeK>KDqD`cI4= zAOmWBmQ*1z%rpts12rUCb>D-ll>T>j2>!dj|5L@uGK72{IYegO4B%$r*keHBfRmvf zLmtVUe>oVa=&MK%W+730fFFe3&xVq@1?h<5JI)25_%33uh;!AdH-c%Z%;AD%3tj-4 zN1Gc+hZo;b`&UiTyC>pbykU~-w8K)3I=P44kK}5697Z!iaqOE4PhX1qH)T#${K(6T zeM%L+T?pR+`(*%$o~i-Il_o&|8;`BKyB~b-Kg)0idD+(hMZ!#lfe?HGDZT?_AVjJZ z_J@@dMS3BK@W_1)dfN}xXpw)+Q~}|55KxarF8%o9L;iUx4V+F7n2fYBVmN+COA?VQ z11xRFP3=B4L_QLcpi1?55LS__z!g^7g&|4gIx!r_^YfiuHo!<-a`1SSH9>XMVkdhz z*!^_^RapMV4$&}u_Yq+tM*uH}2x|b5HHMo<+WPlWZs}1l(Nty~nfa>9S}(K;I+*nc z;I#`uqAa11pg^{UId<(u&jcJZ8V;Ttuy~8~7Ih+>nlJ3c%LYQ9LFO1b8<-=XWrIQ? z@MnrFaF+2@A2kZT|L17|4#+>RVF3j}1EeTW4&tmTnGhJR3})$m`kx-AiMK(0oisH@ zI;(`8z^fn#P69*f)1Q8<19HVG1gS9#!xjg`v_DvHBt|fht@zc@28W?3-QJCt%8QdN z-#)bI&xqNsDbTc2ln1RRAEk55HQD`>sLa(*Q^=*5Wr*Cs2sVx6N@yDQM_pz`&;vH@OzX}tu*Z(}l zf7~YT#Q)SW;-4P=9~1rm>FxhHweml6`!DAfmSamWg~E{yNH+jvWC55mLX1H6;KL}? zjn#4VorygndumjOtiXY_Ow~x>&F(pWxwOnX_ALfv8E`#STh<* zNG0yesnr0M1f3nh2~B?Sa**y>JSb#!_nyp5e89fE>6gJ@PHeqYAv^b2*3PEq!Zuv& zg%q>1i*iw9uh~fbpOkr;mhsfdlAhV1W*-TBoJqwUj8*apeD}JT{lTYQ-LmcXp4bge z8)|qJ40w5k!b>L{tPzlm?4Pg&=R9z3M5+`yK7@`Eyg9wGkr?S9$gFE+S=Tvc06-gJ7+C0TYZ%4$ zm}dMmA~|2JtMTxbP->9oh;#g4&+}KFa(jk7<$lJYBhy>nRK88HiwsXrj2|{W zpwg=caIt_oc!Rm8b-q~Qbci&2w?=rGGC*pP(foUOU-P@T7l*EY4ycSjPmB6=>WvVy z4KTnbSNt;&!#_jlAjAHK*1(4nJ`EGI_YXGrwAw(4HCS`rkSrX{)cFy$IFt1D5A8 z+ke(CfV1sFjgPQTtPMer(4G^qe^VR_N^m-o^QfdqIIReE_qN*fB4_&`_b4pLG-yOr z!BFlKx+U<$MvM>$7^mPOFp>3=2Yu>>McVh}Tj`iEAIv;V>@CU3ZQHAk(kWhlkt4_5 zy)zXvz6DoijVJ={eQd`Yj4)Jlfb5*vnfAK9dIQ(Ay=vdfzCKVv4HN$N=7PTg2U!Nt5~t zbpWQ!lgu>~0g`-vKcQat=nDBG%F6-fZ>3Pu?(>cIV7f3MO0B!gseOTr3bN>4)MD|< zHuEyc9v>N{Uq$#-Dk|JY1!5YvK4Me|ckC65`u@(ium{8jInM(xTVRnvO zeKPlPOaw@qM_a$Y`F*d(IGsuIhO^UipVn$!8A#GZeKz}ms>V345yHaiVGJM^VW5~- zL>5GmKLYmdG02nt3+1=+C^r(k`3o=lNbK-xI2zTYf^_^sl_DsVq!~tjKmV%&=M@nT zLK-SR7vE$k(8+bJCYEJ;3bG2d8G0P-@vMQ9MR77^>F7sVy!5@zMJ{vplInvel+Jxa zT^0Y@vAYuyR^?bjbimU(0S2_;<4W^jVSqOA3&AXf=dT>-xFQYX2bgMg1nT?^Fik>a zp4Ex*mGbif3%u|k!=j^Vca649ZY-PV%luJ}DXm}})#5FLU9&GLY%t#SVT0mN$(`N8 zBl{AL&wUi6b2jmTMz^n2pr_M8s~!}#0iepH?bxq3fN-H&61T3WAO*a;g-5)q>Frhx z`>8FL z5zEi0ve2rp1G7bCV5D~&r>hPPjP5!w|6$Xd>8Z7w*Nck7&f{R=Uw{$a1q;8@aA(*w zq<#R+JWcEeB#yW+G%t0MN#;uBviGnoS{Hh7!US~+9hR}8*uCRrf@#u#7#@`PC;b;N z4O{fSc3*1>P`Q8mGRUec*1Q7*xdvREK14yZe%4)G;ma^(Xdq@et%o)RaQ8@$f zf=-LBpXiykzB8OpAnZ05d*Jy<-r3LNV(F!qUOJY;56Db&($|;K1f`(kQ@}Fp<(%d~ z-EL{?#1VO=Y3Q>F#tXwWGy{9j!T^!qHjl&_aDcqhSCappMojuq;cXSuXnKmiy|o?F zq)tRb-PFE=(1|P4Fw=P{nZW*1*x(Ch_nw{0ZjA%NpAQ}LzYw$?ELuA+D(HBY*{fmh zCYY3VcwckWo?$>Wk$Bm*%d?hqqwYQh-f^Q_ljTzS50w{m6u;06HZhZUCT=-6@4F}+jZFo}rtkYGG83}aj0??n`yITAqAAArMwC%unz22AXa0%5=% zQ#qnjO52E(VL8|WOqFcUjbeNx?mqiDFPqk0dPySUs7yQDw8Jx?b@O@M@|GOw1gWCv z>wtBGT^J@v>|u^A82`xc@q^`w&ICKbladNpKv1tRVL) zY0MDpU9j`-87g{z^Yiu7EAXeE^a(S!}K`5_pKVH{`O9|Z-83=iiM+R*FAI;z$2 z<>Io()9@9?cv)2v^mkfEPds(kH)Nl|rcc?1RIzfKr6)gqemC-{kQzS@l=Vf-*EqHt zF9~uwXwZlgGUUs_Lk&t>c)$d@;?_>4Zs|o-xaZ)gSD4UIINv7^a<*w>X*3LLBcF;A z&sCIHS4NuIX_sTYulzLjDXCRn5C#{>Ra8DzyRC&F7Nao3^uWRc;YTv9BjZKL>Wd~@)Ei7 z%<|c~q$|++Dhn2tlB(IxRVjYvmrNAJL#8bI=-k3qZ^^eM9ckpQyy-3Ps@x@)=ghpP z2_5W!+JT?ii0ISk!yx6gJ8r`uP>b(TN&H zo;LS9n0<~HH%eVAC$IxccOD9QDargT8$Fnw)Ee*B}KXY6bT!d>Ssg>Ril zUmcyj4}5T#2GtzGc;dJQK$4$UF2&Kbr};QLJ<qPFBQyONivYMX(ElZxKGdGvS^gOL9QK zOB10}DQg8q2dM#^5&(nTfIbEGD!7+5OKW7za!xsLk1<_)DfpWj9*EXpvqIwtCL9rG z_PmNOVUES$5T39t8(kfWtQH6w_)_Y_1%{Q0c$6r_sbY;;98M$En6AMgUjUeb<+|PvE>wX+G z#bsu;c&!mYq&}M?G4DK3@SAG8%DMJU#Y$^GA8Nl%N%5gGQj@a+UPJVh+W3%@9c)~9A zxaKPbi>DOi`B&f7#n8jSpDdf=R&MHzfz_Yx13u5Ypn!W7x;GMu{f(XJxW8fNBI43V z5(79bVfO%X&elQ0YC_YIxnGl~+JJu`RqB@zz)>vgWgOsO4|bIwK1K2MmE<~Ni_Q1S zwdTri+;`qtS!i`dBUeg;t@}ak14&|e6*LN3@)SpcO8H{I2(OYCVzB+>zd=)(6||uL z3zZkKl8OoAf1}8cD^e3gsP8X$Q)wK+>>atB?{(#K<>v}Hh5-j#pXt$}?DUwj8?Kwm zwl}U5siu)znjr(5M5Q1G+BSHruBC)@KN4_trr9rM#qALl$i zM3OhxJRGf(^5J;R`FYjtQVScgTq!i$psMrAOqq5lTHtd3j)=0m#^`L+5Py41km|*=q}NIdA(4 zsP&5ls%kgc$<92mpn$uUwQ9J3NXceKjhBV-T=P6s#lB4?7SnL;VFaIM%OT)Kxlzze zOVZbp`hec3Os*Q7YIwj~-rPrG5b1=<6svA!OtxgRJlK1JoNT_89-CiV?RI*s!Esma zrn_qZYK$oWC;0%myI^KsH?Ba}{e&jD`(O_Quz}}Kp`bx8WvGQ*Nh(`ijT8n++=AL$ z30`aIUpq*^4jJ7!SaNMqrBTuGvmmRr@@OrDWRe z*i$_b|I*d{Ys2d|Pfa5Gd#)~u`h&4P>;Or8>kV4j4bHx7|r&rV+?JNX};OuR1;_Q(o4h(VVnQqL43a+f; z?;`~+^o~jDLM*0#0PGTJ9GB9w;u!i9YAcBmT3l^9^Z+LrWUgBF5i8}%rk2Su%zV=E z{-Uq1Yt09_Bxo?qY_3&$zZI=BjnX$4ZIa6(!m8TSIsl_bda%T^L3r$6yjK90FP;*L zf+!l3qsR?&4zOE)cN`FZ$gNEdPiX|^VD=HPcf=r%eC}};mb=G?(kjep7*=@`I_tnv zEPPO{$34ePjmq|{xMpY5=!frD3eT+9`dA)5BkFK7$2Gv22Lh2)(bIdu#+PjyL7xWd z>Hz25EnxLL3qPR2F(rJ5xfcW-kQz;VHK74lk`flCv&Lu9{?vtmA+O;r z-8=j3vG(NdeJLkqex9j*Pks-+2Wd&o&97#z7PuxD-{4#9UZI~MVG`4d{1DW>&EWWR z9a!2Fs-#D?CUY^r;=bXY{xRbAw;eP4h$8S&llFZBw-vs;5P`4k^D=lRb?5T@g~4VY zm4pC2*|7tp1rU4*l$0QD#ZP^Z$adf*R+)pfZd-USGD59?FFcDJfd~UX-X}JAD zJ$74e#!BN~bYS6U2*j(M<=1Yl_P5ru{;S}WNX*J<>OiPT*v1bA&S zCMp;aM4?{ON4f|vQp0MNj%ZT3p%zy99skXkFQ)yTl@(w7J%Lqg3fK|qD{+1Qjeq=g zkUOVnk+1ap>6jFLn*Jph}j27`hkOI9` zsiSB9Y?tGgeUSVVIvvOKr7~C?Q{*d&d1m$@uISjU>gTS^y=-?l?ge)bjAcfezmK4p zvGnR%&5yO`Es`D%YX+)Sl=K8y=T7M_vP0;}tyV6tnZyT428w&M^k*-y{L>5W!~;b1 z2$tg9fNFN+RauNo`M_H%R%q0I?Rww_cg!OCfURJj((a3_IRisAXFkOqrnRA942Qpg z%zXHDXz5hjNWMi7_SNL@j`tA~etsV5eG~nv4~1sixDw{bnnoIkFbbRgg~~t7I)?Wh z@nN7@g@{Dd?a`h2J4vp2ut{wceZboq#0P{C1U5z`8}{Qu?dtTKy1C{m*X;2nHdU$2 z8jqKKKa%{23~}y`*-&V~k}Cq_IQkxl(^%#LgB%9*K#dKNO9RSX|2k(MfDlzcQS9*H z%Dntb9NvOMZjQ5o@*cl^2wkGIo0kZ=;gsrC2|#WXufEyS`*DYR|sp^On}+l{tNXvh0u!T&U%c35CpIby$y-*78B&=EZZG) z>CT~btj1}RQA}8ey*)K(Bd9>CWXSNXB`9_5d}_zHm9he2?&B83i@-(N~{rgHn+9Po8to@eYJ94SOc2rlMtRebmF;X_DXm}$-&Ulv}@1zy*K!v zo0Rk7F9~!%_r;;XNBh&nMY{;7pdIfM%xFyids)p}G|vnecUq+R$Gi-XZS&^5J}PaR z|2=qE^_)cQ^AqI0;9@cybs2Sz(HsUga-M^xfJM`lpQ_UvfLn>&~j7@aL6Eqi%Qns_|^ zYZ)&o2<=;Chj*svDd;Yx8pmZ=l=%D4%+b7)%gYBB`>&^zjqroW8SDEXPHinPF&qvs z=B$WoEU>eGEPx&ZJ3sadMQFiso$ealBeI>CA3+@>%ppeS(atxG*d;&S8X12~Um`^r zahVk#CobLd$Xev=ZUiy>Z3(Hsbqc2RnYtE%|BVOxPkwav@P=q<)RKc$QSz=%AtO;6 zp1T_Fu?_ST*#b0tp)Mv$SZc-Gn0P&eu-$PhW=0_t??BdGiP;~$Rg3Y&>rkloO05{*| z*_dn8k?je$qjs8&Jq{84o@AyPQ#@yIK=TR_)Ht%c4kkfE2vIYc2B7&9C~^;ezQyY`gBW!C-<1{K1z!_;bDk>;+zO7zWl&6VKSfORd_m zn;TnrF6N ztlC4#A?NTg9hB6+;s5s%(9#r1b+_UZ56+TuZB2pZPW}1<>fcX77`wJES zgl}0w=mU0j;V=^$Vq!Q>sXPQ8JKVu2-MX{S5tgEb@EeIRA5ZjB`t`6}0k+UW;pfAw z+lWZ{bt0_N;MQU@%uczYYg|;A z=$6&w<+koTAGA$iUPVhYIU~Ry#WM|~5%0#$;`M6zk?*ZQel@fU#~D^?z>ZPO%VU3@ zW|HqsdvEC*w;y)5l_@KAa0!9WShGp;7bpE@bLZHuaOJ3ss!#c0pLB~hamH0s<- zK?MTTm(wE;6~YbmUQG5s@KtFriCtR&0y!k4iv;dcjGkTlA=;rbV)C$N)Y?djnLj=2 z8%Qq{9~x|~XnFIty>wzc&Pmgw+QvK8q;KPnonr>4&l}4eLLU^~eIbnMYyE+cs z>UnWb(^%8G6jyIo=~%|Oc_jn)5tBHloY_luo;l+v;iV7mB&My?H4H6^y7Z;34~TMf zXDHZwg4t^P$Qs}jq=@n2fV;eix?{+vf?BI`7bUNf3fL$!LzI3`t!d?vio|&@f%ti2 zVGr|rB$eO*&j+!Y4y&K??Mmp9ckQe`#7iW*Qy&7T3G6t@A*Z0=ih6_hI2UomqVXZ1E|fkC0;;^{A+roBssFEK%KvA# za{lX#`~P{*|6Owf?ZPkv61mSS8c6JG=vC0dQcmwYu!pM)p(cJ1O*UP)5{`Z9t=F>L zN;VMryI8djsKtF31|pc%YDG>Z!uxI`rsx$m(G{v8FNZWALU%=%6nG3Pc8+x|9(Z)k z`m%Yx3qIz(>)35DW!Ii12(C> z6elyK(UV`sV)wKLWZf+us>)O$ogR{;ntll^k1^TyOe~80+6z&ub6)?G@z<1+FYRX(o%t1Vg1vfo%TD$ zo99k8${YSv%Sh^IyAD@Xhpbb475Tuo%0W0&d~IY(P^b9)u>>J;E1m2USI?{I7d+YZ zloT_niQVM*+U6uIu0e0<-Sw*Ot(fQ}QF83*bgoz^UH=Vy^kGq--^JDZXhUP)kPip2 zJ8OGGHt9?rIG=l_G@HU7x!leW>9e)iVRA4u*S(~F(0J-#{9;C(=K$@^qtdf=tP^4C zbvidpHn-epXa)MuTHl_U=yelq;ujN=(lDOXaW-L9|HVzUEfyc4hHtfE6VEneIVU`FlrUMt*I;R;!VMcGMJVaF7s%@j`A&aQ6&7M&Ch7^k zHomH}gtc61y?(awbgos#hK3jMJL0mob)CETTurv~?A6x%opI$8SI(u4#QAq`%O9UJ zswtlj%I40?lbVU2cVC@*^bW#156uVO{&{6+-n${N6<`BwX}9LL;`a|NeD-9!s^n^H z7mWrt?C9?j&C5DK`)(#Q-WE1|fMr?s4d~ZNlE?7bfMwZ~bvJ-JK)h{Q1Uprek~YL) zJ2TJMl_bY#Ty=h9ti=v`8Gq5or*gIL=WDxq^yBJ|iZ!3@KQ~rV8Yri-X^4u^ZTGO% zw!2b>R{lO-S)MYRUsc8VYb-o#@|IJZTR`^CbVs=-I!&%tax9birN{PfRvk##{r#b< z&}}hJyi{|P{_&MOqjNIhBk1*%w1EM__Zc6bmmDL~6?vy<8{{x&DqOf;;Dn1sOY7T; zpqxVimbGg#m_zjmnfrX!4VgHr4*pnvAMzn)fwORVn@#OD2az(->8K6DGmYDJbv!e0 z;iR*W%SAW+x~&@a<~4h(E9cPM&k1iMpw}`oGRh$_V2_q@VsIr`@ILfb=Ra^ zxMqaQg)I@P%@)@;`$z9pcG`dH!R0`gOF23IVK-u43+8)K*bbY^Llx3BXdHSciT&1byzl4CpmJ{* z2hrEh%|F_!*wRCTPYR+YVCf87{*lhpI6MaW#kDk|uW%iDERz8X+oz>!mX(OREm zOqU;W5OmL7wXd-Kh6{#3lqrO zU0kcOBWh>c?k9UUqS`x;GmFYLJ}dV=ba?%HFU;QQ{2LO-TCZ*sQ4b-h^53m76}X($ zb@QE=XYC#BvlZsK3LooyKYYJ?DZ|BBG{j!%fP_j^`~8;>3tsNIe`?6#a8{o^b!e|@ z*%K-Ii+haciYWq@ZET#QYzA{Q*5_nI?-IO`D&D(Dm9;M~kFlK?mYmC~=*+)Z@0&9z z_41zB_fCpN(@|c5--A#D<246`tc4UQ-)$2IA0Vi)j8f=g>!vPt^#oK@xI{UXKWftXDYgD_?R#Zn3qiC* z|JeQ9t2^@e+`#Sd9fposJ5zFA=6*j}p=(`U{8YPis!q`PUFlQ);&X!pYwK{BsvvWw z1b?QnF6nZL7BSq<&f@BAuPh?Ai}t_Rd(WUI*S1|41*J;pNJpwtrK=zj6%bj7sPqz* z4gw+|C?$wWF9HIJLR5M)^e&MuAiW161x4uzB?eN~zF5!fJ?}T~nmy~CdG?=go*%$W zCgHyBGv|F>XF1N}h&Bx^t2k{e_yV2(hh9~&Qm$A3h+c`zUHL~YZ~ND?uy2O>z3OXL z^<^Bj$1u0N?UQ(~RJt~WpZlCw={;z0y!Vm+LnKGOA*C_O^`Vd*i*7T++eiaY#gvRv zXVK#_z($>s+(n)l(>nskNpKklU}@JB&Dp#Jc?Lg*SYPKg^YKya@ejrAUDrSlZ7G?^ zmY3Hro$hKWe@M8L4oTWY35FDgv$v8ftilZ6HF?d}@da9SnT_4|fO46Uk zm(;J+*qbDlSC^hi_FR0EHXRmFu_}JDi69vHp*F^5yz9X#np9b;w3Bex+09<;oRNK! z)sM`}H&!KPomHL&XIQcrN8F5A)q+KZTq!5xm~?#nTO(=JxZ?)cXXw6B&sjbWUBs_z_Arrw=od zY(O_7HUl5Djv)ABsCk$UI4MM;x|d#l8QJGIw@IRq11ONNv-Hu4h+aG*q3u*nvw1Y? z*o{?}{g=yajCdH&tK$yKP8?$&jZ`*zvx3SUjnMkZ7!OcGf5i}ifj3|}5i_Bn1u!YaA~`LrU) zQOBP40q&9GXO_9x0cY2f_g75P?bH_!IaBbU-U(BT@m;m#cOP>-(<$5!kTn_dDGYM1zPQ}bhm)npOMW9BEi|FKNZ;u4>l?Xs|8ZWfNF zdSy&Wzxdw_w>b&^pCa{t2)XHpyTkV+B^cprDO3>YPiPo^?CjD%F=5t6=zUf(^d9f& zgT^lz8YmaV1X53drmc*osmd%D+WsnAjR{8s%d&F8$M?PTAut?yLH{UdLrIW5fikmO zf}?g_p|_KGxHF9KxR$iP{&p%tlaMU57G`=!>T24&Jvy;?5|?cID&KU;Nh$pmXRq}KNgI{F7x*D;DSSNy#TQ-=Uc~KvgzlZwzPtKI-pt6^zAuI-slZY%jA>hUcw3wwd@qTx{949(U}bo;90GV+vRG+S5~u=Nhd+g+K)t)+vfBx zU;3V-Mda0;`ytJ>Kca6bSSH!2&fJx^hnRCG6kI)CIuZC~^Fyu8F6;ZoM&qDu+>I!k zc5(YJ&lsvR{$#M>y2t!pFFl{z#Y8%YGr2&_#?{t_tN$~>fGG-c-OEQ?CYv-QWF7cm z!M)*4EGRNwInez)KC zQQ&IQ*7@@CCZnpXG-*$kZRw24qf1}Yw12FQZ$f(agvo9a2`H|i)+zZe=%3|jj8 z-64H|K6QURPHWyDz>zA7z1?_AzJ(V(9Kk5a`PSctVG7ms&!)U+V?;8NG3qbWSPN#&_^nDvSYEzRp*WzW79+WW~5e z!#-ULSyzip*n7KBC)0L(9!j^3uWOmw zmaD$EUH28dms;gIG$v+id&MO%$lCaZkgdVd;t;s$q-IQZf4D)=F*z-Rg4dI%TA!Ck zkNN0DvJ^zguvNAN?>bg}DCO|fqR?*IrC%oOcXE#x!q&r$b`7{nO=6mTr8fq{CK8)%fOm!A>v+tvTL%OYuf((G--v) zDZ+v`=(laH^yv(%JaNbMp4`%XJ|or<`IR@|9>?s7VC@nA#zoCT{*NK==$5aMNJ!YJq#vyKaA`aB&W-%It8YsPUY3cLRa$Q z`M+(OOX}aclgx9vOKj|lB_HGOSEXvW*SnNC&pNUDw)PdCEonHQFTy zhgaX?l19atrc9>e%?qvDvdL(- zqo2y52Xqx;n3JF>i&SkW&DrMlx@Lm< zvX77QXSKrBvf0;?fDS$6$q{NFduj14jM8m^R#WURtv#PQZ;`%QKc3}M>p!L{9)*ku z`c=<&TpK8p_jJMV-hQI=N><#aU^Vdh#P}c8W^Se?L55GPNcM-M&?aYOG(OiTT;c!B zaY>5dM}6tX5slXK;&tL#48&vI1wEzfN?CF}yvNG*owJhBl3le4zR&M&zdD-kyY%hB zW}Sb)v5bQ5ADw&+wNc)$=PkawMl(Hfy(8+ib@rCg32xh&q|$;|rbB+UtDph8YBu3B zw$Zbt9lf+*Hs_L5>7Svn(tp7v&^2VuC_Jr%o?=0xBBvfxq>P<>knxD_aD@2?k*1z}rXit{!cv$H`{z#u& zm++W)Yw!8k^3CC%t&87H;ObJfao%3l*i-I2>RK(2ALeGM+0GeGX5imwItrZro)|-~6)~Py!n|&JRahd>?Ks>IOVb+!;QzYLhDu;3kV= z$~#G3%xT>wCfI9 z1!;1>*6(PTE7e>{y~N?2VW{4}lIt=odg^VTRQv!zJP$wkK%@fR#l2iF>{cdLyC5-I zp5wcrv=$poF{2lVJPPJrknZX^ndM?|XpXU_i$PmYq>zlbQr$F8H7TF-?m%OE^H;n% zDpxd?+j(zK@uLENSG9dt@7Q|nY?_{sjM1ko!z}wc0Z3| zeZ4u#IR0y?v8J^7jju^3Y!87%5X%Ev){tEYdq~=O>-KZ`=YnH)O~;{e)Kd{P=7mk^ zadl!Wy-jOchxx6)$ojY(Vmvq3&RF34Ej;Sv@>24hc+gu z9q@M0MZuNZRuI>!|cfo?*U%%i}~i9mlG2~t^hRCwm3j6?TJs} zvoispMJUDtt%;dM_Vd1o}v@iY6eiAEPf=XtVIS! z`mt6}yY?MUF$WVo*mnoW5%{4Df^d#*H{iBio5io6dAgX!5sVh>#w0Y0?K~1^Vg51^ zb_5JBbC5@9Z@L!b>OsFrhoWjqjM*1s;5%}i{}w|j;Nd&Xz!=GZJ@lFH?_R6@-`v0d zj=zWKdZ6{Mx@Q9=y#n=jEWw?gs|SJoy73pS9{`vD&8D%>2Xhv~bC$lYA!Ju% z5t4s0jQn0yrJmYF(XSzYxzmqL5q`I$l2Engy+}yJWZT*4e8R&Fg57{E;kzB6bK&)v zD(Wg(%sJY1CB++G0U6HGY#G?S{HSPFG_ATzMpJs_!oO+~R?`Q3e6UOjLLXqH-`p3S{@?GhI6!}b|Ahp0 z9fT8zWboMMK4*w1Lt5-g3>6{!RmmWFPpVpO9>037B) zTN$)1N7mqk?T~S>kR)IYIF)Cd9s_dy&cQ_({V|Z+;`uLRmB=38fG!74SVV&y1F)($ zJt*pqCmaV9#4=GkdSC(yE)1|$qwgTI#rw$deaps!LjWa!!o7rJGco_=9R&wSP_P2v zA|t!gK9Tq7C~n%$fs(O<4QdwxR7_J12Kom z%|O>T4U*(^R-?Df_LuPUp0|W2M1#HzKk4Urha^-OX69L#Kf~FfKO`{(Yd?zKF^xYN zEEiH?cdUQ>r1Gxbn%G4pVCw006<#{%wyH8R*aGxR%Dm78^F8lNR=n{JX~okQi0Zgz;+OEYa@oeIvneZJ@3e3jPNcfy%wwGRrl|poIq*LS4bSwH z3msPVC5cCC9%N?wW)-64_CET$!O0x=JB3?+YqVNTjhgvuTAX1fmO-dR&h1cAy6SVp z{;0SBk`#g@+L9#}6(MLwI=dl3u@`YnljdhLrY{F?H`M1OXLVTl6kctfC#9EABM9BJ zmt)KKMdB*HhaMvJ;qKavajggZ*t@X?0Q6q})?ufE>dSwA{{LkI`fWINQ+4l>UlMc; zs}GLBzByi(q~=$yB_b1#pSw9$$9~M%w0S~vuFRQ# z@f`dVfCn<^tkr6K-`GwMAEOkgDAqtXSV6AZy|u78nE8Qsg$jQ2bT?i!=3`t z$yW;x9CTvaRO-4cOgU~Mo}GK0ea1b+CHMFbow>173+UM&Nj5Sme(YzQ`CP+I>5|d3 zO!zNy1QfvATgX9wGW7AmySOmb!!!v9IPa)BaBxUSMe6;4V!uPla@QHbMZ4R9?4n88 z{yT8&cQ{$@f+T`344APKybPbY*s@8E5+Q-3BJY437wn^(IB=rLfUTJ8Qp5ms zCk!e9PS1HNXzG5C2MQc?&Og9L>oOqRt^>$*1`fL@sfWo-F3289IBhd`{UoD?1{cGC zntYnsUBH*n1tXXYOwEVOciGpxO%Ea{*hiV){iCKy{`~3$KtHkg&AQW5VnAF$7eU3q zsHw(OgJTpWFis3Q;9!NpgjGN}tZ+;6jhC8P{hWnK{rsGdvi;+RZz`1BNA7^yR0!UF zs+JlKU^)1pT(yQZ1e0$CX>q*2`H#@M%@X7|XF6-_jWcuIC}B1u&Mqj+Zd9zj?|OU? zPg#rC2Kwm3#Gi+oRsRPDIC~;U<;u1q+M#r1WoT%6(t}#d{s~#+%t)8A0cL@L^Jkp+ z1~>p`1>m+2j;7E#O~LCnD-V(GaJ*`wwf5FYh$w=2Hz4XLW8Sd~nh#V=Or%`{^~x9> zRsZhhPyWdedjjQ(*w>c;aSGX&E(YH+ye`_43T__kyQ-RRWU64oRR7jwdAOYE@U!gl z8bfx`USAo(FLq~)2ZV0hsP>boi(=iaUv7A~k#);VMB}gB z??*pje#`YZ>JSqb%hcapQsQqNoHH0eAD^b7u$guo>gc(U@x}alAGJ(0dKITqan43s zMR;K{iTuIYw_GNC0odiv`={Q0S{_{R1vnF5 zrg>|lcN-Gg&MY^-RA&rQPU#S{I=O2ap=0lL`pr^uU>Ji-$Jf(EU**muhJ#q7eGs^@ z>FLv9*2FR-je8z8NB^L8^PgT;Y7&M00R|mOpIl|5EASGD(^i}P8)V&b);li_3wS@u zvfwdL{~43?L|S1k^|e_N5UU4~RFti!a8O$={VaLCnD*M1XM=(}S|&T*yQF(L?Mv2Z z%;}y1wkF4~#q2zuh`+P%-&*BRBZ?3JqLIeKkOY0Lt;g_=w^NYg61&_VgRcVHq{Ff4 zc5-$8Wy|`f$*V<%;p`QpR6hdT(oqWkJ>%444w!KSM8@wPgxM2oifG)QoDZxxNKU779)w>eO> zeo}M!R;E=$ljG&86#s#CNsm`)xEt5g^8ePQ{{EI{=HTrkFv8VWC*n`(xe|MDi_vEm z9z}4he6W4ZZ&~=l7%qC(ghhpJC*|v0$A6y*?8FBPH5q8}tWec6BYs#y*6<`f-9X0Z_O`MIV>x^7X{5-cN$Cs$9YJ8)N#MUiDjE4`cgX$9J!w_pkW*6eV5$ zW30#R7F*OMVV2bv@K_h1u<4i`oiK0!b*h$w;zECG**|>*&O!NsRR>UnE9U&Bp2g6_ zHhqtM`rBXo!LhaQr;k5n(Q@YST=9qW<~92s%^&1f6}I!c3oqS3O5| zKxbPl$*+-Ai*fz4VPfQrqU8lOvVSqzzH@6CWPo7?`lv9~I`1UIc%^8?(7@<!L0nzNH&TMsn^+&DoGIBy zMfTabO99Hy*X~~#I5_E!!}f=1Nl<6i*-a8N4M7#6XhnRbGuIQ$JK;w^kF{JGbppLy zahaCnBU2#J_!MT~2R&%Vp7O!Jv7FqHiav>p9tLgKf6*NzFdU`#K&w*LC=|ws;WpNT zsWAl8TnOkRKUGdl8#oca+0~{&RQ`5drDYK%wpwC@}>X6jq=m4BM8AM)I#$f4C%sOA!x!) zrJCd+xe!MV;3p0hKpB7h^cUlF3?fde~HK`6^y{^q)uhU$?r7d|o^i{Am*>Zd7i?CW<7VHU&~ z5Q(T%fFKICy8`{^p1i~bZ|?<4k>0LbSfXI}4A+3t*B8_i6N{I$x`zD&)NZw1cWrFU zv|W0Uv^6M`_QiJo)jW)RR*=q`)-)WiueP1Cm!I8$qFq)TLH+l0bix`+cmUKLwkW}KR0HCJ-@&jts@TNG zfa$Z_NCfnNb^D**D8GtY`ZbFLZ?`d@$J$Yw-peyv`M`QHV1F+=f$-}(qz|8lL=G=4 zrtEphVWw%N%OEs8h@oZcp1`WSfl!xH)DA_p{gV;qxTteq+aJz}&IJ$uxt-5kgz-76 z+7#`*Yq8fA{Y|}G1!^B1?(T4Ys{6wSwy)c`z1F^deem^bkZ2kJaHg5`m4LVcgz8bS zC|06%j~`X@TtgA>TMX%)nM-u~%rliH*;$sNT^ZL#kb*aMV zFZSZZ%lJFH@_ic+I$yK6&LusUwL<5Y<|O{2NgW&j2VqpDfxf~)c`N#)}NGCD19w~rI)o`EIkxF8&TF2t2Ae6ErYx8xlH1pe)D{X zz|aXHK#W^vqgHFZM0itOp(T<-t+}*+U@ZW>>zg^rrb`+5} zhRQQQk47cI$nHUKy3Mq1IJl%Vu+mAD-B9Jnm~JqhQTxem$|gIs0t)V&b;2j(WuJR- zk6*6hD_2Vv$E>02e5|Kq^Q`}&A4F>PQbj=~%7kQqkgw9U2+Y*cL6Ey-YTdN@SAdgl zxBjyEA@U&5t$&d0oQ_?nY7#vxVB;bjn z={&W3qK#$Epfs4s%}3jvbY*+5PK5hre7p>C7AReAW`JzARE? z1`3DB5Nyl6##k- z1DyB{`~r?mU4S5N2}6^i9xOW*Mpu5uTtZ;!Rr+rVzA>I?4A*i&X?)HyoEYGKJ)(a2 z=wYUx|9tzOMBz_?1PjN31omDlFR(Rl_qmgi(Rkv?#C-!HCebL)WBzmru!=0O2ft_& zoDLF$T_hrV*R_XlJ;~hetPB$c!EvP-F;ho4r|vaMZtRzormWBRD;y8_ZBlP32mJGW z`OsPd_yWv<+sQ=&x7$pHkzMN$Fbb=+1RmtzXVd8{dy9~2M+b86;!@T}&*bW5eVH-+ zW8T`2r?P)_M@BCPF)n>J)n4`NN)6i>js4+w+W&v>psbI8D^I`)CLv=oa3Si$j8twr zTYWY!+;;^APf_DyDdq?4r!U{?)3r2WIG7E@BH;wfVNRf!SRd0noPel`iPy8w3)3P0 zB$(nL9Z;T-Jcki?LQ10BSeT%9$uQnb@M=TaM(8KG0oxW-Lsi>^bBL;MEya7_ILAZl zZP3D-5d1qQS6o${X`mltqeY=yktyOl~}7!~H%2(gS!`1#^40YUf$1 zxvP9m;R;=IRP&AE07__fSy1r^Eha`~mtE}e%Km~HUQrDGhi70D0~Bm@*tub{_8NJI z4570@K!J~$AOt&9GT!P)(T*6*RT+=fKP%ZA{yxl+-0~h{oQjPMOEUiKba>+Sa1vw| zjIDe7kuSlkcTmUS??(<@usmdWpoz-B$iQ%hfs;Y>(1pKw&Gg?&Z+k#JEV0-GmJYA3 znSf)l=4b#RR#rx?sRH#5IO{CTW}%&bGUrFmR1x--ijT^Ak;#e1`o?JE=ny&Mc#oXL zRH5TfX(K5!bEJzPj7+qoy=jK*y+?7t_p*>UFz);V3`sK6$0bN%;v9hCw)e>@jm`Sq zKIF#*30>7SABrRv-lGsPIL}NS!9iMLzAV36 z-SXPP)Z?+`7yNzvm8lKW2b=e+dpY30esif5Z$_dA8)Dacqm}$VWbWR;hYL&@ZDrkv z=lArzUB$!w-AiD#*?Um!%Ml$ftEM{dp8Bm7_eWYvi;&Jq4Tt93@PyCSgQsUTiLXMI zUL`iL+hs~{r{qNj3CI<4COw`gp6CJ-+QK#01D4EGg^}C@8aTSyJL&>j3$u8gtG`|g zdWzdyI&SmRraX^1Ht$)t+e`IcOJ@b_X>z%t=y?Z+lg(Te7B>TyZkYCHNz9m%n5#V2 zC228Gm(clZ-+8^=CDx`19s*7VnVyg+Qa4-1-LKcKP3bAL6iy;QJBGxgT`7W@c}mg~ zG(7ayfTBeeIUOVO-3Zp6NM>YAdo;v)Tx-{=*w2uls_4iLX>*NYUHp_s@EPi*GO}rJdWpO%g1|++2nJi z8B4_&TQTsv-oA87lnY?hmW6dlW?d-PmMn_)o#yk}7E7|_OBYP%hK3xaT)%M~xw0@f zkfwG)LPMU)J56Hh15PSNOWB8y)`ki0O6iXb5L!Gl{QU7_ zOEG)WC0e|uuJ)%1sLST;JL5(}K7T3`*#S*5x)4HhHhz;@xWYc9EhF2n zandPT(zO7J4E<|N5iAnJMgszpw~)ajnq^7itoOc86ixVKUc9G2kw8i?y?0Vz%t4X& z0r!)WYpEGGD^Ff1{Uiz^Ldfa`2~l)JAEs)fuC$;i2IV|F8?kCy-Ke%IJc;=Xdp?S$ z=BauYJ|`51CcJMl&;I;5Pv|CJ)lldUS{&S2DTUnSH=6)DrP-{6vT1tvRZCgps0U;j&l?t;V#asufOT6~~H zjlKL@I7QRTuIJs?Y`1i80z1w&cYQhvppgl2>g~SImTceh@TuLppVAO;g%*c$vDI94 zpF+7nX048yjb^WiNJJvo?a)$VXOlHO!<5#BUc8!e}EcER2zS zgXiRj8(X^09WF0QSwA01F=ToH(ZHc*1xXi)TGXt1aHg)cc;d9;#>VKsv5=0gi6GO0 z3_f#jUp)mLks}rgnXiSu-+nG;W5xk!WQyUe2{rX&%SEA7W zx$Lk3lkn1xDyw`IwqvCuIQ?sFVzIw$rM0p95Fw7o z=+NRR3RgsT{#*JNSoPi_Jr>fD0@^b-a{TorX)oc36Hb^vG+7%hsWhm*`z6!d|MgF@onxOPvj`Jf9W9(gru}Te7siaRd0RM!%3!w zam{WwDy8EuzwgZn4sNv$v-)i{Y;66eQv6#0?EDi&BBG=1_?X+|Zpy3#$)TY(@|g%=WO~kH zsyW=!-w>sSQ|i+!xMJB~d??rW%IJJVb%X5<7M@3!w`J7I$Cd8NW?LHaT$SR$pOB8i zpI+kGne{snHM0Sx)4o9%oLrnU#&}X!ZU0K9adl zoQswSb@}8gQekWloY2*brw4!K&82F1B#bS8vaRa~7(QHR^{g4eB0lF?cwQa5D+ z6!}eD*2hUgw78)Fig?6;d=YH+H%=8ryQ;Omz(trGevWUc6rNuw$rqCH5gwv3e)~mW zdSU9O+S-%0F((JDk?s|obxs+$PD!x#`W|lFa zqTLaPt$p&{*_)}x1ZQ%&!N0xA7l>cZFL8|ESS)s?R1V0Wp7u{Fp@sT*TOF!Z`0Em+JSZ|`moHk++5$F6YJ}fdpYH1W4*n1iBDLM zc9kPj_MP)xT<;*7vESSsdm00F3{l-++mS zrTadbxpPKO8E-rprcvtPsV_0X<38Ccc|uV5kA|(2To_N+rJH$QQntvEEka;gpe zJl#G0_#Tp`rii^9Ts^6mV`EG8hma`|0`PtV6!s&A6e8?SFZ(3b_Lhi9q@E(Ic4Pzg zhOhwSa;^X&=6UTvj(eB5!LOhsbSU|_vvuxLa6B4)>Z4q|JcFM5 zB9txc9H9`kh!9=fcYoe`0_UUt)0W5_tM%q(?RelP?NObk{n#o2M2x{5j{%7@#lmAB0LOe&ggji!@7c_*I*HR>hn=sm zeYL44jT8AY*qvNJ)VNXAnAX|5pkJA0A8Pu@m&%VVt0!!G?*lV~!py(>UqVZTfrMQA ze}4XBga7!z{|g_05J8q6)dcScpqtuW8iM&Lbccw2{3pZ3j00)`>%AFxyH}WnCl-)G zZLTLRI$lMutaO4m*Y%|1%T`SdB<57gYXu0&ReAzZk4`-M=3tMmw%;4;*J4iwB?QP+zf8J39c_|dFSQ?9to0anSd&GQ-5(!nYJ+Rdkbiom89h|5J?3kY zKc2YwH8k$?si5L7db3A;PDmYT80EhIn5J(Fj zke^0BqUEDDhdUwW6p6CoT#JlbgD2fH)W0!pmus`N*~bk2eA+pn@jmNrnMK##`+WXG zZ@R|Yr;#!5?dpa3Erl~Y#!F6~?NEMXsjigvF(?{;waLi#X6*e(59LpGxYvBX8&rmJ-=c%Y}PhLv8os;0-kMU4X`US^{;(9^j-xXaxWWS_owaI9}Ap{*fS0%ffu@ z%FVZYV=W199d4yHwP0e z^YJa0Kb+BR?u5<6Fqn3WKgWUsQKquIRN$1>pxL5D%R8fH1?EbA@(79$1YLW46tcES zxw;ELy-;=>xW`d3Un|=Ng^kd9}8*v~8%b`{T81 zq3G?5y{4&tFcCO92#H^m?RhYXiIgUb|NWB@Dr9 zM;9elK#a8AQ9f@>=PaPb?%FB?Vs{W5dDa9tYX+blI}q&enxEu_Qv^cjC(8D&g@n9% zd2a(Pst9uUBY!FTe)Q|nmwVHxt-WnOOZyNy@VqV>|6p`EQ~m7qniUP?py-7*OF_?< zGLa_Y-w;cO;Mg_4nXe2`+*`QkFlC#7oTm5lD`5x^<=3nO_%eS(Yhddd{!FDeY~1R| z7BV;T!oR2Kke~JU8(;sN;>AEOY}NIH`xRXdI&T~FrscKp`li&Cv$N^t*!5n^i(2$#0vrymf-o0rjWzq-CCD)2{{kk&SRQ6a^49RxZ0Wh@~w=JXZt^2wXEOLc4CoYc$8W#=*(-P zTBdq{pGT#|L9w6$?PlhM=~ARyD#s z)Zy75g;|!YS_KO<=X;AqKP`CmqiS?9X&|GiNkK(!GQuazfTU!rVEMA^1zgpc&iR8D z51JlANKzgavUle|FFIH2jwhV(ka_Jyo>z8sMPbBOXq|FKPibt!$Q-hxc+D+Uw6Tf8 zMm46Kb5xVj`x5_^Jy+P)_A?a8axf)eo$i$@Kdoo5c(#=}4<`RCuaavXwlbX{G^G_{ zBy&2%cMK=aD&@*F@R08O=;{{JtvGxm-G!IVxlW6>76R}PuWQU6`n->w>G4{3LY~q& zduUNqbN*&8P=ffqer?;9Dx0t&VeE3@ zDcS-wpur7mf8kgA7a)o?0;NUBc$Fq*&vso)5xJ@}m zue3Owbxy}C%A_`@eM~N}HDTfcP~er$&r|5Hb~C`AcTb?{Gmc8ubvLgJMM1!4^{ALN?Shg8uUf` zugy(cKeq02;B};#_f8R}sbVFRqajz(8v~bVub^IY-ij@=e)KPcgpZ-2w(hrhj9o=B zJg@BS)82HcQJ>7lVk6HTG#cR%T{fnqBn}u;SAucn z0M4R0m%{7aCS5rNiPcL;P}VTqLR=oHz}qs zM(qr=0{63TS?N{>r+);MrX8ifry)Jg2A58kW)0K+a72lwXmmRhu|c z>2|yH`CM)A-HbaOFMml+ZmRN)XTa+L_F6Qi1lV543RwX)#=-EJQrzG+@VfJIctG+@?$6Jd0= z6GYerGCx7_0x^8J;|)S^xlN4hP8jZN_5ts`%4YduKo9#|)Eq)Mf6q0-pRQ=ok>EizT|4fJcXFBHp;sbO?KaZ_9qyEf z6=v>6L<53Tg^(N0{y2DC1Tz8A1vxe}C{l}NPOLo!?Ru>Dm8j9xW2Sqf{=ukjPj_`< zzk#G!H~buwa1%m4>rDbRwx~N1qp6 z1xm0}os)twnSb=c5Gu{r;Y3B;TBHLEW*Y*~TOFIC_7Ovm8SRY^_mRS34uqc_P^NFT z?M!AU*ef|-(bh?RGT~wT6yPE@ zPYL>DZDZf3ykH7vw3G77#0)@}MJ|Z5Qf|;kofv4TD0kH)L@lQKl&auo7-NJ8xd;!9 zU9X*dvg>0v*ZJ7tcRd)umu}8pnDPnj%8s%NfnSVT!lTjQ&Nr%~@kghM5;^6R`?7M#P3(`bbH#_Y4l#@X9sOB(F2&`k&6H9gn zSKBSw^)bIiw0uz&jWNmOK2ovv`{rCPi`!lvopXzp4`~O`n))h2&9#YjpE?kZXDym{ z`aM5(7iWLHkpPcYK}M*C6z&g-qwiN5JR(p!?X=&YNt?_|=(4z+p4rGXKmhYaLIxC7 zFjIZ@Q!QLAN8 ztQ#;IjTzhC`|5Fs)?{JG_kRYpt-FA_BW$h(S9G8eYDRuaaiTg95XoVFSihLZh$lN0 zrk!w3pB;9D0L&p|v!@vnr8!-d(uD>MLbx30HabOZ?S0g;$rpI>hh(y6yio)O`BRt9 zSr6#EP8`Txkj{cUb$A|=TAlA;_N$ za(NVA_-qFm6ap+8k?RYQY$rebpC7$oeD69l!5r)QyE9x7|Fa9j8UnE;cM=&pCD`@( zf7xx6h&oA1;m%gMjL@n0TsT z8Tm<=Di+BWF`^?ZAFU^g`!VUek!480#Ox%9oYyfP)g1LHTqaqK%%SHDg%u(pyKcyz zrgXN0IQCS;7YxC47!AKp%ZN}5Aa ztrkR)&g_~6`%7*SvrC>97pt%Eu*zAgOZ=)wy#^>R1#_whlps%M%b-|$q3VLWB+xMY zbjK#QSME`Q`x974?lA)LX`PRN2=u7kJQ|HrUaB|S{+Ryi9bgr}`+XbTLQZs^Z15 zJ0pQ+5*O>!Y%6vg70t(dmJseaO6P*|wf#+WGD{?~m)UCHaPvMo0Dw*YKdb0NhK9+f zepC72I3ScXK=OP_`ao^~V!8-`_+o^2^b(MrIX6worp~y5_0UU=#Gsg1l|T%)S6;XN z3Bpi@4{m2d(=?ndGYSlyc5=_FO1t@tLvw8eAh z(~@BHlDybYszAWO3LC+(;SezgV>(tl{kXo2pxc$JLwG4re;)%gUqn1J_ayqYTRr5u zHhQXgf@+SnkM+Ljk#)(_mI<7K|+c5{@U{g?R&VF)9;qq^Z8uRs-f;kLqc6PXA!4t~VottxP zvg^KS_`{ovLjFD;$^>kPe9 zY~$3Cr5!w_lQ}H=X+CGZHFel0fGO>gp1MqxZXUp3a}$Htq`~b2fvGmIC$?YuUOh4V zv`vma2|o^ZZ>gs!ZFy$ai}Mi#JJ;c#?M9lfv?jPuQ%$^yEf-Ve(lrLKGgiD!vUYzm zSi#6Tzexq=Xbcbxq==B|zpWc4sW2=ongkk+)QGz}%0WK(lqbJ=!gL$L?Mz?YQD52L zd{zr%CpXhQ0KsfN@p#(VS2YR@5&hQqEGmB)o&7yU1>P>Hg^FrBPX2*~B}nkLniCtY z5(_`&hrn^ikWV+YR5?BLIa>(yTrC8TYGRl*PS-;zh<3@oxq9?i(%{g0HkSv@9}M>j|%-{q6n`B!1( zYa!mJ+0V@w{?hbVo`U*EiuCAhK%r4nFmAC#g-`ua5)?-+Df}yh?C4e>0h{d@v&+;T|W)w5UmZsEL2%9i&2mPm0>#Gw}sZ{5ZWkQdC~*==;Ph z_0&P5>2&#IhMhD8n*|e_Tumi2A`jJMf{VS zhY7E9-<8b1Xy1{G<(nOi_M3n!dkYS6sl|?U%h;Oj#CYk<3(V1P#-I5{`i%;o{(-|SEv%F+G+S0)Hsg;AyO@5aULX%>$@mLUhacJj=%9}G%6X}$Dcir^oEOdo| z$_#|q3xu*;LY^f{fT#C0lNX^8c?=ggu{uLZm@_6}C%GKWcjux3NQ}JwcZ&bljLRX# zBci9n{(_?XL9dV~{qhsiQ}z7KrMT}92wVr?YimQR2vMGW2nfpVooI7(scbfe_K6sxo0zz%wrHoxFDF~>M zHTa1(B`FEWu1MhQT{eR0BjJVb>D>BjSh1XC_#Q|Nkr9~JFtnzkT>OJtVtud+^t2LJ zl?*$TUYXts1g6E(k?CK&0*do4;<`mi>`L})@=f7lH(S&iA%VyNGGttZ^^U-wGgMi- z;49okeBj7bB{lIV^Lj#$X{x!EYuR-k^;}J-opef0_Qe8e?hYw6pOUisGFN{Qg6PQG zQQmr;5uw7JtQPP)(YbQm5~TZhW%rUH?}KCBupm9Aj2u8w4W9D%|0~v&bMuA&y7oa% z?FT!mebd&=+Yo5(vUQYF#sqwu`u$&R)@LddN(F9QZuh-?O`Bb}*CX`Yos)%`dqiu? zowxtBroy?_66Anzf-5k=#V!HpN7&9{g8b7Z`Fvcyb2dDD)S zm}`k}ZE98SBqW?BFRrBNQHKmWY$Jz1fyB<%C@j7B14*ITIpI!WpA8MakkDtiGllqL z4f!3AKV8NQ1jurbFFn*2*|R0la_NLQ(4dJ@>{$tdO~|34<*K422I0xDT5Yijv`=O1 zzFd;dF)$d<%H+xr>qoqGtA4M`Jh|-8+k_e7|FIb2C(4SeCl9n?210OJP@sBuH#e{u zD_dJ^7cox3e$CS5{v(T#$w0(awPc_n)54M8QJGG_jrfa&kExicih0+hiuQQq+Jy(M zSR(aFb%F6cNB?K!Fl*OwsR`6%2Ehlwo?Zr0YmlM12tYaN%WiV-#L`*l{+QR*h{kHWq2D4eH!Qcms&<|BGJ&pN0SYLttS4Gjw33{9m>hkh@^Bd~#5`pZ|=(|7Hykz6^p$h0koi z(_F@Ef~x1`&|){^C4MaEoM?HV_Fv`>ErFY0l!O2eEdF?bx-?tVoVxGT_0;emzPHcK zGjZ1fTR5JyGJ#zwL=D^2ZhcYKlqs$;Vt9lr#BgW2IcPcxpqBk998P49e%~!- zgisZ`&tC9X#Kf8^&rEon#k|4Wi2AReqC(YK6Azx%U&4EJ17*J*Vi9Z>8yAz|OLwzm zc)v{|Zyq&`G!1FUzn*@1vq^TlBDKr;)#|Ka7UIfQvn@=uxy0Ye{kk5#D?#-#O=^T5 zRkgO%FpLh?B!I5!M;`%3u%&z%?aAV0rFebia1*SX%`!<>>^0-D9B8MK*s$bh;UU0sFHid=I_ zye`aKy%lmbK3fM{G-bO6n=b-uWk+oJNwazgbd<#;9!G*#ycpd&LacC{*tT|i{)>UZ zfU6^Us2NhV<-mmpmT6zS<34*`Elk^LJM+%(Z%{s{9}||taLB}Q&1R-pV#A_P^U(;z zlgqGFB|Sd7w8UO|3)83G4$HofS9aj{EmfaOWUQ2H`!foh_TOncEsIIX5AoUns*e}g z@Byyms_;B;Tw;%()$9rUKn|pXPMfn)t33M9n9rS^QF@{j;UZ-Y=%egF4#!zej2`p5iTcidPbvLQsppG#m)mlM+Mrq3`@SvGU74Gns} zf3W!30_tE8ZWVG#Mos<&ftxmcf>+LL?%Xb3zgzk@8JS^81l=MNnZzcvS5P@l<;4eLcHp z>@fq)4(2U%khBzB3-mm=!!`Vr3h+iRU-E1BEiTU^(I=E0%cYHDpoG`Y_MT?}|{ zOOf5+Z&50G-4>zlaLZkIjruP*DHX8dZIi0~ANEe)z&0YsnxCZqs!DEf^*Mg7*Cck4 z=i|H01YFf$llEF1=@m~L`HS0}Xi+ z7G$alNkF{{C8IOCDU}96VlQjbf!-f&?>|@7J+G_EK44@0C8M)bq;uWl?>X3@P>&CA z_Mbg<K-Mvcy0E{UAi-^%=ZB9VVs1sfNty=fYtPld7HHofxPk@AYc<0GnSWn z2A?i2ysHq+V{BDf7pFu)HnJH{>&QM)o@q-oxLFUxS?49 z)%Qzz_#2f(No}jJn$=9uN0~h?J7HsM;W=VL7sC$mgB5K}hZCdLlICPNIi|SHLU_L2HWmwX?%=cEd|hC=QHS22wne{C_=boXsI}=X zvo&<6{#RiLDV~V8GlQ0*E8*`5PpR89sUOs;KNFJ5cZP+HS%Q^a=vIz@dxY))!&L#9 z+9tZVDx8-FB=I|zp>7`)2id@V z*P{jr>eJn<2i9->y~}P8;`5P>vH2$g2G?#p%Q-RH?pp5O_RK5xV)`X%pCxKMslZKa zRnahm{Vx!X<~NaC!;+XOXbFH}3g~2RE?JHK21QBdgKU?f3;@UC7Px**E;=PDqztXf zs=t<;9HQD+_`oEmD1d$|@R$VH&OA!od>U)u{4ihC=bqeXQsV$B(7HqH&TUu`K z!`~!^EK{V}{d#Lhw-V%!i3gPXW*s%$b$lNX9s`cnBV3yZ*+#&;J=7mS-6;{qV;UJ|CSr)Wk5bqg>!Shto2O1ng+pZT(x3Vd&?l9oTN9k%hb~0fdK658} zr8mn@WmBx#4VuFvj&Q)<9*#9XR>f{zjM|($-BG!$&?~dc#j((?x{tX-^=T^OUAk;= zb!a}_9s&TzGMJYln5WIc<}wEW_0bYt%5H%S1y1Ne9$eH&o;5J+&ub2kIruV>L4@^W zWsgfo^XfsZ%xw-Ge~i!1_6`WlZVRaDoTQx*REPV6t+WTpD4|}P>Zu;G)X@UwR-7Lg zg+q``NklfYCU$cN(6z{foOYd9E?_CC?S6{ZrZz%9S5_zlSe2|bkvj=hahP~S*H#R{xpMi&Q60R??3qJihznu-MHE*0!dhKCvr&YnU02M*P<$6)C)Z zyFEL8)Xu4?C}%mU1GJggT;N^w3p!V`lld;_moChlu>pxHD0}#L_}ZlhHrxyOZdXba zM8naF3j!4$;8~2W(cIN4lVa#6Sj|g4 z|F|1$fik-~yi(Z6lDGkO*A0^fRre{xCqzj~ncgnK|5@S^E_e0#%PV}pRg%~5uW$ok znx6ujB;dG7RxgHG6RR{Xm^f&2H#QGp0W=h+&)5&MS_Y#J2x{QuluGTIMm1(kSmbiD zY*;-#J+y+|$SWU0?lw$cE6f#`W`pKPb=)bA=~u}+=?P%8OGpr|z!stmSVY{ZqdBkGFrM| zR=0IuNoe-lnsYe{f&qyd3n^mP%vgY>uAon23q#;BwL8oN zq_a8soeVE7)^(S`fUo2z44fM+enFyDOTLm4{4C}n4~VHB`JICGp%QU?$Z34)1Y^m> zGf5+*#GSsmbYgODfp)4u3b2JL9BRtbto(Vy^Pnc-KujPqqTvBhVT*Xeaiw$YHR#`n{(Y=JQn4T7pa)(rYD9Uye z(CeD6jWq*()RE;xS+SS0q|WjL0Y0YE1f4`4+d0DQ>reV1`7;HQJg?|%0d7wE9OO#W z8f9f%7tsLRoezJZ#?@S{r2nf$I(!v;t~CMxG~GF#ZU$+NYwblacb8 zUS2&nPCcUteet0_uW`jSHj5u!ypI+e^MtOur^nV#vtMKXpx5Pi@!Og8ad2!_fH;z% z#5YLcqktbo!}R8AiGKpg23PdQgX8UJWUuvoKJU_{WRzrivK-pXhk`qR8n&Y zOR3+H3qvt}H5IRr$6Ts|b!2zjFC>@w!VVAAq!BZCj)1&&uCKQAi?$eb)liU-hRiF1 z1#onua6ZFSC2I7(TFIIN#AXskmY;-qTUm#1u6cX+1D(|(DN1)a867#KQ+2D8f24Z9 zSX)V~8YA}5UPEz21K~MgJW*dT3njpxSP9s2bS-=#SqABbUNNcmO{L`zUx|=#1{EXf zOb4F-JI=+5QEX-J+)_W&ceT*d(dnE?+Y4(i;=XT1`bwaBuzg{Y6;*nZh-buV&Vx$n z{G0%7OtTPvj&9W)L$`O0uXJ$i3Zj>{?iFL3Lj7$L%1H)EkDnp&-d*D^Eibzz8n-j- zL7^V?0j@4k*ro%x z*YY~U$h@)x0D(=kB?#0oWa8y{_pg`f$9hojnA7>3$`@^)4rTeIJY4%;RwDaH=XTM! z@+{m~55bp24eeIyE8CIYB~4YxlZDz^M4B`whLB*2G;j`p3c_H0SDDm2H2i) z|L8D0wRueI5ikOi^N_wq^4#;x1%qK^gRgu~I7Y^9Y%^n}ZBz>9`8Ut;7d8?WjW}@z z-@^|@scTu@JvMC8ou#>B|BlHy@q6+&61M1Pv_1mWdkcTVAscN|i6PwnVCC1_U_j*d zSq->EH=7Dr{20D06K0&_0nSii*tRi0t1^1`YbSNxTEENw`?d{`ioNI7W(EA7wE6{i zMyK!ZeNg?7^xL0+Cjj)*b&Q3;5#=y%7h#kww*t-M-8xaso9AWyhc|1kVWs~fI&-tq{uBbic&UuO)Naq8AOkkW9iV7Fu!Z|AA;8Q z^4%X(Dnc(Jcc(!eKw*;z$E&GzNR=XfB`j)~ywOKAWb8e&p*%yc-t8A?$>?W?KN3#P zV;7!UYO`lv{h4`GG>0g=PyVHfjaW-j6W)OyD9rJd>PouGqg7}K7|HV!aI1Ged9mb|VglvQcl>r!V8IJ+OrU4$}oV`U-xae7-Pz8FLeo{@VX*)e^vMQ;-#-DEdcB#8PjqADeF;K!?1FJNi&&aZZ_j)`MQ9=T>GOFx3P*R(}QbR)JH#uDYk z1;hTtMAS+iOE?5iCIP0z;I_8CV7VlNju$6)7Y<8I67wj}11OUHtbB@CEMYzR8JSh5 zgG~jxZkkM6Kc=@eT+6!6L4t#-Z+@U$W^uX3+=%9&_;wA<#+nu|8SohW-82exnf0JZ@4)0YWd zLdNty$=GqGEicyk{(*H1)@T#l9Qt=hU0$D|0Q3QoVgnLLc^2qV=w=&k2Q9CcO{O|+ z-3s(_9p_C?>?OH)Xy^QN4F7wN#{{6=pCL4|wLwC?4YU9D)ZT$)U&#`Z92zI98k^Xa zEwy>fUN(pkG3#Nbq4oij;00?Mh&YCk9Gd4Yj|mVOAyS10x(Up)!=z8=>b@wm0CTfl zyc)6*QhUOnkYbvdTre`D<`pqC3R;R3rsF4W7oI||c1DVT_wSHi?eogMN^6|_%7aJD zb|)Vs#xKdaZY@HRPb9WpM=8pH1?P+6sMdSDiOrI_-u&BxA4a1Cu#SE<9Ks&=hWpg_ zv^4@7G!`k5$8K-&cN|ZJ2O4X@#74Sa=xRLL$q`tihx_cU2@P(Rf^K`gn5W!ASd`4L zQszu7{`+90Xo*`!S)+?%&2eCoG-dDz_pL>HPbm8gj$|vK`u?Ikh_2VxaM05#Q1P@d z6!+}`sBw%s31)zFd`i9}xGu@sHxS=ijsSCzd(3rJNqX^T!&Rm@li}$@?emlVIv2P{ zs@;jrXM`Y8ntcolDHT0y*Q2s3u1p(__LAEqaU2>l8VBO zMenC#H+|%Mz5Og;h?1ppkD<71M7KlrUxhkMf+Q4rL73QCKo55BX08vzRv}q1xrMp8 zD(-;&KRU*s^Q}h*dRi84d;HE;b&Q5MUkcuzvOdSY#Woz5W)rTs$~omh)kFa5K_N0T z-2wuFeT4o-lJFJ3hAo$7FrATkjx}-bmfm_Z0Jc)P%h95Mldi?zZAvF1!SnqGN~hms z#VMTAf%v_yv`YutANl{2VXHB=7h1673t$F;vuJ&2kVV$99zY(P1Q!^u?#p#sd)NKA zibGHcHzR1(OPGI6kawOJx6|4l7e_F%uIiYGSR20dIeG1&u@&}M?S1SF5x#0WJ_yVR z!J_yBU8j9oY37_BZl?YNv6R2!R>f042RJs9;!M*KqECwVYER0V-7-AlxAsc+#^{Zg zL}m^1bco$G!gpUV98Ft9@I!bSQ)X-S@sH$Sn0SK`tZ}Dz4e7lP(d>VrnthU%jLnIg zdAHL)z^o2;5Vp)HlW7hen08=pxpg8n<*e`(`@N5{hfqZrCu^*jora^ClIFe^;&tLe zKA<}Id#v38H5}*Cw;!lcC9>~sj&RvFlDpBV<-z3z8GHRq?zFr*t`D_aDteAW^}zRk zB!)Gg@1>+A)`%up1;{*VxWnkt{82(h-+0zp^Q@aT;}^b^6V7#`J~Aj_XuWFo_JG8f zJ*ellfOp@9b~|5(j!3Kh&?OU*WW+vXzxKYY^MZ}rdJG^Ud3vdXqr|phtw}0%$<>+mX3@jVcmDn2ADw8o z^{_ZzNi;y>>#>bprBhOUI@i6IZ?AAQI>LJQ97tlWG$_4#f0PVgav5-SC)K>He8pPB z*zWW(#2z~;g!dB-0b>C6q9HqYfb#5uzhcbU7fqX zILfNfD!gdK|9F0%Byp;7!?_dI)+@6W=N2}3m@QtgjQj2Q&uaytf=Wo-V~{2{<<3T0 z6@yL^kXNfLCw8lX)$dgwmh^jEE~H3E!0Fcz5GLL|w7)4%H5$2tbTV-A`A=R|-<%({ z%;m?T<)oPv;32V~%#WO9BWpb-YE?_Rk0g@-ISgA8#4v_$Um1w9O>uJ`bs#L5Q*&nG z(#)-le0r^)YON(@vBwkd$d4WuK0<(-3I854UVjD9AOJFyRni~ks}}YHpXMCKT)J9h z@t`=pwo_%LNNc@>;2lkWukmQOw_9hUj-ck`pS4dO>CXJp2ls)ijSVW(FD3=^7)Mmx z@f;9YwRbYsC`IOU=VN@B#v)QCjow2IC*{_JRuji3t;gXn_2hZ8wOPs>&cd0EKhn7? zkUvK)3B6>7${X<3iKRUy@nBL!-Kzf#X6BsxWvlrO#@^wo-gp!w3N%cd8 zx|n&G`ZRN}fS#q)O|=uw8XU`xe0N1MnrjM|aTO#Zir?8l&Xl#Oc<=r{R&tfPnEn)a z&?VGPTPUOuZi3IYY@r!Ya<_mySW-^_mP=I z(D`_F!?zD3NB+8UXJlxl$}PEUUBxL%rccwXHwd`_g6YW<5syWxzDCpOiNBuEp`aIm zAqOj0-1;fzenHnHxgAhn6lQw*b`>D;A3pd~kQ<)}$^OMQfM_Db(yvA9eiZ(i z?dHUMN5;7TKBkO(>r6QdTVn2f9JWZl3>f8fL)zo+D1(Dvz@{;bG&h;tUE8^8!5qmL z0{L1Sx7^i#Gi!#TF~#0XXAx=Sekn-KCtNiS$V)m;p4DWwVD0t5fwG4R@%b@q7 z0Bwd~(}+}Wijbabh4dHDX9C^j(nfa5g3^9E@c=@JmJch_75GF3$s$v{9ZB{(clTg` zWi9?R%~bwd^GgfI4UO9DONX{BRB8#{w()U}rNuCklC3EcwH z&L?5r{rAmEe^+@lPA(3te&KxAK#H!+CGf)UH0#`*utnUqW1h*VQ3=*6l92w#V;AHQrEtr;u0F#D1?-@7Cr(=h%dxQK;In3HQtB&|2*?0TI!Cor}+Eowl&*bz4n; zhns`00+qYT1;|y#PWeOsJ~=SVtRkC^)z~I}>>I4ICU|vx>6ii$4nDh@jT*XR3&Fye zJ1zjr_YqJzlv}ny>Eud>;-9s{_53~%?l^19`9@`7(bdxVe8S@(`rTIM>JA!aIRelt zf}Zpw9XgbaTO>?!*;hCcE!faw7ZdB{EjEGsKQK8^{R8MZ@N&>+z9BCq(rAW=*TG(a zJ&+v~E7+hOkK+dvzdtcF>ZXr)$y{ys#Gl~750!sKTz>L_-q%u7lw!>|xpwRO!7yJ7 zBY3Jb_w3E9uQ2i1=g)J$bsx=s(-pp_!RqN|T0wP^SK9^}pHdkWhqOq(H;u?3c}Sg1KZTvL?~nF_YG6%4!C!_ezBMM3VDqu{>1ol~{m z0gV>c2=D2iPz6e{;G8ZVCK^P(D*IuT<0VSrMIS|-HIvwU_b4#896(XF(-sS5#YBtA zNa8O@tB0jbH2#`77X?*ErFrn7o>d0;CSkg1ax!K*EZ7P^ym{NHO(vY}A7<)M`_vpn zxM}`<`!(~@%dk~YJ-Fsz+PPHj2-yIVJ%xB~4+`j?mCZ*fQjp`Eh2l*)y+x^&c?M|u zvthCYa}zHohEhtMF0Z`S+Bi9H$n#S6+6>)v#&I*`-iW_nGEpGWLG{4$n*>+SMLr0` z=ENeNKnb>xPTURRpQC69)=Si5uAt8F)o#n+FUfYlt?Xs4XY5Np@G8E^&jMf7^NH8_ zBT(<)%g#xcz-Ji;ySkg4T%2r*V`ykB!wHo6!PEmduKwitAFP{%SuXoD?xBQ*Q>{IP za8W9NUIB#qyEFlxDT#eoqCfHox$D!5&T`DyJYn~C|$cu)RpxRl^yRn&RH27wF{)t98Z8)ssG`>a&^ z2!*w;gMpI-sofv_N27IR1QaZV4-pX?(1%5;exS`2%tdS)Ap%IUfhNfiwTF4HhZcjY zDMXs?|MfonY`Wfwon%*MRiD>@8TIhJFBbtlgTLvJWMBZX0~u$Jz23yaFCNs1H6Fd| zYC6ZS6EkorWFF9TgEM;0)!s_sQf9BkDx@z4p5VKiq^h|RD^Hj_VGL87CFy~KLyeDk z%@3D+Z!*1qbEeckRv92cq7~6ej9oLn`|>k8(k~O#8t|k8V6vKjDzf8VWAB(NRa3l# z)kuI!Bsbya)BTjJhJo1q$`f676YeLw3;6JK2E(l6)Ih+8K*Xvgm?Ykx6U;m-Mt2s6 zO3IR-wLfhPFQg|2Y3FLW&^cC;lxI2qSXH4utNK2|!)7CY!>s}Ezx==TH!AdE0SxTd zjrGe!DNgp4e(Eiw{;YTxPm^RNZBeEF!+f97YxeP+k4JggL@P-4$Io>-Ij#k79FMS6 zx{5CilPt2i+h%&7g4URbP!t)&R?F8l9*y4%TNQPV&Au~Yww#5d2f1D%EI#p9o`h13 z_Y-XE**q~`gykU=hc0U#Dxg8#RsZieGW_+xT}6v zSQ?%o&X$Q2r61wR2G`)G#f_@fV~r)MT?6clv9(LuEUH}K(aRq~GkJ!gs(ryU6vPkp zcsDDkp2l1@3?LFm)M<7@u9eE|E@u?dhvc!~ky~M*Y_f^^RKW*=Sn!vwV0?uB|Vhlugqf^L9vd9lzHt>(m zY6xALhqaLVJ^s=0hAORfAb%ogS-F(iZ?Ro_11vlsHi7DQoww)Mss+89nJYb2cS$N- zB7dEXL`&)JJ z@$Q?_^T~e$c2?XWsGb96vEEro3m{5bmAF>+Y-TsM48|wA_+5tMNv{wHl7rfCjAE!N z_KE-SkW1f>*bCDwHTK$f@$y>sa`Wvz-RQov&qmxf13|xW+;aJv!#e10mfR1uIISVM zmoj|AA$1dMcWGdVdX#<%FhmwYXH!3??YkrYF+q^;4cdsDjW`=IKgl`9)n>ZU*DI%3mqg? zwMM>{Y{B1H0+>sa^|)Wcf_B1DMa>SXb9*y+;_P9 z!J_-?KHT*(3us;Y8zEl`drTsmEE`3LmE$<{gxM)r3Zrg1Dsi&H+Dk5NVJ8NkH2s)6 z3rWrkI7#MTF};_W9VIT3(~Ls>0c(P+KYEv<{wNv6JFRPMFBvv{LHZbjEjodK&!5{v zGsK3-`j&7}I$*P}J%z42u5xBrBbx-vKyQG+)rdP6tn%i6o32Fu{1v@uNEww55NI*u zp}$w>w!GI6&}H)2tCrUxS>8LUcp=`7rp~@Oz4}g_!=>>RQ4S68m(VzTxG*q8W{Fcc!gZdw!r@^bU;Qhh^DdJ+Qu2&HO zz6VYj865qZFej{>Vk(_TRs|7fBh?DS#V=nWuJGhRx9zHy2h^QD5!CfB6c3nY6sLsD z34(<08dEmnXCs!}d;VtD-?PEjgFO)s3h@*XNbO9j2>l{#!E)IvzP-GSy|`IU)MJF# zAlKMNfHV;(p2PD!1hm!|e$4c+P(Eo8>h$qjON~L<3pvpQG+pLZSM(mC?@@nK?ymeh zlMp9_udc6>-%z!?cxtXRUijYp3~I1;Y_**|>}I*Rx$rOMUBdM~3&-xN9kQ+cRi%Oz zZn^<~S6SZb3Sa0|GQ6*KOcz}aHp858gz}gv!hRlJf6&JooS3gv$*lq>6|DifF@seV zxkuSv2eR_R;yw>mOP#F$Tqb=yifvBMoDKkHab>&1DWw6!LHHgQA|=X#r7*uHJ&q|t zXQMLWX)@xBQ!BN11r9-&hz$D?ll>=~NQ)5KNgfQ{iuw+>dLurXL#TQ0IJ5n1lw&Hx zIOMDhh+XJ!y1MkqVG@Tt`6Lbzd~91^Kv7tG<{E4o(;B+r(ZdL&Dv^ug+-w*dRp(er zVUlo(i)|iV^m=G3tQAQrAwwjj$z;o@)Bcj+jVC@D2KtlCD)EN9qp7wJs8?yUDE*FB z^e$8{CBWX&BTCRN@%;)pnQS1typg+AWt|*RK3Wr0pGB<3r(ssizX+9n|t#Dy{n?XK0lP{3j3w<_l46Iq@Crf2CPHh|R(+SRd%(ho8`Bj?*jZ$uGX3hFm#l zowSCrw;QvdA0wo(D+vj1xK=>dAl-6X)sGxo?zZ*=Y74IkH7&ZqC%|K#z`o@U&73MQ zs&V49|0|oF=+`i0Ufo?~5Cb~Ixw0{ki>iMGr7af__4oFwf`rdNvD$&EUXM^U8$hpY z5G2qDp#psON)W31&>dQplg-2xt_2C%SRGaQYF)3uB6Rlgv=8Kca;^b`c(*a)MCh?V zZ)!N_)Y#0+G_%1oDji)F6_APmuEZM8c)|k`!1NX}e1xrsxO*iLj7Eo6zfu{nn~6F| z*t=2rYA?1K-L`Q$cLxX`e)PP$vtfNVKUmJOX=pD@(o5elN0o>D-1zl?B>@JfX>tHk zGH(Fy>p^kQLh{X^ZJkj$fa7Ap*d81#t0kvk>pQG3*w;#vhjzeM*mH%6KSWDMa|zWw zlc+5R33Amv*n6SUN1i9IC<~LEnr2HL4i*$>Iqiwss=A4c3BEWhGx}odWok6#vYC5C z_+{EQ{`LWp9^c1(lgp)+@mr;(yZ_N45Yn-QFmHS-2*=Dk<+OyE5j zvjeM@3Ww=|GjlEr3{>0>L|5j~)F$e`>Jnr>ThW%h>hdiz!V&Hu?PP^ zxb~hsPI9?Xx{!t~Y=46K0rKlZnj9-o?r@2T2!4tLAB5l4&MU^JJc3)}#iJtMF8=YN zHdBK|hT)fuKRtT+iW(g;Q?P5Uv1%Hu_1};H^&IUjrW^L&VjDcschin^V`w3`?{B-T zIt#9{GarzOz@DjxRv<|N75n~f`+`o76owx+w8ka-ZT9{mll#VFqf6codR*ubek;*9 zK=ohP`fkpkgfkmkfE1~hi`|UqF^cJCNQbe#S6~@Nu6&r4YA4l^(3Q=mAxZ4!>|2T9 z@OF=!U!Qlp%dbiN^dzh%;*MlNbkM*C%CECE)F){hbe;$^0l!&@t{8zq^WFeWpr>Zt z0P~HcXu`0JLeS?ME4j<)dEQ8WFf6gv8^i2#PVpbKR(c z^aGWrl_{LhFD%y7#55LpZjSa-aB|B$D|m(#yA z4H4iFpeio<5QYIOm9#02W23+fG-24gEpYVO(5sRU{Ug_W+0IShu&*4}^5Qtcl~eaiYco>mXD;Y`yUb zxle=Fymkb%&)0t-87}J_Qy-Vty{O9aPH(yt%3SNc-q))o(>beSYN_qw)W=6xWfI$L zk<}y=AOCZ*-_n!SDIJmQewL3GfRyrGp+W=unDjai#V2l)6gddVgUNL98!P&6)j(H5 z8fT5O5%=Hkcm{=gO|WTuPjCe9L|b%!=>3;U_9O$l4@a-p z1HJwv;t<@FyFr@>j;y*NJMA6x>RNy6>6>;UiHF)tb-#>bUsWx_169>}Rqn0up;5D| z7ig1!FPIn^Ykxj3buzdwdvC(P&H*ex3L~@O0FEgES#OE1;KCxcAnA=aQ9hlHSu)!} zspr?~YO})J$vw%ZxzzGE_tE;=YlTjQejUs`^yHaK&Xahn>rkkqA z>E5?@>#M3e%1oEZH&zLIsPzIvc)AfBv53!WreXJQo&1e5w!v_aA>6_Wdu$Dm8amCZ zsj{bH!EdbKn)V}H2P3$m;6G|c8J>oo~EJFw%{fqAzLEWKEoRX#xEbNv|dA*z{ zCGS;zX|MubRkVf@>%ku14geRU?#O9HL86zW*?uy0&-TCX*xlE1Q6}OjCrhkk38?XjNO33D-S&US+cP{WyoI=R{o*CLfKZ6wtTkLhSS(q z2+Ue2v>a=K7C^FM@{6pL)3x1vFU>B*uU=U3B_*=>G(XLIdkYyPg%r{vYVN9BwKK(Z zXefZBKg>2{IQ$iLX}BD@@i^nc{eq>lv~$7NwU8ohQELg z)89=ukc}%1epzr-9HA}1XV_Feov^uKO(`zLr`pjLzGaWkCV%`{2^Ao-RWaBC!p}eU z0?(&k`t1d&K!lJTJyEZ5rN?Rj*zG>{^OgI-*Ni>JadX|6+>3=`f{nMrs;9D-;bagj9lFl)>RLy68n%e3yr_NPTwbgVRuiZ-4SfNY1P+xeUUjHujJC$H)UrV|0F?l*GdUnH1Ymz)5oh#*(`ie`ijBpxx zBwgdn6tgQP$4RgcsQgyqCu~(#U{C4`mLra&mo+s#Rp~F2ipoZ}4z#&uCGyQ5mw;u0 z@LEg<8p0!q`omYIuay;Csy}^w?_Dqa-;QskfE+CsBG%2HE9C4&qyER)6NajsgFQCk zw&6b}c4Lc}Z4j=Y%kF3~{m<9M`J;}4`3NcRq{q{F5#b8jvNn?}%Bs8mm3wAk_iA!; zpeJCp&9yft{91jd&8G`gz^AWW$P00pBhFG2UgT%;u5=}CesaKO^&LyeTgO%PYqGH= zkgS~mP_7IiK)JSn>ToNvhCE!af!?1SA;Towf#5^koF^6CKPKO|s&7AkQF-HvJ2fN( zCbnErS9cV_&2tR%k8I z6%jtc@$>3i)fY91W4Nk}LUZjpDm9f|KWLK6>cgr6q_!)HYWZMY7dH58Se@y%reo-+ z3)27)zLG$fPg8H*6O~yr*TB)Y>K~ntJy5tvvx7Vf8s!AyfPlwvhXQllfI{s1qTr(v{k1Vc8kUUP7lfWgpIa zgaGJY{V=37^qM-k^kjR{sKll!e{}JO6Jgnhi%&;@)NG_r{JnpuUu0kPc3Y!W9`r8~ zYI+jv@%UQA5Y*C;bpY%zR9Kb+f0pv zs!JB$i{4!ct(2z1t-&FoHiDI-(OaP7KWo_d31iJw=D)#mYnznBsC8Anyf{W6%u*Xc;M)>6(Epq^fI#$yKht2964oWNyIj%a0L|_PiEtmK?vA<(WsOI^q|oslG3( zbkL((0VI1%NB~@LPlas?c=SIytO#^A+wT?o2oEte*$( zg0h?fsv90*#VozjMH8p}W$KRhqo4{B&j2w-E#bLeil_;f1lVkXb=2}< z|9iy+3}06{#<;JPC0? zr%F9cFQyn+{Nk8vuy$fKcVZS*!5VPhhoqL!Tjihgln)q0d4{`UvO&0*QnRTrRW{&i zOX}de#wv)(*y<{ACP?{e%paCL!j_36V*|XqxSUFn5t;IjPT!yol<9#QHDZe%g7Y&S zAUsR$a3DV7Eviz`B#%DA-!np3y6u%D|Es{cLX3l)Fl`o|SsR z#txT$jDCA+ou}Q?DDon&%}yCbGx4YkKo$g6^x3baOBu6D8x^*{Fg$_%BO_MET^z9Q z@?1f!+;-V;bfsA3NOkAWfgMMZgG4t_uP>fE;!r+m11!|&v8Aa{wcVme}PAq%1G zsp)2@;_zcRVHuM58k*~34UZ{B>HJxz3s0RuvXt}K3K8CumLEh<_ZV^kN1q;j+xG_M zvL1W$K5n7)Lsr7E8BYe$hF$oNP96bWk}SvF!hQkXVZzf;0>S9c%m4_>pmT%-CsMjy z-QndERfI)j+vx7z!dLM9iMqEYeJ0axIL#R(D0KaewTVCCjV@T#T_?7efMe%=g&Z99 z?-0NY@b~lPPCej$Tca&vt_hV^grF;?z-vmEe5~r=Tf%MFLg6Fyuqx>BGqRqufz@jm zz0!dOL@CV11_EL|Ipfw0t4sF%Z~RZ4@(9Z?8pxODk){gZ)+TAvkVE)Zm*pzm*O*N# zb#GQv%{C;sIeU>n!b@HiyKN(XccIOF=nCz}fqZdg=%?QBz14;SPT`uvrdZFxSt#JJ z*EbO0OIJW-Fb78BF9XtG^Ne2{dR#*~Ih^gwQBf7-o7R>p-6ZB*4~46fYrrnJI!C^w z`WlpqtqaQ~2)h`*eWrEV$wa*roG$PE(SErJjL)Jh%~sS|Y-hW8b9TY43OOyg#xE5$DhBK;s*kB;)M6Rz zt-YysJTl<_S2WGO0r!@RY~V{_3pZV-un;B@uX6xYS-SoenY>rT?~0u?ZK(|m_p|)0Dl|!lk+y*|IOjo87J*m z9m9O9K!U)Y4u^O@qdUXquT&^W3~+t4RAp8)b^;<(+8>gBFv?X|DOVM_59RGtX&p5& zZ*2`HN9dESJc2bQZQ3>buE6H!HzVj~fOpZbr4G2GD=pPb?gbKtGS5t%Aza78C$UKc ztr?cVxOac`HFolIjyc)|Y65ZpthN(&#>uJlcqA{z`ay-;>JpECHuWT{M*h|C!XjG) z;3`IO9_;5JMh9mjGO0X3YxS(EPxI{ck`y)O>YU)=H_?OTQT2zb@%r%Q1lx?OSI*bh zy!41^_6VED#Lu)P_1Eu@q6F)I=l)ZwT*~NVSRhRBPM@gn63DES?$hIeJOASkOn{CO zsfBt`@~K{q%LECri*{gd|4`ZagSUFaa@tyTtt#fu|?C5zq6cVK4wSYJ~?z-Zup+KF0&e)KhD zHSkS&aEaTe-Ne)b-R>6uHO?DlqQyvItk*%%4ZQ1-)RecibM{RXcI6Zh5>~F6X&W!9 z>xv$$JqLyPKhJWiGF|-b+to{(fGMsBaT-Luuu_rIfpvp4PwxMssWt)n3y1&7MVGlM z&+zq~U$%k0HLHFYl)9%c&utSlz~w(nPJUQXSHAq&!-}063q7C(TsRn17Ze<_b);ob zCwOK?Zdjo-LeN28<5Nkz+_oU+0)RhFVxpFU52L!9Qp@AX#Vfb@#37J{JFi%LXcZyy8H#;ZB-T%#u$CcJ!Iq+Oc<*Vs+X@1}cpawr5 zHCnljTaoZK@jFPqFM}nwPn`pa!2wp$n!?zv`b{2~eOb0_rfwX%Nmx9i_WTEAE zPKUZM=t@_Ry>4O*ixlfs;1;cK9Dd1-Cn64YiqQYy*@5PLTsJL3!IJ-RZK_zpisu?f zWez&Xrkm3W^F9yJuL6}mn`HqX$6tyVX^l&IPzhu$4mn(AjZh3;2en&_5uRiTeZk(53{x zA7v>3t2yvGAsJlO$bOK;H-bq`kWZr}5hQS6@g5VtCbAX)e$zEX4%{9sGU(tpTJ_MI zr=Y}D{K|0w-}4e>8ho6sD|)O?OiMt2uj_O@eGL zrfT2kcyqOBt6`vdpH!6a?c&{tE#I$eeH{m*06`=!y$J>qc)99FjBR5%vk9zJ=u8?` zNaW!SNV|!zIF2L9>#%y9AMbvcylJ67aS>wKJbxj7 zba`x+h+qBtYj3BTWB+Q{LrV$anXBj+FtBTkj{sWu+{mc^H?)_zhI>JWdVy$oBoPwlowS>X|q?2qyxK4ran3QXEXZtw?gFbmlzm?$b34z2jY!68GB!$g&= zTi&1k?07mxSTmhiE*}4Wj%GdFCy-baHO1~3bllBXGn2A>wvVWK=w5R77A621r-)!T z?1zz`LZ`qY0mXtAX9e2qM;E4N_xD?uQbbtiEB(TIy>kY_i5^?h0)SVVKlo9xL?df> z&0qc>k9IY-q3?#515^jYoaWFNr~xPD8fHoenzWF8FqANpti8sTwGs(AQrQ*hVXxm@ zp4#g1vbrk0eEW&@cZjwNRJGvE%e+nQji~fDdb}!6yc!pN2FkCb#ZgDoNtMQ%qKd4|8$gf8O!SPAX7?lAAKQVQ>P1?|ne~vP`n7UJiHz zm3h5MABj<1IDNMww|vdj36TF)LWb(Zfr6;2Kg0C%FPpZKp~p~ku*pnc< zt2hnI_F-?)FN+UHaTB{w`ww`&q#s7EmW={!2ctqQaL-fmlB7?`Gp>TGS^&fgEZ~9h zXvaABwOL=rQ2-f(!?jeaiH!iG)-h-#Wxu8h8v|S=BRrgN!}vjv0{*L^J8mdCpC&G9 zfs%5DS%tKa=0L})J437qXPhP_a0?8jPid}$dj&!$*=|#&TbCU<2y`2~uYJwTbS%8_ zNTq|cmm+0WS3wr6+JdA!qunvBquERbciMvZO)|@U+eG%AyC3k+z@1MiI%GuS` zuB;B5(a|Jz(tRG20i}e)&ZpgNx`r~zC0_#9b=q0E1RdzFEJ5@?w|aF)1vpZ2bzF%J z)pwlI+y|XfO3TjNwn+5JxR?2L7Az9*IlLeg!)Ot`wB$n{rX}oKkt}`22U2BGoEy^Mjouh>-&LH-n55K3hD9ydO3?M#&7Zd- z=Pj&(kAkd&ms>Z!Cp~X_e-*|cba*RLAT`_YNipP4M)irykRZ^=vxu<{Iwm(~htvnq zE}S<9!_sPj-s}LII12jkUFimzQ7&-UbppuA#l6h$KVv6A`VqvOGy+Z*nsO8F6B9Io z`#<;_@J_9<|DBGnH!&U(_ltye%_8^+H3iG|NySB-B9I3&Htt9I;0i@3JJo3@A^`4(ettdJumSb z=y_%Ip;^qAY!x`RC&0=-ZzKl$r9`x{6BuoP!y*|_sYp#i3HX{mjLtl(`;|ET0E)S# z`gdod)cV}8leH%4caIW;E8dQy|2;~P7Uu%)OVi+-ScC2Oe#fVz=O}@KPD7rcEsC$9|N-W1irADcau?S_6O!TY8Wt0nOuQ9G?pt0b7y5|u|Q z)rAajG@Vzus0-_@g2TfLTb$2r&yO{b9!e`1yqGFzgjQ7!T0MyHOcgHO9?8`6MJgJz z$eYYf-E*&FN`Hun(aS=8gMBjWLJ-(1&OgKDFY1H zN^B!lxVtG!o|Xo#kPfjqh=u&;bFW7{Ufl6@4uC~IS%wQd3rc7myd58{j29J~~LM7@7Q61Be6~Dbm${ zsiGbub&U+0uE_BQ%E8Ls@DRWVVXY#Dv7RGZt-@WqL`XEBVaD~()vj|_hHmIR7iUd9 zkbS(<4!}w4!Lr_>rbwtNqaJi=E)*fnEJr^uUl!eTb}9AScU1}?^Kn3Dg;{Z{$n^=> zeMMn@S}B#*#H0099QZ8!xnQlF)~MuP36FT~iuzJj}e^;{eMp)npJET|EIhmcExP$h1#y2}= zC#zh+z z$vc~9orTOt^fO3A>6l>CTY0g+ixsiHK61tR+w0CJY`TBP9`%L9Z@5A$iO+R0=q+p5C`e3-E&nD7N&Q6-1eq7Cs znW?CE*g4wcD-;b0HK2z@&7zdR#fTkHmWB+`Ewo%uIHUt?>wk4>(=E$?HS9WsAW*IRF}Vd`O&o+m zDXf7WbXlx~xteYU6H@KchBPJA1dajC6SD^G=ITvER#;5rZ5GgduAY5LxcN^*dCy4S zKV85#Jai|`$2I43lb;-yoYys5gf>EAss9@~NQ4$SsT-&qld$7R0YS0^PJbyv2q%($ zxlzenN{>ar8LzEpqCD~{%c#0DG6Tam9Nx(0eowT+uGavC=oiHOTvp(x|!(8YwDL4JzPlvHxy%vxE|=+*n{r^C5e$T zoR-AD;(aalOX|FvP5nl=m4p;;a&V;PYgw)0U!}Z@tFkm92{HUHYmBvsoR&9rNRw>nB3U@+pF$5|`X~KTQTto4Lr^tXd7d*1F6oim~sj4JPLk9?EdIx*Y@{7#>QKFvN0PEdlSAq zL09_6`7@FAAUl_>-P#~%WG)RkTjQhWhju|)Q$_T;E%?*Ae=GNW9K780;z#LH0C1D;#N|HML&pJzyBa^$-!xLayq#L@dc}3GagSjhfj;xNj%6H zXhTKcL^sifr^NsXijk1?#{2zjN@bXpv_nnO!fcu8G-m(Ps!GR)a6?Otq`z-+qAaAL zn%!So_D|)l_j@mC&23b=jtj9V(D`Yhl`s6cqJS2E_!YQCcsVRO*=1+y;^UDQ>%A34f|uU*cUAQ zyh-jldD8Ul>U8p^TH=JN>j~bzXGm9d0^lKlVgR57Rj|4B{Z$M@G zPJ#Qy#TU0r9TPr1(l9Ruv)&MvXV}XXClkt_2O7t^6m=6@U2y!ZXUPh;_41=oC64dZ z*4yiPdqGVvme|le5>x$+(-m%4viRsY&0gOXkXP;tzA#&W=U#eqpwA+~qSDfyTT6VUl-nO=mMma3U#ci(3(jlCX zFL@#zvj040=5wNRvI;Zf^AOWQMD|clbVjm}4b!SsOU9dT`?nXh!4aN4;oYBwR zi2vkPK$(;Ekjw?*51Y!dll!Dh&G9S&ssraGh{tCHmDqT8ZOeT-ejZoau%DlmgG+G^ zcrx(r44kRj=;xhXLfl*x`*hTiC=$V;n2D`2(6xzCXd@ZQ{iOk&i|{i9)oOi zP(4qQv(OIE)^R}dnJc$ws$;(Vf7t46`Ty~ER^L(Z1|L8dK#U-R!ksm_(foFSXbi<6 zI#%*yTd=q7ay6m0t4l9J)S|3{^ToWR-Uo;nN9f_T~lCF`#_ z$T5s5k`tQjeElYjZgt(Z{;=_H?w6gUf11GNLuo}MHK3-52^y-ENV2dfiDiRsQe9T3 z(PN9;7OD$nq+U`0+?qH88q+(3lc2d+lt4F%ytMvCGY7Z>FEqYc4n3dMfYxB)YtW>N zC3xruCUCyavJF2O9z_*e$pWTtIeP^!9xLUKGbO*$HMx9%mdRtR>%TiuLbYJW^?$aa^yy5W(3RuWR|e_mrIIoLyo@{w%M;|3S4!i-NY_`oF#myMzQ2NMYS;OZx2QlI;hG=Wq4GrE^ut7R0!kQnH!uAv29`Py+`mPXVLvqu-a zJih(*q4T}RBth=|oS$|mT`&qP`2r{q5m%u(rQ3XlD3CCZ_Efer~bl8N@ zsAKty<)mKGYi1(4tp{&fSVLKU|97$w;!Q6kN-fy?svf2u`eRw}y^eX*-`{^=aByH? zu!%yZtN_;G;mhYod7ah61f5pyit*eedYc<*Sc*`*mO?G4=yF(PAy?DTj za>H*zdmP)#iZ4Gz-*Fc?6C;AkY<={FTs|g!ZmZa3^Lyp7s|(#OM~jWoEyI<&j7PFg zt$%eU_dt@`mVQ;nT1wvmyQZO=fZfcJ2NJ+y)@pJbNv8)%V*|aGWP^Ho1&!)a(>L25 za_rv8A#2-7`|i(Ekj>srTlG#ox%2bW*Ji=1(F$avccbl*d*MP zlhK;$OpXa(1VZ&I8M+jl;aU!~#<$~A1y?TP_cQfE>f%qEEa57nkz$VJ=`ru|%P#l$ zobo~>={TH_G5dtvujd>4q}5)_lhSUpC8N?iS~PG5JIX%@Wk>&Dt}X^0m$M$}QAz;Q{bjX+qP^EePd{Ofk=iSeS_=_035IcFxrz-dMn1go)JsYUY zT;7*UL0MP()P2&CANi(lRtKlHAOv%TArj1FhLWJ#4kS}Fkz|A^A@QfReQ8CF^EW#S z3X4K3Y_o)UH>5f)n_r=CGK_ol=l(K z9+|9AS}+C)6%Z&xFA#)9GS+MAGTL2*Z>9VYSHewT>Z#ntLq@0M#z>VMcO;y8tY?Oz z^rt*HUfJ&0$YX6vf8U>V<;Ctl#Th=|OcfyB#Farj&~X%(&W|*b28_^UBToT`*&2c1 zAjMV0PC`o4Q!Ts8SD<^44*7{T-mpm*YDM=!+GU~@L;FfR#A+|;a?+@95#B;DLAQzt zIWuh4TUyvVODRb^hM9F?ef0q!@(xgB+X<_|VK+rMPgL*YX!uMzs7!%pAgWCQKbEg9 zQ<q*KpbSw2=94y^fp47}Tr*Bc`pOJ&C5n~P%LMj={;Q(e+t|+SY9+kc;mFctH*W1Sp`D@HcZlo(^<>XP-W< zuxp?mgx{x?AeqhAI6oFQ23%!x`)4PC2v%ixWq`rK?otbGpIt2J7jD*Yt4H_Dpy}0V z-1M|%V)y%Q<4ZOcx7G{^RN&2vUx)xpbkai^&gfz2f*U1qCYZfa33cHpim!$XhbRDsNrRrW9;5_f*DMLeMS3@&Pb#^V=nF2VJo?p@93|i*F3Wppuf-f z$cTV!q*$-6AOu4> zF_B}DJ!x7@b9I&D_^fo3TN%O9lIi#tNd3MTF?R{gs?l$l`IHv(3lcBIVw~|7y>^tzBc@{EUP6UK>eS-OG>3THcGdpp zNKQwiNycz);Zb1b$6P?;U$ka=!F_$)J2~lRuw$*Hw?MVuu6WFks?sMM@NhcDdEr4+ zR3U`IFt_07%VD*Nxwtrn&1v@{(RTeqHWR_Gs;cwc-EUabwNn?B8q|G6oyN|>PBTE zme{m^H7<6nc9>9#JM`#KXMKdk#eT3kaHD*x6u#o3CGlXURK>w_+EF;U_THuVtp=moxax#Z{27qItRq0ja9xL<2l>T%BJZBF)rw zmG0e6KP$#1nAml+U=2V%;GJ#qhvT} z%-K^9fS>X%axHy$bIl95?2*J2tNFzvB3yNNNZLX!7?w=U~4_WnGC zyZC(C&^W$@?VTl9aG>+RD1fPgrTxcYqzLW6$Sl!^ke1c=2r!FrgbSI7HLK~YJXcyY zSqf}%Nh#bj+|>*rXEzWHv6kY5yMICwBpw^6dShI}C~kasp=##~IivU~LXMnHG!%YH z%vC(l7od7dx7@=I!Z$xS%Q6GAc>;X{!M{egOb=WcZy~@(v1!~@DkG%>Pfp<1nm>As z7%3jX9lY4BjLidNj%lsjrB9S#2dzQE#=dz0O%v75&*P8Ze>SZ<@Uq-}B;0N?T!YZd zQk1E4@aBvUqKYDZ$3VQ4FGvC8U^*Fo&>HW2HbOjfq|H_C>}NOBSK13we_`H}g7GWf zK9>}?TP&GJ>Q83m59Dlj(gYCas{hChkl950xM|WO|aTJi`!h_NePGLSY1$%?eCXBWl%M0k~B^_eA9Dyot7 z$`$B)t^pwoE5tTW0>2ZHcIEzOVci3~)rtnzShN?v2#Mhh>vad$R0M?ZQS1Jxi4MJFyr$6-z?!K+PRU?H~ zBK}^z(|MmqZ<;_ERa>i!x3eDJ4$c}Urk$9f0^h>6*PM_POeRDRmr&GdM51EF$l6x6t3=agnG31Ot~&)$7zaDPUZ0f z%u6{_V{XyzP8AwZe$;C863G;#M)_Pg5QE^7T1d(GHQ*yoA_Q3o4?CdEa}WqO-v`WG z{xw(L)>&(ODD_OzNrbE1H(A6)xgBKPz*d;o#F7aq(_9yj zspAmK5!Kpw5TEMdNzmxD18StCs+IS|(9*$J7N1~*UxEU)E+n&vcB1j~x;eJ_-Y&X} zEGBc+D7xnQ-(3{P>16umVQu!kpE!Njv-&=R2<_`C~#s54nmS zpz5Q`=(>i9rvgB8wsbNvp%A$SrJ-hUTM1Ip#M#+nyXK%QWRONxx$fsL|bzao~0)ao0 zQBTOHLEOy*96+ABo=e6ElN8sGX{D12k5J@-R!!;Az*PtcXuDDUI*bu=*R==7*l>EN zD3N1M@jL2W%grP;`?Wk+Yf?26_ZBt0U;61nMK$^rSdsn(1&Id64+7L|Fma$q5!`9g z7)}rqd6`KhZE!W?!M@Yq(TDL z1XnOpIvD8U9~svr^sNZ?V#Z-$2Sq-7R5*OUQ1qsZhH1r{)utUlGU2A??w_pl1u7ls zg{pe{U4et!uhjNuJoO6;Tr1ak?%hSX=JM^-=+^;|dI8B?FsU6PWNY-qsI2aXv~(l0 zYNUy2eCk7({WrwNmuO7?NuA5s=+u|@$s3O(<;@e>3xiL)+Bj$ zf1hNLU-x; z`Ay{YH4X4*=G7o|ijyyYdsek~tst|~a<|9cuXlQ>uh32)P7dT#rw0sxkvly7ODBHO z7hzHw$TS8$doF?kK34vA@U&{5ytC)+$IZ9TWsR$uV%DPlPp7yQFg59(b^a@NC$8?8 z`NqEI)>Xf#gC1XZwgT4jzNPdC^?hzAD~_?Y7l_6*6V`AR(@!J5565Z;0}9g$DI{I` z0$}(~Fl4je6by*Mz)exrg9k%Gq3c$5Ro-AFK1m zeXh>nqi|_ye)>B(bguW<0#X(M_7#2DF9q~A0NR+ALyDh)uN3$^#HbbU&!!V{Kqr@! zOe|}axNo5Y)tyF;LvIil()lt!Rc2A&Gh>?Tj;k2G`=}vjfz$p4ch0Ll9|PE^2;fxF zW!myqblEbPqPU5Wq+N7w0@l4;C%ZA)qX zX#cOHCV{uXa|5{*MUDSzWaMN|Bmu!?CcB}vkpzH7tELzx!*m=nQbOD1Lh(n!k$WGgJlrE|%Hrqr7)pZ(n;Sq2QD zXtnb`Sms_n7z1jKxb3BOSF%iR*46>JBC5{!mv5dr1{CBw7~SQg_bZP9iW7L@e}cI^ z!dQW}lJuO7l?NF@u0HVl3Xu`6S54yhd721SJZbq$H-0g)k?&~MHZ*E7RT9BUqw3R7 za=}gGgk9+d>!5w`r5i3X~U$OFF>ja#ueydxlM&6+z8lT-9kP=wZV z33f5DLC1P}d_5?huj+x)7w9C!X+gYNh&`t=Fjf51O$f`pMKXXc#AGgDCXw_xYk(XL zb7g(7+_agqvu}pEdNk+IqesJ|tzJlw1X>xJhglPvvwf~pG`_QAb>4%^&W7LKE8@6p zlmClX947&GRNQ3*aY;@?Q7bnA-P4U~Fa*~;GmAn^k}~YfmWKzuCKx#ro-|t9hFeJk zZv6pMy!}2WoZrwhgEJ?)m4q4QSQK!RX05gSdL|=7I{($kVI^?kOQ5wBuiO^q$htwe zKom9|08{Kv{qpbc8uJ0>zUT>HnQ6I9q|K!&AabNnD`P@Hp zfCBr4h0shXnBqjjhY?gq3wYpgEJvB9T!0{|dS9SCG?tZ9T3z*&z8Q%EM%sZ};#;E4 zk9vLdr|$#SI_55>twtvFxy!(cXEe|det+-jm|^u`DgAQD=V!n|XcZ3CaY5SURK--i zx(EbuaQ|iiBrOJVB`5#DF0o@SP+9lS02dHPjA|WzH*#D;(xyccACnmvn3!C7vYSP% z#}*>LAI|O!I1SoEMHWLZ?q~FM%D$gapbOy|3P&8PW38BSF${rK!Rt$zfn)hu*ri4uTqcmebUk3P^zXUgjg+l@dIJDvy(Frk+n>C;h!4)$VOK{Tyu!)(f2qmCo{s7tF z1`H&u6uPAtkWy4+p!IDVpw-OTd-qgYW9=_OOf$3uc{>&!Fr$O>>uWwSimt7zW*hni z_f-AU-?lDQbGw|82&~)NSt`>XXa8yBJNS7O#0`p>m5j;90TN*_qJE|V@@TtpjaOA6 zyFy3d{vK&UHOsu#nIs{u*yZw#Ci4il1z+yc3TtYDU?6o6Jf!<1I7+{Pjia>`40JtX z5BM2~>M%MW9lVyx4LJ@Aypa8Jr{>|kf+$RC1;Q~kJJjk6Kl@@>?RNxmZA3S}ig`7q z-SI1RV`aknlX~@KpS(shf8b|S25r(zl|B&Y!T^==0y)%@w?ICZ9!W|e>8Z$;$*8G^ zbZh-?e6GyJQ*f-jdcQP7yQ%hBpz!&LgI6+&{@kVa=+*B|+ZYZ1N$lo-V4(azW3G)I zDOZAK2=6aZl|ze2C{Uy!tuPTdI%cUfVMyB1!b&FSJ8Of!umeB23QP<_Z8hK0>+G(T zbZHVutpkCK`;Q)YhU5F4T*6{}s;k)pcD3w#n7P|ZJlzBqOpL-o6qr;!9zGpV{_WL)saPz&qtAACp#%DkZ27(Ol z0D!XQVg@S|>(qtN`I5mlvt1)HQsZPuQ=Um*m0MZqOsP?8PGmL50*NXMtMK&=^SM2G z4VM{7SQ%D(*N8%q6Psz8z5%F}2U))We% zrf{^8-Qlu~po4HylL$E_5KfAK1?}Q-hCW>VRF0JJ@TT2otKd(q$(Hg}KAYz$f7%6D zxyMjkp_w%5lnD?_VtodLVI7dD@WJgVd;&70r704SozhoOFiXg7KH(ZG6|MS!T&>>+ zv!tINQK`1!v2sn`6(8Zj{!FRbEg?S|pyO3LBdInw^+iY;P>=2laiFO2VAX#P0Ik#| ztq_j~E%5R|Vt`lcpKv7!W9NPp%-;hqO93JuPvm=5=Ud8N4nw^;| ze_HYLoS$ndAXkVEf&SMMAwSZ3Vj7c3w}zgQKJb6bp(f9e|EcD%QFZ7;%pPPvoFvJ7 zjZ4HYMRvm>Jaa5U1zxw@q=|Z*n7IfEd~?y(L-W9k6YHz1fsFIjk$ayTdZSDS%A#CI zUN!Z7QT}a1M-)?2)YnX8Pc*u;T|R@eq-AObQLG&8>9Mg@ny0i;#a@3m>FGS*tUlYp~zOb9fl%{g_J3AnRzQL|_@P2_otr*eDp#jhr-gD5C1 z#WA`He~MEWH|y9g+{>JGOx>c1fG`sG6+F6zQ01UnO;rfM9l@j>93q2WW0Dy9-S~xw zk>tQUrg=Owxtx~jL1`SeLK0YkVLNdNbd1zx$7raLdcSE1(} z@#fmS98;A!LWHMJ)x50l1Fpe>*BtFWs%>kYhyC;kn4K_Fx^jMmcxP<5dvv0xwmi$< zwWg~2!9$vmLOXA5GJkT_0y!AWsB*PxGZ^Jf##@3B_xyBvwbI#IzQ5_EA34vWx@Tfm zCqgopVLNAp>VVeW9}R;h6pp^&VP)^bWMa5^@p-mTXGSz|tx5#|ZCgaZ=o)cw0@kKr z7Zs{ss<6sKG=k!Z`VVIx_%phdjsf!0_c-P9Cqz;TVcBd<-BmlMJ?HL9ZuCq7K-6Gv z3}H2Gn_?`88X|SE(Kc;+@@VYZI|pd6ebs{R+2F8n10(f9!96eRjlH1@zW?`-;!r@= zC}=5ou3Cq*O7>iYB#y{QY50+0p=n#`c2ScxQ0#mVd5Zjk*3@Y9*$S)Yd&;-cC+85m z@JsE~Jr$nTgX*jTkF8>~bDSFFqzQ`h1l>Yz_S~=i9Vp6O<54TsQ8g29<@#87dhp{H zVPZ}Al6ZASA?bUgA0dCVVt&2FS=3hmv6Oh{}+ncq%`cSfmVP z2nd+S2fKPsR7&Z5ape&(Kw#fu1_e`*O-YUF=th!e>1^g2AibHeGErp2*@JZYGuP078Y%l; zq}slTScNO1Iq|N=PJS+UN@lF;4ip!>f3#`7pT!#HQDQLTW~&x_;;v<^q!}oZk==41 zu|u=kZWc-+88gfgt|U97@=MN|vNGr0vhXF!CSlEsS9!x@PA;CNE{1QBQ<&WJ z7L>2gTEzOs{Bh1M@j^eEo5`=d5>-S`S}GmD&}j3$Kyd?k@`KWoXikX#`9A9c8^56^ zlDGhwYwR8#mAeK7M?@9SZrT(_*k*T*`kh`Lz7c5oZ`-0NPeYgTz$zL-ij@n&4%#D7 z7^$DzP2a>CKPDNfNVj503xNY@l0;Dsv!#_y$9h#8%lwe(M%1W|<1h~ZbVya{-pc(2 zr^&+n8}1y8ldJJGZ~VUPX-!1^R6GPG48ef1|LI3t?Y zNu#zaIoKPhB@%N;?vmF~G}^NuN>V2>@I$3*0x460tgzNtxW^%N+zucd-qXs?SV zssr-+_-u3sEo@NS+Cmtdk~NZ+gv;y&j<`%hIQ16C5uV72;??I!TLv3BM%{D|b&1Bk zKk)ZT-V~8K0o_cy!6kPHnV`4LO~J05;ojov z)#D>s%T?nM*4;Z{D~RT(6uA92lijwqS#zojxgbk5xTmK1$E zJt{xdPv0J@C}+|yn=X7U%wgA3V3 z6d^H1wOaaMGgepS(iKS=>9Lux3ty?|p8KUjyMXrO7>9keY$AMO3M!fw`Y2)mSnd>k zA=03d(DNK6mMvXRPvc6;=68UyUEep4i zmiKSpUiPW7=9Xa`k@_Pu){;f5jE^g$X)%1DCR;HFykmFMJ>V5Nea(!u93_hRv|3OR71$qx14P-A<33kFgQs2dEWF+_Vg9Yno2!?H{<(K zl>Zb=LOK=MMpc;@!2XIyNRpJBiG2YzvvdGln;_3CiYh~j#6@Yg+JhYfN-sf`vAOJc zpR;jM!C0jEmMLSXu4&Rb$x6w+cSQlX1dF#Kr2MX#;{a^h9A8*Ln61e60)VZ(p zju{acVG04+T%{ytEIjRuBxsmFR!RRJUO|2(pq*)n-aS5)|&Mh8&dT2e(?uG0-WVNrl`($ zkRFQ5n3WwoRt9qsx=Xbj371SKiWZilhGDfne5Xt-zmzn7oCbI|9P8NqMB*}G8Yy;rCDLIv0XD~VwZV8py-R^VP!4aWRD%cZ0^z!BnQ-V9vd zH)B{Ii#aL4o6`5dqWGwA_k*!NcQ>kMa>vz%(s579xT#Z1MV3JXnYxqa^~DjSg^~^h z2q*D@go`*K4qaX<*H$1fyHzw$asY7jMZzGS$TSjA;p66{aY#1?g9Q1&4JzTuX+?BX z$>d6xb7N);qf*ZF*=&E3MY}ik*6I~1`(L1X|0l{g=4&Px7;rD7C85j4!4W*eRX<^@ z<;6(u$_V=O!>8$YJbibBwV^~9mDXsNnef6Dl&0)Mt=XZQ8 zahOkgBd?RZBQ0bqe$PIA7}a-$?{gg7q`rD5_MmHv;yW_2rz6P=pWzbsanF-~eipal zx=x?!G{l=ERV=447ZJ{52DRO~Co`s-M9F!Uh^EZr_yz5l%}H|8{jcP$H)OOG&n(Kj zXEPVlcq>X)u@(!GB>`OpP17OGfZwqA2jy`|6n(G!K~mag>?&vA_R$1V31V7I10EPp zrlKw5`{f)q+!oj!rTu0jyUw9DX`)YTc4_{y!J8GblQ-}^E|s^Y79axfoH3e1Gf3gV z&7&xpOMz1C4gDICjVF;&C7Cva>)s=)mJ^d_YizL#_80M1zauLqHCrKly32EN+b2=J zH^(^7dm&BQkfz3gzS|8)%SnRLRz+7oa6VAI(G3}blcbm?<0(6rL5m|@`|aG7R>eVw&%cyo(>R*~Mg^46p7nL`b}Q#Ne`qsqvhX^g4IDf; zWSqrl0}|n&Z-u^N?4?ES;Shin>k|O)@bfHw3Sfczis-)}9#?Uhm71Rd+QQAIDhz0r zEsT{t+c(`5xkpHg&sJuHTuCI2DpAZLbv^*#ctSj@^@u}ZSao&YI(0F_9x;0D<9NRO zd$wcJb=8tN!m4_&s8Ew(TF(Tam~pR9jNCa?y|c9>F;Q}f{$~LS6z@yTS3`&eaM`&V zEpI(gnI&orQrt)ODldf|?#$31JOQ%nW@r^TZ!dB`D71gB)JSnk{+yt%+Bx#}Y{jco z&^7_5bNT(!P09)sb6EXoPzvG;FwTB!uHPFubWJ27@Y=nk7DHKCFWRHAx4=QL+Hcg` zB%u9!#_{xFeS|DA^C!lcwz8GDl#R820K)RG3zVQDTk;#D?Y7Ds9Hkd%6^evDTi=Ux zFQu>QqI`2*@G}p~_Gk1R$i5NaOdM%-@odJVv_j73@=N{<7!HbBS#0+1(z-i(ek7O{ zGU>+p0X>g9j|7RiH)r2ehLaQ}$iO2Srn%gIkiWf_)i}Kqj6^)JIP8$Wu!3x@vLjbaiy?cAAhSePhUwf`dN zKo`0Vh#}c&x4$*i@oLV~Lt>tOQSjY9dJGjM*uB}mpz7e=CF^Sc(R=}7#t!b;jwB#4 zkb~dB5P>&3e7}TFLhj0~0ugKYTl2MBc@<%JZ6sJ6?zF#Fejw1KF3Z=j6B*~{G-q+v z)vyz_izn-i3r~IL20LtxP}d2*fn$2fJsRtP+(@MtBIevKaN_{wFeX4{hhmn~?m=Du zYDAR|(>?l~FE4^>a(wr~BkA=7+`OOvX~x{Agi=QPWx`DPPuO=jm06Thhwh**0E`cj zE;sbPYHyKci|E7bOlET45_!Ji!Kyjfyj&V?&?M5Y*NKQ;)p>RJ$6b^s#<1Rq5?SrY zy*V-U{Dk2vu0QuJAW7``)SM|u z%FY5zmpjDU^WWHe)2JrWZ(kTkRK&=vAVdX3Woi{fnW7>fA_h?zg|s7t7@{phl$cWH zK}-uRC^ST6QW2sO2ALv5pp`~s77#MfqD)eS5K59%-^a7oz3bew&X>F1bMOCM>+}bl zmK3R~{p@Eyd;bPt*Y!hOQ|_g$tiZhQ^&OO>^}%Jj1VC<$POsZJK2SE7laWt`$^s{| z9*_H`=U!Qs&MqWN0u<1dSFT>ae)Si`&(~+b&(VkKh2>tYQMXC+(EHre?nw_n}B)ir4`uHs~x>$6K4&gi9!| z*!jhpJw+%X_dR_wddlDKw*m9lQ#&#BR~?~|5^hFw#99Y2Tf=!-jP>r60>}e`X-2ZO zxgtfGTC#9HmPh6#RMf3TTcl@XLGbR35<(M%8zy60VXFp`0@`x8yvNj54W{{*Q<9Z^ z=8Ct^49U(SyIk*P@GrZ{S)!uf;9pv=h>4>D3FxXu6J@7=NY2!6y$DuKJGks|G+P?P z*oeJHInpC2p)vW&6FLyG($aRUMxz@brOBx&PYja^Yr?5wH?OAo=U3}?b>$%i57HOh zZnH&goAK{isD&%0v;>p@pJRBUUN*kQZIs^f+Bcv`Oz1cA@6J%AU<%J&=4k5}x;e5x z81m!w`#H8Jh4%^*TCHO6P1e>!&J>ZG=395%*O$CEBy0^1=J07B048-B*);+jNf;(| z#$9FXB;FDzm=hDvFONtO5fU&QJe)0zOorC9nsnT)eLqVCtwdjxTs{e_17DBhRH@f2D1ZAwoP2zC)uZjpJko^@EJCT%R?&xKR@gje! zFS^E`tUj$=Hq(fcWW>+4nqFzh5o@xj=YA78!~FzRFw^Cxl?kk?%`fe*ZcNkN_O(!!p8@by1c9{ZG{eR0=5%aw*CaghX^Z=((D zw~#BU(m7Hs;0(0}3HWvTs(qG_I6!O|sE96`Pzn+Q)G@$NzymG(Ok$#?QljQ?f5|zN zA}mrTpUZB#+^kQl$KMjbL=tKWTq^W1VhR-qsmXI>J7~7<0kSap@AgKgCfLfuHq~!t zTWuhn3Bl^H!D>6b7O(W-J;hvh;zGb3y+>wTDcM9%Jg#$Q5yr;DZXDF0WilG9{>MP{ zjSWl&!Mag8h*n<}4mpUIa}cGLYw@?{+g_2>Fd$1$4 zr?#?t_n!stT3op=WFg>IK^FU@Y7aJs(ni$kZ-vMBW^0gimUn{Zdadq9E^*t0#!9zi zz3xGM>@uweqM|Ee)hJMt*%gxObi3+)Vwfd2X%pka3i@Q`1$=VemtkwzzD!J9Ge`^* z4N|B^e0x1WEgf|1v;_LWfSEoGF8bHs3Nt{|7U~!MK(2J-MsyXlnep>7H#l~!~-3aZl$wYFiVsq65I|@(OZQmrhq7>Qru$pr zX@*c4+9!bB*@MXdX{*MTIHRwmevDI5pFKgD)ab8`pCqgx_w{Cmmm!KR)XKih zWz#qNhhkIbqufIeqK3s+^T<)otJMxoK2duKXc#wu&%3Y=4`1R;DFZKio&qBR*+qnh zv&dn zQf)@Aydb<2$TUYVQS1Bcpv#*W#+gIGI7q=h{tv3fY*e_n*%8ZeH7%|0Fh$1`_A^j*-LQT!cH zd5r3SBh(aQ^N;UG7Ro@xwXwb*WF7LLGS=?n=liPL$`X#vw8uFzL2WP z`;Cq6Xu%HSW$F=FQ+q#Xq7m7^cM{Y@Q7E|X27qrSNoc;H(#hufVjhsN1^<031RoPf z)d&~N<@Sur57w|TT!{Rs?fx}I(hV5@+U}yqKJ;Hc9|X*7qY?S`;h96vEGPQQTm^A} zdzROIv3G5T^L}e$)0>N}r|ykpcwRLqBcJ8wn15uWmMs!p7(WRz@X{CP4&)48c?@A( zke6`ZF!wTCk;uWk%v#VNEo*aiXNipBOgWztLxk{f3Ouf>=$-81B~?Y)O#wzdr`Lp; z|Ma(l83ae_ru+rL?%?T09FM%}*)T2nsl$X?@>aZl+G6@PUfayPc!Zf^`pmk3ZPF@R zd36zi;WZEHyQP^(P9j)K=oV002>5z>=`NwI><@jCUpni58(ja02lq9wl<0xp<@bC~ z4ZTkDrD)s9pS?&NIFuKgn|&tRAW?y^pMpAp1Pe0hX->QwoFN2HS-+ubdGtfZ(U)ji z0rMTO)(WE-Q?jU}7EJqx&EPRHE#CU}Gt*7h*XO3A%xrZyL1(?X-v)P%>22)bp08ye zeLvf(L;O0*ODCbWMorqxAD@*`+r`;THe>inkIGbFEOp|qGs*`B2AhYsr+R(!Hy9Z3 ziVNGS*q`n*|7Z2De%BuzdTkDZKbKHuo&W{mQNu0S@JO@v0L6NKvIq=S@k?jp?hd#e z7Kr%o?u!8S!hUH5kF239?(A#U{v2oFx!imS&izU%)_!F_{&4D} z&+x!0C-~H$o5{{xr^&*AA5Cw-2;T(ok~i4!rIyJ-k*VhR_&8whas^;Rihe{D?3bAYmP^)JkRvScz7WUNr1{e#=vo-2nW(?03_* z+ufRffo2KM&$0QI@h&U=5Da9FCX2398YE+>W@-VMJ$KW8F_zt{*$=!hl6}D1y(Zfw zk98p8;$$jPU!bs@KfTyE%k8kV8NaGd2Ovp>l~;{6`hOvP$l0@dZ0TZs#YCgPnF}B1 zd0)3ZC0`4#C z>(+`byaeJhsQx$nO50sXS_5hA9BZ%|h);qUO5K((_PcuQwekHtr_MDzk(&{Jcjl-0 zo2(AoU{{Gklu&$uxfw&Ii2jP*f)txT4#lP#*` zX0`^vpX-5w5!0f)`h}Y{CTIb$Y%^NjcKUd$d-<_iGcHeq-hQ%k-O-tMcf1|dQ(xWE zmYV~?3k(>c%;WoiaZS+&`I;L_)f*>GPVls_HbAYc><81`f$wI_EwXJdGc_v098CYj zYi;ni>!~W@?#52GzDuE^M6d~dsYIM*nmU+Ls}zqGwmjE8eiWW%mkN>?S7;pXMoA?AA~HF`F^bsCM!{-~h zG(p)ZgmY|7x(L(yI8kQ|<5IxP+ZwlW|43H*VtPisz0u3=g6?NEJ1%ojU(n5=HKfvk zugP;{d1kLCb6ZuNa=2+qtYDNRQ(aKWgM3}=25*(aWT zDqGiCx4~IBq3p*G&_V29J@Xk?kvSk!Lqn2WpvPEpKR>3z-|)13VBoKr-!`nb?r9kz&K%EWL)brdpvJljn^F_b~QYg6ofkx1-;P8hRb%hPtc*9Qj zM_rZx?lR~;K-G!0-AGumYU6AlI$B|mhkmVQRsn>ZgP zBsIMp22N@#3qtNzBr~j#C0^P$I+-hFXSiKwVL<}!R%oxeS%!$TVeA?)ezuL`S@Ggq z&E8@((DhZ%8SHIMkNGTnwi=wQnpx3xCjTaZ71$H>eyHb27$Zss^V|SfGw-?xOQ!q ze{ha57x65*Ea768I0WHa!r-I9%hf4ay%6{k<8Rt@Tgm-tDm5%vH<{5d`hzlw24UIy-Q1$z{WTs(xlmK10J*h_jw2*c4+7{lKoXFvEf^wHP;{Uk`_i|kCRJ!Ky zPDHmQ&M0n3=L8(q{f-xiaknm!0qTPU`c42PIDn(ffMU(di+>FpfrPgRGl?|R2q(YK zo|J-P_{e_FI|wz!14JU|K-8bEvj@(`pPypA)|}Vtnd=!FqeZp>>$@(o>{pg(CzBz01q3YLyAIn|AM`JO?*O1F+EWc!{I@H-Jg&4;S!Xq7s1w zLhXd+F~8D#udCQvStXr@&Euaj2%6QqZ|W75Y+;$@rMq6)l9Sn-m(JOgNHbcs;l}lC zq`x>|9e@-4e^4*r%su9OQj&mF7wsR49~i+_xrqhn);w?sE#L@EiF~{Urq1vn#i}qA zULvNkF#;tE;-W9T3)_%n*_rnxX5wDwo8rn$l>ST4wswnvS#?yQWHyBzyU3)OK4GU)i7G(Ul8~7%Wq+9Mn$Fu#{Qn z|EhTR2SP_!FuVv{^a+zc4AeUxxjh;K-b-7HZIyrklx^o%15+U}`C33W#f&3ywRv9A zffD)8UA2-J>%%rx-x#xw{!;vuG`_H$dm00-LV(+NjEHXclBu?1N>q-o?6f?OK%T~9 zZNm!khoNk6P*P?pj}tWJq_}v32H^r;1-&tmX%&5OGWRxKX}qWq^&Rm57-B)Nw&fI` zl@zM%HC5_%RlVXHIBVm9yEST+CdSEBbtF+em?`5&OFWkHW9p}aert>f&_rLsyL{!@ zQ!;arKyScdLtlVqZ}D6abX#vDNf~HhYKv}vb3wG}k)D;-mV$(u-LPp!LP=rtEYLUs zo72){`LB@h8GbdFwG`{Xbb3NL+H}LPy-ZC81=irFvk)uM(D0K*uGOxlPBSg;&--LX zk3Sj~_m&CU*M1OusrWYFC-5z}Up(kNq4~4kycEwA07>1cA#hTK)IcI7-qr)^r-f*d zV?~dk!4uoqX&2B_J))dL{e*}t zmfsJN{WoqGNUA;*L3#XCQK29x@u@>(F-nkx!?$xUKcmNWO=WnH2F;t=6Yuz%B}YJt z{pYNcWxM+VR*u#r)bBk++puH=kM5k7TtWo5AQ~x!$Nd-%q>bT5A?LMn#OsKKx!Rid zU#zE_@JTra@RRpoK6b5LT?;g7*vj+_zLw`&)4>lP$1uI>T0dghSm!Co5rXNUF>Xh} zRx0Y{pI(vP*8o(>>xpYAAuh`WM_zSco9Uk0ep~i4zl^O$)gFhceDR5&$L1fwUmt>kHTNO>hY(je(=SPTZhb^iql4Pe#uBZ2e znMQiOs(d9i%*oF4PBz84bcVL-;s?4UJ}BXbD<@lncV57I_@n z88)=j`zy7~POT|tL(f60ZG}Mobzv^P+!ipk619Z5*?2ZSJ6G-+{7h*@Z zjz#%U_1uCRgnQg|p>1`Wpn6uoUeY=?dXfOU<5#xB*o4Og66#G_JeN#b_vM;H+wwmW zU_p~on-<^qZdvumua%1Ry%nYVmz#2DtI{T+e&S?`R1u8;E-`9FB4^Md8fmV5h;=cQ z0a(|SbrZAcuGueA1T{{4-6qs}GB_Oi(5I(Xg(xN#bj=PC8s`BKrL?)$?tT2>^6p~x zk;xxIRK$CbI?7#(yg$2W&v|g^R>(UPod1XFf;C zMWkz7-!8%_jcX_Q)<2^Rujri}O!366Dk!usy-ps<8gkv=Fk>he|MjC|OlWM6 z!8E#S!VqFh&T25N$kyCp_F@E&M{^xuT5RaO+lSIT5L^O>JG8lBr$$0#(I3A}?XlIy zvkdwUVl~}Zg>mrFX}h}|sJXHLICl6UXWTPA z1pnqqpIpml_;1d6Na0UAHE=N6$1aDbFv`$j~EeixrT4VN}R zYXQ^FD?I!UO&vcSRze0aRiEXodDm(;sS*HMpJ{92el5?S%TF-{OMo~^4@UzPtkdJc*LCj#ZK zHO3=^Wx5wSyPB$;YRQOS^P9`GqA%CxhBG*1E$!`=A2TE3Y1A;+O$Gh~!4L3jq59F? zRv8fN!dm>%&#%?inl#0RnNs(3%a7^dzc6;8p9&0{KBJo{a;N6^J0>i;{naCdQ-<&rVIZHl8tWLgL^CLlWamI?h-GT^qw%i;cEO}ZWh1v< zhE)l@%eT`ag&9eO8V5$Ri^x^i`y@H(FFu^|_f~S9puG88fkPT3N~}>sDTCQY0?aVl z0M4nlr%syz6faF_UqdO$emFIh;@ugh;~&^qX9mX=Up@O|e;%6EnAw(6kXsPodODJ9 zl(C!rq(4~OZAu>72TzYc=#B}oHvLVShzG*2J_C;{ta9?>=*U*}naIu;`0Qo&W-%|&It({)v2lxhCQ{~TI4hHyqY`nabrJmIC zoeZAd2I#McAe}lxlSkzDc(dP;W?nHB>={KRKx}HI(s@uKaFN2PMfOjgnoqWfNd2Hm zFNtD_X48%?M8ukKQ;d)HJw&fx9Vtw8Dg&TX)9p$+Y+cocr8TLOi!Zjkh2Vp1bR)(B z*9k;$Qz{%Xfje3#J@CWUrh};shz*&n1{+cw2~LI@UezJRlU{4vsR+IW8UQS30el}w z2iscHK47nJzx*hFn7S5s6V#j2;Gj%M20BO|0B)&e>N?Ec*KO+v($$A{cjQE!-Fpjv zyO23MlF+b~mBH+H|dhO!u z^h$b>GqSs)h@z*_Q?oxzFJ4U5KVH*uz9+PpcCZ7WjpQ>Q2mOZqK#SI|F^n9xy1dV` zVu!O`ZClNk4kGRH(x-^q>W*E`CLYGln|J9y@z{@nQb?GK{~=PEB0Wa$BB5o{jj|(E z@>?>h*rrH+HUPb<72V#1znNKA7JfDz7_*6*z$Ysc#2pJ5y8cxBb#Q<8oM3goCXxnyzM)PJ-t;n1rC5lBbDs92 zi~;Vg7s)ptUv>Pj^?DOvEdRto_hw;AJA@o`yH@jb9KIAcc`uBxp7{<^uyuRdo=<<< z(CDcqi$HtCCz2bf&rssNpUur}4MnvUZ$!LB{_XjG6ZV_B8T-Y>PQB|b{9O;Ej!ay~ zW2+y@D`{aAQ$;?ZynRwoAhV11xZ5_&%9r^gTZ+#IU0`2$cwU(8+=#7zFYrj+Hk$57 zKwXPByA zRuT=#RBA2SF6Y*s=6R)^{NxB88rnFOxn{X6n~~^K1AdbtK(wPkd1P zT>ZZP6Z+ae|M$O^4*1XW{O5W869fOmz&|nYPYnDM1OLRpKQZu64Ez%V|HQz5%NWqu z(B()|FKsuuw0|khqU*@S26w)8Myk)bU!H8Z{3s(Ld{vUt4h1z*Hlzbsr{H{l1lQk7 znJ`c0Rj?)-MQxNLR<2Lzey->X5laW{Bc}d&)7GnQ1*|)rDD$V-&vY~{UD)mxW^%COjty6G^j|fv5+fi|v8FsqDjd%xTEJ20?Hb zNw)TzXHF292vf{e(F=PXsT#g}CEdg9j97c>)un#%?(fVk(S2^XrzK zREN3tM$k3YGl7|$QXEy+4_SS6XYHR|}D zZ)b-WE%Z2EV&i}y#7?yI5hu}JL*@vSJ9uj-vT5fMwmVEsO5%_-z753jAWoJAxJ?J2rm#2c}D4U>U1mp{RWanXC15ep{ z*iqEBeIcToK^+^27Vo?3Jg1)&r zy{HMZLe{(xPixaz0SJ^NbGW{{kb|=M+RYC+X#A<}lA{wT4&J?Gl;m31<8|3}4b`LUCC&l{`1 zU3w=unQpY=wd-n+5XWRpA8-nyoPprRD=AgFB5IQd>7WsvqifHM#X;V@|(n5WeY{%Ki%%Hl5sWaAPIsvop3T zA4H5PvjNCHeFvgjy!wSGW{;puGu`ZBA-W%~Xwn1{4X>+e97i+-TFmXvv}Zv~wd#tT zy^m_(!uUyB(nz&VLTAMrqZKK84Pa|DoeoIW<>a5RI z@4=fO#I!x8?-FA7wJR3%DVZm{=6ze5+3=5x(-=Dhl*el=|Lnx;XP%pVRhifLUe zbE`zdLSH0^Z%-x{QOL~pD~l6SI>BJ+Z-ulW^+i}Agse9LCMHtlgT3$95I4;l)5<+e+lp8MFsr6)E&rMm^)be8n# z3czBPuo{8kA&^YDKk`OZ;2|%pIS#*@3EWXnMbw0Z=2q{&e(wBbffG}M6U$e-&M zXsPS@Zs$v9y=m@_7-ptS8DxXpAHy^-yx4ou1Y3a(g0Tah6;|sJTK*iqo6(G4HpaAh zmE)ti^(;|=9(}Tt5nLel@-A-uvb{nzZNqZsWzHz011w*}5&E@Vre=e!IR-8o57pb~ zG}bNcu?6?8Oq5!U;V-jyUYb?^bVbGF!pabBpq zeJFqT@Diy*{-2~VM=&1&lcT6A><;ev*B%IH-)MJoYpG@ozwITK!-CMM@LE zC-A6A%cMWJ$49VRNj{i{^DA-;(NZyxn|4R90HHry>2fd@Yd-d{vZB0a+N6YL3ecF# zP{T5%t5h=vSzJ`4YwuLvZCYao$i*T2Sr-L@XGux%Gir%BEo39Nlp94Us;+*LTTxzC zUXhZYBNkuv*&MZEvvWE*KkW9Np6vdUF8DXVl0^$!^G<#nP-d0bs*El2+uSKDpj|Up zU^#q0Ozf}YLox5?)oa%nK0Nyb1!NjBt~kjHK|r_kca zveRVlgp{iD`q(KyKN2}-Sy!1s`ohQ$?{g3ZAvDxjacUaeygCw62s}t4B+*Dlf~^hx z&&UZgr@NZtQ%s!aFx3pik6i>+>YPwv@u$)IC~}bW-;3 z%@$`lG3@nBQ^pcxG#POg#y9bF83t&I=uOOmQ3|F@FZ2P#z1h)k`l*MC=TcV9yq%w{ zSiZG)=FO)_n-9O$M=X6`>I9Xwqg%9OdNzo0{d~?k)D?V8TlfT|W@~85znw}5Hjlk# zcsm8bHA;ZD14qJX;>1=tmKB2ZbCxn{H(L0NVISX+vyt0XjGxR|jg*vLDl&pg!2+)( z)wkaQ&?bOviesvvZ(xF0E7pFS%oG{Dt63^C-+-EXm-Wsn2RA#>^JdJZbY;ndI*$vx z=BQ?f>71L+$+DWxFp7=o;dkS`csCAm39O|PKk^|3LJdCix7IkqflVHC0AouGD>80R z7L>A*IbZY8Fl6}~W;^xz6LRpfCZNF#Y&KNta~b>Lsmi(}KeyfOO;u2`?GEZ?QC9oh zAMzMNNPGVTw#rnx2Q`=6lV{1i^mrni+F*WRg00;($#bAASC~RArWPWKjhqB+%Ub@M z4u+3tEaZ`2Lb_jyl~>p2E}5ff$iWL9evud4_W*yk!%C?15;0qWmFzyfLF=2`yZ(N@hi|wPI`1iVb&}7A9M|w&6vxi1c3r#==Rep*kHK zWyWAh|EMME`Ruhk;iCW}#C)<^x@{t7GuT0t(JP)+U18J%osn%gK2)|$azD&uo0zzM zvl7+81@$=RSPesst__?gv0NmORm}F+Hj0zsWKZ1{tC$^Zu3&1B^pxy)pS>m4DbSQr zfG1RZLQ=s)al1}%XRwt!K;FLe{W4&hWTH)stJzz)KHuM{(SAeMEC)wcIrN^Kc|Omm zm80~Z7}#R9V2cn;qkFByxJf^N2FBiyDvVMX`w>OS?j9W?+F3iIJ0d#-}VB0qAzwcKX@bl05$}~5}0ML@}XEnbI^+@}ig|i`}=% z<&00FiHkw1i^1=yE`Oz7HJEWTRCizG`0u^~z#(Bo&`j%Ri3?+xRtyC+P0WiCXww!At= zP!$ws$kyU9C4tZ!-Ja1H3iKumrW^4%UI-L#y(5cXhkJRxlS@bmSl4wr)(|a%+liIG zJpb5It!D?@{XTyMy)cg%T|2{43U=S03mxjgR+3iMnJ^Ba8i;v4eqG&KdaFNr9kCJd z5@rROKUB%IYf~G2VXQX)Xz4(}z$xdup?fHitu`^4t;b$uekOLCmRt-eG5rL}MicR`JK&u_Dj&~ zzdWvLPXo$8xWps&>yw%r`J_*NNaTnj-<-u$W%wiUBVMeUlL$c^&$1+!wF6_4=1l%@ zwb(S`*j*39)UVi#i!ODF`Y5W!?Rmx8u?5d8D!G81UY>b9zwDWp{T>DtEtDRT`J=Ij zU?lKX8I*w6sN06Jk0B8$GXIhzhIKtzsB8F%;n{}%QCa|Mt}@URnrBMSJyLPl|lEMg~HFOyVr7^{%G*j*8DCK4%U zPe6h6dC|9zH69dSW|r-Xu@N!;Mz3VLNpDF{cCnFGQKYE4)=AJ1DPsX$%Ha_a zkxu}4ybbp2QQU}D127E(ig*;nrr{bs1=s@(X`Z#e&p`v5t}(Q8(w_M3+U}~wCSP8k z**m(2W>T`WJmc5>VN2f^KJc1p1eu;0L|AVDx<1sBQsAwfIb1BHsbiqspgD)TVXgPf zFtw`@kZ@jzrq@jLV)$2;iw|~`5pHQaH%&;oid)BeGhg-4Og{t!UrREYv-<~W_g^5O z|KD;l_8*@cv=$FvZJ+us=EXwG>lQKVNvmtfkZ1y6uUF8c#hjLEWzETc(m1q&VFb}0 z(v=#n)g;({x7{(q*5xExx=wf-!~}d~{1n$bSJ+h&0I0ENKVFr zshi@!ZnybZOoc9i`b%Gje{uCC*Xg0EohaV>cEbVhR&p_8|4W3I%n(kiSLo?irhX_c z+Ly2hz5hQ~`TzP|;|oyBQUBr1jp)~tR@kqlo0HH)#7bY@{jSoe2h`bQ+FcQvn ztE+m+O+J=6WKg?XleCP&g98g?nu>=sTZk3%44Hf2u9aQ~(822=7h}%Y#eK$9X^YXY zQ>U)p+9cKSzx&gh&k2iS-Y$|o?<-`e!9*5jC_9h3!kKfVqK%WP3Er~pR_R5*otF&KUv|Vi7xe4E81(@0I+R^QxP_$R2 z3&f|uOPQ=Lp=UJYXm;B==Yhr_yNRt!fz%k}hytH{8E~kQr7?`VelRarDo<_}w>w2m zw5a%uTQ}|aP&<`h)~y}Qkb^u{3Tz-)MAU@y8n;Sz2@M8Y)%D{?(R-lBG}q?f)noCv zZapIirZoyQ=oLr%%KTRvB_Bl=u^R_)NfxH r!_tV2llbnM*F-s0)mhohj{u!^{`Zaq$T_VsK1zxA&#Y~22MX{oA$Lh2=BnGZq86;ymq zhJ--C$Gsg;A+$L)r=I}cxmSD*Rcs!|D=oQiIq>EYdPT^Jwyc($Hz+^!ZeYGS3*JSP zBY|bv9MfhxTr2iJ;KMPyv+*3TP$r<}MpMTg1EL=6!WlFdPsfKjB=3W%#XWNQP|3q0dO(-Ag3RU-?`3-mkm@2%Nkwjyz}Kt+SIKGgjp#zQ z2($*9OBoKRrMR25ro-Q#en!wBP@7%xVAJ9w<-@i$uWpmicu#$fe^(hlItA3!B;deQ zA27kn``C~D>FXBt@zjC3E#3B3{w@#SY}mQeIXr#AE6S3fsuWpv%(Uu_XtvsW$g!u4 z`*6e^_bHWECO~(d6|tIz{3LvNF=kG_Hq0xK;bG^=xreAu^EJz7rOqOOy6gbxnXQ@q|c+`LvSEoUr zmfY+Hx#|FV1v!5F615d+}4 z&LGz5XJZTYRhx5+i}hxPplUX1p*Ufh4*8=$FF5C^swUd#F@%e-NFExLHC?XDo|tiKUx1jnR*JHkzwiyTaTfEy3_ zS608gq;}NKS@N8RUIm2HHug(={M@qgDtR7rJB7Vg7N)NHwXzace4T8kNhLV)ZY=kn zl~cruw`JxW-#@WaF9q~vx#fYkbw1$-vZ1>?nVt@^Fd_z><~#N(`>*WjBmh#LR7AUR zuB*TFA~1@-Nqfu)?zId`!<#I`$J=kB8y&6O_-n@(&q{?jgC*aIkAVBj|MpeE*dd~+ z(FcLdV2o_Ns4ap&yseL;-!)56rk_33Z3%o{C|)yBXI^xMQnwFJAM2(Wu4=ZS*pLe@ z6$Wq6#@<0f5bNZYX3{Ne3(Rf-K9MCiix`@yR-nW#(pjFRjZuw9BAULRO|?n(w-F!Q zoh)I+*ekvA&-LlNP#Z1Im$^pd?Y=AAn^%r3)AD~U`vxM+7c^o@*=2F+5y$Yt zCtBWP5UDiUnH=qJVQvr}TsDsl`J`V1;=3iAq>fFiO1b!PL_}PL2EMLa2Tw=UXo%!G4Ztxi zJux2zzA0Mp*DP}G7d>5Al6ZbiR_>R~EorCKHLWk)%^#EWN<1|+FF8`ll-2J0wVjf3 znKuZ30PSEfrVBO#I(YmH!MQ#_v}7-q?Wp*sLMXGnEIlYw>^2F1nR;OcP-3KF&B9O3 z`@Hb|!{jr&4^E`x??7eqlU_&#w(_%-NWqr|PDKiJoeu)T5DwRr9P~ z?5c&t1d;a*j0yI{W(o#zWya$8;Y8|ErKL~Rad_U^i2V#GbS*y^zS$^XA*!~e>> z^v}=!{}cLz z?TMD03vN$<`4;e9_iRF>jD?B`5|t zpwl}_)GmI$WOcLuQ-`(ADEHqr6f}lqKcPN|@kk)c)(Wl`;cm!$kX>S{Tb3(kIZCL} z>xDJ>Vv-sqV`e#|PwccPiY$85bj%<_YuAbx&F~j)5fLW4-?whp2~k|;2;~7e5MxaF zv^<-sDO0UNK(ZbbapJMPj7XHFnE~M%W|6?8i!xo<=wOmjwym`#w3wcDfT)GeyQhmt z{Dp?$aXsxWz0SUa@RQo8L3ae70A00LgX%R1!ZgzIGG&4nY(A+ZwMsdDIMXKE$^*%` zNyWC3eEb~^|9n0)IK(*0ZdP-?N;BRGcgwq?tyP z7n8%D6qcr^j4@fz#M}Alv$6sRuqO>Ora-P;rhy#Iet@+CX6IReJ5v4_d}pkxkKY#D z@VP;-jIk;4@bE`^mN@*yQhS{wsE z^x6^sWBLd!nc5q)Izaoqd5P_IUiE?=JMWA3`MBhVL~4bSjH`nb^7q2B5BXasip3@8eNTqqfqRb7D6Jr$a@fGyy4CK;8n64K$?Hu@&00oxw8CNEHblM_ zBJ0X-w!-fv_kIuG@xJib>F0EML&*y^#>@jNKAc^6sQfoE|GbH`-S+k&-71ad3;{qWN^yQ7}slff#uIm$;1pqMC!Qq0X z%tc?SYJBZEBkN}$t_sfCf;d%5u4STDqS2d{J$c%N3Fo>jZKU!8;{q30!g@NSe8A0I z#sP(p7W0Du_MAqy3KE$XGJSM6!&&OaxQbRhq<to)3{CSuW5Sikzte9%@I|N`>-u#wKZE5DP?02Hs zMHV}sUgtQYIAM{Smeg$gC($QvWiPh-OV#58DeG$97aLsgO8QAK+5?mZWO`$WdN2gW z5tIIK#?-Q`dO~SnGIMj5n=nCF_ z0IP+l{mfoGe1ex`4aI|VAW*Joz%REb4$z?`5x9X#p_Dn`#A|1-zj`1gJv)^72A@)M zt@oFQ9kOK)hO?c-;?cJw{xz^5KCc>4PW|PW+nc-$FFA9GOU1b?rt_zh{=nDA*}K~$ zG<$Qy{K=j$o9akqt0P_1z`AZrrHNM_N`A&V#@?S^Rad{t?$n740wc-%(AyS)KjRku z`jMWaLqM1MHXg^&7qW4|&`ZU5djnur*I{wp#vSQn0V880k3Jds=~(*n#;anYI=R<= zXIG7}9aa{$Au|?ZpB71VSM4>5^fTQ)%->v2>>~AlmZ-{;pz|&s(~`^byY>pQbv@~u zlu*`df)ZoXLweQdrp12LIwYm;E;vg&JE_G;BhJu%_MZ3f&Z?p2f^QY=cAJQXQo)7wy1Lb=lU}6tE+$OzX(m(6;EQ+_AhqWN)A*1=Kyc=8^63#M9^2T(I4QCt9)m}pTzNEc zY0Q38$5*r2Y+oB?&v)Z*U2Cnsj<=28lw~EJ2(i!G`RrYRpV!_ve^w4Lp5M_rG5Wq< zY?$8BA|{{Y#Mhr}*-ld;Y6YaMntLoN^t621nd_OiY3uQ<+ea@QKk-rgbkD2Dd(9$N zaDV@e_NO?@<&E){&X6g5dxcCl3ua-OG?_~DhgmHf7a)UZ;Y#f zaMrP!a`$HrzF*iHmM$Tu)MsYrYVKs6*;(-yPqt2qT$uusJ_3z*qtB`u5^10};p5`b zltRFp2g8+>^YOM+p+I$b@WxSQD}c%jHvAf(sl+Pxjxo=ek(FX=tU+Y|`r`=F0O^4M zzt4V&i?uaGNDXxc&#Qp1`Zq~JUrEUve)W$|R&njS7a~$rre~I2p7szHTQn9fsUWFY z`c3d~DNdaWf<`@jZ}D*=avGfW(4B=s{M4og%=c{W^wg3kvb{*aP3aCgM{>O!Hf6Eq zhBUt&f`K=h{WeErSA%a$+$2K>dhC2LXKS(p+?ze}O{~#-BJxtw7mlYFom-zY9jOg(sa;$ zF-H$DTW-nfoIaZEW=WVF81you9uM4hTG3O5arnGtgW9BdfoTRt?bn@<*YWYL^ zP3_umAJ1(5IksZw0^%I-?5Xn)rwX|5bxs~nRVwtT+fN{8;NH;?n8MG>OC;E=h~ zh91$2Yb$btdzz^mg{cD4@W9@uRpoEQPs?PKPQw8VuWLrHDm%^OgFP)jBf9y0Mo*<| z;F#YMvfdq5DOc{1MdXrm!cv{-53M3Qp#Jjxqc#|`JE#{7XiW5cPe-oOur$bm3vb3Z z;o+T*4OkiE9Nha9rt=6>2YIchRKYvJgCgldsRpcSf**_(NP6Q5CL<7Tj zbLG#JgCublDQ9rPUAx?E4|nN0#6(o)L4v|VqFi-`-_nlB%$-+GoO1mN(}x!X%l+~i zO-@_wJ0WuMY-rUq9vXCxay30MI6lkXe%B!9@mf}8-=&)P#~zpRh=D1Uf$0pF{;$Nv z@W|@@Df<;2?&sDNW+WF@T1gfbzLd>T6n)-mvG(YL!!Kea`eUf{(Fxq*!0PdMWJY1< z$MHAJ5w|LC!BUMZ<8_Qf*~kFXHbZH?z>Fe(j62T+I_pNoBwTM8a0t z*e)SoCGo}0iDc-aS?JAc#pBzn&3zf}>}L~;Je=T@mi+kPSt~Sz06F2|ZA?TlsR1Wvf)_6FY2ZUa7qA*i?bF@kKO;-g zviWDyeRcbCz5ek~@>Hj)Dr+Ti9GkJTgDUR~_a7;j))AGwR9*Toy@&SF>@KFr&FL zV4Rf1=Ok1UdCXPFJ|JGDixBxVby8nr3Z3UBUmtoYVj^{L3#*&)`0DwNr=9IZt=uOU z5_R&nZSj{u9kGyaDx!qv0sJtb5xcGygRF${ep!@t;tYg5EaO=R3@)lY{DR2CGcksJ zg2;lYcx9}>qEX@TtT^QEW`&gof zAZjf`4kn3$N&s`REsjU;Z5}ohs12!Ym4+#feC|z8`eQ=fpy68x z-+-6BBKNiS=~aqsQ0Nxt(!0BheoG|cV1sNv1CeLEV^uJat$+NafHHJ&G~cSB-&=}J zFHUVF-=+8V&9HT&3G$8e?~~YzskZ~FluM1VO2|K?jD5}TxUyvjeJ(-WtepN}nstOt zwb0KCqV50ji&FfgU;pFio{*8Z@rJq&=N3jH8osbUx)5iKyvOq@)yobp9lw@Q$XV4k z@b*~-OMVFhqSFx*Rbgx$0XiD4*u(FM2$)$pUO6gQEhID&q@D@aWrXFr!LhtOH95g@ z6U~i*fuWH#K&tHPA}ii1;mCvF=c3Bo$d@X)4?BAY>$HI*guk7;@A?0n`~5e3mw*54 zzp4HIeO>;CtdRe5Mx=NcWK%mZz&w=e`* zYPI(BBvvoySfYJ+p+faAl|MBL{o5vkHHs4iuz7!B|aQPf&K?71k z*N&768SiM3z`l5N$r&!;88*{o;m}TZIBrOH9by12#GB|06^QLGR!7uzS3Vik`Md`Z z!`PzJ&Q8Ix_Ax(3kEOenf7#aF*!F058Q9iJr^8#pb1zM?2Z81_mF>P>n2|z%J|->9 z>ul_F^18?BT}Vq{C#(areOZ5L(xZ$N?VR)mb%l=ht6Fld6ZFvqEb?z25!TC&1M@#MuMRptc@Dx3?e+x*DlPviEk(wd^QAM0aSuZgu*>jM!=( zS*d+wXI&Ep-#I=xhb((%P%kvtMKnr$NU%w@=_u|DFY@1|KU^9@2q!O8l@!GI+{Rk` zmYAGhoX5Jf|G!y%wu!d)>`T4-Rwgw1Ppi52g-&_zK?3hzjE}BZS$eyVG~&77`my2o zey)8dWb{T0bZ21w4MWZ<4A%VaC&qDpP9JE!{5a1MGy+g0Q zC^e~^Ncog7B&MCNc-v0#%jJ6Rj`sZ3 zO9$=pIktQ0+Nb>OcN)(g`%p;xL_;wO;s;>)h`f@@HF6Zh^1sd}D4?IX%GG~#QhBU; zrhJ_!y;YtUbk}4#FDjt{w<^G6w)rW6v{(<&O=k3|^JF6b3Sz@g?Qr?Iz&6u##g6r( zGA80u+6DSO)@#Xxsdfm@quoH4p`C=~rByqDE+%%xl@|<9Bd$<52 z0D5j^PCqPMnS4!7dujF0l5w-`(2tx8Dc;hJnH#@sE$cDSW4h?}mBwB8*fFic-ya8D zB0WVcaSF|w2^(%h*Bs;FQpelQWN<1CV{d>rFP@7y|2DdaQyJS z8rItBc^%C3I+`+d(^)s4lCW9RhqEvZpi*}Oa3h^xabKUsAZ!p|fLE+tEtr4RInpj= zNDp)Be|c9M54W!U3VRN->L%mN_aNp_Dt~Xu?6iJw>~6gY>U!Z{mc9kS(v7iXHO3wP zOKF1_Ukf#3`EHmaVw3-+k;7mA8%8#mpMX#h(J_-agRUunTn0QY8hK8sLC>0wTv(O- z9To!5ghPjyC!b8ab=bnMcE9N==J!La0uF4F;9|r;6xi%?<29fHGXx5aD(&VwHXPLw zG8?(FFiBnmRd#oFM=ZDFt>*Eos*CuWb23apt?Zi<by3xe#m1!3ab=i@%U?_T%9 z!)Fv-%ZI~x;d&wy@e6c!zsoqgsa98<`w~@~G<^OX@T$qt1nFvJK0(H%t$9z{^$G3< zilf1^xO|Nk_e8g6s<_u1N1H8BwZ-`C#h$pBrZ15powwg^leRkAIMBO)pfhYhn|QIR zn~S)LpU(^SPiT+5 zr0T&PZ7uJ72VHB&RzX(maQV9R6Dble44NJc%Q!ZsU78JfFz9OIsnx5rcof1$0P0j{ zeJJKe?WSNWwz>p_i+s7YquOAID>-K57>Q`}1p73DV)5-JxMwcCL?gFElSCtzuYKmL zLbhLve0=w+qZvcEX>MU=4*kdB-f92MU@-h!!U>HQDzr_uWxIFyIP_L928w3_Jn)_? zYPzz=Y78et(*eFVopfmUSAtJ=#qq8v-)7t>BFq%8(|&ks*c3lEx(Fuggl!KBHq_ua zzs$0;)W4MO@~U+Roh(o2YO@lY-ff>Zg1w~*fw%PEK21HyHD&V4~l&-2MjGKZQQGZm}5ul9xS7nT-G#uh9mW9CRJ zYCy}3Ynkl4r~aj6ZTgKr(b(*_1ZR@aO|hA)73#&+#=ph2XUFWDUe^fW*LuFy46A>f z=g5AvXA|do%;Wu7y*jp(^TqY=F22oO>yF*u*rE{EbO~eJcwyAU2WQySQ|R3F>*HC| zTlJ#~dTH_vR9Im3*OE^6Y1~?PT1S$pKZAww z$G%MKqG}Y|S0x=&%pxC(Hh)V#bIFt<3A3Q+;h8#m6kXYDSulfLs}CCprR$CGcg)m) z)_JDOm7q-3v}KaHg@tLB%g2>eEb8{e<-^qJ?iM>}I-fB(=8_v3mCbN-u0w>hx+WTQ zC{7h!a|wzf^kGF|pZy24MI*2t?`_f{Cx?DKz>A=jDHTdgD7zu(y1t?iuCgKS@j;b> zQx?tFs@z_1$Mk4MTlDA{FhSwCjSc}^9#LA}$c3!2@s5DhfETz0@QtxwXZgDEK6435 znS1mD%VHD@D|PP|P}~BNaQBzF)X9y5Nf0 z9@Nz|TkN>wrC^(52gM?1JQTh8g4p*XvWVF2QX5-HR4VUFNh%mON-lM*49w4+*nf)S z2>{Z_C(uBzd!LO#*_YCG{EA}Btq0rxb?Je{)$NxAe=*aC`+l9-R$>$>e?#H%drU@bd!l;ehTldP^4tkQvxFC)1Ws{GFqu zzDfL=MbzGyvLkJkW+w+cNlM>{hAE#bTGevVRMQN>GsCTY{LLXxF;Q0lg;84!hLd+` zYbZL92fEs33DCys0~O2n4{8BNS4knsIn2|UeiE$jhHGMDsM}f9ZyiE3s*>!5-VLf6 zfh1{CSh$X&cG$*qvB<3gF&;P6H`C|B0T~C8OBjlZ)A8DxgDXVg!n2o8aWKf% z(0gE~$>Dvk!%;yGj}cp~>=nQEeobpG-%|5J;wqEsFCR|cPX1+Y*-Ac8nm+d>w6rqg z(H;VEdv}ovCC!_%@C*By612iY3c_{1Mm=k;SYE>@BRCFzwn_53kdygaV)Py;eG4O+ zkJoGh^NCbbQ;hEvEjRdcK}8bx#jk*Qgk#RGn6IGGhZ2q6Ft)LNp-&gl5JACO-fIqs z4=vwlx~K2OA6JdPfzn3jIm6$&U+Vpd)pP>m`2>dL{>WD3I7jRGae+@ynpp@RbzVDw z`v7Jn+^ipEL+K0?5Oiicu{Q;o4y%i$4&p`=poQj*Tg#rNa%YjZs1iWmX{N%h zw5MzIVW>+hQ5+IVsWK~H9qKm@(;mE6e+-J-nuDkl!1We=BhEgQn;S(&k-3>ElFy*~ zpT4P<+mdG)*a;?1D_N4{7U5p6MLou;;w=u#Zb~{kUEKHJ*2Yb@Fqw|%#xH=rEYi58Pgjye1lY9I5T6Y2zj#gV|^*TRSA=k}p znl$va@FcIW8~-hVsXia16fO8(27>4v%Ff(z!Z?krg=hSlTlAldfyjLTDw?W;(uaZ) z%2@r<&Dhnzu~FI?ntsirskaEz9Y2=>@rr~)J#+nqxHC}3Kzz;S=!TNu2-~z#+gP@u zLthl5NVzpKo*v}8@6dor#?VdqDdhK6thFAT&3C_?i3jShQ%2-1ADCoH_xNph}1A)#YAN|qJn04 z^GITc1yo*ItXpUF{TO)&Y+7hCs@xSot_t zX^9*?hq*my_VxKi$4+FKaFj=j4QStb!)a?U?bw&g>v9iYx7?H{FL$&MHTi-HSY?1V zS%vQl6WNJ#ah}GFWqT1nSYGJLJG^gje@mD#X#L=AuF#LveElp9Qiy(y^~ut%bCvOi zDINT7?MDwk$T`AiNPUX0dEr4W5a*8nWFeLSO&2~NbNP`Jt{k;s-K`({p;U~wt-kZU zG2l`d*|e}`ZIiwERAGiEyWj=!aJKgI>fQH>mSLyFS;5iP!gUXFNsAid^89C$Y2NW* zXFu@z?(Wd|)cn5YFb22p(_3fnB7s&EfAtZI#;tGO8K7$R=t}}bjuzknS;-P~6(!004B?S)J(!bLdS?afArWyKT?9d#_%JVDp^!z;WG@LDk(0}fQ z!+L%u0U&S(+D`|&d9vt706Eew()u|3v%pQ`iuHz??pj$`PZV1E8QqFpfi;Ao^&cmC z%!NnZKHtyGg)MmE+kPDNjA77L3^%5!SMceQUPkwfY;QhymoZype`O6#;6YLgnyLc# zIbCSLsmH02SnDXR!scNDn{cB8&aPj~XuHe5Qfnilpc@BgIMg@+EW^$TYx6oQwy%2t&+8OsttHTs0h(1bK*vaFFo$3@<2V zMn7DAwh^aPeSTO?m+ihmSoAtO__S@;mSQOW?uANBoIV!UxcKAs%d^CVh4B$s%Qfnt zQ=s`kDDl@OfpaMZG!!dNh|6%qgs%*+Ck!obpqHFA;>PNN{a6bKU&pz_S~)j1J#*De zFBsEQyR&1i#=1V$<Qw`zxo!Ng6S_KRX9C|$=QTlVs*JMO* z$QZjS*le61&&OlJbM#vmNoO|ycSQLAN{s(M|9Ux618%___Af$k7`i4Jift-fZ{*mZ z`*oD}y}U(Dj~s*DSZZ^s_1g~l7Vq=o$m<=7o=!~O?Z~6UoS#_2STi_)D}g3b(Gs|rT{UT6P8=-j+HCV z8gdnZ?JdD(jAnmi1e5QVYe@#tC5^jcEnU{QB|X$p)_rSU);(Hy zD=Ar#HTo@Wwqzk`sh_Z|ApYtCvgp^jnYI@-$X@&1BNNB@VY)~)iFZ+r|A#XSs&#i7 zw#W2H>dxv_-C%Y=+{Ng)eh*2!$%Ki(+Dt`<-KQ7Ja85OCTMsq>$3sIDCT z0G`fYU4YVs;Jdfgz7pqAe4hndQVbzge$XBN?-gb36w=U#vh+B^MwNsFlQ(<(nX07j zLHQDhd{5v#hm#|^l;^*!eG3e?Qo(y)LAz)QwSUv<9I0E-HYMr-p?W@)F7km=>t@!% z+jup3*})VI5B|(1iZ0aLz?Bb<*ZHe2u#=3O&v!%2ZPv}Yg*ko!utsTyG5g1*@(%t@ z8n{VAPq5HsW*t8IL2wWsk8DWbUutf~C|ikaj^le5c#4by7|&aodcgj{98;Nc$Z3vs z#~t_c>+?)qX-rflMQQIbpPo#O&Wp2`CtY&K;{C|z+759Aj>wJUu0TzxAF&ELWlR-h zo%qpJL^=e*^8*XjkWP3XR@sfzPC#T*U7s8y6^vDS*Cu1{R?FvwMA$)5>6#@uCMB@f zaDAxHO>t?@BLoX;L(w7%v@#kh81iK6{)KFXy?M_m85poIvaWh>Hf+Uqp^jLl@pZHh zmzI=;O*X|{?lMpw27DsBx4Ho|KVQG?i4}~~|NX|#|M|f7pP$G4X$|n^Lfqvg<_#L; z#Jo4C9dW`NSI;f206YLnIMkAJygccfmzf$qI@9oCnt{{c&r!Y-o3mk3>DAk%Gqiri z*-v@eRm97H@^>6Hq74Z-U-wN8GXcSnD{6<1#F_q%nejE0F@C&BM4vTShmA56l%R5YPd|lN^CA?a-{L!b_AI5*ir-ZM%h>FsZ9aa+U zpaJYJqtI)s&bDDkcA*|WAp#iW{IzA6dAsMQ6UitrW8FS72t$&=^-B&l7Xs69%PceU z3A;R|jgImdtMnwxm<4_x?QcPGA%ly7=9X@EC{*GU1&{ z?>O*IPr#YXMLc24V12S2@p9)8xD2A7>C*A3c7+O>uDd?I)Dd95=3M{|<$JMz#y&cH{!=rUgD z)7djn?0Y)mRIH;-AX+g|S-7rvr(eQs=P&H+c${lU2zk&45)1vm2C~bP{7q@l3V;<-a54kVPWwQQk#C#`ib8- zcOJM6yO(`*ZMnZM1jNS{#JADA-}>Hi6x788CUCs1IC~fDnnt~c@wZv~&Lm;0+r((&=oI#MW zgXuz#VMYV)&g7px;q@k-VL3o&*bbUK`dX-)!1#7jk1l>erhh`*#FHDR0oI~v+ zU&=oC=etQ4Jm-z@r0rdi2T1sp-*g)Cs7>yj=tVgTQ7C)gLq*&}*u}0u&!Tv;y^d+MgR(^c3^tpr zeJkudhlgJcJHDf;e6Cg=qOFB#b(t8bHq|o=_N=FFXfK^v2b0*9dotGa6Bmy}W)T&; zL){NzDuQp z9$~{0ACO^X#vJ!!C4Wm?^>|MGJh+Ty?OkcliZ|#VVr`;mNOpMNWP)-aRmuHtv@L3* z{4oev)2(s7eSgQBRI$&b5SZ&_fEwscJ8S60Z;4mjShMxSzt}eLJk&Qm4B$yFpVS@I zr%Jo4awIv7sC#QZ&RkSh5oxADR8lfi>~f|fcanxW2FQ}2W0{qe30O0loBg{P$nPE4K9mI{H7`*KBo}CkT9{%DjAW!YFS3hW{ zb#uilYncYkqna^R{2da^0bO{j5~8@Rfd!qw#x6!jQUXXpe0s0(u;Sa?4?;3KMKLyr zuPZsZAClR}=F@MT(ZBx9E7T_4(wk3SS5m6J3z{!>t$#3?b~!qT47*x-IwTv-jU3Mj z$uhWHS(MLcXS@h+xjkZg{952cw@##UI;}lhT!Jc7Y$4sonPtHi{Ae~-VQkP44j*on zvK3n0HgV-mHq^Gyi>2)=tkcyFedPUp+*&>|MfsF=y7ldpj-RW1&Zljddvkd#EC}00 z#XS(y+T>B0`v68xL0sUyOJ{<$L>QrMNf7_fszgK=VyzTe>UP|^pqHvZu`2@;XFf@X zP`~@~dB&Lgfj1r7+bND&+hxx0fBl>|xFdSWY=Y~S3i9R=QV5tm`u-YTycnqP^T11! zR{=Hm(jU5387Ay(+Hm_wfqf@r$C0X~ zxn|ry+OeG+IcP8ZFbW^#7fgQ<6CDKElWISjw=vf@AopFDO>ad1H(JuQApJCBL14Y>r7 z>sYaaIK|w%>*;r?`=N^H6TH2VhTC|Dnw;d$x=8%9N>6+ZOq{C0OaQQ3TxfMltRec~ zrk`dUaqgcnA73i9 ztEn#=>>Q5%i$yY*K^R0~n`n0jZYT@;3MbHB(&B0qlUQ<8tovEYfv%#o9oe5z9r)0X z{wAEX0nz8;mDaM`W(Pl)T8H~L8&tUk2TnS6W(E({JfaJ?2ys!>=0hwwIJ$0dcl$gB z6I@Uq`lFD?l!Ivw<%t~o&xm;z;TtiuEg`@6lcKAe#fdI&pQ}voT>C`nN89hdySSCb zz0PVKMb~`>kC|a`0WY)$lq z-<^e?LihW56WH}%iL6mWS9Nvax*DDu?Xb`>GN90rP1S}qJ*2>y#lZ)Z^LcJ^Eissywvop5>mw3&Ls|6nC z^CyAngvLX&%@MY);YI{GVp^*xgE4LtAV^6iE=7VM^Gj@Xc5Yz>*5#M-5q~e+dmX3q zzPV+__Ztitya*+>tM~cluj+~vY^P_|^!U$(p$zA>8Ai0lC90_vYg2b)Y0XNoA36yy z#*7r^w&4~LS|%&H#@l?$3RxWyoMJ@pA zCl}>AD$Y@#fcjgj!k1Ig{nlyw`xUfAT-GLe?l_287nji)vr_cgq`9Jr>+NQ3eF4Xt zO}bO30`_}?WkvUVrDqj63@p;l&f&yY(L)siI%5v;>k(p8yQ}hB zyaS29`Ylqt+Qdi=7>Z5;I@dO4KMUPQN7q@x`9t%|?bJd4ck2m^9a?j^QN#5nk8{r* z11pw4UiRR=jD*XJOPCgl)3LH@9#@t!A}>$C5nTOQ8PKP-s3B;lA-?tbSx7;`-Om&s}QvSa~iN|)br z!^9h^;NznGxk`u`oWR4a2TCreLid)XBguT+YPjHd6?nLxdHp?dA|*64IXuAgn!*F* z?W3kKo>SnNlTt7se7FO-RIEGzLXv+=l;aYp%Yt9DxP752dIc@i?i6RDJ10~quZb*q zGX*dCyK{GY?2c~f92ExL?&jm9k$*aM4tq0#Q|iOs=tKbygkuGma4VpbK)!j(ducy) zfUq(EQ2WYs>sjj%d)Tk&@()#0ccqGMM7LQv#BPYZeqvBg z$czG~R|;mDgU!w$w%N}1c`OEwwyce_A8c`G4EsIXt=o3fAVO{cCXoQ$8Q~*)`;rvmeu(34RT@Y ze?S`uugP`B+}>=_ad8^Uv(*+=JM9GPLB=46Wdxlr-`?kCwgqC|*3ZytQ0g|nd+~Ku zcOi3n_l3)WQD1m>bMEH{{+be=ip?_kSa9rvv1{7N7>DcANgX;1L;@o}^&rcu%)bNg z_AtFHHQhD$d#`A%+}uI5nwA7dWR_L@iDrnYsrXLOsXj1zS;U^DntgdkxdzWbEYW4y zScnsK z>%~MxN@#d;b&sRnkwKgN{c%P8Ui-@ntF*UQR*+oLW&QfuVPh3ly{=W^tmR|IA%9z$ zH7{ZtsGczcU~Y8d52?j&vFB$g9SR&pc%f(mte~AvawcIt6(ry+7|aEH;C9YVDJ-{-xBnG)8g9S61vT%rppSeJjWMj>9g^) zUFbO;h+3~{L-)to!Og?XJjlZ3z;B6<^dhM|?dP9aLsMYcuS`5tUJGXk)7hVY;k86j zOh~J#q&NCd)w!Kdt%?Z0ibtx;msa1FB=5iSGplmE<7H;)kK~y!oHp~#=)>`eheUn~`0o zw=BD%*`?^PD;FSpw?N%n_mNi260nxGQBI1ZSQj}Gh=b7VfzZ$e4%3aPJrLa2*UVdp z0__&q$&;ZRjq0z@lzD5^e2-$OSoi`yeY`l0Am2|hOUDnGesrEVw&L1=ml$k%0G2j?1X-Q{w${J#=m%c^q(l}zkiSaqG|Pi z5#j#dbnTb_#Gq>cc)P+@91GI&OSAPisRqGblkaG%h-qF1?j50&#*X#Y!^7#kNvAYt z0f4eKtIASs;fSF#RVl~l^;E6+%bktgxrVm`5>`)CW()%OVN?ykrz#+FkZ?metXGN! zMZpn=VefqO%WsKgyDeKs5}FcvLE~9jafi8E|IunsI$x_kUGD`v^UiU9;5)a8o_sQ& zu>9+x=8wWJvljk*E#jn=B zT(x<<dJ*9iEm8kU;dU$r; z#{E}OfQ^G-aN$&S)t@ub=gmDZ3T_!vIXr71bgK4ib_8;qo3p3J*@4e8W;9E$CwW(0 zDUHu}6U(gK$L_FW>8D?RQtn99)~HD??Jpp?s@YdRUJjOfIkwyrYmjshssXrFM*_4x zSi>O;Y5|JN8xaGcF~H3*F)o1&zKl{GL3)mZ`Q|>ZF4y71D#;Pz?gZczf+N_W_PH5o zQOS6kWo~j)_VdBBP7~LyffCvkE&BY0-bL!haY4fPFUL_UWP=jAdfO5>w{^Cs-v!Z1 z8Bx^)KvPgP&nKd?!UcB125?fg@s~`coZpt$bX6Y=H*HHV9INb(H5I%JD(_xwx*P7B zbM^ym&)tB$@`4xX1Fwp61)C@H!wmd?0Xfkx#4tbSL9TB`fHBVVV~)VbyTWm%a~7uz z@au?X*7-MW_^G-jN8z|sJq8USFl&O+bP;tNII6s`1K)dSo-_Sqe3#+-?PVQ+thdd% z!Z2C z(YkPw)vJ5&E%#JzL1armLPD)E3zczAC7kzL#Cd=#z(%#h0@W?KEP)9SusA?rD)jZyFw(G-{D^au*4<5PJ$ zr+LhJSLvEl!u8SzM!rm$9h?l^pW)%Fchhuu;Ou})zm>^{yaC%+&Y%QVphMQQn*`_> zHbzBDAoG$|=YYFH>vJ92KB$0QL*r%o<9qw9BJ8@&G|b9?ZpAiK@fpd&4#o(RPJq|9 z&->G(b+cRZC#aHT_eqP-au(;(PahLsgTeS`1pw0`&Y*h1&4)gh{vjv}>Y$8(*h%RF zrT92h9irV<=(8@k;}f^gcv<}DG|w~AdvM3ri~yWGw~x3;F)Zi!>U?`ZaP6ZeM?N-z_9bM1(c?F#huX%rA{|w{E+8mE@^m$gEdA4NtzSNQw z?X|fju?059T+f*+GwqY#sV3kopQjBm)HT5)R7d@4rVh`Op+G`pU?_%-mqw&PHQ#R| z)ZjMAfEuI2#B-P|Kfchjcw1%Nt<=FCLdA;Vr7^B;hyGFitpjrd`v%=?on+>F7H3g5 zYmEU0)U2;Pr=lSX$IhINfCLb2OO)ObqEy-*?>+77@iJH*d_{`xi@tA_iq3nUb?H|n zSEl+sE?SXTQgzTnNJr!+(NzoJy1f#|>SNYW4v7=ZmQ(f$9q;UUS!h7RP}|Y%T`$r7 z$f54Ue_XKZgHQ7>SD00J^4{y7)Z{_6xHV&j7j}#+EkCYuL`-JJCm%fZFuzTXvFrM^ zhni13S6i0t__(h$dq-(k(y_cWuOAgZx)*9ITn*aCp90YB#(&it`oH~pS7tVYxL8XM z&zb1nADWl^!g+w$m-+a4zj+vA!YF(S;~nA=?s+}2->r9`-&&*QOIgEpa}zm}Q`t>Z zc_SN^A@<t(<2DjGJ*o)3vTjtn9k;wq&A4{r_16TMIpj7hO#JUa7Wj79n*J^ zW`0iZ;3Qp&OwJ)@=wF%RZU|mo6zio;D2Gs?6l;bda^+}OaEfOsmMPvIe1oaXJy1`CLBe0i1tIs=T+C*T8HSx(+FTn-L(|q+C9^W0K5!)gLyAy zt5Zx#p|RhYr2I?8eeLdc?YcGrW>NadJC4Y9?>%l9R;3ZR=rt{xTnKRZc`kzf^KUi1$i?UF!nKWf_B>{Huj{KQ`avk5=<9YmvNnWM48fHd?4 z+@wmFJIqx@EaI_!%X#X#!>wdHVBi}ICGqi5b2HLc7HgT$tzUR01(Oa7S!}LaV~+E{ z0Oy)Vd$)6`+nzx?&u8O(-Z_e&>&AvgqHGDy_A0&!TADqz)!jrc$rT?XUbjnF_$+5Q zd*N+mp-OqmyqQli_-5}}=M6F?tHh6hC$Ljt+LgwJZV^t($1~=y35^tKB6X*WmG5rf z=_^kYCweum$rf#K*r%J#1|3@hc!c(Om>|+%9UcQR*g_9WfSVL+w;8Zty>!v4>JS!B zQJhO7wJNB$XP2v>Okm^Hs`%8wCiQfA1{7O=f#2$!b@_FiPF_H~rAmzQ10H&mHdKa# zY%o%^x;-osa}BkXwa#o?a20baAuv-L^W7GGS{IjbbfnBoY9&5 zrhr{%9;VsdmVcODZ1Z(?qOE4fNBgrIlo~bpiTDDa?)h<;-YZ}r>UMvgAtEmZe+ygT zU3%Sm6WP}gRA)DAWGam#i9cLveb@QCXLsa&uG*Y=;pXj=AD8T^Jw;4#bz5kc!m)rH z{0x1ME#Wk=?&4$(2ISAYD($J=)54?Q9$W_n2!GVahqST{uxsQBFTtKm-V{git7_|WTQhq^!SLH*dw6yD%i<#yN~a8OK7&0V5`Q%A zY3JF)sX zjud=4{?S0~+_++3kIM9G-N(CAL%YjN(ZVqO5`0LQ$*lFZrAW%n=l|lN7I0m=Ofu?@ zgyt61Ermc(2c0N1dQd{z2dAeAb zE-DmGXZGyx90P|WB{dI@t?M>~?!UHr4iGOu_~kAmIGB($ zB0F4bF0;+cYTvxJ+1YbE#5O~bk#FIx_MKjSt)jiB5!K1HR=CMq>rS{?xUp^HH?(kV z;*%`x181UCv8PhbyFB6l$~qOb=B0-8v19p`ETdZQ8&AJoa@R{7)K7XYKKSg3{PdHg z6DUd>qSK%w+^+<5^>_$*m=Hl-)rI&8eGB)TiylsG%&@W%MmI=n>zryAkiDz5_Rw># zP!%EXTtRZ{7qF-)2TdmzOoUp_H$5&Ssgyk_id&o~YuSby0gCMG6!mG(uW~n99cbyH zGuz1LiP?xXOy(8X81;H@sptSkOZ(=ItvI&HVbTQEED=h6iz9Eipnw1D_QWX9w;|vA zCW9oTk({RK3NB1tUo#4GgtGpFHJ@_yh{aL?e-BRnCgjF)3pMUv% zJfV3h$|;;Nry?vb4vy(%F1Ia+p?=H@OFv#r99Jz;_g5$@whuAk95EpzcDlE1@O==8 zwyrsDMuw4|a92l!GnZXKrK8mtv|<~y$uXJQfh}Rx&-C>V;TpI=66r{C$V+4&T*k|% z=<#Ds_oFtt_EB2`NcrPwWv1&KtbsHfYu3 zVPlNw6*NDRM!5pw!*b+!$CmI40(4`-%m}vQOFze_MtNI1cAM=S`*5AnrjZ48tv||? zTeWrXhuz#a*y2%SQx7=q5Io@L@f2~P1L8vQXiQP&Dg{*I3LScY;NDbcB%e~ntHT>f zG9F~*-9835$4K`^6bV zxn*h7;73M1y^JQ%eP%KGP0xz2rpF`S9ohf#UEiS+V(kVfW;e9s(wnz`>j#~%=a-eN zv8#@D0k?KfwG1T@BTcJ2L~(rfo~&}zl^{+lO_1M8{0=-1DiJW`WO#!Os^+vCW z`u(mod)ii;9206al)ru$#*v0415B0kIg;pY`oidhnHC%SCtAN8x>Nc5HB} z4sTPcw5vS*@+0o^^N{@(mD3FOO&tyR4HI=H2AkA&1-#z)G5J)yN~B7em$P~Lg<3{b zmWhqKlilruBl`W7)o0FJ6_09m#1Q0(I-FUx*Wy%k6YPkt!GG^wm8sjeR(v0e0ut9dbd{C ztdMA_p6_YI{%$P7tj3r63XPreGk(pv#HDm7QyuZiu*Yo`G9MAE5kMHkyJjdsc z@(fw zEwRts9jD-N?EUnyM(r(~)S(&L4FDql}4(z-s1MBMtrKdS_}!<#_;4E!en`fVoz}xIoM-)`U1h6a3jRu{v({IGs}PQ{Vb0 z7r7nkwoh=~D^}#MCU5U8g_!M4>6W#A-zNI#{Enac;$nd+m74c76b$5e4vfjmv*K;L zg;Js|Yc(dY_5HjtZ>X>omoV1RD)-C4z_c^#G!Pwd6M91Nb)9Z@0WlkvT^&7nN)3e=E zED29?(>YOBk;1&F?Vy(Yx$M}vDpd%-w`db;yw}M4MiQH|@ZDB@QM+$z`@Dg3?tS-b zXFBI=UVN@@6+cVY{Y_tK|8LMl+3=rh{I3%p_}^*mTMw;~3GAng+UNxh&M+Ug=p{TM zLO{rU(+`@GGdNZ@=L)@{EN-mlqgk{_pEt&I|I?jKw+S(jTd12?y?iVDx!05>nh+cE zLX~kJI2Rd;)Jgn$Hld4}w3 ztxdVk`D^$o`x65Fp;c6h+sF?SnRvWz36PkaHtdWc^6t;}r)pY472L&Wt|agk#gV!2 zY&tULPktKc84>`vL5J_&Qf|NQ-m2%=N^kR`t4mX$o2$v^;kqksf3>**p%Y&Ix17iP zWC;VV_0c|U<|EC36Un9$d4DUY{{Dg3lvcKFSa-PE?R;CHF%auMQEeEC)JXq)ot((D zF#5_LU$nIR>HvdHSo?PNM}&mqSITE#Hm_yaDvdBEp$}e3m*qexkChqw;0^l z_SNGXQ4{88UC!v2B09AfNJ3kD&{~>Un+6O(o4c~uh_n=@dH3xAzEyt;ejj&sa|+oZ zz@P%Fm^G^}iQ@foxH=J4u4FW4gxpYd1Ez94U^aY>2>nF9+7)k)oOeWJg%+xLA3{F= z5Rr3Yy@0%=x&4ne&NShvQ`Qa@4*uRk|v|{fvy1B7F!8zUY2TB&5l9` zFI+E@CQIlj>mqEimJZo@cF^g*QGX>mUSfdmxceM=d$7sp^aFvHvP}+8QV=YMc|{f8q6w3coUH+1gZ%v| zH6eXgboVA@`VIZvVCk<9&0;y-O&r4A?m4%>p}p%K7G6gry;S?Qg-xtsRmUrVbhe&# zGlG6}Pb-q?IiuQb_V#Mm)ne0~9F_5F=8cM%_?{|*nFU`<@?F|TE0hBLVR_CgJUlv^ zbf9u%HZ=j{r^+)E{v%IHjD^P<_hG2jMB(|5Q^4p_>FCg@h|q9(55?7;r|;WX%9h_d z_%!cGkDg1{JpWDRn{RL5+TX#50M3m)OMvtcX@p?Iw0}oO32}Jok&7=u%B28R!eAoI zn7&fnSeIdX@#eAWD7BUiXGo|iNRE?}Nx=kPsSX|3+%g-tfgocw@*}ql3LN@L6TrY# z2F25$<}FkLj1pw(PGI(_LTRQZvRR$qsBlG`DRap8HQ%Nj(hMBFf?J|4PUb2IPXVXy>DlhJL>psY@J zQrf!+OQKw#o@T)Pp%T0K<$WG1DQ@9aGRYlCrQ5e(iu0m6_?>}#&nTC~dyi|wn?hYP zFV1d@3g8b_PF}lyyIRxBF3uul{oR>R`64^3|=Mb{;x*%nBiQ z>wEgV|FpB$bi$s@`lElXi{ATqTjyyqwdF*w-2hOl)?KvyIf)#i2&K?VCekkApO+fy z>+9-iXAHu^!fN(rOZlAEy79H(WR^^6;Bj#wA1m=mT`8}!k>ur|;{CX_A~|X*9QUZx zuAn}lAW6^a=wy}O(D{;5*&+{R2R09?{P<|1sL|E8KYu#?`k=7U!a|qjcjbQ2k229t z2l2KNAyUjlP`ld^3Go}ji9rPIoNTC(4Nso2f;L5%%#>W2pygOPk{}lV3f*3vnc1oi zWtH|+FpY^9Jt+(|w4nD1RIH+@A?eFw{ps=e398r4UqDdVL|V&;bTkzVuf4>>H^h&| z>kj^q<6xOtA!@Nbh#-_CMllN;RT1KRIaptO2x@kERWNk^aJ?T+iD~MJ3%pDljUp?I zgM!J2DwdBsV8%$mHFtRDLZ*^Q_j_Lsz7Be9llUnco?5RHrN3CAgKF*=dhMDsqV?#z zWn97SN;dDK`3T``Px-|I7i2r^{Y8xxgL%tOM&JJ_(_Xq3M?83Juk$Gd(M$~I{~}BJ z|4765Z~uLTUy_R@LN}b6VE0;Ht4aI_QD(UX>&GLs5hQ!KaW=`vDOTT(kPrD|B8lZJ4)cw~owGQ@`cgb9PQ2h3=V}t0PBhl&1-m6_d z-_FT9YXOVvBc7Xy3dj8}&uScnF=g#cfCQ@FbG1#AA(0=%W7myG1vexqz4-yq3 zEA~-eK~X}}Q_pcfHO$4w9GfSB__0-28A)_#aBqE+*M*VgNvvXs_`qPnOeB%%cy$l0 z5Q2k9@d!LHF~F%C1syy}B}36({JavLmH^v|$#66jL_ls_4JRfu|BprUl^kw<+TxJI zXscdG#K~QzJ!G3e_NOMALyJ_K`9`v7A@ra*;+L%COUd8G2(4xE5<@yZiBf4J^$_>y z@q3f`1LyIkcgIM!xl$yZ^$)kcMeMq^42%{a`^FyG2^R|%igge zX$>OI8Smcu!bzNM_YamxNlh%}17 z1UDcF;a@1c8RWn@xYFDr$1EV+Y*n0JZJ-qc1Bwc6-{nuhh-qfasK}iWIvN$*Q#`64 zrn@ms^l(wepJ4GzY*@!HYB|O#X7F}-kjw1pcTdiK4z%v2@V|G5h=(}XXJxmIdCTR2 zBDL4AemHq`UiRFkj!bh+7z}HT(+doA^P?SjJI8wPd%jT+pmHoONXfCevngs{isR9m zYP(#YlY=ovD@TUJQXuWwrIFc!pr;!PPckkIj zj;2hdOv;5P((7}sg!LsJ@{;7h$0dJDoxbv5b_t z*wMptbGbgKOjlIR+g~sy_8a!N9$U^b3pcp1x!~R5qLt3kf;oDSw$d*jFZd*EVH%_g z!6NJ7CF5x&LDgD5{S^DvrNhkF+5QB7od$k!aa~W6sT|&#kJnTiUS71`y!L4+Xf{Wd z^-x9JUpD0^%M4#L7QZ3VfIrMFAvPE>NvOB$Ms60_G}9VU#w#e7u zf{k=?CTU+v<%7Rtp&ce2UCzn-&-0xJtfN#-stNUG`O!>WdfB9+oN15Udce7iI(WqKdBG{Gly z{{;7|*RX=|wE0g@00%V{jI7nQn#QvT6w?esFeAIE!@kq0@n$?#B+mN&T&pOSV)P|;$1+Wg7YJUkt@a2g!Ncg|*pJ}E6J)O|y)F@$d zvkzox0XBukpPV;ed|zLAO7jWv(I2<+Sgi35zmIZ>j}6ydx~@*_82b<>-I|{J%0Z*R zKns{5LH%;+-Hb(F`OzcAu;i;)*oh>|=hZRBRTsV%T-3F1IC70-xAPkkc7m#~;$>Tg zkPF|?XTw%ez|`{no1mFNR)ew`-!i7n6VKC*-?2nZ`6-V(7^z%f3k zd~P-WsA^Kyuu_B4sB0fOHt+^o2N@cTj*zmmqV`ed)Ia#z>~Yv$e$V#Xd}-*6r8_!* z*BiT@o!Sv_*Z#MyFXC^?-TYT%HsRm0#}W}tgbtQ1$~?nwfux=ua}*Smb&%7%Nu(j1 z5(jo`=4%g~kIKM4-S#B+44b!<^5juej>7!6rptM^GkSXw--&WOUp3B*S5{TQEDy4O zwcV)evdMRD0Wp#o9_{KTq|X`Pp{h z`!S2XPe*&6Pmu$w1=qzQ0(*=A=}0af6yil2p>7^gkelej?*=T6z-S0vf{UG9TP^-W z?m+B-W`;WN&WY9dpBG*~r_F-IUOO?9=C6~DHi652Yw9YB;NlRp#jvP<*2xoGP59jl zaG^AQ+rM8uzHeqLC=AvIqb~5qlAoHVq~VbSg+@NAgRIVK3j@L=iL*YC7Lo=f`V~yb zdyXlZ@6SP#aM)7`fbUG1)Fsq@7Zd0X?;&g|w3HT6BlA}4F7F58P}H@lrk&?YRKrR3 zxxOyU?o}Lear;@_%WxNx@CAG{z6v>2nlsFc{O-(jF7q#=R4IE$w5&9Tn9lg7mH)B| z3=66PjEz?Lx&OEFRR5j7kJw9KgDES|os|SUj1jl2RYAWKz7fNc7i)=JM@;~%T?4|D z_)mVjZvLb3$=ucXn67FrOMco0*Q(CTOVSYBS0qK1&3XTzMck;GYlyj?rFVoCVjlGS zu9yX`>p`BZ7jLuUO50*5lx7vHVaziow!@YP9_;lIU&w~dNY=`}aFi&Evw!>f6aVtl z=VtmrTx_apN`|GYgoE+*KkpQ?G+vy@yp-i9n3J&xyffzscwmg>G05w+M!)91@(uv+Qv=>BCogckC zL>G}4Bq@^1@%mN9^XSG-!rDBpvF)%oW?{GebqDm@CmufN42s}k>_@$dzwMiDBX&gS zCR(*LO_biDG6aSLOC32I@YZiD0&ryq-vx9Ny*ir4SMa3E(3PX`$usP{vvVU=t_I;u zGdnr=;vIPg)r2dg)QXfyH1l=e)knoTVa7p{NtDnt$nl1S#un1x*VP*C7M<|)~S1}m%`csHJ?;l^X6S#t_AfAZpC??UaU*ltnVzd9(JlR!{7hhG== zqg4e*6kLIRmFd;!>4^;aYTi7%^qSr0KJRTA6z_xV+Gmt&bKh3}J}N#seAoNrJE2V- zA@XY3VBu?#+vyYevOHn=#1}7toJr5+!UpKwtfYga(!kqXr&Hi1|6Tce|E1h%on8XR zUO@?2@8sm4Rz>XnK(Wa@@ad}2KkG)z;!+8b@c|;;WhDW)r34#7n-d{W&7s9-HfRCG z?`L{%F8WG9<7iAxjTCN5LS)CyyL+36S*`Lc+`q!z&7lF(oj>X~D(Of~_UI&Fel_U3 zaIKa1maILcGB*3dIx5KXn(}-tronz@w~Vc)J#@ybTWGVllWinwJbE9$!ZT4{p&dC# za;8RfkQ2I{uaBGwdFpb6gwQ>x8?XEH%G@`?O;}05?|_wZxIN=Y~qj1bQZ?a`enwSSaa*|DW+` zpOV^T50G@kh({SO?Xs1+i1pY`;!d2ppDz=UDpHC#I0{@#W|ZwK8u-z-Rul{W8X2!< zdbd^WYaypDKfIi~>=XryBxZ~aJlMNegR)>L_;%?#a@Q6DqiHFu_}VIqt6cis79lXdEQ z?!|n%n`ae!&(Wo|K=)k0Ir1^F=Cea~+dpa-KlCrFc-`gsXEMVql$~>_SGHSkiE@_` z`tGlwymMQlKgs^(%lZDt06dWZR^CP@s1tz6RYMZLcXrBJ8&VP~Px?);4AX1$e4LMUBvUD& z`@+kIFXIf4Hx@S(IZkq5d4G89-qL1b@Q zS5ErXu2B!o{dC&ts7=rr!1l*Y=-YHPEmHDWlQE@R!vg>WhB;P64r%vuT5X?zfkSwN z$;oFT*5Os4SayFBe49x78R!6YGy8X-<7IiMM3Gf+gSTl4-RjvycTgF+;n0TnF3MW2 zdcS&1rl%J5r$4PKMf4dzByNn~B08{ED3-^;*#t0AlK4j0VXYuC`>+U%(m_@Z#Z0x8 z&Bfq1ea<_xFZT<5nCfT$)2GPQcg)10urJ7e6l=IrbXhDs-`d<FQjClKYeFmp}B{ z<+FP(nzR_=J=X9c#m&h|aZ&hw;=lvvQ}G94cK^56`Tvo@_@D2s{O_^mlAn-^;56F^ zNFxas#ZxSKlm0B0=0EF#5~KMU9Rh*k%swcMos)`?v4&$b_lVyP&JHa1wnW?WXlbBy z+4^0Tnn%P2Tq*cM;`A1U#DLGiar~hB<)`f?dqeA(4vRM8e#)NTOLZ9yzA8~2quh#W|{eiE2%X( z{0O!ncXU=|koXz^1!Yk{D^Jvr5X-o~9z(z9C#ELv(@|B>tlGSs#N$0fanlM}(W3Ka5 zeD1w9T{`-$x}p}M_t(TZYWY_$1C;R~t=ZDn$3d^_y|v83_8sGktWJD#I;m7 zRDpOK7LM8%_7!Rn4-$B1!4&;~>B3bLDe{D_;4C!F8#+Fi`ApbVkF#CsU`g*jbg4Hd zZDu=GMr8hK%ab=}HTOSMX2g_sMUQoSnE%y%YtP=d2=;cuPEdr*=86}ie&-9%5!%$i z;N=mKDl(_AR0#n~w@o$ok7^YzB6iXxp5!FKWmSLLwCdY`?P5s>@_@|T^u#;G-~ix# zP=8^!H1W_he!Rz zXgA95W07=vrpLCy%0iNhTrQP|ZQFRa{JN?5ASG|Ojv1#={r+$GxBMiQ z6JtA6vJsHNuKoye$&z#-nZNpWwK&S|-oYO3&xHxiZka7-4vcmN&YRhLxMWtADHz`E z;&mBh-n)%Ce#|SjrsZg}ZRGUOqKdFTRCVZA!JoICLrN~4a_g?2?pF_E{P=w9LjUxx z#JR_+pY)$AdQ=?f=-r;VW9a9blxv4wVvpSOupaLu*b+7%*dtg-x2($u6EO=*L2M%g zIqv^{sj3{0f$E?3;YQ!}wsfb6WsmbcIuvO>4i6WoMHZ(Y6>WGQS`zh8+$#b@t3-X{ z1J!w}f~>X1C=7s3xDlj9a*#t1Y0}F{$xsKH?DpS3@_A%;(8Y}P5TT?=&;P=!J5x8@ zS&wj}zCE((LPe*A*Fs<>tHV_Zy4uI2{5xf zxr^KQ?QQAP5KOZKb3C17?vfNjnLpK8S#d7&e7@dew==(LE=~VN`H)EcTq#ZpfaPYO zD5wsC6!4HX0H=}Xo1-$9Yo`m}LkAlA}EauTlr%$;x< zyMfiiCCC~P)$u`4S~LkNn>EeZqqG5IWr5;{CZ|2|^u!U)o?e_C5@~ZZB$=A>={Pm7 z{FB5Ek3S9ds_a{~Q4e@Lbx|lE zEji{|Wm|ph;82x)sd|tT=~$JQv+X( zSZ~HV@`!0aN;AX0B6%rkHFOoel>41Khp{A_wcz_#*H1Uq$$#hrDAFf_!;}oHea`ZB{Ab-hyM3$Y zU7LP3t=K^r{5j|pfO}}Ck$9*sMiNoB6$UlIjQV)wPJ{$5|8?uGQ%UXi-F85tnKWR{BvO^FlH%*yPS3?VB>wT+19tLI4ywh_}eA2cZ-mlT+5olAgvHaY0cafk*P< z_B0*b=0JD zr+hGDM650zIQDtv>c@Dk-e2_et?hEvNgdAI$3=;7D1-#E1&EiLN5ByTpGIvM%t zK_MM)8ML*c!rX=AHtEp$q2lKQpUi?KZG}&Se0_?*!&ByLQmQa3>Hb{p&43jZ&6U`N zs!YL3dv@*`Ts+R=p_fGR0fI>JDBo~lEu77m)Z(n972pUQ)cfL1Q1hnS`dV>i&X5;( z?c@aNeZbCC3Tr(y+il+2F=*cXd<*xN+Aj|dT(t9_b>eN``Ms{@c1euC`zMTS?j0PR zaUf^<%jC1Ib79-O1Bb1yK-sqpXP>|ZT%E#_|Ooaxw zD`_;Q`L$0Klhf2@7WSRK|6rHNk;(ooj}NgnoX&bB*Spc(Liy_H_6ilV=}}T%rNF)J z^ucQwEdo^`US>|rJEpI3qmb|B!^NzY;7*O0w4tI(b4+K zjf2aJHbA3W6kS#$adu4AIDvH-$yAJ|OsN zGyi;1cR+J>Pmt7p6Ennx@iM$6xmjFcQaxSM_0dS%#jE#wA=6En?>Sy$WiG{gsCw76 zwmR%j)|F8?u0yFc3+`$$yI3JKe@}{DyZ}XLxZe);DAT#)lKFryo3+2uJKRafsZ1yO zdU%Y#dFfqI9C&th5$sGB6)RHk;aa`q+1`)ieHFwGP@O~hMuPi%|BPtuL}U4egZoxf zL@HN)dJ@{(`NF@x<%tzPENM{(3%O$5*c(^h&N3TML> z#yR^3C}x)(QC-%Cqn8rT@xOK~DYa1CS7(m!zc4gfDu!1u57I8w*HdYAAC{$$?H;tW zBF)A4(T`@Vpl;E(vWdyoJ9$gs$W76d;f>v-Z{=thOrGS?67d?((5-4bMv8H)$vEbD zaDn38Q1o+j-l2ome$*n$p^D3YH$O_7|1N6gSItRuwjQ3HckO&%G%Ow9ik19rYfl1rzAdF z*sj>B0_7ee?)Sx?;!^Wh-=1Vx#f_YbQ3(qzxb&(#pbp=A=xsO! zta)Z{_`}CLbX3cJTs%ZQ=$2i9xQ;nr{4H%L=~S=F&c}qja2HhMeLFr%7zCcB$Ak^| z%T45T#069i=(y^WPuEXP;B_IEMt=u3xqkX^IQBW(+G=nY6%>t&Iwpsen#YWnGQ97b z+eb8<_aS`xE@%YbVK)I-v0v1;=OWV>^-9@vH_nzHNmOE|;VnTvO4(YI)%B|ogQ==^ zXuCc((Vujog}8Iv8v=AZULGl8@13rK8)%Lf;-h=IIFD^e`kJAx;<3Gcd#I*fJBOrX zf1|YWLPVu{g}3LG)%6RtPShXM`1axeSNHq=ONMvT-@G)t{rloKQ^r1$BdfH+iBEf= zE+aN2NPxTzu#b3ds0pu718Ze*2b`iIcvP-D!R*kq=GBXK@kq-qcN@MiJ5kD5ODRtC z9TKy=QjT;OHO${^H}Uu)=av@q32fOi^0fgbU9 z-@CN^S)5Rq6VI(J^{O|Edp7;2bmdt>3wim)vzD2Ie=txj5%lBNr@W~PauIZnlw|Z9 zxy7|SH#F&VYj^uRjndDqo|*3nv`M*Ye9t9H$7i8T_1te;85aO$hW}=Kh6%toNpwV9 zG8}i{*5vaEf^>*n89(9Oo%B5WSDRRZk8EF)<9uzKj5~L7J_@CR+d)teIW+SZJ0Eme z6v5AqSURFP88_T*NAU>m)=i~(sF7+am6}U!DJr5X=H=Zv6^g-x%3t;I@j0&Axv7ON zqjPHe&W;RqXWJ_E@XBX8PIEFRT`7rIK1H^G|9Z?+L_{3xKv7e8xv-4%$=UD`NUc!X&$xqGcU&?lOcxCQFLmchKfu= zt)~=S)Yv_F#_PxOGJmxrG~!KbVSDkql=Jn?FDb>KOEOA#t^7EHHkZDW-t99QxeGRB zt}9v+Z4%lN+NMxE_iBByp}4=HTBsM_KeM|fc1aTXYz`qEzYcV^&Nlj_E*ea$zmY}#V=({C}wWu$RjyoOUhXSJ&tDdjUg1vS#O zN#9M)vuoK)YEXbvUA`9|c8eN^bXHP5uUbQWg6ct+V$3gRteyUkPy2rc8~-!U0LoB2 zIb+B-WiA2LCpv#AVWo;C4RyD{flNR*@WONG@V=Cw?{J)Nn%pW6)q8!2sRb#b(&B0{<(}W)+{65PSw$am%&mgcW;e;wB;w1{p_-w`mA5@l)}} zYfZ-e6JvsOKDasto?@-EMkI@1*x4JDswCbBD=i9}G5TkD0^^C!leL|AVQ%msKV)go zX+8by4+Dv)Nd-c2ib#1D^a+5tmjzIj2wLd;XI)u;w#gbEB&9C@as&|dNRezb+Y+uE zZ#g}$xr>j;Fo=>?;z+hJw)I~sHixRXj^=7!*u`Zs?Q}zvUGnlq#?TMEA9r#kulp}* zYhAiUDAId)IaKNVy0xRv7>PhUr!5*>`4e$ciU~>rm4DSNpncez7a*x>zj>O!^cXf? z8SldXDiEiJIZ~fZKrA2VDUWrAPboM*5w}+TzL5jlf{uy%JY20dCm7r-1Fr?wD`M~> z>8zcIU?Kb?`CpXt(O%(>_z&jY>AQK?v$%qn{IaB0xDUkiXs)m0u@YOgaB^JBX#b;- zhc!9ss{R$rzzSSOhrisb;`#S+A*ik2xPHDj3HC%ilhbWGQR9w7Z|O3Es-!fsOfXrzDb>5WmY8RDn6I-0a(b)*T~2yNDviYyIM_`9eMX z4Peef6cG4~MMq(PcnAewmWR}MvF#0Gi=`U}TQxdKr)r>Qj*64c{G&DXP=a7^%z>Aa z6xnEEb$z1S|K6-G@148iYV=L`e9|exdK6HwW)mT^S%Dpe22BdV&b8@_;Xv4&KvxYw z^;ClP^(?1ai#ZVD>Zu$`C8XvN<>^S+t(A;L+EMc;a`rOOTCI5iYn>Z+jRy7+y3&8jjm9D_nAuet*9d6XQ>PHg^;L9GoX@;G%ZmJh|1n3*4yLzfUr; z{_8>z*Kjjlu6`{~6wXH7Cd#xP0fH?>wCo`8ssf*2mO4&o!|xQ_EP$t9iPTMtBGy zyB)7D@`RlETHP_L*~WeA1JZxuYD8AEIqOB1lu@kzkI^5^VXjOA&C5mSEyG0?##Pyz2N`L8}R_zN-Pg)!0x4a&_d>67(pr zOw;ItrD*GQPz8_7H37%0r31%;rTD$x>)V8XGn#uQG;%H4dFTpNW5c9fYrg*=u#h(Y z-zNFgg?@m-b&k;X8r+u0!iw^Prt=d{cWH-o8?)x+v zNtE=)H6tlwltq1~>QZuw$gX5UZ4rC&sM1Y14i_Na0%DJ(D;`&0%U|kTf^_zm0ErgD zM1#NfkVu6Kn^c=^pKsdn@}3iJ$a)iG(*fT>aijjMe(q<=oap;FL4|^N>RdI%y zgfJ|0OiH|EJs)RN=d&^vt&c#4oxe!+g^Suul-j3SD%8ds z>k6OiwSLN3)a~ASp=AF4cwfBbZmxrc)oHu6Kk@Gw)YS#5&=>`Rc$3}Ul4(>8Rkhr#l9Y}K>7<1Mpww$3Z*)rI{e7;V(U}pwvT*E;ppCyXZ=gi- zu>wsVfDK^MTz{$ms!UqS9Qg!aNk za%v#IOgJlk+f=sD_zaI36I-AQayrS1BDciO_}#oBTEdsX&-}}294k%24)laVS*i$$ z#Xk3DB-xhlbv*7J=io8?LsS09*jIQKf=xqE00@iC!GbA!+pf@fd6C*HNTbFV3R4m9 zn>s#wf$zO>lo43MA7ytM$Lk}vC}wn|M?Q3b{=W6}PBuB-dg?F4^MV89&sBmka9`iW z1B`k@k&>hUkn$5yI0ue)n-zg0*+ovxu$RtT={Wo@K#7OibLfu5pWJ7S{mM8-I}1kN$0cauzILc0Z;O=JWY~92 zT|PEFO8CvV55_E36*GE+^=RWuLWug4~_t;=VH`cvrJ69k+AO;Yp zwgF-TYSBY@0yoK463HM0QV2@xU3Qh@X+Qjbc>~kqxZkJ4x{d`7KH*Q3Z;8~%IJgrO z&Hge{u$i~=&$VFMgbcj42fep}F&I^3 zu(;>n;nvd-_thBdmJ(E_!k0&t<9^;}A7vV?i*5N==Kp`uKb5BUwInFoZUle42IYf} zF-UpN!l-tY-Pn~pxGSy`d)zq1WfPaCLKRDWe<$!Y+$?t3MMFC!=!kJ3|Q6` zeE3==0{z{BEQ{X>g-5>-e~5zT2z}JxTdwJiMlWBql!R}tSxJia~x!Ln|9jnXWKOi89~|odNxL+jA!u5A4iGhnt>V|#lKj;q@n(E9 zRK_b|wwk;eiPve1;8x4MjyE2)G$nnfottYqHTpQ;J2$ULdgM)Qv4f4}J+-)6`BUqJ z3;|$@CSH%3BU z*!?a|)#0e-rQ|=vjK!c}6F(_VS&B&F87`#@uEZHKa9;k-kVTsoEAr?xXWtM_%~4C_ zQ#)+M4E`mN7nvfGFBCY8St2SyGaBSwcdd^?HoTHf4~M|#xU%+Q1*o2v;+5`pRCem0 zb(fFxbEE!Qw;ifF*HHhe!0p3iN3ncyEYV=pL1?pSs#XMwlg&NxQ*fRw0@6k;(s@3K1ggTAj5rZWb6B zp|;Jp%JPF!R*(S9rTW9&LEmYdo8dCFuL*LvX57E?TfVJy5Hn_xf+-JHUCAcCK-V9S z3&b8-7vB>PbUui=yT*@g)SwBdMJcOHBv2zK+Mwzb2%?CM9BtuFTDX<*v@4Lc%}%7*%yzwBFwR-_ofm$VWvUEt#g@m76a{b@e2yT!qskLW;Y zFkf`@GT7Jz>z?pgR%-NwRTFh&nS4X9CJN}aGQl}9Ck)<;Lu&Zs}pTl6j(Ob+kCE$pWQm- zt2E!AAOoVZUeu10u zCU^92oYR!%6_;ck=#YM&%aQ7Ju%Hyxmv1^+E3O97X&;0A1vZ`t3SGY3S-=O;gkq*3m**xvX+{y z>tms7;rXnALTN}h(`RR!UC7cpfP9WXF4OxN7_66_K$k?YDr&uP5Sw8}X%IHpkv5#J(_PTWap%aOsXd#07~nKz}XLV00+j^_F^f_ z4f-i;2`)MGaA*KucV0>-c(p62-sj@o7hiCUJY)uohife=jeo_PA~k(}wBUA=xX_#%BoLF?c`H zUWh{YW!=P`BKUx%Goi%O7o)iqierPqOZPnS4DSos@<@m(23POHZ>T_|oqF)$RJqq& z%qB`DcNBFNT1w1yFUgFwL>%6S;S%jXO?4%?==kH4=p|9VVy3KN;al3wn!=>kk) z3#l(dT%a-BflF+Uas1v{QDiXbJWzO+ukm$WQluUvw4DcnyJI3*Edh<+-#!q^L8hXV zNx+m==tnqRJ5(L^xfN=x4z}LPuc4LOhpNwDV7z=JUok7N@@-v+dk(fl4O70kFhl@E ze89bxplX$vuv-kCBSK0%dNtU5^HV-e&^%@tRrP)5n2%{;hpHVDxgP%)Uy`}BO=LzJ zHkg(5LLaPxOx^j(RcKPGew3rDcWvkys^kabN1^MiWByCH;Z*zpYGb@U4#pvf6alzr zjISin0yeqANeoI_bZrRicTjt=+J|SwKY#X6kvs9np!`x6F42cK@}Oetm`ToNT)o%m zG6v(*`-ZMo^@4vq^be@;Hl6_I_ty{x4*%DSr$Bhkjz2o% zo>qE#`dLs`<`sU|=Pi2a*PciS0dOQC5Ab6^G6mqN`%+#Xi$4}WsJXl8-ja7y(+9C4 z{s52m5gWe`@6QjU$RNW@aER0N=K2JEQ`>|u6RVxAdLuPg=*Ku1wL*DD%YE-ry6H0v zq1WIR$O#MyZI}O9hxSp+$sp7cL^2V6eA>c~CwC~py#3f8u3fELv$D$C3&0=2O&M;d;HEk;aOVa@Zt;X zvM1?MqP-MMaGb+{%yPtA9N9~-V8B60S*+!i0SPZ&*WEH4f$6WpW3{6)|G)t!;mJ3_ zow+|wX-~g31-1M5nS$utEQQmfEFNv2n0fzpa7$?Q({$wld5NzmXz!{>oywCCZ*kl^ntNVsSp-dMK&Ct71c_v_-r z1B>J+izo*xIiE2ThLHnuu7L4QPQ2sF8X2TOg>VW3$ig56BVhZk#(#)c;F~VmajdQh z9l&cSLh$mlEmdmli??sVN3;7(yrQ>*ZAyt6E;fYqN9;mCiDpI z{UgqzRVYyZq5*QpxG7*y?vH=RpzIqHgs?=R&{iOKTU@1>J-J-vhw1j0_&`5X?S=lK zd2TdMwV~uj(0bXot-cah>b6^~i_`eeVBmjdF!2BPbw&XN9njAG^coR{5*qbSfB<)% zoK^K3(3uk)!4VS42m$?mQ2YyB#jlCf;tXB@#~_DCwuQcP`frOj@vw<>R}MICpcJlM z8#zs^@xeq_I>>`>nt=~6cv{iMmNx9x)Gh9MAFA5+v|;#1D7S@HU#rMqpm?ruIb3ub zK$tiYCuu0Ye{(4magw@r2PF1|UMr#4bJQVY=*J4-wpe#yIm0OGhTb_Ukd{GxM4P)6 z)cekxlU9n00_9ip&)MvfPA4JDC+>4|*tHY^C0 zUU>C?#h#{FA*fBDQs`!vwKrPoUT|)Njmq*OfgCB@MVA&+e*C-Ro&5C#YfSiR+cyAb3OO`DC2G5AQ z0qX5I=O?{`#aZnjC%Uuhv6&)2sW}1n@(>=BkaE;LHDK(gGpH+Vcp3!KE!7K=(}s?Mib0pP4zp$&vwDVvv=D!1Q0G#&J1z|6*e)bnR{TtYkYfV*=K0Clf5L|1XiGCmVjddy?6uT%?6VQg<~Y_COX zShc~^^e?0H6P{mbjkF>~PXqn0X~hSh7VP)hlXo=uldXrX&T&MysYvsN@GKV1LY!!C&QnT8y1F1c!0kAw~#N3d(o6=TnuD z732xI0qeV^cAvf&0atJ)tZX>yH-1M{k^K!)?sGdA#X0ZTRn|n|50=PY??byh{jCLCzxRRs>104cFYcHxVz% z6CNDKZEIjA{LeZKYXvaHI^8fvm*dNElsb@-xRTS&G-_k=>M6eD?Um4@;=>AyEhDtz zJ5$CDb-ma_w5Dveq!WC7!gdrdASFF&eFdTIw56=e=D~t)fgj)gG+^1FbIzR+Tg)BV zlVdiyMY{o4a6*4HVjn&Bd2stP0#JwneXjqby=x7JI_>{jNy1JcHe!lOtu)&@m6+L% zl2DXm%$CHMG*62R#!QHD2v7CI#y?Z!6k~F>4l@mgm6dYLh&gp|n*63Rersla{qLT4 z|Lb}Af3eStedBVuF5Gh;zW4q8d_UjM=S>m$6Vg3sK%323;atbTdwY=+Xr)0c_}N4j zFau|74p)CqgB>kI!S`h2$hR7*TNm`4<2cfiLG z`Ka4YVwRhGu)omjnNDrRZcRJn7F!d-r2q-*uhQfwESgL_3Xzi9H$Eg3ZG!SsoerEQ zmCgj4K}a@XV^02~*?`2yE+j%|C_(2`mqEDN*}mPpU%#jo;gp*#zo=R52kUIlSwss~ z#-{CApD#Q>eYyj(l&9^w56K6Rbuo%n6n}`vWG6{L6>u-~wuw_c)AWSkSo5$#xgUOM zsk8`CePI6l1TP?7vv*VmoGmJevCY5ccqQYJKmU22+(&j8$-aw_IZ&L7k`b%a_eQ!+ ziC@%w&64mlF@G7Z`5$;H6$-aaZ54+bxC|b>-q&{mFq1;r~u~lXuX#mvIrK zQ~`8Gz&DYD+F)`kGR(FfAvqnaA?FJRuVdKXGjzfceb5%5CB&uM%;rs-5vAJ4pkBHY z?ve8N5W{!)vvb6O;}qOuqG1v0$

    o65n|D`aVn!4GJxnnn3RZXhy*Iq#1u>DXV`c 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 Ifc`S^zh7kK9{>OV literal 0 HcmV?d00001 diff --git a/web/clearquest/cqd/CheckinPreop.php b/web/clearquest/cqd/CheckinPreop.php new file mode 100644 index 0000000..6e20d9f --- /dev/null +++ b/web/clearquest/cqd/CheckinPreop.php @@ -0,0 +1,50 @@ + + + + + + + ClearSCM: Clearquest: Deamon: CQD + + + + + + + + + + + + + + + +

    + + + + + diff --git a/web/clearquest/cqd/Releasenotes.html b/web/clearquest/cqd/Releasenotes.html new file mode 100644 index 0000000..c3a6437 --- /dev/null +++ b/web/clearquest/cqd/Releasenotes.html @@ -0,0 +1,33 @@ + + + +Release 2.3.0.1 + + +

    Add a bug to a release +
    Official US 2.2 list +
    Official Shanghai 2.2 list

    +

    Release 2.3.0.1

    +

    Introduction

    +
    • Closed 01/26/2004 @ 12:00 Pm
    • +
    + + +
    6 bugs in this release
    + + + + + + + + + +
    #Bug IDStateOwnerLocked?Description
    1BUGS200003541ClosedyxiuFlash driver: improve exit code
    2BUGS200003544CloseddkoCheck in: IGMP should provide groupid lookup function for SAL
    3BUGS200003558ClosedgtsangIGMP fast start feature
    4BUGS200003559ClosedshouWebSAM: Cannot create VLAN's
    5BUGS200003547Closeddkoigmp_getProxyStats Exception
    6BUGS200003568ClosedzlouSAL support multicasting service dynamic activation
    +
    +

    Look up other Release:

    +
    + + + + diff --git a/web/clearquest/cqd/cqc.pm.php b/web/clearquest/cqd/cqc.pm.php new file mode 100644 index 0000000..cd7860a --- /dev/null +++ b/web/clearquest/cqd/cqc.pm.php @@ -0,0 +1,50 @@ + + + + + + + ClearSCM: Clearquest: Deamon: CQD + + + + + + + + + + + + + + + +
    +
    + +

    Clearquest Daemon API (code)

    + +

    Defines the API to the Clearquest Daemon.

    + + + +
    + +
    + + + + + diff --git a/web/clearquest/cqd/cqd.php b/web/clearquest/cqd/cqd.php new file mode 100644 index 0000000..7bc8c34 --- /dev/null +++ b/web/clearquest/cqd/cqd.php @@ -0,0 +1,51 @@ + + + + + + + ClearSCM: Clearquest: Deamon: CQD + + + + + + + + + + + + + + + +
    +
    + +

    Clearquest Daemon

    + +

    Implements a daemon which services requests for information + about Clearquest defects.

    + + + +
    + +
    + + + + + diff --git a/web/clearquest/cqd/index.php b/web/clearquest/cqd/index.php new file mode 100644 index 0000000..e8bfb2f --- /dev/null +++ b/web/clearquest/cqd/index.php @@ -0,0 +1,334 @@ + + + + + + + ClearSCM: Clearquest: Daemon + + + + + + + + + + + + + + +
    +
    + +

    Clearquest Daemon

    + + +

    Overview

    + +

    At a previous company I was asked to provide a mechanism for + controlled checkins of code into release branches. It was + decided not to use Rational's UCM + Model since the company was small and it's needs were + simple. Additionally the company wanted to be able to produce + Release Notes depicting which bugs were fixed in the release + in an automated fashion. They did not want to incur significant + overhead when checking in code and wanted to tightly control which + bugs went into which release branch.

    + + + Problem Statement: Provide a mechanism for controlled + checkins and a way to automate Release Notes for releases. + + +

    Environment

    + +

    The environment of this company was as follows:

    + +
      +
    • Small company - ~30 Engineers in Santa Clara, USA and ~20 in + Shanghai, China
    • + +
    • All Windows shop
    • + +
    • Rational Clearcase LT
    • + +
    • Rational Clearquest
    • + +
    • Rational Multisite
    • + +
    • One main server serving both Clearcase and Clearquest
    • + +
    • Slow VPN WAN to Shanghai
    • +
    + +

    Multisite and the Shanhai office were not initially rolled out + but the design considered them nonetheless. Unfortunately + Multisiting of the Clearquest database was ruled out as too + expensive for our little startup company.

    + +

    Requirements

    + +

    The requirements for this Clearcase/Clearquest integration were as follows: + +

      +
    • Verify that all elements checked into a release branch were associated with + a Clearquest defect intended for that release.
    • + +
    • Verify the defect was: + +
        +
      • Owned
      • + +
      • Only in certain states (Must be in Assigned + or Resolved).
      • + +
      • On the list of defects for this release.
      • + +
      • Different release branches will have different lists.
      • +
      + +
    • Allow for some branches to not require a defect number while + those releases were in a state of "development".
    • + +
    • Defect numbers will be entered by the engineers as part of the + comment. This process should allow multiple defect numbers per + checkin.
    • + +
    • Provide a way to lock out checkins of defects for building.
    • + +
    • Provide a way to generate Release Notes for a release based on + the defects fixed.
    • + +
    + +

    Assumptions

    + +

    There were certain assumptions and other processes already put into place + that assisted in the solution.

    + +
      +
    • All checkins that required a bug ID would have a label applied to them + that consisted of the bug ID.
    • + +
    • When engineers were done checking in these labels would be locked so + that further checkins for this bug were stopped.
    • + +
    • Engineers would be allowed to continue to work on the release branch + while the release was building
    • +
    + +

    Check In Trigger

    + +
      +
    • Controlled checkins would be done through a check in + trigger that would make sure that the conditions were right to + allow checkin to proceed.
    • + +
    • In order to retrieve data from Clearquest CQPerl was used.
    • + +
    • Initial testing of this trigger showed that it took a very + long time to connect to the Clearquest database only to retrieve a + bit of information. If many elements were to be checked in the + opening and closing of the database made the checkins take + a long time! + +
    • Our sister lab in Shanghai, China would also participate in + this process therefore the trigger must also must minimize wait + time over the WAN.
    • + +
    + +

    A better method was needed...

    + + + +

    Daemon

    + +
      +
    • In order to minimize database open/close times a daemon was + developed that would hold the Clearquest database open and respond + to requests for information through a socket.
    • + +
    • The daemon would return information about a bug ID to the + caller. This drastically sped up the process for the Checkin + Trigger.
    • + +
    • Additionally this general purpose daemon could be used in + other ways (e.g. Web Page Based Release Notes).
    • +
    + + + +

    CQPerl Problems

    + +

    A good daemon process:

    + +
      +
    • Puts itself into Daemon mode
    • + +
    • Is Multithreaded. This means + that it responds to a request and forks a child process off to + handle the request so that the parent process can accept the next + client.
    • +
    + +

    Since, at the time, CQPerl was the only supported way to + interface with Clearquest it had to be used. Because CQPerl is based + off of ActiveState Perl a number of problems arose:

    + +
      +
    1. ActiveState Perl does not support calling setsid which is required to enter + Daemon mode.
    2. + +
    3. ActiveState Perl does not reliably handle signals. This mean + that the parent process could not reliably catch SIGCLD deaths
    4. +
    + +

    As a result the Clearquest Daemon Process is not multithreaded. Since the company + is small and requests relatively infrequent this was an acceptable + limitation. Still when processing large lists of Release Notes and + over the WAN the service would, at times, be unavailable.

    + +

    SetSID

    + +

    The question remained then, How does one go into daemon mode?

    + +

    Here I resorted to using something that the company was already + using - Cygwin.

    + +

    Cygwin is a Linux emulation running under Windows. It is one of + the most complete emulations I have found. We used it to build + (gnumake) as well as many other things.

    + +

    Cygwin has a program called cygrunsrv which allows you to + daemonize any other process.

    + +

    Multithreading

    + +

    The problem with making the server multithreaded was harder to + resolve. Code was written to perform multithreading but the + unreliability of signal handling proved to be a problem that could + not be easily overcome.

    + +

    Options for a multithreading included:

    + +
      +
    • Figure out how to handle signals properly under ActiveState + Perl. Research was done on ActiveState's forums and eventually the + engineer for ActiveState Perl said that signals just can't be + reliably done under Windows.
    • + +
    • Rewrite code into another language. The client/server could + have been rewritten into another language that supported + multithreading however much work had already been done on the + daemon and a few clients, also written in Perl, would need to also + be rewritten or interfaced to this other language
    • +
    + + +

    In the end it was decided since the demand on the server would not + be that great, that a single threaded server would suffice.

    + +

    Client/Server

    + + + In depth: Code listings for CQD + Daemon, CQC Client and cqc.pm + + +

    Since this is a client server application the CQD Daemon + was written as well as a CQC Client. A Perl module named + cqc.pm was made to define the API for CDQ.

    + +

    The test client, CQC, ended up being a useful command line tool + to get information about a bug from Clearquest. A user could, for + example, obtain the owner of a bug by simply doing:

    + +
    +    $ cqc 1234 owner
    +    swang
    +    $ cqc 1322 owner headline
    +    owner: jliu
    +    headline: Unable to modify ACLS that are created (observed during ACL tests)
    +    $
    +  
    + +

    Trigger

    + + + In depth: Code listing for Check + in Trigger. + + +

    A preop Checkin Trigger was created to:

    + +
      +
    • Make sure that a comment was specified
    • + +
    • Extract all bug IDs from the comment
    • + +
    • If the check in was on a release branch requiring bug ID checkin then + the trigger would make sure:
    • + +
        +
      • The bug ID existed in Clearquest, was owned and in the + proper state.
      • + +
      • The bug ID label was not locked.
      • + +
      • The bug ID was listed in a file for that release branch + (i.e. <release branch>.lst)
      • +
      +
    + +

    A postop Checkin Trigger would then create labels for the + bug IDs and apply those labels to the checked in elements.

    + +

    Release Notes

    + + + In depth: Code listing for Releasenote CGI + Script. + + +

    With the Clearquest Daemon satisifying requests and with the + Checkin Trigger already relying on a flat file of bug IDs for a + release, generating a web page of release notes merely involved some + ordinary formatting of a web page and a calling of the daemon to + supply Clearquest information in a tabular format.

    + +

    Additionally web pages were created to allow addition of bug IDs + to the release list

    + +

    Since CQD returns all fields in the defect record a web page + showing all details of a defect was also developed

    + +

    And example of Release notes is shown here.

    + +
    + + +
    + + + + + diff --git a/web/clearquest/cqd/rn.php b/web/clearquest/cqd/rn.php new file mode 100644 index 0000000..161ea09 --- /dev/null +++ b/web/clearquest/cqd/rn.php @@ -0,0 +1,48 @@ + + + + + + + ClearSCM: Clearquest: Deamon: CQD + + + + + + + + + + + + + + + +
    +
    + +

    Releasenotes.cgi Code Listing

    + + + +
    + +
    + + + + + diff --git a/web/clearquest/db.php b/web/clearquest/db.php new file mode 100644 index 0000000..aca8d6a --- /dev/null +++ b/web/clearquest/db.php @@ -0,0 +1,94 @@ + + + + + + + ClearSCM: Clearquest + + + + + + + + + + + + + +
    +
    + +

    Clearquest

    + + +

    There are many times when we have written custom code to + interact with Clearquest databases. Below are links to some of the + code we have developed over the years.

    + +

    At one client, we had written a Clearquest Daemon, a daemon process + that maintained a connection to a Clearquest database and serviced + requests for information about Clearquest defects.

    + +

    Other Perl scripts had been developed for a client to merge + together two similar, yet different, Clearquest databases into a + new combined database. This script, pqamerge does just that. Obviously such + conversions and merges are very specific to the customer at + hand. Still this script serves to show how to interact with the + Clearquest API to perform such actions.

    + +

    The pqamerge script, while it did perform the merge in general, + also had a few side scripts that were useful when performing this + merge:

    + +
      +
    • PQA.pm: Perl Module to hold common + routines
    • + +
    • pqamerge: Main script - performs + the merge
    • + +
    • pqaclean: Cleans up by removing + all records from the destination database as well as removing + all Dynamic Lists.
    • + +
    • CheckCodePage.pl: Checks to + see if there are any non US ASCII characters in the database + fields
    • + +
    • check_attachments: + Checks to make sure that the size of the attachments added up + after the merge
    • + +
    • listdynlist: Lists Dynamic + Lists present in the database
    • + +
    • enable_ldap: Prompts for the + data necessary to enable LDAP Authentication in Clearquest and + issues the necessary installutil commands to enable LDAP. Reads + data from a config file.
    • +
    +
    + + +
    + + + + + diff --git a/web/clearquest/enable_ldap.php b/web/clearquest/enable_ldap.php new file mode 100644 index 0000000..aded2f8 --- /dev/null +++ b/web/clearquest/enable_ldap.php @@ -0,0 +1,52 @@ + + + + + + + ClearSCM: Clearquest: Enable LDAP + + + + + + + + + + + + + + + +
    +
    + +

    enable_ldap

    +

    This script prompts for the data necessary to enable LDAP + authentication in Clearquest and issue the necessary + installutil commands to enable LDAP. Reads data from a config + file.

    + + + +
    + +
    + + + + + diff --git a/web/clearquest/index.php b/web/clearquest/index.php new file mode 100644 index 0000000..aca8d6a --- /dev/null +++ b/web/clearquest/index.php @@ -0,0 +1,94 @@ + + + + + + + ClearSCM: Clearquest + + + + + + + + + + + + + +
    +
    + +

    Clearquest

    + + +

    There are many times when we have written custom code to + interact with Clearquest databases. Below are links to some of the + code we have developed over the years.

    + +

    At one client, we had written a Clearquest Daemon, a daemon process + that maintained a connection to a Clearquest database and serviced + requests for information about Clearquest defects.

    + +

    Other Perl scripts had been developed for a client to merge + together two similar, yet different, Clearquest databases into a + new combined database. This script, pqamerge does just that. Obviously such + conversions and merges are very specific to the customer at + hand. Still this script serves to show how to interact with the + Clearquest API to perform such actions.

    + +

    The pqamerge script, while it did perform the merge in general, + also had a few side scripts that were useful when performing this + merge:

    + +
      +
    • PQA.pm: Perl Module to hold common + routines
    • + +
    • pqamerge: Main script - performs + the merge
    • + +
    • pqaclean: Cleans up by removing + all records from the destination database as well as removing + all Dynamic Lists.
    • + +
    • CheckCodePage.pl: Checks to + see if there are any non US ASCII characters in the database + fields
    • + +
    • check_attachments: + Checks to make sure that the size of the attachments added up + after the merge
    • + +
    • listdynlist: Lists Dynamic + Lists present in the database
    • + +
    • enable_ldap: Prompts for the + data necessary to enable LDAP Authentication in Clearquest and + issues the necessary installutil commands to enable LDAP. Reads + data from a config file.
    • +
    +
    + + +
    + + + + + diff --git a/web/clearquest/ldap_settings.cfg b/web/clearquest/ldap_settings.cfg new file mode 100644 index 0000000..6a87d36 --- /dev/null +++ b/web/clearquest/ldap_settings.cfg @@ -0,0 +1,26 @@ +################################################################################# +# +# File: ldap_settings.cfg +# Description: Describes the various LDAP parameters +# Author: Andrew@DeFaria.com +# Created: Wed Nov 2 11:19:04 PST 2005 +# Language: None +# +# (c) Copyright 2005, Andrew@DeFaria.com, all rights reserved. +# +################################################################################ +DBSet: 2005.02.00 +Admin_username: admin +#Admin_password: +Servers: server.clearscm.com +Port: 389 +Search_distinguished_name: +#Search_password: +BaseDN: +Scope: sub +Account_attribute: samAccountName +Search_filter: samAccountName=%login% +CQ_field: CQ_LOGIN_NAME +attribute_search_entry: samAccountName +Test_username: +Test_password: diff --git a/web/clearquest/listdynlists.php b/web/clearquest/listdynlists.php new file mode 100644 index 0000000..012512a --- /dev/null +++ b/web/clearquest/listdynlists.php @@ -0,0 +1,49 @@ + + + + + + + ClearSCM: Clearquest: listdynlists + + + + + + + + + + + + + + + +
    +
    + +

    listsynlists

    +

    Lists Dynamic Lists present in the database

    + + + +
    + +
    + + + + + diff --git a/web/clearquest/pqaclean.php b/web/clearquest/pqaclean.php new file mode 100644 index 0000000..7a79584 --- /dev/null +++ b/web/clearquest/pqaclean.php @@ -0,0 +1,50 @@ + + + + + + + ClearSCM: Clearquest: pqaclean + + + + + + + + + + + + + + + +
    +
    + +

    pqaclean

    +

    Cleans up by removing all records from the destination + database as well as removing all Dynamic Lists.

    + + + +
    + +
    + + + + + diff --git a/web/clearquest/pqamerge.php b/web/clearquest/pqamerge.php new file mode 100644 index 0000000..43863fe --- /dev/null +++ b/web/clearquest/pqamerge.php @@ -0,0 +1,50 @@ + + + + + + + ClearSCM: Clearquest: pqamerge + + + + + + + + + + + + + + + +
    +
    + +

    pqamerge

    +

    This script performed the merge of the two Clearquest + databases

    + + + +
    + +
    + + + + + diff --git a/web/contact.php b/web/contact.php new file mode 100644 index 0000000..d411d89 --- /dev/null +++ b/web/contact.php @@ -0,0 +1,47 @@ + + + + + + + ClearSCM: Contact Information + + + + + + + + + + + + + + +
    +
    +
    + + +
    +
    + + +
    + + + + + diff --git a/web/css/Article.css b/web/css/Article.css new file mode 100644 index 0000000..c660e39 --- /dev/null +++ b/web/css/Article.css @@ -0,0 +1,38 @@ +/**************************************************************************** +Sets minimal style for unsupported browsers and then imports the css +files (used by compliant browsers). The following browsers will only +get the minimal css styling listed below this comment: + + NN 4.x, IE 3, IE 4. + +NOTE: imports must use the quoted file name syntax for the import, +'@import "x.css",' not the '@import url(x.css)' form which IE 4 +recognizes. +*****************************************************************************/ +@import url(LevelThePlayingField.css); +@import url(ColoredBoxesRoundedCorners.css); +@import url(ArticleLayout.css); + +h2 { + text-align: center; +} + +div.filtered { + color: red; + border: 2px solid #6600FF; + background-color: #E8E8E8; + padding: 5px; +} + +.filtered, .filtered a { + color: #333399; + font-style: italic; +} + +.fontsize-set a#medium { + font-size: 120%; +} + +.fontsize-set a#large { + font-size: 140%; +} diff --git a/web/css/ArticleLayout.css b/web/css/ArticleLayout.css new file mode 100644 index 0000000..4ff9670 --- /dev/null +++ b/web/css/ArticleLayout.css @@ -0,0 +1,174 @@ +/*----------------------------------------------------------------------- +This css file is part of a layout package. Used on its own it won't +have the desired effect. The corresponding HTML file should LINK to a +filter.css file which will then IMPORT this style sheet (effecively +hiding it from IE3, IE4 and NN4). Imported ahead of this file should +be one named lpf.css (lpf = Level Playing Field) which attempts to get +all the different browsers using the same measurements, including font +sizes. + +Not related to the layout but also used in this example is 1 of Stu +Nicholls' wonderful creations (somewhat customized). This styles the +color boxes with rounded corners used in the content area. The style +for this, also imported by filter.css, is cbrc.css and it contains its +own documentation. + +====================================================================== + Copyright and LICENSE -- do not remove -- +====================================================================== +This CSS file is copyrighted (c) 2005, Paul Pomeroy/AdaptiveView + +see: http://design.adaptiveview.com + +but free to use under a Creative Commons Attribution 2.5 license. +Full details about this license are online at: + +http://creativecommons.org/licenses/by/2.5/ +----------------------------------------------------------------------- */ +html, body, #page { + height:100%; + width:100%; +} + +body { + background-color: #fff; + margin-left:auto; + margin-right:auto; + text-align:center; +} + +html>body, html>body #page { + height:auto; +} + +#head { + background: url(/Images/TopOfTheWorld.jpg); + color:white; + border-top:1px solid #306; + border-bottom:1px solid #306; + position:absolute; + height:165px; + left:0; + min-width:775px; + top:0px; + width:100%; + width:expression(document.body.clientWidth < 800? "775px": "100%" ); /* min-width IE style*/ + z-index:10; +} + +#head h1 { + color: #fff; + font-size: 3em; + padding-top: 20px; + text-align: center; +} + +#page { + left:0; + background: white; + color: #2a4c96; + position:absolute; + text-align:center; + top:166px; + z-index:8; +} + +#content { + margin-left:auto; + margin-right:auto; + max-width:955px; + min-width:775px; + padding-bottom:4.0em; /* you can get all of the padding set in one line, but Mac IE5.2 has issues with the shorthand method. */ + padding-left:4px; + padding-right:4px; + padding-top:10px; + width:expression(document.body.clientWidth < 800? "775px" : document.body.clientWidth > 1024? "999px": "99%"); /* IE's version of min- and max-width */ + z-index:1; +} + +* html #page, * html #content { + height:100%; +} + +* html #page { + width:auto; +} + +#foot { + width:100%; + z-index:99; +} + +#foot p { + color: #aaa; + font-size: 80%; + text-align: center; +} + +html>body #foot { /* anyone but IE */ */ + bottom: 0; + left: 0; + position: absolute; +} + +* html #foot { /* IE */ + color: #aaa; + margin-left: auto; + margin-right: auto; + width: auto; +} + +abbr { + cursor: help; +} + +#head, #foot { + padding-bottom:0; + padding-top:0; +} + +#content p, #content h2, #content h3, #content h4, #content h5 { + margin:11px 11px; +} + +#main { + background:transparent; + min-height:100%; + z-index:15; +} + +#main { + width:90%; +} + +#main p.tagline { + color:#939; + font-size:1.4em; + font-style:italic; + text-align:center; +} + + +.hide, .filtered {display:none;} + +.clear { + clear:both; + margin-bottom: -1px; /* for Gecko-based browsers */ + overflow:hidden; + padding-bottom: 1px; /* for Gecko-based browsers */ +} + +.clearfix:after { + clear: both; + content: "."; + display: block; + height: 0; + visibility: hidden; +} + +.clearfix {display: inline-table;} + +/* Hides from IE-mac \*/ +* html .clearfix {height: 1%;} +.clearfix {display: block;} +/* End hide from IE-mac */ \ No newline at end of file diff --git a/web/css/Code.css b/web/css/Code.css new file mode 100644 index 0000000..b8f4d7d --- /dev/null +++ b/web/css/Code.css @@ -0,0 +1,34 @@ +.code { + border-top: 1px solid #ddd; + border-left: 1px solid #ddd; + border-right: 2px solid #000; + border-bottom: 2px solid #000; + padding: 10px; + margin-top: 5px; + margin-left: 5%; + margin-right: 5%; + background: #ffffea; + color: black; + font-family: courier; + white-space: pre; + -moz-border-radius: 10px; + border-radius: 10px; +} + +#code { + color: black; + font-size: 14px; + font-family: courier; + border-bottom: 1px dotted #ddd; + padding-left: 5px; +} + +#line-number { + color: #804000; + font-family: Arial; + font-size: 14px; + padding-right: 5px; + border-right: 1px dotted #804000; + text-align: right; + width: 15px; +} diff --git a/web/css/ColoredBoxesRoundedCorners.css b/web/css/ColoredBoxesRoundedCorners.css new file mode 100644 index 0000000..c732b50 --- /dev/null +++ b/web/css/ColoredBoxesRoundedCorners.css @@ -0,0 +1,86 @@ +/******************************************************************************* +Color Boxes with Round Corners. This is pretty much straight from Stu +Nicholls' Snazzy Borders (see +http://www.stunicholls.myby.co.uk/boxes/snazzy.html). Things look a +little different only because the code's been reformatted and some +color schemes have been predefined. + +- Paul Pomeroy (July, 2005) +*******************************************************************************/ +.rcbox h1, .rcbox h2, .rcbox p {margin:-10px; letter-spacing:0.5px; border:0;} +.rcbox {background: transparent; padding:3px 6px;} +.rcbox div {padding:4px 4px;} + + +.xboxcontent {display:block; background:#eee; border:0 solid #fff; border-width:0 1px;} +.xb2, .xb3, .xb4 {border-left:1px solid #fff; border-right:1px solid #fff;} +.xb1 {margin:0 5px; background:#fff;} +.xb2 {margin:0 3px; border-width:0 2px;} +.xb3 {margin:0 2px;} +.xb4 {height:2px; margin:0 1px;} +.xb1, .xb2, .xb3, .xb4 {display:block; overflow:hidden;} +.xb1, .xb2, .xb3 {height:1px;} +.xtop, .xbottom {display:block; background:transparent; font-size:1px;} + +/* ================Default Color Scheme================= */ +.xb2, .xb3, .xb4 { + background:#eac; border-left-color:#fff; border-right-color:#fff;} .xb1 {background:#fff;} + /* : : : : */ + /* :..background. :............border......:......border...........: */ + /* : : */ +.xboxcontent { background:#eac; border-color:#fff;} +.xboxcontent h2, .xboxcontent h3, .xboxcontent h4, .xboxcontent p {color:#111;} + +/* =================Color Scheme 1===================== */ +.cs1 .xb2, .cs1 .xb3, .cs1 .xb4 { + background:#906; border-left-color:#fff; border-right-color:#fff;} .cs1 .xb1 {background:#fff;} + /* : : : : */ + /* :..background...... :........border..........:......border................: */ + /* : : */ +.cs1 .xboxcontent { background:#906; border-color:#fff;} +.cs1 .xboxcontent h2, .cs1 .xboxcontent h3, .cs1 .xboxcontent h4, .cs1 .xboxcontent p {color:#000;} + +/* =================Color Scheme 2===================== */ +.cs2 .xb2, .cs2 .xb3, .cs2 .xb4 { + background:#eef; border-left-color:#ddd; border-right-color:#ddd;} .cs2 .xb1 {background:#ddd;} + /* : : : : */ + /* :..background...... :........border..........:......border................: */ + /* : : */ +.cs2 .xboxcontent { background:#eef; border-color:#ddd;} +.cs2 .xboxcontent h2, .cs2 .xboxcontent h3, .cs2 .xboxcontent h4, .cs2 .xboxcontent p {color:#222;} + +/* ==IMG============Color Scheme 3================IMG== */ +.cs3 .xb2, .cs3 .xb3, .cs3 .xb4 { + background:#fff url(/Images/Clouds.jpg); border-left-color:#eef; border-right-color:#eef;} .cs3 .xb1 {background:#eef;} + /* : : : : */ + /* :..background...... :........border..........:......border................: */ + /* : : */ +.cs3 .xboxcontent { background:#fff url(/Images/Clouds.jpg); border-color:#eef;} +.cs3 .xboxcontent h2, .cs3 .xboxcontent h3, .cs3 .xboxcontent h4, .cs3 .xboxcontent p {color:#113;} + +/* ==IMG============Color Scheme 4================IMG== */ +.cs4 .xb2, .cs4 .xb3, .cs4 .xb4 { + background:#fff url(/Images/tbg-bl-mg.jpg); border-left-color:#eef; border-right-color:#eef;} .cs4 .xb1 {background:#eef;} + /* : : : : */ + /* :..background...... :........border..........:......border................: */ + /* : : */ +.cs4 .xboxcontent { background:#fff url(/Images/tbg-bl-mg.jpg); border-color:#eef;} +.cs4 .xboxcontent h2, .cs4 .xboxcontent h3, .cs4 .xboxcontent h4, .cs4 .xboxcontent p {color:#113;} + +/* ==IMG============Color Scheme 5================IMG== */ +.cs5 .xb2, .cs5 .xb3, .cs5 .xb4 { + background:#fff url(/Images/tbg-mg-bl.jpg); border-left-color:#b9f; border-right-color:#b9f;} .cs5 .xb1 {background:#b9f;} + /* : : : : */ + /* :..background...... :........border..........:......border................: */ + /* : : */ +.cs5 .xboxcontent { background:#fff url(/Images/tbg-mg-bl.jpg); border-color:#b9f;} +.cs5 .xboxcontent h2, .cs5 .xboxcontent h3, .cs5 .xboxcontent h4, .cs5 .xboxcontent p {color:#111;} + +/* =================Color Scheme 6===================== */ +.cs6 .xb2, .cs6 .xb3, .cs6 .xb4 { + background:#fff; border-left-color:#aac; border-right-color:#aac;} .cs6 .xb1 {background:#aac;} + /* : : : : */ + /* :..background...... :........border..........:......border................: */ + /* : : */ +.cs6 .xboxcontent { background:#fff; border-color:#aac;} +.cs6 .xboxcontent h2, .cs6 .xboxcontent h3, .cs6 .xboxcontent h4, .cs6 .xboxcontent p {color:#222;} \ No newline at end of file diff --git a/web/css/FrontPage.css b/web/css/FrontPage.css new file mode 100644 index 0000000..68f5cdf --- /dev/null +++ b/web/css/FrontPage.css @@ -0,0 +1,34 @@ +/**************************************************************************** +Sets minimal style for unsupported browsers and then imports the css +files (used by compliant browsers). The following browsers will only +get the minimal css styling listed below this comment: + + NN 4.x, IE 3, IE 4. + +NOTE: imports must use the quoted file name syntax for the import, +'@import "x.css",' not the '@import url(x.css)' form which IE 4 +recognizes. +*****************************************************************************/ +@import url(LevelThePlayingField.css); +@import url(ColoredBoxesRoundedCorners.css); +@import url(Main.css); + +div.filtered { + color: red; + border: 2px solid #6600FF; + background-color: #E8E8E8; + padding: 5px; +} + +.filtered, .filtered a { + color: #333399; + font-style: italic; +} + +.fontsize-set a#medium { + font-size: 120%; +} + +.fontsize-set a#large { + font-size: 140%; +} \ No newline at end of file diff --git a/web/css/LevelThePlayingField.css b/web/css/LevelThePlayingField.css new file mode 100644 index 0000000..0287ccf --- /dev/null +++ b/web/css/LevelThePlayingField.css @@ -0,0 +1,191 @@ +/***************************************************************************** +lpf.css -- Level the Playing Field. Majorly modified form of +"undohtml.css" (C) 2004 by Tantek Celik. Some Rights Reserved. His +style sheet is licensed under a Creative Commons License. + +See http://creativecommons.org/licenses/by/2.0 + +Modifications made by Paul Pomeroy, July 2005. + +Whatever isn't Tantek's, consider it free for the taking but as I have +no idea what your requirments may be and no control over how you use +the following, all risks are assumed by you. Okay? + +Purpose: undo some of the default styling of common (X)HTML browsers +so all browers can start from the same settings (or as close as +possible) +*****************************************************************************/ +* { + /* IE5/Mac likes this, doesn't like ...:relative;. */ + /* hide from IE5/Mac */ + position: static; + /* rumored to help with some IE problems (other than IE5/Mac. */ + position: relative; + /* IE sometimes decides to center stuff for the heck of it */ + text-align: left; + font-size: 1em; +} + +body { + margin: 0; + padding: 0; +} + +/*ul, +ol,*/ + +li, +h1, +h2, +h3, +h4, +h5, +h6, +pre, +form, +body, +html, +p, +blockquote, +fieldset, +input { + margin: 0; +} + +dt { + padding-left: 12px; +} + +blockquote { + padding-left: 25px; +} + +/***************************************************************************** +No list-markers by default, since lists are used more often for semantics +*****************************************************************************/ +/* ul,ol { list-style:none; }*/ + +/***************************************************************************** +Link underlines tend to make hypertext less readable, because +underlines obscure the shapes of the lower halves of words +*****************************************************************************/ +:link,:visited { + text-decoration: none; +} + +/***************************************************************************** +Try getting rid of blue linked borders. +*****************************************************************************/ +#page a { + border: none; + font-weight: bold; +} + +#page a:link { + color: red; +} + +#page a:visited { + color: teal; +} + +#page a:active { + color: red; +} + +#page a:hover { + color: blue; + background: #ffff80; +} + +a img, +:link img, +:visited img { + border: none; +} + +/***************************************************************************** +Now set up the default fonts and font sizes... + +We'll start with a size of 12px (1em = 12px) +*****************************************************************************/ + +body { + font-family: verdana, arial, sans-serif; + font-size: 75%; /* assumes a 14-16px default size */ + line-height: 137%; +} + +/***************************************************************************** +Let IE use percentage for base font size so it can still zoom +text. Everyone else we'll give a pixel value to ... +*****************************************************************************/ +html>body { + font-size: 12px; /* For everyone except IE ... */ +} + +p { + font-size: 1.0em; /* ~12px */ +} + +h1, h2, h3, h4, h5, h6 { /* georgia is a better x-browser font */ + font-family: georgia, "new century schoolbook", times, serif; + margin-top: 4px; + margin-bottom: 10px; + color: #993333; +} + +h1 { + font-weight: normal; +} +h2, h3, h4, h5, h6 { + font-weight: bolder; +} + + +h1 { + font-size: 2em; + font-variant: small-caps; + text-align: center; +} + +h2 { + font-size: 1.5em; + font-variant: small-caps; + font-weight: bolder; +} + +h3 { + font-size: 1em; + font-weight: bolder; +} + +h4 { + font-size: 0.8em; + font-style: italic; +} + +h5 { + font-size: 0.6em; + font-style: italic; +} + +h6 { + font-size: 0.4em; + font-weight: bold; +} + +.standout { + font-family: verdana, + arial, + sans-serif; + font-size: 12px; + color: #933; + line-height: 13px; + font-weight: bold; + margin-bottom: 10px; +} + +.dim { + color: #999; +} diff --git a/web/css/Main.css b/web/css/Main.css new file mode 100644 index 0000000..6370184 --- /dev/null +++ b/web/css/Main.css @@ -0,0 +1,413 @@ +/*----------------------------------------------------------------------- +This CSS file is for implementing a 3-column layout based on the +"opposite floats" concept. I've seen this concept explained a few +times but never as clearly as in the article "3 Column Layouts - A +Different Approach" -- (C) 2005, Sebastian Schmieg. The article can be +found at: http://www.kingcosmonaut.de/journal/3col-layout/ and is +worth your time to go have a read first before diving into the +followng css. + +This css file is part of a layout package. Used on its own it won't +have the desired effect. The corresponding HTML file should LINK to a +filter.css file which will then IMPORT this style sheet (effecively +hiding it from IE3, IE4 and NN4). Imported ahead of this file should +be one named lpf.css (lpf = Level Playing Field) which attempts to get +all the different browsers using the same measurements, including font +sizes. + +Not related to the layout but also used in this example is 1 of Stu +Nicholls' wonderful creations (somewhat customized). This styles the +color boxes with rounded corners used in the content area. The style +for this, also imported by filter.css, is cbrc.css and it contains its +own documentation. + +====================================================================== + Copyright and LICENSE -- do not remove -- +====================================================================== +This CSS file is copyrighted (c) 2005, Paul Pomeroy/AdaptiveView + +see: http://design.adaptiveview.com + +but free to use under a Creative Commons Attribution 2.5 license. +Full details about this license are online at: + +http://creativecommons.org/licenses/by/2.5/ +----------------------------------------------------------------------- */ +html, body, #page { + height: 100%; + width: 100%; +} + +body { + background-color: #fff; + margin-left: auto; + margin-right: auto; + text-align: center; +} + +html>body, html>body #page { + height: auto; +} + +#head { + background: url(/Images/TopOfTheWorld.jpg); + color: white; + border-top: 1px solid #306; + border-bottom: 1px solid #306; + position: absolute; + height: 165px; + left: 0; +/* max-width: 955px;*/ + min-width: 775px; + top: 0px; + width: 100%; + width: expression(document.body.clientWidth < 800? "775px": "100%" ); /* min-width IE style*/ + z-index: 10; +} + +#head h1 { + color: #fff; + font-size: 3em; + padding-top: 20px; + text-align: center; +} + +#page { + left: 0; + background: #2a4c96 url(/Images/Background.jpg); + color: #000; + position: absolute; + text-align: center; + top: 166px; + z-index: 8; + min-height: 858px; +} + +#content { + margin-left: auto; + margin-right: auto; + max-width: 955px; + min-width: 775px; + padding-bottom: 4.0em; /* you can get all of the padding set in one line, but Mac IE5.2 has issues with the shorthand method. */ + padding-left: 4px; + padding-right: 4px; + padding-top: 10px; + width: expression(document.body.clientWidth < 800? "775px" : document.body.clientWidth > 1024? "999px": "99%"); /* IE's version of min- and max-width */ + z-index: 1; +} + +* html #page, * html #content { + height: 100%; +} + +* html #page { + width: auto; +} + +#foot { + background: transparent; + width: 100%; + z-index: 99; + text-align: center; +} + +#foot p { + color: #aaa; + font-size: 80%; + text-align: center; +} + +html>body #foot { /* anyone but IE */ + bottom: 0; + left: 0; + position: absolute; +} + +* html #foot { /* IE */ + color: #eee; + margin-left: auto; + margin-right: auto; + width: auto; +} + +abbr { + cursor: help; +} + +#head, #foot { + padding-bottom: 0; + padding-top: 0; +} + +#content p, +#content h2, +#content h3, +#content h4, +#content h5 { + margin: 11px 11px; +} + +#supporting, #related { + font-size: 90%; +} + +* html #supporting, * html #related { + overflow:hidden; /* keeps columns from getting pushed down when large font sizes cause words to exceed column width in IE6 */ +} + +/* + The quick explanation for this layout scheme: + + You have 3 columns for your content. I call them "main," "supporting" and + "related." There are two containers (divs) in which to put these 3 columns, + so one container will get 2 columns and the other will get 1. The containers + are named "contentWrapper1" and "contentWrapper2." Both of these containers + are in a container of their own, called "content." In XHTML, the heirarchy + looks like: + +
    + +
    +
    ... your main content ...
    +
    + +
    +
    + ... content supporting main ... +
    + +
    + +
    + + NOTE: In the XHTML it doesn't matter which wrapper div is first. Within + the wrapper that's containing two content columns (contentWrapper2) + it doesn't matter which content comes first. It's fairly easy, + therefore, to put your content in any order you want. + + The content div is alloted 100% width. The two content wrapper divs sit + side by side, one floated left, the other floated right. Their combined + width must be UNDER 100% (if they're >= 100% then the second wrapper is + going to slip down under the first). + + Within one of the wrappers, it doesn't matter which, you'll have two + content divs (again, it doesn't matter which) and one will be floated left + and the other right. Their combined widths must also be < 100%. + + Without touching the XHTML, you can get FOUR arrangements of the three + columns by swapping the left and right floats (==> and <== indicate swaps): + + 1. Wrapper 1 Wrapper 2 + Main Supporting Related + + 2. Wrapper 1 Wrapper 2 + Main ==> Related Supporting <== + + 3. ==> Wrapper 2 Wrapper 1 <== + Related Supporting Main + + 4. Wrapper 2 Wrapper 1 + ==> Supporting Related <== Main + + by changing the XHTML so the main content is paired up with one of the + others (doesn't matter which) you can get TWO additional layouts in + which the main content is flanked by the other two columns: + + 5. Wrapper 1 Wrapper 2 + Supporting Main Related + + 6. ==> Wrapper 2 Wrapper 1 <== + Related ==> Main Supporting <== + + ****************************** + ********* HOWEVER ********** + ****************************** + I've set this css up to change column arrangements via the class assigned to + the body tag. See the HTML file for more information, and below for the css + styles used to accomplish this... + + */ + +#contentWrapper1, +#contentWrapper2, +#main, +#related, +#supporting { + background:transparent; + min-height:100%; + z-index:15; +} + +#contentWrapper1 { + width:25%; +} + +#contentWrapper2 { + width:74.0%; +} + +#main { + width:66.0%; +} + +#main p.tagline { + color:#939; + font-size:1.4em; + font-style:italic; + text-align:center; +} + +#supporting { + width:33%; +} + +#related { + width:33%; +} + +body.m-sr #contentWrapper1, +body.m-rs #contentWrapper1, +body.sr-m #contentWrapper1, +body.rs-m #contentWrapper1 { + width:49.5%; +} + +body.m-sr #contentWrapper2, +body.m-rs #contentWrapper2, +body.sr-m #contentWrapper2, +body.rs-m #contentWrapper2 { + width:49.5%; +} + +body.m-sr #main, +body.m-rs #main, +body.sr-m #main, +body.rs-m #main { + width:99.5%; +} + +body.m-sr #supporting, body.m-sr #related, +body.m-rs #supporting, body.m-rs #related, +body.sr-m #supporting, body.sr-m #related, +body.rs-m #supporting, body.rs-m #related { + width:49.5%; +} + +body.r-sm #related, +body.r-ms #related, +body.sm-r #related, +body.ms-r #related { + width:99.0%; +} + +body.s-rm #supporting, +body.s-mr #supporting, +body.rm-s #supporting, +body.mr-s #supporting { + width:99.0%; +} + +body.m-sr #contentWrapper1, +body.m-rs #contentWrapper1, +body.s-mr #contentWrapper1, +body.s-rm #contentWrapper1, +body.r-ms #contentWrapper1, +body.r-sm #contentWrapper1 { + float:left; +} + +body.sr-m #contentWrapper1, +body.rs-m #contentWrapper1, +body.mr-s #contentWrapper1, +body.rm-s #contentWrapper1, +body.ms-r #contentWrapper1, +body.sm-r #contentWrapper1 { + float:right; +} + +body.m-sr #contentWrapper2, +body.m-rs #contentWrapper2, +body.s-mr #contentWrapper2, +body.s-rm #contentWrapper2, +body.r-ms #contentWrapper2, +body.r-sm #contentWrapper2 { + float:right; +} + +body.sr-m #contentWrapper2, +body.rs-m #contentWrapper2, +body.mr-s #contentWrapper2, +body.rm-s #contentWrapper2, +body.ms-r #contentWrapper2, +body.sm-r #contentWrapper2 { + float:left; +} + +body.s-mr #main, +body.r-ms #main, +body.mr-s #main, +body.ms-r #main { + float:left; +} + +body.s-rm #main, +body.r-sm #main, +body.rm-s #main, +body.sm-r #main { + float:right; +} + +body.m-sr #related, +body.s-mr #related, +body.sr-m #related, +body.mr-s #related { + float:right; +} + +body.m-rs #related, +body.s-rm #related, +body.rs-m #related, +body.rm-s #related { + float:left; +} + +body.m-sr #supporting, +body.r-sm #supporting, +body.sr-m #supporting, +body.sm-r #supporting { + float:left; +} + +body.m-rs #supporting, +body.r-ms #supporting, +body.rs-m #supporting, +body.ms-r #supporting { + float:right; +} + +.hide, .filtered {display:none;} + +.clear { + clear:both; + margin-bottom: -1px; /* for Gecko-based browsers */ + overflow:hidden; + padding-bottom: 1px; /* for Gecko-based browsers */ +} + +.clearfix:after { + clear: both; + content: "."; + display: block; + height: 0; + visibility: hidden; +} + +.clearfix {display: inline-table;} + +/* Hides from IE-mac \*/ +* html .clearfix {height: 1%;} +.clearfix {display: block;} +/* End hide from IE-mac */ + diff --git a/web/css/ManPage.css b/web/css/ManPage.css new file mode 100644 index 0000000..46bc03d --- /dev/null +++ b/web/css/ManPage.css @@ -0,0 +1,34 @@ +/***************************************************************************** +Sets minimal style for unsupported browsers and then imports the css +files (used by compliant browsers). The following browsers will only +get the minimal css styling listed below this comment: + + NN 4.x, IE 3, IE 4. + +NOTE: imports must use the quoted file name syntax for the import, +'@import "x.css",' not the '@import url(x.css)' form which IE 4 +recognizes. +*****************************************************************************/ +@import url(LevelThePlayingField.css); +@import url(ColoredBoxesRoundedCorners.css); +@import url(ManPageLayout.css); + +div.filtered { + color: red; + border: 2px solid #6600FF; + background-color: #E8E8E8; + padding: 5px; +} + +.filtered, .filtered a { + color: #333399; + font-style: italic; +} + +.fontsize-set a#medium { + font-size: 120%; +} + +.fontsize-set a#large { + font-size: 140%; +} diff --git a/web/css/ManPageLayout.css b/web/css/ManPageLayout.css new file mode 100644 index 0000000..7b6c57f --- /dev/null +++ b/web/css/ManPageLayout.css @@ -0,0 +1,174 @@ +/*----------------------------------------------------------------------- +This css file is part of a layout package. Used on its own it won't +have the desired effect. The corresponding HTML file should LINK to a +filter.css file which will then IMPORT this style sheet (effecively +hiding it from IE3, IE4 and NN4). Imported ahead of this file should +be one named lpf.css (lpf = Level Playing Field) which attempts to get +all the different browsers using the same measurements, including font +sizes. + +Not related to the layout but also used in this example is 1 of Stu +Nicholls' wonderful creations (somewhat customized). This styles the +color boxes with rounded corners used in the content area. The style +for this, also imported by filter.css, is cbrc.css and it contains its +own documentation. + +====================================================================== + Copyright and LICENSE -- do not remove -- +====================================================================== +This CSS file is copyrighted (c) 2005, Paul Pomeroy/AdaptiveView + +see: http://design.adaptiveview.com + +but free to use under a Creative Commons Attribution 2.5 license. +Full details about this license are online at: + +http://creativecommons.org/licenses/by/2.5/ +----------------------------------------------------------------------- */ +html, body, #page { + height:100%; + width:100%; +} + +body { + background-color: #fff; + margin-left:auto; + margin-right:auto; + text-align:center; +} + +html>body, html>body #page { + height:auto; +} + +#head { + background: url(/Images/TopOfTheWorld.jpg); + color:white; + border-top:1px solid #306; + border-bottom:1px solid #306; + position:absolute; + height:165px; + left:0; + min-width:775px; + top:0px; + width:100%; + width:expression(document.body.clientWidth < 800? "775px": "100%" ); /* min-width IE style*/ + z-index:10; +} + +#head h1 { + color: #933; + font-size: 1.75em; + padding-top: 10px; + text-align: left; +} + +#page { + left:0; + background: white; + color: #2a4c96; + position:absolute; + text-align:center; + top:166px; + z-index:8; +} + +#content { + margin-left:auto; + margin-right:auto; + max-width:955px; + min-width:775px; + padding-bottom:4.0em; /* you can get all of the padding set in one line, but Mac IE5.2 has issues with the shorthand method. */ + padding-left:4px; + padding-right:4px; + padding-top:10px; + width:expression(document.body.clientWidth < 800? "775px" : document.body.clientWidth > 1024? "999px": "99%"); /* IE's version of min- and max-width */ + z-index:1; +} + +* html #page, * html #content { + height:100%; +} + +* html #page { + width:auto; +} + +#foot { + width:100%; + z-index:99; +} + +#foot p { + color: #aaa; + font-size: 80%; + text-align: center; +} + +html>body #foot { /* anyone but IE */ */ + bottom: 0; + left: 0; + position: absolute; +} + +* html #foot { /* IE */ + color: #aaa; + margin-left: auto; + margin-right: auto; + width: auto; +} + +abbr { + cursor: help; +} + +#head, #foot { + padding-bottom:0; + padding-top:0; +} + +#content p, #content h2, #content h3, #content h4, #content h5 { + margin:11px 11px; +} + +#main { + background:transparent; + min-height:100%; + z-index:15; +} + +#main { + width:90%; +} + +#main p.tagline { + color:#939; + font-size:1.4em; + font-style:italic; + text-align:center; +} + + +.hide, .filtered {display:none;} + +.clear { + clear:both; + margin-bottom: -1px; /* for Gecko-based browsers */ + overflow:hidden; + padding-bottom: 1px; /* for Gecko-based browsers */ +} + +.clearfix:after { + clear: both; + content: "."; + display: block; + height: 0; + visibility: hidden; +} + +.clearfix {display: inline-table;} + +/* Hides from IE-mac \*/ +* html .clearfix {height: 1%;} +.clearfix {display: block;} +/* End hide from IE-mac */ diff --git a/web/css/Menus.css b/web/css/Menus.css new file mode 100644 index 0000000..6c379f4 --- /dev/null +++ b/web/css/Menus.css @@ -0,0 +1,228 @@ +.imcm ul, +.imcm li, +.imcm div, +.imcm span, +.imcm a { + text-align: left; + vertical-align: top; + padding: 0px; + margin: 0; + list-style: none outside none; + border-style: none; + background-image: none; + clear: none; + float: none; + display: block; + position: static; + overflow: visible; + line-height: normal; +} + +.imcm li a img { + display: inline; + border-width: 0px; +} + +.imcm span { + display: inline; +} + +.imcm .imclear, +.imclear { + clear: both; + height: 0px; + visibility: hidden; + line-height: 0px; + font-size: 1px; +} + +.imcm .imsc { + position: relative; +} + +.imcm .imsubc { + position: absolute; + visibility: hidden; +} + +.imcm li { + list-style: none; + font-size: 1px; + float: left; +} + +.imcm ul ul li { + width: 100%; + float: none !important; +} + +.imcm a { + display: block; + position: relative; +} + +.imcm ul .imsc, +.imcm ul .imsubc { + z-index: 10; +} + +.imcm ul ul .imsc, +.imcm ul ul .imsubc { + z-index: 20; +} + +.imcm ul ul ul .imsc, +.imcm ul ul ul .imsubc { + z-index: 30; +} + +.imde ul li:hover .imsubc { + visibility: visible; +} + +.imde ul ul li:hover .imsubc { + visibility: visible; +} + +.imde ul ul ul li:hover .imsubc { + visibility: visible; +} + +.imde li:hover ul .imsubc { + visibility: hidden; +} + +.imde li:hover ul ul .imsubc { + visibility: hidden; +} + +.imde li:hover ul ul ul .imsubc { + visibility: hidden; +} + +.imcm .imea { + display: block; + position: relative; + left: 0px; + font-size: 1px; + line-height: 1px; + height: 0px; + width: 1px; + float: right; +} + +.imcm .imea span { + display: block; + position: relative; + font-size: 1px; + line-height: 0px; +} + +.dvs,.dvm { + border-width: 0px +} + +#imenus0 .imeam span, +#imenus0 .imeamj span { + background-image: url(/Icons/orange_arrow_down.gif); + background-repeat: no-repeat; + background-position: top left; + width: 16px; + height: 9px; + left: -16px; + top: 3px; +} + +#imenus0 li:hover .imeam span, +#imenus0 li a.iactive .imeamj span { + background-image: url(/Icons/orange_arrow_down.gif); + background-repeat: no-repeat; + background-position: top left; +} + +#imenus0 ul .imeas span, +#imenus0 ul .imeasj span { + background-image: url(/Icons/orange_arrow_right.gif); + background-repeat: no-repeat; + background-position: top left; + width: 10px; + height: 13px; + left: -10px; + top: 0px; +} + +#imenus0 ul li:hover .imeas span, +#imenus0 ul li a.iactive .imeasj span { + background-image: url(/Icons/orange_arrow_right.gif); + background-repeat: no-repeat; + background-position: top left; +} + +#imouter0 { + background-image: url(/Images/orange_gradient.gif); + border-style: solid; + border-color: #111111; + border-width: 1px; + padding: 0px; +} + +#imenus0 li ul { + background-color: #fef5d8; + border-style: solid; + border-color: #111111; + border-width: 1px; + padding: 5px; +} + +#imenus0 li a, +#imenus0 .imctitle { + color: #333333; + text-align: center; + font-family: Arial; + font-size: 12px; + font-weight: bold; + text-decoration: none; + border-style: none; + border-color: #000000; + border-width: 0px; + padding: 2px 5px; +} + +#imenus0 li:hover>a { + color: #ff0000; +} + +#imenus0 li a.ihover, +.imde imenus0 a:hover { + color: #ff0000; +} + +#imenus0 li a.iactive { + text-decoration: underline; +} + +#imenus0 ul a, +#imenus0 .imsubc li .imctitle { + color: #111111; + text-align: left; + font-size: 11px; + font-weight: normal; + text-decoration: none; + border-style: none; + border-color: #000000; + border-width: 1px; + padding: 2px 5px; +} + +#imenus0 ul li:hover>a { + color: #ff0000; +} + +#imenus0 ul li a.ihover { + color: #ff0000; +} + +#imenus0 ul li a.iactive { + text-decoration: underline; +} + diff --git a/web/css/Plain.css b/web/css/Plain.css new file mode 100644 index 0000000..896e8af --- /dev/null +++ b/web/css/Plain.css @@ -0,0 +1,462 @@ +/************************************************************************/ +/* File: Plain.css */ +/* Description: Cascading Style Sheet definitions for site */ +/* Author: Andrew@DeFaria.com */ +/* Created: Mon Nov 3 21:55:05 PST 2003 */ +/* Language: Cascading Style Sheet */ +/* */ +/* (c) Copyright 2003, Andrew@DeFaria.com, all rights reserved. */ +/************************************************************************/ +body { + background: white; + margin: 3px; + padding: 2px; + font-family: trebuchet MS, + trebuchet, + verdana, + sans-serif, + arial; + color: black; +} + +.heading { + margin-top: 5px; + padding: 5px; +} + +h1, h2, h3, h4, h5 { + color: #005A9C; +} + +h1.centered { + text-align: center; +} + +h1 { + font-size: 1.5em; +} + +h2 { + font-size: 1.25em; +} + +h3 { + font-size: 1em; +} + +h4 { + font-size: .75em; +} + +h5 { + font-size: .5em; +} + +a { + color: #4A7184; + text-decoration: none; + font-weight: bold; + font-family: trebuchet MS, + trebuchet, + verdana, + arial, + sans-serif; + font-size: 14px; + font-weight: bold; +} + +a:link { + color: #0080c0; +} + +a:visited { + color: #4A7184; +} + +a:active { + color: #999966; +} + +a:hover { + color: blue; + background: #ffff80; +} + +/* For img's that happen to be links, don't put that silly */ +/* border! */ +img { + border: none; +} + +font { + font-family: trebuchet MS, + trebuchet, + verdana, + arial, + sans-serif; + font-weight: normal; + text-transform: none; +} + +font.title { + font-family: trebuchet MS, + trebuchet, + verdana, + arial, + sans-serif; + font-size: 11px; + color: #666; + line-height: 15px; +} + +font.title-list { + font-family: trebuchet MS, + trebuchet, + verdana, + arial, + sans-serif; + font-size: 11px; + color: #666; + line-height: 15px; +} + +font.bold-label { + font-family: trebuchet MS, + trebuchet, + verdana, + arial, + sans-serif; + font-size: 11px; + color: #666; + font-weight: bold; +} + +font.title-no-padding { + font-family: trebuchet MS, + trebuchet, + verdana, + arial, + sans-serif; + font-size: 11px; + color: #FFF; +} + +font.title-padding { + font-family: trebuchet MS, + trebuchet, + verdana, + arial, + sans-serif; + font-size: 11px; + color: #FFF; + line-height: 15px; + padding: 5px; +} + +font.title-padding-grey { + font-family: trebuchet MS, + trebuchet, + verdana, + arial, + sans-serif; + font-size: 11px; + color: #333; + line-height: 15px; + padding: 5px; +} + +font.pagetitle { + font-family: trebuchet MS, + trebuchet, + verdana, + arial, + sans-serif; + font-size: 12px; + color: #666; + line-height: 15px; + font-weight: bold; +} + +font.pagetitlelink { + font-family: trebuchet MS, + trebuchet, + verdana, + arial, + sans-serif; + font-size: 12px; + color: #336699; + line-height: 15px; + font-weight: bold; +} + +font.plain { + font-family: trebuchet MS, + trebuchet, + verdana, + arial, + sans-serif; + font-size: 11px; + color: #666; + line-height: 15px; +} + +font.instructional { + font-family: verdana, + arial, + sans-serif; + font-size: 10px; + color: #666; + line-height: 13px; + text-align: justify; +} + +.message { + font-family: verdana, + arial, + sans-serif; + font-size: 11px; + color: #669966; + line-height: 13px; + font-weight: bold; + margin-bottom: 10px; +} + +.message a { + font-family: verdana, + arial, + sans-serif; + font-size: 11px; +} + +.standout { + font-family: verdana, + arial, + sans-serif; + font-size: 12px; + color: #993333; + line-height: 13px; + font-weight: bold; + margin-bottom: 10px; +} + +.error-message { + font-family: verdana, + arial, + sans-serif; + font-size: 11px; + color: #993333; + line-height: 13px; + font-weight: bold; + margin-bottom: 10px; +} + +.error-message a { + font-family: verdana, + arial, + sans-serif; + font-size: 21px; +} + +font.instructional-just { + font-family: verdana, + arial, + sans-serif; + font-size: 10px; + color: #666; + line-height: 13px; +} + +font.head1 { + font-family: trebuchet MS, + trebuchet, + verdana, + arial, + sans-serif; + font-size: 11px; + color: #336699; + line-height: 15px; + font-weight: bold; +} + +font.command { + font-family: trebuchet MS, + trebuchet, + verdana, + arial, + sans-serif; + font-size: 12px; + line-height: 15px; + text-transform: uppercase; + font-weight: bold; +} + +font.command-grey { + font-family: trebuchet MS, + trebuchet, + verdana, + arial, + sans-serif; + font-size: 12px; + line-height: 15px; + color: #666; + font-weight: bold; +} + +p { + color: black; + font-family: Times New Roman, + Arial; + font-size: 12pt; + line-height: 12pt; +} + +ul { + color: black; + font-family: trebuchet MS, + trebuchet, + verdana, + helvetica, + sans-serif; + font-size: 14px; + line-height: 16px; +} + +li { + font-family: trebuchet MS, + trebuchet, + verdana, + helvetica, + sans-serif; + font-size: 14px; + line-height: 16px; +} + +.box { + padding: 5px; + background-color: #ffffea; + color: rgb(165, 42, 42); + font-size: 8pt; + margin-bottom: 5px; + margin-left: auto; + margin-right: auto; + border-top: 4px solid #804000; + border-left: 1px solid #804000; + border-bottom: 1px solid #aca899; + border-right: 1px solid #aca899; + width: 75%; + text-align: center; + -moz-border-radius: 7px; +} + +.padded-box { + border: 1px solid #696; + padding: 5px; + margin-top: 50px; + text-align: center; + background: #ccc; +} + +.copyright { + border-bottom: 1px dotted #ccc; + border-top: 1px dotted #ccc; + color: #999; + font-family: verdana, + arial, + sans-serif; + font-size: 10px; + margin-top: 5px; + text-align: center; + width: auto; +} + +.copyright a { + font-size: 10px; +} + +.input { + font-family: verdana, + arial, + sans-serif; + font-size: 11px; + font-weight: bold; + color: #666; +} + +/* Code */ +.code { + border: 2px solid #336699; + padding: 5px; + margin-top: 20px; + background: #ffffea; + color: black; + font-size: 14px; + font-family: courier; + -moz-border-radius: 10px; +} + +#code { + color: black; + font-size: 14px; + font-family: courier; + padding-left: 5px; +} + +#line-number { + color: #804000; + font-family: Arial; + font-size: 14px; + padding-right: 5px; + border-right: 1px dotted #804000; +} + +.highlightbox { + border: solid; + border-top-color: #336699; + border-top-width: 4pt; + border-left-color: #336699; + border-left-width: 1pt; + border-bottom-color: #000000; + border-bottom-width: 1pt; + border-right-color: #000000; + border-right-width: 1pt; + background-color: #eeeeee; + font-size: 8pt; + padding: 5px; + float: right; + text-align: center; + width: 200px; + -moz-border-radius: 7px; +} + +.centered { + text-align: center; + margin-left: auto; + margin-right: auto; +} + +.floatright { + float: right; +} + +.floatcenter { + text-align: center; +} + +pre { + white-space: pre-wrap; /* css-3 */ + white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + word-wrap: break-word; /* Internet Explorer 5.5+ */ +} + +address { + font-family: Georgia, + Times New Roman, + Arial, + sans-serif; + font-size: 12pt; +} +address.my { + text-align: right; +} \ No newline at end of file diff --git a/web/css/Print.css b/web/css/Print.css new file mode 100644 index 0000000..3f3facf --- /dev/null +++ b/web/css/Print.css @@ -0,0 +1,176 @@ +/************************************************************************/ +/* File: Print.css */ +/* Description: A printer version of the standard Cascading Style Sheet */ +/* definitions for site. This basically removes the menu */ +/* on the left hand side so that printed output is more */ +/* readable. */ +/* Author: Andrew@DeFaria.com */ +/* Created: Mon Jan 17 10:45:57 PST 2005 */ +/* Language: Cascading Style Sheet */ +/* */ +/* (c) Copyright 2005, Andrew@DeFaria.com, all rights reserved. */ +/* */ +/************************************************************************/ +@import url(Code.css); + +body { + background: white; + margin: 3px; + padding: 2px; + font-family: trebuchet MS, + trebuchet, + verdana, + sans-serif, + arial; + font-size: 14px; + color: black; +} + +.heading { + margin-top: 5px; + padding: 5px; +} + +/* Turn off menus */ +#imenus0 li a, #imenus0 .imctitle { + display: none; +} + +#imouter0 { + display: none; +} + +/* Turn off heading */ +.head { + display: none; +} + +.filtered { + display: none; +} + +h1, h2, h3, h4, h5 { + color: #00498b; + font-family: verdana, + sans-serif, + arial; +} + +h1 { + text-align: center; +} + +h1 { + font-size: 2em; +} + +h2 { + font-size: 1.75em; +} + +h3 { + font-size: 1.5em; +} + +h4 { + font-size: 1.25em; +} + +h5 { + font-size: 1em; +} + +#content { + font-size: 16px; +} + +#content p { + color: black; + font-family: arial, + trebuchet MS, + trebuchet, + verdana, + geneva, + helvetica, + sans-serif; + font-size: 16px; +} + +/* Turn on underlining of links for clarity on paper */ +a { + color: #4A7184; + text-decoration: underline; + font-weight: bold; + font-family: trebuchet MS, + trebuchet, + verdana, + arial, + sans-serif; + font-size: 16px; + font-weight: bold; +} + +a:link { + color: #0080c0; +} + +/* For img's that happen to be links, don't put that silly */ +/* border! */ +img { + border: none; +} + +font { + font-family: trebuchet MS, + trebuchet, + verdana, + arial, + sans-serif; + font-weight: normal; + text-transform: none; +} + +p { + color: black; + font-family: arial, + trebuchet MS, + trebuchet, + verdana, + geneva, + helvetica, + sans-serif; + font-size: 16px; +} + +ul { + color: black; + font-family: arial, + trebuchet MS, + trebuchet, + verdana, + geneva, + helvetica, + sans-serif; + font-size: 16px; +} + +li { + font-family: arial, + trebuchet MS, + trebuchet, + verdana, + geneva, + helvetica, + sans-serif; + font-size: 16px; + line-height: 18px; +} + +#foot p { + color: #aaa; + font-size: 80%; + text-align: center; + border-bottom: 1px dotted #ccc; + border-top: 1px dotted #ccc; +} + diff --git a/web/css/TableBorders.css b/web/css/TableBorders.css new file mode 100644 index 0000000..d68ca9d --- /dev/null +++ b/web/css/TableBorders.css @@ -0,0 +1,253 @@ +/************************************************************************/ +/* */ +/* File: TableBorders.css */ +/* Description: Define tables with borders */ +/* Author: Andrew@DeFaria.com */ +/* Created: Mon Nov 3 21:55:05 PST 2003 */ +/* Language: Cascading Style Sheet */ +/* */ +/* (c) Copyright 2004, Andrew@DeFaria.com, all rights reserved. */ +/* */ +/************************************************************************/ + +/* Table headers */ +th.th { + border-style: solid; + border-color: black; + border-top-width: 2pt; + border-bottom-width: 0pt; + border-left-width: 1pt; + border-right-width: 0pt; + padding: 3px; + background: teal; + color: white; + font-size: 12px; +} + +th.tht { + border-style: solid; + border-color: black; + border-top-width: 2pt; + border-bottom-width: 0pt; + border-left-width: 1pt; + border-right-width: 0pt; + padding: 3px; + background: teal; + color: white; + font-size: 16px; + -moz-border-radius-topleft: 10px; + -moz-border-radius-topright: 10px; +} + +/* Table Header Top Left */ +th.thtl { + border-style: solid; + border-color: black; + border-top-width: 2pt; + border-bottom-width: 0pt; + border-left-width: 2pt; + border-right-width: 0pt; + padding: 3px; + background: teal; + color: white; + font-size: 12px; + -moz-border-radius-topleft: 10px; +} + +/* Table Header Top Right */ +th.thtr { + border-style: solid; + border-color: black; + border-top-width: 2pt; + border-bottom-width: 0pt; + border-left-width: 1pt; + border-right-width: 2pt; + padding: 3px; + background: teal; + color: white; + font-size: 12px; + -moz-border-radius-topright: 10px; +} + +/* Table Header Left */ +th.thl { + border-style: solid; + border-color: black; + border-top-width: 2pt; + border-bottom-width: 1pt; + border-left-width: 2pt; + border-right-width: 1pt; + padding: 3px; + background: teal; + color: white; + font-size: 12px; +} + +/* Table Header Bottom Left */ +th.thbl { + border-style: solid; + border-color: black; + border-top-width: 1pt; + border-bottom-width: 2pt; + border-left-width: 2pt; + border-right-width: 1pt; + padding: 3px; + background: teal; + color: white; + font-size: 12px; +} + +/* Table Data Top Left */ +td.tdtl { + border-style: solid; + border-color: black; + border-right-width: 0pt; + border-left-width: 2pt; + border-top-width: 2pt; + border-bottom-width: 0pt; + padding: 5px; + background: white; + color: black; + font-size: 12px; +} + +/* Table Data Bottom Left */ +td.tdbl { + border-style: solid; + border-color: black; + border-right-width: 0pt; + border-left-width: 2pt; + border-top-width: 1pt; + border-bottom-width: 2pt; + padding: 5px; + background: white; + color: black; + font-size: 12px; +} + +/* Table Data Bottom Right */ +td.tdbr { + border-style: solid; + border-color: black; + border-right-width: 2pt; + border-left-width: 1pt; + border-top-width: 1pt; + border-bottom-width: 2pt; + padding: 5px; + background: white; + color: black; + font-size: 12px; +} + +/* Table Data Bottom */ +td.tdb { + border-style: solid; + border-color: black; + border-right-width: 0pt; + border-left-width: 1pt; + border-top-width: 1pt; + border-bottom-width: 2pt; + padding: 5px; + background: white; + color: black; + font-size: 12px; +} + +/* Table Data */ +td.td { + border-style: solid; + border-color: black; + border-right-width: 0pt; + border-left-width: 1pt; + border-top-width: 1pt; + border-bottom-width: 0pt; + padding: 5px; + background: white; + color: black; + font-size: 12px; +} + +/* Table Data Left */ +td.tdl { + border-style: solid; + border-color: black; + border-right-width: 0pt; + border-left-width: 2pt; + border-top-width: 1pt; + border-bottom-width: 0pt; + padding: 5px; + background: white; + color: black; + font-size: 12px; +} + +/* Table Data Right */ +td.tdr { + border-style: solid; + border-color: black; + border-right-width: 2pt; + border-left-width: 1pt; + border-top-width: 1pt; + border-bottom-width: 0pt; + padding: 5px; + background: white; + color: black; + font-size: 12px; +} + +/* Table Data Top Right */ +td.tdtr { + border-style: solid; + border-color: black; + border-right-width: 2pt; + border-left-width: 1pt; + border-top-width: 2pt; + border-bottom-width: 0pt; + padding: 5px; + background: white; + color: black; + font-size: 12px; +} + +/* Table Data Top Right (right?) */ +td.tdtrr { + border-style: solid; + border-color: black; + border-right-width: 2pt; + border-left-width: 1pt; + border-top-width: 2pt; + border-bottom-width: 0pt; + padding: 5px; + background: white; + color: black; + font-size: 12px; + -moz-border-radius-topright: 10px; +} + +/* Table Data Right */ +td.tdr { + border-style: solid; + border-color: black; + border-right-width: 2pt; + border-left-width: 1pt; + border-top-width: 1pt; + border-bottom-width: 0pt; + padding: 5px; + background: white; + color: black; + font-size: 12px; +} + +/* Table Data Top */ +td.tdt { + border-style: solid; + border-color: black; + border-right-width: 0pt; + border-left-width: 1pt; + border-top-width: 2pt; + border-bottom-width: 0pt; + padding: 5px; + background: white; + color: black; + font-size: 12px; +} diff --git a/web/error404.php b/web/error404.php new file mode 100644 index 0000000..eac405e --- /dev/null +++ b/web/error404.php @@ -0,0 +1,44 @@ + + + + + + ClearSCM: Oops! Page not found + + + + + + + + + + + + +
    +
    + +

    ERROR: Page not found

    + + +

    The page you were looking for was not found.

    + + +
    + + + + + diff --git a/web/favicon.ico b/web/favicon.ico new file mode 100755 index 0000000000000000000000000000000000000000..1c51991570c31d51fdcc79794ce63a6abd6850ed GIT binary patch literal 2551 zcmeHJTWeHT6#mYc%b8m;lVm27CRL-csjalaOKK2*4Fm1vs1&;QW*~(RG_tnY1-J> zXkv4-ii;Pw@bqaDk%)s>%)zZ&yNpppv)Sgk4I~l{9zCk!=FJiwKCCiUi`Xibm-mp* zuVZd*6Un6DN|>MDL^}Nkrl$VFjTH}IikcoH## zP0zrPNJ_n7oapTm}Ojy{@HBklRT$ij<`yfgDp zv@=Kj6zyk8EzSbXzOFeRTw<@*?6<@|ZqbiL7hh-Ii{s?FvF#Rnzh=+ZoFkfZ$0hb* zS0^_5%AI6qK0!U)dp9{pG<&$_Oc474;tbI2`&x4IVg%<)7H+L-oMW2v>Hw}cj@aa0 z)RjGqVC_{>UHmw&G-po-&zYw=H#)N1M`|LzK4yjcnlHO}&b`2{Ig2!BQ%8OYFZ$mk z1FsIYn?L7q$3KW0>|&EUQkL4qJ*{-2=by9k!ERL*YuG3A~5Z{7KYJacg0Xj}4-+67B^|GN+y_&Gm&=lA}Vx8>HB z`E}ljoGEX{e>d{>4qMJoo}avpCwcOIEgiNhXTi7q%kM|xIwRfa_C7KE{ROMn=IJVn xN);`2niMV=N&(6$7#X8r45Z16JX|m)*mq0 + + + + + + + ClearSCM + + + + + + + + + + + + + + +
    +
    +
    +
    + +

    We provide top notch talent for your SCM projects. With over 10 + years of direct experience with the top SCM tools and + methodologies we have what it takes to help you succeed.

    + + +

    Our services

    + + +

    You have your own business to run, your own set of + problems, deadlines, challenges and schedules. You want + talent to come in and hit the ground running, tackling your + toughest challenges. We offer consulting services in the + areas of SCM, Build & Release management, Systems + Administration and customized scripting solutions.

    + +

    ClearSCM, Inc. provides consultants who have technical + expertise coupled with an in-depth knowledge of the business + process to Fortune 500 companies and other Organizations + nationwide and around the world. Our senior consultants and + specialists in the computer industry specialize in + implementing SCM and ECM for your business needs. We can + customize your SCM and maintain your application. We provide + a comprehensive technology to help clients successfully + build, deploy and manage mission critical enterprise + applications in an open systems environment. +

    + +
    + +

    SCM + & ECM

    +

    Utilizing top tier professional tools such as Clearcase, + Multisite, + Clearquest + as well as various Open Source tools such as CVS we work with your + company to build professional, customized solutions to + your SCM needs.

    + + + +

    Beyond SCM

    +

    Rarely do companies live on SCM alone. There are many + internal and inhouse systems that grow up around Build and + Release Management systems including things like internal + web sites tracking nightly build processes. Integrations + between bug tracking systems and source control + systems. Many times such intergrations have yet to be + made... (more) + +

    +
    + +
    + +
    +
    + + +
    + + + + + diff --git a/web/people.php b/web/people.php new file mode 100644 index 0000000..bbdc5aa --- /dev/null +++ b/web/people.php @@ -0,0 +1,164 @@ + + + + + + ClearSCM: Our People + + + + + + + + + + + + +
    +
    + +

    Our People

    + + +

    Andrew DeFaria

    + +

    Specialities: Clearcase, Clearquest, Build & Release, + Test Automation, Web Applications

    + + +

    Andrew has approximately 30 years of experience in the computer + field and has been working with Clearcase since ~1993. His initial + exposure to Clearcase was at Hewlett Packard's Language Lab where he + become the Clearcase adminstrator as well as general Unix + administrator. Andrew has continued to work with Clearcase, + Clearquest as well as Unix and Windows administration, Build & + Release processes, Test Automation and has added on various Web + Applications at numbers Fortune 500 and Fortune 100 companies such + as HP, Sun, Cisco, Ameriquest, Broadcom and Texas + Instruments.

    + +

    Andrew is the president of ClearSCM, Inc. and it the principal + writer, designer and implementor of this web site. He also runs his + own domain at defaria.com where he + keeps his administration skills sharp and develops web + applications. For example, Andrew has developed a personal spam + filtering system known as Mail Authorization and Permission System + or MAPS that filters the some 2000-5000 emails he receives a day! + Andrew also keeps active on various mail lists and forums regarding + topics from SCM to ones of a personal nature.

    + +

    For more detailed information see Andrew's Resume.

    + +

    + Tom Connor +

    Tom Connor

    + +

    Specialities Clearcase, UCM, Clearquest, Build/Release, Perl

    + +

    Tom is a Sr. Software Engineer with over 20 years of varied + experience as a software developer. Specializations in + Software Configuration Management, Release Engineering, Build + Engineering, workflow automation, Installer Development, and + Deployment Engineering. Creative problem solver with strong + analytical and communication skills.

    + +

    For more information see Tom's + Resume.

    + +
    + +

    Kevin Haralson

    + +

    Specialities System Adminstration, Managing vendor + relationships, IT Tech/Helpdesk expertise

    + +

    19 years experience in the IT field. 5 years as a manager, + overseeing projects, and/or budgets. 4 years in a technical sales + capacity, and 10 years as a Technician (Tech Support/Helpdesk to + Desktop/Hardware/Config Tech to System Admin to Network Admin).

    + +

    In the last 3 years I have been involved in building the + infrastructure for medical, legal and financial offices, including + installing most/ all of their industry specific software. I've + worked in three large organizations (over 2000 employees) as an IT + Tech, and a number of smaller organizations (20 - 200 employees) + from help desk to director of IT.

    + +

    For more information see Kevin's + Resume.

    + +
    + +

    Don Skanes

    + +

    Specialities Clearcase, Clearquest, Windows, Project Management

    + +

    Don, originally from Canada, is a highly skilled senior software + engineer with over 27 years of experience. Special strengths in the + areas of design and implementation of Software Configuration + Management tools, operating systems, programming languages, + compilers, databases, system validation tools, build and deployment + automation.

    + +

    Don has been working with Clearcase since the early 90's. He has + a firm grasp of the Rational Toolset including the Team Unifying + Platorm, UCM, Clearquest, Test Director. Don is equally comfortable + in a management role as he is in a role of designer and + developer. Whether writing project plans, training material or + coding Clearcase triggers or build automation tools,

    + +

    For more information see Don's Resume.

    + +
    + +

    Ron Van Sherpe

    + +

    Specialities: Windows Administration, Unix/Linux + Administration and advocate

    + +

    Ron is one of those people who loves technology and loves to put + different technological ideas together building solutions for + clients. He is a strong advocate of Linux and has put together many + packages for small companies to use Open Source packages with Linux + backends to solve their corporate business needs as cheaply as + possible.

    + +

    For more information see Ron's Resume.

    + +
    + +

    Mohammed Ansari

    + +

    Specialities: With more than 15 years experience Mohammed + specializes as a Configuration Management Program and Project Lead.

    + +

    Mohammed's experience is one who's breath is about as broad as his + depth. Mohammed specializes in Configuration management from a project + and program perspective but has years of depth in the trenches as well.

    + +

    For more information see Mohammed's Resume.

    + +
    + + +
    + + + + + diff --git a/web/php/clearscm.php b/web/php/clearscm.php new file mode 100644 index 0000000..5b4bf11 --- /dev/null +++ b/web/php/clearscm.php @@ -0,0 +1,395 @@ +\n"; + + foreach ($lines as $line) { + print $line; + } // foreach + + print ""; +} // menu_css + +function menu () { + print << +
    + + +
     
    +
    +
    +END; +} // menu + +function heading () { + print "
    + +
    +

    You are viewing an unstyled version of this + page. Either your browser does not support Cascading + Style Sheets (CSS) or CSS styling has been disabled.

    +
    +END; +} // heading + +function start_box ($type) { + print <<
    +END; +} // start_box + +function end_box () { + print <<
    +END; +} // end_box + +function search_box () { + print << + +
    +
    Search my site + + + +
    +
    + +
    +
    +END; +} // search_box + +function copyright ($start_year = "", + $author = "Andrew DeFaria", + $email = "info@clearscm.com", + $home = "") { + global $base; + global $base1; + + $today = getdate (); + $current_year = $today ["year"]; + + $this_file = $base1 . "/" . $_SERVER['PHP_SELF']; + + $mod_time = date ("F d Y @ g:i a", filemtime ($this_file)); + + print <<

    + Last modified: $mod_time
    + Copyright ©  +END; + + if ($start_year != "") { + print "$start_year-"; + } // if + +print <<

    +END; +} // copyright + +function get_file_from_cvs ($file, + $machine = "clearscm.com", + $port = ":8080", + $path = "/viewvc/clearscm.com/") { + $user = "andrew"; + $password = "airafed"; + $url = "http://$user:$password@$machine$port$path$file?view=co"; + + $contents = @file ($url) + or die ("$url not found"); + + return $contents; +} # get_file_from_cvs + +function display_contents_as_code ($contents) { + print "
    "; + print ""; + + $line_number = 1; + + foreach ($contents as $line) { + print ""; + print " "; + print " "; + print "\n"; + } // foreach + + print "
    " . + $line_number++ . ""; + + for ($i = 0; $i < strlen ($line); $i++) { + if ($line [$i] == " ") { + if ($i == 0 && $line_number == 2) { + continue; + } // if + echo " "; + } else if ($line [$i] == "\t") { + echo "        "; + } else if ($line [$i] != "\n") { + echo $line [$i]; + } // if + } // for + + print "
    "; + print "
    "; +} // display_contents_as_code + +function display_contents_as_snippet ($contents) { + print "
    "; + + foreach ($code as $line) { + for ($i = 0; $i < strlen ($line); $i++) { + if ($line [$i] == " ") { + echo " "; + } else if ($line [$i] == "\t") { + echo "        "; + } else if ($line [$i] != "\n") { + echo $line [$i]; + } // if + } // for + } // foreach + + print "
    "; +} // display_contents_as_snippet + +function display_code ($file, + $machine = "clearscm.com", + $port = ":8080", + $path = "/viewvc/clearscm.com/") { + display_contents_as_code (get_file_from_cvs ($file, $machine, $port, $path)); +} # display_code + +function cvs_man ($file, + $machine = "clearscm.com", + $port = ":8080", + $path = "/viewvc/clearscm.com/") { + + $desc_spec = array ( + 0 => array ("pipe", "r"), // stdout + 1 => array ("pipe", "w"), // stdin + 2 => array ("pipe", "w"), // stderr + ); + + $pod2html = proc_open ("pod2html -cachedir /tmp -noindex -htmlroot=http://perldoc.perl.org", $desc_spec, $pipes); + + if (!is_resource ($pod2html)) { + die ("Unable to start pod2html"); + } // if + + $stdin = $pipes [0]; + $stdout = $pipes [1]; + $stderr = $pipes [2]; + + $contents = get_file_from_cvs ($file, $machine, $port, $path); + + // Write to stdin + foreach ($contents as $line) { + fwrite ($stdin, $line); + } // foreach + fclose ($stdin); + + $end_of_index = 0; + $pre_just_ended = 0; + $machine = "clearscm.com"; + $port = ""; + $path = "/viewvc/clearscm.com/"; + $url = "http://$machine$port$path$file?view=co"; + $history = "http://$machine$port$path$file"; + + // Now get the output and write it out + while (!feof ($stdout)) { + $line = fgets ($stdout); + + if (preg_match ("//", $line)) { + $end_of_index = 1; + continue; + } + + if (!$end_of_index) { + continue; + } // if + + // Filter some CVS keywords properly + $line = preg_replace ("/\\\$Revision\:\s*(\S*)\s*\\\$/", + "Revision $1", + $line); + $line = preg_replace ("/\\\$Date\:\s*(.*)\s*\\\$/", + "Modifed $1", + $line); + $line = preg_replace ("/\\\$RCSfile\:\s*(\S*),v\s*\\\$/", + "$1", + $line); + + // Collapse adjacent
     sections to instead have a simple blank
    +    // line
    +    if (preg_match ("/<\/pre>$/", $line)) {
    +      $line = preg_replace ("/<\/pre>/", "", $line);
    +      print "$line\n";
    +      $pre_just_ended = 1;
    +      continue;
    +    } // if
    +
    +    if (preg_match ("/^
    $/", $line)) {
    +      if ($pre_just_ended) {
    +        $pre_just_ended = 0;
    +	continue;
    +      } // if
    +    } else {
    +      if ($pre_just_ended) {
    +        $pre_just_ended = 0;
    +	echo "
    $line"; + continue; + } // if + } // if + + $line = preg_replace ("/
    /",
    +			  "
    ",
    +			  $line);
    +    $line = preg_replace ("/<\/pre>/",
    +			  "
    ", + $line); + $line = preg_replace ("/(.*)<\/a>/", + "$1", + $line); + $line = preg_replace ("/NAME (\S*)<\/h1>/", + "NAME $1 ", + $line); + $line = preg_replace ("/NAME (\S*)<\/h2>/", + "NAME $1 ", + $line); + echo $line; + } // while + + fclose ($stdout); + + //while (!feof ($stderr)) { + // echo fgets ($stderr) . "
    "; + //} // while + + fclose ($stderr); + + proc_close ($pod2html); +} // cvs_man +?> diff --git a/web/php/cvs_man.php b/web/php/cvs_man.php new file mode 100644 index 0000000..e4f6196 --- /dev/null +++ b/web/php/cvs_man.php @@ -0,0 +1,41 @@ + + + + + + ClearSCM: <?php echo $cvs_file?> + + + + + + + + + +
    + + +
    +
    + +
    +
    + + + + + diff --git a/web/phpinfo.php b/web/phpinfo.php new file mode 100644 index 0000000..157dd06 --- /dev/null +++ b/web/phpinfo.php @@ -0,0 +1 @@ + diff --git a/web/scripts/ecrd/ecr23184.html b/web/scripts/ecrd/ecr23184.html new file mode 100644 index 0000000..731e314 --- /dev/null +++ b/web/scripts/ecrd/ecr23184.html @@ -0,0 +1,70 @@ + + + + + + + ECR 23184: Native PPC Toolchain build failing (atoi missing) + + + + + +
    +

    ECR 23184: Native PPC Toolchain build failing (atoi missing)

    +
    + +
    +
    State: Pending Review Status: Duplicate Severity: Medium Fixed:

    +
    +###### adefaria: 24 Jan 2005 10:50:45 (-00:05)
    +
    +I am unable to build the toolchain natively on t-mcpn765-1 (PPC). Note 
    +I am trying to use XFree86 however the problem seems to be with atoi:
    +
    +gcc  tkAppInit.o -L/mnt/toolchain/3.2.2/010405/build-powerpc/tk/unix 
    +-ltk8.0 -L/mnt/toolchain/3.2.2/010405/build-powerpc/tcl/unix -ltcl8
    +.0  -L/usr/X11R6/lib -lX11   -lc \
    +       -o wish
    +/usr/X11R6/lib/libX11.a(ConnDis.o): In function 
    +`_X11TransConnectDisplay':
    +ConnDis.o(.text+0x2ac): undefined reference to `atoi'
    +ConnDis.o(.text+0x344): undefined reference to `atoi'
    +/usr/X11R6/lib/libX11.a(lcGeneric.o): In function 
    +`read_charset_define':
    +lcGeneric.o(.text+0x8cc): undefined reference to `atoi'
    +lcGeneric.o(.text+0x928): undefined reference to `atoi'
    +/usr/X11R6/lib/libX11.a(lcGeneric.o): In function 
    +`read_segmentconversion':
    +lcGeneric.o(.text+0xd7c): undefined reference to `atoi'
    +/usr/X11R6/lib/libX11.a(lcGeneric.o)(.text+0x1324): more undefined 
    +references to `atoi' follow
    +collect2: ld returned 1 exit status
    +make[3]: *** [wish] Error 1
    +make[2]: *** [all] Error 2
    +make[1]: *** [all-tk] Error 2
    +make: *** [stamp-all-powerpc] Error 2
    +
    +I spoke with Adam about this and he said the problem is in libc.a, 
    +which lacks an atoi:
    +
    +# nm /lib/libc.a | grep atoi
    +catoi.as.o:
    +00000038 T catoi
    +00000124 t regatoi
    +
    +Adam also said that Steve might know if there was a recent change.
    +-- 
    +Andrew DeFaria <adefaria@lnxw.com>
    +Build & Release
    +QA
    +LynuxWorks
    +
    +
    +
    +###### oleg (quintus): 14 Feb 2005 05:45:20
    +
    +This ECR is a duplicate of ECR 22979.
    +
    + diff --git a/web/scripts/ecrd/ecrc.php b/web/scripts/ecrd/ecrc.php new file mode 100644 index 0000000..f946faf --- /dev/null +++ b/web/scripts/ecrd/ecrc.php @@ -0,0 +1,48 @@ + + + + + + ClearSCM: Scripts: ECRC: + + + + + + + + + + + + + + +
    +
    + +

    ECRD Daemon

    + +

    This is a client script that calls ecrd.

    + + + +
    + +
    + + + + + diff --git a/web/scripts/ecrd/ecrc.php.php b/web/scripts/ecrd/ecrc.php.php new file mode 100644 index 0000000..a069a60 --- /dev/null +++ b/web/scripts/ecrd/ecrc.php.php @@ -0,0 +1,48 @@ + + + + + + ClearSCM: Scripts: ECRD: + + + + + + + + + + + + + + +
    +
    + +

    ECRC API

    + +

    This is the PHP API for ecrc.

    + + + +
    + +
    + + + + + diff --git a/web/scripts/ecrd/ecrd.php b/web/scripts/ecrd/ecrd.php new file mode 100644 index 0000000..69608df --- /dev/null +++ b/web/scripts/ecrd/ecrd.php @@ -0,0 +1,48 @@ + + + + + + ClearSCM: Scripts: ECRD: + + + + + + + + + + + + + + +
    +
    + +

    ECRD Daemon

    + +

    This is a daemon script that opens a database and waits for requests for service by reading a socket. When requests come in it responds with the data for an ECR record from the database.

    + + + +
    + +
    + + + + + diff --git a/web/scripts/ecrd/index.php b/web/scripts/ecrd/index.php new file mode 100644 index 0000000..04938d4 --- /dev/null +++ b/web/scripts/ecrd/index.php @@ -0,0 +1,134 @@ + + + + + + + ClearSCM: ECRDig + + + + + + + + + + + + + + + +
    +
    + +

    ECRDig

    + +

    by Andrew DeFaria

    + +

    ECRs, or Electronic Change Records, was a bug tracking + systems in used at LynuxWorks, + Inc.. What started as a simple quest to display an ECR as a + web pages turned into a full blown, full text search of the + defect/issue tracking system. Here's how it developed...

    + + +

    Introduction

    + +

    While at LynuxWorks I decided to leverage some code that I had + previously developed (See Clearquest + Daemon) that utilizes a client/server model to provide a + service that interogates a database and returns information. Again + this database happens to be a defect tracking database residing on + another machine.

    + +

    Daemon

    + +

    The daemon opens the database then listens on a socket for + requests, in this case a defect ID, then obtains the detail + information about the defect and returns it to the caller in the + form of a Perl hash. This avoids the overhead associated with + opening and closing the database or otherwise connecting to the + datastore. The daemon runs continually in the background listening + for and servicing requests (ecrd source + code).

    + +

    Client

    + +

    The caller, or client, then can process the information in + anyway they see fit. Often the caller is a Perl or PHP script that + outputs the information in to a nicely formatted web page but it + can as easily be a command line tool that spits out the answer to + a question. For example:

    + +

    +      $ ecrc 142 owner
    +      adefaria
    +    
    + +

    uses a command line client to display the owner of the defect + 142. (ecrc source code).

    + +

    PHP Module

    + +

    As PHP is a nice language for writing dynamic web pages I then + developed a PHP API library in order to be a client to ecrd which + was written in Perl. This allowed me to call the daemon to get + information about a defect then format out whatever web page I + wanted (ecrc.php API source code).

    + +

    For example, here is an example of + a web page describing a specific defect. Notics that the ECR + (LynuxWorks defect tracking system) displays the one line + description as well as other fields such as State, Status, + Severity and Fixed info. Additionally the long description is + displayed as well as parsed for references to other ECRs or + auxilary files, courtesy of PHP.

    + + + + + +
    + +

    The link to ECR 22979 will not work unless you are + within the LynuxWorks Intranet

    + +
    + +

    Tying it into HtDig

    + +

    Since ECRs and their full text descriptions are now available + via a web link it was relatively trival to hook this up to HtDig to enable full text + searching on all ECRs and their descriptions. All that was needed + was to produce a web page with all ECRs listed linked to web pages + of their descriptions. HtDig would then crawl through and index + everything. Additionally, since the ECR descriptions were scanned + for references to certain auxilary files (files not + necessarily in the defect database but on a network accessible + area and used to further support the ECR in question) HtDig would + crawl through and index them too. This resulted in a very flexible + and powerful internal search facility.

    +
    + + +
    + + + + + diff --git a/web/scripts/index.php b/web/scripts/index.php new file mode 100644 index 0000000..24aa5aa --- /dev/null +++ b/web/scripts/index.php @@ -0,0 +1,55 @@ + + + + + + + ClearSCM: Scripting + + + + + + + + + + + + + +
    +
    + +

    Scripting Expertise

    + + +

    Over the years there have been many languages that have come + about. Each have their strengths and places in a professionals bag + of tricks. Perl is very good as a "glue" language bringing + together various other systems, home grown as well as purchased + packages, to help build larger, customized solutions for your + business. PHP, on the other hand, grew up on the web and is + excellent for web applications. In fact we use it here. And there + are many shops who have a heritage of shell scripts. We have + expereince with all of these.

    +
    + + +
    + + + + + diff --git a/web/scripts/perl.php b/web/scripts/perl.php new file mode 100644 index 0000000..b9f64e1 --- /dev/null +++ b/web/scripts/perl.php @@ -0,0 +1,166 @@ + + + + + + ClearSCM: Perl + + + + + + + + + +
    +
    + +

    Perl

    + + +

    Perl is an extremely versitle language that is widely used. A + quick Perl script can be conjured up in a snap or you can utilize + an elaborate set of Perl Modules from CPAN + or create your own modules, packages and objects. Perl supports many + advanced language concepts.

    + +

    With any langauge there comes a style that is developed by users + of that lanaguage. Styles sometime vary widely - other times beginners + to the language have little if any. Over the years we have developed + our own style.

    + +

    Perl Modules

    + +

    Another thing we have developed is a set of modules of often used + functionality. Oh sure, there are tons of modules in CPAN, often more + comprehensive than ours. However such modules are written to cover each + and every aspect of their topic of focus, often including many constructs + and subroutines, parameters and the like as to make them more unweildy + and difficult to work with. Our modules are for "commonly used" things + to be used in a way so as to simply, not complicate, programming. Some of + the more basic modules are under GPL and available for download as clearlib.tar.gz and they are desribed here:

    + +
    +
    CmdLine.pm
    + +
    Adds command history stack to command line oriented programs
    + +
    DateUtils.pm
    + +
    Simple date/time utilities
    + +
    Display.pm
    + +
    Simple and consistant display routines for Perl
    + +
    GetConfig.pm
    + +
    Simple config file parsing
    + +
    Logger.pm
    + +
    Object oriented interface to handling logfiles
    + +
    Mail.pm
    + +
    A simplified approach to sending email
    + +
    OSDep.pm
    + +
    Isolate OS dependencies
    + +
    Rexec.pm
    + +
    Execute commands remotely
    + +
    TimeUtils.pm
    + +
    Time utilities
    + +
    Utils.pm
    + +
    Simple and often used utilities
    +
    + +

    Clearcase Perl Modules

    + +

    These modules are © ClearSCM, Inc. If you wish to use them then + please contact us.

    + +
    +
    Clearcase.pm
    + +
    Object oriented interface to Clearcase
    + +
    Clearcase::Vobs.pm
    + +
    Object oriented interface to Clearcase VOBs
    + +
    Clearcase::Vob.pm
    + +
    Object oriented interface to a Clearcase VOB
    + +
    Clearcase::Views.pm
    + +
    Object oriented interface to Clearcase Views
    + +
    Clearcase::View.pm
    + +
    Object oriented interface to a Clearcase View
    + +
    Clearcase::Element.pm
    + +
    Object oriented interface to a Clearcase Element
    + +
    Clearcase::UCM::Activity.pm
    + +
    Object oriented interface to a Clearcase UCM Activity
    + +
    Clearcase::UCM::Stream.pm
    + +
    Object oriented interface to a Clearcase UCM Stream
    +
    + +

    Clearquest Perl Modules

    + +

    These modules are © ClearSCM, Inc. If you wish to use them then + please contact us.

    + +
    +
    Clearquest.pm
    + +
    Object oriented interface to Clearquest
    + +
    Clearquest::Client.pm
    + +
    Client interface to Clearquest Daemon
    + +
    Clearquest::DBService.pm
    + +
    Clearquest Database Service module
    + +
    Clearquest::LDAP.pm
    + +
    Interface to LDAP info for Clearquest
    + +
    Clearquest::Server.pm
    + +
    Clearquest Server Module
    +
    Clearquest::REST.pm
    +
    Clearquest REST Module
    +
    +
    + + +
    + + + + + diff --git a/web/services/consultancy.php b/web/services/consultancy.php new file mode 100644 index 0000000..4e1c07d --- /dev/null +++ b/web/services/consultancy.php @@ -0,0 +1,128 @@ + + + + + + + ClearSCM: Services + + + + + + + + + + + + + + +
    +
    +
    +
    +

    Consultancy

    + +

    The core of our services lies in our people and their + expertise. Beyond our expertise, and perhaps because of it, we + are in a unique position to offer your company our unique + perspective based on years of experience.

    + +
    + +
    + + +

    Consultancy

    + +

    Our core service is our people + and their years of experience in the field.

    + + + + + +

    Custom Software + Solutions

    + +

    In addition to SCM, we build custom software solutions + using:

    + +
      +
    • Web Site Design
    • + +
    • Web Application Design and Implementation
    • + +
    • Custom Build Automation
    • + +
    • Test Automation
    • +
    + + + + + +

    Systems Administration

    + +

    Whether large or small, today's software is more + complex. Many resources are brought to bear to get your code + from inception to release....(more) + + + +

    +
    + +
    + +
    +
    + +
    + + + + + diff --git a/web/services/custom_software.php b/web/services/custom_software.php new file mode 100644 index 0000000..19df594 --- /dev/null +++ b/web/services/custom_software.php @@ -0,0 +1,155 @@ + + + + + + + ClearSCM: Services + + + + + + + + + + + + + + +
    +
    +
    +
    +

    Custom Software Solutions

    + +

    Rarely do companies live on SCM alone. There are many + internal and inhouse systems that grow up around Build and + Release Management systems including things like internal web + sites tracking nightly build processes. Integrations between + bug tracking systems and source control systems. Many times + such intergrations have yet to be made. As such we here at + ClearSCM also provide expertise in other, nontraditional SCM + roles such as:

    + +
      +
    • Web Site Design
    • + +
    • Web Application Design and Implementation
    • + +
    • Custom Build Automation
    • + +
    • Test Automation
    • +
    + +

    Additionally, as any large group collaberative system such + as source control and build systems are, often we get involved + in other aspects of your corporate data processing systems + such as:

    + +
      +
    • Network Trouble Shooting
    • + +
    • Licensing Monitoring
    • + +
    • Systems Performance Monitoring
    • +
    + +
    + +
    + + +

    Consultancy

    + +

    Our core service is our people + and their years of experience in the field.

    + + + + + +

    Custom Software + Solutions

    + +

    In addition to SCM, we build custom software solutions + using:

    + +
      +
    • Web Site Design
    • + +
    • Web Application Design and Implementation
    • + +
    • Custom Build Automation
    • + +
    • Test Automation
    • +
    + + + + + +

    Systems Administration

    + +

    Whether large or small, today's software is more + complex. Many resources are brought to bear to get your code + from inception to release....(more) + + + +

    +
    + +
    + +
    +
    + +
    + + + + + diff --git a/web/services/customers.php b/web/services/customers.php new file mode 100644 index 0000000..067945e --- /dev/null +++ b/web/services/customers.php @@ -0,0 +1,206 @@ + + + + + + + ClearSCM: Services + + + + + + + + + + + + + + +
    +
    +
    +
    +

    Customers

    + +

    Some of our previous clients:

    + + + + + + + + + + + + + + + + + + + + + +
    + + Ameriquest Mortgage + + + + Broadcom + +
    + + Cisco Systems + + + + Hewlett Packard + +
    + + LynuxWorks + + + + Salira Optical Network Systems + +
    + + Sun Microsystems + + + + Texas Instruments + +
    + + +

    We'd love to make you our next client.

    + +
    + +
    + + +

    Consultancy

    + +

    Our core service is our people + and their years of experience in the field.

    + + + + + +

    Custom Software + Solutions

    + +

    In addition to SCM, we build custom software solutions + using:

    + +
      +
    • Web Site Design
    • + +
    • Web Application Design and Implementation
    • + +
    • Custom Build Automation
    • + +
    • Test Automation
    • +
    + + + + + +

    Systems Administration

    + +

    Whether large or small, today's software is more + complex. Many resources are brought to bear to get your code + from inception to release....(more) + + + +

    +
    + +
    + +
    +
    + +
    + + + + + diff --git a/web/services/index.php b/web/services/index.php new file mode 100644 index 0000000..19e0a1e --- /dev/null +++ b/web/services/index.php @@ -0,0 +1,125 @@ + + + + + + + ClearSCM: Services + + + + + + + + + + + + + + +
    +
    +
    +
    +

    ClearSCM Services

    + +

    We offer the following services.

    + +
    + +
    + + +

    Consultancy

    + +

    Our core service is our people + and their years of experience in the field.

    + + + + + +

    Custom Software + Solutions

    + +

    In addition to SCM, we build custom software solutions + using:

    + +
      +
    • Web Site Design
    • + +
    • Web Application Design and Implementation
    • + +
    • Custom Build Automation
    • + +
    • Test Automation
    • +
    + + + + + +

    Systems Administration

    + +

    Whether large or small, today's software is more + complex. Many resources are brought to bear to get your code + from inception to release....(more) + + + +

    +
    + +
    + +
    +
    + +
    + + + + + diff --git a/web/services/scm.php b/web/services/scm.php new file mode 100644 index 0000000..f9be223 --- /dev/null +++ b/web/services/scm.php @@ -0,0 +1,129 @@ + + + + + + + ClearSCM: Services + + + + + + + + + + + + + + +
    +
    +
    +
    +

    Source Configuration Management

    + +

    Managing the complexities of modern software requires + professional methodologies, professional tools and, well, + professionals. That's where we come in. We apply solid + configuration management practices to your software to insure + a smooth flow from design through deployment.

    + +
    + +
    + + +

    Consultancy

    + +

    Our core service is our people + and their years of experience in the field.

    + + + + + +

    Custom Software + Solutions

    + +

    In addition to SCM, we build custom software solutions + using:

    + +
      +
    • Web Site Design
    • + +
    • Web Application Design and Implementation
    • + +
    • Custom Build Automation
    • + +
    • Test Automation
    • +
    + + + + + +

    Systems Administration

    + +

    Whether large or small, today's software is more + complex. Many resources are brought to bear to get your code + from inception to release....(more) + + + +

    +
    + +
    + +
    +
    + +
    + + + + + diff --git a/web/services/sysadmin.php b/web/services/sysadmin.php new file mode 100644 index 0000000..3774e1d --- /dev/null +++ b/web/services/sysadmin.php @@ -0,0 +1,131 @@ + + + + + + + ClearSCM: Services + + + + + + + + + + + + + + +
    +
    +
    +
    +

    Systems Administration

    + +

    Whether large or small, today's software is more + complex. Many resources are brought to bear to get your code + from inception to release. As such many different machines, + architectures and operating systems need to be tamed to work + in tandem with each other for smooth operations. Thus the SCM + professional needs to be well versed in Systems + Administrations of these resources.

    + +
    + +
    + + +

    Consultancy

    + +

    Our core service is our people + and their years of experience in the field.

    + + + + + +

    Custom Software + Solutions

    + +

    In addition to SCM, we build custom software solutions + using:

    + +
      +
    • Web Site Design
    • + +
    • Web Application Design and Implementation
    • + +
    • Custom Build Automation
    • + +
    • Test Automation
    • +
    + + + + + +

    Systems Administration

    + +

    Whether large or small, today's software is more + complex. Many resources are brought to bear to get your code + from inception to release....(more) + + + +

    +
    + +
    + +
    +
    + +
    + + + + + diff --git a/web/services/web.php b/web/services/web.php new file mode 100644 index 0000000..0327000 --- /dev/null +++ b/web/services/web.php @@ -0,0 +1,134 @@ + + + + + + + ClearSCM: Services + + + + + + + + + + + + + + +
    +
    +
    +
    +

    Web Applications

    + +

    It's hard to imagine utilizing computers today without + using a browser. Sure we all surf the web with our + browers. But increasingly internal processes are presented and + managed via web applications. Some of these web + applications are produced by 3rd partys such as IBM Rational's + Clearcase web or Clearquest web, to monitoring web + applications that show us the state of our servers and + networks to home grown applications that give us vital + information about our internal processes. ClearSCM people can + deal with all of them.

    + +
    + +
    + + +

    Consultancy

    + +

    Our core service is our people + and their years of experience in the field.

    + + + + + +

    Custom Software + Solutions

    + +

    In addition to SCM, we build custom software solutions + using:

    + +
      +
    • Web Site Design
    • + +
    • Web Application Design and Implementation
    • + +
    • Custom Build Automation
    • + +
    • Test Automation
    • +
    + + + + + +

    Systems Administration

    + +

    Whether large or small, today's software is more + complex. Many resources are brought to bear to get your code + from inception to release....(more) + + + +

    +
    + +
    + +
    +
    + +
    + + + + + diff --git a/web/sysadm/env/index.php b/web/sysadm/env/index.php new file mode 100644 index 0000000..a941ece --- /dev/null +++ b/web/sysadm/env/index.php @@ -0,0 +1,437 @@ + + + + + + + ClearSCM: Environment + + + + + + + + + + + + + + + +
    +
    + +

    Your Environment Can Make or Break You

    + +

    It's often no wonder that mere morals shudder at the site of a +command prompt and scream bloody murder when asked to drop into the +command line. It's a baren and unfamilar place for most people filled +with all the potential pitfalls of actually getting work done! But it +doesn't have to be that way.

    + +

    Indeed the basic shell presented by Unix, and Windows for that +matter, is pretty bleak, harsh and forboding configured with default +settings. But shells and shell languages are wonderfully configureable +and customizable as well as extremely powerful. Often error messages +come out in command line executions that are never caught by the GUIs +and displayed. And you can often get and manipulate a lot of +information given the rich set of commands and piping techniques +afforded by most modern shells. Nevermind you can easily set up quick +loops to itterate through the information in ways that will quite +frankly dazzle your friends... well your geek friends at least.

    + +

    All that said this document is to describe a set of start up +scripts that I've developed over the years that I find extremely +useful. The environment is easily installable yet quite sophisticated +at the same time.

    + +
    Note: For Windows I use Cygwin as it provides a +full, Linux like environment such that most stuff runs the same on +Unix, FreeBSD, Solaris, Linux and Windows without change. There are +additional Cygwin Tweaks to be described +later that make the Windows environment further normalized and make +productived.
    + +

    The Package

    + +

    For quite some time now I have packaged up my stuff to reside under +~/.rc. This allows me to easily grep for the occurances of things in +one convienent place. I've even places my XEmacs customizations under +~/.rc too. All of this is in CVS on my corporate site and I keep +things up to date and documented there. You can obtain the package as +a tarball clearenv.tar.gz. Unpack the +tar image and simply run ~/.rc/setup_rc:

    + +
    $ cd ~ +$ tar -zxf rc.tar.gz +$ .rc/setup_rc +$ +
    + +

    History

    + +

    Full version history can be viewed here.

    + +

    I now have separated out client customizations, i.e. startup +functionality particular to different clients or employers, into the +~/.rc/client_scripts directory. The start up scripts will source all +executable scripts under that directory.

    + +

    A set of Clearcase functions exist under ~/.rc/clearcase and a set +of Multisite scripts under ~/.rc/multisite. By and large these serve +to set up the Clearcase environment and mostly change common Clearcase +commands from cleartool lsview to simply lsview. Where appropriate +additional functionality has been added such as lsview <part of +view name> which effectively does a cleartool lsview piped to grep +to find views with <part of view name> in their names. An lsview +by itself will do cleartool lsview piped to your $PAGER and lsviews +will generate a list of views useful in constructs such as:

    + +
    $ for view in $(lsviews); do +> echo "Processing view $view" +> # Do some thing with $view +> done +
    + +

    Other functions are provided like cm (an alias for cleartool - +stands for configuration management), cdiff (do a clearcase diff), +clist (list all checkouts), etc. Note this has been named cm because +I'm starting to integrate other CM systems such as CVS. So a cdiff +does a cleartool diff if we are on a machine that has Clearcase +whereas if we are in a diretory that has a CVS directory a cvs diff +will be done instead. Similarly clist works for both CM systems +too. Further development of this is ongoing.

    + +

    Finally there are some environment variables that are available for +handy reference such as $RGY which points to where the Clearcase +registry files are, etc.

    + +

    Functions

    + +

    Most shell functions are defined in ~/.rc/functions. Most of these +functions deal with setting it up such that the title bar of the +terminal contains an indication of whether or not you are in a view or +a cvs work area, what portion of the vob or directory you are +currently in and whether or not you are root (called Wizard). These +functions seek to maintain the proper titlebar such that if you say +ssh'ed to another machine the titlebar would change - if you exit that +ssh session the titlebar should change back.

    + +

    Another handy function is sj - stands for Show Job. It's basically +a ps -ef | grep -i <str&gr;. How many times to do you that? Why not +make it shorter? There is also user and group functions which +essentially do ypcat [passwd|group] | grep -i <str>. This allows +you to easily search the passwd and group NIS maps (Note I have not +implemented this for Windows yet but the thought would be to make it +function the same).

    + +

    bash_login

    + +

    This whole start up environment is oriented for the bash(1) +shell. It used to be ksh(1) but I've moved on to bash. As such the +~/.rc/base_login is where most stuff gets sourced and set up. It also +mitigates some of the differences between the various supported OSes +as well as sets up aliases, etc.

    + +

    set_path

    + +

    This script sets up the PATH from scratch. The idea was if your +PATH ever gets hosed you can get it all back with +~/.rc/set_path. There is a list of paths to places where applications +may or may not exist. These are fed into a function that appends to +the PATH variable but only if the directory actually exists. So while +you might see /usr/local/mysql/bin but not have mysql installed, the +append_to_path function will recognize that /usr/local/mysql/bin does +not exist and not append it to the path.

    + +

    Cygwin Tweaks

    + +

    In order to help out the start up scripts I mount the Clearcase +view drive (by default M) to /view. Now /view/<viewname> is the +same between Unix and Cygwin. Also I mount C:\Program Files -> +/apps. Just makes more sense and is easier to type.

    + +

    Clearcase Functions

    + +

    General function - scm = cleartool

    + +

    The scm function calls cleartool (and possibly cvs). It also gets +rid of the problem with Clearcase under Windows sending extra carriage +returns. If you wish to do a Clearcase cleartool command and it is not +short circuted then you can use scm instead. Short circuted commands +are basically cleartool command that you don't need to even specify scm +for. Examples include lsview, lsstream, pwv, etc.

    + +

    Note: The ct command has been aliased to the scm command.

    + +

    Clearcase commands, using either the scm function of the familiar scm +function, also provide full command line completion! This means that if +you do scm lsview ad and then type tab, bash will complete your ad string +to expand to all of the views that start with the letters "ad". In other +words, bash completion for Clearcase commands means that hitting tab +in scm commands will complete the command line much like file name +completion currently works in bash, except it's smart in that if the +context of the scm command calls for a view name here then bash +completes view names. If the context of a scm command calls for a vob name +then completion will complete vob names, or baselines or labels, etc. +Even options are completed (type scm lsview - then tab) or even command +names themselves (type scm <space> then tab twice and you'll be +given a list of all Clearcase commands!

    + +

    ci

    + +

    The ci short circut stands for check in. This will use your +~/.clearcase_profile to specify the -nc if that is your default. So +then the common action of check in goes from cleartool checkin -> +ci

    + +

    co

    + +

    Same as ci but stands for checkout.

    + +

    unco

    + +

    Undo checkout

    + +

    Setview

    + +

    This is the regular setview command for Unix. Setview is not +supported under Windows but we fake it by doing a startview then +mounting /view/<viewname> to /vobs and start a new bash +shell. We are attempting to emulate the setview of Unix but we can't +fully because in Unix you are chrooted and /view is what is called a +super root. when we exit the setview under Windows we then +umount /vobs. The problem here is that we can only have one view set +on the system because the /vobs mount point contains that view's +name. So if terminal 1 setview view1 and terminal 2 setview view2, +terminal 1 would see things as of the last mount of /vob which is +view2. Further if either terminal exists the mount is unmounted and +the other terminal now has no current working directory. This is a +known bug and... I'm working on it!

    + +

    startview

    + +

    Stgarts a view then cd's the /view/<viewname>

    + +

    endview

    + +

    Does cm endview

    + +

    killview

    + +

    Does endview with -server

    + +

    mkview

    + +

    Short circut of cm mkview

    + +

    makeview (experimental)

    + +

    Attempts to create or reuse a view. It takes one parameter - the +stream. The stream you say? Yes! This is UCM. It takes the stream name +and attempts create a view on that stream. First it checks to see if +that view has already been made and if so it does a setview. If not it +attempts to make the view. If it's unsuccessful it tries to do an +lsstream by first lopping off a few characters of the stream name and +searching for that hoping that the stream name you provided was +"close".

    + +

    Note: The view tag composed will be ${USER}_$STREAM.

    + +

    rmview

    + +

    Short circut for cm rmview

    + +

    lsview

    + +

    Lists views. If no parameters are given then it does an cm lsview +-short | $PAGER. This lists all viewnames and pages it. If you give it +one parameter then it pipes the output to grep, grepping for that +string case insensitive. If you give it more parameters it just short +circuts to cm lsview <parms>.

    + +

    myviews

    + +

    Lists views that have $USER in them assuming they are UCM oriented +and getting the headline of UCM activity set in the view, if any. Note +that this is a little slow to talk to UCM to get the headlines. If you +just want to see what views are yours (i.e. have your userid in their +names) then do lsview $USER.

    + +

    llview

    + +

    One of the "ll" commands. When you see ll think "list long". This +does a cm lsview -l.

    + +

    lsviews

    + +

    Easy way to get a list of all views (remember lsview by itself will +use $PAGER). Useful for loops.

    + +

    lsvob

    + +

    Short circut for cm lsvob. Functions like lsview (pages, searches, etc). + +

    llvob

    + +

    Long vob listing

    + +

    setcs

    + +

    Short circut for cm setsc

    + +

    edcs

    + +

    Short circut for cm edsc

    + +

    catcs

    + +

    Short circut for cm catsc

    + +

    pwv

    + +

    Prints the current view (-short)

    + +

    rmtag

    + +

    Short circut for cm rmtag

    + +

    mktag

    + +

    Short circut for cm mktag

    + +

    describe

    + +

    Short circut for cm describe

    + +

    vtree

    + +

    Display version tree (cm lsvtree -g)

    + +

    merge

    + +

    Short circut for cm merge

    + +

    cdiff

    + +

    Performs graphical diff for Clearcase or with two non-Clearcase +files or does a cvs diff if in CVS mode.

    + +

    space

    + +

    Short circut of cm space

    + +

    register

    + +

    Short circut of cm register

    + +

    unregister

    + +

    Short circut of cm unregister

    + +

    hostinfo

    + +

    Short circut of cm hostinfo

    + +

    lstrig

    + +

    Lists the trigger type if two parms are given (trtype and pvob) +otherwise alias for cm lstype -kind trtype | $PAGER.

    + +

    lltrig

    + +

    Lists long the trigger type if two parms are given (trtype and pvob) +otherwise alias for cm lstype -long -kind trtype | $PAGER.

    + +

    lsbr

    + +

    Lists lbtype's

    + +

    lsstream

    + +

    Lists all streams to $PAGER to alias for cm lsstream if parameters +are specified.

    + +

    llstream

    + +

    Lists long all streams to $PAGER to alias for cm lsstream -lif +parameters are specified.

    + +

    rebase

    + +

    Short circut for cm rebase.

    + +

    deliver

    + +

    Short circut for cm deliver.

    + +

    lsbl

    + +

    Short circut for cm lsbl.

    + +

    lsproject

    + +

    Lists all projects to your $PAGER or alias for cm lsproject.

    + +

    llproject

    + +

    Lists long all projects to your $PAGER or alias for cm lsproject -long.

    + +

    lsstgloc

    + +

    Lists all stglocs to your $PAGER or alias for cm lsstgloc.

    + +

    llstgloc

    + +

    Lists long all stglocs to your $PAGER or alias for cm lsstgloc -long.

    + +

    lsstream

    + +

    Lists all streams to your $PAGER or alias for cm lsstream.

    + +

    llstream

    + +

    Lists long all streams to your $PAGER or alias for cm lsstream -long.

    + +

    lsact

    + +

    Lists all activities to your $PAGER or alias for cm lsactivity.

    + +

    llact

    + +

    Lists long all activities to your $PAGER or alias for cm lsactivity -long.

    + +

    setact

    + +

    Short circut for cm setactivity

    + +

    clist

    + +

    Lists all currently checked out elements or locally modified cvs entries.

    + +

    ciwork

    + +

    Check in all checked out work.

    +
    + + +
    + + + + + diff --git a/web/sysadm/index.php b/web/sysadm/index.php new file mode 100644 index 0000000..9ba040c --- /dev/null +++ b/web/sysadm/index.php @@ -0,0 +1,61 @@ + + + + + + + ClearSCM: Systems Administration + + + + + + + + + + + + + +
    +
    + +

    Systems Administration

    + + +

    Whether large or small, today's software is more complex. Many + resources are brought to bear to get your code from inception to + release. As such many different machines, architectures and + operating systems need to be tamed to work in tandem with each + other for smooth operations. Thus the SCM professional needs to be + well versed in Systems Administrations of these resources.

    + +

    Our people have many years of Systems Administration experience + in Windows, Unix and Linux Operating Systems. Additionally we have + been at many clients so we have seen how shops administer their + machines from various perspectives. So instead of having 10 years + of systems administration experinece in one company, thus knowing + only how they manage their systems, we have years of experience at + many companies which helps us understand the many ways of + administrating systems, some good and some not so good.

    +
    + + +
    + + + + + -- 2.17.1