# # add_dir "" # # add_dir "graphs" # # add_dir "projects" # # add_dir "skel" # # add_dir "skel/keys" # # add_dir "www" # # add_dir "www/projects" # # add_dir "www/viewmtn" # # add_dir "www/viewmtn/MT" # # add_dir "www/viewmtn/MochiKit" # # add_dir "www/viewmtn/mimetypes" # # add_dir "www/viewmtn/tests" # # add_file "Makefile" # content [9a2ffce8aaa8a1c927d5f18eedc448b0cbaf6c6b] # # add_file "README" # content [bc70210de10219e59d3621429c06ca1a767f39e6] # # add_file "hostconfig" # content [81e3f0264c41c02e3a88634d3d08a0ecebc6a17b] # # add_file "reset" # content [e03f22f58ed710cc61fbe18a710085f412dc5a15] # # add_file "skel/keys/address@hidden" # content [9bc20d5e58966d35f2981f121786116c85d4e9ed] # # add_file "skel/monotonerc" # content [fbb7f8d0c99e1461941e4200bb8c114533675b5d] # # add_file "usher.cc" # content [dad3c7f40eb143cc64f8ac661fb1d52ed19b060e] # # add_file "www/JSON.php" # content [e74c9792a17996ea4e2ab7c313e24238e8022650] # # add_file "www/admin-description.js" # content [d8fab719409fc04467c21a9670d51ebd8b1ebbc1] # # add_file "www/admin-description.php" # content [f4d40babaff2b48eb133853bd2eb938bf01ce084] # # add_file "www/admin-description_backend.php" # content [35aaa14b1ed2fd7b061c029208d095f3ed520a58] # # add_file "www/admin-files.js" # content [1d84e9f90e267d99e4bddffe5f2e3185e31a0952] # # add_file "www/admin-files.php" # content [cf293356f2e2925a5212262f24965413fd10d1d2] # # add_file "www/admin-files_backend.php" # content [ef16f5dbaffe7f400e6b75126f7707abb5d5f9af] # # add_file "www/admin-permissions.js" # content [590199b8d6d1ac81c6d109a606785135b3b3f1ee] # # add_file "www/admin-permissions.php" # content [1b1e6d4dc769ca39aa9dd01a5a8f7b3c4438c2bc] # # add_file "www/admin-permissions_backend.php" # content [9dfe7767585351057995a7131f324748ec3136d1] # # add_file "www/admin-resources.js" # content [da39a3ee5e6b4b0d3255bfef95601890afd80709] # # add_file "www/admin-resources.php" # content [011cc85d5bd32650ca04e0b1b40f2dfd40e1b19e] # # add_file "www/admin-resources_backend.php" # content [da39a3ee5e6b4b0d3255bfef95601890afd80709] # # add_file "www/admin-source_control.js" # content [a082da5d4a5e50cb274bb9328c9f9b1c2118043b] # # add_file "www/admin-source_control.php" # content [c0b97c31128d5033bfb60b4808e9778720a7c9c2] # # add_file "www/admin-source_control_backend.php" # content [ada854e455f7bca6723429842e412ea3559554c0] # # add_file "www/admin-web.js" # content [da39a3ee5e6b4b0d3255bfef95601890afd80709] # # add_file "www/admin-web.php" # content [011cc85d5bd32650ca04e0b1b40f2dfd40e1b19e] # # add_file "www/admin-web_backend.php" # content [da39a3ee5e6b4b0d3255bfef95601890afd80709] # # add_file "www/admin.php" # content [32aaf240f93758d13591685cf6bba7b0b2bc7766] # # add_file "www/common-ctrl.php" # content [5cdf8502620216366fe9a4fbf60f228ef2e4414b] # # add_file "www/common.js" # content [775a226f88575520d8f512e38400ca78a4f85168] # # add_file "www/common.php" # content [75a41f4b1a6c60cff1c6c07204bc3aa61c855357] # # add_file "www/index.php" # content [c13794fe13c9178b6da873f0a6114d4507703a1e] # # add_file "www/login.php" # content [059f010ebc7b0b3dece03207fc009ac0be3b4da9] # # add_file "www/logo.png" # content [e6c7794bf02d16122ecfe0906eaa74cac0b3e17e] # # add_file "www/main.css" # content [05ecec3016114cd457762e6786192b124fbceb3e] # # add_file "www/proj-ctrl.php" # content [3edf710d4011a6e74f3adc0484e91bdc214d1581] # # add_file "www/project.php" # content [44be90a1f008f16900f68f135c62b7f37cba651f] # # add_file "www/sidebar.js" # content [591674ba9a1f37b50fe779982c88caaca5aa0145] # # add_file "www/sidebar.php" # content [c2f6674477eb24e00356b7c08b8983aba132ae44] # # add_file "www/top.php" # content [f0b9cc047ef6aa963f3fcd04e5fda912b4d0ba3a] # # add_file "www/user.php" # content [8a88e97360ed641441ac944fbbb831fca160ec65] # # add_file "www/viewmtn/.htaccess" # content [829a9f62d4252e9e700d4385e0db34620d05f854] # # add_file "www/viewmtn/.mt-attrs" # content [86977de7325070bc357b010cf6c414c869437763] # # add_file "www/viewmtn/AUTHORS" # content [efc3dd1070798deb1fc2a2312204cc2f9faf00d4] # # add_file "www/viewmtn/ChangeLog" # content [c16ac2013e466b7eafcdf54b808052056d2f3e89] # # add_file "www/viewmtn/LICENSE" # content [dfac199a7539a404407098a2541b9482279f690d] # # add_file "www/viewmtn/MT/log" # content [da39a3ee5e6b4b0d3255bfef95601890afd80709] # # add_file "www/viewmtn/MT/options" # content [22c5554869a19c266858b53736d41feb5da4be34] # # add_file "www/viewmtn/MT/revision" # content [c631e51e1b595cd9ab865c31ccd401af64ee0cb4] # # add_file "www/viewmtn/MochiKit/MochiKit.js" # content [db162319790852ff3e02f6e673b729a3c262f4ec] # # add_file "www/viewmtn/MochiKit/__package__.js" # content [6f05da1941ddb410b89f26ea4ea4e72c235ceaed] # # add_file "www/viewmtn/README" # content [46ffe1ce5080c9fd0081574ae8a8da30c3df65dc] # # add_file "www/viewmtn/TODO" # content [6372d06a8696794b11e1f5dece588de4d1057896] # # add_file "www/viewmtn/about.psp" # content [92120d31e268182f68917b20874c60f8f0ef70d2] # # add_file "www/viewmtn/authors.py" # content [0ed0661cf3ddf1b402a73dfc1ff0fccc64ad05f1] # # add_file "www/viewmtn/branch.psp" # content [d830ff244a8f4668b76109a677700df56e251a81] # # add_file "www/viewmtn/builtpython.sh" # content [c1d18a362622ec209ea6818957a5778d5374de45] # # add_file "www/viewmtn/common.py" # content [19a25a6ea138661ce01454ec58c1ea20fcd884aa] # # add_file "www/viewmtn/config.py" # content [0b3642715ca87c6fb355d802cbea1753b7235aac] # # add_file "www/viewmtn/config.py.example" # content [7627e154e30ee6e88e547312876aa5cca8bf7c7e] # # add_file "www/viewmtn/diff.psp" # content [4c495def64a5c64f8da213eaa870663f859b404e] # # add_file "www/viewmtn/enscriptlangs.py" # content [95c443cac402a883b7f523a46faba66d2c79caaf] # # add_file "www/viewmtn/error.psp" # content [e1defd5a7a1469119dcbe07f0624e3412c394a03] # # add_file "www/viewmtn/file.psp" # content [b18003b58deaf7a838a644a0757d57c5e8b69f91] # # add_file "www/viewmtn/fileinbranch.psp" # content [044caac4a12a1326f3aed19c92078614926368e6] # # add_file "www/viewmtn/headofbranch.psp" # content [7be2770cfdf0a0c364d0d9e3f38a0f7e0d7ebe63] # # add_file "www/viewmtn/help.psp" # content [02a65e870a2cfc34680ba57740ea4fe9f82a48c7] # # add_file "www/viewmtn/html.py" # content [55d2ec5e347f1e75a5dd89295c9492699e8b8487] # # add_file "www/viewmtn/index.psp" # content [0b257820d558b3a44081ccba10279e76d9e82555] # # add_file "www/viewmtn/json.py" # content [8011edc26270fc6ae7e34887279cb08479418368] # # add_file "www/viewmtn/manifest.psp" # content [707922f63bdcc200ab716a047b3116afb0ffccf2] # # add_file "www/viewmtn/mimetypes/gnome-fs-directory.png" # content [f79ed53a7400ffe50afbbbb085221efb4857caec] # # add_file "www/viewmtn/mimetypes/gnome-library.png" # content [ebd329284c6e1795dad94eb38a21e51f56935e59] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-magicpoint.png" # content [739d087d785e1e42d2322a029f64bbfc06f1f737] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-msword.png" # content [e1112f34189ff864d2b8edf22d44fcdec9a5fbf3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-ogg.png" # content [1693339c452cdf728566dfeb8b6d3ffca37f61f3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-par.png" # content [131842477237314ceacac51b8d16899a9a4bf69b] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-pdf.png" # content [4b6c29ff36f567c3fce222ef3332c9f7521fb2f1] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-pgp-encrypted.png" # content [0973c972918fc59fd6beb167401a3ee02dcc9c38] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-pgp-keys.png" # content [af8e3c600348a7d60b68f15da96986694ed48148] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-pgp.png" # content [4d0507cd5023c4d4c7344579fcdc9bc06e5f41e7] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-postscript.png" # content [38ed48c0066385e1952697e5d2780b0c4a594d22] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-qif.png" # content [28567d468893846922cdee08431623efef56d05e] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-rhythmbox-effect.png" # content [91e77f6b28f956b3fae1b303ce1c5d9e9012eb3b] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-rhythmbox-playlist.png" # content [437e838a2c0ec07c28fdff8333db59ccfc59ad40] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-rtf.png" # content [a286e242089888b1c185e73575850cb1b80e0de3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-smil.png" # content [02f0c0f4ce3fdb88a273c594fc446e111adf9160] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-vnd.lotus-1-2-3.png" # content [b5683645633e56bea766aa54fe3642c831eb18a5] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-vnd.ms-excel.png" # content [c1cf7fd30c5301e50f24a6683414ad9fc908a9f7] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-vnd.ms-powerpoint.png" # content [f790c027f1a99367ef8198f8bee39545034a36cd] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-vnd.ms-word.png" # content [e1112f34189ff864d2b8edf22d44fcdec9a5fbf3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-vnd.stardivision.calc.png" # content [8b880870364d822ccdd7f0a8ebb9947f7fcea941] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-vnd.stardivision.impress.png" # content [739d087d785e1e42d2322a029f64bbfc06f1f737] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-vnd.stardivision.writer.png" # content [8b880870364d822ccdd7f0a8ebb9947f7fcea941] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-vnd.sun.xml.calc.png" # content [8c242711a9550d15e763272b8d07fe9589e3954f] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-vnd.sun.xml.draw.png" # content [39f4a1531594cdb2b2adb28324d621c7e8a37870] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-vnd.sun.xml.impress.png" # content [739d087d785e1e42d2322a029f64bbfc06f1f737] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-vnd.sun.xml.writer.png" # content [a286e242089888b1c185e73575850cb1b80e0de3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-vnd.sun.xml.writer.template.png" # content [9810bc38085f24aa49208ca138dff5171bc1e105] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-wordperfect.png" # content [a286e242089888b1c185e73575850cb1b80e0de3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-abiword.png" # content [8b880870364d822ccdd7f0a8ebb9947f7fcea941] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-applix-spreadsheet.png" # content [b5683645633e56bea766aa54fe3642c831eb18a5] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-applix-word.png" # content [a286e242089888b1c185e73575850cb1b80e0de3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-archive.png" # content [84f46f2d1fe05ffa9cf3c2031351159ae82fb4e3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-arj.png" # content [84f46f2d1fe05ffa9cf3c2031351159ae82fb4e3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-bittorrent.png" # content [75eabe830158329f465cc1cd64fc69048b2e8780] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-bla.png" # content [42afef70502d28ac2cdff4638f168e195085eed5] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-blender.png" # content [43dc9850549ed898502ac12f9c38e749cd52529c] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-blf.png" # content [19242cf6b4d5531c96c2282859d403ce255892bf] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-blv.png" # content [19242cf6b4d5531c96c2282859d403ce255892bf] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-bzip-compressed-tar.png" # content [84f46f2d1fe05ffa9cf3c2031351159ae82fb4e3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-bzip.png" # content [84f46f2d1fe05ffa9cf3c2031351159ae82fb4e3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-cd-image.png" # content [8e5f068b2026b9da64555c47bb4bfac05e3f5a18] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-class-file.png" # content [c9ff30653096ffbcafb308446c2e6fafe9c272d9] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-compress.png" # content [84f46f2d1fe05ffa9cf3c2031351159ae82fb4e3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-compressed-tar.png" # content [84f46f2d1fe05ffa9cf3c2031351159ae82fb4e3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-core.png" # content [d5780e52bd7d33b580d149d4930e80320c694346] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-cpio-compressed.png" # content [84f46f2d1fe05ffa9cf3c2031351159ae82fb4e3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-cpio.png" # content [84f46f2d1fe05ffa9cf3c2031351159ae82fb4e3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-dc-rom.png" # content [7124ebd35822064f481bbd42a6e9b40c8ed70a98] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-deb.png" # content [84f46f2d1fe05ffa9cf3c2031351159ae82fb4e3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-desktop.png" # content [456ed7f266c5de63d6128f441b32445f83785c8b] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-dia-diagram.png" # content [84514d7aae7eb6f94b787642985708f2fc29c72d] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-dv.png" # content [7a0ec215aa2b8469fbd9d414b439a8338f4c51fc] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-dvi.png" # content [7934576ac8828ad3e9fb2a781c6eb7ce4bf6dcea] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-e-theme.png" # content [c8a083a1554f6a0892bc0cfb9d19091347319da7] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-executable.png" # content [79c7b7baf3a7f1fd0b08f72ad823c497f40a5485] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-extension-nfo.png" # content [7934576ac8828ad3e9fb2a781c6eb7ce4bf6dcea] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-extension-par2.png" # content [131842477237314ceacac51b8d16899a9a4bf69b] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-font-afm.png" # content [ad93bc3d05f87a5a740ead51d6fd08549e8dc5d2] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-font-bdf.png" # content [ef29014d5b82cc5b9f032992a1801cab703fbaed] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-font-linux-psf.png" # content [ef29014d5b82cc5b9f032992a1801cab703fbaed] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-font-pcf.png" # content [ef29014d5b82cc5b9f032992a1801cab703fbaed] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-font-sunos-news.png" # content [ad93bc3d05f87a5a740ead51d6fd08549e8dc5d2] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-font-ttf.png" # content [f165b959b5b75ed8c345758e810018e06f307c0d] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-gameboy-rom.png" # content [ef11f15a3fc7a9b626348f7fc6da17c31cb5b883] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-genesis-rom.png" # content [7124ebd35822064f481bbd42a6e9b40c8ed70a98] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-glade.png" # content [f1c95cffc82943a2c1dda13d3dde427ce3e567e0] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-gnucash.png" # content [28567d468893846922cdee08431623efef56d05e] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-gnumeric.png" # content [a27c59f6252100d0ff754c233d625b419ea94b8a] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-gtktalog.png" # content [7cafdcfe9aebcdb39f41d2a008d27022f373bf38] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-gzip.png" # content [84f46f2d1fe05ffa9cf3c2031351159ae82fb4e3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-ipod-firmware.png" # content [3af3bf3a96bf2f10056d7f02387518e730972322] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-jar.png" # content [6692e92dc5db912423752071dc28baa565dbd0dd] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-killustrator.png" # content [8ef47847c64047e2ca3617f004175bd3393d86cf] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-kpresenter.png" # content [9c967a00b93b709a78902a194fa9ace44564fdbd] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-kspread.png" # content [63ce8f4fab889bafc7df12c842c31d41e4ae7711] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-kword.png" # content [6676c3fe23bee4fc6d90f2db44c126315ad51583] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-lha.png" # content [84f46f2d1fe05ffa9cf3c2031351159ae82fb4e3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-lhz.png" # content [84f46f2d1fe05ffa9cf3c2031351159ae82fb4e3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-mrproject.png" # content [4ae99b14dd1d66176da71f49715ff1face60087e] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-msx-rom.png" # content [7124ebd35822064f481bbd42a6e9b40c8ed70a98] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-n64-rom.png" # content [7124ebd35822064f481bbd42a6e9b40c8ed70a98] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-nes-rom.png" # content [7124ebd35822064f481bbd42a6e9b40c8ed70a98] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-object.png" # content [ebd329284c6e1795dad94eb38a21e51f56935e59] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-perl.png" # content [8cf2de3f4126c9910e5e76956ece641b27cd3c2e] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-php.png" # content [9d8175b9b5561fd940bdbb176707846ab39f0212] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-python-bytecode.png" # content [e1ca7e759d348f3efb508fcf6bdb48ae707189d3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-python.png" # content [e1ca7e759d348f3efb508fcf6bdb48ae707189d3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-qw.png" # content [28567d468893846922cdee08431623efef56d05e] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-rar.png" # content [84f46f2d1fe05ffa9cf3c2031351159ae82fb4e3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-reject.png" # content [4af5c0c2ab2a9db8c923144f3821a214fd49edab] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-rpm.png" # content [84f46f2d1fe05ffa9cf3c2031351159ae82fb4e3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-ruby.png" # content [2ca2fd30c5f6069a82caed12233c4ef0d942cb84] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-sharedlib.png" # content [ebd329284c6e1795dad94eb38a21e51f56935e59] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-shellscript.png" # content [3681109a34a94241b36206c5b89907e9e3713f27] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-shockwave-flash.png" # content [1c3bf562aeb737e47bd8afee682c17242ec80d98] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-sms-rom.png" # content [7124ebd35822064f481bbd42a6e9b40c8ed70a98] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-sql.png" # content [d4311b0c14f8aea2a4355e06b62a73e545a1b230] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-stuffit.png" # content [5908130299934cfcc315b344fb9c5ff58db6b743] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-tar.png" # content [84f46f2d1fe05ffa9cf3c2031351159ae82fb4e3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-tex.png" # content [7934576ac8828ad3e9fb2a781c6eb7ce4bf6dcea] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-trash.png" # content [31d6a4e66859ca048c936d42008828547dc924d2] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-x509-ca-cert.png" # content [1e9b7b2dc52bbf4a671db2f840472bc4a8aebdeb] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-zip.png" # content [84f46f2d1fe05ffa9cf3c2031351159ae82fb4e3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application.png" # content [720047d3ca1bc3732209ca22375224e84ced5ffc] # # add_file "www/viewmtn/mimetypes/gnome-mime-audio-ac3.png" # content [7a968533b90ee52d29f974a0b984008e11e3b197] # # add_file "www/viewmtn/mimetypes/gnome-mime-audio-basic.png" # content [f9a2fd7fd93a96a811de884c6d45329c9a832493] # # add_file "www/viewmtn/mimetypes/gnome-mime-audio-midi.png" # content [f9a2fd7fd93a96a811de884c6d45329c9a832493] # # add_file "www/viewmtn/mimetypes/gnome-mime-audio-x-aiff.png" # content [62b5e1a199a7cd6bcb4994b871b5f5eaa3773814] # # add_file "www/viewmtn/mimetypes/gnome-mime-audio-x-it.png" # content [8253bd3c980fd20458b228963dd3d1680ff5f90d] # # add_file "www/viewmtn/mimetypes/gnome-mime-audio-x-midi.png" # content [503ac481576798bf1c252379e8b5e3ccf9d076fe] # # add_file "www/viewmtn/mimetypes/gnome-mime-audio-x-mod.png" # content [87718ba80b769468fac73d151201bd93b988db33] # # add_file "www/viewmtn/mimetypes/gnome-mime-audio-x-mp3.png" # content [a1349a504ebc0a0430fed2ca06d2b9e16e2d1129] # # add_file "www/viewmtn/mimetypes/gnome-mime-audio-x-s3m.png" # content [e3ab485e60bec66b954f2de8057bc4c6a56bf107] # # add_file "www/viewmtn/mimetypes/gnome-mime-audio-x-stm.png" # content [1a9a9beb8154f8b28896b6bdd95f9bd6a1233afd] # # add_file "www/viewmtn/mimetypes/gnome-mime-audio-x-ulaw.png" # content [23a589c2adf360b4dee4892e7b2189f1ad414960] # # add_file "www/viewmtn/mimetypes/gnome-mime-audio-x-voc.png" # content [8d41654fac8a0661a7cc080970621ef21062193a] # # add_file "www/viewmtn/mimetypes/gnome-mime-audio-x-wav.png" # content [88a2b15d50b108d9b143ff2bbc961ff3cb260c55] # # add_file "www/viewmtn/mimetypes/gnome-mime-audio-x-xi.png" # content [d069dfa614727fcde17ba56c13215a666eb1d0c6] # # add_file "www/viewmtn/mimetypes/gnome-mime-audio-x-xm.png" # content [5f2977093bb4c63a6e1662170ec3ecfb4ef5be89] # # add_file "www/viewmtn/mimetypes/gnome-mime-audio.png" # content [f9a2fd7fd93a96a811de884c6d45329c9a832493] # # add_file "www/viewmtn/mimetypes/gnome-mime-image-bmp.png" # content [b242be3384e0d1d6bb187ac6eba78966f98dfea5] # # add_file "www/viewmtn/mimetypes/gnome-mime-image-gif.png" # content [1ac422e10e6d1b87681a73fadcc7e06cee44f6ec] # # add_file "www/viewmtn/mimetypes/gnome-mime-image-jpeg.png" # content [a91d9172010836be94c5da85a4e582c8b0e3086c] # # add_file "www/viewmtn/mimetypes/gnome-mime-image-png.png" # content [d73ddf225c6195d32cb68abdd3d1d2b7c0eca0f0] # # add_file "www/viewmtn/mimetypes/gnome-mime-image-svg+xml.png" # content [1a0212e4ff3a2b5d2e3c0b3665942b11712f24ca] # # add_file "www/viewmtn/mimetypes/gnome-mime-image-svg.png" # content [f0972930316fc2dfac32bfc45040089f73bf4c01] # # add_file "www/viewmtn/mimetypes/gnome-mime-image-tiff.png" # content [dc1514a3b2181662d10aae62404258702db2ed06] # # add_file "www/viewmtn/mimetypes/gnome-mime-image-wmf.png" # content [3dd0e3e4b6051e17f10650e8b2487d72b3438230] # # add_file "www/viewmtn/mimetypes/gnome-mime-image-x-3ds.png" # content [a80f834ce6f7da4fffc9863e33d5796d213bca3f] # # add_file "www/viewmtn/mimetypes/gnome-mime-image-x-applix-graphic.png" # content [b5ef331f879718da43a5529ea3b83deaf3b905f3] # # add_file "www/viewmtn/mimetypes/gnome-mime-image-x-cmu-raster.png" # content [b563e9550b55017c7b266d4de80c6b696642cdf9] # # add_file "www/viewmtn/mimetypes/gnome-mime-image-x-lwo.png" # content [94287f107b6ed6ff32d3eec34e712029eef90eaa] # # add_file "www/viewmtn/mimetypes/gnome-mime-image-x-lws.png" # content [d8f646deb6bd9a130aadad5732a2a2137149e4b4] # # add_file "www/viewmtn/mimetypes/gnome-mime-image-x-xcf.png" # content [b1ed95e9eb5579fe0c710b84d065b06e9d698113] # # add_file "www/viewmtn/mimetypes/gnome-mime-image.png" # content [8bacc8bae4d551bd9548981c35c01b7867ce87ac] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-css.png" # content [534aa3cfa7e87ba71b3f279369016dcc78591a5c] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-html.png" # content [534aa3cfa7e87ba71b3f279369016dcc78591a5c] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-authors.png" # content [944f28ee96f2d9a6ecc0dea253990fa25fe25807] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-c++src.png" # content [9d8175b9b5561fd940bdbb176707846ab39f0212] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-c-header.png" # content [c43823aef326aa88ec29c1661ad19c33be4d4b70] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-c.png" # content [9d8175b9b5561fd940bdbb176707846ab39f0212] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-chdr.png" # content [c43823aef326aa88ec29c1661ad19c33be4d4b70] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-copying.png" # content [1e9b7b2dc52bbf4a671db2f840472bc4a8aebdeb] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-credits.png" # content [944f28ee96f2d9a6ecc0dea253990fa25fe25807] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-csh.png" # content [79c7b7baf3a7f1fd0b08f72ad823c497f40a5485] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-csharp.png" # content [309944a912142103ef93853e2f7fe115fef3ee19] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-csrc.png" # content [9d8175b9b5561fd940bdbb176707846ab39f0212] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-haskell.png" # content [41e2702ff9aebff23bb3ab745d605d273caa9f2f] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-install.png" # content [458303f02450bff93bca68f1e3b5c2c6dc23b0a7] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-java.png" # content [a3b64323cf44fc00e659d3d926abe130789b2b54] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-literate-haskell.png" # content [41e2702ff9aebff23bb3ab745d605d273caa9f2f] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-lyx.png" # content [7125d2793f43014235d3a73fc4a8025c562395ec] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-makefile.png" # content [37ec02d20fe9be5ebce54bf732eb80789db54e18] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-objcsrc.png" # content [9d8175b9b5561fd940bdbb176707846ab39f0212] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-patch.png" # content [17f158169a515bbe08c4980b12a2404aa70c7db4] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-readme.png" # content [9ca1f04881a070880fdece18c748222e439d96f7] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-scheme.png" # content [72861f75f24eb30e25c0b455606c76816d697b3a] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-sql.png" # content [9fa1802efbdcbc8c28219db2ef999fb48e97a4c4] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-tex.png" # content [7934576ac8828ad3e9fb2a781c6eb7ce4bf6dcea] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-troff-man.png" # content [d49baf527b02e113d0bcf5fd91975690a6108b24] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-txt.png" # content [7934576ac8828ad3e9fb2a781c6eb7ce4bf6dcea] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-vcalendar.png" # content [3857f66f89d54022741c23b78bcbce0167fa9ecc] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-vcard.png" # content [7deb5dc19c36923a10a8b18b54c26885829ab0a4] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-zsh.png" # content [5905b531efd06bcac7fe88cddfcf89093fcde27e] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-xml.png" # content [e3a454d829935a7f5bf466367a92b77a31c4acf0] # # add_file "www/viewmtn/mimetypes/gnome-mime-text.png" # content [cdb065454549392e47e63a1070b42feee806a1de] # # add_file "www/viewmtn/mimetypes/gnome-mime-video-mpeg.png" # content [09f6566f2c1a2d395af353dbd86a550cb6233abe] # # add_file "www/viewmtn/mimetypes/gnome-mime-video-quicktime.png" # content [09f6566f2c1a2d395af353dbd86a550cb6233abe] # # add_file "www/viewmtn/mimetypes/gnome-mime-video-x-ms-asf.png" # content [09f6566f2c1a2d395af353dbd86a550cb6233abe] # # add_file "www/viewmtn/mimetypes/gnome-mime-video-x-ms-wmv.png" # content [09f6566f2c1a2d395af353dbd86a550cb6233abe] # # add_file "www/viewmtn/mimetypes/gnome-mime-video-x-msvideo.png" # content [09f6566f2c1a2d395af353dbd86a550cb6233abe] # # add_file "www/viewmtn/mimetypes/gnome-mime-video.png" # content [09f6566f2c1a2d395af353dbd86a550cb6233abe] # # add_file "www/viewmtn/mimetypes/gnome-mime-x-directory-nfs-server.png" # content [c08ada537f4211734ec93ffe5b7e99db9c0e3a24] # # add_file "www/viewmtn/mimetypes/gnome-mime-x-directory-smb-server.png" # content [2e2122d5bb87c6589fd42c353a2d68a944c14123] # # add_file "www/viewmtn/mimetypes/gnome-mime-x-directory-smb-share.png" # content [d79a265bcf526b8dc6627433cc1d427521cf60b3] # # add_file "www/viewmtn/mimetypes/gnome-mime-x-directory-smb-workgroup.png" # content [67c53c04c13c3b5db93a07322d173ff34d0bae9f] # # add_file "www/viewmtn/mimetypes/gnome-mime-x-font-afm.png" # content [94eb3dabef44920053f00e6c5146fc9a9ece66d7] # # add_file "www/viewmtn/mimetypes/gnome-package.png" # content [84f46f2d1fe05ffa9cf3c2031351159ae82fb4e3] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-database.png" # content [75af2189c6086d38c16cf1da70ae820f61a91304] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-drawing-template.png" # content [4926861dd6bcc4837f5200793a26712e92479070] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-drawing.png" # content [46234e776bb71be41b2a2301c658d573692dac87] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-formula.png" # content [68974d489be536a81223d4d71436f7050b85feec] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-master-document.png" # content [31b5b33e1c2caf0f7fc314293ac2104ac2e82d93] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-oasis-database.png" # content [fb0e854ec6d6a983d53d2bd2ef5aff2c02960766] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-oasis-drawing-template.png" # content [67ff9b09a567ee9d04276bbe54cea47aa32593be] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-oasis-drawing.png" # content [c743b78d95bbcb82a7a0aab3287eec6ab8d83bf5] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-oasis-formula.png" # content [89b5b5b574966e84d34cc192829c51e81e724659] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-oasis-master-document.png" # content [bdb0a62d8efb12dbc34599481ef3e122f9cc4be2] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-oasis-presentation-template.png" # content [8de665dac190c60219d18c13ff1ff313f97960f5] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-oasis-presentation.png" # content [27dd1f4b90dece268b3d7589ff0125bfd0712cfb] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-oasis-spreadsheet-template.png" # content [d51e92c30ddc424ed2b13f3589de7c2e894431f6] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-oasis-spreadsheet.png" # content [24e9fa7049318165e13acac08452a55e08a8d7b0] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-oasis-text-template.png" # content [0752fa6fa92e01497298d0eaaba375e0b2a3404a] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-oasis-text.png" # content [ae00b6f2eaac52ab6188b27ddba261251a7dc470] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-oasis-web-template.png" # content [5df111ae4802669436855769381590fc64db4cba] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-presentation-template.png" # content [5d5ff5bc82de286dd7e8857ad92d6e832f1d90cb] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-presentation.png" # content [bf141c3cbc5ccc8f771d6f41e3f989b703539641] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-spreadsheet-template.png" # content [cefe2bbd334ab8eddedb2e92f7d49a4e7858fdb2] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-spreadsheet.png" # content [9bb1e9e5d3457a33b844c4acbc2336731a0a5325] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-text-template.png" # content [5296ce7641867a68669ee3d9bbedd14f30a0941d] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-text.png" # content [5ee318f65c862137f6f52240daa3105f454935ea] # # add_file "www/viewmtn/monotone.py" # content [b4f6cf2091756a3992836759df138b9a183fdbba] # # add_file "www/viewmtn/revision.psp" # content [b0be5ff714ff6ecdf9f86b4dd34d8d1fa8044d0e] # # add_file "www/viewmtn/rss_feed.gif" # content [027515fd4558abf317d54c437b83ec6bc76e3dd8] # # add_file "www/viewmtn/tags.psp" # content [7b81023a3fbf60ce0ed258d7c9b9086b233e4e8d] # # add_file "www/viewmtn/tarofbranch.psp" # content [7fcb5ab84ad5b6538d0c64228440cb5acbdd9110] # # add_file "www/viewmtn/tests/automate.py" # content [f04942987a6f0f4c71b14d9be69256d5dffa2bfa] # # add_file "www/viewmtn/utility.py" # content [78cd2c53af05e63bb76c097dc832b6f75394e40d] # # add_file "www/viewmtn/viewmtn.css" # content [6efcadac0d56fb3d77a22786f477ba621f4af33d] # # add_file "www/viewmtn/viewmtn.js" # content [b36f4e737e9b054f563c320becbc74d7a4c1df48] # # add_file "www/viewmtn/viewmtn.py" # content [a071b2192e2092ec222ab0d59ef95efd1c9c81e6] # # add_file "www/viewmtn/wrapper.py" # content [cecbc2c42ee0b90795c18c39364a0df5f0f208ef] # # set "reset" # attr "mtn:execute" # value "true" # # set "www/logo.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/builtpython.sh" # attr "mtn:execute" # value "true" # # set "www/viewmtn/mimetypes/gnome-fs-directory.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-library.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-magicpoint.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-msword.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-ogg.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-par.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-pdf.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-pgp-encrypted.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-pgp-keys.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-pgp.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-postscript.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-qif.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-rhythmbox-effect.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-rhythmbox-playlist.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-rtf.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-smil.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-vnd.lotus-1-2-3.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-vnd.ms-excel.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-vnd.ms-powerpoint.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-vnd.ms-word.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-vnd.stardivision.calc.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-vnd.stardivision.impress.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-vnd.stardivision.writer.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-vnd.sun.xml.calc.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-vnd.sun.xml.draw.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-vnd.sun.xml.impress.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-vnd.sun.xml.writer.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-vnd.sun.xml.writer.template.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-wordperfect.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-abiword.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-applix-spreadsheet.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-applix-word.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-archive.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-arj.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-bittorrent.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-bla.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-blender.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-blf.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-blv.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-bzip-compressed-tar.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-bzip.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-cd-image.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-class-file.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-compress.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-compressed-tar.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-core.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-cpio-compressed.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-cpio.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-dc-rom.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-deb.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-desktop.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-dia-diagram.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-dv.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-dvi.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-e-theme.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-executable.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-extension-nfo.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-extension-par2.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-font-afm.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-font-bdf.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-font-linux-psf.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-font-pcf.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-font-sunos-news.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-font-ttf.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-gameboy-rom.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-genesis-rom.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-glade.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-gnucash.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-gnumeric.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-gtktalog.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-gzip.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-ipod-firmware.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-jar.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-killustrator.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-kpresenter.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-kspread.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-kword.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-lha.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-lhz.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-mrproject.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-msx-rom.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-n64-rom.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-nes-rom.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-object.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-perl.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-php.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-python-bytecode.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-python.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-qw.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-rar.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-reject.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-rpm.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-ruby.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-sharedlib.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-shellscript.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-shockwave-flash.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-sms-rom.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-sql.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-stuffit.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-tar.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-tex.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-trash.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-x509-ca-cert.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-zip.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-audio-ac3.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-audio-basic.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-audio-midi.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-audio-x-aiff.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-audio-x-it.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-audio-x-midi.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-audio-x-mod.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-audio-x-mp3.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-audio-x-s3m.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-audio-x-stm.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-audio-x-ulaw.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-audio-x-voc.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-audio-x-wav.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-audio-x-xi.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-audio-x-xm.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-audio.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-image-bmp.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-image-gif.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-image-jpeg.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-image-png.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-image-svg+xml.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-image-svg.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-image-tiff.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-image-wmf.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-image-x-3ds.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-image-x-applix-graphic.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-image-x-cmu-raster.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-image-x-lwo.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-image-x-lws.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-image-x-xcf.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-image.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-css.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-html.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-authors.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-c++src.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-c-header.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-c.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-chdr.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-copying.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-credits.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-csh.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-csharp.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-csrc.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-haskell.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-install.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-java.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-literate-haskell.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-lyx.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-makefile.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-objcsrc.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-patch.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-readme.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-scheme.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-sql.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-tex.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-troff-man.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-txt.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-vcalendar.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-vcard.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-zsh.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-xml.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-video-mpeg.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-video-quicktime.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-video-x-ms-asf.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-video-x-ms-wmv.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-video-x-msvideo.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-video.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-x-directory-nfs-server.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-x-directory-smb-server.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-x-directory-smb-share.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-x-directory-smb-workgroup.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-x-font-afm.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-package.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-database.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-drawing-template.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-drawing.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-formula.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-master-document.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-oasis-database.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-oasis-drawing-template.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-oasis-drawing.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-oasis-formula.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-oasis-master-document.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-oasis-presentation-template.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-oasis-presentation.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-oasis-spreadsheet-template.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-oasis-spreadsheet.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-oasis-text-template.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-oasis-text.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-oasis-web-template.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-presentation-template.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-presentation.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-spreadsheet-template.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-spreadsheet.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-text-template.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-text.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/rss_feed.gif" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/tests/automate.py" # attr "mtn:execute" # value "true" --- Makefile +++ Makefile @@ -0,0 +1,8 @@ +all: + +symlinks: + ln -s viewmtn/MochiKit www/MochiKit + ln -s ../../graphs www/viewmtn/graph + +usher: usher.cc + g++ usher.cc -o usher -g -Wall `pkg-config libpqxx --cflags --libs` --- README +++ README @@ -0,0 +1,30 @@ +make symlinks + Set up a couple of symlinks +make usher + Modified usher that uses the project database for a server list, + and uses the same config file as the web interface. + +skel/ Some useful files + +www/ Make this accessible to your webserver +www/viewmtn/ viewmtn install, modified slightly + +projects/*/ Project-specific (internal) +www/projects/*/ Project-specific (web-available) +graphs/ viewmtn graph cache + These three need to be +rw for the webserver. + Also, the webserver needs to be able to symlink from + www/projects/ to www/{project.php,admin.php,viewmtn/}. + This probably means that those have to be owned by the + webserver, since apache likes to only follow symlinks + that are owned by the same user as the target file. + +hostconfig config file +common.php $conffile must point to the hostconfig file + +Make sure all dns addresses *. map to an address that the +usher is listening on. + +usher hostconfig + There's a modified usher that uses the same config file and + takes the server list from a database --- hostconfig +++ hostconfig @@ -0,0 +1,9 @@ +userpass "username" "password" +hostname "hostname" +serverdir "/home/timothy/experiment" +dbstring "dbname=testdb" +admin "127.0.0.5:12345" + +hostkey "address@hidden" +monotone "/home/timothy/bin/monotone" +comment "parsing is somewhat broken, keep this line at the end" --- reset +++ reset @@ -0,0 +1,8 @@ +#!/bin/sh + +psql -c 'delete from users;' testdb +psql -c 'delete from projects;' testdb +psql -c 'delete from permissions;' testdb +rm -rf projects/* +rm -rf www/projects/* +echo -e 'userpass \n' >usherlist --- skel/keys/address@hidden +++ skel/keys/address@hidden @@ -0,0 +1,19 @@ +[keypair address@hidden +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMYG6FSLV6gI1k6WXYOd+SWZllh2EZi0a9 +aB+tqyUneitVpG8YN9JJfLPOunBG9eNH1ydcPYxQSTIxwBKQkI/zyNoiy/5J5UYKndJ3Hudb +aQnHGCwkQJ4VpJ33YkhJdV2pP89rHsdxmemdlPHB8G09+Fld4eoI0W5j+utdGxhKYQIDAQAB# +MIICyTBDBgkqhkiG9w0BBQ0wNjAeBgkqhkiG9w0BBQwwEQQIeCiY2t9IweYCAggAAgEYMBQG +CCqGSIb3DQMHBAjyozzRlfQzzwSCAoCaoSHdkWWQNVO0+g6bBKHaAr9rTAQwWkoyMhhmHzlI +DppORiCGtr6Eu3ipgnGQG12itWzvTNXflbga9mTgtFGR2LlJ6SFuts+t6itzd/BAhfpDwLV4 +lvFsdKpEUZMDTCG/yE5gH0TMrqraIk9rUmKme1bTbMuBdK17RrzlPh3Ud9CfwJ1TlElItGWK +4HgUMo4DuxxWw9L4yCK/uB8FZvtYlfKTKYASj4kXuHjJkhju0vS1OtZF9B42aimjEq+qEQqy +fl9Ol2ECdwQem1wbVT+nHUDoyfLDypNR8JriT3IhulH58/twAzasmCb0fgwo2OMbZAERgL6D +iTVpm0gmscBxw7eqaWFt9wrjs+B83Fho8H/UC2dsTCkHhCbIKknE/AG0uX5GXZ5QPfFd53c4 +fo1KcQqMJxEDk7QWiT/WspE/3OWr0SAVVBIm8qhR/JpCYvQ+Pn2dkek0iK8Ke6hixfDY89BR +5UnsCixSsxZqAhbTh47T2BaZFGHRDyQXqj4l5qMgJ2SfAv+qWIoeJ+9dPfQYITeIKsRzCJ2R +F89qDWgk2ed9TwSLF6roo5dSYl6uUXIuemnmpkpuKMdNTIdLFusYfxIQ4KXPqeQmr6vUDnEK +uEnMlGZQa/bNP+XuYl+4g9YZRKTC68oMxN7SsNnTMjwVsYV1hIBDfGIBaS+pemSJ5Kckknu+ +bq9t2yDMts8TasivwrlN8zFXzEVlM0bqsQUdn//lKqiocUciq18L1PSv/VwiyXQcA4tGg/YP +zj94iK9k6eKcFMXGZVAxR+GGn2JKcL4lO60fp59n8EXE2MBzD5MkT1tAEx3jn2VbmAh6m50C +hSq3zxT9RAa7uHQ3VbBv +[end] --- skel/monotonerc +++ skel/monotonerc @@ -0,0 +1,23 @@ +hostkey = "address@hidden" +hostkeypass = "address@hidden" + +function get_passphrase(keyid) + if keyid == hostkey then + return hostkeypass + end + return nil +end + +function persist_phrase_ok() + return true +end + +old_get_wperm = get_netsync_write_permitted +function get_netsync_write_permitted(ident) + if ident == hostkey then return true end + return old_get_wperm(ident) +end + +function get_netsync_read_permitted(foo, bar) + return true +end --- usher.cc +++ usher.cc @@ -0,0 +1,1504 @@ +// Timothy Brownawell +// GPL v2 +// +// **************** SPECIALIZED FOR A SPECIFIC HOSTING SETUP ****************** +// **************************************************************************** +// +// USERPASS username password +// Required before any other command, so random people can't do bad things. +// If incorrect, the connection will be closed immediately. +// +// STATUS [servername] +// Get the status of a server, as named by the "server" lines in the +// config file. If a server is specified, the result will be one of: +// REMOTE - this is a remote server without active connections +// ACTIVE n - this server currently has n active connections +// WAITING - this (local) server is running, but has no connections +// SLEEPING - this (local) server is not running, but is available +// STOPPING n - this (local) server has been asked to stop, but still has +// n active connections. It will not accept further connections. +// STOPPED - this (local) server has been stopped, and will not accept +// connections. The server process is not running. +// SHUTTINGDOWN - the usher has been shut down, no servers are accepting +// connections. +// SHUTDOWN - the usher has been shut down, all connections have been closed, +// and all local server processes have been stopped. +// If no server is specified, the repsonse will be SHUTTINGDOWN, SHUTDOWN, +// WAITING, or ACTIVE (with n being the total number of open connections, +// across all servers). +// +// STOP servername +// Prevent the given local server from receiving further connections, and stop +// it once all connections are closed. The result will be the new status of +// that server: ACTIVE local servers become STOPPING, and WAITING and SLEEPING +// servers become STOPPED. Servers in other states are not affected. +// +// START servername +// Allow a stopped or stopping server to receive connections again. The result +// will be the new status of that server. (A server in the "STOPPING" state +// becomes ACTIVE, and a STOPPED server becomes SLEEPING. A server in some +// other state is not affected.) +// +// LIST [state] +// Returns a space-separated list of all servers. If a state is given, only +// list the servers that are in that state. +// +// SHUTDOWN +// Do not accept new connections for any servers, local or remote. Returns "ok". +// +// STARTUP +// Begin accepting connections again after a SHUTDOWN. Returns "ok". +// +// CONNECTIONS +// Returns the number of connections currently open. +// +// RELOAD +// Reload the config file (same as sending SIGHUP). The reply will be "ok", +// and will not be given until the config file has been reloaded. +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include + +using std::vector; +using std::max; +using std::string; +using std::list; +using std::set; +using std::map; +using boost::lexical_cast; +using boost::shared_ptr; +using std::cerr; +using std::pair; +using std::make_pair; + +namespace { + +string hostname; +string serverdir; +string dbstring; + +// defaults, overridden by command line +int listenport = 4691; +string listenaddr = "0.0.0.0"; + +// keep local servers around for this many seconds after the last +// client disconnects from them (only accurate to ~10 seconds) +int const server_idle_timeout = 60; + +// ranges that dynamic (local) servers can be put on +int const minport = 15000; +int const maxport = 65000; +int const minaddr[] = {127, 0, 1, 1}; +int const maxaddr[] = {127, 254, 254, 254}; +int currport = 0; +int curraddr[] = {0, 0, 0, 0}; + +char const netsync_version = 6; + +string const greeting = " Hello! This is the monotone usher at localhost. What would you like?"; + +string const notfound = "!Sorry, I don't know where to find that."; + +string const disabled = "!Sorry, this usher is not currently accepting connections."; + +string const srvdisabled = "!Sorry, that server is currently disabled."; + +struct errstr +{ + std::string name; + int err; + errstr(std::string const & s): name(s), err(0) {} + errstr(std::string const & s, int e): name(s), err(e) {} +}; + +int tosserr(int ret, std::string const & name) +{ + if (ret == -1) + throw errstr(name, errno); + if (ret < 0) + throw errstr(name, ret); + return ret; +} + +// packet format is: +// byte version +// byte cmd {100 if we send, 101 if we receive} +// uleb128 {size of everything after this} +// uleb128 {size of string} +// string +// { +// uleb128 {size of string} +// string +// } // only present if we're receiving + +// uleb128 is +// byte 0x80 | +// byte 0x80 | +// ... +// byte 0xff & +// +// the high bit says that this byte is not the last + +void make_packet(std::string const & msg, char * & pkt, int & size) +{ + size = msg.size(); + char const * txt = msg.c_str(); + char header[6]; + header[0] = netsync_version; + header[1] = 100; + int headersize; + if (size >= 128) { + header[2] = 0x80 | (0x7f & (char)(size+2)); + header[3] = (char)((size+2)>>7); + header[4] = 0x80 | (0x7f & (char)(size)); + header[5] = (char)(size>>7); + headersize = 6; + } else if (size >= 127) { + header[2] = 0x80 | (0x7f & (char)(size+1)); + header[3] = (char)((size+1)>>7); + header[4] = (char)(size); + headersize = 5; + } else { + header[2] = (char)(size+1); + header[3] = (char)(size); + headersize = 4; + } + pkt = new char[headersize + size]; + memcpy(pkt, header, headersize); + memcpy(pkt + headersize, txt, size); + size += headersize; +} + +struct buffer +{ + static int const buf_size = 16*1024; + static int const buf_reset_size = 8*1024; + char * ptr; + int readpos; + int writepos; + buffer(): readpos(0), writepos(0) + { + ptr = new char[buf_size]; + } + ~buffer(){delete[] ptr;} + buffer(buffer const & b) + { + ptr = new char[buf_size]; + memcpy(ptr, b.ptr, buf_size); + readpos = b.readpos; + writepos = b.writepos; + } + bool canread(){return writepos > readpos;} + bool canwrite(){return writepos < buf_size;} + void getread(char *& p, int & n) + { + p = ptr + readpos; + n = writepos - readpos; + } + void getwrite(char *& p, int & n) + { + p = ptr + writepos; + n = buf_size - writepos; + } + void fixread(int n) + { + if (n < 0) throw errstr("negative read\n", 0); + readpos += n; + if (readpos == writepos) { + readpos = writepos = 0; + } else if (readpos > buf_reset_size) { + memcpy(ptr, ptr+readpos, writepos-readpos); + writepos -= readpos; + readpos = 0; + } + } + void fixwrite(int n) + { + if (n < 0) + throw errstr("negative write\n", 0); + writepos += n; + } +}; + +struct sock +{ + int *s; + static set all_socks; + operator int() + { + if (!s) + return -1; + else + return s[0]; + } + sock(int ss) + { + s = new int[2]; + s[0] = ss; + s[1] = 1; + all_socks.insert(s); + } + sock(sock const & ss) + { + s = ss.s; + if (s) + s[1]++; + } + void deref() + { + if (s && !(--s[1])) { + try { + close(); + } catch(errstr & e) { + // if you want it to throw errors, call close manually + } + delete[] s; + all_socks.erase(all_socks.find(s)); + } + s = 0; + } + ~sock() + { + deref(); + } + sock const & operator=(int ss) + { + deref(); + s = new int[2]; + all_socks.insert(s); + s[0]=ss; + return *this; + } + sock const & operator=(sock const & ss) + { + deref(); + s = ss.s; + if (s) + ++s[1]; + return *this; + } + void close() + { + if (!s || s[0] == -1) + return; + shutdown(s[0], SHUT_RDWR); + while (::close(s[0]) < 0) { + if (errno == EIO) + throw errstr("close failed", errno); + if (errno != EINTR) + break; + } + s[0]=-1; + } + static void close_all_socks() + { + for (set::iterator i = all_socks.begin(); i != all_socks.end(); ++i) { + while (::close((*i)[0]) < 0) + if (errno != EINTR) + break; + } + } + bool read_to(buffer & buf) + { + if (!s) + return false; + char *p; + int n; + buf.getwrite(p, n); + n = read(s[0], p, n); + if (n < 1) { + close(); + return false; + } else + buf.fixwrite(n); + return true; + } + bool write_from(buffer & buf) + { + if (!s) + return false; + char *p; + int n; + buf.getread(p, n); + n = write(s[0], p, n); + if (n < 1) { + close(); + return false; + } else + buf.fixread(n); + return true; + } +}; +set sock::all_socks; + +bool check_address_empty(string const & addr, int port) +{ + sock s = tosserr(socket(AF_INET, SOCK_STREAM, 0), "socket()"); + int yes = 1; + tosserr(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, + &yes, sizeof(yes)), "setsockopt"); + sockaddr_in a; + memset (&a, 0, sizeof (a)); + if (!inet_aton(addr.c_str(), (in_addr *) &a.sin_addr.s_addr)) + throw errstr("bad ip address format", 0); + a.sin_port = htons(port); + a.sin_family = AF_INET; + int r = bind(s, (sockaddr *) &a, sizeof(a)); + s.close(); + return r == 0; +} + +void find_addr(string & addr, int & port) +{ + if (currport == 0) { + currport = minport-1; + for(int i = 0; i < 4; ++i) + curraddr[i] = minaddr[i]; + } + do { + // get the next address in our list + if (++currport > maxport) { + currport = minport; + for (int i = 0; i < 4; ++i) { + if (++curraddr[i] <= maxaddr[i]) + break; + curraddr[i] = minaddr[i]; + } + } + port = currport; + addr = lexical_cast(curraddr[0]) + "." + + lexical_cast(curraddr[1]) + "." + + lexical_cast(curraddr[2]) + "." + + lexical_cast(curraddr[3]); + } while (!check_address_empty(addr, port)); +} + +sock start(string addr, int port) +{ + sock s = tosserr(socket(AF_INET, SOCK_STREAM, 0), "socket()"); + int yes = 1; + tosserr(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, + &yes, sizeof(yes)), "setsockopt"); + sockaddr_in a; + memset (&a, 0, sizeof (a)); + if (!inet_aton(addr.c_str(), (in_addr *) &a.sin_addr.s_addr)) + throw errstr("bad ip address format", 0); + a.sin_port = htons(port); + a.sin_family = AF_INET; + tosserr(bind(s, (sockaddr *) &a, sizeof(a)), "bind"); + cerr<<"bound to "<(addrport.substr(c+1)); + } catch (...) { + throw errstr("Could not parse address"); + } + return start(addr, port); +} + +sock make_outgoing(int port, string const & address) +{ + sock s = tosserr(socket(AF_INET, SOCK_STREAM, 0), "socket()"); + + struct sockaddr_in a; + memset(&a, 0, sizeof(a)); + a.sin_family = AF_INET; + a.sin_port = htons(port); + + if (!inet_aton(address.c_str(), (in_addr *) &a.sin_addr.s_addr)) + throw errstr("bad ip address format", 0); + + tosserr(connect(s, (sockaddr *) &a, sizeof (a)), "connect()"); + return s; +} + +int fork_server(vector const & args) +{ + int err[2]; + if (pipe(err) < 0) + return false; + int pid = fork(); + if (pid == -1) { + close(err[0]); + close(err[1]); + cerr<<"Failed to fork server.\n"; + return false; + } else if (pid == 0) { + close(err[0]); + close(0); + close(1); + close(2); + sock::close_all_socks(); + if (dup2(err[1], 2) < 0) { + exit(1); + } + + char ** a = new char*[args.size()+1]; + for (unsigned int i = 0; i < args.size(); ++i) { + a[i] = new char[args[i].size()+1]; + memcpy(a[i], args[i].c_str(), args[i].size()+1); + } + a[args.size()] = 0; + + execvp(a[0], a); + perror("execvp failed\n"); + exit(1); + } else { + close(err[1]); + char head[256]; + int got = 0; + int r = 0; + bool line = false; + // the first line output on the server's stderr will be either + // "monotone: beginning service on : " or + // "monotone: network error: bind(2) error: Address already in use" + do { + r = read(err[0], head + got, 256 - got); + if (r) + cerr<<"Read '"< 0) { + for (int i = 0; i < r && !line; ++i) + if (head[got+i] == '\n') + line = true; + got += r; + } + } while(r > 0 && !line && got < 256); + head[got] = 0; + if (string(head).find("beginning service") != string::npos) + return pid; + kill(pid, SIGKILL); + do {r = waitpid(pid, 0, 0);} while (r==-1 && errno == EINTR); + return -1; + } +} + +bool connections_allowed = true; +int total_connections = 0; + +struct serverstate +{ + enum ss {remote, active, waiting, sleeping, stopping, + stopped, shuttingdown, shutdown, unknown}; + ss state; + int num; + serverstate(): state(unknown), num(0) {} + serverstate const & operator=(string const & s) + { + if (s == "REMOTE") + state = remote; + else if (s == "ACTIVE") + state = active; + else if (s == "WAITING") + state = waiting; + else if (s == "SLEEPING") + state = sleeping; + else if (s == "STOPPING") + state = stopping; + else if (s == "STOPPED") + state = stopped; + else if (s == "SHUTTINGDOWN") + state = shuttingdown; + else if (s == "SHUTDOWN") + state = shutdown; + return *this; + } + bool operator==(string const & s) + { + serverstate foo; + foo = s; + return foo.state == state; + } +}; +std::ostream & operator<<(std::ostream & os, serverstate const & ss) +{ + switch (ss.state) { + case serverstate::remote: + os<<"REMOTE"; + break; + case serverstate::active: + os<<"ACTIVE "< >::iterator> by_host, by_pat; + map >::iterator by_name; + static map > servers_by_host; + static map > servers_by_pattern; + static map > servers_by_name; + static set > live_servers; + bool local; + int pid; + string arguments; + string addr; + int port; + int connection_count; + int last_conn_time; + server() : enabled(true), local(false), pid(-1), port(0), + connection_count(0), last_conn_time(0) + { + } + ~server() + { + yeskill(); + } + serverstate get_state() + { + serverstate ss; + ss.num = connection_count; + if (!connections_allowed) { + if (!total_connections) + ss.state = serverstate::shutdown; + else + ss.state = serverstate::shuttingdown; + } else if (connection_count) { + if (enabled) + ss.state = serverstate::active; + else + ss.state = serverstate::stopping; + } else if (!local) + ss.state = serverstate::remote; + else if (!enabled) + ss.state = serverstate::stopped; + else if (pid == -1) + ss.state = serverstate::sleeping; + else + ss.state = serverstate::waiting; + return ss; + } + void delist() + { + vector foo; + set_hosts(foo); + set_patterns(foo); + servers_by_name.erase(by_name); + by_name = 0; + } + void rename(string const & n) + { + shared_ptr me = by_name->second; + servers_by_name.erase(by_name); + by_name = servers_by_name.insert(make_pair(n, me)).first; + } + void set_hosts(vector const & h) + { + shared_ptr me = by_name->second; + map >::iterator c; + for (list >::iterator>::iterator + i = by_host.begin(); i != by_host.end(); ++i) + servers_by_host.erase(*i); + by_host.clear(); + for (vector::const_iterator i = h.begin(); i != h.end(); ++i) { + c = servers_by_host.find(*i); + if (c != servers_by_host.end()) { + list >::iterator>::iterator j; + for (j = c->second->by_host.begin(); j != c->second->by_host.end(); ++j) + if ((*j)->first == *i) { + servers_by_host.erase(*j); + c->second->by_host.erase(j); + } + } + c = servers_by_host.insert(make_pair(*i, me)).first; + by_host.push_back(c); + } + } + void set_patterns(vector const & p) + { + shared_ptr me = by_name->second; + map >::iterator c; + for (list >::iterator>::iterator + i = by_pat.begin(); i != by_pat.end(); ++i) + servers_by_pattern.erase(*i); + by_pat.clear(); + for (vector::const_iterator i = p.begin(); i != p.end(); ++i) { + c = servers_by_pattern.find(*i); + if (c != servers_by_pattern.end()) { + list >::iterator>::iterator j; + for (j = c->second->by_pat.begin(); j != c->second->by_pat.end(); ++j) + if ((*j)->first == *i) { + servers_by_pattern.erase(*j); + c->second->by_pat.erase(j); + } + } + c = servers_by_pattern.insert(make_pair(*i, me)).first; + by_pat.push_back(c); + } + } + sock connect() + { + if (!connections_allowed) + throw errstr("all servers disabled"); + if (!enabled) + throw errstr("server disabled"); + if (local && pid == -1) { + // server needs to be started + // we'll try 3 times, since there's a delay between our checking that + // a port's available and the server taking it + for (int i = 0; i < 3 && pid == -1; ++i) { + if (i > 0 || port == 0) + find_addr(addr, port); + vector args; + args.push_back("monotone"); + args.push_back("serve"); + args.push_back("--bind=" + addr + ":" + lexical_cast(port)); + unsigned int n = 0, m = 0; + n = arguments.find_first_not_of(" \t"); + while (n != string::npos && m != string::npos) { + m = arguments.find_first_of(" ", n); + args.push_back(arguments.substr(n, m-n)); + n = arguments.find_first_not_of(" ", m); + } + pid = fork_server(args); + } + } + sock s = make_outgoing(port, addr); + if (local && !connection_count) { + live_servers.insert(by_name->second); + } + ++connection_count; + ++total_connections; + return s; + } + void disconnect() + { + --total_connections; + if (--connection_count || !local) + return; + last_conn_time = time(0); + maybekill(); + } + void maybekill() + { + if (!local) + return; + if (pid == -1) + return; + int difftime = time(0) - last_conn_time; + if (!connection_count + && (difftime > server_idle_timeout || !connections_allowed)) + yeskill(); + else if (waitpid(pid, 0, WNOHANG) > 0) { + pid = -1; + port = 0; + } + } + void yeskill() + { + if (local && pid != -1) { + kill(pid, SIGTERM); + int r; + do {r = waitpid(pid, 0, 0);} while (r==-1 && errno == EINTR); + pid = -1; + port = 0; + live_servers.erase(live_servers.find(by_name->second)); + } + } + string name() + { + if (local && port == 0) + return "dynamic local server"; + else + return addr + ":" + lexical_cast(port); + } +}; + +map > server::servers_by_host; +map > server::servers_by_pattern; +map > server::servers_by_name; +set > server::live_servers; + +string getline(std::istream & in) +{ + string out; + char buf[256]; + do { + in.getline(buf, 256); + int got = in.gcount()-1; + if (got > 0) + out.append(buf, got); + } while (in.fail() && !in.eof()); + return out; +} + +void make_server(string const & name) +{ + vector hosts, patterns; + string projdir = serverdir + "/projects/" + name; + string desc = "-d " + projdir + "/database "; + desc += "--confdir " + projdir + " *"; + hosts.push_back(name + "." + hostname); + + // If it exists, find it. Otherwise, make a new one. + shared_ptr srv; + map >::iterator + i = server::servers_by_name.find(name); + if (i != server::servers_by_name.end()) { + srv = i->second; + i->second->delist(); + } else + srv = shared_ptr(new server); + + srv->by_name = server::servers_by_name.insert(make_pair(name, srv)).first; + srv->set_hosts(hosts); + srv->local = true; + srv->arguments = desc; +} + +shared_ptr get_server(string const & srv, string const & pat) +{ + map >::iterator i; + for (i = server::servers_by_host.begin(); + i != server::servers_by_host.end(); ++i) { + if (srv.find(i->first) == 0) { + return i->second; + } + } + for (i = server::servers_by_pattern.begin(); + i != server::servers_by_pattern.end(); ++i) { + if (pat.find(i->first) == 0) { + return i->second; + } + } + std::cerr<<"no server found for '"<(); +} + +shared_ptr get_server(string const & name) +{ + map >::iterator i; + for (i = server::servers_by_name.begin(); + i != server::servers_by_name.end(); ++i) { + if (name == i->first) { + return i->second; + } + } + return shared_ptr(); +} + +void kill_old_servers() +{ + set >::iterator i; + for (i = server::live_servers.begin(); i != server::live_servers.end(); ++i) { + (*i)->maybekill(); + } +} + +int extract_uleb128(char *p, int maxsize, int & out) +{ + out = 0; + int b = 0; + unsigned char got; + do { + if (b == maxsize) + return -1; + got = p[b]; + out += ((int)(p[b] & 0x7f))<<(b*7); + ++b; + } while ((unsigned int)(b*7) < sizeof(int)*8-1 && (got & 0x80)); + return b; +} + +int extract_vstr(char *p, int maxsize, string & out) +{ + int size; + out.clear(); + int chars = extract_uleb128(p, maxsize, size); + if (chars == -1 || chars + size > maxsize) { + return -1; + } + out.append(p+chars, size); + return chars+size; +} + +bool extract_reply(buffer & buf, string & host, string & pat) +{ + char *p; + int n, s; + buf.getread(p, n); + if (n < 4) return false; + p += 2; // first 2 bytes are header + n -= 2; + // extract size, and make sure we have the entire packet + int pos = extract_uleb128(p, n, s); + if (pos == -1 || n < s+pos) { + return false; + } + // extract host vstr + int num = extract_vstr(p+pos, n-pos, host); + if (num == -1) { + return false; + } + pos += num; + // extract pattern vstr + num = extract_vstr(p+pos, n-pos, pat); + if (num == -1) { + cerr<<"old-style reply.\n"; + pat = host; + host.clear(); + return true; + } + pos += num; + buf.fixread(pos+2); + return true; +} + +struct channel +{ + static int counter; + int num; + sock cli; + sock srv; + bool have_routed; + bool no_server; + buffer cbuf; + buffer sbuf; + shared_ptr who; + channel(sock & c): num(++counter), + cli(c), srv(-1), + have_routed(false), no_server(false) + { + char * dat; + int size; + make_packet(greeting, dat, size); + char *p; + int n; + sbuf.getwrite(p, n); + if (n < size) size = n; + memcpy(p, dat, size); + sbuf.fixwrite(size); + delete[] dat; + + cli.write_from(sbuf); + } + ~channel() + { + if (who && !no_server) + who->disconnect(); + } + bool is_finished() + { + return (cli == -1) && (srv == -1); + } + void add_to_select(int & maxfd, fd_set & rd, fd_set & wr, fd_set & er) + { + int c = cli; + int s = srv; + + if (c > 0) { + FD_SET(c, &er); + if (cbuf.canwrite()) + FD_SET(c, &rd); + if (sbuf.canread()) + FD_SET(c, &wr); + maxfd = max(maxfd, c); + } + if (s > 0) { + FD_SET(s, &er); + if (sbuf.canwrite()) + FD_SET(s, &rd); + if (cbuf.canread()) + FD_SET(s, &wr); + maxfd = max(maxfd, s); + } + } + bool process_selected(fd_set & rd, fd_set & wr, fd_set & er) + { + int c = cli; + int s = srv; +/* NB: read oob data before normal reads */ + if (c > 0 && FD_ISSET(c, &er)) { + char d; + errno = 0; + if (recv(c, &d, 1, MSG_OOB) < 1) + cli.close(), c = -1; + else + send(s, &d, 1, MSG_OOB); + } + if (s > 0 && FD_ISSET(s, &er)) { + char d; + errno = 0; + if (recv(s, &d, 1, MSG_OOB) < 1) + srv.close(), s = -1; + else + send(c, &d, 1, MSG_OOB); + } + + char *p=0; + int n; + + if (c > 0 && FD_ISSET(c, &rd)) { + if (!cli.read_to(cbuf)) c = -1; + if (!have_routed) { + string reply_srv, reply_pat; + if (extract_reply(cbuf, reply_srv, reply_pat)) { + who = get_server(reply_srv, reply_pat); + if (who && who->enabled) { + try { + srv = who->connect(); + have_routed = true; + s = srv; + } catch (errstr & e) { + cerr<<"cannot contact server "<name()<<"\n"; + no_server = true; + } + } else { + char * dat; + int size; + sbuf.getwrite(p, n); + if (who) + make_packet(srvdisabled, dat, size); + else + make_packet(notfound, dat, size); + if (n < size) size = n; + memcpy(p, dat, size); + sbuf.fixwrite(size); + delete[] dat; + no_server = true; + } + } + } + } + if (s > 0 && FD_ISSET(s, &rd)) { + if (!srv.read_to(sbuf)) s = -1; + } + + if (c > 0 && FD_ISSET(c, &wr)) { + if (!cli.write_from(sbuf)) c = -1; + } + if (s > 0 && FD_ISSET(s, &wr)) { + if (!srv.write_from(cbuf)) s = -1; + } + + // close sockets we have nothing more to send to + if (c < 0 && !cbuf.canread()) { + srv.close(), s = -1; + } + if ((no_server || have_routed && s < 0) && !sbuf.canread()) { + cli.close(), c = -1; + } + return true; + } +}; +int channel::counter = 0; + +bool reload_pending; +map admins; +string conffile; + +struct token +{ + enum type {key, str, hex, notok}; + type t; + string s; +}; + +token readtok(std::istream & is) +{ + char c; + do + is.get(c); + while(is && isspace(c)); + token tok; + if(!is) { + tok.t = token::notok; + return tok; + } + switch(c) { + case '[': + is.get(c); + tok.t = token::hex; + do { + tok.s += c; + is.get(c); + } while(is && c != ']'); + break; + case '"': + is.get(c); + tok.t = token::str; + do { + tok.s += c; + is.get(c); + if(c == '\\') { + is.get(c); + tok.s += c; + is.get(c); + } + } while(is && c != '"'); + break; + default: + tok.t = token::key; + do { + tok.s += c; + is.get(c); + } while(is && !isspace(c)); + } + return tok; +} + + +void sched_reload(int sig) +{ + reload_pending = true; +} + +void reload_conffile(string const & file); + +struct administrator +{ + sock port; + struct cstate + { + bool auth; + bool rdone; + string buf; + cstate(): auth(false), rdone(false) {} + }; + list > conns; + administrator(): port(-1) + {} + bool process(cstate & cs) + { + unsigned int n = cs.buf.find("\n"); + if (n == cs.buf.npos) + return true; + string l = cs.buf.substr(0, n); + cs.buf.erase(0, n+1); + std::istringstream iss(l); + string cmd; + iss>>cmd; + if (cmd == "USERPASS") { + string user, pass; + iss>>user>>pass; + map::iterator i = admins.find(user); + if (i == admins.end() || i->second != pass) { + cerr<<"Failed admin login.\n"; + return false; + } else { + if (cs.auth == true) + return false; + cs.auth = true; + return process(cs); + } + } else if (cmd == "STATUS") { + string srv; + iss>>srv; + std::ostringstream oss; + if (srv.empty()) { + serverstate ss; + ss.num = total_connections; + if (connections_allowed) { + if (total_connections) + ss.state = serverstate::active; + else + ss.state = serverstate::waiting; + } else { + if (total_connections) + ss.state = serverstate::shuttingdown; + else + ss.state = serverstate::shutdown; + } + oss< >::iterator i; + i = server::servers_by_name.find(srv); + if (i != server::servers_by_name.end()) + oss<second->get_state()<<"\n"; + else + oss<<"No such server.\n"; + } + cs.buf = oss.str(); + } else if (cmd == "ADD") { + string srv; + iss>>srv; + make_server(srv); + cs.buf = "ok"; + } else if (cmd == "START") { + string srv; + iss>>srv; + std::ostringstream oss; + map >::iterator i; + i = server::servers_by_name.find(srv); + if (i != server::servers_by_name.end()) { + i->second->enabled = true; + oss<second->get_state()<<"\n"; + } else + oss<<"No such server.\n"; + cs.buf = oss.str(); + } else if (cmd == "STOP") { + string srv; + iss>>srv; + std::ostringstream oss; + map >::iterator i; + i = server::servers_by_name.find(srv); + if (i != server::servers_by_name.end()) { + i->second->enabled = false; + i->second->maybekill(); + oss<second->get_state()<<"\n"; + } else + oss<<"No such server.\n"; + cs.buf = oss.str(); + } else if (cmd == "LIST") { + string state; + iss>>state; + map >::iterator i; + for (i = server::servers_by_name.begin(); + i != server::servers_by_name.end(); ++i) { + if (state.empty() || i->second->get_state() == state) + cs.buf += (cs.buf.empty()?"":" ") + i->first; + } + cs.buf += "\n"; + } else if (cmd == "SHUTDOWN") { + connections_allowed = false; + kill_old_servers(); + cs.buf = "ok\n"; + } else if (cmd == "CONNECTIONS") { + cs.buf = lexical_cast(total_connections) + "\n"; + } else if (cmd == "RELOAD") { + reload_conffile(conffile); + cs.buf = "ok\n"; + } else if (cmd == "STARTUP") { + connections_allowed = true; + cs.buf = "ok\n"; + } else { + return true; + } + cs.rdone = true; + return true; + } + void initialize(string const & ap) + { + try { + port = start(ap); + } catch (errstr & s) { + cerr<<"Could not initialize admin port: "< >::iterator i = conns.begin(); + i != conns.end(); ++i) { + int c = i->second; + if (!i->first.rdone) + FD_SET(c, &rd); + else + FD_SET(c, &wr); + maxfd = max(maxfd, int(c)); + } + } + void process_selected(fd_set & rd, fd_set & wr, fd_set & er) + { + if (int(port) == -1) + return; + if (FD_ISSET(port, &rd)) { + try { + struct sockaddr_in addr; + unsigned int l = sizeof(addr); + memset(&addr, 0, l); + sock nc = tosserr(accept(port, (struct sockaddr *) + &addr, &l), "accept()"); + conns.push_back(make_pair(cstate(), nc)); + } catch(errstr & s) { + cerr<<"During new admin connection: "< >::iterator> del; + for (list >::iterator i = conns.begin(); + i != conns.end(); ++i) { + int c = i->second; + if (c <= 0) { +// cerr<<"Bad socket.\n"; + del.push_back(i); + } else if (FD_ISSET(c, &rd)) { + char buf[120]; + int n; + n = read(c, buf, 120); + if (n < 1) { + // cerr<<"Read failed.\n"; + del.push_back(i); + } + i->first.buf.append(buf, n); + if (!process(i->first)) { +// cerr<<"Closing connection...\n"; +// i->second.close(); + del.push_back(i); + } + } + else if (FD_ISSET(c, &wr)) { + int n = write(c, i->first.buf.c_str(), i->first.buf.size()); + if (n < 1) { +// cerr<<"Write failed.\n"; + del.push_back(i); + } else { + i->first.buf.erase(0, n); + if (i->first.buf.empty() && i->first.rdone) { +// cerr<<"Done.\n"; + del.push_back(i); + } + } + } + } + for (list >::iterator>::iterator i = del.begin(); + i != del.end(); ++i) { + conns.erase(*i); + } + } +}; + +struct pidfile +{ + string filename; + void initialize(string const & file) + { + filename = file; + std::ofstream ofs(filename.c_str()); + ofs< names; + pqxx::connection conn(dbstring); + pqxx::work w(conn, "trx"); + pqxx::result r = w.exec("SELECT name FROM projects"); + for(unsigned int i = 0; i < r.size(); ++i) { + string n = r[i][0].c_str(); + if(n.empty()) + continue; + make_server(n); + names.insert(n); + cerr<<"Server: "< >::iterator + i = server::servers_by_name.begin(); + i != server::servers_by_name.end(); ++i) { + if (names.find(i->first) == names.end()) + i->second->delist(); + } + cerr<<"Reload complete.\n"; +} + +};//namespace anonymous + +int main (int argc, char **argv) +{ + pidfile pf; +// administrator admin; + { + int i; + for (i = 1; i < argc; ++i) { + if (string(argv[i]) == "-p") + pf.initialize(argv[++i]); + else + conffile = argv[i]; + } + if (conffile.empty() || i != argc) { + cerr<<"Usage:\n"; + cerr<<"\tusher [-p pidfile] \n"; + exit (1); + } + } + reload_conffile(conffile); + + + struct sigaction sa, sa_old; + sa.sa_handler = &sched_reload; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + while(sigaction(SIGHUP, &sa, &sa_old) == -1 && errno == EINTR); + sa.sa_handler = SIG_IGN; + while(sigaction(SIGPIPE, &sa, &sa_old) == -1 && errno == EINTR); + sa.sa_handler = sig_end; + while(sigaction(SIGTERM, &sa, &sa_old) == -1 && errno == EINTR); + while(sigaction(SIGINT, &sa, &sa_old) == -1 && errno == EINTR); + + try { + h = start(listenaddr, listenport); + } catch (errstr & s) { + std::cerr<<"Error while opening socket: "< channels; + + done = false; + while (!done) { + fd_set rd, wr, er; + FD_ZERO (&rd); + FD_ZERO (&wr); + FD_ZERO (&er); + FD_SET (h, &rd); + int nfds = h; + channel *newchan = 0; + + for (std::list::iterator i = channels.begin(); + i != channels.end(); ++i) + i->add_to_select(nfds, rd, wr, er); + + admin.add_to_select(nfds, rd, wr, er); + + timeval timeout; + timeout.tv_sec = 10; + timeout.tv_usec = 0; + int r = select(nfds+1, &rd, &wr, &er, &timeout); + + if (r < 0 && errno != EINTR) { + perror ("select()"); + exit (1); + } + if (done) + return 0; + if (FD_ISSET(h, &rd)) { + try { + struct sockaddr_in client_address; + unsigned int l = sizeof(client_address); + memset(&client_address, 0, l); + sock cli = tosserr(accept(h, (struct sockaddr *) + &client_address, &l), "accept()"); + if (connections_allowed) + newchan = new channel(cli); + else { + char * dat; + int size; + make_packet(disabled, dat, size); + write(cli, dat, size); + delete[] dat; + } + } catch(errstr & s) { + cerr<<"During new connection: "<::iterator> finished; + for (std::list::iterator i = channels.begin(); + i != channels.end(); ++i) { + try { + i->process_selected(rd, wr, er); + if (i->is_finished()) + finished.push_back(i); + } catch (errstr & e) { + finished.push_back(i); + cerr<<"Error proccessing connection "<num<<": "<::iterator>::iterator i = finished.begin(); + i != finished.end(); ++i) + channels.erase(*i); + if (newchan) { + channels.push_back(*newchan); + delete newchan; + newchan = 0; + } + kill_old_servers(); + if (reload_pending) + reload_conffile(conffile); + + admin.process_selected(rd, wr, er); + } + return 0; +} --- www/JSON.php +++ www/JSON.php @@ -0,0 +1,791 @@ + + * @author Matt Knapp + * @author Brett Stimmerman + * @copyright 2005 Michal Migurski + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198 + */ + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_SLICE', 1); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_STR', 2); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_ARR', 3); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_OBJ', 4); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_CMT', 5); + +/** + * Behavior switch for Services_JSON::decode() + */ +define('SERVICES_JSON_LOOSE_TYPE', 16); + +/** + * Behavior switch for Services_JSON::decode() + */ +define('SERVICES_JSON_SUPPRESS_ERRORS', 32); + +/** + * Converts to and from JSON format. + * + * Brief example of use: + * + * + * // create a new instance of Services_JSON + * $json = new Services_JSON(); + * + * // convert a complexe value to JSON notation, and send it to the browser + * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4))); + * $output = $json->encode($value); + * + * print($output); + * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]] + * + * // accept incoming POST data, assumed to be in JSON notation + * $input = file_get_contents('php://input', 1000000); + * $value = $json->decode($input); + * + */ +class Services_JSON +{ + /** + * constructs a new JSON instance + * + * @param int $use object behavior flags; combine with boolean-OR + * + * possible values: + * - SERVICES_JSON_LOOSE_TYPE: loose typing. + * "{...}" syntax creates associative arrays + * instead of objects in decode(). + * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression. + * Values which can't be encoded (e.g. resources) + * appear as NULL instead of throwing errors. + * By default, a deeply-nested resource will + * bubble up with an error, so all return values + * from encode() should be checked with isError() + */ + function Services_JSON($use = 0) + { + $this->use = $use; + } + + /** + * convert a string from one UTF-16 char to one UTF-8 char + * + * Normally should be handled by mb_convert_encoding, but + * provides a slower PHP-only method for installations + * that lack the multibye string extension. + * + * @param string $utf16 UTF-16 character + * @return string UTF-8 character + * @access private + */ + function utf162utf8($utf16) + { + // oh please oh please oh please oh please oh please + if(function_exists('mb_convert_encoding')) + return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16'); + + $bytes = (ord($utf16{0}) << 8) | ord($utf16{1}); + + switch(true) { + case ((0x7F & $bytes) == $bytes): + // this case should never be reached, because we are in ASCII range + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0x7F & $bytes); + + case (0x07FF & $bytes) == $bytes: + // return a 2-byte UTF-8 character + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0xC0 | (($bytes >> 6) & 0x1F)) + . chr(0x80 | ($bytes & 0x3F)); + + case (0xFFFF & $bytes) == $bytes: + // return a 3-byte UTF-8 character + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0xE0 | (($bytes >> 12) & 0x0F)) + . chr(0x80 | (($bytes >> 6) & 0x3F)) + . chr(0x80 | ($bytes & 0x3F)); + } + + // ignoring UTF-32 for now, sorry + return ''; + } + + /** + * convert a string from one UTF-8 char to one UTF-16 char + * + * Normally should be handled by mb_convert_encoding, but + * provides a slower PHP-only method for installations + * that lack the multibye string extension. + * + * @param string $utf8 UTF-8 character + * @return string UTF-16 character + * @access private + */ + function utf82utf16($utf8) + { + // oh please oh please oh please oh please oh please + if(function_exists('mb_convert_encoding')) + return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8'); + + switch(strlen($utf8)) { + case 1: + // this case should never be reached, because we are in ASCII range + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return $ut8; + + case 2: + // return a UTF-16 character from a 2-byte UTF-8 char + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0x07 & (ord($utf8{0}) >> 2)) + . chr((0xC0 & (ord($utf8{0}) << 6)) + | (0x3F & ord($utf8{1}))); + + case 3: + // return a UTF-16 character from a 3-byte UTF-8 char + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr((0xF0 & (ord($utf8{0}) << 4)) + | (0x0F & (ord($utf8{1}) >> 2))) + . chr((0xC0 & (ord($utf8{1}) << 6)) + | (0x7F & ord($utf8{2}))); + } + + // ignoring UTF-32 for now, sorry + return ''; + } + + /** + * encodes an arbitrary variable into JSON format + * + * @param mixed $var any number, boolean, string, array, or object to be encoded. + * see argument 1 to Services_JSON() above for array-parsing behavior. + * if var is a strng, note that encode() always expects it + * to be in ASCII or UTF-8 format! + * + * @return mixed JSON string representation of input var or an error if a problem occurs + * @access public + */ + function encode($var) + { + switch (gettype($var)) { + case 'boolean': + return $var ? 'true' : 'false'; + + case 'NULL': + return 'null'; + + case 'integer': + return (int) $var; + + case 'double': + case 'float': + return (float) $var; + + case 'string': + // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT + $ascii = ''; + $strlen_var = strlen($var); + + /* + * Iterate over every character in the string, + * escaping with a slash or encoding to UTF-8 where necessary + */ + for ($c = 0; $c < $strlen_var; ++$c) { + + $ord_var_c = ord($var{$c}); + + switch (true) { + case $ord_var_c == 0x08: + $ascii .= '\b'; + break; + case $ord_var_c == 0x09: + $ascii .= '\t'; + break; + case $ord_var_c == 0x0A: + $ascii .= '\n'; + break; + case $ord_var_c == 0x0C: + $ascii .= '\f'; + break; + case $ord_var_c == 0x0D: + $ascii .= '\r'; + break; + + case $ord_var_c == 0x22: + case $ord_var_c == 0x2F: + case $ord_var_c == 0x5C: + // double quote, slash, slosh + $ascii .= '\\'.$var{$c}; + break; + + case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)): + // characters U-00000000 - U-0000007F (same as ASCII) + $ascii .= $var{$c}; + break; + + case (($ord_var_c & 0xE0) == 0xC0): + // characters U-00000080 - U-000007FF, mask 110XXXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, ord($var{$c + 1})); + $c += 1; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xF0) == 0xE0): + // characters U-00000800 - U-0000FFFF, mask 1110XXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2})); + $c += 2; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xF8) == 0xF0): + // characters U-00010000 - U-001FFFFF, mask 11110XXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3})); + $c += 3; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xFC) == 0xF8): + // characters U-00200000 - U-03FFFFFF, mask 111110XX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3}), + ord($var{$c + 4})); + $c += 4; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xFE) == 0xFC): + // characters U-04000000 - U-7FFFFFFF, mask 1111110X + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3}), + ord($var{$c + 4}), + ord($var{$c + 5})); + $c += 5; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + } + } + + return '"'.$ascii.'"'; + + case 'array': + /* + * As per JSON spec if any array key is not an integer + * we must treat the the whole array as an object. We + * also try to catch a sparsely populated associative + * array with numeric keys here because some JS engines + * will create an array with empty indexes up to + * max_index which can cause memory issues and because + * the keys, which may be relevant, will be remapped + * otherwise. + * + * As per the ECMA and JSON specification an object may + * have any string as a property. Unfortunately due to + * a hole in the ECMA specification if the key is a + * ECMA reserved word or starts with a digit the + * parameter is only accessible using ECMAScript's + * bracket notation. + */ + + // treat as a JSON object + if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) { + $properties = array_map(array($this, 'name_value'), + array_keys($var), + array_values($var)); + + foreach($properties as $property) + if(Services_JSON::isError($property)) + return $property; + + return '{' . join(',', $properties) . '}'; + } + + // treat it like a regular array + $elements = array_map(array($this, 'encode'), $var); + + foreach($elements as $element) + if(Services_JSON::isError($element)) + return $element; + + return '[' . join(',', $elements) . ']'; + + case 'object': + $vars = get_object_vars($var); + + $properties = array_map(array($this, 'name_value'), + array_keys($vars), + array_values($vars)); + + foreach($properties as $property) + if(Services_JSON::isError($property)) + return $property; + + return '{' . join(',', $properties) . '}'; + + default: + return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS) + ? 'null' + : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string"); + } + } + + /** + * array-walking function for use in generating JSON-formatted name-value pairs + * + * @param string $name name of key to use + * @param mixed $value reference to an array element to be encoded + * + * @return string JSON-formatted name-value pair, like '"name":value' + * @access private + */ + function name_value($name, $value) + { + $encoded_value = $this->encode($value); + + if(Services_JSON::isError($encoded_value)) + return $encoded_value; + + return $this->encode(strval($name)) . ':' . $encoded_value; + } + + /** + * reduce a string by removing leading and trailing comments and whitespace + * + * @param $str string string value to strip of comments and whitespace + * + * @return string string value stripped of comments and whitespace + * @access private + */ + function reduce_string($str) + { + $str = preg_replace(array( + + // eliminate single line comments in '// ...' form + '#^\s*//(.+)$#m', + + // eliminate multi-line comments in '/* ... */' form, at start of string + '#^\s*/\*(.+)\*/#Us', + + // eliminate multi-line comments in '/* ... */' form, at end of string + '#/\*(.+)\*/\s*$#Us' + + ), '', $str); + + // eliminate extraneous space + return trim($str); + } + + /** + * decodes a JSON string into appropriate variable + * + * @param string $str JSON-formatted string + * + * @return mixed number, boolean, string, array, or object + * corresponding to given JSON input string. + * See argument 1 to Services_JSON() above for object-output behavior. + * Note that decode() always returns strings + * in ASCII or UTF-8 format! + * @access public + */ + function decode($str) + { + $str = $this->reduce_string($str); + + switch (strtolower($str)) { + case 'true': + return true; + + case 'false': + return false; + + case 'null': + return null; + + default: + if (is_numeric($str)) { + // Lookie-loo, it's a number + + // This would work on its own, but I'm trying to be + // good about returning integers where appropriate: + // return (float)$str; + + // Return float or int, as appropriate + return ((float)$str == (integer)$str) + ? (integer)$str + : (float)$str; + + } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) { + // STRINGS RETURNED IN UTF-8 FORMAT + $delim = substr($str, 0, 1); + $chrs = substr($str, 1, -1); + $utf8 = ''; + $strlen_chrs = strlen($chrs); + + for ($c = 0; $c < $strlen_chrs; ++$c) { + + $substr_chrs_c_2 = substr($chrs, $c, 2); + $ord_chrs_c = ord($chrs{$c}); + + switch (true) { + case $substr_chrs_c_2 == '\b': + $utf8 .= chr(0x08); + ++$c; + break; + case $substr_chrs_c_2 == '\t': + $utf8 .= chr(0x09); + ++$c; + break; + case $substr_chrs_c_2 == '\n': + $utf8 .= chr(0x0A); + ++$c; + break; + case $substr_chrs_c_2 == '\f': + $utf8 .= chr(0x0C); + ++$c; + break; + case $substr_chrs_c_2 == '\r': + $utf8 .= chr(0x0D); + ++$c; + break; + + case $substr_chrs_c_2 == '\\"': + case $substr_chrs_c_2 == '\\\'': + case $substr_chrs_c_2 == '\\\\': + case $substr_chrs_c_2 == '\\/': + if (($delim == '"' && $substr_chrs_c_2 != '\\\'') || + ($delim == "'" && $substr_chrs_c_2 != '\\"')) { + $utf8 .= $chrs{++$c}; + } + break; + + case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)): + // single, escaped unicode character + $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2))) + . chr(hexdec(substr($chrs, ($c + 4), 2))); + $utf8 .= $this->utf162utf8($utf16); + $c += 5; + break; + + case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F): + $utf8 .= $chrs{$c}; + break; + + case ($ord_chrs_c & 0xE0) == 0xC0: + // characters U-00000080 - U-000007FF, mask 110XXXXX + //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 2); + ++$c; + break; + + case ($ord_chrs_c & 0xF0) == 0xE0: + // characters U-00000800 - U-0000FFFF, mask 1110XXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 3); + $c += 2; + break; + + case ($ord_chrs_c & 0xF8) == 0xF0: + // characters U-00010000 - U-001FFFFF, mask 11110XXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 4); + $c += 3; + break; + + case ($ord_chrs_c & 0xFC) == 0xF8: + // characters U-00200000 - U-03FFFFFF, mask 111110XX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 5); + $c += 4; + break; + + case ($ord_chrs_c & 0xFE) == 0xFC: + // characters U-04000000 - U-7FFFFFFF, mask 1111110X + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 6); + $c += 5; + break; + + } + + } + + return $utf8; + + } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) { + // array, or object notation + + if ($str{0} == '[') { + $stk = array(SERVICES_JSON_IN_ARR); + $arr = array(); + } else { + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $stk = array(SERVICES_JSON_IN_OBJ); + $obj = array(); + } else { + $stk = array(SERVICES_JSON_IN_OBJ); + $obj = new stdClass(); + } + } + + array_push($stk, array('what' => SERVICES_JSON_SLICE, + 'where' => 0, + 'delim' => false)); + + $chrs = substr($str, 1, -1); + $chrs = $this->reduce_string($chrs); + + if ($chrs == '') { + if (reset($stk) == SERVICES_JSON_IN_ARR) { + return $arr; + + } else { + return $obj; + + } + } + + //print("\nparsing {$chrs}\n"); + + $strlen_chrs = strlen($chrs); + + for ($c = 0; $c <= $strlen_chrs; ++$c) { + + $top = end($stk); + $substr_chrs_c_2 = substr($chrs, $c, 2); + + if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) { + // found a comma that is not inside a string, array, etc., + // OR we've reached the end of the character list + $slice = substr($chrs, $top['where'], ($c - $top['where'])); + array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false)); + //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + if (reset($stk) == SERVICES_JSON_IN_ARR) { + // we are in an array, so just push an element onto the stack + array_push($arr, $this->decode($slice)); + + } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { + // we are in an object, so figure + // out the property name and set an + // element in an associative array, + // for now + if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { + // "name":value pair + $key = $this->decode($parts[1]); + $val = $this->decode($parts[2]); + + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $obj[$key] = $val; + } else { + $obj->$key = $val; + } + } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { + // name:value pair, where name is unquoted + $key = $parts[1]; + $val = $this->decode($parts[2]); + + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $obj[$key] = $val; + } else { + $obj->$key = $val; + } + } + + } + + } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) { + // found a quote, and we are not inside a string + array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c})); + //print("Found start of string at {$c}\n"); + + } elseif (($chrs{$c} == $top['delim']) && + ($top['what'] == SERVICES_JSON_IN_STR) && + (($chrs{$c - 1} != '\\') || + ($chrs{$c - 1} == '\\' && $chrs{$c - 2} == '\\'))) { + // found a quote, we're in a string, and it's not escaped + array_pop($stk); + //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n"); + + } elseif (($chrs{$c} == '[') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a left-bracket, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false)); + //print("Found start of array at {$c}\n"); + + } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) { + // found a right-bracket, and we're in an array + array_pop($stk); + //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } elseif (($chrs{$c} == '{') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a left-brace, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false)); + //print("Found start of object at {$c}\n"); + + } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) { + // found a right-brace, and we're in an object + array_pop($stk); + //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } elseif (($substr_chrs_c_2 == '/*') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a comment start, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false)); + $c++; + //print("Found start of comment at {$c}\n"); + + } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) { + // found a comment end, and we're in one now + array_pop($stk); + $c++; + + for ($i = $top['where']; $i <= $c; ++$i) + $chrs = substr_replace($chrs, ' ', $i, 1); + + //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } + + } + + if (reset($stk) == SERVICES_JSON_IN_ARR) { + return $arr; + + } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { + return $obj; + + } + + } + } + } + + /** + * @todo Ultimately, this should just call PEAR::isError() + */ + function isError($data, $code = null) + { + if (class_exists('pear')) { + return PEAR::isError($data, $code); + } elseif (is_object($data) && (get_class($data) == 'services_json_error' || + is_subclass_of($data, 'services_json_error'))) { + return true; + } + + return false; + } +} + +if (class_exists('pear_error')) { + + class Services_JSON_Error extends PEAR_Error + { + function Services_JSON_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + parent::PEAR_Error($message, $code, $mode, $options, $userinfo); + } + } + +} else { + + /** + * @todo Ultimately, this class shall be descended from PEAR_Error + */ + class Services_JSON_Error + { + function Services_JSON_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + + } + } + +} + +?>--- www/admin-description.js +++ www/admin-description.js @@ -0,0 +1,25 @@ +ctrl = '../../admin-description_backend.php'; + +chdesc = function () { + status("Changing project description..."); + var args = {'project':project}; + args.description = getElement("description").value; + args.longdescription = getElement("longdescription").value; + args.action = "chdesc"; + call_server(ctrl, args, "chdesc", function(data){clearstatus();}); +} + +addLoadEvent(function() { + status("Loading..."); + var x = {'project':project}; + x.action = "getdesc"; + call_server(ctrl, x, "getdesc", function (data) { + if (data.description) { + getElement("description").value = data.description; + } + if (data.longdescription) { + getElement("longdescription").value = data.longdescription; + } + clearstatus(); + }); +}); --- www/admin-description.php +++ www/admin-description.php @@ -0,0 +1,7 @@ +
+Short description:
+
+Long description:
+
+ +
--- www/admin-description_backend.php +++ www/admin-description_backend.php @@ -0,0 +1,20 @@ +encode(array( + "description" => file_get_contents($projwww . "/description"), + "longdescription" => file_get_contents($projwww . "/longdescription") + )); +} else if ($action === "chdesc") { + if(allowed('description')) { + file_put_contents($projwww . "/description", $args->description, LOCK_EX); + file_put_contents($projwww . "/longdescription", $args->longdescription, LOCK_EX); + print $json->encode(array("description" => file_get_contents($projwww . "/description"), + "longdescription" => file_get_contents($projwww . "/longdescription"))); + } +} +pg_close($db); +?> --- www/admin-files.js +++ www/admin-files.js @@ -0,0 +1,71 @@ +ctrl = '../../admin-files_backend.php'; + +do_rmfile = function (name) { + status("removing " + name + "..."); + var x = {'project':project,"action":"rmfile","file":name}; + call_server(ctrl, x, "rmfile", function (data) { + hideElement("trow-" + name); + clearstatus(); + }); +} + +do_chfilecomment = function (name) { + var c = getElement("filecomment-" + name).value; + status("changing description of " + name + "..."); + var x = {'project':project,"action":"chfiledesc","filedesc":c,"file":name}; + call_server(ctrl, x, "chfiledesc", function (data) { + clearstatus(); + }); +} + +add_file_to_present = function (name, desc) { + appendChildNodes("currentfiles-body", TR({}, + TD(null, A({'href': "files/" + name}, name)), + TD(null, INPUT({'type':'text','size':40,'value':desc,'id':'filecomment-' + name})), + TD(null, BUTTON({'type':'button','onclick':'do_rmfile("' + name + '");'}, "Remove")), + TD(null, BUTTON({'type':'button','onclick':'do_chfilecomment("' + name + '");'}, "Change description")) + )); +} + +empty_row = function () { + return TR({}, + TD(null, INPUT({"type": "file", "size": 40, "name": "upfiles[]"})), + TD(null, INPUT({"type": "text", "size": 40, "name": "upfile_comments[]"})) + ); +} +morefiles = function() { + appendChildNodes("filetablebody", empty_row()); +} +row_display = function (row) { + return TR(null, map(partial(TD, null), row)); +} +setup_upload = function() { + replaceChildNodes("filelist", TABLE({"id":"filetable"}, + THEAD(null, row_display(["File to upload", "File description"])), + TFOOT(null, row_display(["",""])), + TBODY({"id":"filetablebody"})) + ); + morefiles(); +} + + +do_upload = function() { + status("Uploading page..."); + getElement("upload_form").target = "upload_frame"; + getElement("upload_form").submit(); +} +uploaddone = function() { + getElement("upload_form").reset(); + clearstatus(); +} + +addLoadEvent(function() { +// status("Loading..."); +// var x = {'project':project}; +// x.action = "getmaint"; +// call_server(ctrl, x, "getmaint", function (data) { +// set_maint_display(data.maintainers); + setup_upload(); +// clearstatus(); +// }); +}); --- www/admin-files.php +++ www/admin-files.php @@ -0,0 +1,34 @@ +
+ +
+This project has already released the following files:
+ + + + + + + + + +
File nameFile description
\n"; +?> +
+
+ + +

+ + +
+ +
--- www/admin-files_backend.php +++ www/admin-files_backend.php @@ -0,0 +1,51 @@ +File uploadFile uploaded"; +} elseif ($action == "rmfile") { + header('Content-type: text/x-json'); + unlink($projwww . "/files/" . basename($args->file)); + unlink($projwww . "/files-about/" . basename($args->file)); + print $json->encode(array("ok" => "ok")); +} elseif ($action == "chfiledesc") { + header('Content-type: text/x-json'); + unlink($projwww . "/files-about/" . basename($args->file)); + file_put_contents($projwww . "/files-about/" . basename($args->file), $args->filedesc); + print $json->encode(array("ok" => "ok")); +} +pg_close($db); +?> --- www/admin-permissions.js +++ www/admin-permissions.js @@ -0,0 +1,93 @@ +ctrl = '../../admin-permissions_backend.php'; + +row_display = function (row) { + return TR(null, map(partial(TD, null), row)); +} +make_cbox_properties = function (row, who) { + var out = {}; + out['type'] = "checkbox"; + out['name'] = "maint_" + who + "[]"; + if(row[who]) { + out['checked'] = true; + } + return out; +} +maint_row = function (row) { + return TR({"class":"maintainerrow"}, + TD(null, INPUT({"type": "text", "name": "maint_username[]", "value": row.username})), + TD(null, INPUT(make_cbox_properties(row, "give"))), + TD(null, INPUT(make_cbox_properties(row, "server"))), + TD(null, INPUT(make_cbox_properties(row, "upload"))), + TD(null, INPUT(make_cbox_properties(row, "description")))/*, + TD(null, INPUT(make_cbox_properties(row, "homepage"))), + TD(null, INPUT(make_cbox_properties(row, "resources")))*/ + ); +} +empty_row = function () { + return TR({"class":"maintainerrow"}, + TD(null, INPUT({"type": "text", "name": "maint_username[]"})), + TD(null, INPUT({"type": "checkbox", "name": "maint_give[]"})), + TD(null, INPUT({"type": "checkbox", "name": "maint_server[]"})), + TD(null, INPUT({"type": "checkbox", "name": "maint_upload[]"})), + TD(null, INPUT({"type": "checkbox", "name": "maint_description[]"}))/*, + TD(null, INPUT({"type": "checkbox", "name": "maint_homepage[]"})), + TD(null, INPUT({"type": "checkbox", "name": "maint_resources[]"}))*/ + ); +} +maint_addrow = function() { + appendChildNodes("maintainertablebody", empty_row()); +} +set_maint_display = function(data) { + var labels = []; + replaceChildNodes("maintainerlist", TABLE({"id":"maintainertable"}, + THEAD(null, row_display(["Username", "Edit maintainers", "Control server", "Upload files", "Edit description"/*, "Edit homepage", "Change resources"*/])), + TFOOT(null, row_display(["","","","",""/*,"",""*/])), + TBODY({"id":"maintainertablebody"}, map(maint_row, data))) + ); + maint_addrow(); +} +read_maint_line = function(row) { + var out = {}; + for(var i = 0; i < row.childNodes.length; ++i) { + var cur = row.childNodes[i].firstChild; + var nm = getNodeAttribute(cur,"name"); + nm = nm.replace(/.*_(.*)\[\]/, "$1"); + if (nm == "username") { + out[nm] = cur.value; + } else { + out[nm] = cur.checked; + } + } + return out; +} + + +chmaint = function () { + status("Changing maintainers..."); + var args = {'project':project}; + args.action = "chmaint"; + var lst = getElement("maintainertablebody").childNodes; + var maints = []; + for(var i = 0; i < lst.length; ++i) { + var line = read_maint_line(lst[i]); + if (line.username != "") { + maints.push(line); + } + } + args.newmaint = maints; + call_server(ctrl, args, "chmaint", function (data) { + set_maint_display(data.maintainers); + clearstatus(); + }); +} + + +addLoadEvent(function() { + status("Loading..."); + var x = {'project':project}; + x.action = "getmaint"; + call_server(ctrl, x, "getmaint", function (data) { + set_maint_display(data.maintainers); + clearstatus(); + }); +}); --- www/admin-permissions.php +++ www/admin-permissions.php @@ -0,0 +1,6 @@ +
+Project maintainers:
+

+ + +
--- www/admin-permissions_backend.php +++ www/admin-permissions_backend.php @@ -0,0 +1,82 @@ +encode(array("maintainers" => maintlist())); +} else if ($action == "chmaint") { + if(allowed('give')) { + $res = array(); + $ok = true; + $begun = false; + foreach ($args->newmaint as $i) { + if ($i->username === $username && !$i->give) { + $ok = false; + break; + } + } + do { + if(!$ok) { + $res["error"] = "You're not allowed to revoke your own permissions to edit maintainers."; + break; + } + pg_exec($db, "BEGIN"); + $begun = true; + pg_exec($db, "LOCK TABLE permissions"); + $query = sprintf("DELETE FROM permissions WHERE project = '%s'", $safeproj); + $result = pg_exec($db, $query); + if (!$result) { + $res['error'] = 'Internal server error.'; + $ok = false; + break; + } + $fields = "username, project, give, upload, homepage, access, server, description"; + $query = sprintf("INSERT INTO permissions (%s) VALUES (%%s)", $fields); + foreach ($args->newmaint as $i) { + $values = sprintf("'%s', '%s', %s, %s, %s, %s, %s, %s", + pg_escape_string($i->username), $safeproj, + $i->give?1:0, $i->upload?1:0, $i->homepage?1:0, + $i->access?1:0, $i->server?1:0, $i->description?1:0); + $result = pg_exec($db, sprintf($query, $values)); + if (!$result) { + $res['error'] = 'Internal server error.'; + $ok = false; + break; + } + } + if($ok) + $res["maintainers"] = maintlist(); + } while(false); + if (!$ok && $begun) + pg_exec($db, "ROLLBACK"); + print $json->encode($res); + pg_exec($db, "END"); + } +} +pg_close($db); +?> --- www/admin-resources.js +++ www/admin-resources.js @@ -0,0 +1,0 @@ --- www/admin-resources.php +++ www/admin-resources.php @@ -0,0 +1,2 @@ +
+
--- www/admin-resources_backend.php +++ www/admin-resources_backend.php @@ -0,0 +1,0 @@ --- www/admin-source_control.js +++ www/admin-source_control.js @@ -0,0 +1,82 @@ +ctrl = '../../admin-source_control_backend.php'; + +chwperm = function () { + status("Changing permissions..."); + var args = {'project':project}; + args.newperm = getElement("newperm").value; + args.action = "chwriters"; + call_server(ctrl, args, "chwperm", function(data){clearstatus();}); +} + +sendkey = function() { + status("Uploading key..."); + var args = {'project':project}; + args.action = "new_key"; + args.keydata = getElement("keydata").value; + call_server(ctrl, args, "sendkey", function(data){ + getElement("keydata").value = ""; + status("Key uploaded."); + }); +} + +resetdb = function() { + status("Resetting database..."); + var args = {'project':project}; + args.action = "resetdb"; + args.oath = getElement("oath").value; + call_server(ctrl, args, "resetdb", function(data){ + getElement("oath").value = ""; + status(data.ok); + }); +} + +chstate = function() { + status("Changing server state..."); + var args = {'project':project}; + if (getElement("chstate").value == "Disable server") { + args.action = "disable"; + } else { + args.action = "enable"; + } + call_server(ctrl, args, "chstate", function(data){ + set_state_display(data.status); + clearstatus(); + }); +} + +set_state_display = function(state){ + if (state == "") { + replaceChildNodes("serverstate", "(server not found)"); + } else { + replaceChildNodes("serverstate", state); + } + // set 'chstate' button to enable/disable appropriately + if (state.match("WAITING") || state == "RUNNING" || state == "SLEEPING") { + getElement("chstate").value = "Disable server"; + } else { + getElement("chstate").value = "Enable server"; + } +} + +restate = function() { + status("Refreshing server state..."); + var args = {'project':project}; + args.action = "refresh_state"; + call_server(ctrl, args, "restate", function(data){ + set_state_display(data.status); + clearstatus(); + }); +} + +addLoadEvent(function() { + status("Loading..."); + var x = {'project':project}; + x.action = "getsrv"; + call_server(ctrl, x, "getsrv", function (data) { + if (data.wperm) { + getElement("newperm").value = data.wperm; + } + set_state_display(data.status); + clearstatus(); + }); +}); --- www/admin-source_control.php +++ www/admin-source_control.php @@ -0,0 +1,29 @@ +
+ + + + +
+People allowed to write to the database:
+(key name)
+
+ +
+Upload a new pubkey.
+This is equivalent to someone with push access doing +
mtn read file-with-key-packet
+mtn --key-to-push=<keyname> push  ''
+Pubkey packet:
+
+ +
+Current server status is "server state"
+ + +
+
+This is to reset your database, for example if you had to do a rebuild. For this to work, you must type "I solemnly swear that I have a backup." in the box below.
+Note that Bad Things may happen if your server is running while you do this. ("Current server status" above should be SLEEPING or STOPPED or SHUTDOWN)
+ + +
--- www/admin-source_control_backend.php +++ www/admin-source_control_backend.php @@ -0,0 +1,82 @@ +encode(array( + "status" => usherctrl("STATUS " . $project), + "wperm" => file_get_contents($projdir . "/write-permissions"), + )); +} else if ($action === "refresh_state") { + print $json->encode(array("status" => usherctrl("STATUS " . $project))); +} else if ($action === "disable" || $action === "enable") { + if(allowed('server')) { + if ($action === "disable") + $what = "STOP"; + else + $what = "START"; + print $json->encode(array("status" => usherctrl("$what $project"))); + } +} else if ($action === "resetdb") { + if(allowed('server')) { + if ($args->oath === "I solemnly swear that I have a backup.") { + $out = array(); + exec("rm $projdir/database 2>&1", $out, $res1); + exec("$monotone -d $projdir/database db init 2>&1", $out, $res2); + exec("chmod g+rw $projdir/database 2>&1", $out, $res3); + if ($res2 > 0 || $res3 > 0) { + $err = ""; + if ($res1) $err = $err . "Remove old database failed. "; + if ($res2) $err = $err . "Init new database failed. "; + if ($res3) $err = $err . "Set database permissions failed."; + print $json->encode(array("error" => $err, "verboseError" => $out)); + } else + print $json->encode(array("ok" => "Database reset... You *did* disable the server first, right?")); + } else { + print $json->encode(array("error" => "I'm sorry Dave, I can't let you do that.")); + } + } +} else if ($action === "new_key") { + if(allowed('access')) { + $args->keydata; + preg_match('/^\[[^ \]]* ([^\]]*)\]/', $args->keydata, $matches); + $keyname = $matches[1]; + if($keyname == null) + print $json->encode(array("error" => "Cannot extract key name.")); + else { + $out = array(); + file_put_contents($projdir . "/keyfile", $args->keydata, LOCK_EX); + exec("$monotone -d '$projdir/kdb' db init 2>&1", $out, $res1); + exec("$monotone -d '$projdir/kdb' read '$projdir/keyfile'", $out, $res2); + exec("$monotone --key-to-push '$keyname' -d '$projdir/kdb' push '$project.$hostname' '' 2>&1", $out, $res3); + exec("rm '$projdir/kdb' '$projdir/keyfile'"); + if($res1 || $res2 || $res3) { + $outstr = ""; + foreach ($out as $i) + $outstr = $outstr . $i . "\n"; + print $json->encode(array("error" => "Key upload failed.", "verboseError" => $outstr)); + } else + print $json->encode(array("result" => "ok")); + } + } +} +pg_close($db); +?> --- www/admin-web.js +++ www/admin-web.js @@ -0,0 +1,0 @@ --- www/admin-web.php +++ www/admin-web.php @@ -0,0 +1,2 @@ +
+
--- www/admin-web_backend.php +++ www/admin-web_backend.php @@ -0,0 +1,0 @@ --- www/admin.php +++ www/admin.php @@ -0,0 +1,53 @@ + + + + + + +Project admin page for <?=$project?> + + + + + + + + + + + + + +
+ +
    + "Permissions", + 'source_control' => "Source Control", + 'files' => "Files", + 'description' => "Description"/*, + 'web' => "Web", + 'resources' => "Resources"*/); + foreach ($pages as $k => $i) { + if ($k == $page) + print "
  • $i
  • "; + else + print "
  • $i
  • "; + } +?> +
+ +
+ + --- www/common-ctrl.php +++ www/common-ctrl.php @@ -0,0 +1,73 @@ +decode($GLOBALS['HTTP_RAW_POST_DATA']); + +$action = $args->action; if($_REQUEST['action']) $action = $_REQUEST['action']; +$project = $args->project; if($_REQUEST['project']) $project = $_REQUEST['project']; + +$safeproj = pg_escape_string($project); +$projdir = "$serverdir/projects/" . basename($project); +$projwww = "$serverdir/www/projects/" . basename($project); +$monotone = "$monotone --confdir '$projdir'"; + + +function getarg($name) { + global $args; + if ($_REQUEST[$name]) + return $_REQUEST[$name]; + else + return $args->$name; +} +function safearg($name) { + return pg_escape_string(getarg($name)); +} +function dirsafe($name) { + return ($name == basename($name)) && ($name != '..'); +} + +if (!$project) { + $project = basename(dirname($_SERVER['PHP_SELF'])); + $safeproj = pg_escape_string($project); +} +function allowed($what) { + global $json, $permissions, $validuser, $username; + if(!$validuser) + print $json->encode(array("error" => "username or password is incorrect.")); + else if(!$permissions[$what]) + print $json->encode(array("error" => "You're not allowed to do that.")); + else + return true; + return false; +} + +$permissions = array( + "give" => false, // grant/revoke permissions for other users + "upload" => false, // upload files + "homepage" => false, // upload new home page + "access" => false, // upload key, write-permissions + "server" => false, // start/stop server, reset db + "description" => false + ); + +if ($validuser) { + $fields = "give, upload, homepage, access, server, description"; + $query = sprintf("SELECT %s FROM permissions WHERE ", $fields); + $query = $query . "username = '%s' AND project = '%s'"; + $result = pg_exec($db, sprintf($query, $safeuser, $safeproj)); + if ($result) { + $rows = pg_numrows($result); + $permissions['rows'] = $rows; + $permissions['safeuser'] = $safeuser; + $permissions['safeproj'] = $safeproj; + if ($rows == 1) { + $row = pg_fetch_row ($result, 0); + $permissions['give'] = ($row[0] == 1); + $permissions['upload'] = ($row[1] == 1); + $permissions['homepage'] = ($row[2] == 1); + $permissions['access'] = ($row[3] == 1); + $permissions['server'] = ($row[4] == 1); + $permissions['description'] = ($row[5] == 1); + } + } +} +?> --- www/common.js +++ www/common.js @@ -0,0 +1,65 @@ +var hostname = "localhost"; +var userctrl = "user-ctrl.php"; + +var todo = {}; +var verberr = ""; +var respTxt = ""; + +// POST an object in JSON format, and get a return object also in JSON format +loadJSONPost = function (url, obj) { + respTxt = ""; + var data = serializeJSON(obj); + + var req = getXMLHttpRequest(); + req.open("POST", url, true); + req.setRequestHeader('Content-Type', 'text/x-json'); + var d = sendXMLHttpRequest(req, data); + d = d.addCallback(function(data){respTxt = data.responseText; return data;}); + d = d.addCallback(evalJSONRequest); + return d; +} + +call_server = function (url, args, nam, callback) { + var d = loadJSONPost(url, args); + todo[nam] = d; + d.addCallback(function (data) { + if (!isNull(data.error)) { + if(isNull(data.verboseError)) { + status("Error: " + data.error); + } else { + verberr = data.verboseError; + status("Error: " + data.error, BR(), SPAN({"class": "jslink", "onclick": "alert(verberr);"}, "(Click here for details)")); + } + } else { + callback(data); + } + }); + d.addErrback(function(err){ + status("Error: " + err, BR(), SPAN({"class": "jslink", "onclick": "alert(respTxt);"}, "(Click here for details)")); + }); +} + +userpass = function () { + var x = {}; + x.username = getElement("username").value; + x.password = getElement("password").value; + return x; +} + +userpassproj = function () { + var x = {}; + x.username = getElement("username").value; + x.password = getElement("password").value; + x.project = getElement("project").value; + return x; +} + + +clearstatus = function() { + replaceChildNodes('status'); +} + +status = function() { + replaceChildNodes("status", SPAN({"class": "jslink", "onclick": "clearstatus();", "style": "float: right;"}, "[X]"), arguments); +} + --- www/common.php +++ www/common.php @@ -0,0 +1,78 @@ +decode(stripslashes($_COOKIE['AUTH'])); + $username = $auth->username; + $password = $auth->password; +} else { + $username = ''; + $password = ''; +} + +$safeuser = pg_escape_string($username); +$safepass = pg_escape_string($password); + +$validuser = false; +$db = pg_connect($dbstring); +$query = sprintf("SELECT password FROM users WHERE username = '%s'", $safeuser); +$result = pg_exec($db, $query); +if ($result) { + $rows = pg_numrows($result); + if ($rows == 1) { + $row = pg_fetch_row ($result, 0); + if ($row[0] === $password) { + $validuser = true; + } + } +} +?> --- www/index.php +++ www/index.php @@ -0,0 +1,42 @@ + + + + + +Project List + +File uploaded"; + pg_close(); + exit; +} else if ($action === "upload_files") { + header('Content-type: text/html'); + print "File uploadFile uploaded"; + pg_close(); + exit; +} + +function usherctrl($what) { + global $adminaddr, $adminport, $adminuser, $adminpass; + $addr = sprintf("tcp://%s:%s", $adminaddr, $adminport); + $fp = stream_socket_client($addr, $errno, $errstr, 30); + if (!$fp) { + return "Error: $errstr ($errno)"; + } else { + $out = ""; + fprintf($fp, "USERPASS %s %s\n%s\n", $adminuser, $adminpass, $what); + while (!feof($fp)) { + $out = $out . fgets($fp, 1024); + } + fclose($fp); + return $out; + } +} + +function maintlist() { + global $db, $safeproj; + $fields = "username, give, upload, homepage, access, server, description"; + $query = sprintf("SELECT %s FROM permissions WHERE ", $fields); + $query = $query . "project = '%s'"; + $result = pg_exec($db, sprintf($query, $safeproj)); + $out = array(); + if ($result) { + $rows = pg_numrows($result); + for($i = 0; $i < $rows; ++$i) { + $row = pg_fetch_row ($result,$i); + $perm['username'] = $row[0]; + $perm['give'] = ($row[1] == 1); + $perm['upload'] = ($row[2] == 1); + $perm['homepage'] = ($row[3] == 1); + $perm['access'] = ($row[4] == 1); + $perm['server'] = ($row[5] == 1); + $perm['description'] = ($row[6] == 1); + $out[] = $perm; + } + } + return $out; +} + +header('Content-type: text/x-json'); + +if ($action == "new_project") { + if ($validuser) { + pg_exec($db, "BEGIN"); + pg_exec($db, "LOCK TABLE projects, permissions"); + $err = false; + + $query = "SELECT * FROM projects WHERE name = '%s'"; + $result = pg_exec($db, sprintf($query, $safeproj)); + do { + if(!preg_match('/^[a-zA-Z0-9-]*$/D', $project)) { + print $json->encode(array("error" => "Only letters, numbers, and dash are allowed in a project name.")); + $err = true; + break; + } + if(!$result) { + $err = true; + print $json->encode(array("error" => "Internal server error.")); + break; + } + if(pg_numrows($result)) { + print $json->encode(array("error" => "That project name is already taken.")); + $err = true; + break; + } + $projdir = $serverdir . '/projects/'. $project; + $projwww = $serverdir . '/www/projects/'. $project; + $query = "INSERT INTO projects (name, directory) VALUES ('%s', '%s')"; + $result = pg_exec($db, sprintf($query, $safeproj, '/foobar')); + if(!$result) { + $err = true; + print $json->encode(array("error" => "Internal server error.")); + break; + } + $fields = "username, project, give, upload, homepage, access, server, description"; + $query = sprintf("INSERT INTO permissions (%s) VALUES (%%s)", $fields); + $values = sprintf("'%s', '%s', 1, 1, 1, 1, 1, 1", $username, $safeproj); + $result = pg_exec($db, sprintf($query, $values)); + if(!$result) { + $err = true; + print $json->encode(array("error" => "Internal server error.")); + break; + } + $out = array(); + exec("cp -r '$serverdir/skel' '$projdir'", $out, $res1); + mkdir($projwww); + mkdir("$projwww/files"); + mkdir("$projwww/files-about"); + symlink('../../admin.php', "$projwww/admin.php"); + symlink('../../project.php', "$projwww/index.php"); + symlink('../../viewmtn', "$projwww/viewmtn"); + exec("$monotone -d $projdir/database db init 2>&1", $out, $res2); + exec("cp $projdir/database $projdir/database.viewmtn"); + exec("cp $projdir/database $projdir/database.transfer"); + exec("chmod ug+rwX '$projdir' '$projdir'/* 2>&1", $out, $res3); + if ($res1 || $res2 || $res3) { + print $json->encode(array("error" => "Internal server error", "verboseError" => $out)); + $err = true; + } + usherctrl("ADD $project"); + } while (false); + if ($err) + pg_exec($db, "ROLLBACK"); + else + print $json->encode(array("name" => $project)); + + pg_exec($db, "END"); + } else + print $json->encode(array("error" => "username or password incorrect.")); +} else + print $json->encode(array("error" => sprintf("'%s' not implemented.", $action))); +pg_close($db); +?> --- www/project.php +++ www/project.php @@ -0,0 +1,39 @@ +\n" + . "This project has released the following files:
\n" + . "
    \n"; + foreach($files as $i) { + if ($i == "." || $i == "..") + continue; + $inf = file_get_contents("files-about/$i"); + $filediv = $filediv + . "
  • $i: $inf
  • \n"; + } + $filediv = $filediv . "
\n"; + return $filediv; +} + +?> + + +Project page for <?=$project?> + + + + + + + +
+

+ +
+ --- www/sidebar.js +++ www/sidebar.js @@ -0,0 +1,31 @@ +//needs common.js + +do_newuser = function () { + var x = userpass(); + x.action = "newuser"; + status("Adding user '" + x.username + "'..."); + call_server(userctrl, x, "newuser", function (data) { + status("Added user '" + data.username + "'."); + }); +} + +do_chpass = function () { + var x = userpass(); + x.action = "chpass"; + x.newpass = getElement("newpassword").value; + status("Changing password..."); + call_server(userctrl, x, "chpass", function (data) { + getElement("password").value = getElement("newpassword").value; + getElement("newpassword").value = ""; + clearstatus(); + }); +} + +do_newproj = function() { + status("Creating project..."); + var args = {'project':getElement('newproject').value}; + args.action = "new_project"; + call_server('proj-ctrl.php', args, "newproj", function(data) { + window.location = "projects/" + data.name + "/admin.php"; + }); +} --- www/sidebar.php +++ www/sidebar.php @@ -0,0 +1,73 @@ + +
+
+ + You are .
+
+ + +
+ +
+ Username:
+
+ Password:
+
+ + +
+ +
+ + + +
+Project index
+
+ + Project info
+ Maintainer section
+ Browse source
+
+
+
+ + +
--- www/top.php +++ www/top.php @@ -0,0 +1,4 @@ + --- www/user.php +++ www/user.php @@ -0,0 +1,31 @@ + + + + + + +Project admin page for <?=$project?> + + + + + + + + + + + +
+ +
+Change password:
+
+ +
+ +
+ + --- www/viewmtn/.htaccess +++ www/viewmtn/.htaccess @@ -0,0 +1,7 @@ +AddHandler mod_python .psp +Options FollowSymLinks +PythonHandler wrapper +PythonInterpPerDirectory on +AddHandler mod_python .py +DirectoryIndex index.psp +PythonAutoReload On --- www/viewmtn/.mt-attrs +++ www/viewmtn/.mt-attrs @@ -0,0 +1,614 @@ + file "builtpython.sh" +execute "true" + + file "mimetypes/gnome-fs-directory.png" +manual_merge "true" + + file "mimetypes/gnome-library.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-magicpoint.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-msword.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-ogg.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-par.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-pdf.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-pgp-encrypted.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-pgp-keys.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-pgp.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-postscript.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-qif.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-rhythmbox-effect.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-rhythmbox-playlist.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-rtf.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-smil.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-vnd.lotus-1-2-3.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-vnd.ms-excel.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-vnd.ms-powerpoint.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-vnd.ms-word.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-vnd.stardivision.calc.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-vnd.stardivision.impress.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-vnd.stardivision.writer.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-vnd.sun.xml.calc.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-vnd.sun.xml.draw.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-vnd.sun.xml.impress.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-vnd.sun.xml.writer.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-vnd.sun.xml.writer.template.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-wordperfect.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-abiword.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-applix-spreadsheet.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-applix-word.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-archive.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-arj.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-bittorrent.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-bla.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-blender.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-blf.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-blv.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-bzip-compressed-tar.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-bzip.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-cd-image.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-class-file.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-compress.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-compressed-tar.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-core.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-cpio-compressed.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-cpio.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-dc-rom.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-deb.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-desktop.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-dia-diagram.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-dv.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-dvi.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-e-theme.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-executable.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-extension-nfo.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-extension-par2.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-font-afm.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-font-bdf.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-font-linux-psf.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-font-pcf.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-font-sunos-news.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-font-ttf.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-gameboy-rom.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-genesis-rom.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-glade.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-gnucash.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-gnumeric.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-gtktalog.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-gzip.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-ipod-firmware.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-jar.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-killustrator.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-kpresenter.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-kspread.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-kword.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-lha.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-lhz.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-mrproject.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-msx-rom.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-n64-rom.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-nes-rom.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-object.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-perl.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-php.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-python-bytecode.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-python.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-qw.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-rar.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-reject.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-rpm.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-ruby.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-sharedlib.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-shellscript.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-shockwave-flash.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-sms-rom.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-sql.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-stuffit.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-tar.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-tex.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-trash.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-x-x509-ca-cert.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application-zip.png" +manual_merge "true" + + file "mimetypes/gnome-mime-application.png" +manual_merge "true" + + file "mimetypes/gnome-mime-audio-ac3.png" +manual_merge "true" + + file "mimetypes/gnome-mime-audio-basic.png" +manual_merge "true" + + file "mimetypes/gnome-mime-audio-midi.png" +manual_merge "true" + + file "mimetypes/gnome-mime-audio-x-aiff.png" +manual_merge "true" + + file "mimetypes/gnome-mime-audio-x-it.png" +manual_merge "true" + + file "mimetypes/gnome-mime-audio-x-midi.png" +manual_merge "true" + + file "mimetypes/gnome-mime-audio-x-mod.png" +manual_merge "true" + + file "mimetypes/gnome-mime-audio-x-mp3.png" +manual_merge "true" + + file "mimetypes/gnome-mime-audio-x-s3m.png" +manual_merge "true" + + file "mimetypes/gnome-mime-audio-x-stm.png" +manual_merge "true" + + file "mimetypes/gnome-mime-audio-x-ulaw.png" +manual_merge "true" + + file "mimetypes/gnome-mime-audio-x-voc.png" +manual_merge "true" + + file "mimetypes/gnome-mime-audio-x-wav.png" +manual_merge "true" + + file "mimetypes/gnome-mime-audio-x-xi.png" +manual_merge "true" + + file "mimetypes/gnome-mime-audio-x-xm.png" +manual_merge "true" + + file "mimetypes/gnome-mime-audio.png" +manual_merge "true" + + file "mimetypes/gnome-mime-image-bmp.png" +manual_merge "true" + + file "mimetypes/gnome-mime-image-gif.png" +manual_merge "true" + + file "mimetypes/gnome-mime-image-jpeg.png" +manual_merge "true" + + file "mimetypes/gnome-mime-image-png.png" +manual_merge "true" + + file "mimetypes/gnome-mime-image-svg+xml.png" +manual_merge "true" + + file "mimetypes/gnome-mime-image-svg.png" +manual_merge "true" + + file "mimetypes/gnome-mime-image-tiff.png" +manual_merge "true" + + file "mimetypes/gnome-mime-image-wmf.png" +manual_merge "true" + + file "mimetypes/gnome-mime-image-x-3ds.png" +manual_merge "true" + + file "mimetypes/gnome-mime-image-x-applix-graphic.png" +manual_merge "true" + + file "mimetypes/gnome-mime-image-x-cmu-raster.png" +manual_merge "true" + + file "mimetypes/gnome-mime-image-x-lwo.png" +manual_merge "true" + + file "mimetypes/gnome-mime-image-x-lws.png" +manual_merge "true" + + file "mimetypes/gnome-mime-image-x-xcf.png" +manual_merge "true" + + file "mimetypes/gnome-mime-image.png" +manual_merge "true" + + file "mimetypes/gnome-mime-text-css.png" +manual_merge "true" + + file "mimetypes/gnome-mime-text-html.png" +manual_merge "true" + + file "mimetypes/gnome-mime-text-x-authors.png" +manual_merge "true" + + file "mimetypes/gnome-mime-text-x-c++src.png" +manual_merge "true" + + file "mimetypes/gnome-mime-text-x-c-header.png" +manual_merge "true" + + file "mimetypes/gnome-mime-text-x-c.png" +manual_merge "true" + + file "mimetypes/gnome-mime-text-x-chdr.png" +manual_merge "true" + + file "mimetypes/gnome-mime-text-x-copying.png" +manual_merge "true" + + file "mimetypes/gnome-mime-text-x-credits.png" +manual_merge "true" + + file "mimetypes/gnome-mime-text-x-csh.png" +manual_merge "true" + + file "mimetypes/gnome-mime-text-x-csharp.png" +manual_merge "true" + + file "mimetypes/gnome-mime-text-x-csrc.png" +manual_merge "true" + + file "mimetypes/gnome-mime-text-x-haskell.png" +manual_merge "true" + + file "mimetypes/gnome-mime-text-x-install.png" +manual_merge "true" + + file "mimetypes/gnome-mime-text-x-java.png" +manual_merge "true" + + file "mimetypes/gnome-mime-text-x-literate-haskell.png" +manual_merge "true" + + file "mimetypes/gnome-mime-text-x-lyx.png" +manual_merge "true" + + file "mimetypes/gnome-mime-text-x-makefile.png" +manual_merge "true" + + file "mimetypes/gnome-mime-text-x-objcsrc.png" +manual_merge "true" + + file "mimetypes/gnome-mime-text-x-patch.png" +manual_merge "true" + + file "mimetypes/gnome-mime-text-x-readme.png" +manual_merge "true" + + file "mimetypes/gnome-mime-text-x-scheme.png" +manual_merge "true" + + file "mimetypes/gnome-mime-text-x-sql.png" +manual_merge "true" + + file "mimetypes/gnome-mime-text-x-tex.png" +manual_merge "true" + + file "mimetypes/gnome-mime-text-x-troff-man.png" +manual_merge "true" + + file "mimetypes/gnome-mime-text-x-txt.png" +manual_merge "true" + + file "mimetypes/gnome-mime-text-x-vcalendar.png" +manual_merge "true" + + file "mimetypes/gnome-mime-text-x-vcard.png" +manual_merge "true" + + file "mimetypes/gnome-mime-text-x-zsh.png" +manual_merge "true" + + file "mimetypes/gnome-mime-text-xml.png" +manual_merge "true" + + file "mimetypes/gnome-mime-text.png" +manual_merge "true" + + file "mimetypes/gnome-mime-video-mpeg.png" +manual_merge "true" + + file "mimetypes/gnome-mime-video-quicktime.png" +manual_merge "true" + + file "mimetypes/gnome-mime-video-x-ms-asf.png" +manual_merge "true" + + file "mimetypes/gnome-mime-video-x-ms-wmv.png" +manual_merge "true" + + file "mimetypes/gnome-mime-video-x-msvideo.png" +manual_merge "true" + + file "mimetypes/gnome-mime-video.png" +manual_merge "true" + + file "mimetypes/gnome-mime-x-directory-nfs-server.png" +manual_merge "true" + + file "mimetypes/gnome-mime-x-directory-smb-server.png" +manual_merge "true" + + file "mimetypes/gnome-mime-x-directory-smb-share.png" +manual_merge "true" + + file "mimetypes/gnome-mime-x-directory-smb-workgroup.png" +manual_merge "true" + + file "mimetypes/gnome-mime-x-font-afm.png" +manual_merge "true" + + file "mimetypes/gnome-package.png" +manual_merge "true" + + file "mimetypes/openofficeorg-19-database.png" +manual_merge "true" + + file "mimetypes/openofficeorg-19-drawing-template.png" +manual_merge "true" + + file "mimetypes/openofficeorg-19-drawing.png" +manual_merge "true" + + file "mimetypes/openofficeorg-19-formula.png" +manual_merge "true" + + file "mimetypes/openofficeorg-19-master-document.png" +manual_merge "true" + + file "mimetypes/openofficeorg-19-oasis-database.png" +manual_merge "true" + + file "mimetypes/openofficeorg-19-oasis-drawing-template.png" +manual_merge "true" + + file "mimetypes/openofficeorg-19-oasis-drawing.png" +manual_merge "true" + + file "mimetypes/openofficeorg-19-oasis-formula.png" +manual_merge "true" + + file "mimetypes/openofficeorg-19-oasis-master-document.png" +manual_merge "true" + + file "mimetypes/openofficeorg-19-oasis-presentation-template.png" +manual_merge "true" + + file "mimetypes/openofficeorg-19-oasis-presentation.png" +manual_merge "true" + + file "mimetypes/openofficeorg-19-oasis-spreadsheet-template.png" +manual_merge "true" + + file "mimetypes/openofficeorg-19-oasis-spreadsheet.png" +manual_merge "true" + + file "mimetypes/openofficeorg-19-oasis-text-template.png" +manual_merge "true" + + file "mimetypes/openofficeorg-19-oasis-text.png" +manual_merge "true" + + file "mimetypes/openofficeorg-19-oasis-web-template.png" +manual_merge "true" + + file "mimetypes/openofficeorg-19-presentation-template.png" +manual_merge "true" + + file "mimetypes/openofficeorg-19-presentation.png" +manual_merge "true" + + file "mimetypes/openofficeorg-19-spreadsheet-template.png" +manual_merge "true" + + file "mimetypes/openofficeorg-19-spreadsheet.png" +manual_merge "true" + + file "mimetypes/openofficeorg-19-text-template.png" +manual_merge "true" + + file "mimetypes/openofficeorg-19-text.png" +manual_merge "true" + + file "rss_feed.gif" +manual_merge "true" + + file "tests/automate.py" +execute "true" --- www/viewmtn/AUTHORS +++ www/viewmtn/AUTHORS @@ -0,0 +1,10 @@ +Authors: +Grahame Bowland + +Contributors: +Matt Johnston +Nathaniel Smith +Bruce Stephens +Lapo Luchini +David Reiss + --- www/viewmtn/ChangeLog +++ www/viewmtn/ChangeLog @@ -0,0 +1,42 @@ +2005-11-21 Grahame Bowland + + * release 0.05 + * requires Monotone 0.23 or greater + * new file browser + * branch view showing recent changes, including RSS support + * source highlighting file view + * much heavier use of "monotone automate stdio"; significant + reduction in number of "monotone" processes created to service + requests. + * requires monotone 0.24; 0.23 will work, however you'll find + "monotone automate stdio" process tend to leak due to bugs in + monotone. + +2005-05-21 Grahame Bowland + + * release 0.03 + * fileinbranch.psp&branch=net.angrygoats.interapplet&path=ChangeLog + links to version of file in HEAD + * tarofbranch.psp&branch=net.angrygoats.interapplet + links to tar of HEAD manifest + * now works with monotone 0.19 + * branches and tags pages split + * user interface improved, help added + * ancestry graph direction fixed (thanks to Bruce Stephens) + * ancestry graph much improved (thanks to Emile Snyder) + * ancestry graph colours nodes using an algorithm nicked + from monotone-viz (with tweaks from Matt Johnston) + * abbreviate hashes, clean up linking code. + +2005-04-26 Grahame Bowland + + * release 0.02 + * fixed bug where monotone processes were left running as childs + of Apache. + * changed the ancestry to be a directed graph + * added blue highlight on target revision + +2005-04-25 Grahame Bowland + + * release 0.01 + --- www/viewmtn/LICENSE +++ www/viewmtn/LICENSE @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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. + + 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 this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. --- www/viewmtn/MT/log +++ www/viewmtn/MT/log @@ -0,0 +1,0 @@ --- www/viewmtn/MT/options +++ www/viewmtn/MT/options @@ -0,0 +1,4 @@ + branch "net.angrygoats.viewmtn" +database "/home/timothy/src/managed/mt.db" + key "" + keydir "" --- www/viewmtn/MT/revision +++ www/viewmtn/MT/revision @@ -0,0 +1,1 @@ +ecd514b665a66043eccdc296bf67bf263acff803 --- www/viewmtn/MochiKit/MochiKit.js +++ www/viewmtn/MochiKit/MochiKit.js @@ -0,0 +1,4123 @@ +/*** + + MochiKit.MochiKit 1.2 : PACKED VERSION + + THIS FILE IS AUTOMATICALLY GENERATED. If creating patches, please + diff against the source tree, not this file. + + See for documentation, downloads, license, etc. + + (c) 2005 Bob Ippolito. All rights Reserved. + +***/ + +if(typeof (dojo)!="undefined"){ +dojo.provide("MochiKit.Base"); +} +if(typeof (MochiKit)=="undefined"){ +MochiKit={}; +} +if(typeof (MochiKit.Base)=="undefined"){ +MochiKit.Base={}; +} +MochiKit.Base.VERSION="1.2"; +MochiKit.Base.NAME="MochiKit.Base"; +MochiKit.Base.update=function(_1,_2){ +if(_1==null){ +_1={}; +} +for(var i=1;i>b; +},zrshift:function(a,b){ +return a>>>b; +},eq:function(a,b){ +return a==b; +},ne:function(a,b){ +return a!=b; +},gt:function(a,b){ +return a>b; +},ge:function(a,b){ +return a>=b; +},lt:function(a,b){ +return al){ +_37=l; +} +} +_36=[]; +for(i=0;i<_37;i++){ +var _38=[]; +for(var j=1;j0){ +_51=m.concat(me.im_preargs,_51); +} +var _46=me.im_self; +if(!_46){ +_46=this; +} +return me.im_func.apply(_46,_51); +}; +_50.im_self=_49; +_50.im_func=_47; +_50.im_preargs=_48; +return _50; +},bindMethods:function(_52){ +var _53=MochiKit.Base.bind; +for(var k in _52){ +var _54=_52[k]; +if(typeof (_54)=="function"){ +_52[k]=_53(_54,_52); +} +} +},registerComparator:function(_55,_56,_57,_58){ +MochiKit.Base.comparatorRegistry.register(_55,_56,_57,_58); +},_primitives:{"bool":true,"string":true,"number":true},compare:function(a,b){ +if(a==b){ +return 0; +} +var _59=(typeof (a)=="undefined"||a==null); +var _60=(typeof (b)=="undefined"||b==null); +if(_59&&_60){ +return 0; +}else{ +if(_59){ +return -1; +}else{ +if(_60){ +return 1; +} +} +} +var m=MochiKit.Base; +var _61=m._primitives; +if(!(typeof (a) in _61&&typeof (b) in _61)){ +try{ +return m.comparatorRegistry.match(a,b); +} +catch(e){ +if(e!=m.NotFound){ +throw e; +} +} +} +if(ab){ +return 1; +} +} +var _62=m.repr; +throw new TypeError(_62(a)+" and "+_62(b)+" can not be compared"); +},compareDateLike:function(a,b){ +return MochiKit.Base.compare(a.getTime(),b.getTime()); +},compareArrayLike:function(a,b){ +var _63=MochiKit.Base.compare; +var _64=a.length; +var _65=0; +if(_64>b.length){ +_65=1; +_64=b.length; +}else{ +if(_640))){ +var kv=MochiKit.DOM.formContents(_103); +_103=kv[0]; +_104=kv[1]; +}else{ +if(arguments.length==1){ +var o=_103; +_103=[]; +_104=[]; +for(var k in o){ +var v=o[k]; +if(typeof (v)!="function"){ +_103.push(k); +_104.push(v); +} +} +} +} +var rval=[]; +var len=Math.min(_103.length,_104.length); +var _108=MochiKit.Base.urlEncode; +for(var i=0;i=stop){ +throw self.StopIteration; +} +_137+=step; +return rval; +}}; +},imap:function(fun,p,q){ +var m=MochiKit.Base; +var self=MochiKit.Iter; +var _141=m.map(self.iter,m.extend(null,arguments,1)); +var map=m.map; +var next=self.next; +return {repr:function(){ +return "imap(...)"; +},toString:m.forward("repr"),next:function(){ +return fun.apply(this,map(next,_141)); +}}; +},applymap:function(fun,seq,self){ +seq=MochiKit.Iter.iter(seq); +var m=MochiKit.Base; +return {repr:function(){ +return "applymap(...)"; +},toString:m.forward("repr"),next:function(){ +return fun.apply(self,seq.next()); +}}; +},chain:function(p,q){ +var self=MochiKit.Iter; +var m=MochiKit.Base; +if(arguments.length==1){ +return self.iter(arguments[0]); +} +var _143=m.map(self.iter,arguments); +return {repr:function(){ +return "chain(...)"; +},toString:m.forward("repr"),next:function(){ +while(_143.length>1){ +try{ +return _143[0].next(); +} +catch(e){ +if(e!=self.StopIteration){ +throw e; +} +_143.shift(); +} +} +if(_143.length==1){ +var arg=_143.shift(); +this.next=m.bind(arg.next,arg); +return this.next(); +} +throw self.StopIteration; +}}; +},takewhile:function(pred,seq){ +var self=MochiKit.Iter; +seq=self.iter(seq); +return {repr:function(){ +return "takewhile(...)"; +},toString:MochiKit.Base.forward("repr"),next:function(){ +var rval=seq.next(); +if(!pred(rval)){ +this.next=function(){ +throw self.StopIteration; +}; +this.next(); +} +return rval; +}}; +},dropwhile:function(pred,seq){ +seq=MochiKit.Iter.iter(seq); +var m=MochiKit.Base; +var bind=m.bind; +return {"repr":function(){ +return "dropwhile(...)"; +},"toString":m.forward("repr"),"next":function(){ +while(true){ +var rval=seq.next(); +if(!pred(rval)){ +break; +} +} +this.next=bind(seq.next,seq); +return rval; +}}; +},_tee:function(_145,sync,_147){ +sync.pos[_145]=-1; +var m=MochiKit.Base; +var _148=m.listMin; +return {repr:function(){ +return "tee("+_145+", ...)"; +},toString:m.forward("repr"),next:function(){ +var rval; +var i=sync.pos[_145]; +if(i==sync.max){ +rval=_147.next(); +sync.deque.push(rval); +sync.max+=1; +sync.pos[_145]+=1; +}else{ +rval=sync.deque[i-sync.min]; +sync.pos[_145]+=1; +if(i==sync.min&&_148(sync.pos)!=sync.min){ +sync.min+=1; +sync.deque.shift(); +} +} +return rval; +}}; +},tee:function(_149,n){ +var rval=[]; +var sync={"pos":[],"deque":[],"max":-1,"min":-1}; +if(arguments.length==1){ +n=2; +} +var self=MochiKit.Iter; +_149=self.iter(_149); +var _tee=self._tee; +for(var i=0;i0&&_155>=stop)||(step<0&&_155<=stop)){ +throw MochiKit.Iter.StopIteration; +} +var rval=_155; +_155+=step; +return rval; +},repr:function(){ +return "range("+[_155,stop,step].join(", ")+")"; +},toString:MochiKit.Base.forward("repr")}; +},sum:function(_156,_157){ +var x=_157||0; +var self=MochiKit.Iter; +_156=self.iter(_156); +try{ +while(true){ +x+=_156.next(); +} +} +catch(e){ +if(e!=self.StopIteration){ +throw e; +} +} +return x; +},exhaust:function(_158){ +var self=MochiKit.Iter; +_158=self.iter(_158); +try{ +while(true){ +_158.next(); +} +} +catch(e){ +if(e!=self.StopIteration){ +throw e; +} +} +},forEach:function(_159,func,self){ +var m=MochiKit.Base; +if(arguments.length>2){ +func=m.bind(func,self); +} +if(m.isArrayLike(_159)){ +for(var i=0;i<_159.length;i++){ +func(_159[i]); +} +}else{ +self=MochiKit.Iter; +self.exhaust(self.imap(func,_159)); +} +},every:function(_161,func){ +var self=MochiKit.Iter; +try{ +self.ifilterfalse(func,_161).next(); +return false; +} +catch(e){ +if(e!=self.StopIteration){ +throw e; +} +return true; +} +},sorted:function(_162,cmp){ +var rval=MochiKit.Iter.list(_162); +if(arguments.length==1){ +cmp=MochiKit.Base.compare; +} +rval.sort(cmp); +return rval; +},reversed:function(_163){ +var rval=MochiKit.Iter.list(_163); +rval.reverse(); +return rval; +},some:function(_164,func){ +var self=MochiKit.Iter; +try{ +self.ifilter(func,_164).next(); +return true; +} +catch(e){ +if(e!=self.StopIteration){ +throw e; +} +return false; +} +},iextend:function(lst,_165){ +if(MochiKit.Base.isArrayLike(_165)){ +for(var i=0;i<_165.length;i++){ +lst.push(_165[i]); +} +}else{ +var self=MochiKit.Iter; +_165=self.iter(_165); +try{ +while(true){ +lst.push(_165.next()); +} +} +catch(e){ +if(e!=self.StopIteration){ +throw e; +} +} +} +return lst; +},groupby:function(_166,_167){ +var m=MochiKit.Base; +var self=MochiKit.Iter; +if(arguments.length<2){ +_167=m.operator.identity; +} +_166=self.iter(_166); +var pk=undefined; +var k=undefined; +var v; +function fetch(){ +v=_166.next(); +k=_167(v); +} +function eat(){ +var ret=v; +v=undefined; +return ret; +} +var _170=true; +return {repr:function(){ +return "groupby(...)"; +},next:function(){ +while(k==pk){ +fetch(); +if(_170){ +_170=false; +break; +} +} +pk=k; +return [k,{next:function(){ +if(v==undefined){ +fetch(); +} +if(k!=pk){ +throw self.StopIteration; +} +return eat(); +}}]; +}}; +},groupby_as_array:function(_171,_172){ +var m=MochiKit.Base; +var self=MochiKit.Iter; +if(arguments.length<2){ +_172=m.operator.identity; +} +_171=self.iter(_171); +var _173=[]; +var _174=true; +var _175; +while(true){ +try{ +var _176=_171.next(); +var key=_172(_176); +} +catch(e){ +if(e==self.StopIteration){ +break; +} +throw e; +} +if(_174||key!=_175){ +var _177=[]; +_173.push([key,_177]); +} +_177.push(_176); +_174=false; +_175=key; +} +return _173; +},arrayLikeIter:function(_178){ +var i=0; +return {repr:function(){ +return "arrayLikeIter(...)"; +},toString:MochiKit.Base.forward("repr"),next:function(){ +if(i>=_178.length){ +throw MochiKit.Iter.StopIteration; +} +return _178[i++]; +}}; +},hasIterateNext:function(_179){ +return (_179&&typeof (_179.iterateNext)=="function"); +},iterateNextIter:function(_180){ +return {repr:function(){ +return "iterateNextIter(...)"; +},toString:MochiKit.Base.forward("repr"),next:function(){ +var rval=_180.iterateNext(); +if(rval===null||rval===undefined){ +throw MochiKit.Iter.StopIteration; +} +return rval; +}}; +}}); +MochiKit.Iter.EXPORT_OK=["iteratorRegistry","arrayLikeIter","hasIterateNext","iterateNextIter",]; +MochiKit.Iter.EXPORT=["StopIteration","registerIteratorFactory","iter","count","cycle","repeat","next","izip","ifilter","ifilterfalse","islice","imap","applymap","chain","takewhile","dropwhile","tee","list","reduce","range","sum","exhaust","forEach","every","sorted","reversed","some","iextend","groupby","groupby_as_array"]; +MochiKit.Iter.__new__=function(){ +var m=MochiKit.Base; +this.StopIteration=new m.NamedError("StopIteration"); +this.iteratorRegistry=new m.AdapterRegistry(); +this.registerIteratorFactory("arrayLike",m.isArrayLike,this.arrayLikeIter); +this.registerIteratorFactory("iterateNext",this.hasIterateNext,this.iterateNextIter); +this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)}; +m.nameFunctions(this); +}; +MochiKit.Iter.__new__(); +reduce=MochiKit.Iter.reduce; +MochiKit.Base._exportSymbols(this,MochiKit.Iter); +if(typeof (dojo)!="undefined"){ +dojo.provide("MochiKit.Logging"); +dojo.require("MochiKit.Base"); +} +if(typeof (JSAN)!="undefined"){ +JSAN.use("MochiKit.Base",[]); +} +try{ +if(typeof (MochiKit.Base)=="undefined"){ +throw ""; +} +} +catch(e){ +throw "MochiKit.Logging depends on MochiKit.Base!"; +} +if(typeof (MochiKit.Logging)=="undefined"){ +MochiKit.Logging={}; +} +MochiKit.Logging.NAME="MochiKit.Logging"; +MochiKit.Logging.VERSION="1.2"; +MochiKit.Logging.__repr__=function(){ +return "["+this.NAME+" "+this.VERSION+"]"; +}; +MochiKit.Logging.toString=function(){ +return this.__repr__(); +}; +MochiKit.Logging.EXPORT=["LogLevel","LogMessage","Logger","alertListener","logger","log","logError","logDebug","logFatal","logWarning"]; +MochiKit.Logging.EXPORT_OK=["logLevelAtLeast","isLogMessage","compareLogMessage"]; +MochiKit.Logging.LogMessage=function(num,_182,info){ +this.num=num; +this.level=_182; +this.info=info; +this.timestamp=new Date(); +}; +MochiKit.Logging.LogMessage.prototype={repr:function(){ +var m=MochiKit.Base; +return "LogMessage("+m.map(m.repr,[this.num,this.level,this.info]).join(", ")+")"; +},toString:MochiKit.Base.forward("repr")}; +MochiKit.Base.update(MochiKit.Logging,{logLevelAtLeast:function(_184){ +var self=MochiKit.Logging; +if(typeof (_184)=="string"){ +_184=self.LogLevel[_184]; +} +return function(msg){ +var _186=msg.level; +if(typeof (_186)=="string"){ +_186=self.LogLevel[_186]; +} +return _186>=_184; +}; +},isLogMessage:function(){ +var _187=MochiKit.Logging.LogMessage; +for(var i=0;i=0&&this._messages.length>this.maxSize){ +this._messges.shift(); +} +},getMessages:function(_196){ +var _197=0; +if(!(typeof (_196)=="undefined"||_196==null)){ +_197=Math.max(0,this._messages.length-_196); +} +return this._messages.slice(_197); +},getMessageText:function(_198){ +if(typeof (_198)=="undefined"||_198==null){ +_198=30; +} +var _199=this.getMessages(_198); +if(_199.length){ +var lst=map(function(m){ +return "\n ["+m.num+"] "+m.level+": "+m.info.join(" "); +},_199); +lst.unshift("LAST "+_199.length+" MESSAGES:"); +return lst.join(""); +} +return ""; +},debuggingBookmarklet:function(_200){ +if(typeof (MochiKit.LoggingPane)=="undefined"){ +alert(this.getMessageText()); +}else{ +MochiKit.LoggingPane.createLoggingPane(_200||false); +} +}}; +MochiKit.Logging.__new__=function(){ +this.LogLevel={ERROR:40,FATAL:50,WARNING:30,INFO:20,DEBUG:10}; +var m=MochiKit.Base; +m.registerComparator("LogMessage",this.isLogMessage,this.compareLogMessage); +var _201=m.partial; +var _202=this.Logger; +var _203=_202.prototype.baseLog; +m.update(this.Logger.prototype,{debug:_201(_203,"DEBUG"),log:_201(_203,"INFO"),error:_201(_203,"ERROR"),fatal:_201(_203,"FATAL"),warning:_201(_203,"WARNING")}); +var self=this; +var _204=function(name){ +return function(){ +self.logger[name].apply(self.logger,arguments); +}; +}; +this.log=_204("log"); +this.logError=_204("error"); +this.logDebug=_204("debug"); +this.logFatal=_204("fatal"); +this.logWarning=_204("warning"); +this.logger=new _202(); +this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)}; +m.nameFunctions(this); +}; +MochiKit.Logging.__new__(); +MochiKit.Base._exportSymbols(this,MochiKit.Logging); +if(typeof (dojo)!="undefined"){ +dojo.provide("MochiKit.DateTime"); +} +if(typeof (MochiKit)=="undefined"){ +MochiKit={}; +} +if(typeof (MochiKit.DateTime)=="undefined"){ +MochiKit.DateTime={}; +} +MochiKit.DateTime.NAME="MochiKit.DateTime"; +MochiKit.DateTime.VERSION="1.2"; +MochiKit.DateTime.__repr__=function(){ +return "["+this.NAME+" "+this.VERSION+"]"; +}; +MochiKit.DateTime.toString=function(){ +return this.__repr__(); +}; +MochiKit.DateTime.isoDate=function(str){ +str=str+""; +if(typeof (str)!="string"||str.length==0){ +return null; +} +var iso=str.split("-"); +if(iso.length==0){ +return null; +} +return new Date(iso[0],iso[1]-1,iso[2]); +}; +MochiKit.DateTime._isoRegexp=/(\d{4,})(?:-(\d{1,2})(?:-(\d{1,2})(?:[T ](\d{1,2}):(\d{1,2})(?::(\d{1,2})(?:\.(\d+))?)?(?:(Z)|([+-])(\d{1,2})(?::(\d{1,2}))?)?)?)?)?/; +MochiKit.DateTime.isoTimestamp=function(str){ +str=str+""; +if(typeof (str)!="string"||str.length==0){ +return null; +} +var res=str.match(MochiKit.DateTime._isoRegexp); +if(typeof (res)=="undefined"||res==null){ +return null; +} +var year,month,day,hour,min,sec,msec; +year=parseInt(res[1],10); +if(typeof (res[2])=="undefined"||res[2]==""){ +return new Date(year); +} +month=parseInt(res[2],10)-1; +day=parseInt(res[3],10); +if(typeof (res[4])=="undefined"||res[4]==""){ +return new Date(year,month,day); +} +hour=parseInt(res[4],10); +min=parseInt(res[5],10); +sec=(typeof (res[6])!="undefined"&&res[6]!="")?parseInt(res[6],10):0; +if(typeof (res[7])!="undefined"&&res[7]!=""){ +msec=Math.round(1000*parseFloat("0."+res[7])); +}else{ +msec=0; +} +if((typeof (res[8])=="undefined"||res[8]=="")&&(typeof (res[9])=="undefined"||res[9]=="")){ +return new Date(year,month,day,hour,min,sec,msec); +} +var ofs; +if(typeof (res[9])!="undefined"&&res[9]!=""){ +ofs=parseInt(res[10],10)*3600000; +if(typeof (res[11])!="undefined"&&res[11]!=""){ +ofs+=parseInt(res[11],10)*60000; +} +if(res[9]=="-"){ +ofs=-ofs; +} +}else{ +ofs=0; +} +return new Date(Date.UTC(year,month,day,hour,min,sec,msec)-ofs); +}; +MochiKit.DateTime.toISOTime=function(date,_210){ +if(typeof (date)=="undefined"||date==null){ +return null; +} +var hh=date.getHours(); +var mm=date.getMinutes(); +var ss=date.getSeconds(); +var lst=[((_210&&(hh<10))?"0"+hh:hh),((mm<10)?"0"+mm:mm),((ss<10)?"0"+ss:ss)]; +return lst.join(":"); +}; +MochiKit.DateTime.toISOTimestamp=function(date,_214){ +if(typeof (date)=="undefined"||date==null){ +return null; +} +var sep=_214?"T":" "; +var foot=_214?"Z":""; +if(_214){ +date=new Date(date.getTime()+(date.getTimezoneOffset()*60000)); +} +return MochiKit.DateTime.toISODate(date)+sep+MochiKit.DateTime.toISOTime(date,_214)+foot; +}; +MochiKit.DateTime.toISODate=function(date){ +if(typeof (date)=="undefined"||date==null){ +return null; +} +var _217=MochiKit.DateTime._padTwo; +return [date.getFullYear(),_217(date.getMonth()+1),_217(date.getDate())].join("-"); +}; +MochiKit.DateTime.americanDate=function(d){ +d=d+""; +if(typeof (d)!="string"||d.length==0){ +return null; +} +var a=d.split("/"); +return new Date(a[2],a[0]-1,a[1]); +}; +MochiKit.DateTime._padTwo=function(n){ +return (n>9)?n:"0"+n; +}; +MochiKit.DateTime.toPaddedAmericanDate=function(d){ +if(typeof (d)=="undefined"||d==null){ +return null; +} +var _219=MochiKit.DateTime._padTwo; +return [_219(d.getMonth()+1),_219(d.getDate()),d.getFullYear()].join("/"); +}; +MochiKit.DateTime.toAmericanDate=function(d){ +if(typeof (d)=="undefined"||d==null){ +return null; +} +return [d.getMonth()+1,d.getDate(),d.getFullYear()].join("/"); +}; +MochiKit.DateTime.EXPORT=["isoDate","isoTimestamp","toISOTime","toISOTimestamp","toISODate","americanDate","toPaddedAmericanDate","toAmericanDate"]; +MochiKit.DateTime.EXPORT_OK=[]; +MochiKit.DateTime.EXPORT_TAGS={":common":MochiKit.DateTime.EXPORT,":all":MochiKit.DateTime.EXPORT}; +MochiKit.DateTime.__new__=function(){ +var base=this.NAME+"."; +for(var k in this){ +var o=this[k]; +if(typeof (o)=="function"&&typeof (o.NAME)=="undefined"){ +try{ +o.NAME=base+k; +} +catch(e){ +} +} +} +}; +MochiKit.DateTime.__new__(); +(function(_220,_221){ +if((typeof (JSAN)=="undefined"&&typeof (dojo)=="undefined")||(typeof (MochiKit.__compat__)=="boolean"&&MochiKit.__compat__)){ +var all=_221.EXPORT_TAGS[":all"]; +for(var i=0;i_229){ +var i=_235.length-_229; +res=fmt.separator+_235.substring(i,_235.length)+res; +_235=_235.substring(0,i); +} +} +res=_235+res; +if(_227>0){ +while(frac.length<_230){ +frac=frac+"0"; +} +res=res+fmt.decimal+frac; +} +return _231+res+_232; +}; +}; +MochiKit.Format.numberFormatter=function(_237,_238,_239){ +if(typeof (_238)=="undefined"){ +_238=""; +} +var _240=_237.match(/((?:[0#]+,)?[0#]+)(?:\.([0#]+))?(%)?/); +if(!_240){ +throw TypeError("Invalid pattern"); +} +var _241=_237.substr(0,_240.index); +var _242=_237.substr(_240.index+_240[0].length); +if(_241.search(/-/)==-1){ +_241=_241+"-"; +} +var _243=_240[1]; +var frac=(typeof (_240[2])=="string"&&_240[2]!="")?_240[2]:""; +var _244=(typeof (_240[3])=="string"&&_240[3]!=""); +var tmp=_243.split(/,/); +var _246; +if(typeof (_239)=="undefined"){ +_239="default"; +} +if(tmp.length==1){ +_246=null; +}else{ +_246=tmp[1].length; +} +var _247=_243.length-_243.replace(/0/g,"").length; +var _248=frac.length-frac.replace(/0/g,"").length; +var _249=frac.length; +var rval=MochiKit.Format._numberFormatter(_238,_241,_242,_239,_244,_249,_247,_246,_248); +var m=MochiKit.Base; +if(m){ +var fn=arguments.callee; +var args=m.concat(arguments); +rval.repr=function(){ +return [self.NAME,"(",map(m.repr,args).join(", "),")"].join(""); +}; +} +return rval; +}; +MochiKit.Format.formatLocale=function(_251){ +if(typeof (_251)=="undefined"||_251==null){ +_251="default"; +} +if(typeof (_251)=="string"){ +var rval=MochiKit.Format.LOCALE[_251]; +if(typeof (rval)=="string"){ +rval=arguments.callee(rval); +MochiKit.Format.LOCALE[_251]=rval; +} +return rval; +}else{ +return _251; +} +}; +MochiKit.Format.twoDigitAverage=function(_252,_253){ +if(_253){ +var res=_252/_253; +if(!isNaN(res)){ +return MochiKit.Format.twoDigitFloat(_252/_253); +} +} +return "0"; +}; +MochiKit.Format.twoDigitFloat=function(_254){ +var sign=(_254<0?"-":""); +var s=Math.floor(Math.abs(_254)*100).toString(); +if(s=="0"){ +return s; +} +if(s.length<3){ +while(s.charAt(s.length-1)=="0"){ +s=s.substring(0,s.length-1); +} +return sign+"0."+s; +} +var head=sign+s.substring(0,s.length-2); +var tail=s.substring(s.length-2,s.length); +if(tail=="00"){ +return head; +}else{ +if(tail.charAt(1)=="0"){ +return head+"."+tail.charAt(0); +}else{ +return head+"."+tail; +} +} +}; +MochiKit.Format.lstrip=function(str,_259){ +str=str+""; +if(typeof (str)!="string"){ +return null; +} +if(!_259){ +return str.replace(/^\s+/,""); +}else{ +return str.replace(new RegExp("^["+_259+"]+"),""); +} +}; +MochiKit.Format.rstrip=function(str,_260){ +str=str+""; +if(typeof (str)!="string"){ +return null; +} +if(!_260){ +return str.replace(/\s+$/,""); +}else{ +return str.replace(new RegExp("["+_260+"]+$"),""); +} +}; +MochiKit.Format.strip=function(str,_261){ +var self=MochiKit.Format; +return self.rstrip(self.lstrip(str,_261),_261); +}; +MochiKit.Format.truncToFixed=function(_262,_263){ +_262=Math.floor(_262*Math.pow(10,_263)); +var res=(_262*Math.pow(10,-_263)).toFixed(_263); +if(res.charAt(0)=="."){ +res="0"+res; +} +return res; +}; +MochiKit.Format.roundToFixed=function(_264,_265){ +return MochiKit.Format.truncToFixed(_264+0.5*Math.pow(10,-_265),_265); +}; +MochiKit.Format.percentFormat=function(_266){ +return MochiKit.Format.twoDigitFloat(100*_266)+"%"; +}; +MochiKit.Format.EXPORT=["truncToFixed","roundToFixed","numberFormatter","formatLocale","twoDigitAverage","twoDigitFloat","percentFormat","lstrip","rstrip","strip"]; +MochiKit.Format.LOCALE={en_US:{separator:",",decimal:".",percent:"%"},de_DE:{separator:".",decimal:",",percent:"%"},fr_FR:{separator:" ",decimal:",",percent:"%"},"default":"en_US"}; +MochiKit.Format.EXPORT_OK=[]; +MochiKit.Format.EXPORT_TAGS={":all":MochiKit.Format.EXPORT,":common":MochiKit.Format.EXPORT}; +MochiKit.Format.__new__=function(){ +var base=this.NAME+"."; +var k,v,o; +for(k in this.LOCALE){ +o=this.LOCALE[k]; +if(typeof (o)=="object"){ +o.repr=function(){ +return this.NAME; +}; +o.NAME=base+"LOCALE."+k; +} +} +for(k in this){ +o=this[k]; +if(typeof (o)=="function"&&typeof (o.NAME)=="undefined"){ +try{ +o.NAME=base+k; +} +catch(e){ +} +} +} +}; +MochiKit.Format.__new__(); +(function(_267,_268){ +if((typeof (JSAN)=="undefined"&&typeof (dojo)=="undefined")||(typeof (MochiKit.__compat__)=="boolean"&&MochiKit.__compat__)){ +var all=_268.EXPORT_TAGS[":all"]; +for(var i=0;i=0)){ +this._fire(); +} +},_continue:function(res){ +this._resback(res); +this._unpause(); +},_resback:function(res){ +this.fired=((res instanceof Error)?1:0); +this.results[this.fired]=res; +this._fire(); +},_check:function(){ +if(this.fired!=-1){ +if(!this.silentlyCancelled){ +throw new MochiKit.Async.AlreadyCalledError(this); +} +this.silentlyCancelled=false; +return; +} +},callback:function(res){ +this._check(); +this._resback(res); +},errback:function(res){ +this._check(); +if(!(res instanceof Error)){ +res=new MochiKit.Async.GenericError(res); +} +this._resback(res); +},addBoth:function(fn){ +if(arguments.length>1){ +fn=MochiKit.Base.partial.apply(null,arguments); +} +return this.addCallbacks(fn,fn); +},addCallback:function(fn){ +if(arguments.length>1){ +fn=MochiKit.Base.partial.apply(null,arguments); +} +return this.addCallbacks(fn,null); +},addErrback:function(fn){ +if(arguments.length>1){ +fn=MochiKit.Base.partial.apply(null,arguments); +} +return this.addCallbacks(null,fn); +},addCallbacks:function(cb,eb){ +this.chain.push([cb,eb]); +if(this.fired>=0){ +this._fire(); +} +return this; +},_fire:function(){ +var _273=this.chain; +var _274=this.fired; +var res=this.results[_274]; +var self=this; +var cb=null; +while(_273.length>0&&this.paused==0){ +var pair=_273.shift(); +var f=pair[_274]; +if(f==null){ +continue; +} +try{ +res=f(res); +_274=((res instanceof Error)?1:0); +if(res instanceof MochiKit.Async.Deferred){ +cb=function(res){ +self._continue(res); +}; +this._pause(); +} +} +catch(err){ +_274=1; +res=err; +} +} +this.fired=_274; +this.results[_274]=res; +if(cb&&this.paused){ +res.addBoth(cb); +} +}}; +MochiKit.Base.update(MochiKit.Async,{evalJSONRequest:function(){ +return eval("("+arguments[0].responseText+")"); +},succeed:function(_276){ +var d=new MochiKit.Async.Deferred(); +d.callback.apply(d,arguments); +return d; +},fail:function(_277){ +var d=new MochiKit.Async.Deferred(); +d.errback.apply(d,arguments); +return d; +},getXMLHttpRequest:function(){ +var self=arguments.callee; +if(!self.XMLHttpRequest){ +var _278=[function(){ +return new XMLHttpRequest(); +},function(){ +return new ActiveXObject("Msxml2.XMLHTTP"); +},function(){ +return new ActiveXObject("Microsoft.XMLHTTP"); +},function(){ +return new ActiveXObject("Msxml2.XMLHTTP.4.0"); +},function(){ +throw new MochiKit.Async.BrowserComplianceError("Browser does not support XMLHttpRequest"); +}]; +for(var i=0;i<_278.length;i++){ +var func=_278[i]; +try{ +self.XMLHttpRequest=func; +return func(); +} +catch(e){ +} +} +} +return self.XMLHttpRequest(); +},sendXMLHttpRequest:function(req,_280){ +if(typeof (_280)=="undefined"){ +_280=null; +} +var _281=function(){ +try{ +req.onreadystatechange=null; +} +catch(e){ +try{ +req.onreadystatechange=function(){ +}; +} +catch(e){ +} +} +req.abort(); +}; +var self=MochiKit.Async; +var d=new self.Deferred(_281); +var _282=function(){ +if(req.readyState==4){ +try{ +req.onreadystatechange=null; +} +catch(e){ +try{ +req.onreadystatechange=function(){ +}; +} +catch(e){ +} +} +var _283=null; +try{ +_283=req.status; +if(!_283&&MochiKit.Base.isNotEmpty(req.responseText)){ +_283=304; +} +} +catch(e){ +} +if(_283==200||_283==304){ +d.callback(req); +}else{ +var err=new self.XMLHttpRequestError(req,"Request failed"); +if(err.number){ +d.errback(err); +}else{ +d.errback(err); +} +} +} +}; +try{ +req.onreadystatechange=_282; +req.send(_280); +} +catch(e){ +try{ +req.onreadystatechange=null; +} +catch(ignore){ +} +d.errback(e); +} +return d; +},doSimpleXMLHttpRequest:function(url){ +var self=MochiKit.Async; +var req=self.getXMLHttpRequest(); +if(arguments.length>1){ +var m=MochiKit.Base; +var qs=m.queryString.apply(null,m.extend(null,arguments,1)); +if(qs){ +url+="?"+qs; +} +} +req.open("GET",url,true); +return self.sendXMLHttpRequest(req); +},loadJSONDoc:function(url){ +var self=MochiKit.Async; +var d=self.doSimpleXMLHttpRequest.apply(self,arguments); +d=d.addCallback(self.evalJSONRequest); +return d; +},wait:function(_287,_288){ +var d=new MochiKit.Async.Deferred(); +var m=MochiKit.Base; +if(typeof (_288)!="undefined"){ +d.addCallback(function(){ +return _288; +}); +} +var _289=setTimeout(m.bind(d.callback,d),Math.floor(_287*1000)); +d.canceller=function(){ +try{ +clearTimeout(_289); +} +catch(e){ +} +}; +return d; +},callLater:function(_290,func){ +var m=MochiKit.Base; +var _291=m.partial.apply(m,m.extend(null,arguments,1)); +return MochiKit.Async.wait(_290).addCallback(function(res){ +return _291(); +}); +}}); +MochiKit.Async.DeferredLock=function(){ +this.waiting=[]; +this.locked=false; +this.id=this._nextId(); +}; +MochiKit.Async.DeferredLock.prototype={__class__:MochiKit.Async.DeferredLock,acquire:function(){ +d=new MochiKit.Async.Deferred(); +if(this.locked){ +this.waiting.push(d); +}else{ +this.locked=true; +d.callback(this); +} +return d; +},release:function(){ +if(!this.locked){ +throw TypeError("Tried to release an unlocked DeferredLock"); +} +this.locked=false; +if(this.waiting.length>0){ +this.locked=true; +this.waiting.shift().callback(this); +} +},_nextId:MochiKit.Base.counter(),repr:function(){ +var _292; +if(this.locked){ +_292="locked, "+this.waiting.length+" waiting"; +}else{ +_292="unlocked"; +} +return "DeferredLock("+this.id+", "+_292+")"; +},toString:MochiKit.Base.forward("repr")}; +MochiKit.Async.EXPORT=["AlreadyCalledError","CancelledError","BrowserComplianceError","GenericError","XMLHttpRequestError","Deferred","succeed","fail","getXMLHttpRequest","doSimpleXMLHttpRequest","loadJSONDoc","wait","callLater","sendXMLHttpRequest","DeferredLock"]; +MochiKit.Async.EXPORT_OK=["evalJSONRequest"]; +MochiKit.Async.__new__=function(){ +var m=MochiKit.Base; +var ne=m.partial(m._newNamedError,this); +ne("AlreadyCalledError",function(_294){ +this.deferred=_294; +}); +ne("CancelledError",function(_295){ +this.deferred=_295; +}); +ne("BrowserComplianceError",function(msg){ +this.message=msg; +}); +ne("GenericError",function(msg){ +this.message=msg; +}); +ne("XMLHttpRequestError",function(req,msg){ +this.req=req; +this.message=msg; +try{ +this.number=req.status; +} +catch(e){ +} +}); +this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)}; +m.nameFunctions(this); +}; +MochiKit.Async.__new__(); +MochiKit.Base._exportSymbols(this,MochiKit.Async); +if(typeof (dojo)!="undefined"){ +dojo.provide("MochiKit.DOM"); +dojo.require("MochiKit.Iter"); +} +if(typeof (JSAN)!="undefined"){ +JSAN.use("MochiKit.Iter",[]); +} +try{ +if(typeof (MochiKit.Iter)=="undefined"){ +throw ""; +} +} +catch(e){ +throw "MochiKit.DOM depends on MochiKit.Iter!"; +} +if(typeof (MochiKit.DOM)=="undefined"){ +MochiKit.DOM={}; +} +MochiKit.DOM.NAME="MochiKit.DOM"; +MochiKit.DOM.VERSION="1.2"; +MochiKit.DOM.__repr__=function(){ +return "["+this.NAME+" "+this.VERSION+"]"; +}; +MochiKit.DOM.toString=function(){ +return this.__repr__(); +}; +MochiKit.DOM.EXPORT=["formContents","currentWindow","currentDocument","withWindow","withDocument","registerDOMConverter","coerceToDOM","createDOM","createDOMFunc","getNodeAttribute","setNodeAttribute","updateNodeAttributes","appendChildNodes","replaceChildNodes","removeElement","swapDOM","BUTTON","TT","PRE","H1","H2","H3","BR","CANVAS","HR","LABEL","TEXTAREA","FORM","STRONG","SELECT","OPTION","OPTGROUP","LEGEND","FIELDSET","P","UL","OL","LI","TD","TR","THEAD","TBODY","TFOOT","TABLE","TH","INPUT","SPAN","A","DIV","IMG","getElement","$","computedStyle","getElementsByTagAndClassName","addToCallStack","addLoadEvent","focusOnLoad","setElementClass","toggleElementClass","addElementClass","removeElementClass","swapElementClass","hasElementClass","escapeHTML","toHTML","emitHTML","setDisplayForElement","hideElement","showElement","scrapeText","elementPosition"]; +MochiKit.DOM.EXPORT_OK=["domConverters"]; +MochiKit.DOM.Coordinates=function(x,y){ +this.x=x; +this.y=y; +}; +MochiKit.DOM.Coordinates.prototype.repr=function(){ +var repr=MochiKit.Base.repr; +return "{x: "+repr(this.x)+", y: "+repr(this.y)+"}"; +}; +MochiKit.DOM.elementPosition=function(elem,_298){ +var self=MochiKit.DOM; +elem=self.getElement(elem); +if(!elem){ +return undefined; +} +var x=0; +var y=0; +if(elem.offsetParent){ +while(elem.offsetParent){ +x+=elem.offsetLeft; +y+=elem.offsetTop; +elem=elem.offsetParent; +} +}else{ +x=elem.x||x; +y=elem.y||y; +} +if(_298){ +_298=arguments.callee(_298); +if(_298){ +x-=(_298.x||0); +y-=(_298.y||0); +} +} +return new self.Coordinates(x,y); +}; +MochiKit.DOM.currentWindow=function(){ +return MochiKit.DOM._window; +}; +MochiKit.DOM.currentDocument=function(){ +return MochiKit.DOM._document; +}; +MochiKit.DOM.withWindow=function(win,func){ +var self=MochiKit.DOM; +var _300=self._document; +var _301=self._win; +var rval; +try{ +self._window=win; +self._document=win.document; +rval=func(); +} +catch(e){ +self._window=_301; +self._document=_300; +throw e; +} +self._window=_301; +self._document=_300; +return rval; +}; +MochiKit.DOM.formContents=function(elem){ +var _302=[]; +var _303=[]; +var m=MochiKit.Base; +var self=MochiKit.DOM; +if(typeof (elem)=="undefined"||elem==null){ +elem=self._document; +}else{ +elem=self.getElement(elem); +} +m.nodeWalk(elem,function(elem){ +var name=elem.name; +var _304=elem.value; +if(m.isNotEmpty(name,_304)){ +if(elem.tagName=="INPUT"&&(elem.type=="radio"||elem.type=="checkbox")&&!elem.checked){ +return null; +} +_302.push(name); +_303.push(_304); +return null; +} +return elem.childNodes; +}); +return [_302,_303]; +}; +MochiKit.DOM.withDocument=function(doc,func){ +var self=MochiKit.DOM; +var _306=self._document; +var rval; +try{ +self._document=doc; +rval=func(); +} +catch(e){ +self._document=_306; +throw e; +} +self._document=_306; +return rval; +}; +MochiKit.DOM.registerDOMConverter=function(name,_307,wrap,_308){ +MochiKit.DOM.domConverters.register(name,_307,wrap,_308); +}; +MochiKit.DOM.coerceToDOM=function(node,ctx){ +var im=MochiKit.Iter; +var self=MochiKit.DOM; +var iter=im.iter; +var _313=im.repeat; +var imap=im.imap; +var _315=self.domConverters; +var _316=self.coerceToDOM; +var _317=MochiKit.Base.NotFound; +while(true){ +if(typeof (node)=="undefined"||node==null){ +return null; +} +if(typeof (node.nodeType)!="undefined"&&node.nodeType>0){ +return node; +} +if(typeof (node)=="number"||typeof (node)=="bool"){ +node=node.toString(); +} +if(typeof (node)=="string"){ +return self._document.createTextNode(node); +} +if(typeof (node.toDOM)=="function"){ +node=node.toDOM(ctx); +continue; +} +if(typeof (node)=="function"){ +node=node(ctx); +continue; +} +var _318=null; +try{ +_318=iter(node); +} +catch(e){ +} +if(_318){ +return imap(_316,_318,_313(ctx)); +} +try{ +node=_315.match(node,ctx); +continue; +} +catch(e){ +if(e!=_317){ +throw e; +} +} +return self._document.createTextNode(node.toString()); +} +return undefined; +}; +MochiKit.DOM.setNodeAttribute=function(node,attr,_320){ +var o={}; +o[attr]=_320; +try{ +return MochiKit.DOM.updateNodeAttributes(node,o); +} +catch(e){ +} +return null; +}; +MochiKit.DOM.getNodeAttribute=function(node,attr){ +var self=MochiKit.DOM; +var _321=self.attributeArray.renames[attr]; +node=self.getElement(node); +try{ +if(_321){ +return node[_321]; +} +return node.getAttribute(attr); +} +catch(e){ +} +return null; +}; +MochiKit.DOM.updateNodeAttributes=function(node,_322){ +var elem=node; +var self=MochiKit.DOM; +if(typeof (node)=="string"){ +elem=self.getElement(node); +} +if(_322){ +var _323=MochiKit.Base.updatetree; +if(self.attributeArray.compliant){ +for(var k in _322){ +var v=_322[k]; +if(typeof (v)=="object"&&typeof (elem[k])=="object"){ +_323(elem[k],v); +}else{ +if(k.substring(0,2)=="on"){ +if(typeof (v)=="string"){ +v=new Function(v); +} +elem[k]=v; +}else{ +elem.setAttribute(k,v); +} +} +} +}else{ +var _324=self.attributeArray.renames; +for(k in _322){ +v=_322[k]; +var _325=_324[k]; +if(k=="style"&&typeof (v)=="string"){ +elem.style.cssText=v; +}else{ +if(typeof (_325)=="string"){ +elem[_325]=v; +}else{ +if(typeof (elem[k])=="object"&&typeof (v)=="object"){ +_323(elem[k],v); +}else{ +if(k.substring(0,2)=="on"){ +if(typeof (v)=="string"){ +v=new Function(v); +} +elem[k]=v; +}else{ +elem.setAttribute(k,v); +} +} +} +} +} +} +} +return elem; +}; +MochiKit.DOM.appendChildNodes=function(node){ +var elem=node; +var self=MochiKit.DOM; +if(typeof (node)=="string"){ +elem=self.getElement(node); +} +var _326=[self.coerceToDOM(MochiKit.Base.extend(null,arguments,1),elem)]; +var _327=MochiKit.Iter.iextend; +while(_326.length){ +var n=_326.shift(); +if(typeof (n)=="undefined"||n==null){ +}else{ +if(typeof (n.nodeType)=="number"){ +elem.appendChild(n); +}else{ +_327(_326,n); +} +} +} +return elem; +}; +MochiKit.DOM.replaceChildNodes=function(node){ +var elem=node; +var self=MochiKit.DOM; +if(typeof (node)=="string"){ +elem=self.getElement(node); +arguments[0]=elem; +} +var _328; +while((_328=elem.firstChild)){ +elem.removeChild(_328); +} +if(arguments.length<2){ +return elem; +}else{ +return self.appendChildNodes.apply(this,arguments); +} +}; +MochiKit.DOM.createDOM=function(name,_329){ +var elem; +var self=MochiKit.DOM; +if(typeof (name)=="string"){ +if(_329&&"name" in _329&&!self.attributeArray.compliant){ +name="<"+name+" name=\""+self.escapeHTML(_329.name)+"\">"; +} +elem=self._document.createElement(name); +}else{ +elem=name; +} +if(_329){ +self.updateNodeAttributes(elem,_329); +} +if(arguments.length<=2){ +return elem; +}else{ +var args=MochiKit.Base.extend([elem],arguments,2); +return self.appendChildNodes.apply(this,args); +} +}; +MochiKit.DOM.createDOMFunc=function(){ +var m=MochiKit.Base; +return m.partial.apply(this,m.extend([MochiKit.DOM.createDOM],arguments)); +}; +MochiKit.DOM.swapDOM=function(dest,src){ +var self=MochiKit.DOM; +dest=self.getElement(dest); +var _332=dest.parentNode; +if(src){ +src=self.getElement(src); +_332.replaceChild(src,dest); +}else{ +_332.removeChild(dest); +} +return src; +}; +MochiKit.DOM.getElement=function(id){ +var self=MochiKit.DOM; +if(arguments.length==1){ +return ((typeof (id)=="string")?self._document.getElementById(id):id); +}else{ +return MochiKit.Base.map(self.getElement,arguments); +} +}; +MochiKit.DOM.computedStyle=function(_334,_335,_336){ +if(arguments.length==2){ +_336=_335; +} +var self=MochiKit.DOM; +var el=self.getElement(_334); +var _338=self._document; +if(!el||el==_338){ +return undefined; +} +if(el.currentStyle){ +return el.currentStyle[_335]; +} +if(typeof (_338.defaultView)=="undefined"){ +return undefined; +} +if(_338.defaultView==null){ +return undefined; +} +var _339=_338.defaultView.getComputedStyle(el,null); +if(typeof (_339)=="undefined"||_339==null){ +return undefined; +} +return _339.getPropertyValue(_336); +}; +MochiKit.DOM.getElementsByTagAndClassName=function(_340,_341,_342){ +var self=MochiKit.DOM; +if(typeof (_340)=="undefined"||_340==null){ +_340="*"; +} +if(typeof (_342)=="undefined"||_342==null){ +_342=self._document; +} +_342=self.getElement(_342); +var _343=_342.getElementsByTagName(_340)||self._document.all; +if(typeof (_341)=="undefined"||_341==null){ +return MochiKit.Base.extend(null,_343); +} +var _344=[]; +for(var i=0;i<_343.length;i++){ +var _345=_343[i]; +var _346=_345.className.split(" "); +for(var j=0;j<_346.length;j++){ +if(_346[j]==_341){ +_344.push(_345); +break; +} +} +} +return _344; +}; +MochiKit.DOM._newCallStack=function(path,once){ +var rval=function(){ +var _349=arguments.callee.callStack; +for(var i=0;i<_349.length;i++){ +if(_349[i].apply(this,arguments)===false){ +break; +} +} +if(once){ +try{ +this[path]=null; +} +catch(e){ +} +} +}; +rval.callStack=[]; +return rval; +}; +MochiKit.DOM.addToCallStack=function(_350,path,func,once){ +var self=MochiKit.DOM; +var _351=_350[path]; +var _352=_351; +if(!(typeof (_351)=="function"&&typeof (_351.callStack)=="object"&&_351.callStack!=null)){ +_352=self._newCallStack(path,once); +if(typeof (_351)=="function"){ +_352.callStack.push(_351); +} +_350[path]=_352; +} +_352.callStack.push(func); +}; +MochiKit.DOM.addLoadEvent=function(func){ +var self=MochiKit.DOM; +self.addToCallStack(self._window,"onload",func,true); +}; +MochiKit.DOM.focusOnLoad=function(_353){ +var self=MochiKit.DOM; +self.addLoadEvent(function(){ +_353=self.getElement(_353); +if(_353){ +_353.focus(); +} +}); +}; +MochiKit.DOM.setElementClass=function(_354,_355){ +var self=MochiKit.DOM; +var obj=self.getElement(_354); +if(self.attributeArray.compliant){ +obj.setAttribute("class",_355); +}else{ +obj.setAttribute("className",_355); +} +}; +MochiKit.DOM.toggleElementClass=function(_356){ +var self=MochiKit.DOM; +for(var i=1;i/g,">"); +}; +MochiKit.DOM.toHTML=function(dom){ +return MochiKit.DOM.emitHTML(dom).join(""); +}; +MochiKit.DOM.emitHTML=function(dom,lst){ +if(typeof (lst)=="undefined"||lst==null){ +lst=[]; +} +var _372=[dom]; +var self=MochiKit.DOM; +var _373=self.escapeHTML; +var _374=self.attributeArray; +while(_372.length){ +dom=_372.pop(); +if(typeof (dom)=="string"){ +lst.push(dom); +}else{ +if(dom.nodeType==1){ +lst.push("<"+dom.nodeName.toLowerCase()); +var _375=[]; +var _376=_374(dom); +for(var i=0;i<_376.length;i++){ +var a=_376[i]; +_375.push([" ",a.name,"=\"",_373(a.value),"\""]); +} +_375.sort(); +for(i=0;i<_375.length;i++){ +var _377=_375[i]; +for(var j=0;j<_377.length;j++){ +lst.push(_377[j]); +} +} +if(dom.hasChildNodes()){ +lst.push(">"); +_372.push(""); +var _378=dom.childNodes; +for(i=_378.length-1;i>=0;i--){ +_372.push(_378[i]); +} +}else{ +lst.push("/>"); +} +}else{ +if(dom.nodeType==3){ +lst.push(_373(dom.nodeValue)); +} +} +} +} +return lst; +}; +MochiKit.DOM.setDisplayForElement=function(_379,_380){ +var m=MochiKit.Base; +var _381=m.extend(null,arguments,1); +MochiKit.Iter.forEach(m.filter(null,m.map(MochiKit.DOM.getElement,_381)),function(_380){ +_380.style.display=_379; +}); +}; +MochiKit.DOM.scrapeText=function(node,_382){ +var rval=[]; +(function(node){ +var cn=node.childNodes; +if(cn){ +for(var i=0;i0){ +var _387=m.filter; +_386=function(node){ +return _387(_386.ignoreAttrFilter,node.attributes); +}; +_386.ignoreAttr={}; +MochiKit.Iter.forEach(_385.attributes,function(a){ +_386.ignoreAttr[a.name]=a.value; +}); +_386.ignoreAttrFilter=function(a){ +return (_386.ignoreAttr[a.name]!=a.value); +}; +_386.compliant=false; +_386.renames={"class":"className","checked":"defaultChecked","usemap":"useMap","for":"htmlFor"}; +}else{ +_386=function(node){ +return node.attributes; +}; +_386.compliant=true; +_386.renames={}; +} +this.attributeArray=_386; +var _388=this.createDOMFunc; +this.UL=_388("ul"); +this.OL=_388("ol"); +this.LI=_388("li"); +this.TD=_388("td"); +this.TR=_388("tr"); +this.TBODY=_388("tbody"); +this.THEAD=_388("thead"); +this.TFOOT=_388("tfoot"); +this.TABLE=_388("table"); +this.TH=_388("th"); +this.INPUT=_388("input"); +this.SPAN=_388("span"); +this.A=_388("a"); +this.DIV=_388("div"); +this.IMG=_388("img"); +this.BUTTON=_388("button"); +this.TT=_388("tt"); +this.PRE=_388("pre"); +this.H1=_388("h1"); +this.H2=_388("h2"); +this.H3=_388("h3"); +this.BR=_388("br"); +this.HR=_388("hr"); +this.LABEL=_388("label"); +this.TEXTAREA=_388("textarea"); +this.FORM=_388("form"); +this.P=_388("p"); +this.SELECT=_388("select"); +this.OPTION=_388("option"); +this.OPTGROUP=_388("optgroup"); +this.LEGEND=_388("legend"); +this.FIELDSET=_388("fieldset"); +this.STRONG=_388("strong"); +this.CANVAS=_388("canvas"); +this.hideElement=m.partial(this.setDisplayForElement,"none"); +this.showElement=m.partial(this.setDisplayForElement,"block"); +this.removeElement=this.swapDOM; +this.$=this.getElement; +this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)}; +m.nameFunctions(this); +}; +MochiKit.DOM.__new__(this); +MochiKit.Base._exportSymbols(this,MochiKit.DOM); +if(typeof (dojo)!="undefined"){ +dojo.provide("MochiKit.LoggingPane"); +dojo.require("MochiKit.Logging"); +dojo.require("MochiKit.Base"); +} +if(typeof (JSAN)!="undefined"){ +JSAN.use("MochiKit.Logging",[]); +JSAN.use("MochiKit.Base",[]); +} +try{ +if(typeof (MochiKit.Base)=="undefined"||typeof (MochiKit.Logging)=="undefined"){ +throw ""; +} +} +catch(e){ +throw "MochiKit.LoggingPane depends on MochiKit.Base and MochiKit.Logging!"; +} +if(typeof (MochiKit.LoggingPane)=="undefined"){ +MochiKit.LoggingPane={}; +} +MochiKit.LoggingPane.NAME="MochiKit.LoggingPane"; +MochiKit.LoggingPane.VERSION="1.2"; +MochiKit.LoggingPane.__repr__=function(){ +return "["+this.NAME+" "+this.VERSION+"]"; +}; +MochiKit.LoggingPane.toString=function(){ +return this.__repr__(); +}; +MochiKit.LoggingPane.createLoggingPane=function(_389){ +var m=MochiKit.LoggingPane; +_389=!(!_389); +if(m._loggingPane&&m._loggingPane.inline!=_389){ +m._loggingPane.closePane(); +m._loggingPane=null; +} +if(!m._loggingPane||m._loggingPane.closed){ +m._loggingPane=new m.LoggingPane(_389,MochiKit.Logging.logger); +} +return m._loggingPane; +}; +MochiKit.LoggingPane.LoggingPane=function(_390,_391){ +if(typeof (_391)=="undefined"||_391==null){ +_391=MochiKit.Logging.logger; +} +this.logger=_391; +var _392=MochiKit.Base.update; +var _393=MochiKit.Base.updatetree; +var bind=MochiKit.Base.bind; +var _394=MochiKit.Base.clone; +var win=window; +if(typeof (MochiKit.DOM)!="undefined"){ +win=MochiKit.DOM.currentWindow(); +} +if(!_390){ +var url=win.location.href.split("?")[0].replace(/[:\/.><&]/g,"_"); +var name="MochiKit_LoggingPane_"+url; +var nwin=win.open("",name,"dependent,resizable,height=200"); +if(!nwin){ +alert("Not able to open debugging window due to pop-up blocking."); +return undefined; +} +nwin.document.write(""+"[MochiKit.LoggingPane]"+""); +nwin.document.close(); +nwin.document.title+=" "+win.document.title; +win=nwin; +} +var doc=win.document; +this.doc=doc; +var _396=doc.getElementById("_debugPane"); +if(_396&&typeof (_396.loggingPane)!="undefined"){ +_396.loggingPane.logger=this.logger; +_396.loggingPane.buildAndApplyFilter(); +return _396.loggingPane; +} +_396=doc.createElement("div"); +_396.id="_debugPane"; +_396.loggingPane=this; +var _397=doc.createElement("input"); +var _398=doc.createElement("input"); +var _399=doc.createElement("button"); +var _400=doc.createElement("button"); +var _401=doc.createElement("button"); +var _402=doc.createElement("button"); +var _403=doc.createElement("div"); +var _404=doc.createElement("div"); +var _405="_debugPaneListener"; +this.colorTable=_394(this.colorTable); +var _406=[]; +var _407=null; +var _408=function(msg){ +var _409=msg.level; +if(typeof (_409)=="number"){ +_409=MochiKit.Logging.LogLevel[_409]; +} +return _409; +}; +var _410=function(msg){ +return msg.info.join(" "); +}; +var _411=bind(function(msg){ +var _412=_408(msg); +var text=_410(msg); +var c=this.colorTable[_412]; +var p=doc.createElement("span"); +p.className="MochiKit-LogMessage MochiKit-LogLevel-"+_412; +p.style.cssText="margin: 0px; white-space: -moz-pre-wrap; white-space: -o-pre-wrap; white-space: pre-wrap; white-space: pre-line; word-wrap: break-word; wrap-option: emergency; color: "+c; +p.appendChild(doc.createTextNode(_412+": "+text)); +_404.appendChild(p); +_404.appendChild(doc.createElement("br")); +if(_403.offsetHeight>_403.scrollHeight){ +_403.scrollTop=0; +}else{ +_403.scrollTop=_403.scrollHeight; +} +},this); +var _415=function(msg){ +_406[_406.length]=msg; +_411(msg); +}; +var _416=function(){ +var _417,infore; +try{ +_417=new RegExp(_397.value); +infore=new RegExp(_398.value); +} +catch(e){ +logDebug("Error in filter regex: "+e.message); +return null; +} +return function(msg){ +return (_417.test(_408(msg))&&infore.test(_410(msg))); +}; +}; +var _418=function(){ +while(_404.firstChild){ +_404.removeChild(_404.firstChild); +} +}; +var _419=function(){ +_406=[]; +_418(); +}; +var _420=bind(function(){ +if(this.closed){ +return; +} +this.closed=true; +if(MochiKit.LoggingPane._loggingPane==this){ +MochiKit.LoggingPane._loggingPane=null; +} +this.logger.removeListener(_405); +_396.loggingPane=null; +if(_390){ +_396.parentNode.removeChild(_396); +}else{ +this.win.close(); +} +},this); +var _421=function(){ +_418(); +for(var i=0;i<_406.length;i++){ +var msg=_406[i]; +if(_407==null||_407(msg)){ +_411(msg); +} +} +}; +this.buildAndApplyFilter=function(){ +_407=_416(); +_421(); +this.logger.removeListener(_405); +this.logger.addListener(_405,_407,_415); +}; +var _422=bind(function(){ +_406=this.logger.getMessages(); +_421(); +},this); +var _423=bind(function(_424){ +_424=_424||window.event; +key=_424.which||_424.keyCode; +if(key==13){ +this.buildAndApplyFilter(); +} +},this); +var _425="display: block; left: 0px; bottom: 0px; position: fixed; width: 100%; background-color: white; font: "+this.logFont; +if(_390){ +_425+="; height: 10em; border-top: 2px solid black"; +}else{ +_425+="; height: 100%;"; +} +_396.style.cssText=_425; +doc.body.appendChild(_396); +_425={"cssText":"width: 33%; display: inline; font: "+this.logFont}; +_393(_397,{"value":"FATAL|ERROR|WARNING|INFO|DEBUG","onkeypress":_423,"style":_425}); +_396.appendChild(_397); +_393(_398,{"value":".*","onkeypress":_423,"style":_425}); +_396.appendChild(_398); +_425="width: 8%; display:inline; font: "+this.logFont; +_399.appendChild(doc.createTextNode("Filter")); +_399.onclick=bind(this.buildAndApplyFilter,this); +_399.style.cssText=_425; +_396.appendChild(_399); +_400.appendChild(doc.createTextNode("Load")); +_400.onclick=_422; +_400.style.cssText=_425; +_396.appendChild(_400); +_401.appendChild(doc.createTextNode("Clear")); +_401.onclick=_419; +_401.style.cssText=_425; +_396.appendChild(_401); +_402.appendChild(doc.createTextNode("Close")); +_402.onclick=_420; +_402.style.cssText=_425; +_396.appendChild(_402); +_403.style.cssText="overflow: auto; width: 100%"; +_404.style.cssText="width: 100%; height: "+(_390?"8em":"100%"); +_403.appendChild(_404); +_396.appendChild(_403); +this.buildAndApplyFilter(); +_422(); +if(_390){ +this.win=undefined; +}else{ +this.win=win; +} +this.inline=_390; +this.closePane=_420; +this.closed=false; +return this; +}; +MochiKit.LoggingPane.LoggingPane.prototype={"logFont":"8pt Verdana,sans-serif","colorTable":{"ERROR":"red","FATAL":"darkred","WARNING":"blue","INFO":"black","DEBUG":"green"}}; +MochiKit.LoggingPane.EXPORT_OK=["LoggingPane"]; +MochiKit.LoggingPane.EXPORT=["createLoggingPane"]; +MochiKit.LoggingPane.__new__=function(){ +this.EXPORT_TAGS={":common":this.EXPORT,":all":MochiKit.Base.concat(this.EXPORT,this.EXPORT_OK)}; +MochiKit.Base.nameFunctions(this); +MochiKit.LoggingPane._loggingPane=null; +}; +MochiKit.LoggingPane.__new__(); +MochiKit.Base._exportSymbols(this,MochiKit.LoggingPane); +if(typeof (dojo)!="undefined"){ +dojo.provide("MochiKit.Color"); +dojo.require("MochiKit.Base"); +} +if(typeof (JSAN)!="undefined"){ +JSAN.use("MochiKit.Base",[]); +} +try{ +if(typeof (MochiKit.Base)=="undefined"){ +throw ""; +} +} +catch(e){ +throw "MochiKit.Color depends on MochiKit.Base"; +} +if(typeof (MochiKit.Color)=="undefined"){ +MochiKit.Color={}; +} +MochiKit.Color.NAME="MochiKit.Color"; +MochiKit.Color.VERSION="1.2"; +MochiKit.Color.__repr__=function(){ +return "["+this.NAME+" "+this.VERSION+"]"; +}; +MochiKit.Color.toString=function(){ +return this.__repr__(); +}; +MochiKit.Color.Color=function(red,_427,blue,_429){ +if(typeof (_429)=="undefined"||_429==null){ +_429=1; +} +this.rgb={r:red,g:_427,b:blue,a:_429}; +}; +MochiKit.Color.Color.prototype={__class__:MochiKit.Color.Color,colorWithAlpha:function(_430){ +var rgb=this.rgb; +var m=MochiKit.Color; +return m.Color.fromRGB(rgb.r,rgb.g,rgb.b,_430); +},colorWithHue:function(hue){ +var hsl=this.asHSL(); +hsl.h=hue; +var m=MochiKit.Color; +return m.Color.fromHSL(hsl); +},colorWithSaturation:function(_434){ +var hsl=this.asHSL(); +hsl.s=_434; +var m=MochiKit.Color; +return m.Color.fromHSL(hsl); +},colorWithLightness:function(_435){ +var hsl=this.asHSL(); +hsl.l=_435; +var m=MochiKit.Color; +return m.Color.fromHSL(hsl); +},darkerColorWithLevel:function(_436){ +var hsl=this.asHSL(); +hsl.l=Math.max(hsl.l-_436,0); +var m=MochiKit.Color; +return m.Color.fromHSL(hsl); +},lighterColorWithLevel:function(_437){ +var hsl=this.asHSL(); +Math.min(hsl.l+_437,1); +var m=MochiKit.Color; +return m.Color.fromHSL(hsl); +},blendedColor:function(_438,_439){ +if(typeof (_439)=="undefined"||_439==null){ +_439=0.5; +} +var sf=1-_439; +var s=this.rgb; +var d=_438.rgb; +var df=_439; +return MochiKit.Color.Color.fromRGB((s.r*sf)+(d.r*df),(s.g*sf)+(d.g*df),(s.b*sf)+(d.b*df),(s.a*sf)+(d.a*df)); +},compareRGB:function(_442){ +var a=this.asRGB(); +var b=_442.asRGB(); +return MochiKit.Base.compare([a.r,a.g,a.b,a.a],[b.r,b.g,b.b,b.a]); +},isLight:function(){ +return this.asHSL().b>0.5; +},isDark:function(){ +return (!this.isLight()); +},toHSLString:function(){ +var c=this.asHSL(); +var ccc=MochiKit.Color.clampColorComponent; +var rval=this._hslString; +if(!rval){ +var mid=(ccc(c.h,360).toFixed(0)+","+ccc(c.s,100).toPrecision(4)+"%"+","+ccc(c.l,100).toPrecision(4)+"%"); +var a=c.a; +if(a>=1){ +a=1; +rval="hsl("+mid+")"; +}else{ +if(a<=0){ +a=0; +} +rval="hsla("+mid+","+a+")"; +} +this._hslString=rval; +} +return rval; +},toRGBString:function(){ +var c=this.rgb; +var ccc=MochiKit.Color.clampColorComponent; +var rval=this._rgbString; +if(!rval){ +var mid=(ccc(c.r,255).toFixed(0)+","+ccc(c.g,255).toFixed(0)+","+ccc(c.b,255).toFixed(0)); +if(c.a!=1){ +rval="rgba("+mid+","+c.a+")"; +}else{ +rval="rgb("+mid+")"; +} +this._rgbString=rval; +} +return rval; +},asRGB:function(){ +return MochiKit.Base.clone(this.rgb); +},toHexString:function(){ +var m=MochiKit.Color; +var c=this.rgb; +var ccc=MochiKit.Color.clampColorComponent; +var rval=this._hexString; +if(!rval){ +rval=("#"+m.toColorPart(ccc(c.r,255))+m.toColorPart(ccc(c.g,255))+m.toColorPart(ccc(c.b,255))); +this._hexString=rval; +} +return rval; +},asHSV:function(){ +var hsv=this.hsv; +var c=this.rgb; +if(typeof (hsv)=="undefined"||hsv==null){ +hsv=MochiKit.Color.rgbToHSV(this.rgb); +this.hsv=hsv; +} +return MochiKit.Base.clone(hsv); +},asHSL:function(){ +var hsl=this.hsl; +var c=this.rgb; +if(typeof (hsl)=="undefined"||hsl==null){ +hsl=MochiKit.Color.rgbToHSL(this.rgb); +this.hsl=hsl; +} +return MochiKit.Base.clone(hsl); +},toString:function(){ +return this.toRGBString(); +},repr:function(){ +var c=this.rgb; +var col=[c.r,c.g,c.b,c.a]; +return this.__class__.NAME+"("+col.join(", ")+")"; +}}; +MochiKit.Base.update(MochiKit.Color.Color,{fromRGB:function(red,_447,blue,_448){ +var _449=MochiKit.Color.Color; +if(arguments.length==1){ +var rgb=red; +red=rgb.r; +_447=rgb.g; +blue=rgb.b; +if(typeof (rgb.a)=="undefined"){ +_448=undefined; +}else{ +_448=rgb.a; +} +} +return new _449(red,_447,blue,_448); +},fromHSL:function(hue,_450,_451,_452){ +var m=MochiKit.Color; +return m.Color.fromRGB(m.hslToRGB.apply(m,arguments)); +},fromHSV:function(hue,_453,_454,_455){ +var m=MochiKit.Color; +return m.Color.fromRGB(m.hsvToRGB.apply(m,arguments)); +},fromName:function(name){ +var _456=MochiKit.Color.Color; +var _457=_456._namedColors[name.toLowerCase()]; +if(typeof (_457)=="string"){ +return _456.fromHexString(_457); +}else{ +if(name=="transparent"){ +return _456.transparentColor(); +} +} +return null; +},fromString:function(_458){ +var self=MochiKit.Color.Color; +var _459=_458.substr(0,3); +if(_459=="rgb"){ +return self.fromRGBString(_458); +}else{ +if(_459=="hsl"){ +return self.fromHSLString(_458); +}else{ +if(_458.charAt(0)=="#"){ +return self.fromHexString(_458); +} +} +} +return self.fromName(_458); +},fromHexString:function(_460){ +if(_460.charAt(0)=="#"){ +_460=_460.substring(1); +} +var _461=[]; +var i,hex; +if(_460.length==3){ +for(i=0;i<3;i++){ +hex=_460.substr(i,1); +_461.push(parseInt(hex+hex,16)/255); +} +}else{ +for(i=0;i<6;i+=2){ +hex=_460.substr(i,2); +_461.push(parseInt(hex,16)/255); +} +} +var _462=MochiKit.Color.Color; +return _462.fromRGB.apply(_462,_461); +},_fromColorString:function(pre,_464,_465,_466){ +if(_466.indexOf(pre)==0){ +_466=_466.substring(_466.indexOf("(",3)+1,_466.length-1); +} +var _467=_466.split(/\s*,\s*/); +var _468=[]; +for(var i=0;i<_467.length;i++){ +var c=_467[i]; +var val; +var _469=c.substring(c.length-3); +if(c.charAt(c.length-1)=="%"){ +val=0.01*parseFloat(c.substring(0,c.length-1)); +}else{ +if(_469=="deg"){ +val=parseFloat(c)/360; +}else{ +if(_469=="rad"){ +val=parseFloat(c)/(Math.PI*2); +}else{ +val=_465[i]*parseFloat(c); +} +} +} +_468.push(val); +} +return this[_464].apply(this,_468); +},fromComputedStyle:function(elem,_470,_471){ +var d=MochiKit.DOM; +var cls=MochiKit.Color.Color; +for(elem=d.getElement(elem);elem;elem=elem.parentNode){ +var _472=d.computedStyle.apply(d,arguments); +if(!_472){ +continue; +} +var _473=cls.fromString(_472); +if(!_473){ +break; +} +if(_473.asRGB().a>0){ +return _473; +} +} +return null; +},fromBackground:function(elem){ +var cls=MochiKit.Color.Color; +return cls.fromComputedStyle(elem,"backgroundColor","background-color")||cls.whiteColor(); +},fromText:function(elem){ +var cls=MochiKit.Color.Color; +return cls.fromComputedStyle(elem,"color","color")||cls.blackColor(); +},namedColors:function(){ +return MochiKit.Base.clone(MochiKit.Color.Color._namedColors); +}}); +MochiKit.Base.update(MochiKit.Color,{clampColorComponent:function(v,_474){ +v*=_474; +if(v<0){ +return 0; +}else{ +if(v>_474){ +return _474; +}else{ +return v; +} +} +},_hslValue:function(n1,n2,hue){ +if(hue>6){ +hue-=6; +}else{ +if(hue<0){ +hue+=6; +} +} +var val; +if(hue<1){ +val=n1+(n2-n1)*hue; +}else{ +if(hue<3){ +val=n2; +}else{ +if(hue<4){ +val=n1+(n2-n1)*(4-hue); +}else{ +val=n1; +} +} +} +return val; +},hsvToRGB:function(hue,_477,_478,_479){ +if(arguments.length==1){ +var hsv=hue; +hue=hsv.h; +_477=hsv.s; +_478=hsv.v; +_479=hsv.a; +} +var red; +var _480; +var blue; +if(_477==0){ +red=0; +_480=0; +blue=0; +}else{ +var i=Math.floor(hue*6); +var f=(hue*6)-i; +var p=_478*(1-_477); +var q=_478*(1-(_477*f)); +var t=_478*(1-(_477*(1-f))); +switch(i){ +case 1: +red=q; +_480=_478; +blue=p; +break; +case 2: +red=p; +_480=_478; +blue=t; +break; +case 3: +red=p; +_480=q; +blue=_478; +break; +case 4: +red=t; +_480=p; +blue=_478; +break; +case 5: +red=_478; +_480=p; +blue=q; +break; +case 6: +case 0: +red=_478; +_480=t; +blue=p; +break; +} +} +return {r:red,g:_480,b:blue,a:_479}; +},hslToRGB:function(hue,_482,_483,_484){ +if(arguments.length==1){ +var hsl=hue; +hue=hsl.h; +_482=hsl.s; +_483=hsl.l; +_484=hsl.a; +} +var red; +var _485; +var blue; +if(_482==0){ +red=_483; +_485=_483; +blue=_483; +}else{ +var m2; +if(_483<=0.5){ +m2=_483*(1+_482); +}else{ +m2=_483+_482-(_483*_482); +} +var m1=(2*_483)-m2; +var f=MochiKit.Color._hslValue; +var h6=hue*6; +red=f(m1,m2,h6+2); +_485=f(m1,m2,h6); +blue=f(m1,m2,h6-2); +} +return {r:red,g:_485,b:blue,a:_484}; +},rgbToHSV:function(red,_489,blue,_490){ +if(arguments.length==1){ +var rgb=red; +red=rgb.r; +_489=rgb.g; +blue=rgb.b; +_490=rgb.a; +} +var max=Math.max(Math.max(red,_489),blue); +var min=Math.min(Math.min(red,_489),blue); +var hue; +var _493; +var _494=max; +if(min==max){ +hue=0; +_493=0; +}else{ +var _495=(max-min); +_493=_495/max; +if(red==max){ +hue=(_489-blue)/_495; +}else{ +if(_489==max){ +hue=2+((blue-red)/_495); +}else{ +hue=4+((red-_489)/_495); +} +} +hue/=6; +if(hue<0){ +hue+=1; +} +if(hue>1){ +hue-=1; +} +} +return {h:hue,s:_493,v:_494,a:_490}; +},rgbToHSL:function(red,_496,blue,_497){ +if(arguments.length==1){ +var rgb=red; +red=rgb.r; +_496=rgb.g; +blue=rgb.b; +_497=rgb.a; +} +var max=Math.max(red,Math.max(_496,blue)); +var min=Math.min(red,Math.min(_496,blue)); +var hue; +var _498; +var _499=(max+min)/2; +var _500=max-min; +if(_500==0){ +hue=0; +_498=0; +}else{ +if(_499<=0.5){ +_498=_500/(max+min); +}else{ +_498=_500/(2-max-min); +} +if(red==max){ +hue=(_496-blue)/_500; +}else{ +if(_496==max){ +hue=2+((blue-red)/_500); +}else{ +hue=4+((red-_496)/_500); +} +} +hue/=6; +if(hue<0){ +hue+=1; +} +if(hue>1){ +hue-=1; +} +} +return {h:hue,s:_498,l:_499,a:_497}; +},toColorPart:function(num){ +var _501=Math.round(num).toString(16); +if(num<16){ +return "0"+_501; +} +return _501; +},__new__:function(){ +var m=MochiKit.Base; +this.Color.fromRGBString=m.bind(this.Color._fromColorString,this.Color,"rgb","fromRGB",[1/255,1/255,1/255,1]); +this.Color.fromHSLString=m.bind(this.Color._fromColorString,this.Color,"hsl","fromHSL",[1/360,0.01,0.01,1]); +var _502=1/3; +var _503={black:[0,0,0],blue:[0,0,1],brown:[0.6,0.4,0.2],cyan:[0,1,1],darkGray:[_502,_502,_502],gray:[0.5,0.5,0.5],green:[0,1,0],lightGray:[2*_502,2*_502,2*_502],magenta:[1,0,1],orange:[1,0.5,0],purple:[0.5,0,0.5],red:[1,0,0],transparent:[0,0,0,0],white:[1,1,1],yellow:[1,1,0]}; +var _504=function(name,r,g,b,a){ +var rval=this.fromRGB(r,g,b,a); +this[name]=function(){ +return rval; +}; +return rval; +}; +for(var k in _503){ +var name=k+"Color"; +var _507=m.concat([_504,this.Color,name],_503[k]); +this.Color[name]=m.bind.apply(null,_507); +} +var _508=function(){ +for(var i=0;i"+el.innerHTML+"
"; +},_roundTopCorners:function(el,_524,_525){ +var _526=this._createCorner(_525); +for(var i=0;i=0;i--){ +_529.appendChild(this._createCornerSlice(_527,_528,i,"bottom")); +} +el.style.paddingBottom=0; +el.appendChild(_529); +},_createCorner:function(_530){ +var dom=MochiKit.DOM; +return dom.DIV({style:{backgroundColor:_530.toString()}}); +},_createCornerSlice:function(_531,_532,n,_533){ +var _534=MochiKit.DOM.SPAN(); +var _535=_534.style; +_535.backgroundColor=_531.toString(); +_535.display="block"; +_535.height="1px"; +_535.overflow="hidden"; +_535.fontSize="1px"; +var _536=this._borderColor(_531,_532); +if(this.options.border&&n==0){ +_535.borderTopStyle="solid"; +_535.borderTopWidth="1px"; +_535.borderLeftWidth="0px"; +_535.borderRightWidth="0px"; +_535.borderBottomWidth="0px"; +_535.height="0px"; +_535.borderColor=_536.toString(); +}else{ +if(_536){ +_535.borderColor=_536.toString(); +_535.borderStyle="solid"; +_535.borderWidth="0px 1px"; +} +} +if(!this.options.compact&&(n==(this.options.numSlices-1))){ +_535.height="2px"; +} +this._setMargin(_534,n,_533); +this._setBorder(_534,n,_533); +return _534; +},_setOptions:function(_537){ +this.options={corners:"all",color:"fromElement",bgColor:"fromParent",blend:true,border:false,compact:false,__unstable__wrapElement:false}; +MochiKit.Base.update(this.options,_537); +this.options.numSlices=(this.options.compact?2:4); +},_whichSideTop:function(){ +var _538=this.options.corners; +if(this._hasString(_538,"all","top")){ +return ""; +} +var _539=(_538.indexOf("tl")!=-1); +var _540=(_538.indexOf("tr")!=-1); +if(_539&&_540){ +return ""; +} +if(_539){ +return "left"; +} +if(_540){ +return "right"; +} +return ""; +},_whichSideBottom:function(){ +var _541=this.options.corners; +if(this._hasString(_541,"all","bottom")){ +return ""; +} +var _542=(_541.indexOf("bl")!=-1); +var _543=(_541.indexOf("br")!=-1); +if(_542&&_543){ +return ""; +} +if(_542){ +return "left"; +} +if(_543){ +return "right"; +} +return ""; +},_borderColor:function(_544,_545){ +if(_544=="transparent"){ +return _545; +}else{ +if(this.options.border){ +return this.options.border; +}else{ +if(this.options.blend){ +return _545.blendedColor(_544); +} +} +} +return ""; +},_setMargin:function(el,n,_546){ +var _547=this._marginSize(n)+"px"; +var _548=(_546=="top"?this._whichSideTop():this._whichSideBottom()); +var _549=el.style; +if(_548=="left"){ +_549.marginLeft=_547; +_549.marginRight="0px"; +}else{ +if(_548=="right"){ +_549.marginRight=_547; +_549.marginLeft="0px"; +}else{ +_549.marginLeft=_547; +_549.marginRight=_547; +} +} +},_setBorder:function(el,n,_550){ +var _551=this._borderSize(n)+"px"; +var _552=(_550=="top"?this._whichSideTop():this._whichSideBottom()); +var _553=el.style; +if(_552=="left"){ +_553.borderLeftWidth=_551; +_553.borderRightWidth="0px"; +}else{ +if(_552=="right"){ +_553.borderRightWidth=_551; +_553.borderLeftWidth="0px"; +}else{ +_553.borderLeftWidth=_551; +_553.borderRightWidth=_551; +} +} +},_marginSize:function(n){ +if(this.isTransparent){ +return 0; +} +var o=this.options; +if(o.compact&&o.blend){ +var _554=[1,0]; +return _554[n]; +}else{ +if(o.compact){ +var _555=[2,1]; +return _555[n]; +}else{ +if(o.blend){ +var _556=[3,2,1,0]; +return _556[n]; +}else{ +var _557=[5,3,2,1]; +return _557[n]; +} +} +} +},_borderSize:function(n){ +var o=this.options; +var _558; +if(o.compact&&(o.blend||this.isTransparent)){ +return 1; +}else{ +if(o.compact){ +_558=[1,0]; +}else{ +if(o.blend){ +_558=[2,1,1,1]; +}else{ +if(o.border){ +_558=[0,2,0,0]; +}else{ +if(this.isTransparent){ +_558=[5,3,2,1]; +}else{ +return 0; +} +} +} +} +} +return _558[n]; +},_hasString:function(str){ +for(var i=1;i"+"<"+"/script"+">"; +document.write(tag); +} +} +})(); +} + + --- www/viewmtn/MochiKit/__package__.js +++ www/viewmtn/MochiKit/__package__.js @@ -0,0 +1,2 @@ +dojo.hostenv.conditionalLoadModule({"common": ["MochiKit.MochiKit"]}); +dojo.hostenv.moduleLoaded("MochiKit.*"); --- www/viewmtn/README +++ www/viewmtn/README @@ -0,0 +1,62 @@ + +ViewMTN +------- + +A web interface to monotone. See "LICENSE" for distribution terms. +ViewMTN is Copyright 2005 Grahame Bowland. + +The minimum version of monotone required is: 0.24 + +For the graphs to work you'll need dotty installed - it is a +part of GraphViz. + http://www.research.att.com/sw/tools/graphviz/ + +ViewMTN requires mod_python. + http://www.modpython.org/ + +When installing be sure to copy config.py.example to config.py, and +then edit config.py (paying attention to the comments!) + +Common issues +------------- + +If you are getting a 404 error for "getfile.py", make sure that you have +updated ".htacess" to include the following line. + PythonHandler wrapper +"wrapper.py" contains several Python functions which are called +for various pages that do not need to go through mod_python's PSP +framework. The above line causes all page requests to go through +wrapper.py, which decides which function (or PSP file) should be called +to handle the request. + +(Note: recent versions of ViewMTN will not work at all if you are missing + the line above.) + +MacOS +----- + +I run ViewMTN on MacOS by using mod_python and apache2 compiled from source +using Darwin Ports: + http://darwinports.opendarwin.org/ +You should be able to install graphviz from Darwin Ports as well. + +The graphs seem to look terrible with the default fault from config.py.example; +I set the font to "Monaco" instead and it looks pretty good. + +Bugs, suggestions, feedback +--------------------------- + +Send any bugs, suggestions or feedback to: + Grahame Bowland + PO BOX 3105, Broadway, Nedlands WA 6009 Australia + +In particular, please look at the TODO file. If you're interested in +fixing any of the issues listed (or just adding extra TODO entries) +please go ahead - perhaps let me know so I can keep track and let you +know if the item is already done but not committed. + +As monotone is a distributed version control system, feel free to +grab a copy of viewmtn, commit to your local DB, etc. If you want to +send me your commits, email me (or catch me in #monotone) and we'll +work something out. + --- www/viewmtn/TODO +++ www/viewmtn/TODO @@ -0,0 +1,89 @@ + +BUGS: + + * \n in title of the ancestry graph + +TODO: + + * support for translation, and see if we can get some people to help with that + モノトネ + + * some sort of contest for a logo (with some sort of cool prize, perhaps + con someone into putting one up on offer or pay up myself.) + + * a revision selector interface - make sure it prints out the revision + selector to the person, so that they can use it on the command line later + ** make this AJAX; show the results *as the person types* ** + ** this feature would blow people out of the water ** + + * optionally pop-up a window (or some sort of AJAX box lurking someplace) + which updates with the commands that have been run - useful for debugging, + also useful for beginners to see how to do stuff. + + * Use monotone automate graph to do the ancestry graphing, and show some + future information. Perhaps cache based on the mtime of the db viewmtn + is looking at? Might be good enough. + + Show dotted lines for propogates (with that information on the arc) + + Show lines leading off from the boxes at either end of the graph (if + applicable) to give context that the image being shown is part of a + large overall graph. + + * Show information when mousing over long hex strings. + + * Magically make http:// ftp:// etc links inside files clickable + + * In the file diff view for multiple files, show the same change set + information that is available in the revision view - this will help + explain what's happened. For bonus points, index into the diff so you + can click on a file that changed and jump to that section in the diff. + + * Improve the file listing; use data from the new rosters branch + to get a better idea of when a given file was last touched. + + * When viewing a file, give a list of revisions in which that file + was recently changed. Include in the list a link to diff with each + revision. + + * generally clean up table formatting code; things like + list_of_branches() + list_of_tags() + revision_certs() + revision_details() + use a generic method to output the tables + + * from [mrb] + support for multiple databases (perhaps some sort of dropdown to say which + database you want to look in) - perhaps also make the branches page show the + branches in each of the DBs, for ease. + +AJAX ideas: + + * When hovering over nodes in the ancestry graph, display the ChangeLog. + + * When hovering over branch links, show heads and other information + + * When hovering over revisions, show author / date + + * When hovering over diff links + + * turn line numbering in file / diff view (etc) on and off + + * file viewer; treat merges as a special case and show the side which + actually has the resultant file (no change on that edge) + + * file viewer / diff viewer / anywhere showing info related to a rev + magic box that downloads and inserts + revision stuff. + + * we need some concept of selecting two points revisions for diffs or other + comparison. This is the main strength ViewCVS seems to have over us. + +LONG TERM: + + * provide some option for people without apache2 / mod_python to run the + thing; even if it's running the program from a standalone python webserver. + Would settle for a solution that required a cgi capable webserver rather than + specifically mod_python (and thus apache2) while still supporting mod_python + acceleration if present. --- www/viewmtn/about.psp +++ www/viewmtn/about.psp @@ -0,0 +1,66 @@ +<% + +from authors import authors + +psp.set_error_page("error.psp") +info = { 'title' : "About" } +req.write(template.header(info)) + +%> + +

Authors and Contributers

+ +
+<%= hq (authors.strip()) %>
+
+ +

Licensing

+ +
+<%
+req.write('''\
+Copyright (C) 2005  Grahame Bowland
+
+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.
+
+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 this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA''')
+%>
+
+ +

Dependencies

+ +

+ViewMTN is written in Python and +runs under mod_python. +

+ +

+Code highlighting via +GNU Enscript. +

+ +

+Graphing via GraphViz. +

+ +

+Graph colour generation algorithm from monotone-viz with modifications from Matt Johnston. +

+ +

+AJAX funtionality uses the MochiKit Javascript library. +

+ +<% +req.write(template.footer(info)) +%> --- www/viewmtn/authors.py +++ www/viewmtn/authors.py @@ -0,0 +1,11 @@ +authors='''Authors: +Grahame Bowland + +Contributors: +Matt Johnston +Nathaniel Smith +Bruce Stephens +Lapo Luchini +David Reiss + +''' --- www/viewmtn/branch.psp +++ www/viewmtn/branch.psp @@ -0,0 +1,160 @@ +<% + +import datetime +import config +import urllib +import common +import time +from common import parse_timecert, ago_string + +# +# branch.psp +# display a log of changes on a given branch, with an +# optional offset +# + +psp.set_error_page("error.psp") + +def prettify(s): + return ' '.join(map(lambda x: hq(x[0].upper() + x[1:]), s.replace("_", " ").split(" "))) + +if not form.has_key('branch'): + raise Exception("No branch specified.") + +page = 1 +if form.has_key('page'): + try: page = int(form['page']) + except: pass + +if form.has_key('rss'): + output = 'rss' +else: + output = 'html' + +if page < 1: page = 1 + +display = 10 +offset = page * display + +branch = form['branch'] + +page_title = "Changes to branch %s" % (hq(branch)) +if output == 'html': + info = { + 'title' : page_title, + 'branch_rss' : [branch] + } + req.write(template.header(info)) +elif output == 'rss': + req.write('''\ + + + en-us + + %s + %s + %s''' % (page_title, config.base_url(req.uri), page_title)) + +heads = mt.heads(branch) + +recent = mt.toposort(mt.ancestors(heads) + heads) + +if display > len(recent): + display = len(recent) +if offset > len(recent): + offset = len(recent) + no_next = True +else: + no_next = False + +start = -1*offset +stop = start + display +if stop == 0: stop = None +recent = recent[start:stop] +recent.reverse() + +%> + +<% +if output == 'html': + req.write('''\ +

+This page lists changes %s through %s to %s. +It may also be useful to view the %s of this branch. +

''' % (-1*(stop or 0), (-1 * start), link('branch', branch), link('headofbranch', branch, 'current head revision'))) + req.write('') +%> + +<% +for idx, id in enumerate(recent): + certs = mt.certs(id) + + if output == 'rss': + req.write('\n') + req.write('%s\n' % (urllib.basejoin(config.base_url(req.uri), 'revision.psp?id=%s' % (urllib.quote(id))))) + + certdate, quicklog, certinfo = None, "", "" + for cert in certs: + name, value = None, None + for k, v in cert: + if k == "name": name = v + elif k == "value": value = v + if name == None or value == None: continue + if name == "date": + certdate = parse_timecert(value) + if output == 'rss': req.write("\t%s\n" % hq(certdate.strftime("%a, %d %b %Y %H:%M:%S +0000"))) + elif name == "branch": + # hackish, but oh well + if value == branch: + value = None + else: + name = "other_branches" + value = link("branch", value) + else: + if name == "changelog": + quicklog = common.quicklog(value) + if output == 'rss': req.write("\t%s\n%s\n" % (quicklog, hq(value))) + elif name == "author": + if output == 'rss': req.write("\t%s\n" % (hq(value))) + value = '
'.join(map(hq, value.split('\n'))) + if name != None and value != None: + certinfo += '
\n' % (prettify(name), value) + + now = datetime.datetime.utcnow() + ago = ago_string(certdate, now) + + if output == 'html': + style = "border-bottom-style: solid; border-bottom-width: 1px; border-bottom-color: black;" + if idx != 0: + style += "border-top-style: solid; border-top-width: 1px; border-top-color: black;" + revision = mt.revision(id) + diff_links = ' | '.join([link("diff", [t[1], id], "diff") for t in revision.get('old_revision', [])]) + style = "" + req.write('\n' % (style, ago, quicklog, link("revision", id, "revision info"), link("manifest", id, "browse files"), diff_links)) + req.write(certinfo) + elif output == 'rss': + req.write('\n') + +if output == 'html': + req.write('
%s:%s
%s ago: %s
%s | %s | %s
') + + c = [] + if page != 1: + c.append('Newer changes' % (urllib.quote(branch), page-1)) + if not no_next: + c.append('Older changes' % (urllib.quote(branch), page+1)) + req.write('

' + ' | '.join(c) + '

') + +%> + +<% + +if output == 'html': + req.write(template.footer(info)) +elif output == 'rss': + req.write('''\ +
+
+''') +%> + --- www/viewmtn/builtpython.sh +++ www/viewmtn/builtpython.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +# generate the list of enscript formatting options +LANGS=enscriptlangs.py +echo -n 'enscript_langs = [' > "$LANGS" +for i in `enscript --help-highlight | grep Name | awk {'print $2'}`; do + echo -n "'$i', " >> "$LANGS" +done; echo ']' >> "$LANGS" + +# generate the help file data +AUTHORS=authors.py +echo -n "authors='''" > "$AUTHORS" +cat AUTHORS >> "$AUTHORS" +echo "'''" >> "$AUTHORS" + + --- www/viewmtn/common.py +++ www/viewmtn/common.py @@ -0,0 +1,239 @@ + +import datetime +import urllib +import pydoc +import time + +escape_function = pydoc.HTMLRepr().escape + +def type_wrapper(e, x): + if x == None: + return "" + elif type(x) == type([]): + return '
'.join(map(e, x)) + else: + return e(x) + +def parse_timecert(value): + return apply(datetime.datetime, time.strptime(value, "%Y-%m-%dT%H:%M:%S")[:6]) + +def get_branch_links(mt, branches): + if len(branches) > 1: + branch_links = "branches " + else: + branch_links = "branch " + links = [] + for branch in branches: + links.append(link(mt, "branch", branch)) + return branch_links + ', '.join(links) + +def extract_cert_from_certs(certs, certname, as_list=False): + rv = [] + for cert in certs: + name, value = None, None + for k, v in cert: + if k == "name": name = v + elif k == "value": value = v + if name == None or value == None: continue + if name == certname: + if not as_list: + return value + else: + rv.append(value) + return rv + +def determine_date(certs): + dateval = extract_cert_from_certs(certs, "date") + if dateval == None: + return None + else: + return parse_timecert(dateval) + +def quicklog(value): + hq = html_escape() + rv = hq(value.strip().split('\n')[0]) + if rv.startswith('*'): + rv = rv[1:].strip() + return rv + +def ago_string(event, now): + def plural(v, singular, plural): + if v == 1: + return "%d %s" % (v, singular) + else: + return "%d %s" % (v, plural) + now = datetime.datetime.utcnow() + ago = now - event + if ago.days > 0: + rv = "%s, %s" % (plural(ago.days, "day", "days"), + plural(ago.seconds / 3600, "hour", "hours")) + elif ago.seconds > 3600: + hours = ago.seconds / 3600 + minutes = (ago.seconds - (hours * 3600)) / 60 + rv = "%s, %s" % (plural(hours, "hour", "hours"), + plural(minutes, "minute", "minutes")) + else: + minutes = ago.seconds / 60 + seconds = (ago.seconds - (minutes * 60)) + rv = "%s, %s" % (plural(minutes, "minute", "minutes"), + plural(seconds, "second", "seconds")) + return rv + +def link(mt, link_type, link_to, description = None, no_quote = False): + hq = html_escape() + if not no_quote and description != None: description = hq(description) + if link_type == "revision": + rv = '' % (urllib.quote(link_to)) + if description != None: rv += description + else: rv += hq(link_to[:8]) + ".." + rv += '' + if description == None: rv = '[' + rv + ']' + elif link_type == "diff" or link_type == "download_diff": + link_to = map(urllib.quote, filter(lambda x: x != None, link_to)) + if link_type == "diff": + handler = "diff.psp" + else: + handler = "getdiff.py" + uri = '%s?id1=%s&id2=%s' % (handler, link_to[0], link_to[1]) + if len(link_to) == 3: + uri += '&fname=%s' % (link_to[2]) + rv = '' + if description != None: rv += description + else: rv += "diff" + rv += '' + elif link_type == "download": + if type(link_to) == type([]): + rv = '' % (urllib.quote(link_to[0]), + urllib.quote(link_to[1])) + link_id = link_to[0] + else: + rv = '' % (urllib.quote(link_to)) + link_id = link_to + if description != None: rv += description + "" + else: rv = "[" + rv + hq(link_id[:8]) + ".." + "]" + elif link_type == "file": + revision_id, path = link_to + rv = '' % (urllib.quote(revision_id), + urllib.quote(path)) + if description != None: rv += description + "" + else: rv = "[" + rv + hq(path + '@' + revision_id[:8]) + ".." + "]" + elif link_type == "fileinbranch": + branch, path = link_to + rv = '' % (urllib.quote(branch), + urllib.quote(path)) + if description != None: rv += description + "" + else: rv = "[" + rv + hq(path + '@' + branch) + "]" + elif link_type == "branch": + rv = '' % (urllib.quote(link_to)) + if description != None: rv += description + else: rv += hq(link_to) + rv += '' + elif link_type == "tar": + rv = '' % (urllib.quote(link_to)) + if description != None: rv += description + else: rv = "tar of [" + rv + hq(link_to[:8]) + "..]" + "]" + rv += '' + elif link_type == "headofbranch": + rv = '' % (urllib.quote(link_to)) + if description != None: rv += description + else: rv += "head of " + hq(link_to) + rv += '' + elif link_type == "manifest": + if type(link_to) == type([]): + link_to, path = link_to + rv = '' % (urllib.quote(link_to), urllib.quote(path)) + else: + rv = '' % (urllib.quote(link_to)) + if description != None: rv += description + else: rv += hq(link_to[:8]) + ".." + rv += '' + if description == None: rv = '[' + rv + ']' + else: + rv = 'Unknown link type: %s' % (hq(link_type)) + return '%s' % (hq(link_type+'Link'), rv) + +def html_escape(): + "returns a function stolen from pydoc that can be used to escape HTML" + return lambda x: type_wrapper(escape_function, x) + +from enscriptlangs import enscript_langs +from utility import run_command +import mimetypes +import config +import pipes + +# is it binary? +def is_binary(str): + nontext_chars = "\x01\x02\x03\x04\x05\x06\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1c\x1d\x1e\x1f" + check = {} + for char in nontext_chars: + check[char] = True + for i in str: + if check.has_key(i): return True + return False + +# hm, later on might make this be some javascript that does an call back to the server. +# then could have a pull down to let people choose which enscript encoding to use, and +# just update the DOM with the new data. +def colourise_code(req, hq, path, contents, filter=None): + mime_type = mimetypes.guess_type(path)[0] + if mime_type == None: mime_type = 'text/plain' + if mime_type == 'image/png' or mime_type == 'image/jpeg' or mime_type == 'image/gif': + display_as_image = True + else: display_as_image = False + + # okay; can we guess a valid enscript filter to run this through? + tsp = mime_type.split('/', 1) + if filter == None and tsp[0] == 'text': + candidate = tsp[1] + if candidate.startswith('x-'): candidate = candidate[2:] + if candidate.endswith('src'): candidate = candidate[:-3] + if candidate.endswith('hdr'): candidate = candidate[:-3] + if candidate == 'c++': candidate = 'cpp' # ugly + if candidate in enscript_langs: filter = candidate + if filter == None: + # heh, will at least work for lua files + last_dot = path.rfind('.') + if last_dot == -1: last_dot = 0 + candidate = path[last_dot:] + if candidate in enscript_langs: filter = candidate + + # if no filter then let's check if it's binary or not; if not binary + # we'll just treat it as text; otherwise display a warning and a download + # link + if filter == None and not is_binary(contents): + filter = 'text' + + req.write('''
''') + if display_as_image: + req.write('''') + def stop_code(): + req.write('') + def text(): + start_code() + req.write(hq(contents)) + stop_code() + def enscript(): + command = config.enscript_path + ' -o - --color --language=html' + command += ' --highlight=%s' % (pipes.quote(filter)) + result = run_command(command, to_child=contents) + if result['exitcode'] != 0: + raise Exception('Error running enscript (%s) : "%s".' % (hq(command), hq(result['childerr']))) + in_contents = False + for line in result['fromchild'].split('\n'): + if line.startswith('
'):
+		    in_contents = True
+		    start_code()
+		elif line.startswith('
'): + in_contents = False + stop_code() + elif in_contents: + req.write(line + '\r\n') + if filter == "text": text() + else: enscript() + else: + req.write('''

This file seems to binary and not suitable for display in the browser. You must %s the file and use a suitable viewer.

''' % (link("download", [matching_file_id, path], "download"))) + req.write('''
''') --- www/viewmtn/config.py +++ www/viewmtn/config.py @@ -0,0 +1,72 @@ +# +# config.py +# +# This python script is imported by ViewMTN +# Note that changes to the script may not be +# noticed immediately by ViewMTN, as mod_python +# caches imported Python modules. +# +# If config changes are not picked up, reloading +# the web server should solve the issue. +# +# If you want to run multiple viewmtn installs from +# a single apache server, you might want to look at +# giving them seperate python interpreter instances, +# ie set PythonInterpreter viewmtn1, viewmtn2 etc +# in .htaccess. + +import sys +import os + +# the base URL of this install 'http://%s%s/' % (req.hostname, os.path.dirname(req.uri))# +def base_url(uri): + d = os.path.dirname(os.path.dirname(uri)) + return 'http://localhost/~timothy/host/projects/%s/viewmtn/' % os.path.basename(d) + +# the path to the 'monotone' binary +monotone = '/home/timothy/bin/monotone' + +# the monotone database to be shared out +# obviously, everything in this database might +# become public if something goes wrong; probably +# a good idea not to leave your private key in it +def dbfile(uri): + d = os.path.dirname(os.path.dirname(uri)) + return '/home/timothy/experiment/projects/%s/database.viewmtn' % os.path.basename(d) + +# where to find GNOME icons (used in manifest listing) +gnome_mimetype_icon_path = '/usr/share/icons/gnome/' + +# and where they are on the web +gnome_mimetype_uri = 'mimetypes/' + +# where to find GNU enscript +enscript_path = '/usr/bin/enscript' + +graphopts = { + # a directory (must be writable by the web user) + # in which viewmtn can output graph files + # (you should set up a cronjob to delete old ones + # periodically) + 'directory' : '/var/viewmtn-graphs', + + # a URL, relative or absolute, at which the files + # in the 'graphdir' directory can be found. Should + # end in a '/' character + 'uri' : 'graph/', + + # the path to the 'dot' program + 'dot' : '/usr/bin/dot', + + # options to use for nodes in the dot input file + # we generate. + 'nodeopts' : { 'fontname' : 'Windsor', + 'fontsize' : '8', + 'shape' : 'box', + 'height' : '0.3', + 'spline' : 'true', + 'style' : 'filled', + 'fillcolor' : '#dddddd' } +} + + --- www/viewmtn/config.py.example +++ www/viewmtn/config.py.example @@ -0,0 +1,67 @@ +# +# config.py +# +# This python script is imported by ViewMTN +# Note that changes to the script may not be +# noticed immediately by ViewMTN, as mod_python +# caches imported Python modules. +# +# If config changes are not picked up, reloading +# the web server should solve the issue. +# +# If you want to run multiple viewmtn installs from +# a single apache server, you might want to look at +# giving them seperate python interpreter instances, +# ie set PythonInterpreter viewmtn1, viewmtn2 etc +# in .htaccess. + +import sys + +# the base URL of this install +base_url = 'http://localhost/~grahame/viewmtn/' + +# the path to the 'monotone' binary +monotone = '/opt/monotone/bin/monotone' + +# the monotone database to be shared out +# obviously, everything in this database might +# become public if something goes wrong; probably +# a good idea not to leave your private key in it +dbfile = '/path/to/monotone.db' + +# where to find GNOME icons (used in manifest listing) +gnome_mimetype_icon_path = '/path/to/share/icons/gnome/' + +# and where they are on the web +gnome_mimetype_uri = 'mimetypes/' + +# where to find GNU enscript +enscript_path = '/usr/bin/enscript' + +graphopts = { + # a directory (must be writable by the web user) + # in which viewmtn can output graph files + # (you should set up a cronjob to delete old ones + # periodically) + 'directory' : '/path/to/graph/directory', + + # a URL, relative or absolute, at which the files + # in the 'graphdir' directory can be found. Should + # end in a '/' character + 'uri' : 'graph/', + + # the path to the 'dot' program + 'dot' : '/usr/bin/dot', + + # options to use for nodes in the dot input file + # we generate. + 'nodeopts' : { 'fontname' : 'Windsor', + 'fontsize' : '8', + 'shape' : 'box', + 'height' : '0.3', + 'spline' : 'true', + 'style' : 'filled', + 'fillcolor' : '#dddddd' } +} + + --- www/viewmtn/diff.psp +++ www/viewmtn/diff.psp @@ -0,0 +1,65 @@ +<% + +import mimetypes +import datetime +import monotone +import common +import config +import urllib +import pipes +import time +import os +from enscriptlangs import enscript_langs +from html import get_icon, TableWriter +from utility import run_command +from common import colourise_code + +# file.psp ; provide information about a file +# if possible, display it (with syntax highlighting, etc.) +# provide a download link + +psp.set_error_page("error.psp") + +if not form.has_key('id1') and not form.has_key('id2'): + raise Exception("Revision IDs not specified.") + +id1, id2 = form['id1'], form['id2'] +if not monotone.is_valid_id(id1) or not monotone.is_valid_id(id2): + raise Exception("Specified revision IDs are not valid.") + +# should support diffing more than one file eventually +fname = form.get('fname', None) +if fname != None: + title = 'of file %s ' % hq(fname) + files = [fname] +else: + title = '' + files = [] + +title = 'Diff %sbetween revisions [%s..] and [%s..]' % (title, hq(id1[:8]), hq(id2[:8])) + +info = { 'title' : title } +req.write(template.header(info)) + +if fname != None: + file_str = 'of file ' + link("file", [id2, fname], fname) + ' ' +else: + file_str = '' +req.write('

The unified diff %sbetween revisions %s and %s is displayed below.

' % (file_str, + link("revision", id1), + link("revision", id2))) + +if fname != None: + req.write('

You might wish to view the %s between these revisions, without the restriction to this particular file.

' % link('diff', [id1, id2], 'diff')) + +req.write('

You can also %s this diff verbatim.

' % link("download_diff", [id1, id2, fname], "download")) + +### FIXME FIXME +### this means having _the entire file_ in memory +### which is pointless and dumb. +contents = mt.diff(id1, id2, files) +colourise_code(req, hq, 'a.diff', contents, filter='diffu') + +req.write(template.footer(info)) + +%> --- www/viewmtn/enscriptlangs.py +++ www/viewmtn/enscriptlangs.py @@ -0,0 +1,1 @@ +enscript_langs = ['ada', 'asm', 'awk', 'bash', 'changelog', 'cpp', 'csh', 'c', 'delphi', 'diffs', 'diff', 'diffu', 'dylan', 'eiffel', 'elisp', 'erlang', 'forth', 'fortran_pp', 'fortran', 'haskell', 'html', 'icon', 'idl', 'inf', 'javascript', 'java', 'ksh', 'lua', 'm4', 'mail', 'makefile', 'matlab', 'nroff', 'oberon2', 'objc', 'outline', 'oz', 'pascal', 'perl', 'postscript', 'pyrex', 'python', 'rfc', 'scheme', 'sh', 'skill', 'Smalltalk', 'sml', 'sql', 'states', 'synopsys', 'tcl', 'tcsh', 'tex', 'vba', 'verilog', 'vhdl', 'vrml', 'wmlscript', 'zsh', ] --- www/viewmtn/error.psp +++ www/viewmtn/error.psp @@ -0,0 +1,51 @@ + + +Error + + + +<% + +import common +hq = common.html_escape() + +# now; if they raised a plain Exception() then we shall display a simple error +# message without a traceback: this is an intentionally raised Exception rather than +# a program error +e_type, e_value, e_traceback = sys.exc_info() + +if (str(e_type) == "exceptions.Exception"): + # this is a deliberate exception, pretty print +%> + +

+An error has occurred: +

+ +

+<%= hq(str(e_value)) %> +

+ +

+If necessary, please report this error to the system administrator. You may wish to return +to the index page. +

+ +<% +else: + # show the raw exception +%> +

Exception:

+ +

+<% + import traceback + mesg = traceback.format_exception(e_type, e_value, e_traceback) + mesg = map(hq, mesg) + req.write('

' + '
'.join(mesg) + '
') + +%> +

+ + + --- www/viewmtn/file.psp +++ www/viewmtn/file.psp @@ -0,0 +1,82 @@ +<% + +import mimetypes +import datetime +import monotone +import common +import config +import urllib +import pipes +import time +import os +from enscriptlangs import enscript_langs +from html import get_icon, TableWriter +from utility import run_command +from common import colourise_code, get_branch_links + +# file.psp ; provide information about a file +# if possible, display it (with syntax highlighting, etc.) +# provide a download link + +psp.set_error_page("error.psp") + +if not form.has_key('id'): + raise Exception("No revision ID specified.") + +id = form['id'] +if not monotone.is_valid_id(id): + raise Exception("Specified revision ID is not valid.") + +if not form.has_key('path'): + raise Exception("No path specified.") +path = form['path'] + +certs = mt.certs(id) +branches = common.extract_cert_from_certs(certs, "branch", as_list=True) + +revision = mt.revision(id) +if not revision.has_key('new_manifest'): + raise Exception("There is no manifest in this revision ID.") +manifest_id = revision['new_manifest'][0][1] +manifest = mt.manifest(manifest_id) + +matching_file_id = None +for file_id, filename in manifest: + if filename == path: + matching_file_id = file_id + break +if matching_file_id == None: + raise Exception("File not found in this revision.") + +link_components = "" +components = [''] +components += path.split('/') +for idx, component in enumerate(components[:-1]): + link_components += link("manifest", [id, '/'.join(components[:idx+1])], component + '/') +link_components += components[-1] +branch_links = get_branch_links(mt, branches) + +info = { + 'title' : "File '%s' in revision %s of %s" % (link_components, + link("revision", id), + branch_links) + } +req.write(template.header(info)) + +req.write('

For more information about the revision containing this file, see its %s page. For other files contained in this revision, see the %s.

' % \ + (link("revision", id, "revision"), + link("manifest", id, "manifest"))) + +req.write('

You can also %s the file verbatim. In addition, this %s will always download the latest revision of the file.

' \ + % (link("download", [matching_file_id, path], "download"), + link("fileinbranch", [branches[0], path], "link"))) + +### FIXME FIXME +### this means having _the entire file_ in memory +### which is pointless and dumb. +contents = mt.file(matching_file_id) +colourise_code(req, hq, path, contents) + +req.write(template.footer(info)) + +%> --- www/viewmtn/fileinbranch.psp +++ www/viewmtn/fileinbranch.psp @@ -0,0 +1,70 @@ +<% + +import monotone +import urllib + +psp.set_error_page("error.psp") + +if not form.has_key('branch'): + raise Exception("No branch specified.") +if not form.has_key('path'): + raise Exception("No path specified.") +branch = form['branch'] +path = form['path'] + +# fileinbranch.psp +# +# determine the latest version of the file +# in a branch (and present a choice iff there +# are multiple heads and the files difer in them) + +heads = mt.heads(branch) +if len(heads) == 0: + raise Exception("No head ID can be determined for this branch.") + +# build a hash with a list of all files in each head which +# match PATH and their file ID. +file_version = {} +for id in heads: + revision = mt.revision(id) + manifest_id = revision['new_manifest'][0][1] + file_revisions = filter(lambda x: x[1] == path, mt.manifest(manifest_id)) + if len(file_revisions) > 1: + raise Exception("More than one file matches path?") + elif len(file_revisions) == 1: + file_version[id] = file_revisions[0][0] +if len(file_version.values()) == 0: + raise Exception("No file such file found in this branch.") + +unique = True +last = None +values = file_version.values() +for file_revision in values: + if last != None and file_revision != last: + unique = False + break + last = file_revision +if unique: + psp.redirect("getfile.py?id=%s&path=%s" % (urllib.quote(values[0]), urllib.quote(path))) +else: + info = {'title' : "Latest version of %s in branch %s" % (hq(path), hq(branch))} + req.write(template.header(info)) +%> +

+The branch you have selected has multiple head revisions, and the file you are attempting +to access differs between them. The head revisions are listed below, along with +links to download "<%=hq(path)%>" from that revision. +

+ + + +<% + for id in file_version.keys(): + req.write('' % (link("revision", id), link("download", [file_version[id], path], "view file"))) + +%> +
Head revisionView file
%s%s
+ +<% +req.write(template.footer(info)) +%> --- www/viewmtn/headofbranch.psp +++ www/viewmtn/headofbranch.psp @@ -0,0 +1,60 @@ +<% + +import urllib + +psp.set_error_page("error.psp") + +if not form.has_key('branch'): + raise Exception("No branch specified.") +branch = form['branch'] + +# headofbranch.psp +# +# we want to redirect the user to revision.psp displaying +# the head ID of this branch. +# +# if more than one HEAD exists, prompt the user +# and allow them to choose which one to view +# otherwise, redirect to revision.psp and show that +# ID. + +heads = mt.heads(branch) +if len(heads) == 0: + raise Exception("No head ID can be determined for this branch.") +elif len(heads) == 1: + # a single head ID; redirect to it + id = heads[0] + psp.redirect("revision.psp?id=%s" % (urllib.quote(id))) +else: + info = {'title' : "Branch details for %s" % (hq(branch))} + req.write(template.header(info)) +%> +

+The following head IDs are available. Please select one to view. You might find the +<%= link("branch", branch, "recent changes") %> page for this branch helpful. +

+ + + +<% + for id in heads: + certs = mt.certs(id) + # find the author cert + author = None + for cert in certs: + name, value = None, None + for k, v in cert: + if k == "name": name = v + elif k == "value": value = v + if name == None or value == None: continue + if name =="author": + author = value + req.write('' % (link("revision", id), hq(author or ""))) + +%> +
Head revisionRevision author
%s%s
+ +<% +req.write(template.footer(info)) +%> + --- www/viewmtn/help.psp +++ www/viewmtn/help.psp @@ -0,0 +1,30 @@ +<% + +from authors import authors + +psp.set_error_page("error.psp") +info = { 'title' : "Help" } +req.write(template.header(info)) + +%> + +

+ViewMTN is a web interface to the Monotone revision control +system. These web pages provide various methods to access the data +controlled within a particular Monotone database. +

+ +

+To make full use of this web interface, it is recommended that you read +the Monotone +manual.

+ +

+Feature suggestions, bug reports and patches are welcome. Please go +to the ViewMTN +software page and follow the contact instructions there.

+ +<% +req.write(template.footer(info)) +%> --- www/viewmtn/html.py +++ www/viewmtn/html.py @@ -0,0 +1,100 @@ + +import mimetypes +import urllib +import common +import config +import time +import os +from viewmtn import release + +hq = common.html_escape() + +def get_icon(filename, mime_type=None, is_directory=False): + if config.gnome_mimetype_icon_path == None: + return None + if is_directory: + filename = 'gnome-fs-directory.png' + else: + if mime_type == None: + mime_type = mimetypes.guess_type(filename)[0] + if not mime_type: + mime_type = "text" + if mime_type.endswith('hdr'): + mime_type = mime_type[:-3] + 'src' + # some specific fixups + filename = 'gnome-mime-' + mime_type.replace('/', '-') + '.png' + if os.access(os.path.join(config.gnome_mimetype_icon_path, filename), os.R_OK): + return '/'.join([config.gnome_mimetype_uri, filename]) + else: + return None + +class TableWriter: + def __init__(self, req): + self.req = req + self.odd = False + def start(self): + self.req.write('') + def write(self, row, is_header=False): + if self.odd: c = 'odd' + else: c = 'even' + if is_header: self.odd = False + else: self.odd = not self.odd + self.req.write('' % (c) + row + '\n') + def stop(self): + self.req.write('
') + +def strip_html_from(t): + in_tag, rv = False, '' + for c in t: + if in_tag and c == '>': + in_tag = False + elif not in_tag and c == '<': + in_tag = True + elif not in_tag: + rv += c + return rv + +class Template: + def header(self, info): + if not info.has_key("title"): info['title'] = "untitled" + if not info.has_key("extra_header"): info['extra_header'] = '' + if not info.has_key("short_title"): + info["short_title"] = strip_html_from(info["title"]) + + if info.has_key('branch_rss'): + for branch in info['branch_rss']: + info['extra_header'] += '\n' % (urllib.quote(branch), hq(branch)) + return """\ + + + + + ViewMTN: %(short_title)s + + + + + %(extra_header)s + + + + + +

%(title)s

+ """ % (info) + def footer(self, info): + rv = """ + + ''' % (release, time.strftime("%a, %d %b %Y %H:%M:%S %Z", time.localtime())) + return rv --- www/viewmtn/index.psp +++ www/viewmtn/index.psp @@ -0,0 +1,54 @@ +<% + +psp.set_error_page("error.psp") +info = { 'title' : "Branches" } +req.write(template.header(info)) + +branches = mt.branches() + +%> + +<% +if len(branches) == 0: + # no branches +%> + +

+There are no branches in the provided monotone database. There is +therefore nothing for ViewMTN to display! +

+ +<% +else: + # okay, so display the branches +%> +

+There are <%=len(branches)%> branches in this +Monotone database. Each branch +might represent an individual project. If you are new to Monotone, the +Monotone tutorial +may help you understand these pages better. +

+ +

+Select a branch from the list below to view recent changes that have occured +within it. The list of tagged revisions could be +helpful if you are looking for a particular revision. +

+ + + +<% + branches.sort() + for branch in branches: + req.write('' % (link("branch", branch, branch))) + +%> +
Branch
%s
+ +<% + +req.write(template.footer(info)) + +%> + --- www/viewmtn/json.py +++ www/viewmtn/json.py @@ -0,0 +1,310 @@ +import string +import types + +## json.py implements a JSON (http://json.org) reader and writer. +## Copyright (C) 2005 Patrick D. Logan +## Contact mailto:address@hidden +## +## This library is free software; you can redistribute it and/or +## modify it under the terms of the GNU Lesser General Public +## License as published by the Free Software Foundation; either +## version 2.1 of the License, or (at your option) any later version. +## +## This library 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 +## Lesser General Public License for more details. +## +## You should have received a copy of the GNU Lesser General Public +## License along with this library; if not, write to the Free Software +## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +class _StringGenerator(object): + def __init__(self, string): + self.string = string + self.index = -1 + def peek(self): + i = self.index + 1 + if i < len(self.string): + return self.string[i] + else: + return None + def next(self): + self.index += 1 + if self.index < len(self.string): + return self.string[self.index] + else: + raise StopIteration + def all(self): + return self.string + +class WriteException(Exception): + pass + +class ReadException(Exception): + pass + +class JsonReader(object): + hex_digits = {'A': 10,'B': 11,'C': 12,'D': 13,'E': 14,'F':15} + escapes = {'t':'\t','n':'\n','f':'\f','r':'\r','b':'\b'} + + def read(self, s): + self._generator = _StringGenerator(s) + result = self._read() + return result + + def _read(self): + self._eatWhitespace() + peek = self._peek() + if peek is None: + raise ReadException, "Nothing to read: '%s'" % self._generator.all() + if peek == '{': + return self._readObject() + elif peek == '[': + return self._readArray() + elif peek == '"': + return self._readString() + elif peek == '-' or peek.isdigit(): + return self._readNumber() + elif peek == 't': + return self._readTrue() + elif peek == 'f': + return self._readFalse() + elif peek == 'n': + return self._readNull() + elif peek == '/': + self._readComment() + return self._read() + else: + raise ReadException, "Input is not valid JSON: '%s'" % self._generator.all() + + def _readTrue(self): + self._assertNext('t', "true") + self._assertNext('r', "true") + self._assertNext('u', "true") + self._assertNext('e', "true") + return True + + def _readFalse(self): + self._assertNext('f', "false") + self._assertNext('a', "false") + self._assertNext('l', "false") + self._assertNext('s', "false") + self._assertNext('e', "false") + return False + + def _readNull(self): + self._assertNext('n', "null") + self._assertNext('u', "null") + self._assertNext('l', "null") + self._assertNext('l', "null") + return None + + def _assertNext(self, ch, target): + if self._next() != ch: + raise ReadException, "Trying to read %s: '%s'" % (target, self._generator.all()) + + def _readNumber(self): + isfloat = False + result = self._next() + peek = self._peek() + while peek is not None and (peek.isdigit() or peek == "."): + isfloat = isfloat or peek == "." + result = result + self._next() + peek = self._peek() + try: + if isfloat: + return float(result) + else: + return int(result) + except ValueError: + raise ReadException, "Not a valid JSON number: '%s'" % result + + def _readString(self): + result = "" + assert self._next() == '"' + try: + while self._peek() != '"': + ch = self._next() + if ch == "\\": + ch = self._next() + if ch in 'brnft': + ch = self.escapes[ch] + elif ch == "u": + ch4096 = self._next() + ch256 = self._next() + ch16 = self._next() + ch1 = self._next() + n = 4096 * self._hexDigitToInt(ch4096) + n += 256 * self._hexDigitToInt(ch256) + n += 16 * self._hexDigitToInt(ch16) + n += self._hexDigitToInt(ch1) + ch = unichr(n) + elif ch not in '"/\\': + raise ReadException, "Not a valid escaped JSON character: '%s' in %s" % (ch, self._generator.all()) + result = result + ch + except StopIteration: + raise ReadException, "Not a valid JSON string: '%s'" % self._generator.all() + assert self._next() == '"' + return result + + def _hexDigitToInt(self, ch): + try: + result = self.hex_digits[ch.upper()] + except KeyError: + try: + result = int(ch) + except ValueError: + raise ReadException, "The character %s is not a hex digit." % ch + return result + + def _readComment(self): + assert self._next() == "/" + second = self._next() + if second == "/": + self._readDoubleSolidusComment() + elif second == '*': + self._readCStyleComment() + else: + raise ReadException, "Not a valid JSON comment: %s" % self._generator.all() + + def _readCStyleComment(self): + try: + done = False + while not done: + ch = self._next() + done = (ch == "*" and self._peek() == "/") + if not done and ch == "/" and self._peek() == "*": + raise ReadException, "Not a valid JSON comment: %s, '/*' cannot be embedded in the comment." % self._generator.all() + self._next() + except StopIteration: + raise ReadException, "Not a valid JSON comment: %s, expected */" % self._generator.all() + + def _readDoubleSolidusComment(self): + try: + ch = self._next() + while ch != "\r" and ch != "\n": + ch = self._next() + except StopIteration: + pass + + def _readArray(self): + result = [] + assert self._next() == '[' + done = self._peek() == ']' + while not done: + item = self._read() + result.append(item) + self._eatWhitespace() + done = self._peek() == ']' + if not done: + ch = self._next() + if ch != ",": + raise ReadException, "Not a valid JSON array: '%s' due to: '%s'" % (self._generator.all(), ch) + assert ']' == self._next() + return result + + def _readObject(self): + result = {} + assert self._next() == '{' + done = self._peek() == '}' + while not done: + key = self._read() + if type(key) is not types.StringType: + raise ReadException, "Not a valid JSON object key (should be a string): %s" % key + self._eatWhitespace() + ch = self._next() + if ch != ":": + raise ReadException, "Not a valid JSON object: '%s' due to: '%s'" % (self._generator.all(), ch) + self._eatWhitespace() + val = self._read() + result[key] = val + self._eatWhitespace() + done = self._peek() == '}' + if not done: + ch = self._next() + if ch != ",": + raise ReadException, "Not a valid JSON array: '%s' due to: '%s'" % (self._generator.all(), ch) + assert self._next() == "}" + return result + + def _eatWhitespace(self): + p = self._peek() + while p is not None and p in string.whitespace or p == '/': + if p == '/': + self._readComment() + else: + self._next() + p = self._peek() + + def _peek(self): + return self._generator.peek() + + def _next(self): + return self._generator.next() + +class JsonWriter(object): + + def _append(self, s): + self._results.append(s) + + def write(self, obj, escaped_forward_slash=False): + self._escaped_forward_slash = escaped_forward_slash + self._results = [] + self._write(obj) + return "".join(self._results) + + def _write(self, obj): + ty = type(obj) + if ty is types.DictType: + n = len(obj) + self._append("{") + for k, v in obj.items(): + self._write(k) + self._append(":") + self._write(v) + n = n - 1 + if n > 0: + self._append(",") + self._append("}") + elif ty is types.ListType or ty is types.TupleType: + n = len(obj) + self._append("[") + for item in obj: + self._write(item) + n = n - 1 + if n > 0: + self._append(",") + self._append("]") + elif ty is types.StringType or ty is types.UnicodeType: + self._append('"') + obj = obj.replace('\\', r'\\') + if self._escaped_forward_slash: + obj = obj.replace('/', r'\/') + obj = obj.replace('"', r'\"') + obj = obj.replace('\b', r'\b') + obj = obj.replace('\f', r'\f') + obj = obj.replace('\n', r'\n') + obj = obj.replace('\r', r'\r') + obj = obj.replace('\t', r'\t') + self._append(obj) + self._append('"') + elif ty is types.IntType or ty is types.LongType: + self._append(str(obj)) + elif ty is types.FloatType: + self._append("%f" % obj) + elif obj is True: + self._append("true") + elif obj is False: + self._append("false") + elif obj is None: + self._append("null") + else: + raise WriteException, "Cannot write in JSON: %s" % repr(obj) + +def write(obj, escaped_forward_slash=False): + return JsonWriter().write(obj, escaped_forward_slash) + +def read(s): + return JsonReader().read(s) --- www/viewmtn/manifest.psp +++ www/viewmtn/manifest.psp @@ -0,0 +1,205 @@ +<% + +import datetime +import monotone +import common +import config +import urllib +import time +from html import get_icon, TableWriter +from common import get_branch_links + +# +# manifest.psp +# display all information we can about a particular manifest +# + +psp.set_error_page("error.psp") + +if not form.has_key('id'): + raise Exception("No revision ID specified.") + +id = form['id'] +if not monotone.is_valid_id(id): + raise Exception("Specified revision ID is not valid.") + +if not form.has_key('path'): + path = '' +else: + path = form['path'] +if path.startswith('/'): path = path[1:] +if path.endswith('/'): path = path[-1:] + +certs = mt.certs(id) +revision = mt.revision(id) +if not revision.has_key('new_manifest'): + raise Exception("There is no manifest in this revision ID.") +manifest_id = revision['new_manifest'][0][1] +manifest = mt.manifest(manifest_id) + +link_components = "" +components = [''] +components += path.split('/') +for idx, component in enumerate(components[:-1]): + link_components += link("manifest", [id, '/'.join(components[:idx+1])], component + '/') +link_components += components[-1] +branches = common.extract_cert_from_certs(certs, "branch", as_list=True) +branch_links = get_branch_links(mt, branches) + +info = { + 'title' : "Dir '%s' in revision %s of %s" % (link_components, + link("revision", id), + branch_links) + } +req.write(template.header(info)) +%> + +

+This page shows files contained within a particular revision; for more information +about that revision see its page: <%= link("revision", id) %>. +

+ +

+All <%=len(manifest)%> files in this manifest can be downloaded together in a <%=link("tar", manifest_id, "tar archive")%>. +

+ +<% + +# filter this list of files into two lists; +# subdirectories of 'path' +# files in 'path' +# each will contain entries relative to 'path' +# +# NB: the following code will break (perhaps) if monotone ever supports +# empty subdirectories. +subdirs, files, last_dir = [], [], None +# root dir is depth 0, subdir of that 1, .. +if path == '': + sp = [] +else: + sp = path.split('/') +depth = len(sp) + +for file_id, filename in manifest: + parts = filename.split('/') + + if len(parts) < depth or parts[:depth] != sp[:depth]: + continue + + if len(parts) > depth + 1 and parts[depth] != last_dir: + subdirs.append((parts[depth], '/'.join(parts[:depth+1]))) + last_dir = parts[depth] + elif len(parts) == depth + 1: + files.append((parts[-1], file_id, filename)) + +# hopefully this will eventually be less ugly +# +# we need to find out the revision in which each of the objects in +# 'files' was last touched. so iterate; get the certs for each ancestor +# of the current revision, until we have that information for every +# file in 'files' + +ancestors = mt.toposort(mt.ancestors([id])) +ancestors.append(id) +ancestors.reverse() # so we can iterate back through time.. +file_to_rev = {} + +for name, file_id, file in files: + file_to_rev[file] = None + +found = 0 +for ancestor in ancestors: + rev = mt.revision(ancestor) + if rev.has_key('old_revision') and len(rev['old_revision']) > 1: + # this is a merge, exclude from our search + continue + for type in rev.keys(): + for stanza in rev[type]: + affected = None + if type == "patch" or type == "add_file" or type == "delete_file" or type == "delete_dir": + affected = stanza[1] + elif type == "rename_file" or type == "rename_dir": affected = stanza[3] + if affected != None and file_to_rev.has_key(affected) and file_to_rev[affected] == None: + file_to_rev[affected] = ancestor + found += 1 + if found >= len(files): break + +revinfo = {} +now = datetime.datetime.utcnow() +for filename in file_to_rev.keys(): + rev = file_to_rev[filename] + + if rev == None: + certs = [] + else: + certs = mt.certs(rev) + + ago, author, certdate, quicklog = None, None, None, None + for cert in certs: + name, value = None, None + for k, v in cert: + if k == "name": name = v + elif k == "value": value = v + if name == None or value == None: continue + if name == "date": + certdate = apply(datetime.datetime, time.strptime(value, "%Y-%m-%dT%H:%M:%S")[:6]) + elif name == "changelog": + quicklog = value.strip().split('\n')[0] + if quicklog.startswith('*'): quicklog = quicklog[1:].strip() + elif name == "author": + author = value + if certdate: + ago = now - certdate + if ago.days > 0: + ago = "%d days" % (ago.days) + elif ago.seconds > 3600: + hours = ago.seconds / 3600 + minutes = (ago.seconds - (hours * 3600)) / 60 + ago = "%d hours" % (hours) + else: + minutes = ago.seconds / 60 + seconds = (ago.seconds - (minutes * 60)) + ago = "%d minutes" % (minutes) + revinfo[rev] = (ago, author, quicklog) + +if depth != 0: + subdirs = [('..', '/'.join(sp[:-1]))] + subdirs + +tr = TableWriter(req) +tr.start() +tr.write('NameAgeAuthorLast log entry', is_header=True) +icon_uri = get_icon('', is_directory=True) +for name, subdir in subdirs: + tr.write('%s' % (icon_uri, + link("manifest", + [id, subdir], name))) +for name, file_id, file in files: + icon_uri = get_icon(file) + if icon_uri != None: + icon_code = '' % (icon_uri) + else: + icon_code = '' + rev = file_to_rev.get(file, None) + if not rev: + tr.write('%s %s' % (icon_code, link("file", [id, file], name))) + else: + age, author, quicklog = revinfo[rev] + author = hq(author or "").replace(' ', ' ') + quicklog = hq(quicklog or "") + tr.write('%s %s %s%s%s' % (icon_code, \ + link("file", [id, file], name), \ + link("revision", rev, (age or "").replace(' ', ' '), no_quote=True), + author, \ + quicklog)) +tr.stop() + +%> + + + +<% + +req.write(template.footer(info)) + +%> + --- www/viewmtn/mimetypes/gnome-fs-directory.png +++ www/viewmtn/mimetypes/gnome-fs-directory.png @@ -0,0 +1,6 @@ +‰PNG + ++Ê>address@hidden>Òàüü’¯‡ß׃¿ìfsó ’h"°%F£1ïÞ¾¿eðýøh)@UUüþó É4Ñ address@hidden,±„å¬v +ëëÏ(ŠÜ¨ˆ ®ªªbP–EAD!‘m©Ï -´M„°"ÐÍ$$e¿Z;ÔM}'1Ø&¤ ÑÅëº^np^e7… address@hidden)'address@hidden'Ë5„ÆOjãûA+++ www/viewmtn/mimetypes/gnome-library.png @@ -0,0 +1,6 @@ +‰PNG + ++Áš¿ÿŽy$+%zÑÙ +N8Ȇ!Ó0+++ www/viewmtn/mimetypes/gnome-mime-application-magicpoint.png @@ -0,0 +1,4 @@ +‰PNG + ++‚‚Èþ€ÛJbmga+²°í‚Ÿ‹ˆÍv‚Š3²ƒ)Ãܓ"̠ɘ.çã¹/çž /v H ÿÎ ­u¤û¾/‹ÅBúý~+++ www/viewmtn/mimetypes/gnome-mime-application-msword.png @@ -0,0 +1,8 @@ +‰PNG + ++ +This image was created using Gimp. Gimp is free software +as defined by the Free Software Foundation and is available +for download at http://www.gimp.orgFZ +»~œwˆ"Ât:%cš&š¦àóù+++ www/viewmtn/mimetypes/gnome-mime-application-ogg.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/gnome-mime-application-par.png @@ -0,0 +1,5 @@ +‰PNG + ++ò0<ðˆåãÇO߀+¬—f+++ www/viewmtn/mimetypes/gnome-mime-application-pdf.png @@ -0,0 +1,4 @@ +‰PNG + ++>‡i¹¬÷¬„ÏÇGÖR)|ß×Nð|y‰ˆ ”šÌ 1+++ www/viewmtn/mimetypes/gnome-mime-application-pgp-encrypted.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/gnome-mime-application-pgp-keys.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/gnome-mime-application-pgp.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/gnome-mime-application-postscript.png @@ -0,0 +1,4 @@ +‰PNG + ++¸K`+++ www/viewmtn/mimetypes/gnome-mime-application-qif.png @@ -0,0 +1,4 @@ +‰PNG + ++,+++ www/viewmtn/mimetypes/gnome-mime-application-rhythmbox-effect.png @@ -0,0 +1,4 @@ +‰PNG + ++AmÓâÁcOYGçþMl™]xýÁÔ~¿Cï‚s>àGL¥R¸vãÖß[?påñ2Òé4tÍÂZKI,Ñó<Ò44M#QIDQ$UUéÞí§´ò¥A¥R)´ÄÈ+++ www/viewmtn/mimetypes/gnome-mime-application-rhythmbox-playlist.png @@ -0,0 +1,4 @@ +‰PNG + ++¥FƒétJµZ¥Õj|Mûòÿjè8á¦sC©üUÁÃb±àþþŽù|þJÝKëŒ9ð²Ý¡JEÖë5Zk+++ www/viewmtn/mimetypes/gnome-mime-application-rtf.png @@ -0,0 +1,4 @@ +‰PNG + ++ªÕ*ý~ŸÉdÂjµb±X0 ½—Ç(F#Ž&B³ÙÄqf3‹ù|«'†Ã!¥R ÏóØn·ÔëuŠÅ""€mÛ´Ûít¥T¢!¤ëÑù˜ˆ "X–…®ë¸®‹¦i+++ www/viewmtn/mimetypes/gnome-mime-application-smil.png @@ -0,0 +1,5 @@ +‰PNG + ++Nà8ιP(iš4 +F‡Ãá±c ¡P(¼ÙlÄb1D"¨ªzGžÍf^JyOY×uضý·Õ_Ò-qµZ¡X,Ž+++ www/viewmtn/mimetypes/gnome-mime-application-vnd.lotus-1-2-3.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/gnome-mime-application-vnd.ms-excel.png @@ -0,0 +1,8 @@ +‰PNG + ++‚˜+ +This image was created using Gimp. Gimp is free software +as defined by the Free Software Foundation and is available +for download at http://www.gimp.orgFZ +++ www/viewmtn/mimetypes/gnome-mime-application-vnd.ms-powerpoint.png @@ -0,0 +1,9 @@ +‰PNG + ++ +This image was created using Gimp. Gimp is free software +as defined by the Free Software Foundation and is available +for download at http://address@hidden ¤Eoàø'r!„‹ œû5ĽhÛ¯>Îù8ç<çå=>”äµK€Äu]m)¥d4I§Óñ address@hidden&Æã1ívÛ?»ª³Óh4H&“Äãq‰ÃáÃ0˜N_ Áü&H˲¼9+++ www/viewmtn/mimetypes/gnome-mime-application-vnd.ms-word.png @@ -0,0 +1,8 @@ +‰PNG + ++ +This image was created using Gimp. Gimp is free software +as defined by the Free Software Foundation and is available +for download at http://www.gimp.orgFZ +»~œwˆ"Ât:%cš&š¦àóù+++ www/viewmtn/mimetypes/gnome-mime-application-vnd.stardivision.calc.png @@ -0,0 +1,4 @@ +‰PNG + ++Ì;+++ www/viewmtn/mimetypes/gnome-mime-application-vnd.stardivision.impress.png @@ -0,0 +1,4 @@ +‰PNG + ++‚‚Èþ€ÛJbmga+²°í‚Ÿ‹ˆÍv‚Š3²ƒ)Ãܓ"̠ɘ.çã¹/çž /v H ÿÎ ­u¤û¾/‹ÅBúý~+++ www/viewmtn/mimetypes/gnome-mime-application-vnd.stardivision.writer.png @@ -0,0 +1,4 @@ +‰PNG + ++Ì;+++ www/viewmtn/mimetypes/gnome-mime-application-vnd.sun.xml.calc.png @@ -0,0 +1,5 @@ +‰PNG + ++iPtÁ rrrÓ address@hidden www/viewmtn/mimetypes/gnome-mime-application-vnd.sun.xml.draw.png @@ -0,0 +1,4 @@ +‰PNG + address@hidden www/viewmtn/mimetypes/gnome-mime-application-vnd.sun.xml.impress.png @@ -0,0 +1,4 @@ +‰PNG + ++‚‚Èþ€ÛJbmga+²°í‚Ÿ‹ˆÍv‚Š3²ƒ)Ãܓ"̠ɘ.çã¹/çž /v H ÿÎ ­u¤û¾/‹ÅBúý~+++ www/viewmtn/mimetypes/gnome-mime-application-vnd.sun.xml.writer.png @@ -0,0 +1,4 @@ +‰PNG + ++ªÕ*ý~ŸÉdÂjµb±X0 ½—Ç(F#Ž&B³ÙÄqf3‹ù|«'†Ã!¥R ÏóØn·ÔëuŠÅ""€mÛ´Ûít¥T¢!¤ëÑù˜ˆ "X–…®ë¸®‹¦i+++ www/viewmtn/mimetypes/gnome-mime-application-vnd.sun.xml.writer.template.png @@ -0,0 +1,5 @@ +‰PNG + ++ûðáÃÿÿþýŊp—À +Ì;+++ www/viewmtn/mimetypes/gnome-mime-application-wordperfect.png @@ -0,0 +1,4 @@ +‰PNG + ++ªÕ*ý~ŸÉdÂjµb±X0 ½—Ç(F#Ž&B³ÙÄqf3‹ù|«'†Ã!¥R ÏóØn·ÔëuŠÅ""€mÛ´Ûít¥T¢!¤ëÑù˜ˆ "X–…®ë¸®‹¦i+++ www/viewmtn/mimetypes/gnome-mime-application-x-abiword.png @@ -0,0 +1,4 @@ +‰PNG + ++Ì;+++ www/viewmtn/mimetypes/gnome-mime-application-x-applix-spreadsheet.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/gnome-mime-application-x-applix-word.png @@ -0,0 +1,4 @@ +‰PNG + ++ªÕ*ý~ŸÉdÂjµb±X0 ½—Ç(F#Ž&B³ÙÄqf3‹ù|«'†Ã!¥R ÏóØn·ÔëuŠÅ""€mÛ´Ûít¥T¢!¤ëÑù˜ˆ "X–…®ë¸®‹¦i+++ www/viewmtn/mimetypes/gnome-mime-application-x-archive.png @@ -0,0 +1,8 @@ +‰PNG + ++I¢¢bâ¿ÿüTÔ7TåææbgX¶dëþ·o?肔Âô+df˜+‚1RT2|þü‰èdP:aj…(P address@hidden €€^eûóǏŸ°ð .òðp6ÓÒVaŸ2yΧgÏ^MŠw „„Ð{ +++ www/viewmtn/mimetypes/gnome-mime-application-x-arj.png @@ -0,0 +1,8 @@ +‰PNG + ++I¢¢bâ¿ÿüTÔ7TåææbgX¶dëþ·o?肔Âô+df˜+‚1RT2|þü‰èdP:aj…(P address@hidden €€^eûóǏŸ°ð .òðp6ÓÒVaŸ2yΧgÏ^MŠw „„Ð{ +++ www/viewmtn/mimetypes/gnome-mime-application-x-bittorrent.png @@ -0,0 +1,6 @@ +‰PNG + ++ˆÍ+æΝ‹ˆ0W €+¢®Âø+++ www/viewmtn/mimetypes/gnome-mime-application-x-bla.png @@ -0,0 +1,5 @@ +‰PNG + ++ +‚11|x÷žá׏_ ¿þËC-„€+++ www/viewmtn/mimetypes/gnome-mime-application-x-blender.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/gnome-mime-application-x-blf.png @@ -0,0 +1,4 @@ +‰PNG + ++ À+++ www/viewmtn/mimetypes/gnome-mime-application-x-blv.png @@ -0,0 +1,4 @@ +‰PNG + ++ À+++ www/viewmtn/mimetypes/gnome-mime-application-x-bzip-compressed-tar.png @@ -0,0 +1,8 @@ +‰PNG + ++I¢¢bâ¿ÿüTÔ7TåææbgX¶dëþ·o?肔Âô+df˜+‚1RT2|þü‰èdP:aj…(P address@hidden €€^eûóǏŸ°ð .òðp6ÓÒVaŸ2yΧgÏ^MŠw „„Ð{ +++ www/viewmtn/mimetypes/gnome-mime-application-x-bzip.png @@ -0,0 +1,8 @@ +‰PNG + ++I¢¢bâ¿ÿüTÔ7TåææbgX¶dëþ·o?肔Âô+df˜+‚1RT2|þü‰èdP:aj…(P address@hidden €€^eûóǏŸ°ð .òðp6ÓÒVaŸ2yΧgÏ^MŠw „„Ð{ +++ www/viewmtn/mimetypes/gnome-mime-application-x-cd-image.png @@ -0,0 +1,7 @@ +‰PNG + ++ÿÕ«W address@hidden +‚c+Ðc®®®ð0+++ www/viewmtn/mimetypes/gnome-mime-application-x-class-file.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/gnome-mime-application-x-compress.png @@ -0,0 +1,8 @@ +‰PNG + ++I¢¢bâ¿ÿüTÔ7TåææbgX¶dëþ·o?肔Âô+df˜+‚1RT2|þü‰èdP:aj…(P address@hidden €€^eûóǏŸ°ð .òðp6ÓÒVaŸ2yΧgÏ^MŠw „„Ð{ +++ www/viewmtn/mimetypes/gnome-mime-application-x-compressed-tar.png @@ -0,0 +1,8 @@ +‰PNG + ++I¢¢bâ¿ÿüTÔ7TåææbgX¶dëþ·o?肔Âô+df˜+‚1RT2|þü‰èdP:aj…(P address@hidden €€^eûóǏŸ°ð .òðp6ÓÒVaŸ2yΧgÏ^MŠw „„Ð{ +++ www/viewmtn/mimetypes/gnome-mime-application-x-core.png @@ -0,0 +1,4 @@ +‰PNG + ++666†uëց命+++ www/viewmtn/mimetypes/gnome-mime-application-x-cpio-compressed.png @@ -0,0 +1,8 @@ +‰PNG + ++I¢¢bâ¿ÿüTÔ7TåææbgX¶dëþ·o?肔Âô+df˜+‚1RT2|þü‰èdP:aj…(P address@hidden €€^eûóǏŸ°ð .òðp6ÓÒVaŸ2yΧgÏ^MŠw „„Ð{ +++ www/viewmtn/mimetypes/gnome-mime-application-x-cpio.png @@ -0,0 +1,8 @@ +‰PNG + ++I¢¢bâ¿ÿüTÔ7TåææbgX¶dëþ·o?肔Âô+df˜+‚1RT2|þü‰èdP:aj…(P address@hidden €€^eûóǏŸ°ð .òðp6ÓÒVaŸ2yΧgÏ^MŠw „„Ð{ +++ www/viewmtn/mimetypes/gnome-mime-application-x-dc-rom.png @@ -0,0 +1,6 @@ +‰PNG + ++… Xcdp:Ãß¿ u9¡X]+× "ŽÕ+²‰ÌÎ0œËL+++ www/viewmtn/mimetypes/gnome-mime-application-x-deb.png @@ -0,0 +1,8 @@ +‰PNG + ++I¢¢bâ¿ÿüTÔ7TåææbgX¶dëþ·o?肔Âô+df˜+‚1RT2|þü‰èdP:aj…(P address@hidden €€^eûóǏŸ°ð .òðp6ÓÒVaŸ2yΧgÏ^MŠw „„Ð{ +++ www/viewmtn/mimetypes/gnome-mime-application-x-desktop.png @@ -0,0 +1,5 @@ +‰PNG + ++Dq1E]°-LLL U5©`¯_¿føóç\H+D€+++ www/viewmtn/mimetypes/gnome-mime-application-x-dia-diagram.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/gnome-mime-application-x-dv.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/gnome-mime-application-x-dvi.png @@ -0,0 +1,5 @@ +‰PNG + ++L—–– ‚€+ãLA6‘Ù†S`Þ0+++ www/viewmtn/mimetypes/gnome-mime-application-x-e-theme.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/gnome-mime-application-x-executable.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/gnome-mime-application-x-extension-nfo.png @@ -0,0 +1,5 @@ +‰PNG + ++L—–– ‚€+ãLA6‘Ù†S`Þ0+++ www/viewmtn/mimetypes/gnome-mime-application-x-extension-par2.png @@ -0,0 +1,5 @@ +‰PNG + ++ò0<ðˆåãÇO߀+¬—f+++ www/viewmtn/mimetypes/gnome-mime-application-x-font-afm.png @@ -0,0 +1,4 @@ +‰PNG + ++v ²+a+++ www/viewmtn/mimetypes/gnome-mime-application-x-font-bdf.png @@ -0,0 +1,4 @@ +‰PNG + address@hidden www/viewmtn/mimetypes/gnome-mime-application-x-font-linux-psf.png @@ -0,0 +1,4 @@ +‰PNG + address@hidden www/viewmtn/mimetypes/gnome-mime-application-x-font-pcf.png @@ -0,0 +1,4 @@ +‰PNG + address@hidden www/viewmtn/mimetypes/gnome-mime-application-x-font-sunos-news.png @@ -0,0 +1,4 @@ +‰PNG + ++v ²+a+++ www/viewmtn/mimetypes/gnome-mime-application-x-font-ttf.png @@ -0,0 +1,6 @@ +‰PNG + ++ +2HJJ2¼yó†ÁÔԔŸŸŸáÝ»7 address@hidden address@hidden www/viewmtn/mimetypes/gnome-mime-application-x-gameboy-rom.png @@ -0,0 +1,5 @@ +‰PNG + address@hidden;+++ www/viewmtn/mimetypes/gnome-mime-application-x-genesis-rom.png @@ -0,0 +1,6 @@ +‰PNG + ++… Xcdp:Ãß¿ u9¡X]+× "ŽÕ+²‰ÌÎ0œËL+++ www/viewmtn/mimetypes/gnome-mime-application-x-glade.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/gnome-mime-application-x-gnucash.png @@ -0,0 +1,4 @@ +‰PNG + ++,+++ www/viewmtn/mimetypes/gnome-mime-application-x-gnumeric.png @@ -0,0 +1,6 @@ +‰PNG + ++Ñëu)‹sð•¸k0Çg^Žù9—C¯W wëX­c&¿ÞKx®o±}ø˜Ýø>ý~8ˆ×a­ò…ucˆxŒË~Nm›ÑÏïÔ×üj·Ù¹ŸÌÍàÇÇ÷lCLËblZôðÑði+Aμ>rÞá8Îâ+Ê×<Ïîß;˜kAŸá¿u›oÇUNš†oKLÌ ›ÊMž<ÊáÁšÍær@"address@hidden Ðh4+ÿ?°W+~ç©^N™4$ê ¨í+++ www/viewmtn/mimetypes/gnome-mime-application-x-gtktalog.png @@ -0,0 +1,4 @@ +‰PNG + ++¢®Âø+++ www/viewmtn/mimetypes/gnome-mime-application-x-gzip.png @@ -0,0 +1,8 @@ +‰PNG + ++I¢¢bâ¿ÿüTÔ7TåææbgX¶dëþ·o?肔Âô+df˜+‚1RT2|þü‰èdP:aj…(P address@hidden €€^eûóǏŸ°ð .òðp6ÓÒVaŸ2yΧgÏ^MŠw „„Ð{ +++ www/viewmtn/mimetypes/gnome-mime-application-x-ipod-firmware.png @@ -0,0 +1,6 @@ +‰PNG + ++4*{ `+>Ü|‘LR·‚8 F­‚ƒ¸Ø^’Ó¡&š4/¸Üÿœÿ|ç&þVÂ"a‰çy±áº®Ìçs‡¾I3Ñ`¿ßÇÆh4­µôûýD’À ÓéÄâ[–%­VKjµš€º0àp8ÍfÑú‡ÕjêP,i4^?Øn·Am*éRîóyÇÀ0M+L&P*Ðϔ×FPðøôˆmÛär9ÔÉøê!%Pz.Õ£F,AJa˜&™t†ô]šãñ;‘Àˆvèv»˜†ñﳶÛíX‚àGr]—ÍfJð÷—gÓé”z½Ô­lÛFk}µ`¹\|F ßn|Î~¼ú…¿!½‰PgÃ+++ www/viewmtn/mimetypes/gnome-mime-application-x-jar.png @@ -0,0 +1,4 @@ +‰PNG + ++Ì;+++ www/viewmtn/mimetypes/gnome-mime-application-x-killustrator.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/gnome-mime-application-x-kpresenter.png @@ -0,0 +1,4 @@ +‰PNG + ++ld+++ www/viewmtn/mimetypes/gnome-mime-application-x-kspread.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/gnome-mime-application-x-kword.png @@ -0,0 +1,4 @@ +‰PNG + ++`ld1P¸ººÂ]+++ www/viewmtn/mimetypes/gnome-mime-application-x-lha.png @@ -0,0 +1,8 @@ +‰PNG + ++I¢¢bâ¿ÿüTÔ7TåææbgX¶dëþ·o?肔Âô+df˜+‚1RT2|þü‰èdP:aj…(P address@hidden €€^eûóǏŸ°ð .òðp6ÓÒVaŸ2yΧgÏ^MŠw „„Ð{ +++ www/viewmtn/mimetypes/gnome-mime-application-x-lhz.png @@ -0,0 +1,8 @@ +‰PNG + ++I¢¢bâ¿ÿüTÔ7TåææbgX¶dëþ·o?肔Âô+df˜+‚1RT2|þü‰èdP:aj…(P address@hidden €€^eûóǏŸ°ð .òðp6ÓÒVaŸ2yΧgÏ^MŠw „„Ð{ +++ www/viewmtn/mimetypes/gnome-mime-application-x-mrproject.png @@ -0,0 +1,4 @@ +‰PNG + ++ãLA6‘Ù†S`®0+++ www/viewmtn/mimetypes/gnome-mime-application-x-msx-rom.png @@ -0,0 +1,6 @@ +‰PNG + ++… Xcdp:Ãß¿ u9¡X]+× "ŽÕ+²‰ÌÎ0œËL+++ www/viewmtn/mimetypes/gnome-mime-application-x-n64-rom.png @@ -0,0 +1,6 @@ +‰PNG + ++… Xcdp:Ãß¿ u9¡X]+× "ŽÕ+²‰ÌÎ0œËL+++ www/viewmtn/mimetypes/gnome-mime-application-x-nes-rom.png @@ -0,0 +1,6 @@ +‰PNG + ++… Xcdp:Ãß¿ u9¡X]+× "ŽÕ+²‰ÌÎ0œËL+++ www/viewmtn/mimetypes/gnome-mime-application-x-object.png @@ -0,0 +1,6 @@ +‰PNG + ++Áš¿ÿŽy$+%zÑÙ +N8Ȇ!Ó0+++ www/viewmtn/mimetypes/gnome-mime-application-x-perl.png @@ -0,0 +1,6 @@ +‰PNG + ++Ö<«&Š¡(̆AG‚ƒ!ØB,ßßÙÁ`jj + 38+²‰ÌÎ0œËL+++ www/viewmtn/mimetypes/gnome-mime-application-x-php.png @@ -0,0 +1,4 @@ +‰PNG + ++÷" +++ www/viewmtn/mimetypes/gnome-mime-application-x-python-bytecode.png @@ -0,0 +1,4 @@ +‰PNG + ++ãLA6‘Ù†S`™ À+++ www/viewmtn/mimetypes/gnome-mime-application-x-python.png @@ -0,0 +1,4 @@ +‰PNG + ++ãLA6‘Ù†S`™ À+++ www/viewmtn/mimetypes/gnome-mime-application-x-qw.png @@ -0,0 +1,4 @@ +‰PNG + ++,+++ www/viewmtn/mimetypes/gnome-mime-application-x-rar.png @@ -0,0 +1,8 @@ +‰PNG + ++I¢¢bâ¿ÿüTÔ7TåææbgX¶dëþ·o?肔Âô+df˜+‚1RT2|þü‰èdP:aj…(P address@hidden €€^eûóǏŸ°ð .òðp6ÓÒVaŸ2yΧgÏ^MŠw „„Ð{ +++ www/viewmtn/mimetypes/gnome-mime-application-x-reject.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/gnome-mime-application-x-rpm.png @@ -0,0 +1,8 @@ +‰PNG + ++I¢¢bâ¿ÿüTÔ7TåææbgX¶dëþ·o?肔Âô+df˜+‚1RT2|þü‰èdP:aj…(P address@hidden €€^eûóǏŸ°ð .òðp6ÓÒVaŸ2yΧgÏ^MŠw „„Ð{ +++ www/viewmtn/mimetypes/gnome-mime-application-x-ruby.png @@ -0,0 +1,4 @@ +‰PNG + ++î‡P/+++ www/viewmtn/mimetypes/gnome-mime-application-x-sharedlib.png @@ -0,0 +1,6 @@ +‰PNG + ++Áš¿ÿŽy$+%zÑÙ +N8Ȇ!Ó0+++ www/viewmtn/mimetypes/gnome-mime-application-x-shellscript.png @@ -0,0 +1,4 @@ +‰PNG + ++…X^^"Êpsu…ÝÝ" Æ ûÁ…øU“Z­†¦iCŝ.q+++ www/viewmtn/mimetypes/gnome-mime-application-x-shockwave-flash.png @@ -0,0 +1,4 @@ +‰PNG + ++‚¬øM—ÍOP«,DIa#‚…?ÁÖÎҏBÂBb1/FFgQˆ…ΚÂ&ÙYgUb2Þ9“wLF÷À{/÷}Î{Ͻ‚Ȳ‹¼DBˆïûsS)%–eI½^!ÙDÀšeɳ••¨âªùN\וf³)žçI¡P˜q²'‹E²Ù+++ www/viewmtn/mimetypes/gnome-mime-application-x-sms-rom.png @@ -0,0 +1,6 @@ +‰PNG + ++… Xcdp:Ãß¿ u9¡X]+× "ŽÕ+²‰ÌÎ0œËL+++ www/viewmtn/mimetypes/gnome-mime-application-x-sql.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/gnome-mime-application-x-stuffit.png @@ -0,0 +1,4 @@ +‰PNG + ++*ckeÇ+Øù>„>€±‚{ŠK —+(LhJ4æ€Æxì±CAvÃÁ­0Édwgv¿ùÙ]ØJ ô‡ˆÖºP×뵌F#ét:Rs\‘£(¥”4ƒL¼+++ www/viewmtn/mimetypes/gnome-mime-application-x-tar.png @@ -0,0 +1,8 @@ +‰PNG + ++I¢¢bâ¿ÿüTÔ7TåææbgX¶dëþ·o?肔Âô+df˜+‚1RT2|þü‰èdP:aj…(P address@hidden €€^eûóǏŸ°ð .òðp6ÓÒVaŸ2yΧgÏ^MŠw „„Ð{ +++ www/viewmtn/mimetypes/gnome-mime-application-x-tex.png @@ -0,0 +1,5 @@ +‰PNG + ++L—–– ‚€+ãLA6‘Ù†S`Þ0+++ www/viewmtn/mimetypes/gnome-mime-application-x-trash.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/gnome-mime-application-x-x509-ca-cert.png @@ -0,0 +1,4 @@ +‰PNG + ++Ãì«O€1‚a+++ www/viewmtn/mimetypes/gnome-mime-application-zip.png @@ -0,0 +1,8 @@ +‰PNG + ++I¢¢bâ¿ÿüTÔ7TåææbgX¶dëþ·o?肔Âô+df˜+‚1RT2|þü‰èdP:aj…(P address@hidden €€^eûóǏŸ°ð .òðp6ÓÒVaŸ2yΧgÏ^MŠw „„Ð{ +++ www/viewmtn/mimetypes/gnome-mime-application.png @@ -0,0 +1,5 @@ +‰PNG + ++Ã_  address@hidden www/viewmtn/mimetypes/gnome-mime-audio-ac3.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/gnome-mime-audio-basic.png @@ -0,0 +1,11 @@ +‰PNG + ++5ª(+ +This image was created using Gimp. Gimp is free software +as defined by the Free Software Foundation and is available +for download at http://www.gimp.orgFZ +‡¢?U=#ð+n‚ˆ`š&4MCßÿ¢( +¼µ0 Ã|APß÷”eçpRÀÛM&ypŒ±+++ www/viewmtn/mimetypes/gnome-mime-audio-midi.png @@ -0,0 +1,11 @@ +‰PNG + ++5ª(+ +This image was created using Gimp. Gimp is free software +as defined by the Free Software Foundation and is available +for download at http://www.gimp.orgFZ +‡¢?U=#ð+n‚ˆ`š&4MCßÿ¢( +¼µ0 Ã|APß÷”eçpRÀÛM&ypŒ±+++ www/viewmtn/mimetypes/gnome-mime-audio-x-aiff.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/gnome-mime-audio-x-it.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/gnome-mime-audio-x-midi.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/gnome-mime-audio-x-mod.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/gnome-mime-audio-x-mp3.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/gnome-mime-audio-x-s3m.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/gnome-mime-audio-x-stm.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/gnome-mime-audio-x-ulaw.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/gnome-mime-audio-x-voc.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/gnome-mime-audio-x-wav.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/gnome-mime-audio-x-xi.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/gnome-mime-audio-x-xm.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/gnome-mime-audio.png @@ -0,0 +1,11 @@ +‰PNG + ++5ª(+ +This image was created using Gimp. Gimp is free software +as defined by the Free Software Foundation and is available +for download at http://www.gimp.orgFZ +‡¢?U=#ð+n‚ˆ`š&4MCßÿ¢( +¼µ0 Ã|APß÷”eçpRÀÛM&ypŒ±+++ www/viewmtn/mimetypes/gnome-mime-image-bmp.png @@ -0,0 +1,4 @@ +‰PNG + ++ÿÛö¶$¾$uNNTÏ$I’F£ÑLÂñþ~Ꮿ¯gÊwP^å;;¼ßÚ*ü«NgFòžÄïÓÓiƒA±Y+++ www/viewmtn/mimetypes/gnome-mime-image-gif.png @@ -0,0 +1,5 @@ +‰PNG + +++1’03dB2BEoYÄ»– eð‡fó+++ www/viewmtn/mimetypes/gnome-mime-image-jpeg.png @@ -0,0 +1,5 @@ +‰PNG + ++ó£Ñ˜V«Åc0›ÍHÓ%få¯ËŒ?כ͐Û{ãNâ&w™¸Íáӛ¨´TUÄ·L¯îYóMçØðŽfèXêϨ´,Ëû9øKzÍד Gè¡áÀ{‡÷nÅÎ +Á÷³)ßnsrö›Óá%MàCG#€ | ¨û‡F¡)I. _½l0_ñîõ&"`£Š^s™¹r‘8ï÷9ØÙᩨ-X•J‰¸¬c‰Xb¿×`¿×#–Øëv-DQôß)çjÂ÷»»tœ[UçùÂäe5çÇÇu½×íÖÿ<Àd2©7k˜$ÄÃ$YPPáôðGq\‡Ø¾+++ www/viewmtn/mimetypes/gnome-mime-image-png.png @@ -0,0 +1,4 @@ +‰PNG + ++Z,+++ www/viewmtn/mimetypes/gnome-mime-image-svg+xml.png @@ -0,0 +1,7 @@ +‰PNG + ++‚Åý),Ll+í“TV»$)¬rD©ä<°È!Äæ +I°‚a²n1 '‡·;û,Ž]n/›ã|™aæû¾ï;ofà4v+,‹Hôz=qGšÍ抓Ø2¯?FÚ{õh“Á`€eYL&Úív»yžl¼yÆщpxìòsvį?'ôÿþæFêÃáwF£Qˆ?/°w0åæÕ8Û[1f“_÷¾q}kƒÏ‡wÙÝmÐjµ.xûän§HÅ\>Õ9ž/øòáý©;address@hidden)År¹$“ɹ¡#xž‡a¸®‹ëºh­ÑZãyõzb±x±Û¶W*Û¶M­Vc:R.—QJ‘Íf×;0M3T¹Z­’L&1M“t:½â ôD„|>–eaš&‰D"ä.òE„J¥‚ˆ ”¢ßïS*•‚dßedºÝ.Žã¬½?”Rär¹!pÿìúËK~gÏýÄÿçI?ÑR8Dq+++ www/viewmtn/mimetypes/gnome-mime-image-svg.png @@ -0,0 +1,8 @@ +‰PNG + ++ +This image was created using Gimp. Gimp is free software +as defined by the Free Software Foundation and is available +for download at http://www.gimp.orgFZ +ªßï«l6;bbœ`¾Ù Õ{ýxœR©D³Ù¤^¯“Ï烳ãÃÃöÛg)ö}~¶øõ÷ïߦ¢?(—w¨T*ó‘a€³ý›éëb“­z‹oÎwnNŽñy†õõ r¹ÜŀwÖ}܉5|¶?¥9ìtùšùxbgÛ(address@hidden<+++ www/viewmtn/mimetypes/gnome-mime-image-tiff.png @@ -0,0 +1,5 @@ +‰PNG + ++.Ã$d†Ìˆ›Í¼€ªA¿? ÕjñŒÇc’d*Ynyó˜rÿœ²Xx}7Þ$^Rñ–Š× ¶ÖPQ€/ JÄÑé5£§w悧îóÁQ ?¹ŒŠüt¼³ÞÝÑ3‡'=ækŽ(@ÍAŽÜ·r¾_ŽØ\_áäòŽóÛGêÎ"G̓> ÊÄáSÈ3]\\Ó»]&^ªÑ<±±ºˆð40ÊÖ«Ò3W address@hidden/{U­ä¾:Êa¿|» ++++ www/viewmtn/mimetypes/gnome-mime-image-wmf.png @@ -0,0 +1,9 @@ +‰PNG + ++ +This image was created using Gimp. Gimp is free software +as defined by the Free Software Foundation and is available +for download at http://www.gimp.orgFZ +iCÌ+54/XDóv§ِ5/Ò 3³;óßÿÌ,\Ë ÿ O"¢µŽT¥”Ôj5) +di(@‘Z,¥ÛíJ&“`âôxõ>’ÞËG£”ËešÍ&õz|>ß«½™ì­=çüJ8»ùæŸóã÷{~r×ýÊññ•JÅʏÝØ=<åÞDŒø¸ƒ_÷ù¼»ÏÔøÎæÙÞ~K6›½`ýÉκ¸NÈa.ÍE;àÓ»7×ì<‰0±‘;èØ/^ä2¾Èý™Idî1+++ www/viewmtn/mimetypes/gnome-mime-image-x-3ds.png @@ -0,0 +1,7 @@ +‰PNG + ++ + + ÷îÝc°¶¶†»+×ñ¦ ›Èì Ã)°Ì`+++ www/viewmtn/mimetypes/gnome-mime-image-x-applix-graphic.png @@ -0,0 +1,4 @@ +‰PNG + ++²‰ÌÎ0œK+++ www/viewmtn/mimetypes/gnome-mime-image-x-cmu-raster.png @@ -0,0 +1,4 @@ +‰PNG + address@hidden www/viewmtn/mimetypes/gnome-mime-image-x-lwo.png @@ -0,0 +1,4 @@ +‰PNG + ++6ä㧠Gf8}æ,É'rrrøùù˜™ÿ3|address@hidden www/viewmtn/mimetypes/gnome-mime-image-x-lws.png @@ -0,0 +1,4 @@ +‰PNG + ++È£+++ www/viewmtn/mimetypes/gnome-mime-image-x-xcf.png @@ -0,0 +1,6 @@ +‰PNG + ++ÝxXšµ‘0÷(f–±¬ÈžäåÝw_îÉ9/ç”u¸Ê+þƒF£s- +Ìbʵm¦CöNf~>address@hidden>P{n/AB­š8>ç©Þû·ÏÃóÚóôd³X¦‹³·+nÁüÃ#9<‰Ó) q'address@hidden:‹³ýä^.™í»dµøÈʔû)& 7××l‹tIRµZ•ŸÔ›¥’$ÉÁgúü–ÔúI$IµZíÛÀf©ôÐ‡Ïs³öA™t”.ŽŽ+++ www/viewmtn/mimetypes/gnome-mime-image.png @@ -0,0 +1,4 @@ +‰PNG + ++ú|oŸÚøbÄîÕ¾|òâå›ã$çpî+++ www/viewmtn/mimetypes/gnome-mime-text-css.png @@ -0,0 +1,6 @@ +‰PNG + ++"â`!]ÖEÅ]PZJ‘ú‹âBÜhý ÄÚ]‘.Tè*ˆàJ ˜é4J*TA’,D35Æ)Cc +¦ÎÌ».dB̛`.œÕ»çÜs.÷ÁßJ+ß÷Ŷm1M3It¨Õj¡°,K\וd2©9ÙÓ"@½^µ—J¥Èf³T*J¥étºÉðŸ à ‹á8[äóùÞ5J©ŽB+++ www/viewmtn/mimetypes/gnome-mime-text-html.png @@ -0,0 +1,6 @@ +‰PNG + ++"â`!]ÖEÅ]PZJ‘ú‹âBÜhý ÄÚ]‘.Tè*ˆàJ ˜é4J*TA’,D35Æ)Cc +¦ÎÌ».dB̛`.œÕ»çÜs.÷ÁßJ+ß÷Ŷm1M3It¨Õj¡°,K\וd2©9ÙÓ"@½^µ—J¥Èf³T*J¥étºÉðŸ à ‹á8[äóùÞ5J©ŽB+++ www/viewmtn/mimetypes/gnome-mime-text-x-authors.png @@ -0,0 +1,5 @@ +‰PNG + ++`ld±7n0¸ººÂc €à.+ãLA6‘Ù†S`Þ0+++ www/viewmtn/mimetypes/gnome-mime-text-x-c++src.png @@ -0,0 +1,4 @@ +‰PNG + ++÷" +++ www/viewmtn/mimetypes/gnome-mime-text-x-c-header.png @@ -0,0 +1,5 @@ +‰PNG + ++ŠØª;nܸÁpõêe†k×®¡È† ,ÔÕ +²³T´u~þùÍ ÅÅÅ™’Âpªñúõë ÖÖÖp+++ www/viewmtn/mimetypes/gnome-mime-text-x-c.png @@ -0,0 +1,4 @@ +‰PNG + ++÷" +++ www/viewmtn/mimetypes/gnome-mime-text-x-chdr.png @@ -0,0 +1,5 @@ +‰PNG + ++ŠØª;nܸÁpõêe†k×®¡È† ,ÔÕ +²³T´u~þùÍ ÅÅÅ™’Âpªñúõë ÖÖÖp+++ www/viewmtn/mimetypes/gnome-mime-text-x-copying.png @@ -0,0 +1,4 @@ +‰PNG + ++Ãì«O€1‚a+++ www/viewmtn/mimetypes/gnome-mime-text-x-credits.png @@ -0,0 +1,5 @@ +‰PNG + ++`ld±7n0¸ººÂc €à.+ãLA6‘Ù†S`Þ0+++ www/viewmtn/mimetypes/gnome-mime-text-x-csh.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/gnome-mime-text-x-csharp.png @@ -0,0 +1,5 @@ +‰PNG + ++3|úø•áï?6°dOOPL "¢¢ ñqq+,+++ www/viewmtn/mimetypes/gnome-mime-text-x-csrc.png @@ -0,0 +1,4 @@ +‰PNG + ++÷" +++ www/viewmtn/mimetypes/gnome-mime-text-x-haskell.png @@ -0,0 +1,5 @@ +‰PNG + address@hidden address@hidden www/viewmtn/mimetypes/gnome-mime-text-x-install.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/gnome-mime-text-x-java.png @@ -0,0 +1,5 @@ +‰PNG + ++ +ü~?étUU; Ôjµúkww—f³‰ªª˜¦I±XÄ0 NNN0M“‹‹ t]g}}½Ó V«%‰ûûû”Ëe±m›ËËK²Ù,×××lnnrxxÈÐÐPçÚ}™žžF×uLÓDJ‰¢(ÌÍÍ155ÅÈȈ'0 ƒX,ö´…ö`aa!ċ¶OÃ0¼{Ó4ôçwnó{[ø[î€ü +++ www/viewmtn/mimetypes/gnome-mime-text-x-literate-haskell.png @@ -0,0 +1,5 @@ +‰PNG + address@hidden address@hidden www/viewmtn/mimetypes/gnome-mime-text-x-lyx.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/gnome-mime-text-x-makefile.png @@ -0,0 +1,4 @@ +‰PNG + ++Ì+++ www/viewmtn/mimetypes/gnome-mime-text-x-objcsrc.png @@ -0,0 +1,4 @@ +‰PNG + ++÷" +++ www/viewmtn/mimetypes/gnome-mime-text-x-patch.png @@ -0,0 +1,6 @@ +‰PNG + ++îmïqQüÆúÎ>ˆ}ü€µ»k“)Ì­8õÀ¶mºÝ.¶m³, à û{ð·þ·Gç||¹‰®ëŒF#V«“á2Yuwñ +ŸÏ…B¼útÆ×÷øž¯" ¦i<Û}°ÜƒÃƒ-ޝsøú>++++ www/viewmtn/mimetypes/gnome-mime-text-x-readme.png @@ -0,0 +1,7 @@ +‰PNG + ++ øóç/Ãß¿€00ÔÖ50€¼ 38+ + +ýá70„˜ÖÿûÇ·ä +d+CWÿ4 ­Å` „ÌÌ,X address@hidden Pdž>}ʐp(®âLA6‘Ù†S`®0+++ www/viewmtn/mimetypes/gnome-mime-text-x-scheme.png @@ -0,0 +1,5 @@ +‰PNG + ++¿¢¢‚ÁÔԔáƍ W¯^f¸víŠ<@+¢®Âø+++ www/viewmtn/mimetypes/gnome-mime-text-x-sql.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/gnome-mime-text-x-tex.png @@ -0,0 +1,5 @@ +‰PNG + ++L—–– ‚€+ãLA6‘Ù†S`Þ0+++ www/viewmtn/mimetypes/gnome-mime-text-x-troff-man.png @@ -0,0 +1,4 @@ +‰PNG + address@hidden ¦_½zN˜+++ www/viewmtn/mimetypes/gnome-mime-text-x-txt.png @@ -0,0 +1,5 @@ +‰PNG + ++L—–– ‚€+ãLA6‘Ù†S`Þ0+++ www/viewmtn/mimetypes/gnome-mime-text-x-vcalendar.png @@ -0,0 +1,4 @@ +‰PNG + ++¥Ôk+++ www/viewmtn/mimetypes/gnome-mime-text-x-vcard.png @@ -0,0 +1,5 @@ +‰PNG + ++.¼xñb!!!˜…p+²‰MPœK`+++ www/viewmtn/mimetypes/gnome-mime-text-x-zsh.png @@ -0,0 +1,4 @@ +‰PNG + address@hidden www/viewmtn/mimetypes/gnome-mime-text-xml.png @@ -0,0 +1,4 @@ +‰PNG + ++ãLA6‘Ù†S`Þ0+++ www/viewmtn/mimetypes/gnome-mime-text.png @@ -0,0 +1,10 @@ +‰PNG + ++ +This image was created using Gimp. Gimp is free software +as defined by the Free Software Foundation and is available +for download at http://www.gimp.orgFZ +8‰9ÕZ«½^/f"+šÍ&ý~ŸÉdÂn·c³Ù0 î³æU%‚g5UÚí6«ÕŠù|Æb±xªÇ F£•J…ÓéÄù|Æ÷}J¥ª +Àr¹¤Ûí&8çb‘ET¼¨*ªÊl6£^¯†!žçÏçh4é€Òét+++ www/viewmtn/mimetypes/gnome-mime-video-mpeg.png @@ -0,0 +1,6 @@ +‰PNG + ++â4FF¸­ address@hidden WWWx:+–D׌nÈÓ§OÁAãLA6‘Ù†S` À+++ www/viewmtn/mimetypes/gnome-mime-video-quicktime.png @@ -0,0 +1,6 @@ +‰PNG + ++â4FF¸­ address@hidden WWWx:+–D׌nÈÓ§OÁAãLA6‘Ù†S` À+++ www/viewmtn/mimetypes/gnome-mime-video-x-ms-asf.png @@ -0,0 +1,6 @@ +‰PNG + ++â4FF¸­ address@hidden WWWx:+–D׌nÈÓ§OÁAãLA6‘Ù†S` À+++ www/viewmtn/mimetypes/gnome-mime-video-x-ms-wmv.png @@ -0,0 +1,6 @@ +‰PNG + ++â4FF¸­ address@hidden WWWx:+–D׌nÈÓ§OÁAãLA6‘Ù†S` À+++ www/viewmtn/mimetypes/gnome-mime-video-x-msvideo.png @@ -0,0 +1,6 @@ +‰PNG + ++â4FF¸­ address@hidden WWWx:+–D׌nÈÓ§OÁAãLA6‘Ù†S` À+++ www/viewmtn/mimetypes/gnome-mime-video.png @@ -0,0 +1,6 @@ +‰PNG + ++â4FF¸­ address@hidden WWWx:+–D׌nÈÓ§OÁAãLA6‘Ù†S` À+++ www/viewmtn/mimetypes/gnome-mime-x-directory-nfs-server.png @@ -0,0 +1,6 @@ +‰PNG + ++ +(c) 2003 Jakub 'jimmac' Steiner'3ïX+)ŠBf³Yù| išÖÅçóY¬µb­­yžËt:m(Q¯ žç±ÙlêÜ9÷‡.‚çyø¾ß¸Uý-ãp8Üôx<ɲì> Ë2Œ1—þDèt:”eyàû>§Ó©aAD”Rh­/+++ www/viewmtn/mimetypes/gnome-mime-x-directory-smb-server.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/gnome-mime-x-directory-smb-share.png @@ -0,0 +1,5 @@ +‰PNG + ++åö+8œV³Àiàp>6òbÛ±Ákà=àòp«L{Š‹C+++ www/viewmtn/mimetypes/gnome-mime-x-directory-smb-workgroup.png @@ -0,0 +1,7 @@ +‰PNG + ++„0EŸ‹‡°Ñ9Aî!x¦ÝÂ=ŒÇX°ñ+ +à ¬¯¶%Ë2Œ1Xk©ÊrW½ÅMDt×uŒãHÓ4¸0+J©/à੔º»…ÈPõíðp^*9ê®`Y·y†Z‹Èº%P€¢ÆJB'vö”D†zÉõuDAýóoü+++ www/viewmtn/mimetypes/gnome-mime-x-font-afm.png @@ -0,0 +1,5 @@ +‰PNG + +++ +VüèÑ#†U«V ÑÖÖÆ0+++ www/viewmtn/mimetypes/gnome-package.png @@ -0,0 +1,8 @@ +‰PNG + ++I¢¢bâ¿ÿüTÔ7TåææbgX¶dëþ·o?肔Âô+df˜+‚1RT2|þü‰èdP:aj…(P address@hidden €€^eûóǏŸ°ð .òðp6ÓÒVaŸ2yΧgÏ^MŠw „„Ð{ +++ www/viewmtn/mimetypes/openofficeorg-19-database.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/openofficeorg-19-drawing-template.png @@ -0,0 +1,6 @@ +‰PNG + address@hidden&†ò & ++x%Œ^¿ã`-­€el­ì=Î+++ www/viewmtn/mimetypes/openofficeorg-19-drawing.png @@ -0,0 +1,4 @@ +‰PNG + address@hidden&¦”RJûˆk­µÖÑ~<-+++ www/viewmtn/mimetypes/openofficeorg-19-formula.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/openofficeorg-19-master-document.png @@ -0,0 +1,4 @@ +‰PNG + ++µù""address@hidden;RJ)]±‡=\ÇðvòTwä¸Vp_÷*|Ù+++ www/viewmtn/mimetypes/openofficeorg-19-oasis-database.png @@ -0,0 +1,8 @@ +‰PNG + ++ + +ýk&&&žÿˆd+Ãß¿˜™™f͚ÅðáÃ!!!†§OŸ2$&&2øøx346·˜ÊHI+v^A`¸üdø ̍j¡ fΠߟ½+++ www/viewmtn/mimetypes/openofficeorg-19-oasis-drawing-template.png @@ -0,0 +1,5 @@ +‰PNG + ++2,^µžaËÎ] ¿~þ`+/_¾€\úÄ h °Á\+++ www/viewmtn/mimetypes/openofficeorg-19-oasis-drawing.png @@ -0,0 +1,8 @@ +‰PNG + ++ + +ýk&&&žÿˆd+Ãß¿˜™™f͚ÅðáÃ!!!†§OŸ2$&&2øøx346·˜ÊHI+œx~ y0üúÃÀðë|ïæ address@hidden www/viewmtn/mimetypes/openofficeorg-19-oasis-formula.png @@ -0,0 +1,8 @@ +‰PNG + ++ + +ýk&&&žÿˆd+Ãß¿˜™™f͚ÅðáÃ!!!†§OŸ2$&&2øøx346·˜ÊHI+L>'A+++ www/viewmtn/mimetypes/openofficeorg-19-oasis-master-document.png @@ -0,0 +1,9 @@ +‰PNG + ++ + +ýk&&&žÿˆd+Ãß¿˜™™f͚ŰóÈ)yYi~6&†ÄÄDo†ÆæS)address@hidden;×+bÓ³gϾ±spéÿúó—á÷ïßÀøଠV¸tÇAE tðøñcuD+++ www/viewmtn/mimetypes/openofficeorg-19-oasis-presentation-template.png @@ -0,0 +1,5 @@ +‰PNG + ++2,^µžaËÎ] address@hidden www/viewmtn/mimetypes/openofficeorg-19-oasis-presentation.png @@ -0,0 +1,8 @@ +‰PNG + ++ + +ýk&&&žÿˆd+Ãß¿˜™™f͚ÅðáÃ!!!†§OŸ2$&&2øøx346·˜ÊHI+¾yQ Ä p°øüe÷«çOc¼{ÉðçÇW†½1,’—dàåå't+++ www/viewmtn/mimetypes/openofficeorg-19-oasis-spreadsheet-template.png @@ -0,0 +1,7 @@ +‰PNG + ++2,^µžaËÎ] ¿~þ`+å…̺v+£°Aà͛7 ?þÜ b˳gϾi}PŠ› 2+ûïoF ŸN@ "+$`+++ www/viewmtn/mimetypes/openofficeorg-19-oasis-spreadsheet.png @@ -0,0 +1,10 @@ +‰PNG + ++ + +ýk&&&žÿˆd+Ãß¿˜™™f͚ÅðáÃ!!!†§OŸ2$&&2øøx346·˜ÊHI+†jŸ{`Í Me®wᆀb˜~v€Ø+®™ÍÌÄŽR`ò9 âÓêÕ«YÿýûÏúÿ?ÐV`²aF†ÿ !‹Šn1ü address@hidden www/viewmtn/mimetypes/openofficeorg-19-oasis-text-template.png @@ -0,0 +1,5 @@ +‰PNG + ++2,^µžaËÎ] ¿~þ`+ œlàdÃðæÍuD+++ www/viewmtn/mimetypes/openofficeorg-19-oasis-text.png @@ -0,0 +1,12 @@ +‰PNG + ++ + +ýk&&&žÿˆd+Ãß¿˜™™f͚ÅðáÃ!!!†§OŸ2$&&2øøx346·˜ÊHI+ + +­ˆ¥­­Íõ͛77“Êêu_¼yÏ d3üúüžáÚëï address@hidden b|\  address@hidden@ T  (&Ÿ“ address@hidden|Ì õav X0‡‚2Ð;Z address@hidden www/viewmtn/mimetypes/openofficeorg-19-oasis-web-template.png @@ -0,0 +1,8 @@ +‰PNG + ++ + +ýk&&&žÿˆd+Ãß¿˜™™f͚ÅðáÃ!!!†§OŸ2$&&2øøx346·˜ÊHI+I€cåó§÷ Ý¦‹o/rsso(_K(¨j~øòá7$V=\\CcΟÞË°qÕT°Øý»—Áž½|ÏÀÊ)yÄ ¦gϞ}cçàÒÿõç/Ãïß¿1ð‡AYA–AZV…AMÓ˜\¼¼ *š† z&v ÒZ ¬lìAâ+++ www/viewmtn/mimetypes/openofficeorg-19-presentation-template.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/openofficeorg-19-presentation.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/openofficeorg-19-spreadsheet-template.png @@ -0,0 +1,5 @@ +‰PNG + ++1Æ£*lFáAt&TUUTEDDÖÅ…å¼ zöÞüK‡·0„BXGœsÎ9_Ǐ§W¡)ÀPryÆÌÌÌ×ñ¶nZ€wP‹`»DDDþ¾V +§ð˜™EŠð%)èu %ĔzOx:P;a Þtµ•́;'́'address@hidden; ÁÉÚþïøCoíF¿œ±kµ¶+++ www/viewmtn/mimetypes/openofficeorg-19-spreadsheet.png @@ -0,0 +1,4 @@ +‰PNG + ++Y:)+++ www/viewmtn/mimetypes/openofficeorg-19-text-template.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/mimetypes/openofficeorg-19-text.png @@ -0,0 +1,3 @@ +‰PNG + ++++ www/viewmtn/monotone.py @@ -0,0 +1,396 @@ + +import utility +import urllib +import signal +import string +import syslog +import pipes +import sets +import sha +import re +import os +from colorsys import hls_to_rgb + +# +# a python wrapper for the "monotone" command +# +# should really use the "automate" interface as much as possible +# + +id_re = re.compile(r'^[0-9a-f]+$') +dash_re = re.compile(r'^-+$') +cert_value_re = re.compile(r'^(\S*) *: (.*)$') +basic_io_re = re.compile(r'^ *(\S+) [\"\[](.*)[\"\]]$') +basic_io_hex_re = re.compile(r'^ *(\S+) (\[[0-9A-Fa-f]*\])$') +basic_io_string_re = re.compile(r'^ *(\S+) (\".*)$') +manifest_entry_re = re.compile(r'^(\S+) *(.*)$') +log_entry_re = re.compile(r'^(\S+): (.*)$') + +def colour_from_string(str): + def f(off): + return ord(hashval[off]) / 256.0 + hashval = sha.new(str).digest() + hue = f(5) + li = f(1) * 0.15 + 0.55 + sat = f(2) * 0.5 + .5 + return ''.join(map(lambda x: "%.2x" % int(x * 256), hls_to_rgb(hue, li, sat))) + +class Automation: + def __init__(self, base_command): + self.command = "%s automate stdio" % (base_command) + self.process = None + self.debug = False + def __del__(self): + self.stop() + self.process = None + def start(self): + self.process = popen2.Popen3(self.command) + set_nonblocking(self.process.fromchild) + if self.debug: syslog.syslog("AUTOMATE; %s starting %s" % (os.getpid(), self.process.pid)) + def stop(self): + if not self.process: return + if self.debug: syslog.syslog("AUTOMATE: %s stopping %s" % (os.getpid(), self.process.pid)) + try: + self.process.tochild.close() + self.process.fromchild.close() + if self.process.poll() == -1: + # the process is still running, so kill it. + os.kill(self.process.pid, signal.SIGKILL) + self.process.wait() + except: + if self.debug: syslog.syslog("AUTOMATE: %s failed_to_stop %s" % (os.getpid(), self.process.pid)) + pass + def run(self, command, args): + if self.process == None: self.start() + if self.debug: + syslog.syslog("AUTOMATE: %s run_via %s %s %s" % (os.getpid(), self.process.pid, str(command), str(args))) + enc = "l%d:%s" % (len(command), command) + enc += ''.join(map(lambda x: "%d:%s" % (len(x), x), args)) + 'e' + if self.debug: + syslog.syslog("AUTOMATE: %s run_via_send %s %s %s" % (os.getpid(), self.process.pid, str(command), enc)) + # note that fromchild is nonblocking, but tochild is blocking + self.process.tochild.write(enc) + self.process.tochild.flush() + r = RecvPacket() + complete = False + result_string = "" + result_code = None + while not complete: + if self.debug: + syslog.syslog("AUTOMATE: %s run_via_select %s" % (os.getpid(), self.process.pid)) + ro, rw, re = select.select([self.process.fromchild], [], [], None) + if not ro and not rw and not re: + break + if self.process.fromchild in ro: + recv = self.process.fromchild.read() + if recv == "": break + if self.debug: + syslog.syslog("AUTOMATE: %s run_via_recv %s %s %s" % (os.getpid(), self.process.pid, str(command), recv)) + + data_to_parse = recv + while not complete: + tv = r.process_data(data_to_parse) + if tv == None: + # this data did not result in a complete packet; we need more data from the client + break + else: + cmdnum, error, length, is_last, result = tv + if result_code == None: result_code = int(error) + result_string += result + if is_last: + complete = True + else: + # any left-over bytes must be parsed in case we have another complete packet + data_to_parse = r.buffer + r = RecvPacket() + return result_code, result_string + +class Monotone: + def __init__(self, mt, dbfile): + self.mt = mt + self.dbfile = dbfile + self.base_command = "%s --db=%s" % (self.mt, pipes.quote(self.dbfile)) + self.automate = Automation(self.base_command) + def branches(self): + result = utility.run_command(self.base_command + " ls branches") + if result['exitcode'] != 0: + raise Exception("Unable to list branches: %s" % (result['childerr'])) + else: + return filter(None, result['fromchild'].split('\n')) + def tags(self): + result = utility.run_command(self.base_command + " ls tags") + if result['exitcode'] != 0: + raise Exception("Unable to list tags: %s" % (result['childerr'])) + else: + return map(lambda x: x.split(' ', 2), filter(None, result['fromchild'].split('\n'))) + def heads(self, branch): + error, result = self.automate.run('heads', [branch]) + if error != 0: + raise Exception("Unable to get list of heads for %s: %s" % (branch, result)) + else: + return filter(None, result.split('\n')) + def basic_io_parser(self, data): + """returns a list of lists of (key, value) tuples. hashes are returned with []s around + them; strings are returned raw.""" + def unescape_string_value(str): + rv = "" + is_terminated = False + in_escape = False + if str[0] != '"': + raise Exception("basic_io parse error; not a string.") + for c in str[1:]: + if in_escape: + if c != '\\' and c != '\"': + raise Exception(r'basic_io parse error; expected \" or \\') + rv += c + in_escape = False + else: + if c == '\\': + in_escape = True + elif c == '"': + if is_terminated: + raise Exception("basic_io parse error; string ends twice!") + is_terminated = True + else: + rv += c + return is_terminated, rv + + # 14:46 < tbrownaw> list>>, with the outer list divided according to + # what item starts a stanza? + + rv = {} + + stanza = [] + ongoing_string = None + + for line in data.split('\n'): + if ongoing_string != None: + ongoing_string += '\n' + line + is_terminated, e_value = unescape_string_value(ongoing_string) + if is_terminated: + stanza += [key, e_value] + ongoing_string = None + continue + + if line == '' and len(stanza) != 0: + rv.setdefault(stanza[0], []).append(stanza) + stanza = [] + continue + + m = basic_io_hex_re.match(line) + if m: + key, value = m.groups() + stanza += [key, value[1:-1]] + continue + + m = basic_io_string_re.match(line) + if m: + key, value = m.groups() + is_terminated, e_value = unescape_string_value(value) + if not is_terminated: ongoing_string = value + else: stanza += [key, e_value] + continue + return rv + def certs(self, id): + "returns a list of certs, each a list of tuples (attribute,value)" + error, data = self.automate.run('certs', [id]) + if error != 0: raise Exception("Error obtaining cert for %s: %s" % (id, data)) + parsed = self.basic_io_parser(data) + if len(parsed.keys()) != 1 or parsed.keys()[0] != 'key': + raise Exception("basic_io format for certs has changed: unknown cert types '%s' found" % (str(parsed.keys()))) + certs = parsed['key'] + rv = [] + for cert in certs: + rv.append([(cert[t], cert[t+1]) for t in range(0, len(cert), 2)]) + return rv + def ancestors(self, ids): + if type(ids) == type(""): ids = [ids] + error, result = self.automate.run('ancestors', ids) + if error != 0: + raise Exception("Unable to get ancestors of %s: %s" % (str(ids), + result)) + else: + return filter(None, result.split('\n')) + def toposort(self, ids): + if type(ids) == type(""): ids = [ids] + error, result = self.automate.run('toposort', ids) + if error != 0: + raise Exception("Unable to toposort: %s" % (result)) + else: + return filter(None, result.split('\n')) + def revision(self, id): + error, result = self.automate.run('get_revision', [id]) + if error != 0: + raise Exception("Unable to get revision %s: %s" % (id, result)) + return self.basic_io_parser(result) + def parents(self, id): + error, result = self.automate.run('parents', [id]) + if error != 0: + raise Exception("Unable to get parents of %s: %s" % (id, result)) + else: + return filter(None, result.split('\n')) + def manifest(self, id): + error, result = self.automate.run('get_manifest', [id]) + if error != 0: + raise Exception("Unable to get manifest %s: %s" % (id, result)) + rv = [] + for line in result.split('\n'): + m = manifest_entry_re.match(line) + if not m: continue + rv.append(m.groups()) + return rv + def file(self, id): + error, result = self.automate.run('get_file', [id]) + if error != 0: + raise Exception("Unable to get file %s: %s" % (id, result)) + else: + return result + + def annotate(self, id, file): + result = utility.run_command(self.base_command + " annotate --revision=%s %s" % (pipes.quote(id), + pipes.quote(file))) + if result['exitcode'] != 0: + raise Exception("Unable to annotate file: %s using command '%s'" % (result['childerr'], + result['run_command'])) + else: + return result['fromchild'] + + def diff(self, rev_from, rev_to, files=None): + command = self.base_command + " diff -r %s -r %s" % (pipes.quote(rev_from), pipes.quote(rev_to)) + if files != None: command += ' ' + ' '.join(map(pipes.quote, files)) + syslog.syslog(command) + result = utility.run_command(command) + if result['exitcode'] != 0: + raise Exception("Unable to calculate diff: %s" % (result['childerr'])) + else: + return result['fromchild'] + def log(self, ids, limit=0): + rv = [] + entry = None + command = self.base_command + " log " + ' '.join(map(lambda x: '-r ' + pipes.quote(x), ids)) + if limit > 0: command += " --last=%d" % (limit) + iterator = utility.iter_command(command) + for line in iterator: + if dash_re.match(line): + entry = {} + if entry == None: continue + if not line: + rv.append(entry) + entry = None + continue + m = log_entry_re.match(line) + if m: + attr, value = m.groups() + if not entry.has_key(attr): entry[attr] = [] + entry[attr].append(value) + # clean up; otherwise we'll leak filehandlers + map(None, iterator) + if entry: rv.append(entry) + return rv + def ancestry_graph(self, graphopts, id, limit=0): + def dot_escape(s): + # kinda paranoid, should probably revise later + permitted=string.digits + string.letters + ' -<>-:,address@hidden&.+_~?/' + return ''.join(filter(lambda x: x in permitted, s)) + graphdir = graphopts['directory'] + graphuri = graphopts['uri'] + graph_id = "%s.%d" % (id, limit) + rv = { + 'dot_file' : os.path.join(graphdir, graph_id + ".dot"), + 'image_file' : os.path.join(graphdir, graph_id + ".png"), + 'imagemap_file' : os.path.join(graphdir, graph_id + ".html"), + 'dot_uri' : "%s/%s.dot" % (graphuri, urllib.quote(graph_id)), + 'image_uri' : "%s/%s.png" % (graphuri, urllib.quote(graph_id)), + 'imagemap_uri' : "%s/%s.html" % (graphuri, urllib.quote(graph_id)), + } + need_access = ['dot_file', 'image_file', 'imagemap_file'] + missing = filter(lambda x: x != True, map(lambda x: os.access(rv[x], os.R_OK), need_access)) + if len(missing) == 0: + rv['cached'] = True + return rv + contents = 'digraph ancestry {\nratio=compress\nnodesep=0.1\nranksep=0.2\nedge [dir=back];\n' + revisions = {} + for attrs in self.log([id], limit): + if not attrs.has_key("Revision") or not attrs.has_key("Ancestor"): + continue + revision = attrs['Revision'][0] + revisions[revision] = attrs + for ancestor in attrs['Ancestor']: + if len(ancestor) == 0: continue + if not revisions.has_key(ancestor): revisions[ancestor] = None + contents += '"%s"->"%s"[href="getdiff.py?id1=%s&id2=%s"]\n' % (urllib.quote(revision), + urllib.quote(ancestor), + urllib.quote(ancestor), + urllib.quote(revision)) + for revision in revisions.keys(): + label = "%s..." % (revision[0:8]) + attrs = revisions[revision] + if attrs == None: + # fill in the gaps; would be nice to clean this up. + # shouldn't take long, anyway. + attrs = self.log([revision], 1)[0] + if attrs.has_key('Date'): + d = dot_escape(attrs['Date'][0]) + d = d[0:d.find("T")] + label += " on %s" % d + if attrs.has_key('Author'): + label += "\\n%s" % (dot_escape(attrs['Author'][0])) + override_fillcolor = colour_from_string(attrs['Author'][0]) + else: override_fillcolor = None + opts = 'label="%s"' % label #revision[0:8] + for opt in graphopts['nodeopts']: + if opt == 'fillcolor' and override_fillcolor != None: continue + opts += ',%s="%s"' % (opt, graphopts['nodeopts'][opt]) + if revision == id: opts += ",color=blue" + opts += ',href="revision.psp?id=%s"' % urllib.quote(revision) + if override_fillcolor != None: opts += ',fillcolor="#%s"' % (override_fillcolor) + contents += '"%s" [%s]\n' % (revision, opts) + contents += "}\n" + open(rv['dot_file'], 'w').write(contents) + os.system("%s -Tcmapx -o %s -Tpng -o %s %s" % (graphopts['dot'], + pipes.quote(rv['imagemap_file']), + rv['image_file'], + rv['dot_file'])) + rv['cached'] = False + return rv + +def is_valid_id(s): + return len(s) == 40 and id_re.match(s) != None + +import popen2 +import select +from utility import set_nonblocking + +packet_header_re = re.compile(r'(\d+):(\d+):([lm]):(\d+):') + +class RecvPacket: + "A packet received from monotone automate stdio" + def __init__(self, init_buffer=""): + self.buffer = init_buffer + self.cmdnum = None + self.error = None + self.length = None + self.is_last = None + self.result = "" + def process_data(self, data): + self.buffer += data + if self.length == None: + # we have not yet read a complete header line for this packet + m = packet_header_re.match(self.buffer) + if m: + self.cmdnum, self.error, pstate, self.length = m.groups() + self.length = int(self.length) + self.is_last = pstate == "l" + self.buffer = self.buffer[m.end(m.lastindex)+1:] + if self.length != None and len(self.result) < self.length: + needed = self.length - len(self.result) + if len(self.buffer) >= needed: + available = needed + else: + available = len(self.buffer) + self.result += self.buffer[:available] + self.buffer = self.buffer[available:] + if len(self.result) == self.length: + return (self.cmdnum, self.error, self.length, self.is_last, self.result) + else: return None + --- www/viewmtn/revision.psp +++ www/viewmtn/revision.psp @@ -0,0 +1,172 @@ +<% + +import config +import monotone +import datetime +import common +import urllib +from common import parse_timecert, ago_string, get_branch_links + +# +# revision.psp +# display all information we can about a particular revision +# + +def prettify(s): + return ' '.join(map(lambda x: hq(x[0].upper() + x[1:]), s.replace("_", " ").split(" "))) + +psp.set_error_page("error.psp") + +if not form.has_key('id'): + raise Exception("No revision ID specified.") +id = form['id'] +if not monotone.is_valid_id(id): + raise Exception("Specified revision ID is not valid.") + +# read certificates +certs = mt.certs(id) +branches, cert_table = [], "" +for cert in certs: + name, value = None, None + for k, v in cert: + if k == "name": name = v + elif k == "value": value = v + if name == None or value == None: continue + if name == "date": + event = parse_timecert(value) + now = datetime.datetime.utcnow() + ago = ago_string(event, now) + value += '\n' + ago + ' ago' + if name == "branch": + branches.append(value) + value = link("branch", value) + else: + value = '
'.join(map(hq, value.split('\n'))) + cert_table += '%s%s' % (prettify(name), value) + +certs = mt.certs(id) +branches = common.extract_cert_from_certs(certs, "branch", as_list=True) +branch_links = get_branch_links(mt, branches) + +info = { + 'title' : "Revision '%s' for %s" % (link("revision", id), + branch_links), + 'branch_rss' : branches + } +req.write(template.header(info)) + +# generate the revision graph +ancestry_limit = 10 +ancestry_maximum = 100 +try: + if form.has_key('ancestry_limit'): + ancestry_limit = int(form['ancestry_limit']) +except: pass +if ancestry_limit == 0 or ancestry_limit > ancestry_maximum: + ancestry_limit = ancestry_maximum + +ancestry_graph = mt.ancestry_graph(config.graphopts, id, ancestry_limit) +if os.access(ancestry_graph['imagemap_file'], os.R_OK): + req.write(open(ancestry_graph['imagemap_file']).read()) +else: + req.write('
Unable to access ancestry graph; perhaps graphviz could not be found?
') + +revision = mt.revision(id) + +%> + +
+ +<% +if not revision.has_key('new_manifest'): +%> +

Note: no manifest is associated with this revision and hence it contains no files.

+<% +else: + req.write('''

You might wish to %s.

''' % (link("manifest", id, "browse this revision's contents"))) + +%> + +

Certificates

+ + +<%= cert_table %> +
+ +

Revision details

+ + +<% +old_revisions = [] +for type in revision.keys(): + value = "" + for stanza in revision[type]: + if type == "patch": + fname, from_id, to_id = stanza[1], stanza[3], stanza[5] + # if from_id == None, then this is a new file; we're showing + # that information under "Add file" anyway. + if from_id: + diff_links = ','.join([link("diff", [t, id, fname]) for t in old_revisions]) + value += 'Patch file %s (%s)
' % \ + (link("file", [id, fname], fname), + diff_links) + elif type == "old_revision": + old_revision, old_manifest = stanza[1], stanza[3] + old_revisions.append(old_revision) + value += 'Old revision is: %s (%s)
' % \ + (link("revision", old_revision), + link("diff", [old_revision, id])) + elif type == "new_manifest": + # swallow this, it's not useful + pass + # new_manifest = stanza[1] + # value += 'New manifest is: %s
' % (link("manifest", id)) + elif type == "add_file": + fname = stanza[1] + value += "Add file: %s
" % (link("file", [id, fname], fname)) + elif type == "delete_file": + fname = stanza[1] + value += "Delete file: %s
" % (link("file", [id, fname], fname)) + elif type == "delete_dir": + fname = stanza[1] + value += "Delete directory: %s
" % (link("file", [id, fname], fname)) + elif type == "rename_file": + old_name, new_name = stanza[1], stanza[3] + value += "Rename file %s as %s
" % (hq(old_name), link("file", [id, new_name], new_name)) + elif type == "rename_dir": + old_name, new_name = stanza[1], stanza[3] + value += "Rename directory %s as %s
" % (hq(old_name), hq(new_name)) + if value: + req.write('' % (prettify(type), value)) + +%> +
%s%s
+
+ +
+ +Ancestry of <%= hq(id) %>
+<% +increment = 5 +zoom_out = ancestry_limit + increment +zoom_in = ancestry_limit - increment +if zoom_in <= 0: zoom_in = 1 +%> + + +ancestry: last <%=ancestry_limit%> log entries ( +">fewer, +">more, or +">all) + + +
+ + + +<% + +req.write(template.footer(info)) + +%> + --- www/viewmtn/rss_feed.gif +++ www/viewmtn/rss_feed.gif @@ -0,0 +1,4 @@ +GIF89a +# )½<*—Á–Ã4±"JF +½B'؟Ú+V*Àµ«×¯`Ã~BD¶l"address@hidden www/viewmtn/tags.psp @@ -0,0 +1,59 @@ +<% + +from common import parse_timecert, ago_string, determine_date +import datetime + +psp.set_error_page("error.psp") +info = { 'title' : "Tags" } +req.write(template.header(info)) + +tags = mt.tags() + +%> + +<% +if len(tags) == 0: + # no tags +%> +

+There are no tags in the provided monotone database. +

+<% +else: + # okay, so display the tags +%> +

+A tag marks a particular revision that is in some way significant. A +common use of tags is to mark public release of a piece of software. +To view a particular tag, select it from the list below. +

+ + + +<% + now = datetime.datetime.utcnow() + tags.sort(lambda x,y: cmp(x[0], y[0])) + for tag in tags: + tag_name, tag_id, signed_by = tag + certs = mt.certs(tag_id) + ago = determine_date(certs) + if ago != None: + ago = ago_string(ago, now) + " ago" + else: + ago = "" + req.write('' % \ + (link("revision", tag_id, tag_name), + hq(signed_by), + hq(ago or ""))) + +%> +
TagSigned byWhen
%s%s%s
+ +<% + +%> + +<% +req.write(template.footer(info)) +%> + --- www/viewmtn/tarofbranch.psp +++ www/viewmtn/tarofbranch.psp @@ -0,0 +1,47 @@ +<% + +import urllib + +psp.set_error_page("error.psp") + +if not form.has_key('branch'): + raise Exception("No branch specified.") +branch = form['branch'] + +# tarofbranch.psp +# +# allow a tar file to be retrieved of the latest +# HEAD of a branch. If there are multiple heads, +# present a choice. + +heads = mt.heads(branch) +if len(heads) == 0: + raise Exception("No head ID can be determined for this branch.") +elif len(heads) == 1: + revision = mt.revision(heads[0]) + manifest_id = revision['new_manifest'][0][1] + psp.redirect("gettar.py?id=%s" % (urllib.quote(manifest_id))) +else: + info = {'title' : "Latest tar file of branch %s" % (hq(branch))} + req.write(template.header(info)) +%> +

+The branch you have selected has multiple head revisions The head +revisions are listed below, along with links to download a tar file of +each revision. +

+ + + +<% + for id in heads: + revision = mt.revision(id) + manifest_id = revision['new_manifest'][0][1] + req.write('' % (link("revision", id), link("tar", manifest_id, "download tar archive"))) + +%> +
Head revisionTar file
%s%s
+ +<% +req.write(template.footer(info)) +%> --- www/viewmtn/tests/automate.py +++ www/viewmtn/tests/automate.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python + +import sys +sys.path.append("..") +import time +import monotone +import config +import traceback + +def test_branches(mt): + """Test the mt.branches method""" + result = mt.branches() + if len(result) == 0: + raise Exception("No branches found") + +def test_tags(mt): + """Test the mt.tags method""" + result = mt.tags() + if len(result) == 0: + raise Exception("No tags found") + +def test_heads(mt): + """Test the mt.heads method""" + result = mt.heads(config.test_branch) + if len(result) == 0: + raise Exception("No heads found") + +def test_certs(mt): + """Test the mt.certs method""" + result = mt.certs(config.test_revision) + print result + if len(result) == 0: + raise Exception("No certs") + +def test_ancestors(mt): + """Test the mt.ancestors method""" + result = mt.ancestors(config.test_revision) + if len(result) == 0: + raise Exception("No certs") + +def test_toposort(mt): + """Test the mt.toposort method""" + tosort = mt.heads(config.test_branch) + result = mt.toposort(tosort) + if len(result) == 0: + raise Exception("No result to toposort") + +def test_revision(mt): + """Test the mt.revision method""" + rev = mt.revision(config.test_revision) + if rev == None: + raise Exception("Unable to retrieve revision") + +def test_manifest(mt): + """Test the mt.manifest method""" + rev = mt.revision(config.test_revision) + print rev + return + if not rev.has_key('new_manifest'): + raise Exception("Couldn't find manifest.") + print rev + return + manifest = rev['new_manifest'][0][1] + print manifest + +def test_file(mt): + pass + +def test_crazy_toposort(mt): + """Test huge (silly) arguments""" + #ancestors = mt.heads(config.test_branch) + ancestors = mt.ancestors(mt.heads(config.test_branch)) + while len(ancestors) < 1000: + result = mt.toposort(ancestors) + open('command.txt', 'w').write(config.monotone + 'automate ancestors ' + ' '.join(ancestors)) + ancestors *= 2 + +def test_error_string(mt): + """Test what happens when we do something wrong resulting in an error""" + try: + heads = mt.certs('0000000000000000000000000000000000000000') + raise Exception("No error occurred.") + except Exception: pass + +def test_invalid_data(mt): + """Test what happens when we do something which is invalid""" + try: + mt.certs('0000') # too short + raise Exception("No error occurred.") + except Exception: pass + +tests = [ + +# disabled as they don't use automate +# test_branches, +# test_tags, + + test_ancestors, + test_toposort, + test_revision, + test_manifest, + test_file, + + test_heads, + test_certs, + + test_crazy_toposort, + test_error_string, + test_invalid_data, +] + +def test(): + for idx, test in enumerate(tests): + def log(s): + print "#%-3d %s" % (idx, s) + log(test.__doc__) + try: + test(mt) + except KeyboardInterrupt: + break + except: + log ("Test FAILED; traceback follows") + traceback.print_exc() + print + +if __name__ == "__main__": + mt = monotone.Monotone(config.monotone, config.dbfile) + if len(sys.argv) == 2: + attempts = int(sys.argv[1]) + else: + attempts = 1 + + while attempts > 0: + test() + time.sleep(10) + attempts -= 1 + mt.automate.stop() --- www/viewmtn/utility.py +++ www/viewmtn/utility.py @@ -0,0 +1,100 @@ + +import popen2 +import select +import fcntl +import os + +def set_nonblocking(fd): + fl = fcntl.fcntl(fd, fcntl.F_GETFL) + fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NDELAY) + +def run_command(command, timeout=None, to_child=None): + "returns a tuple of (was_timeout, exit_code, data_read)" + p = popen2.Popen3(command, capturestderr=True) + set_nonblocking(p.fromchild) + set_nonblocking(p.childerr) + fromchild_read = "" + childerr_read = "" + was_timeout = False + if to_child != None: + p.tochild.write(to_child) + p.tochild.close() + while 1: + ro, rw, re = select.select([p.fromchild], [], [p.childerr], timeout) + if not ro and not rw and not re: + was_timeout = True + break + if p.fromchild in ro: + recv = p.fromchild.read() + if recv == "": break + fromchild_read += recv + if p.childerr in re: + recv = p.childerr.read() + if recv == "": break + childerr_read += recv + if not was_timeout: + # check for any data we might have missed (due to a premature break) + # (if there isn't anything we just get a IOError, which we don't mind + try: fromchild_read += p.fromchild.read() + except IOError: pass + try: childerr_read += p.childerr.read() + except IOError: pass + p.fromchild.close() + # if there wasn't a timeout, the program should have exited; in which case we should wait() for it + # otherwise, it might be hung, so the parent should wait for it. + # (wrap in a try: except: just in case some other thread happens to wait() and grab ours; god wrapping + # python around UNIX is horrible sometimes) + exitcode = None + try: + if not was_timeout: exitcode = p.wait() >> 8 + except: pass + return { 'run_command' : command, + 'timeout' : was_timeout, + 'exitcode' : exitcode, + 'fromchild' : fromchild_read, + 'childerr' : childerr_read } + +def iter_command(command, timeout=None): + p = popen2.Popen3(command, capturestderr=True) + set_nonblocking(p.fromchild) + set_nonblocking(p.childerr) + fromchild_read = "" + childerr_read = "" + was_timeout = False + while 1: + ro, rw, re = select.select([p.fromchild], [], [p.childerr], timeout) + if not ro and not rw and not re: + was_timeout = True + break + if p.fromchild in ro: + recv = p.fromchild.read() + if recv == "": break + fromchild_read += recv + while 1: + nl = fromchild_read.find('\n') + if nl == -1: break + yield fromchild_read[:nl] + fromchild_read = fromchild_read[nl+1:] + if p.childerr in re: + recv = p.childerr.read() + if recv == "": break + childerr_read += recv + if not was_timeout: + # check for any data we might have missed (due to a premature break) + # (if there isn't anything we just get a IOError, which we don't mind + try: fromchild_read += p.fromchild.read() + except IOError: pass + try: childerr_read += p.childerr.read() + except IOError: pass + p.fromchild.close() + p.tochild.close() + # yield anything left over + to_yield = fromchild_read.split('\n') + while len(to_yield): yield to_yield.pop() + # call wait() + try: + if not was_timeout: p.wait() + except: pass + if len(childerr_read): raise Exception("data on stderr (command is %s)" % command, childerr_read) + if was_timeout: raise Exception("command timeout") + --- www/viewmtn/viewmtn.css +++ www/viewmtn/viewmtn.css @@ -0,0 +1,93 @@ + +BODY { + background-color: white; + font-family: "Trebuchet MS", Verdana, Sans-Serif; + font-size : 100%; +} + +DIV#menuBar { + font-family: "Trebuchet MS", Verdana, Sans-Serif; + font-size: 80%; + padding-top: 1em; + text-align: center; +} + +A { + text-decoration: none; + background-color: transparent; +} + +A:hover { + background-color: #DDF; + text-decoration: none; +} + +DIV#footer { + clear: both; + border-top-width: 1px; + border-top-style: solid; + border-top-color: black; + margin-top: 2em; + text-align: right; +} + +TABLE.pretty { + border-width: 1px; + border-style: solid; + border-color: black; +} + +TABLE.pretty TD { + padding-left: 0.5em; + padding-right: 0.5em; +} + +TABLE.pretty TH { + padding-left: 0.5em; + padding-right: 0.5em; +} + +TR.odd { + background-color: #eeeeee; +} + +TR.even { + background-color: #ffffff; +} + +TD.branchInfoHeader { + border-width: 3px; + border-style: solid; + border-color: blue; + text-align: center; + +} + +TABLE.pretty TH { + color: blue; + background-color: #dfe7f3; +} + +H1#pageTitle { + font-size: 120%; + font-family: sans-serif; +} + +H2 { + font-size: 120%; + font-family: sans-serif; +} + +.invisible { + display: none; +} + +DIV#popupBox { + position: absolute; + border-width: 1px; + border-style: solid; + border-color: black; + background-color: #d0d0f0; + padding: 2px; + z-index: 10; +} --- www/viewmtn/viewmtn.js +++ www/viewmtn/viewmtn.js @@ -0,0 +1,105 @@ + +var theBox; +var callbacksInstalled = false; +var pendingDeferred = null; + +function installCallbacks() +{ + if (callbacksInstalled != false) { + return; + } + callbacksInstalled = true; + + cbinst = function (e) { + updateNodeAttributes(e, { "onmouseover" : partial(mouseOverHandler, e), + 'onmouseout' : partial(mouseOutHandler, e) } ); + } + + var elems = getElementsByTagAndClassName(null, "branchLink"); + map(cbinst, elems); + + var elems = getElementsByTagAndClassName(null, "revisionLink"); + map(cbinst, elems); + + var elems = getElementsByTagAndClassName(null, "manifestLink"); + map(cbinst, elems); + + theBox = $("popupBox"); +} + +function updatePopup(boundTo, className) +{ + jsonData = boundTo.jsonData; + + var pos = elementPosition(boundTo); + var newBox; + + info = null; + if (jsonData.type == "branch") { + info = "branch last updated " + jsonData.ago + " by " + jsonData.author; + } else if (jsonData.type == "revision") { + info = jsonData.ago + " ago by " + jsonData.author; + } else if (jsonData.type == "manifest") { + info = "manifest contains " + jsonData.file_count + " files in " + jsonData.directory_count + " directories."; + } else { + info = "unknown type: " + jsonData.type; + } + + newBox = DIV({ 'id' : 'popupBox'}, + info); + + if (boundTo.offsetHeight) { + offset_height = boundTo.offsetHeight; + } else { + offset_height = 24; // yick + } + + newY = pos.y + offset_height + 20; + newX = pos.x + 20; + + newBox.style.top = newY + 'px'; + newBox.style.left = newX + 'px'; + swapDOM(theBox, newBox); + theBox = newBox; +} + +function jsonLoadComplete(boundTo, className, jsonData) +{ + boundTo.jsonData = jsonData; + updatePopup(boundTo, className); + pendingDeferred = null; +} + +function mouseOverHandler(boundTo, evt) +{ + var className = getNodeAttribute(boundTo, "class"); + + if (boundTo.jsonData) { + return updatePopup(boundTo, className); + } + + links = getElementsByTagAndClassName('a', null, boundTo); + + if (links.length > 0) { + linkHref = links[0].href; + } else { + return; + } + + var uri = "getjson.py?className=" + encodeURIComponent(className) + "&linkUri=" + encodeURIComponent(linkHref); + var d = loadJSONDoc(uri); + + d.addCallback(jsonLoadComplete, boundTo, className); + pendingDeferred = d; +} + +function mouseOutHandler(boundTo, evt) +{ + if (pendingDeferred != null) { + pendingDeferred.cancel(); + pendingDeferred = null; + } + var newBox = DIV({'id' : 'popupBox', 'class' : 'invisible'}); + swapDOM(theBox, newBox); + theBox = newBox; +} --- www/viewmtn/viewmtn.py +++ www/viewmtn/viewmtn.py @@ -0,0 +1,3 @@ + +# the latest release; make sure to update this (note for Grahame) +release = "0.05" --- www/viewmtn/wrapper.py +++ www/viewmtn/wrapper.py @@ -0,0 +1,218 @@ + +from mod_python import apache,psp,util +from common import parse_timecert, ago_string, determine_date +import mimetypes +import monotone +import urlparse +import datetime +import os.path +import tarfile +import common +import config +import urllib +import json +import os +import re + +from monotone import Monotone +from html import Template + +# paranoid +sane_uri_re = re.compile('^\w+$') + +def get_file(req, vars): + mt = vars['mt'] + form = util.FieldStorage(req) + if not form.has_key('id'): + return apache.HTTP_BAD_REQUEST + id = form['id'] + if not monotone.is_valid_id(id): + return apache.HTTP_BAD_REQUEST + if form.has_key('plain'): + mime_type = "text/plain" + else: + mime_type = None + if form.has_key('path'): + if mime_type == None: mime_type = mimetypes.guess_type(form['path'])[0] + req.headers_out["Content-Disposition"] = "attachment; filename=%s" % urllib.quote(os.path.split(form['path'])[-1]) + if mime_type == None: mime_type = "text/plain" + req.content_type = mime_type + "; charset=utf-8" + req.write(mt.file(id)) + return apache.OK + +def get_diff(req, vars): + mt = vars['mt'] + form = util.FieldStorage(req) + if not form.has_key('id1') or not form.has_key('id2'): + return apache.HTTP_BAD_REQUEST + if form.has_key('fname'): files = [form['fname']] + else: files = None + id1, id2 = form['id1'], form['id2'] + if not monotone.is_valid_id(id1) or not monotone.is_valid_id(id2): + return apache.HTTP_BAD_REQUEST + req.content_type = "text/plain; charset=utf-8" + req.write(mt.diff(id1, id2, files)) + return apache.OK + +def get_json(req, vars): + mt = vars['mt'] + form = util.FieldStorage(req) + if not form.has_key('className') or not form.has_key('linkUri'): + return apache.HTTP_BAD_REQUEST + class_name, link_uri = form['className'], form['linkUri'] + req.content_type = "text/plain; charset=utf-8" + writer = json.JsonWriter() + query = {} + for key, value in [t.split('=', 1) for t in urlparse.urlparse(link_uri)[4].split('&')]: + query[key] = value + + now = datetime.datetime.utcnow() + rv = {} + if class_name == "revisionLink": + rv['type'] = 'revision' + certs = mt.certs(query['id']) + rv['author'] = common.extract_cert_from_certs(certs, 'author') + change_date = determine_date(certs) + if change_date != None: + ago = ago_string(change_date, now) + else: + ago = '' + rv['ago'] = ago + elif class_name == "branchLink": + rv['type'] = 'branch' + heads = mt.heads(query['branch']) + most_recent_change = None + for head in heads: + certs = mt.certs(head) + this_change = determine_date(certs) + if this_change == None: + continue + if most_recent_change == None or this_change > most_recent_change: + most_recent_change = this_change + most_recent_change_certs = certs + if most_recent_change != None: + ago = ago_string(most_recent_change, now) + ' ago' + last_author = common.extract_cert_from_certs(most_recent_change_certs, + "author") or '' + else: + ago = '' + rv['ago'] = ago + rv['author'] = last_author + elif class_name == "manifestLink": + rv['type'] = 'manifest' + revision = mt.revision(query['id']) + if revision.has_key('new_manifest'): + manifest_id = revision['new_manifest'][0][1] + manifest = mt.manifest(manifest_id) + dir_seen = {} # would use a set, but need python2.4 really + for file_id, filename in manifest: + fsp = filename.rsplit('/', 1) + if len(fsp) == 2 and not dir_seen.has_key(fsp[1]): + dir_seen[fsp[1]] = True + rv['file_count'] = len(manifest) + rv['directory_count'] = len(dir_seen.keys()) + 1 # root dir + else: + rv['file_count'] = 0 + rv['directory_count'] = 0 + + req.write(writer.write(rv)) + return apache.OK + +def get_tar(req, vars): + "make a tar file out of a given manifest ID" + class DummyFile: + def __init__(self, buf): + self.buf = buf + def seek(offset, whence=None): + # blah + return + def read(self, size): + rv, nb = self.buf[:size], self.buf[size:] + self.buf = nb + return rv + def write(self, s): + self.buf += s + mt = vars['mt'] + form = util.FieldStorage(req) + if not form.has_key('id'): + return apache.HTTP_BAD_REQUEST + id = form['id'] + tar_file = DummyFile("") + tar_file_name = "%s.tar" % (id) + req.content_type = 'application/x-tar; charset=utf-8' + req.headers_out["Content-Disposition"] = "attachment; filename=%s" % tar_file_name + tf = tarfile.open(mode="w", fileobj=tar_file) + for fileid, filename in mt.manifest(id): + data = mt.file(fileid) + ti = tarfile.TarInfo() + ti.mode = 00700 + ti.mtime = 0 + ti.type = tarfile.REGTYPE + ti.uid = 0 + ti.gid = 0 + ti.name = os.path.join(id, filename) + ti.size = len(data) + tf.addfile(ti, DummyFile(data)) + tf.close() + req.write(tar_file.buf) + return apache.OK + +handlers = { + 'getfile.py' : get_file, + 'getdiff.py' : get_diff, + 'gettar.py' : get_tar, + 'getjson.py' : get_json +} + +def cleanup(req, vars): + mt = vars['mt'] + mt.automate.stop() + del mt + +def handler(req): + uri = req.uri + slash = uri.rfind('/') + if slash <> -1: uri = uri[slash+1:] + + # most monotone output is utf8 + req.content_type = "text/html; charset=utf-8" + + # common variables which all handlers (PSP or not) + # should have access to; ensure that these variables + # are cleaned up + mt = Monotone(config.monotone, config.dbfile(req.uri)) + def our_link (link_type, link_to, description=None, no_quote=False): + return common.link(mt, link_type, link_to, description, no_quote) + vars = { + 'mt' : mt, + 'link' : our_link, + 'hq' : common.html_escape(), + 'template' : Template() + } + req.register_cleanup(cleanup, (req, vars)) + + # + # these handlers don't use PSP, for example if they need + # to return binary or otherwise pristine data to the user + # agent + # + if handlers.has_key(uri): + if req.header_only: + return apache.OK + return handlers[uri](req, vars) + + # + # PSP or 404 + # + try: + if uri.endswith('.psp') and sane_uri_re.match(uri[:-4]): + if req.header_only: + return apache.OK + instance = psp.PSP(req, filename=uri, vars=vars) + instance.run() + return apache.OK + except ValueError: + return apache.HTTP_NOT_FOUND + + return apache.HTTP_NOT_FOUND +