Index: /tags/rel-0_10/Nebulous-Server/.cvsignore
===================================================================
--- /tags/rel-0_10/Nebulous-Server/.cvsignore	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/.cvsignore	(revision 17695)
@@ -0,0 +1,6 @@
+Build
+META.yml
+Makefile
+Makefile.PL
+_build
+blib
Index: /tags/rel-0_10/Nebulous-Server/Build.PL
===================================================================
--- /tags/rel-0_10/Nebulous-Server/Build.PL	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/Build.PL	(revision 17695)
@@ -0,0 +1,44 @@
+use Module::Build;
+# See perldoc Module::Build for details of how this works
+
+Module::Build->new(
+    module_name         => 'Nebulous',
+    dist_version_from   => 'lib/Nebulous/Server.pm',
+    author              => 'Joshua Hoblitt <jhoblitt@cpan.org>',
+    license             => 'gpl',
+    create_makefile_pl  => 'passthrough',
+    requires            => {
+        'Apache2::Const'        => 0,
+        'Class::Accessor::Fast' => 0,
+        'Config::YAML'          => '1.42',
+        'DBD::mysql'            => '3.0007',
+        'DBI'                   => '1.53',
+        'Digest::SHA1'          => 0,
+        'File::ExtAttr'         => '1.03',
+        'File::Path'            => '1.08',
+        'File::Spec'            => 0,
+        'File::Spec::Functions' => 0,
+        'File::Temp'            => 0,
+        'Log::Log4perl'         => '0.48',
+        'Net::Server::Daemonize'=> '0.05',
+        'Params::Validate'      => '0.73',
+        'SOAP::Lite'            => '0.69',
+        'Sys::Statistics::Linux::DiskUsage' => '0.02',
+        'URI'                   => '1.30',
+    },
+    build_requires      => {
+        'Test::More'            => '0.49',
+        'Test::URI'             => '1.06',
+    },
+    recommends          => {
+        'Test::Distribution'    => '1.22',
+        'Apache2::SOAP'         => 0,
+        'Apache::DBI'           => '1.05',
+    },
+    script_files        => [qw(
+        bin/neb-addvol
+        bin/neb-fsck
+        bin/neb-initdb
+        bin/nebdiskd
+    )],
+)->create_build_script;
Index: /tags/rel-0_10/Nebulous-Server/Changes
===================================================================
--- /tags/rel-0_10/Nebulous-Server/Changes	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/Changes	(revision 17695)
@@ -0,0 +1,127 @@
+Revision history for Nebulous
+
+0.10 Wed May 14 17:26:48 HST 2008
+    - cleanup error handling in Nebulous::Server::stat_object()/find_objects()
+    - update Nebulous::Server::lock_object()/unlock_object() fault strings
+    - cleanup database error handling: make sure queries are ->finished before
+      showing an expect, remove DBI->rollback from the error paths of methods
+      that don't insert data
+    - improve getxattr_object() error handling
+    - document xattr methods
+    - change Nebulous::Server::stat() to not die when passed a non-existant key
+    - change listxattr_object() to return an arrayref
+    - change default log level to 'WARN'
+    - fix _is_valid_object_key() to handle nebulous keys
+
+0.09 Tue May  6 12:12:02 HST 2008
+    - allow volume name arguments to be undef
+
+0.08 Thu Apr 17 16:08:16 HST 2008
+    - set permissions on storage instance directories
+
+0.07
+    - add Nebulous::Server::mounts()
+    - server/client split into Nebulous::Server and Nebulous::Client packages
+    - add support for storing instances in multi-tier 'hashed' directories 
+    - pickup test DB settings from the env.
+    - add neb-ls -l|-1 option
+    - add neb-fsck
+    - add neb-stat
+    - add additional fkey constrains to the instance table
+    - fix installation of neb-replicate & neb-cull
+    - redo database schema with foreign keys
+    - add Nebulous::Keys pod
+
+0.06 Fri Jan 25 10:55:59 HST 2008
+    - define Nebulous::Client::find_objects() as returning [] when NO keys are
+      found
+    - change neb-touch to use Nebulous::Client::find_objects() instead of
+      ::find()
+    - add Nebulous::Sever:: neb key/uri support
+    - add Nebulous::Util::parse_neb_key()
+    - change bin/* scripts to conistently use --volume instead of --node,
+      etc.
+    - pod cleanups
+    - add neb-replicate & neb-cull
+
+0.05 2007-05-04 16:59:31(???)
+    - make filesystem xattr support optional & disabled by default
+    - generate instance filenames that leave the "key" at the end so as to not
+      break a pplications that expect a certain "extension" on the filename
+    - set an xattr on all created files of "user.nebulous_key"
+    - add neb-locate
+    - change all Nebulous::Client public methods to behave in list context
+    - rework the getmountedvol() stored procedure to attempt to work around
+      what appears to be some nasty transacational isolation leak throught that
+      was causing getmountedvol() to randomly hang when nebdiskd changed the
+      mount table.
+    - fix a nasty logic bug in Nebulous::Client->replicate()
+    - break 1:1 relationship between key names and on disk file names
+    - overhaul Nebulous::Client->cull()
+    - add the number of instances to ->stat_object()
+    - SQL cleanup
+    - better paramter checking
+    - overhaul instance table
+    - add ->_is_valid_object_key() method
+    - change ->find_instances() to check it's params and only return instances
+      on mounted & available volumes
+    - add volume.available field and logic to use it
+    - add volume.allocate field and logic to use it
+    - add instance.vol_id field
+    - remove $class_id & $comment params from Nebulous::Server->create() and
+      Nebulous::Client->create*() and all supporting functions and tests
+    - add Nebulous::Server->{setxattr_object, listxattr_object,
+      getxattr_object, removexattr_object}()
+    - add neb-cat
+    - add neb-mv
+    - change Nebulous::Client->move() to use ->rename_object()
+    - add Nebulous::Server->rename_object()
+    - add neb-rm
+    - add neb-cp
+    - add neb-ls
+    - add Nebulous::Client->find_objects()
+    - add Nebulous::Server->find_objects()
+    - add neb-touch
+    - add neb-df
+    - change neb-addvol to properly mangle URIs into paths
+
+0.04 Mon Apr 23 13:33:16 HST 2007
+    - include autogen.sh in the tarball
+    - add nebdiskd
+    - [nebclient] VERSION to '0.0.3'
+    - change API to generally return true URIs instead of file paths
+    - add support for some what intelligent volume allocations
+    - minor code & build fixes, ws changes
+
+0.03 Mon Dec 11 14:19:45 HST 2006
+    - change Nebulous::Server instantiate objects (init -> new) instead of
+      working with class data
+    - add a DESTROY method to Nebulous::Server to tear down the database
+      connection
+    - add a Nebulous::Server->config() accessor
+    - make Nebulous::Server->db() Apache::DBI aware
+    - make SQL syntax MySQL 5 compatible
+    - update SOAP, DBI & DBD, and Apache::* deps
+    - change Nebulous::Server::SOAP to be mod_perl aware
+    - change Nebulous::Server::SOAP install stub methods to bypass AUTOLOAD on
+      successive calls to the same method
+    - make Nebulous::Server::Log mod_perl aware
+    - change Nebulous::Server::Log to accept a Nebulous::Server::Config object
+      as a parameter instead of fetching it as a singleton
+    - make Nebulous::Server::Config no longer a singleton
+
+0.02 Fri Dec  2 17:36:59 HST 2005
+    - recommend Test::Distribution 1.22
+    - add t/00_distribution.t
+    - rename test files
+    - [nebclient] VERSION to '0.0.2'
+    - Nebulous::Client, Nebulous::Server, & Nebulous::Server::SQL
+        VERSION to '0.02'
+    - add Nebulous::Client->open_create()
+    - change Nebulous::Client->create() to return just a filename
+    - [nebclient] add nebOpenCreate()
+    - [nebclient] change nebCreate() to return just a filename
+
+0.01 Fri Sep  3 10:23:31 2004
+    - first working version
+	- original version; created by ExtUtils::ModuleMaker 0.32
Index: /tags/rel-0_10/Nebulous-Server/LICENSE
===================================================================
--- /tags/rel-0_10/Nebulous-Server/LICENSE	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/LICENSE	(revision 17695)
@@ -0,0 +1,260 @@
+The General Public License (GPL)
+Version 2, June 1991
+
+Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave,
+Cambridge, MA 02139, 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
Index: /tags/rel-0_10/Nebulous-Server/MANIFEST
===================================================================
--- /tags/rel-0_10/Nebulous-Server/MANIFEST	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/MANIFEST	(revision 17695)
@@ -0,0 +1,51 @@
+Build.PL
+Changes
+LICENSE
+MANIFEST
+META.yml
+Makefile.PL
+README
+Todo
+bin/neb-addvol
+bin/neb-fsck
+bin/neb-initdb
+bin/nebdiskd
+docs/c_api.h
+docs/database_setup.txt
+docs/design.txt
+docs/requirements.txt
+docs/setup.txt
+docs/tmp.txt
+examples/Makefile
+examples/uri_test.pl
+examples/nebexample.c
+examples/nebexample.pl
+lib/Test/Nebulous.pm
+lib/Nebulous/Keys.pm
+lib/Nebulous/Server.pm
+lib/Nebulous/Server.pod
+lib/Nebulous/Server/Apache.pm
+lib/Nebulous/Server/Config.pm
+lib/Nebulous/Server/Log.pm
+lib/Nebulous/Server/SOAP.pm
+lib/Nebulous/Server/SQL.pm
+scripts/bench_test.pl
+scripts/nebulous.cgi
+scripts/ptest.pl
+t/00_distribution.t 
+t/01_load.t
+t/02_server_setup.t
+t/03_server_create_object.t
+t/04_server_replicate_object.t
+t/05_server_lock_object.t
+t/06_server_unlock_object.t
+t/07_server_find_instances.t
+t/08_server_delete_instance.t
+t/09_server_stat_object.t
+t/10_server_is_valid_volume_name.t
+t/11_server_is_valid_object_key.t
+t/12_server_find_objects.t
+t/13_server_rename_object.t
+t/14_server_xattr.t
+t/15_mounts.t
+t/75_parse_neb_key.t
Index: /tags/rel-0_10/Nebulous-Server/README
===================================================================
--- /tags/rel-0_10/Nebulous-Server/README	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/README	(revision 17695)
@@ -0,0 +1,16 @@
+pod2text Poi::PixelData.pm > README
+
+If this is still here it means the programmer was too lazy to create the readme file.
+
+You can create it now by using the command shown above from this directory.
+
+At the very least you should be able to use this set of instructions
+to install the module...
+
+perl Build.PL
+./Build
+./Build test
+./Build install
+
+
+If you are on a windows box you should use 'nmake' rather than 'make'.
Index: /tags/rel-0_10/Nebulous-Server/Todo
===================================================================
--- /tags/rel-0_10/Nebulous-Server/Todo	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/Todo	(revision 17695)
@@ -0,0 +1,37 @@
+TODO list for Perl module Nebulous
+
+- make nebclient lib thread safe
+- handle/check for storage volumes being offline
+- admin interface to control volumes (enable/disable, add/remove)
+- add zeroconf support
+- add support for SQL and/or Apache logging
+- make copy replicate source attributes, implement as replicate/reparent?
+- test that client->delete_instance removes the storage object too
+- review caching of database statement handles
+- code tiding/consistency
+- file creation retrying
+- server config file
+- make Apache::DBI behave
+- split SOAP out of Client.pm
+- Perl client error string support with DBI like 'AutoRaise' exceptions
+- CLI utilities
+- write neb-fsck utility
+- write Linux::statfs module so that statfs.f_bavail can be obtained (not available via df)
+- change neb-ls to have a limit number of keys shown (-n) param and default to
+  something reasonable (say 20)
+- add multiple regex and/or glob support to neb-ls, eg. neb-ls -r 'foo.*' -r 'bar$'
+- store the key name as an xattr on the actual on disk file
+- add an neb-locate --reverse option to map uri/paths into keys
+
+distant future
+--
+- cache some database tables (updated at some interval) 
+- volume size/% in-use discovery
+- server side per client lock tracking
+- per client authentication
+- client side lookup table of filehandles/locks
+- Perl only?, scoped lock release/filehandle destruction
+- restructure database so that storage objects are more like inodes
+- add support for directories
+- FUSE interface
+- hot-spot analysis
Index: /tags/rel-0_10/Nebulous-Server/autogen.sh
===================================================================
--- /tags/rel-0_10/Nebulous-Server/autogen.sh	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/autogen.sh	(revision 17695)
@@ -0,0 +1,1 @@
+perl Build.PL -apxs /usr/sbin/apxs2 
Index: /tags/rel-0_10/Nebulous-Server/bin/neb-addvol
===================================================================
--- /tags/rel-0_10/Nebulous-Server/bin/neb-addvol	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/bin/neb-addvol	(revision 17695)
@@ -0,0 +1,177 @@
+#!/usr/bin/env perl
+
+# Copyright (C) 2005-2008  Joshua Hoblitt
+#
+# $Id: neb-addvol,v 1.8 2008-02-05 23:14:22 eugene Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use vars qw( $VERSION );
+$VERSION = '0.01';
+
+use DBI;
+use Nebulous::Server::SQL;
+use URI::file;
+use URI;
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version );
+use Pod::Usage qw( pod2usage );
+
+my ($db, $dbuser, $dbpass, $volume, $uri);
+
+$db     = $ENV{'NEB_DB'} unless $db;
+$dbuser = $ENV{'NEB_USER'} unless $dbuser;
+$dbpass = $ENV{'NEB_PASS'} unless $dbpass;
+
+GetOptions(
+    'db=s'      => \$db,
+    'user=s'    => \$dbuser,
+    'pass=s'    => \$dbpass,
+    'volume=s'  => \$volume,
+    'uri|u=s'   => \$uri,
+) || pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+pod2usage( -msg => "Required options: --db --user --pass", -exitval => 2 )
+    unless $db && $dbuser && $dbpass && $volume && $uri;
+
+my $dbh = DBI->connect(
+    "DBI:mysql:database=$db:host=localhost",
+    $dbuser,
+    $dbpass,
+    {
+        RaiseError => 1,
+        PrintError => 0,
+        AutoCommit => 1,
+    },
+);
+
+my $sql = Nebulous::Server::SQL->new();
+
+print "Checking URI...";
+
+my $path = URI->new($uri)->path;
+unless (-d $path) {
+    die "path: $path dirived from URI: $uri does not exist";
+}
+
+print " OK\n";
+
+print "Adding volume...";
+
+my $query = $dbh->prepare( $sql->new_volume );
+$query->execute( $volume, $path );
+
+print " OK\n";
+
+__END__
+
+=pod
+
+=head1 NAME
+
+neb-addvol - add a storage volume to a Nebulous server
+
+=head1 SYNOPSIS
+
+    neb-addvol --volume <volume name> --uri <volume uri>
+        [--db <database>] [--user <username>] [--pass <password>]
+
+=head1 DESCRIPTION
+
+This program initialize a database for use by L<Nebulous::Server> by creating
+the appropriate set of tables.  Any pre-existing tables with conflicting names
+are first removed.
+
+=head1 OPTIONS
+
+=over 4
+
+=item * --volume <volume name>
+
+Symbolic name of volume being added.
+
+=item * --uri|-u <volume uri>
+
+URI to the volume being added.
+
+=item * --db|-d <database>
+
+Name of database (C<namespace>) to create tables in.
+
+Optional if the appropriate environment variable is set.
+
+=item * --user|-u <username>
+
+Username to authenticate with.
+
+Optional if the appropriate environment variable is set.
+
+=item * --pass|-p <password>
+
+Password to authenticate with.
+
+Optional if the appropriate environment variable is set.
+
+=back
+
+=head1 ENVIRONMENT
+
+These environment variables may be used in place of the specified command line
+options.  All command line option will override the corresponding environment
+value.
+
+=over 4
+
+=item * C<NEB_DB>
+
+Equivalent to --db|-d
+
+=item * C<NEB_USER>
+
+Equivalent to --user|-u
+
+=item * C<NEB_PASS>
+
+Equivalent to --pass|-p 
+
+=back
+
+=head1 CREDITS
+
+Just me, myself, and I.
+
+=head1 SUPPORT
+
+Please contact the author directly via e-mail.
+
+=head1 AUTHOR
+
+Joshua Hoblitt <jhoblitt@cpan.org>
+
+=head1 COPYRIGHT
+
+Copyright (C) 2005-2008  Joshua Hoblitt.  All rights reserved.
+
+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.
+
+The full text of the license can be found in the L<perlgpl> Pod as supplied
+with Perl 5.8.1 and later.
+
+=head1 SEE ALSO
+
+L<Nebulous::Server>, L<neb-initdb>, L<neb-df>, L<nebdiskd>
+
+=cut
Index: /tags/rel-0_10/Nebulous-Server/bin/neb-fsck
===================================================================
--- /tags/rel-0_10/Nebulous-Server/bin/neb-fsck	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/bin/neb-fsck	(revision 17695)
@@ -0,0 +1,224 @@
+#!/usr/bin/env perl
+
+# Copyright (C) 2005-2008  Joshua Hoblitt
+#
+# $Id: neb-fsck,v 1.2 2008-03-20 21:12:06 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use vars qw( $VERSION );
+$VERSION = '0.01';
+
+use DBI;
+use Nebulous::Server::SQL;
+use Nebulous::Client;
+use URI;
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version );
+use Pod::Usage qw( pod2usage );
+
+my ($db, $dbuser, $dbpass, $server);
+
+$db     = $ENV{'NEB_DB'} unless $db;
+$dbuser = $ENV{'NEB_USER'} unless $dbuser;
+$dbpass = $ENV{'NEB_PASS'} unless $dbpass;
+$server = $ENV{'NEB_SERVER'} unless $server;
+
+GetOptions(
+    'db=s'          => \$db,
+    'user=s'        => \$dbuser,
+    'pass=s'        => \$dbpass,
+    'server|s=s'    => \$server,
+) || pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+pod2usage( -msg => "Required options: --server", -exitval => 2 )
+    unless $server;
+pod2usage( -msg => "Required options: --db --user --pass", -exitval => 2 )
+    unless $db && $dbuser && $dbpass;
+
+my $neb = Nebulous::Client->new(
+    proxy => "$server",
+);
+
+die "can't connected to Nebulous Server: $server"
+    unless defined $neb;
+
+my $dbh = DBI->connect(
+    "DBI:mysql:database=$db:host=localhost",
+    $dbuser,
+    $dbpass,
+    {
+        RaiseError => 1,
+        PrintError => 0,
+        AutoCommit => 1,
+    },
+);
+
+my $sql = Nebulous::Server::SQL->new();
+
+print "Pass 1: Look for objects with inaccessable instances\n";
+
+# so_id, ext_id, instances, available_instances, need_recovery, recoverable
+my $query = $dbh->prepare( $sql->find_objects_with_unavailable_instances );
+$query->execute;
+
+my @rows;
+while (my $row = $query->fetchrow_hashref) {
+    push @rows, $row;        
+}
+$query->finish;
+
+foreach my $obj (@rows) {
+#    use Data::Dumper;
+#    print Dumper($obj);
+    my $so_id = $obj->{so_id};
+    my $key = $obj->{ext_id};
+    my $has = $obj->{instances};
+    my $available = $obj->{available_instances} || 0;
+    my $need_recovery = $obj->{need_recovery};
+    my $recoverable = $obj->{recoverable};
+    # if the copies xattr is unset and the object has 2 or more instances, we
+    # will assume a target of 2 copies
+    my $copies = $obj->{copies} || 2;
+
+    # objects with only a single instance that are offline are unrecoverable so
+    # we don't need to handle that special case
+    next unless $need_recovery;
+    unless ($recoverable) {
+        warn "so_id: $so_id key: \'$key\' ",
+            "can not be recovered - no available instances\n";
+        next;
+    }
+
+    my $need = $copies - $available;
+    $need = 0 if $need < 0;
+
+    next unless $need;
+
+    for (; $need > 0; $need--) {
+        warn "so_id: $so_id key: \'$key\' ",
+            "has $available available instances, target $copies -- replicating";
+        $neb->replicate($obj->{ext_id})
+            or warn "so_id: $so_id key: \'$key\' replication failed";
+
+        $available++;
+    }
+}
+
+print "Pass 2: Check volumes for files without an instance\n";
+
+# get a list of volumes and their paths
+# get a list of all instances on a volume
+# get a list of all files under the volumes path
+# compare the two lists
+
+__END__
+
+=pod
+
+=head1 NAME
+
+neb-fsck - check and repair Nebulous volumes
+
+=head1 SYNOPSIS
+
+    neb-fsck [--db <database>] [--user <username>] [--pass <password>]
+        [--server <server>]
+
+=head1 DESCRIPTION
+
+=head1 OPTIONS
+
+=over 4
+
+=item * --server|-s <URL>
+
+URL of the Nebulous server to connect to.
+
+Optional if the appropriate environment variable is set.
+
+=item * --db|-d <database>
+
+Name of database (C<namespace>) to create tables in.
+
+Optional if the appropriate environment variable is set.
+
+=item * --user|-u <username>
+
+Username to authenticate with.
+
+Optional if the appropriate environment variable is set.
+
+=item * --pass|-p <password>
+
+Password to authenticate with.
+
+Optional if the appropriate environment variable is set.
+
+=back
+
+=head1 ENVIRONMENT
+
+These environment variables may be used in place of the specified command line
+options.  All command line option will override the corresponding environment
+value.
+
+=over 4
+
+=item * C<NEB_SERVER>
+
+Equivalent to --server|-s
+
+=item * C<NEB_DB>
+
+Equivalent to --db|-d
+
+=item * C<NEB_USER>
+
+Equivalent to --user|-u
+
+=item * C<NEB_PASS>
+
+Equivalent to --pass|-p 
+
+=back
+
+=head1 CREDITS
+
+Just me, myself, and I.
+
+=head1 SUPPORT
+
+Please contact the author directly via e-mail.
+
+=head1 AUTHOR
+
+Joshua Hoblitt <jhoblitt@cpan.org>
+
+=head1 COPYRIGHT
+
+Copyright (C) 2005-2008  Joshua Hoblitt.  All rights reserved.
+
+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.
+
+The full text of the license can be found in the L<perlgpl> Pod as supplied
+with Perl 5.8.1 and later.
+
+=head1 SEE ALSO
+
+L<Nebulous::Server>, L<neb-initdb>, L<neb-df>, L<nebdiskd>
+
+=cut
Index: /tags/rel-0_10/Nebulous-Server/bin/neb-initdb
===================================================================
--- /tags/rel-0_10/Nebulous-Server/bin/neb-initdb	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/bin/neb-initdb	(revision 17695)
@@ -0,0 +1,162 @@
+#!/usr/bin/env perl
+
+# Copyright (C) 2005-2007  Joshua Hoblitt
+#
+# $Id: neb-initdb,v 1.7 2007-04-28 01:19:59 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use vars qw( $VERSION );
+$VERSION = '0.02';
+
+use DBI;
+use Nebulous::Server::SQL;
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version );
+use Pod::Usage qw( pod2usage );
+
+my ($db, $dbuser, $dbpass);
+
+$db     = $ENV{'NEB_DB'} unless $db;
+$dbuser = $ENV{'NEB_USER'} unless $dbuser;
+$dbpass = $ENV{'NEB_PASS'} unless $dbpass;
+
+GetOptions(
+    'db=s'      => \$db,
+    'user=s'    => \$dbuser,
+    'pass=s'    => \$dbpass,
+) || pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+pod2usage( -msg => "Required options: --db --user --pass", -exitval => 2 )
+    unless $db && $dbuser && $dbpass;
+
+my $dbh = DBI->connect(
+    "DBI:mysql:database=$db:host=localhost",
+    $dbuser,
+    $dbpass,
+    {
+        RaiseError => 1,
+        PrintError => 0,
+        AutoCommit => 1,
+    },
+);
+
+my $sql = Nebulous::Server::SQL->new();
+
+print "Dropping any existing tables...";
+
+foreach my $statement (@{ $sql->get_db_clear }) {
+    $dbh->do( $statement );
+}
+
+print " OK\nCreating new tables...";
+
+foreach my $statement (@{ $sql->get_db_schema }) {
+    $dbh->do( $statement );
+}
+
+print " OK\n";
+
+__END__
+
+=pod
+
+=head1 NAME
+
+neb-initdb - initialize a Nebulous server database
+
+=head1 SYNOPSIS
+
+    neb-initdb [--db <database>] [--user <username>] [--pass <password>]
+
+=head1 DESCRIPTION
+
+This program initialize a database for use by L<Nebulous::Server> by creating
+the appropriate set of tables.  Any pre-existing tables with conflicting names
+are first removed.
+
+=head1 OPTIONS
+
+=over 4
+
+=item * --db|-d <database>
+
+Name of database (C<namespace>) to create tables in.
+
+Optional if the appropriate environment variable is set.
+
+=item * --user|-u <username>
+
+Username to authenticate with.
+
+Optional if the appropriate environment variable is set.
+
+=item * --pass|-p <password>
+
+Password to authenticate with.
+
+Optional if the appropriate environment variable is set.
+
+=back
+
+=head1 ENVIRONMENT
+
+These environment variables may be used in place of the specified command line
+options.  All command line option will override the corresponding environment
+value.
+
+=over 4
+
+=item * C<NEB_DB>
+
+Equivalent to --db|-d
+
+=item * C<NEB_USER>
+
+Equivalent to --user|-u
+
+=item * C<NEB_PASS>
+
+Equivalent to --pass|-p 
+
+=back
+
+=head1 CREDITS
+
+Just me, myself, and I.
+
+=head1 SUPPORT
+
+Please contact the author directly via e-mail.
+
+=head1 AUTHOR
+
+Joshua Hoblitt <jhoblitt@cpan.org>
+
+=head1 COPYRIGHT
+
+Copyright (C) 2005-2007  Joshua Hoblitt.  All rights reserved.
+
+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.
+
+The full text of the license can be found in the L<perlgpl> Pod as supplied
+with Perl 5.8.1 and later.
+
+=head1 SEE ALSO
+
+L<Nebulous::Server>, L<neb-addvol>, L<neb-df>, L<nebdiskd>
+
+=cut
Index: /tags/rel-0_10/Nebulous-Server/bin/nebdiskd
===================================================================
--- /tags/rel-0_10/Nebulous-Server/bin/nebdiskd	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/bin/nebdiskd	(revision 17695)
@@ -0,0 +1,412 @@
+#!/usr/bin/env perl
+
+# Copyright (C) 2007  Joshua Hoblitt
+# 
+# $Id: nebdiskd,v 1.4 2007-05-04 23:36:46 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use vars qw( $VERSION );
+$VERSION = '0.02';
+
+use Config::YAML;
+use DBI;
+use File::Spec;
+use Nebulous::Server::SQL;
+use Net::Server::Daemonize qw( daemonize unlink_pid_file );
+use Sys::Statistics::Linux::DiskUsage;
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version );
+use Pod::Usage qw( pod2usage );
+
+my ($debug, $db, $dbuser, $dbpass, $pidfile, $stop, $restart, $verbose);
+GetOptions(
+    'debug'     => \$debug,
+    'db|d=s'    => \$db,
+    'user|u=s'  => \$dbuser,
+    'pass|p=s'  => \$dbpass,
+    'pidfile=s' => \$pidfile,
+    'stop|s'    => \$stop,
+    'restart|r' => \$restart,
+    'verbose|v' => \$verbose,
+) || pod2usage( 2 );
+
+my $rcfile = "$ENV{HOME}/.nebdiskrc";
+$rcfile = File::Spec->canonpath($rcfile);
+
+my $c = read_rcfile($rcfile);
+
+$db     ||= $c->get_db      || $ENV{'NEB_DB'};
+$dbuser ||= $c->get_dbuser  || $ENV{'NEB_USER'};
+$dbpass ||= $c->get_dbpass  || $ENV{'NEB_PASS'};
+$pidfile ||= $c->get_pidfile || "/var/tmp/nebdiskd";
+my $mounts = $c->get_mounts;
+my $poll_interval = $c->get_poll_interval || 5;
+
+if ($restart || $stop) {
+    my $status = kill_pid_file($pidfile);
+    exit($status) if $stop;
+}
+
+$c->set_db($db);
+$c->set_dbuser($dbuser);
+$c->set_dbpass($dbpass);
+$c->set_pidfile($pidfile);
+$c->set_poll_interval($poll_interval);
+$c->write;
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+pod2usage( -msg => "Required options: --db --user --pass", -exitval => 2 )
+    unless $db && $dbuser && $dbpass;
+
+daemonize(
+    $<,     # User
+    $),     # Group
+    $pidfile,  # PID file
+) unless $debug;
+
+$SIG{TERM} = sub { unlink_pid_file($pidfile); exit(); };
+$SIG{INT}  = $SIG{TERM};
+$SIG{HUP}  = sub { $c = read_rcfile($rcfile) }; 
+
+my $dbh = setup_db(
+        db      => $db,
+        dbuser  => $dbuser,
+        dbpass  => $dbpass,
+    );
+
+poll_mounts(
+        dbh             => $dbh,
+        poll_interval   => $poll_interval,
+        debug           => $debug,
+    ) or die "poll_mounts() should not have returned";
+
+sub poll_mounts
+{
+    my %p = @_;
+
+    my $dbh             = $p{dbh} or return;
+    my $poll_interval   = $p{poll_interval} || 60;
+    my $debug           = $p{debug} || 0;
+
+    my $lxs = Sys::Statistics::Linux::DiskUsage->new;
+
+    while (1) {
+        # list of mount points to stat so that the automounter will mount these
+        # volumes if they have been unmounted.
+        stat_dirs($mounts);
+
+        eval {
+            my $query = $dbh->prepare_cached("INSERT INTO mount VALUES(?, ?, ?)");
+            $dbh->do("DELETE FROM mount");
+
+            print "flushed mount table\n" if $debug;
+
+            my $stats = $lxs->get;
+            foreach my $dev (keys %$stats) {
+                my $mnt = $stats->{$dev};
+                my %mount;
+                $query->execute(@$mnt{qw( mountpoint total usage )});
+                print "adding $mnt->{mountpoint} to db\n" if $debug;
+            }
+
+            $dbh->do("call getmountedvol()");
+
+            $dbh->commit;
+        };
+        if ($@) {
+            $db->rollback;
+            warn $@;
+        }
+
+        sleep $poll_interval;
+    }
+}
+
+sub read_rcfile
+{
+    my $rcfile = shift;
+
+    return unless defined $rcfile;
+
+    if (!-f $rcfile) {
+        open(my $fh, '>', $rcfile) or die "can't open file: $!";
+        close($fh) or die "can't close file:$!";
+    }
+
+    return Config::YAML->new(
+            config => $rcfile,
+            output => $rcfile,
+    );
+}
+
+
+sub setup_db
+{
+    my %p = @_;
+
+    return unless defined $p{db}
+              and defined $p{dbuser}
+              and defined $p{dbpass};
+
+    my $sql = Nebulous::Server::SQL->new;
+
+    my $dbh = DBI->connect(
+        "DBI:mysql:database=$p{db}:host=localhost",
+        $p{dbuser},
+        $p{dbpass},
+        {
+            RaiseError => 1,
+            PrintError => 1,
+            AutoCommit => 0,
+        },
+    );
+
+    eval {
+        $dbh->do( $sql->set_transaction_model );
+        $dbh->commit;
+    };
+    if ($@) { 
+        $db->rollback;
+        die $@;
+    }
+
+    return $dbh;
+}
+
+
+sub stat_dirs
+{
+    my $mounts = shift;
+
+    return unless defined $mounts;
+
+    foreach my $path (@$mounts) {
+        stat File::Spec->canonpath($path) or warn "can not stat path: $path";
+        print "stated $path\n" if $debug;
+    }
+}
+
+#
+# This function, originally named check_pid_file() was copied from
+# Net::Server::Daemonize '0.05' licensed under the Perl license.  It has been
+# renamed and modified to kill off process who's PID value is stored in pidfile.
+#
+
+sub kill_pid_file {
+  my $pid_file = shift;
+
+  ### no pid_file = return success
+  return 1 unless -e $pid_file;
+
+  ### get the currently listed pid
+  if( ! open(_PID,$pid_file) ){
+    die "Couldn't open existant pid_file \"$pid_file\" [$!]\n";
+  }
+  my $_current_pid = <_PID>;
+  close _PID;
+  my $current_pid = $_current_pid =~ /^(\d{1,10})/ ? $1 : die "Couldn't find pid in existing pid_file";
+
+  my $exists = undef;
+
+  ### try a proc file system
+  if( -d '/proc' ) {
+    $exists = -e "/proc/$current_pid";
+
+  ### try ps
+  #}elsif( -x '/bin/ps' ){ # not as portable
+  # the ps command itself really isn't portable
+  # this follows BSD syntax ps (BSD's and linux)
+  # this will fail on Unix98 syntax ps (Solaris, etc)
+  }elsif( `ps h o pid p $$` =~ /^\s*$$\s*$/ ){ # can I play ps on myself ?
+    $exists = `ps h o pid p $current_pid`;
+
+  }
+
+  ### running process exists, ouch
+    if( $exists ){
+
+        if( $current_pid == $$ ){
+            warn "Pid_file created by this same process. Doing nothing.\n";
+            return 1;
+        }else{
+            kill 'TERM', $current_pid
+                or die "Failed to signal process ($current_pid)";
+            unlink $pid_file || die "Couldn't remove pid_file \"$pid_file\" [$!]\n";
+            return 1;
+
+        }
+    }
+}
+
+__END__
+
+=pod
+
+=head1 NAME
+
+nebdiskd - Nebulous mounted volume capacity monitoring daemon
+
+=head1 SYNOPSIS
+
+    nebdiskd [--db <db name>] [--user <db username>] [--pass <db password>] [--debug] [--pidfile <filename>] [--stop] [--restart] [--verbose]
+
+=head1 DESCRIPTION
+
+This program looks for mounted volumes, at a configurable interval, and records
+statistics about them into a database.
+
+=head1 OPTIONS
+
+=over 4
+
+=item * --db|-d <db name>
+
+Name of database (C<namespace>) to write too.
+
+Optional if defined in the F<.nebdiskdrc> file or the appropriate environment
+variable is set.
+
+=item * --user|-u <db username>
+
+Username to authenticate with.
+
+Optional if defined in the F<.nebdiskdrc> file or the appropriate environment
+variable is set.
+
+=item * --pass|-p <db password>
+
+Password to authenticate with.
+
+Optional if defined in the F<.nebdiskdrc> file or the appropriate environment
+variable is set.
+
+=item * --debug
+
+This flag prevents the program from "daemonizing" so output can be sent to
+C<stdout>/C<stderr> and a debugger used on the running process.
+
+=item * --pidfile <filename>
+
+Filename to log the running daemon's PID too.
+
+Optional, if this option is omitted and no value is defined in the
+F<.nebdiskrc> then no pidfile is created.
+
+=item * --stop|-s
+
+Uses the pidfile to kill an already running daemon.
+
+=item * --restart|-r
+
+Uses the pidfile to kill an already running daemon and then starts a new
+instance.
+
+=item * --verbose|-r
+
+Turns on informational/debugging messages to be sent to the
+C<stderr>/C<stdout>.  This option is probably not very usefully unless combined
+with C<--debug>.
+
+=back
+
+=head1 ENVIRONMENT
+
+These environment variables may be used in place of the specified command line
+options.  All command line option will override the corresponding environment
+value.
+
+=over 4
+
+=item * C<NEB_DB>
+
+Equivalent to --db|-d
+
+=item * C<NEB_USER>
+
+Equivalent to --user|-u
+
+=item * C<NEB_PASS>
+
+Equivalent to --pass|-p 
+
+=back
+
+=head1 RCFILE
+
+This program will attempt to read an C<rcfile> from F<$HOME/.nebdiskrc> and if
+found will use it's values to override program defaults.  Please not that the
+precedence for values is: command line, rcfile, environment variables.
+
+The format of the C<rcfile> is in L<YAML> format and uses the following values:
+
+    ---
+    db: nebulous
+    dbpass: '@neb@'
+    dbuser: nebulous
+    mounts:
+      - /mnt
+      - /tmp
+      - /usr
+    pidfile: /var/tmp/nebdiskd
+    poll_interval: 5
+
+The values C<db>, C<dbpass>, C<dbuser>, and C<pidfile> have the same semantics
+as their command line and/or environment variable equivalents.
+
+=over 4
+
+=item * C<mounts>
+
+A list of "paths" to C<stat(2)> before mounted volumes are polled.  The propose
+of this option is to attempt to keep "automounted" volumes mounted while this
+program is running.
+
+This value may be omitted or left blank.
+
+=item * C<poll_interval>
+
+The number of seconds to wait between checks for mounted volumes.
+
+This value may be omitted or left blank.  The default value is C<60>s.
+
+=back
+
+=head1 CREDITS
+
+Just me, myself, and I.
+
+=head1 SUPPORT
+
+Please contact the author directly via e-mail.
+
+=head1 AUTHOR
+
+Joshua Hoblitt <jhoblitt@cpan.org>
+
+=head1 COPYRIGHT
+
+Copyright (C) 2007  Joshua Hoblitt.  All rights reserved.
+
+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.
+
+The full text of the license can be found in the L<perlgpl> Pod as supplied
+with Perl 5.8.1 and later.
+
+=head1 SEE ALSO
+
+L<Nebulous::Server>, L<YAML>, L<neb-addvol>, L<neb-df>, L<neb-initdb>
+
+=cut
Index: /tags/rel-0_10/Nebulous-Server/docs/c_api.h
===================================================================
--- /tags/rel-0_10/Nebulous-Server/docs/c_api.h	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/docs/c_api.h	(revision 17695)
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2004 Joshua Hoblitt
+ *
+ * $Id: c_api.h,v 1.1.1.1 2004-12-22 02:16:23 jhoblitt Exp $
+ */
+
+// should there be an argument for "suggested" storage location?
+int add( const char *ext_id, const char *data, int size )
+
+int delete( const char *ext_id )
+
+// should there be an argument for "suggested" storage location?
+int update( const char *ext_id, const char *data, int size )
+
+// should there be an argument for "suggested" storage location?
+int move( const char *old_ext_id, const char *new_ext_id )
+
+// open returns a fd 
+int open( const char *ext_id )
+
+int close( const char *ext_id )
+
+// maybe open and close are a bad idea, perhaps...
+int read( const char *ext_id, char *data )
Index: /tags/rel-0_10/Nebulous-Server/docs/database_setup.txt
===================================================================
--- /tags/rel-0_10/Nebulous-Server/docs/database_setup.txt	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/docs/database_setup.txt	(revision 17695)
@@ -0,0 +1,59 @@
+# Copyright (C) 2004  Joshua Hoblitt
+#
+# $Id: database_setup.txt,v 1.7 2005-11-09 00:49:44 jhoblitt Exp $
+
+### Database Setup
+
+### MySQL 4.1
+
+DROP DATABASE IF EXISTS nebulous;
+CREATE DATABASE nebulous;
+GRANT ALL PRIVILEGES ON *.* TO 'nebulous'@'localhost'
+    IDENTIFIED BY 'pixel' WITH GRANT OPTION;
+FLUSH PRIVILEGES;
+USE nebulous;
+
+### Initialization
+
+# localhost
+
+$ mkdir /tmp/po01 /tmp/po02
+INSERT INTO volume VALUES (1,'po01','file:/tmp/po01');
+INSERT INTO volume VALUES (1,'po02','file:/tmp/po02');
+
+# NFS
+
+INSERT INTO volume VALUES (1,'po01','file:/netdisks/po01');
+INSERT INTO volume VALUES (2,'po02','file:/netdisks/po02');
+INSERT INTO volume VALUES (3,'po03','file:/netdisks/po03');
+INSERT INTO volume VALUES (4,'po04','file:/netdisks/po04');
+INSERT INTO volume VALUES (5,'po05','file:/netdisks/po05');
+INSERT INTO volume VALUES (6,'po06','file:/netdisks/po06');
+INSERT INTO volume VALUES (7,'po07','file:/netdisks/po07');
+INSERT INTO volume VALUES (8,'po08','file:/netdisks/po08');
+
+INSERT INTO class VALUES (1,3,0,'raw image data');
+
+### Example queries
+
+LOAD DATA INFILE '/home/moa/jhoblitt/tmp/storage_objects.txt' INTO TABLE storage_object; 
+LOAD DATA INFILE '/home/moa/jhoblitt/tmp/instances.txt' INTO TABLE instance; 
+
+SELECT * FROM storage_object LEFT JOIN instance ON storage_object.so_id=instance.so_id WHERE storage_object.ext_id = "foobarbaz1.fits";
+SELECT * FROM storage_object LEFT JOIN instance USING (so_id) WHERE storage_object.ext_id = "foobarbaz1.fits";
+
+SET AUTOCOMMIT = 0;
+BEGIN;
+UPDATE instance SET read_lock=read_lock+1 WHERE ins_id=1;
+INSERT INTO lock_record values (1, 'read', NULL);
+COMMIT;
+
+SET AUTOCOMMIT = 0;
+BEGIN;
+INSERT INTO storage_object values (NULL, 'myfooedbar.fits', 'mr. wizard made this', NULL);
+INSERT INTO instance values (NULL, LAST_INSERT_ID(), 'file://tmp/bat/man/12345a', 'da39a3ee5e6b4b0d3255bfef95601890afd80709',0, NULL, NULL);
+COMMIT;
+
+IF select write_lock from instance WHERE id=1 THEN
+ELSE
+ENDIF
Index: /tags/rel-0_10/Nebulous-Server/docs/design.txt
===================================================================
--- /tags/rel-0_10/Nebulous-Server/docs/design.txt	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/docs/design.txt	(revision 17695)
@@ -0,0 +1,283 @@
+# Copyright (C) 2004-2005  Joshua Hoblitt
+#
+# $Id: design.txt,v 1.6 2005-11-05 01:13:35 jhoblitt Exp $
+
+Abstract
+--
+
+Nebulous is a user-space distributed object (file) storage system.  It is much
+more of a database of objects locations then a file system in the traditional
+sense.  Nebulous is considered distributed because of it's data storage model,
+where objects are stored across a number of storage volumes and may have one
+or more redundant copies.  While IPC is done in the traditional client/server
+model with a single[1] centralized server containing all storage object
+meta-data.  This system was designed, unlike most distributed storage system,
+specifically so that clients have the option of hosting one or more storage
+volumes and that data may be stored and accessed locally on a node.  This
+allows network I/O to be dramatically reduced if the end user does simple data
+locality optimizations.  Although, if you so choose, clients and storage
+volumes can reside on independent hardware.  Above all else, Nebulous is
+designed to make efficient use of both disk and network bandwidth.
+
+Data model
+--
+
+In Nebulous terminology a file is refereed to as a a resource.  Each resource
+is represented inside the Nebulous Server as a "Storage Object".  There is one
+Storage Object per resource (a file) that is being managed by the Nebulous
+Server.  A Storage Object maps an External Identifier (eg. filename) to a
+Storage ID.  In addition, creation time and comments may be stored in a
+Storage Object.  Storage Objects /do not/ contain the storage location of a
+resource as we need to allow for N copies of a resource to be stored.  The
+actual storage location of a resource is held in a Storage Object Instance,
+which maps to a Storage ID in a many-to-one relationship.
+
+A Storage Object Instance, or just Instance, represents a single copy of a
+resource being managed by the Nebulous Server. An Instance contains an
+Instance ID, the parent Storage ID, the location of the resource as a URI, and
+other assorted meta-data.
+
+Storage IDs are a signed 64-bit integer.  Negative values are considered
+reserved for future use.  Allocation should be incremental and, as we are
+unlikely to exceed 2^63 Storage Objects in the life time of the project, never
+reused. Instance IDs follow the same scheme as Storage IDs but exists in a
+unique namespace.
+
+Database record relationships
+--
+* All Storage Objects must have at least once associated Instance.
+
+* All Instances must be associated with a Storage Object.
+
+* All Instances must be associated with a valid URI (file exists).
+
+* All Storage Objects with locks set must have the correct number of
+corresponding Lock Records.
+
+* All Lock Records must be associated with a Storage Object.
+
+Data transport
+--
+XXX
+
+Data Storage
+--
+XXX
+
+
+Components
+--
+
+There are 4 principle components required for a working Nebulous system;
+server, client, data transport, and data storage.  The Nebulous software
+distribution provides only the server and the client software.  Data
+transport[2] and Data storage are left to 3rd party software packages.  As
+those functionalities are typically included with most operating systems.
+Nebulous build upon and benefits from advances made in those technologies.
+
+Server
+--
+
+The server is responsible for keeping track of storage objects, all instances
+of that object, and enforcing locking semantics.  Extensive logging and
+tracing support is provided for debug and to allow for statics generation and
+possible X<hotspot> optimization.
+
+Nebulous uses a centralized server model.  This model was choosen because it
+allows efficient X<pattern matching> of storage object names.  The current
+'best' technique for a distributed metadata store is with distributed hash
+tables.  Unfortunately, no widely available DHT implementation allows efficient
+I<pattern matching> of key names.
+
+
+[1] Multiple Nebulous servers should be possible via database replication.
+[2] A future version of Nebulous may include it's own data transport layer
+based on the WEB DAV protocol.
+
+Client <-> Server IPC
+--
+
+The Nebulous Server and clients communicate via the SOAP protocol.  Since SOAP
+is platform independent this allows native Nebulous clients to be written in a
+variety of languages.  With in the Nebulous Server itself the IPC mechanism is
+abstracted to allow ether multi-protocol support or complete replacement of
+SOAP by another protocol.
+
+XXX SOAP namespace
+
+
+Storage volume state tracking.
+
+Nebulous itself does not attempt to determine the state of storage volumes.
+Instead, it relies on some 3rd party to determine both the state
+(available/not) and capacity (total size + % utilized).  This information is
+then feed into Nebulous via either the administrative API or CLI management
+tools.
+
+XXX Storage Location determination
+
+
+Locking semantics
+--
+Nebulous uses 'advisory locking' where clients are excepted to check for the
+existence of locks but this policy is not enforced.  Locks are set on a
+Storage Object and apply to all Instances of that object.
+
+Two types of locks can be set; 'read' locks and 'write' locks.  Read locks are
+implemented as a semaphore while write locks use an exclusive mutex.
+
+    - Read lock
+In order to obtain a Read Lock on a Storage Object the Write Lock mutex must
+be in the state of 'free'.  When a Read Lock is acquire the Read semaphore is
+incremented by one.  When a Read Lock is released the Read semaphore is
+decremented by one.  This must happen atomically with the
+creation/destruction of a Lock Record.
+
+    - Write lock
+In order to obtain a Write lock on a Storage Object the Write Lock mutex must
+be in the state of 'free' and the Read lock semaphore must have a value of
+zero.  When a Write Lock is acquired the Write mutex is set to 'write'.  When a
+Write Lock is released the Write mutex is set to to 'free'.  This must happen
+atomically with the creation/destruction of a Lock Record.  As an extension, a
+sate of 'wait' may be implemented such that clients waiting on Read Locks to
+be released may prevent any additional Read Locks from being acquired.
+
+XXX Lock Records are currently unimplemented
+    - Lock Records A Lock Record is used to double check the indicated lock
+      state of a Storage Object and can be used to identify logical system
+errors.  A Lock Record contains the Storage ID of the object and the type of
+lock set.  eg, enum( 'read', 'write').
+
+    - Client best practices
+When attempting to acquire a lock a client should either "spin lock" with a
+reasonable pause between lock attempts or fail completely.  Aggressive "spin
+locking" is considered antisocial and may eventually require the Nebulous
+Server to identify and ignore such clients.
+
+
+House keeping
+--
+    - Lock sweeping
+In the event that a Storage Object operation fails to complete successfully
+stale locks will have to be identified and removed from the IPP Pixel Data
+Server Database.  This should be done periodically by comparing the entries
+in the Lock table to the list of active nodes maintained by the IPP
+Controller.  It should also happen as soon as possible after a node goes
+offline (triggered by the IPP Controller marking a node as offline?).  A sweep
+must be /completed/ before an offline node can be marked on-line.
+
+Once a node is determined to be offline all entries in the Lock table set by
+that node should be identified.   The locks on the Storage Object
+Instances pointed to by those entries should then be rolled back and Lock
+Record entries themselves must be removed from the lock table.
+
+    - Consistency sweeping
+Periodically the IPP Pixel Data Server meta-data and Storage Object will need
+to be checked for sanity.  This would be similar to running fsck on a modern
+filesystem.  Consistency sweeping should include Lock sweeping and should be
+considered a super-set.
+
+
+Server Operations
+--
+    - setup
+    - create_object
+    - rename_object
+        XXX unimplemented
+    - link_object
+        XXX unimplemented
+    - replicate object
+    - lock_object
+    - unlock_object
+    - find_objects
+        XXX unimplemented
+    - find_instances
+    - delete_instance
+    - fission_object
+        XXX unimplemented
+    - fuse_objects
+        XXX unimplemented
+    - stat_object
+    - stat_instance
+        XXX unimplemented
+
+Client Operations
+--
+    - create
+Creates and opens new Storage Object
+
+    - replicate
+Adds an Instance to a Storage Object
+
+    - cull
+Removes an Instance from a Storage Object
+
+This operation can not remove the last Instance of a Storage Object.
+
+    - lock
+Trys to acquire a lock on a storage object
+
+This operation times out after a default interval of 10s.
+
+    - unlock
+Trys to release a lock on a storage object
+    
+    - find_instances
+Find all instances of a storage object
+
+Only Instances on active storage volumes are found.
+
+    - find
+Find any instance of a storage object
+
+Only Instances on active storage volumes are found.
+
+    - open
+Open a storage object for read or write
+
+Opening a storage object as 'write' will remove all but one Instance to
+prevent Instances from becoming inconsistent.
+
+    - delete
+Delete a storage object and all of it's instances
+
+Removes a storage object and all of it's instances by sequentially deleting each
+Instance.  When a Storage Object has no more associated instances it is
+automatically removed by the Nebulous server.
+
+    - copy
+Copy a storage object
+
+The new storage object will be created with only one Instance.  Most properties
+of the source Storage Object will be preserved.
+
+    - move
+Rename a storage object
+
+Currently this operation will remove all but one instance of the storage
+object and may change it's storage location.
+
+    - delete_instance
+    
+Remove a storage object instance
+
+Removing the last Instance of a Storage Object will destroy the Storage
+Object.
+
+    - stat
+View the properties of a storage object
+
+
+    - import
+        XXX unimplemented
+Import a file into Nebulous
+
+Creates a new Storage Object with the specified External Identifier.  The
+source file will then be "copied" into the Nebulous system leaving the original
+file unmodified.
+
+    - fission
+        XXX unimplemented
+Create a new Storage Object from an existing one by either splitting off an
+Instance and re-parenting it or by cloning an existing Instance and associating
+it with the new Storage Object.
Index: /tags/rel-0_10/Nebulous-Server/docs/requirements.txt
===================================================================
--- /tags/rel-0_10/Nebulous-Server/docs/requirements.txt	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/docs/requirements.txt	(revision 17695)
@@ -0,0 +1,28 @@
+# Copyright (C) 2004  Joshua Hoblitt
+#
+# $Id: requirements.txt,v 1.2 2005-09-14 00:46:01 jhoblitt Exp $
+
+- store all image data aquired during the first two years of PS1's
+  operations.  Approximately 10^8 files (OTA level).
+
+- injest new data from the summit at the same rate it is produced when
+  operating at a cadence of 40s between integrations.  Approximately 400Mb/s
+  and 64 files/integration.
+
+- support phase 1 & 2 processing. x files per t_min
+
+- support phase 3 & 4 processing. x files per t_min
+
+- support accessing small blocks of a file (ie., seek)
+
+- tolerate partal system failures without data loss
+
+- execeedly efficent use of both disk and network I/O
+
+what are the science drivers for these 3?
+--
+- data corruption is guarenteed to be detectable.
+
+- relibably store xx.x% of files 
+
+- xx.x% of stored images must be retreivable in xs.
Index: /tags/rel-0_10/Nebulous-Server/docs/setup.txt
===================================================================
--- /tags/rel-0_10/Nebulous-Server/docs/setup.txt	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/docs/setup.txt	(revision 17695)
@@ -0,0 +1,369 @@
+=pod
+
+=head1 Installing a Nebulous Server
+
+=head2 Create a UNIX group for Nebulous
+
+All Nebulous user's need to be in the same UNIX group as the Nebulous server.
+
+    groupadd -g 877 nebulous
+
+This is a silly hack to generate a gid.
+
+    perl -le 'map { $gid += ord } split //, shift; print $gid' nebulous
+
+=head2 Add the users that you want to be able to access Nebulous to that group
+
+    usermod -G nebulous foouser
+
+=head2 Setup directory permissions for the nebulous group
+
+As root:
+
+    mkdir /data/ipp000.0/nebulous
+    mkdir /data/ipp000.1/nebulous
+    mkdir /data/ipp002.0/nebulous
+    mkdir /data/ipp003.0/nebulous
+
+    # note that this is just an example, this won't work over NFS with
+    # root_squash enablded
+    chown nobody:nebulous /data/ipp00?.?/nebulous
+
+    chmod 0770 /data/ipp00?.?/nebulous
+    ls -lad /data/ipp00?.?/nebulous
+
+=head2 Build and install the Nebulous Perl modules (see: README)
+
+=head2 Create and initialize the database
+
+    mysql -u root mysql -p
+    (enter your MySQL root password)
+
+    DROP DATABASE IF EXISTS nebulous;
+    CREATE DATABASE nebulous;
+    GRANT ALL PRIVILEGES ON nebulous.* TO 'nebulous'@'localhost'
+        IDENTIFIED BY '@neb@';
+    FLUSH PRIVILEGES;
+    exit
+
+    export NEB_DB=nebulous
+    export NEB_USER=nebulous
+    export NEB_PASS=@neb@
+
+    neb-initdb
+    neb-addvol --name ipp000.0 --uri file:///data/ipp000.0/nebulous
+    neb-addvol --name ipp000.1 --uri file:///data/ipp000.1/nebulous
+    neb-addvol --name ipp002.0 --uri file:///data/ipp002.0/nebulous
+    neb-addvol --name ipp003.0 --uri file:///data/ipp003.0/nebulous
+
+#    perl -e 'for (1..24) { $i = sprintf "po%02d", $_ ; system "neb-addvol --name $i --uri file:/$i/nebulous" }'
+    .
+    .
+
+If you get an error similar to this while initializing the database it means
+that the creation of the Nebulous's stored procedures failed:
+
+    ERROR 1146 (42S02): Table 'mysql.proc' doesn't exist
+
+It probably means that your instance of MySQL has been upgraded for an older
+version that did not support stored precedures.  You can fix this by running
+the C<mysql_fix_privilege_tables> script that you should have received with
+MySQL.
+
+In order for the test suite to pass the "test" account needs have the
+appropriate permissions to create and modify stored procedures.
+
+    update user set Create_routine_priv = 'Y', Alter_routine_priv = 'Y', Execute_priv = 'Y' where user = 'test';
+    update db set Create_routine_priv = 'Y', Alter_routine_priv = 'Y', Execute_priv = 'Y' where db = 'test';
+
+    flush privileges;
+
+Please see this webpage for further details:
+
+http://dev.mysql.com/doc/refman/5.0/en/stored-procedure-privileges.html
+
+
+=head2 Setup nebdiskd daemon
+
+Create the .netdiskrc file in the home directory of the user that nebdiskd
+will run as.  This does not need to be/should not be the root user.  Merely a
+user with permissions to see all of the approparite volumes.
+
+Example F<.netdiskrc>:
+
+    db: nebulous
+    dbpass: '@neb@'
+    dbuser: nebulous
+    mounts:
+      - /data/ipp000.0
+      - /data/ipp000.1
+      - /data/ipp002.0
+      - /data/ipp003.0
+    pidfile: /var/tmp/nebdiskd
+    poll_interval: 5
+
+Start the daemon by running the command C<nebdiskd>.  Use this command to
+verify that the daemon is working correclty.
+
+    neb-volstat
+
+nebdiskd should always been running when nebulous is use.  This command is an
+example of how to startup the daemon running as another user.  This could be
+use to start the daemon from the root account while the system init scripts
+are running.
+
+    sudo -H -u jhoblitt nebdiskd
+
+See the nebdiskd POD/man page for further details
+
+=head2 Install Apache2 with mod_perl
+
+=head3 Minimum software requirements:
+
+Nebulous will work with both Apache 1.3 & 2.x.  However, this guide will only
+cover configuraton with Apache 2.x and mod_perl 2.x.
+
+=over 4
+
+=item * Apache2 >= 2.0.54
+
+=item * mod_perl >= 2.0.0
+
+=item * Apache::DBI >= '0.97' [*]
+
+=back
+
+[*] required for connect_on_init() to work under mod_perl >= 2.0.1
+
+=head3 Apache 1.x 
+
+=item * building mod_perl-1.29 from source:
+
+    perl Makefile.PL USE_APXS=1 INSTALLDIRS=vendor WITH_APXS=/usr/sbin/apxs EVERYTHING=1 PERL_DEBUG=1
+
+    #MP_TRACE = 1 MP_DEBUG = 1 MP_USE_DSO = 1 MP_INST_APACHE2 = 1 MP_APXS = /usr/sbin/apxs2
+
+=head3 Apache 2.x 
+
+=head4 removing Apache on RHEl3
+
+On RHEL3 I had to remove these packages in order to fully remove MySQL.
+
+As C<root>:
+
+    rpm -e \
+    httpd-2.0.46-46.2.ent \
+    httpd-devel-2.0.46-46.2.ent \
+    redhat-config-httpd-1.1.0-4.30.2 \
+    mod_perl-1.99_09-10.ent \
+    mod_authz_ldap-0.22-5 \
+    mod_auth_pgsql-2.0.1-4.ent \
+    mod_python-3.0.3-5.ent \
+    mod_auth_mysql-20030510-2.ent \
+    mod_ssl-2.0.46-46.2.ent \
+    webalizer-2.01_10-15.ent \
+    mod_perl-1.99_09-10.ent \
+    mod_auth_pgsql-2.0.1-4.ent \
+    mod_python-3.0.3-5.ent \
+    mod_auth_mysql-20030510-2.ent \
+    mod_ssl-2.0.46-46.2.ent \
+    squirrelmail-1.4.3a-11.EL3 \
+    php-4.3.2-25.ent \
+    php-imap-4.3.2-25.ent \
+    php-ldap-4.3.2-25.ent \
+    php-mysql-4.3.2-25.ent \
+    php-odbc-4.3.2-25.ent \
+    php-pgsql-4.3.2-25.ent 
+
+    rm -rf /etc/httpd
+
+=head4 installing Apache2 from source
+
+    tar -jxvf httpd-2.0.54.tar.bz2
+    cd httpd-2.0.54
+    ./configure \
+    --disable-suexec \
+    --with-perl=/usr/bin/perl \
+    --with-port=80 \
+    --with-program-name=apache2 \
+    --with-devrandom=/dev/urandom \
+    --prefix           /usr \
+    --exec_prefix      /usr \
+    --bindir           /usr/bin \
+    --sbindir          /usr/sbin \
+    --libdir           /usr/lib \
+    --libexecdir       /usr/lib/apache2/modules \
+    --mandir           /usr/share/man \
+    --infodir          /usr/share/info \
+    --includedir       /usr/include/apache2 \
+    --datadir          /var/www/localhost \
+    --sysconfdir       /etc/apache2/conf \
+    --localstatedir    /var
+    make
+    su
+    make install
+    exit
+    cd ..
+
+=head4 installing mod_perl 2.x
+
+    tar -zxvf mod_perl-2.0.1.tar.gz
+    cd mod_perl-2.0.1
+    perl Makefile.PL
+    (apxs = /usr/sbin/apxs)
+    make
+    su
+    make install
+    echo "LoadModule perl_module /usr/lib/apache2/modules/mod_perl.so" >> /etc/apache2/conf/apache2.conf
+    exit
+
+    apachectl configtest
+    Syntax OK
+
+=head4 installing and configuring Nebulous
+
+    tar -zvxf Nebulous-0.01.tar.gz
+    cd Nebulous-0.01
+    perl Build.PL -axps /usr/sbin/apxs
+    ./Build
+    ./Build test
+    su
+    ./Build install
+
+
+    vi /etc/apache2/conf/startup.pl
+    (change Group to "Group nebulous")
+
+    cp fooconf /etc/apache2/conf/startup.pl
+    echo "PerlPostConfigRequire /etc/apache2/conf/startup.pl" >> /etc/apache2/conf/apache2.conf
+
+
+=head3 configurating Apache to act as a Nebulous/SOAP server
+
+=item * add the "nebulous" group to /etc/group
+=item * configure Apache to run as the nebulous group. 
+
+    Group nebulous
+
+Note that in theory the C<perchild> MPM can run vhosts as differenet
+users/groups but this module is currently non-funcitonal.
+
+L<http://httpd.apache.org/docs/trunk/mod/perchild.html>
+
+=item * configure a path for Neublous
+
+Make sure mod_perl is being loaded into Apache.  In order to do this on
+gentoo, you must edit /etc/config.d/apache2 and add C<-DPERL> to
+C<APACHE2_OTPS>.
+
+    cat <<END >> /etc/apache2/conf/apache2.conf
+    <Location /nebulous>
+        SetHandler perl-script
+        PerlResponseHandler Apache2::SOAP
+        PerlSetVar dispatch_to "PerlHandler Nebulous::Server::SOAP"
+        PerlSetVar options "compress_threshold => 10000"
+    </Location>
+    END
+
+    apachectl configtest
+    Syntax OK
+
+=item * initialize Nebulous database connections from C<startup.pl>
+
+    my $dsn         = 'DBI:mysql:database=nebulous:host=localhost';
+    my $dbuser      = 'nebulous';
+    my $dbpasswd    = '@neb@';
+
+    Apache::DBI->connect_on_init( $dsn, $dbuser, $dbpasswd );
+    Nebulous::Server::SOAP->new_on_init(
+        dsn         => $dsn,
+        dbuser      => $dbuser,
+        dbpasswd    => $dbpasswd,
+        log_level   => 'all',
+    );
+
+# out of date from here down...
+
+#
+# CGI interface
+#
+
+ScriptAlias /nebulous /usr/local/apache/perl/imageserver.pl
+
+<Directory "/usr/local/apache/perl">
+        AllowOverride None
+        Options None
+        Order allow,deny
+        Allow from all
+</Directory>
+
+PerlModule Apache::PerlRun
+<Location /perl>
+        SetHandler perl-script
+        PerlHandler Apache::PerlRun
+        Options ExecCGI
+        allow from all
+        PerlSendHeader On
+</Location>
+
+#
+# Nebulous::Apache interface
+#
+
+PerlModule Apache::DBI
+PerlModule Nebulous::Apache
+
+<Location /nebulous>
+        SetHandler perl-script
+        PerlHandler Nebulous::Apache
+</Location>
+
+- setup the server
+
+As a CGI, will with either Apache 1.3.x or 2.0.x
+
+cp scripts/imagesever.pl /var/www/localhost/cgi-bin
+chmod 755 /var/www/localhost/cgi-bin/imageserver.pl
+touch /tmp/imageserver.log
+chmod 1777 /tmp/imageserver.log
+
+=head2 Testing the Nebulous Server
+
+You can test your configuration with the C<telnet> utility.
+
+    $ telnet localhost 80
+    Trying 127.0.0.1...
+    Connected to localhost.localdomain (127.0.0.1).
+    Escape character is '^]'.
+    GET /nebulous HTTP/1.0
+
+    HTTP/1.1 500 Internal Server Error
+    Date: Wed, 31 Aug 2005 01:10:47 GMT
+    Server: Apache/2.0.54 (Unix) mod_perl/2.0.1 Perl/v5.8.6
+    Content-Length: 645
+    Connection: close
+    Content-Type: text/html; charset=iso-8859-1
+    .
+    .
+
+    Connection closed by foreign host.
+
+If you get an HTTP 5xx code check your apache error_log
+
+
+=head2 MySQL Configuration/tuning
+
+in /etc/mysql/my.cnf
+under [mysqld]
+increase the number of alloweed connections, e.g.
+max_connections = 200
+
+Nebulous makes heavy use of MySQL's C<innodb> table type.  Try configuring
+MySQL with these parameters:
+
+    innodb_log_files_in_group = 2
+    innodb_buffer_pool_size = as big as possible (512M+)
+    innodb_log_buffer_size= 16M
+    innodb_log_file_size=10M
+
+=cut
Index: /tags/rel-0_10/Nebulous-Server/docs/tmp.txt
===================================================================
--- /tags/rel-0_10/Nebulous-Server/docs/tmp.txt	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/docs/tmp.txt	(revision 17695)
@@ -0,0 +1,44 @@
+client
+--
+new
+
+new_file
+                new_file
+                open_file
+
+find_file
+    find_instance
+
+open_file
+    find_file
+
+delete_file
+                delete_file
+    nuke
+
+copy_file
+    open_file
+    new_file
+
+move_file
+    copy_file
+    delete_file
+
+replicate_file
+    open_file
+                replicate_file
+
+cull_file
+    find_instance
+    delete_instance
+
+delete_instance
+    nuke
+                delete_instance
+
+find_instance
+                find_instance
+
+# missing
+file_stat
+lock ?
Index: /tags/rel-0_10/Nebulous-Server/examples/Makefile
===================================================================
--- /tags/rel-0_10/Nebulous-Server/examples/Makefile	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/examples/Makefile	(revision 17695)
@@ -0,0 +1,5 @@
+NEB_CFLAGS=`pkg-config nebclient --cflags`
+NEB_LIBS=`pkg-config nebclient --libs`
+
+nebexample: nebexample.c
+	$(CC) $(NEB_CFLAGS) $(CFLAGS) $(NEB_LIBS) $< -o $@
Index: /tags/rel-0_10/Nebulous-Server/examples/nebexample.c
===================================================================
--- /tags/rel-0_10/Nebulous-Server/examples/nebexample.c	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/examples/nebexample.c	(revision 17695)
@@ -0,0 +1,45 @@
+// gcc `pkg-config nebclient --cflags --libs` example.c -o example
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <nebclient.h>
+
+#define NEB_SERVER  "http://podb1.ifa.hawaii.edu:80/nebulous"
+#define NEB_KEY     "foobarbaz"
+
+int main (int argc, char **argv) {
+    nebServer       *server = NULL;
+    char            *key = NEB_KEY;
+
+    server = nebServerAlloc(NEB_SERVER);
+    if (!server) {
+        printf("nebServerAlloc() failed\n");
+        exit(EXIT_FAILURE);
+    }
+
+    // make sure there isn't already a file named "foobarbaz" so this example
+    // doesn't cause an error
+    nebDelete(server, key);
+
+    int fh = nebCreate(server, key, 0, NULL, NULL, NULL);
+    if (fh < 0) {
+        printf( "nebCreate() failed: %s\n", nebErr(server));
+        exit(EXIT_FAILURE);
+    }
+
+    // fh is a file descriptor
+    close(fh);
+
+    if (!nebReplicate(server, key, NULL, NULL)) {
+        printf( "nebReplicate() failed: %s\n", nebErr(server));
+        exit(EXIT_FAILURE);
+    }
+
+    if (!nebDelete(server, key)) {
+        printf( "nebDelete() failed: %s\n", nebErr(server));
+        exit(EXIT_FAILURE);
+    }
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/rel-0_10/Nebulous-Server/examples/nebexample.pl
===================================================================
--- /tags/rel-0_10/Nebulous-Server/examples/nebexample.pl	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/examples/nebexample.pl	(revision 17695)
@@ -0,0 +1,28 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings FATAL => qw( all );
+
+use Nebulous::Client;
+
+my $neb = Nebulous::Client->new(
+    proxy   => 'http://podb1.ifa.hawaii.edu:80/nebulous'
+);
+
+my $key = shift || 'foobarbaz';
+
+# make sure there isn't already a file named "foobarbaz" so this example
+# doesn't cause an error
+$neb->delete($key);
+
+my $fh = $neb->create($key);
+die "can't create file $key " unless $fh;
+close($fh);
+
+if (!$neb->replicate($key)) {
+    die "can't replicate object $key";
+}
+
+if (!$neb->delete($key)) {
+    die "can't delete object $key";
+}
Index: /tags/rel-0_10/Nebulous-Server/examples/uri_test.pl
===================================================================
--- /tags/rel-0_10/Nebulous-Server/examples/uri_test.pl	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/examples/uri_test.pl	(revision 17695)
@@ -0,0 +1,18 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+
+use URI;
+
+my $u1 = URI->new("neb://www.perl.com/bar");
+
+print $u1->scheme, "\n";
+print $u1->opaque, "\n";
+print $u1->path, "\n";
+
+my $u2 = URI->new("neb:/www.perl.com");
+print $u2->scheme, "\n";
+print $u2->opaque, "\n";
+print $u2->path, "\n";
+
Index: /tags/rel-0_10/Nebulous-Server/lib/Nebulous/Keys.pm
===================================================================
--- /tags/rel-0_10/Nebulous-Server/lib/Nebulous/Keys.pm	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/lib/Nebulous/Keys.pm	(revision 17695)
@@ -0,0 +1,214 @@
+# Copyright (c) 2004  Joshua Hoblitt
+#
+# $Id: Keys.pm,v 1.1 2008-03-20 21:13:28 jhoblitt Exp $
+
+package Nebulous::Keys;
+
+use strict;
+use warnings FATAL => qw( all );
+
+our $VERSION = '0.01';
+
+use base qw( Exporter );
+
+use File::Spec::Functions;
+use URI::file;
+use URI;
+
+our @EXPORT_OK      = qw( parse_neb_key );
+
+sub parse_neb_key
+{
+    my $text = shift;
+    return unless defined $text;
+
+    # white space is not allowed
+    if ($text =~ qr/\s+/) {
+        die "keys and URIs may not contain whitespace";
+    }
+
+    my $uri = URI->new($text);
+    my $scheme = $uri->scheme;
+    my $path = $uri->opaque;
+    
+    my $volume;
+    # if this is a valid uri 
+    if (defined $scheme) {
+        # if so, does it use the neb scheme?
+        die "URI does not use the 'neb' scheme"
+            unless $scheme eq 'neb'; 
+
+        # neb:path (not allowed)
+        # neb://<volume name>/...
+        # neb:/path... (leading '/' is stripped)
+        # neb:///path... (same as neb:/path)
+
+        # volume specifier must be at least one character
+        # strip off volume component if it exists
+        $path =~ s|^//([^/]+)||;
+        $volume = $1;
+
+        # require a leading slash if there is no volume name
+        if ((not defined $volume) and (not $path =~ m|^/|)) {
+            die "neb URI scheme requires a leading slash, eg. neb:/";
+        }
+    }
+
+    # strip leading slashes
+    $path =~ s|^/+||;
+
+    # remove multiple /'s and trailing slashes
+    $path = canonpath($path);
+
+    return ($volume, $path);
+}
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Nebulous::Keys - Nebulous Keys Explained
+
+=head1 DESCRIPTION
+
+The Nebulous system interprets it's storage object keys in a I<slightly>
+magical manner.  It supports both plain vanilla C<key strings> and keys in the
+form of an L<URI>.  In the L<URI> form a specific storage volume can be implied
+(as a request, not a requirement).  Both key forms are subject to mangling such
+that I<IF> the key I<looks> like a C<POSIX> semantics file path, it is
+canocalized.
+
+=head1 RESERVED SYNTAX
+
+This particular syntax is disallowed and is reserved for future use.
+
+    <neb:<phrase>/<path>
+
+=head1 EXAMPLES
+
+=head2 Key Strings
+
+    key:        foo/bar/baz/quix
+    mangled to: foo/bar/baz/quix
+    volume:     any
+
+    key:        /foo/bar/baz/quix
+    mangled to: foo/bar/baz/quix
+    volume:     any
+
+    key:        //foo/bar/baz/quix
+    mangled to: foo/bar/baz/quix
+    volume:     any
+
+    key:        ///foo/bar/baz/quix
+    mangled to: foo/bar/baz/quix
+    volume:     any
+
+    key:        foo////bar/baz/quix
+    mangled to: foo/bar/baz/quix
+    volume:     any
+
+    key:        foo/bar/baz/quix/
+    mangled to: foo/bar/baz/quix
+    volume:     any
+
+=head2 URI w/ volume name
+
+neb://<volume>/<path>
+
+    key:        neb://foo/bar/baz/quix
+    mangled to: bar/baz/quix
+    volume:     foo
+
+    key:        neb://foo//bar/baz/quix
+    mangled to: bar/baz/quix
+    volume:     foo
+
+    key:        neb://foo///bar/baz/quix
+    mangled to: bar/baz/quix
+    volume:     foo
+
+    key:        neb://foo/bar///baz/quix
+    mangled to: bar/baz/quix
+    volume:     foo
+
+    key:        neb://foo/bar/baz/quix/
+    mangled to: bar/baz/quix
+    volume:     foo
+
+=head2 URI w/o volume name
+
+neb:/<path>
+
+    key:        neb:/foo/bar/baz/quix
+    mangled to: foo/bar/baz/quix
+    volume:     any
+
+    key:        neb:///foo/bar/baz/quix
+    mangled to: foo/bar/baz/quix
+    volume:     any
+
+    key:        neb://///foo/bar/baz/quix
+    mangled to: foo/bar/baz/quix
+    volume:     any
+
+=head2 Forbidden Keys
+
+=head3 Key with whitespace
+
+    "/ foo/bar/baz/quix"
+    " /foo/bar/baz/quix"
+    "/foo/bar/baz/quix "
+
+=head3 URI with whitespace
+
+    "neb ://foo/bar/baz/quix"
+    "neb:// foo/bar/baz/quix"
+    " neb://foo/bar/baz/quix"
+    "neb://foo/bar/baz/quix "
+
+=head3 URI with out a volume requires a leading slash
+
+    neb:foo/bar/baz/quix
+
+=head1 CREDITS
+
+Just me, myself, and I.
+
+=head1 SUPPORT
+
+Please contact the author directly via e-mail.
+
+=head1 AUTHOR
+
+Joshua Hoblitt <jhoblitt@cpan.org>
+
+=head1 COPYRIGHT
+
+Copyright (C) 2008  Joshua Hoblitt.  All rights reserved.
+
+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.
+
+The full text of the license can be found in the LICENSE file included with
+this module, or in the L<perlgpl> Pod as supplied with Perl 5.8.1 and later.
+
+=head1 SEE ALSO
+
+L<Nebulous::Server>, L<Nebulous::Client>, L<nebclient(3)>
+
+=cut
Index: /tags/rel-0_10/Nebulous-Server/lib/Nebulous/Server.pm
===================================================================
--- /tags/rel-0_10/Nebulous-Server/lib/Nebulous/Server.pm	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/lib/Nebulous/Server.pm	(revision 17695)
@@ -0,0 +1,1355 @@
+# Copyright (c) 2004-2008  Joshua Hoblitt
+#
+# $Id: Server.pm,v 1.71 2008-05-15 03:27:28 jhoblitt Exp $
+
+package Nebulous::Server;
+
+use strict;
+use warnings FATAL => qw( all );
+no warnings qw( uninitialized );
+
+our $VERSION = '0.10';
+
+use base qw( Class::Accessor::Fast );
+
+use DBI;
+use Digest::SHA1 qw( sha1_hex );
+use File::ExtAttr qw( setfattr );
+use File::Path;
+use File::Spec;
+use Log::Log4perl;
+use Nebulous::Server::Config;
+use Nebulous::Server::Log;
+use Nebulous::Server::SQL;
+use Nebulous::Keys qw( parse_neb_key );
+use Params::Validate qw( validate_pos SCALAR SCALARREF UNDEF );
+use URI::file;
+
+__PACKAGE__->mk_accessors(qw( log sql config ));
+
+use constant SUBPATH_DEPTH => 2;
+
+sub new {
+    my $class = shift;
+
+    # let Nebulous::Server::Config validate our params
+    my $config = Nebulous::Server::Config->init( @_ );
+
+    # log4perl is not avaliable until we call init()
+    Nebulous::Server::Log->init($config);
+    my $log = Log::Log4perl::get_logger( "Nebulous::Server" );
+
+    my $sql = Nebulous::Server::SQL->new;
+
+    $log->debug( "entered - @_" );
+
+    my $db;
+    eval {
+        $db = DBI->connect(
+            $config->dsn,
+            $config->dbuser,
+            $config->dbpasswd,
+            {
+                RaiseError => 1,
+                PrintError => 0,
+                AutoCommit => 0,
+            },
+        );
+
+        $db->do( $sql->set_transaction_model );
+
+        $db->commit;
+        $log->debug("commit");
+    };
+    if ( $@ ) {
+        $db->rollback if $db;
+        $log->debug("rollback");
+        $log->logdie( "database error: $@" );
+    }
+
+    $log->debug( "connected to database: ", sub { $db->data_sources; } );
+
+    my $self = bless {}, ref $class || $class;
+    $self->log($log);
+    $self->sql($sql);
+    $self->db($db);
+    $self->config($config);
+
+    $log->debug( "leaving" );
+    
+    return $self;
+}
+
+sub db {
+    my $self = shift;
+
+    if (@_) {
+        $self->{db} = $_[0];
+        return $self;
+    }
+
+    my $log     = $self->log;
+    my $sql     = $self->sql;
+    my $config  = $self->config;
+
+    # if the dbh is still alive, return it
+    if (defined $self->{db} and $self->{db}->ping) {
+        $log->debug("db handle is still alive");
+        return $self->{db};
+    }
+    # otherwise create a new connection
+    $log->debug("db handle is dead");
+
+    # if we're running under mod_perl & Apache::DBI is loaded we want to
+    # reconnect to the database everytime the dbh is requested.  The rational is
+    # that if we're running under mod_perl this is probably a log running
+    # processes and the database might have gone away on us.  Apache::DBI will
+    # take care of getting a valid dbh back.
+    if ($INC{'Apache/DBI.pm'} && $ENV{MOD_PERL}) {
+        my $db;
+        eval {
+            $db = DBI->connect(
+                $config->dsn,
+                $config->dbuser,
+                $config->dbpasswd,
+                {
+                    RaiseError => 1,
+                    PrintError => 0,
+                    AutoCommit => 0,
+                },
+            );
+
+            $db->do( $sql->set_transaction_model );
+
+            $db->commit;
+            $log->debug("commit");
+        };
+        if ( $@ ) {
+            $db->rollback if $db;
+            $log->debug("rollback");
+            $log->logdie( "database error: $@" );
+        }
+
+        $self->{db} = $db;
+
+        return $db;
+    }
+
+    return $self->{db};
+}
+
+
+sub create_object
+{
+    my $self = shift;
+
+    my ($key, $vol_name) = validate_pos(@_,
+        {
+            type        => SCALAR,
+        },
+        {
+            type        => SCALAR|UNDEF,
+            callbacks   => {
+                # check that the volume requested is valid
+                'is valid volume name' => sub {
+                    return 1 if not defined $_[0];
+                    $self->_is_valid_volume_name($_[0])
+                },
+            },
+            optional    => 1,
+        },
+    );
+
+    my $log = $self->log;
+    my $sql = $self->sql;
+    my $db  =$self->db;
+
+    $log->debug( "entered - @_" );
+
+    {
+        my $volume;
+        ($volume, $key) = parse_neb_key($key);
+        # vol_name overrides the key implied volume
+        $vol_name ||= $volume;
+    }
+
+    if (defined $vol_name and !$self->_is_valid_volume_name($vol_name)) {
+        die "$vol_name is not a valid volume name"
+    }
+        
+    my ($vol_id, $vol_path, $vol_xattr) = $self->_get_storage_volume($vol_name);
+
+    my $uri;
+    eval {
+        {
+            # create storage_object
+            my $query = $db->prepare_cached( $sql->new_object ); 
+            $query->execute('NULL', $key);
+        }
+
+        my $so_id;
+        {
+            # get object ID
+            my $query = $db->prepare_cached( $sql->last_insert_id );
+            $query->execute;
+            ($so_id) = $query->fetchrow_array;
+            # XXX finish seems to be required when using LAST_INSERT_ID() or we
+            # get a warning about the stmt handling still be active the next
+            # time LAST_INSERT_ID() is invoked
+            $query->finish;
+        }
+
+        {
+            # create storage_object_attr
+            my $query = $db->prepare_cached( $sql->new_object_attr ); 
+            $query->execute($so_id);
+        }
+
+        {
+            # create instance with no URI
+            my $query = $db->prepare_cached( $sql->new_object_instance );
+            $query->execute($vol_id);
+        }
+
+        my $ins_id;
+        {
+            # get instance ID
+            my $query = $db->prepare_cached( $sql->last_insert_id );
+            $query->execute;
+            ($ins_id) = $query->fetchrow_array;
+            # XXX finish seems to be required when using LAST_INSERT_ID() or we
+            # get a warning about the stmt handling still be active the next
+            # time LAST_INSERT_ID() is invoked
+            $query->finish;
+        }
+
+        # Unfortunately, since we want to use the instance row's ID as part of the
+        # actual on disk file name we can't try to create the file until after
+        # we've create both a new storage_storage object and instance.
+
+        # TODO add some stuff here to retry if unsucessful
+        $uri = $self->_create_empty_instance_file($key, $so_id, $ins_id, $vol_path, $vol_xattr);
+        $log->debug("created $uri on volume ID: $vol_id");
+
+        {
+            # update the instance with URI & vol_id that the file is on
+            my $query = $db->prepare_cached( $sql->update_instance_uri );
+            # vol_id, uri, ins_id
+            $query->execute($vol_id, "$uri", $ins_id);
+        }
+
+        $db->commit;
+        $log->debug("commit");
+    };
+    if ($@) {
+        $db->rollback;
+        $log->debug("rollback");
+        $log->logdie("error: $@");
+    }
+
+    $log->debug("leaving");
+
+    return "$uri";
+}
+
+sub rename_object
+{
+    my $self = shift;
+
+    my ($key, $newkey) = validate_pos(@_,
+        {
+            type        => SCALAR,
+            callbacks   => {
+                'is valid object key' => sub { $self->_is_valid_object_key($_[0]) },
+            },
+        },
+        # make sure the "newkey" doesn't already exist
+        {
+            type        => SCALAR,
+            callbacks   => {
+                'is not valid object key' => sub { not $self->_is_valid_object_key($_[0]) },
+            },
+        },
+    );
+
+    my $log = $self->log;
+    my $sql = $self->sql;
+    my $db  =$self->db;
+
+    $log->debug("entered - @_");
+
+    # ignore volumes
+    $key    = parse_neb_key($key);
+    $newkey = parse_neb_key($newkey);
+
+    eval {
+        # rename storage_object
+        my $query = $db->prepare_cached($sql->rename_object); 
+        # this SQL statment takes the new key name as the first param
+        my $rows = $query->execute($newkey, $key);
+
+        # if we affected more then one row something very bad has happened.
+        unless ($rows == 1) {
+            $query->finish;
+            $log->logdie("affected row count is $rows instead of 1");
+        }
+
+        $db->commit;
+        $log->debug("commit");
+    };
+    if ($@) {
+        $db->rollback;
+        $log->debug("rollback");
+        $log->logdie("database error: $@");
+    }
+
+    $log->debug("leaving");
+
+    return $newkey;
+}
+
+
+sub replicate_object
+{
+    my $self = shift;
+
+    my ($key, $vol_name) = validate_pos(@_,
+        {
+            type        => SCALAR,
+            callbacks   => {
+                'is valid object key' => sub { $self->_is_valid_object_key($_[0]) },
+            },
+        },
+        {
+            type        => SCALAR|UNDEF,
+            callbacks   => {
+                # check that the volume name requested is valid
+                'is valid volume name' => sub {
+                    return 1 if not defined $_[0];
+                    $self->_is_valid_volume_name($_[0])
+                },
+            },
+            optional    => 1,
+        },
+    );
+
+    my $log = $self->log;
+    my $sql = $self->sql;
+    my $db  =$self->db;
+
+    $log->debug("entered - @_");
+
+    {
+        my $volume;
+        ($volume, $key) = parse_neb_key($key);
+        # vol_name overrides the key implied volume
+        $vol_name ||= $volume;
+    }
+
+    if (defined $vol_name and !$self->_is_valid_volume_name($vol_name)) {
+        die "$vol_name is not a valid volume name"
+    }
+        
+    my ($vol_id, $vol_path, $vol_xattr) = $self->_get_storage_volume($vol_name);
+
+    my $uri;
+    eval {
+        my $so_id;
+        {
+            my $query = $db->prepare_cached( $sql->get_object_instances );
+            my $rows = $query->execute($key, 1);
+
+            unless ( $rows > 0 ) {
+                $query->finish;
+                $log->logdie( "storage object does not exist" );
+            }
+
+            $so_id = $query->fetchrow_hashref->{ 'so_id' };
+            $query->finish;
+        }
+
+        {
+            my $query = $db->prepare_cached( $sql->new_instance );
+            $query->execute($so_id, $vol_id);
+        }
+
+        my $ins_id;
+        {
+            my $query = $db->prepare_cached( $sql->last_insert_id );
+            $query->execute();
+            ($ins_id) = $query->fetchrow_array;
+            # XXX finish seems to be required when using LAST_INSERT_ID() or we
+            # get a warning about the stmt handling still be active the next
+            # time LAST_INSERT_ID() is invoked
+            $query->finish;
+        }
+
+        # Unfortunately, since we want to use the instance row's ID as part of
+        # the actual on disk file name we can't try to create the file until
+        # after we've create both a new storage_storage object and instance.
+
+        # TODO add some stuff here to retry if unsucessful
+        $uri = $self->_create_empty_instance_file($key, $so_id, $ins_id, $vol_path, $vol_xattr);
+
+        {
+            my $query = $db->prepare_cached( $sql->update_instance_uri );
+            # vol_id, uri, ins_id
+            $query->execute($vol_id, $uri, $ins_id);
+        }
+
+        $db->commit;
+        $log->debug("commit");
+    };
+    if ($@) {
+        $db->rollback;
+        $log->debug("rollback");
+        $log->logdie("error: $@");
+    }
+
+    $log->debug("leaving");
+
+    return "$uri";
+}
+
+sub lock_object
+{
+    my $self = shift;
+
+    my ( $key, $type ) = validate_pos( @_,
+        {
+            type        => SCALAR,
+            callbacks   => {
+                'is valid object key' => sub { $self->_is_valid_object_key($_[0]) },
+            },
+        },
+        {
+            type        => SCALAR,
+            callbacks   => {
+                'is read or write' => sub { $_[0] =~ /^(?:read|write)$/ },
+            },
+        },
+    );
+
+    my $log = $self->log;
+    my $sql = $self->sql;
+    my $db  =$self->db;
+
+    $log->debug( "entered - @_" );
+
+    # ignore volume
+    $key = parse_neb_key($key);
+
+    my $so_id;
+    my $read_lock;
+    my $write_lock;
+
+    eval {
+        {
+            # this will set update locks
+            my $query = $db->prepare_cached( $sql->get_object_locks );
+            my $rows = $query->execute( $key );
+            unless ( $rows == 1 ) {
+                $query->finish;
+                $log->logdie( "storage object does not exist" );
+            }
+
+            my $row = $query->fetchrow_hashref;
+            $query->finish;
+
+            $so_id      = $row->{ 'so_id' };
+            $read_lock  = $row->{ 'read_lock' };
+            $write_lock = $row->{ 'write_lock' };
+        }
+
+        if ($type eq 'write') {
+            # can't set a write lock twice and
+            # can't set a write lock if there are read locks
+            if ($write_lock) {
+                $log->logdie("can not write lock twice -- retry");
+            }
+            
+            if ($read_lock > 0) {
+                $log->logdie("can not write lock after read lock -- retry");
+            }
+
+            {
+                my $query = $db->prepare_cached( $sql->set_write_lock );
+                my $rows = $query->execute($key);
+            
+                # if we affected more then one row something very bad has happened.
+                unless ($rows == 1) {
+                    $log->logdie("affected row count is $rows instead of 1");
+                }
+
+            }
+        } elsif ($type eq 'read') {
+            # can't set a read lock if there's a write lock
+            if ($write_lock) {
+                $log->logdie("can not read lock after write lock -- retry");
+            }
+
+            {
+                my $query = $db->prepare_cached( $sql->increment_read_lock );
+                my $rows = $query->execute($key);
+            
+                # if we affected more then one row something very bad has happened.
+                unless ($rows == 1) {
+                    $log->logdie("affected row count is $rows instead of 1");
+                }
+            }
+        }
+
+        $db->commit;
+        $log->debug("commit");
+    };
+    if ($@) {
+        $db->rollback;
+        $log->debug("rollback");
+        $log->logdie("error: $@");
+    }
+
+    $log->debug("leaving");
+
+    return 1;
+}
+
+sub unlock_object
+{
+    my $self = shift;
+
+    my ( $key, $type ) = validate_pos( @_,
+        {
+            type        => SCALAR,
+            callbacks   => {
+                'is valid object key' => sub { $self->_is_valid_object_key($_[0]) },
+            },
+        },
+        {
+            type        => SCALAR,
+            callbacks   => {
+                'is read or write' => sub { $_[0] =~ /^(?:read|write)$/ },
+            },
+        },
+    );
+
+    my $log = $self->log;
+    my $sql = $self->sql;
+    my $db  =$self->db;
+
+    $log->debug( "entered - @_" );
+
+    # ignore volume
+    $key = parse_neb_key($key);
+
+    my $so_id;
+    my $read_lock;
+    my $write_lock;
+
+    eval {
+        {
+            # this will set update locks
+            my $query = $db->prepare_cached( $sql->get_object_locks );
+            my $rows = $query->execute($key);
+            unless ($rows == 1) {
+                $query->finish;
+                $log->logdie("storage object does not exist");
+            }
+
+            my $row = $query->fetchrow_hashref;
+            $query->finish;
+
+            $so_id      = $row->{ 'so_id' };
+            $read_lock  = $row->{ 'read_lock' };
+            $write_lock = $row->{ 'write_lock' };
+        }
+
+        if ($type eq 'write') {
+            # can't remove a write lock if it doesn't exist
+            if ($read_lock) {
+                $log->logdie("can not have a write lock under a read lock");
+            }
+
+            unless ($write_lock) {
+                $log->logdie("can not remove non-existant write lock");
+            }
+
+            {
+                my $query = $db->prepare_cached( $sql->delete_write_lock );
+                my $rows = $query->execute($key);
+            
+                # if we affected more then one row something very bad has happened.
+                unless ($rows == 1) {
+                    $log->logdie("affected row count is $rows instead of 1");
+                }
+            }
+        } elsif ($type eq 'read') {
+            # can't remove a read lock if there's a write lock and
+            # can't remove a read lock if there aren't any
+            if ($write_lock) {
+                $log->logdie("can not have a read lock under a write lock");
+            }
+               
+            if ($read_lock == 0) {
+                $log->logdie("can not remove non-existant read lock");
+            }
+
+            {
+                my $query = $db->prepare_cached( $sql->decrement_read_lock );
+                my $rows = $query->execute($key);
+            
+                # if we affected more then one row something very bad has happened.
+                unless ($rows == 1) {
+                    $log->logdie("affected row count is $rows instead of 1");
+                }
+
+            }
+        }
+        $db->commit;
+        $log->debug("commit");
+    };
+    if ($@) {
+        $db->rollback;
+        $log->debug("rollback");
+        $log->logdie("error: $@");
+    }
+
+    $log->debug( "leaving" );
+
+    return 1;
+}
+
+
+sub setxattr_object
+{
+    my $self = shift;
+
+    my ($key, $name, $value, $flags) = validate_pos(@_,
+        {
+            type        => SCALAR,
+            callbacks   => {
+                'is valid object key' => sub { $self->_is_valid_object_key($_[0]) },
+            },
+        },
+        {
+            type        => SCALAR,
+        },
+        {
+            type        => SCALAR,
+        },
+        {
+            type        => SCALAR,
+            callbacks   => {
+                'is read or write' => sub { $_[0] =~ /^(?:create|replace)$/i },
+            },
+        },
+    );
+
+    my $log = $self->log;
+    my $sql = $self->sql;
+    my $db  =$self->db;
+
+    $log->debug("entered - @_");
+
+    # ignore volume
+    $key = parse_neb_key($key);
+
+    eval {
+        my $query;
+
+        if ($flags eq 'create') {
+            $query = $db->prepare_cached( $sql->new_object_xattr );
+        } else {
+            # replace
+            $query = $db->prepare_cached( $sql->replace_object_xattr );
+        }
+
+        # name, value, ext_id
+        my $rows = $query->execute($name, $value, $key);
+        $query->finish;
+
+        # if we affected more then one row something very bad has happened.
+        if ($flags eq 'create') {
+            unless ($rows == 1) {
+                $log->logdie( "affected row count is $rows instead of 1" );
+            }
+        } else {
+            unless ($rows == 2) {
+                $log->logdie( "affected row count is $rows instead of 2" );
+            }
+        }
+
+        $db->commit;
+        $log->debug("commit");
+    };
+    if ($@) {
+        $db->rollback;
+        $log->debug("rollback");
+        $log->logdie("database error: $@");
+    }
+
+    $log->debug("leaving");
+
+    return 1;
+}
+
+
+sub getxattr_object
+{
+    my $self = shift;
+
+    my ($key, $name) = validate_pos(@_,
+        {
+            type        => SCALAR,
+            callbacks   => {
+                'is valid object key' => sub { $self->_is_valid_object_key($_[0]) },
+            },
+        },
+        {
+            type        => SCALAR,
+        },
+    );
+
+    my $log = $self->log;
+    my $sql = $self->sql;
+    my $db  =$self->db;
+
+    $log->debug("entered - @_");
+
+    # ignore volume
+    $key = parse_neb_key($key);
+
+    my $value;
+    eval {
+        my $query = $db->prepare_cached( $sql->get_object_xattr );
+        # ext_id, name
+        my $rows = $query->execute($key, $name);
+
+        # no rows returned means that the xattr does not exist
+        if ($rows == 0) {
+            $query->finish;
+            $log->logdie( "xattr $key:$name does not exist" );
+        }
+        # if we go more then one row bad something very bad has happened.
+        unless ($rows == 1) {
+            $query->finish;
+            $log->logdie( "affected row count is $rows instead of 1" );
+        }
+
+        my $row = $query->fetchrow_hashref;
+        # XXX: DBI bug? ->finish is needed here even though $query is going out
+        # of scope
+        $query->finish;
+        $value = $row->{ 'value' };
+    };
+    $log->logdie("database error: $@") if $@;
+
+    $log->debug("leaving");
+
+    return $value;
+}
+
+
+sub listxattr_object
+{
+    my $self = shift;
+
+    my ($key) = validate_pos(@_,
+        {
+            type        => SCALAR,
+            callbacks   => {
+                'is valid object key' => sub { $self->_is_valid_object_key($_[0]) },
+            },
+        },
+    );
+
+    my $log = $self->log;
+    my $sql = $self->sql;
+    my $db  =$self->db;
+
+    $log->debug("entered - @_");
+
+    # ignore volume
+    $key = parse_neb_key($key);
+
+    my @xattrs;
+    eval {
+        my $query = $db->prepare_cached( $sql->list_object_xattr );
+        # ext_id
+        my $rows = $query->execute($key);
+
+        while (my $row = $query->fetchrow_hashref) {
+            push @xattrs, $row->{ 'name' };
+        }
+    };
+    $log->logdie("database error: $@") if $@;
+
+    $log->debug("leaving");
+
+    return \@xattrs;
+}
+
+
+sub removexattr_object
+{
+    my $self = shift;
+
+    my ($key, $name) = validate_pos(@_,
+        {
+            type        => SCALAR,
+            callbacks   => {
+                'is valid object key' => sub { $self->_is_valid_object_key($_[0]) },
+            },
+        },
+        {
+            type        => SCALAR,
+        },
+    );
+
+    my $log = $self->log;
+    my $sql = $self->sql;
+    my $db  =$self->db;
+
+    $log->debug("entered - @_");
+
+    # ignore volume
+    $key = parse_neb_key($key);
+
+    eval {
+        my $query = $db->prepare_cached( $sql->remove_object_xattr );
+        # ext_id, name
+        my $rows = $query->execute($key, $name);
+        $query->finish;
+
+        # if we affected more then one row something very bad has happened.
+        unless ($rows == 1) {
+            $log->logdie( "affected row count is $rows instead of 1" );
+        }
+
+        $db->commit;
+        $log->debug("commit");
+    };
+    if ($@) {
+        $db->rollback;
+        $log->debug("rollback");
+        $log->logdie("database error: $@");
+    }
+
+    $log->debug("leaving");
+
+    return 1;
+}
+
+
+sub find_objects
+{
+    my $self = shift;
+
+    my ( $pattern ) = validate_pos( @_,
+        {
+            type        => SCALAR,
+            optional    => 1,
+        },
+    );
+
+    my $log = $self->log;
+    my $sql = $self->sql;
+    my $db  =$self->db;
+
+    $log->debug( "entered - @_" );
+
+    unless ($pattern) {
+        $log->debug( "leaving" );
+        $log->logdie("no keys found");
+    }
+
+    # attempt to strip off neb:// if it exists
+    if ($pattern =~ m|^neb:|) {
+        $pattern = parse_neb_key($pattern);        
+    }
+
+    my @keys;
+    eval {
+        my $query = $db->prepare_cached( $sql->find_objects );
+        $query->execute( $pattern );
+
+        while ( my $row = $query->fetchrow_hashref ) {
+            my $key = $row->{ 'ext_id' };
+            push @keys, $key if $key;
+        }
+    };
+    $log->logdie("database error: $@") if $@;
+
+    $log->logdie("no keys found") unless ( scalar @keys );
+
+    $log->debug( "leaving" );
+
+    return \@keys;
+}
+
+
+sub find_instances
+{
+    my $self = shift;
+
+    my ($key, $vol_name) = validate_pos(@_,
+        {
+            type        => SCALAR,
+            callbacks   => {
+                'is valid object key' => sub { $self->_is_valid_object_key($_[0]) },
+            },
+        },
+        {
+            type        => SCALAR|UNDEF,
+            callbacks   => {
+                # check that the volume name requested is valid
+                'is valid volume name' => sub {
+                    return 1 if not defined $_[0];
+                    $self->_is_valid_volume_name($_[0])
+                },
+            },
+            optional    => 1,
+        },
+    );
+
+    my $log = $self->log;
+    my $sql = $self->sql;
+    my $db  =$self->db;
+
+    $log->debug("entered - @_");
+
+    {
+        my $volume;
+        ($volume, $key) = parse_neb_key($key);
+        if (defined $volume) {
+            die "$volume is not a valid volume name"
+                unless $self->_is_valid_volume_name($volume);
+        }
+        $vol_name ||= $volume;
+    }
+
+    my @locations;
+    eval {
+        my $query;
+        if ($vol_name) {
+            $query = $db->prepare_cached( $sql->get_object_instances_by_vol_name );
+            # ext_id, name, available
+            my $rows = $query->execute($key, $vol_name, 1);
+            unless ($rows > 0) {
+                $query->finish;
+                $log->logdie("no instances on storage volume: $vol_name or volume is not available");
+            }
+        } else {
+            $query = $db->prepare_cached( $sql->get_object_instances );
+            # ext_id, available
+            my $rows = $query->execute($key, 1);
+            unless ($rows > 0) {
+                $query->finish;
+                $log->logdie("no instances available");
+            }
+        }
+
+        while (my $row = $query->fetchrow_hashref) {
+            my $instance = $row->{ 'uri' };
+            push @locations, $instance if $instance;
+        }
+    };
+    $log->logdie("database error: $@") if $@;
+
+    # XXX remove this?
+    $log->logdie("no instances found") unless (scalar @locations);
+
+    $log->debug("found: @locations");
+
+    $log->debug("leaving");
+
+    return \@locations;
+}
+
+
+sub delete_instance
+{
+    my $self = shift;
+
+    my ( $uri ) = validate_pos( @_,
+        {
+            type => SCALAR|SCALARREF,
+        },
+    );
+
+    my $log = $self->log;
+    my $sql = $self->sql;
+    my $db  =$self->db;
+
+    $log->debug( "entered - @_" );
+
+    eval {
+        my $so_id;
+        my $instances;
+        # get so_id
+        {
+            my $query = $db->prepare_cached( $sql->get_object_from_uri );
+            my $rows = $query->execute( $uri );
+
+            unless ( $rows > 0 ) {
+                $query->finish;
+                $log->logdie( "no instance is associated with uri" );
+            }
+
+            $so_id = $query->fetchrow_hashref->{ 'so_id' };
+            $query->finish;
+
+        }
+
+        {
+            my $query = $db->prepare_cached( $sql->get_instance_count );
+            $query->execute( $so_id );
+
+            $instances = $query->fetchrow_hashref->{ 'count(ins_id)' };
+            $query->finish;
+        }
+
+        # remove instance
+        {
+            my $query = $db->prepare_cached( $sql->delete_instance );
+            my $rows = $query->execute( $uri );
+            $query->finish;
+            
+            # if we affected something other then two rows something very bad
+            # has happened
+            unless ( $rows == 1 ) {
+                $log->logdie( "affected row count is $rows instead of 1" );
+            }
+        }
+
+        # if we just deleted the last instance associated with a storage object
+        # remove it too
+        if ( $instances == 1 ) {
+            # we just removed the last instance
+            my $query = $db->prepare_cached( $sql->delete_object );
+            my $rows = $query->execute( $so_id );
+            $query->finish;
+
+            # TODO: this will have to be changed in order to support hardlinks
+            unless ( $rows == 1 ) {
+                $log->logdie( "affected row count is $rows instead of 2" );
+            }
+        }
+
+        $db->commit;
+        $log->debug("commit");
+    };
+    if ( $@ ) {
+        $db->rollback;
+        $log->debug("rollback");
+        $log->logdie( "database error: $@" );
+    }
+
+    $log->debug( "leaving" );
+
+    return 1;
+}
+
+
+sub stat_object
+{
+    my $self = shift;
+
+    my ( $key ) = validate_pos( @_,
+        {
+            type        => SCALAR,
+            callbacks   => {
+                'is valid object key' => sub { $self->_is_valid_object_key($_[0]) },
+            },
+        },
+    );
+
+    my $log = $self->log;
+    my $sql = $self->sql;
+    my $db  =$self->db;
+
+    $log->debug("entered - @_");
+
+    # ignore volume
+    $key = parse_neb_key($key);
+
+    my $stat;
+    eval {
+        my $query = $db->prepare_cached( $sql->stat_object );
+        my $rows = $query->execute($key);
+
+        unless ($rows == 1) {
+            $log->logdie("no storage object found");
+        }
+
+        $stat = $query->fetchrow_arrayref;
+        $query->finish;
+    };
+    $log->logdie("database error: $@") if $@;
+
+    $log->debug("leaving");
+
+    return $stat;
+}
+
+
+sub mounts
+{
+    my $self = shift;
+
+    validate_pos(@_); 
+
+    my $log = $self->log;
+    my $sql = $self->sql;
+    my $db  =$self->db;
+
+    $log->debug("entered - @_");
+
+    my $stats;
+    my $query;
+    eval {
+        # ask the db to generate the table of mounted Nebulous volume 
+        $db->do("call getmountedvol()");
+
+        $query = $db->prepare_cached( $sql->get_mounted_volumes );
+        $query->execute();
+
+        # suck that table into an AoA
+        $stats = $query->fetchall_arrayref;
+
+        $query->finish;
+    };
+    $log->logdie("database error: $@") if $@;
+
+    $log->logdie("no mounted volumes found") unless (scalar @$stats);
+
+    $log->debug("leaving");
+
+    return $stats;
+}
+
+
+sub _get_storage_volume
+{
+    my $self = shift;
+
+    my $log = $self->log;
+    my $sql = $self->sql;
+    my $db  =$self->db;
+
+    no warnings qw( uninitialized );
+    $log->debug( "entered - @_" );
+    use warnings;
+
+    my $name = shift;
+
+    my ($vol_id, $vol_path, $xattr);
+    eval {
+        # TODO cache this?
+        my $query;
+        my $rows;
+        if ( $name ) {
+            $query = $db->prepare_cached( $sql->get_storage_volume_by_name );
+            # %free, name, avaiable, allocate
+            $rows = $query->execute(0.95, $name, 1, 1);
+            # XXX destinguish between non-existant and unaviable
+            unless ($rows > 0) {
+                $query->finish;
+                $log->logdie("storage volume: $name is not available");
+            }
+            # when matching by name we shouldn't ever match more than once
+            if ($rows > 1) {
+                $query->finish;
+                $log->logdie("affected row count is $rows instead of 1");
+            }
+        } else {
+            $query = $db->prepare_cached( $sql->get_storage_volume );
+            # %free, avaiable, allocate
+            $rows = $query->execute(0.95, 1, 1);
+            # there has to be atleast one storage volume
+            unless ($rows > 0) {
+                $query->finish;
+                $log->logdie("no storage volume is available");
+            }
+        }
+
+        my $free;
+        ($vol_id, $vol_path, $xattr, $free) = $query->fetchrow_array;
+        $query->finish;
+    };
+    $log->logdie("database error: $@") if $@;
+
+    $log->logdie("failed to find a suitable volume" )
+        unless defined $vol_id and defined $vol_path;
+
+    $log->debug( "leaving" );
+
+    return ($vol_id, $vol_path, $xattr);
+}
+
+
+sub _is_valid_object_key
+{
+    my ($self, $key) = @_;
+
+    my $log = $self->log;
+    my $sql = $self->sql;
+    my $db  =$self->db;
+
+    my $volume;
+    ($volume, $key) = parse_neb_key($key);
+
+    my $ext_id;
+    eval {
+        my $query = $db->prepare_cached( $sql->get_object ); 
+        $query->execute($key);
+        ($ext_id) = $query->fetchrow_array;
+        $query->finish;
+    };
+    if ($@) {
+        $db->rollback;
+        $log->debug("rollback");
+        $log->logdie( "database error: $@" );
+    }
+
+    if (defined $ext_id) {
+        return 1;
+    } 
+
+    return;
+}
+
+
+sub _is_valid_volume_name
+{
+    my ($self, $vol_name) = @_;
+
+    my $log = $self->log;
+    my $sql = $self->sql;
+    my $db  =$self->db;
+
+    my ($vol_id, $vol_path);
+    eval {
+        my $query = $db->prepare_cached( $sql->get_volume_by_name ); 
+        $query->execute( $vol_name );
+        my $free;
+        ($vol_id, $vol_path, $free) = $query->fetchrow_array;
+        $query->finish;
+    };
+    $log->logdie("database error: $@") if $@;
+
+    if (defined $vol_id and defined $vol_path) {
+        return 1;
+    } 
+
+    return;
+}
+
+sub _create_empty_instance_file
+{
+    my $self = shift;
+
+    my ($key, $so_id, $ins_id, $vol_path, $xattr) =  @_;
+
+    my $log = $self->log;
+    my $sql = $self->sql;
+    my $db  = $self->db;
+
+    my $uri;
+    eval {
+        my $storage_path = $self->_generate_storage_path($key, $vol_path);
+        my $storage_filename = $self->_generate_storage_filename($key, $ins_id);
+        unless (-d $storage_path) {
+            mkpath($storage_path, 0, 0775)
+                or die "can't create storage path: $storage_path";
+        }
+        my $fqpn = File::Spec->catfile($storage_path, $storage_filename);
+        $uri = URI::file->new($fqpn);
+        $log->debug("generated uri $uri");
+        $self->_create_empty_file($uri->file);
+    };
+    if ($@) {
+        $log->logdie($@);
+    }
+
+    if ($xattr) {
+        my $path = $uri->file;
+        die "can not set xattr on $path: $!"
+            unless (setfattr($path, 'user.nebulous_key', $key));
+    }
+
+    return $uri;
+}
+
+
+sub _create_empty_file
+{
+    my $self = shift;
+
+    my ($path) = @_;
+
+    # perl's open() can't do an O_CREAT | O_EXCL
+    die "file $path already exists" if (-e $path);
+
+    my $fh;
+    die "can not open $path: $!"
+        unless (open($fh, '>', $path));
+
+    die "can not close $path: $!"
+        unless (close($fh));
+
+    die "can not chmod $path: $!"
+        unless (chmod 0664, $path);
+
+    return $path;
+}
+
+
+sub _generate_storage_filename
+{
+    my $self = shift;
+
+    my ($key, $ins_id) = @_;
+
+    my $filename = $key;
+    # mangle '/'s into ':'
+    $filename =~ s|/|:|g;
+
+    return "$ins_id.$filename"
+}
+
+
+sub _generate_storage_path
+{
+    my $self = shift;
+
+    my ($key, $vol_path) = @_;
+
+    # taken and modified from Cache::File::cache_file_path()
+    # Copyright (C) 2003-2006 Chris Leishman.  All Rights Reserved.
+    my $shakey = sha1_hex($key);
+    my (@path) = unpack('A2' x SUBPATH_DEPTH, $shakey);
+
+    return File::Spec->catdir($vol_path, @path);
+}
+
+
+sub DESTROY
+{
+    my $self = shift;
+
+    my $log = $self->log;
+    my $sql = $self->sql;
+    my $db  =$self->db;
+
+    $log->debug( "entered" );
+
+    $self->db->disconnect;        
+
+    $log->debug( "disconnected from database: ", sub { $db->data_sources; } );
+
+    $log->debug( "leaving" );
+}
+
+1;
+
+__END__
Index: /tags/rel-0_10/Nebulous-Server/lib/Nebulous/Server.pod
===================================================================
--- /tags/rel-0_10/Nebulous-Server/lib/Nebulous/Server.pod	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/lib/Nebulous/Server.pod	(revision 17695)
@@ -0,0 +1,240 @@
+=pod
+
+=head1 NAME
+
+Nebulous::Server - Nebulous server component
+
+=head1 SYNOPSIS
+
+    use Nebulous::Server;
+
+    Nebulous::Server->new( $dsn, $username, $passwd );
+    Nebulous::Server->db( $db );
+    Nebulous::Server->log( $log );
+    Nebulous::Server->sql( $sql );
+    Nebulous::Server->config( $config );
+    Nebulous::Server->create_object( $key, $volume );
+    Nebulous::Server->rename_object( $key, $newkey );
+    Nebulous::Server->replicate_object( $key, $volume );
+    Nebulous::Server->lock_object( $key, $type );
+    Nebulous::Server->unlock_object( $key, $type );
+    Nebulous::Server->setxattr_object( $key, $name, $value, $flags );
+    Nebulous::Server->getxattr_object( $key, $name );
+    Nebulous::Server->listxattr_object( $key );
+    Nebulous::Server->removexattr_object( $key, $name );
+    Nebulous::Server->find_objects( $pattern );
+    Nebulous::Server->find_instances( $key, $volume );
+    Nebulous::Server->delete_instance( $uri );
+    Nebulous::Server->stat_object( $key );
+    Nebulous::Server->mounts();
+
+=head1 DESCRIPTION
+
+This is the server side component of the Nebulous system.  It is primarily a
+middle-ware abstraction of an SQL database.
+
+=head1 USAGE
+
+Typically, this module would not be directly interacted with (although, it is
+possible to do so).  It is intended that one or more intermediate transport
+layers are used (SOAP, XML-RPC, etc.)
+
+=head2 Import Parameters
+
+This module accepts no arguments to it's C<import> method and exports no
+I<symbols>.
+
+=head2 Methods
+
+=head3 Class Methods
+
+=over 4
+
+=item * setup( $dsn, $username, $passwd );
+
+Accepts 3 parameters, all are mandatory.  Returns Boolean true.  Throws an
+exception on error.
+
+=item * create_object( $key, $volume );
+
+Accepts 4 parameters, only the first is mandatory.  Returns a URI.  Throws an
+exception on error.
+
+=item * replicate_object( $key, $volume );
+
+Accepts 2 parameters, only the first is mandatory.  Returns a URI.  Throws an
+exception on error.
+
+=item * lock_object( $key, $type );
+
+Accepts 2 parameters, all are mandatory.  Returns Boolean true.   Throws an
+exception on error.
+
+=item * unlock_object( $key, $type );
+
+Accepts 2 parameters, all are mandatory.  Returns Boolean true.   Throws an
+exception on error.
+
+=item * setxattr_object( $key, $name, $value, $flags );
+
+Accepts 4 parameters, all are mandatory.  
+
+=over 4
+
+=item C<$key>
+
+Nebulous key to create the xattr on.
+
+=item C<$name>
+
+The xattr's name.
+
+=item C<$value>
+
+The xattr's value.
+
+=item C<$flags>
+
+The xattr key insertion mode flag.  Acceptable value are either C<"create"> or
+C<"replace">.  If the xattr already exists then trying to insert another xattr
+with the same name in C<"create"> mode will fail.
+
+=back
+
+Returns Boolean true on success.  Throws an exception on error.
+
+=item * getxattr_object( $key, $name );
+
+Accepts 2 parameters, all are mandatory.  
+
+=over 4
+
+=item C<$key>
+
+Nebulous key to fetch the xattr from.
+
+=item C<$name>
+
+The xattr's name.
+
+=back
+
+Returns the string value of C<$name> on success.  Throws an exception on error.
+
+=item * listxattr_object( $key );
+
+Accepts 1 mandatory parameter. 
+
+=over 4
+
+=item C<$key>
+
+List all xattrs on this Nebulous key.
+
+=back
+
+Returns an C<ARRAYREF> on success.  Throws an exception on error.
+
+=item * removexattr_object( $key, $name );
+
+Accepts 2 parameters, all are mandatory.  
+
+=over 4
+
+=item C<$key>
+
+Nebulous key to remove the xattr from.
+
+=item C<$name>
+
+The xattr's name.
+
+=back
+
+Returns Boolean true on success.  Throws an exception on error.
+
+=item * find_instance( $key, $volume );
+
+Accepts 2 parameters, only the first is mandatory.  Returns an arrayref.
+Throws an exception on error.
+
+=item * delete_instance( $uri );
+
+Accepts 1 parameters, it is mandatory.  Returns Boolean true.  Throws an
+exception on error.
+
+=item * stat_object( $key );
+
+Accepts 1 parameters, it is mandatory.  Returns a list of:
+
+    so_id
+    ext_id
+    read_lock
+    write_lock
+    epoch
+    mtime
+    number of instances
+
+=item * mounts( $key );
+
+Accepts no parameters and returns an AoA of:
+
+    [
+        [
+            mountpoint,
+            total,
+            used,
+            vol_id,
+            name,
+            path,
+            allocate,
+            available,
+            xattr,
+        ],
+        [ ... ],
+        ...
+    ]
+
+=back
+
+=head1 DEVELOPER NOTES
+
+=head2 REFERENCES
+
+=head1 CREDITS
+
+Just me, myself, and I.
+
+=head1 SUPPORT
+
+Please contact the author directly via e-mail.
+
+=head1 AUTHOR
+
+Joshua Hoblitt <jhoblitt@cpan.org>
+
+=head1 COPYRIGHT
+
+Copyright (C) 2004-2008  Joshua Hoblitt.  All rights reserved.
+
+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.
+
+The full text of the license can be found in the LICENSE file included with
+this module, or in the L<perlgpl> Pod as supplied with Perl 5.8.1 and later.
+
+=head1 SEE ALSO
+
+L<Nebulous::Client>
+
+=cut
Index: /tags/rel-0_10/Nebulous-Server/lib/Nebulous/Server/Apache.pm
===================================================================
--- /tags/rel-0_10/Nebulous-Server/lib/Nebulous/Server/Apache.pm	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/lib/Nebulous/Server/Apache.pm	(revision 17695)
@@ -0,0 +1,25 @@
+# Copyright (C) 2004  Joshua Hoblitt
+#
+# $Id
+
+package Nebulous::Server::Apache;
+
+use strict;
+use warnings FATAL => qw( all );
+
+our $VERSION = 0.01;
+
+use Nebulous::Server::SOAP;
+use SOAP::Transport::HTTP;
+
+my $server = SOAP::Transport::HTTP::Apache
+    ->dispatch_to( 'Nebulous::Server::SOAP' )
+    ->options({ compress_threshold => 10 * 1024 });
+
+no warnings qw( redefine );
+sub handler { $server->handler(@_); }
+use warnings;
+
+1;
+
+__END__
Index: /tags/rel-0_10/Nebulous-Server/lib/Nebulous/Server/Config.pm
===================================================================
--- /tags/rel-0_10/Nebulous-Server/lib/Nebulous/Server/Config.pm	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/lib/Nebulous/Server/Config.pm	(revision 17695)
@@ -0,0 +1,62 @@
+# Copyright (C) 2005  Joshua Hoblitt
+#
+# $Id: Config.pm,v 1.3 2008-03-20 21:10:57 jhoblitt Exp $
+
+package Nebulous::Server::Config;
+
+use strict;
+use warnings FATAL => qw( all );
+
+our $VERSION = 0.02;
+
+use base qw( Class::Accessor::Fast );
+
+use Log::Log4perl qw( :levels );
+use Params::Validate qw( validate SCALAR );
+
+our %LEVELS = (
+    off     => $OFF,
+    fatal   => $FATAL,
+    error   => $ERROR,
+    warn    => $WARN,
+    info    => $INFO,
+    debug   => $DEBUG,
+    all     => $ALL,
+);
+
+my $new_validate = {
+    dsn         => { type => SCALAR },
+    dbuser      => { type => SCALAR },
+    dbpasswd    => { type => SCALAR },
+    log_level   => {
+        type        => SCALAR,
+        optional    => 1,
+        default     => 'all',
+        callbacks   => {
+            'is valid level' => sub {
+                defined $LEVELS{ lc $_[0] };
+            },
+        },
+    },
+};
+
+__PACKAGE__->mk_ro_accessors( keys %$new_validate );
+
+sub init {
+    my $class = shift;
+
+    my %p = validate( @_, $new_validate );
+
+    # normalize log levels to lower-case
+    $p{ log_level } = lc $p{ log_level };
+
+    my $self = \%p;
+
+    bless $self, $class || ref $class;
+
+    return $self;
+}
+
+1;
+
+__END__
Index: /tags/rel-0_10/Nebulous-Server/lib/Nebulous/Server/Log.pm
===================================================================
--- /tags/rel-0_10/Nebulous-Server/lib/Nebulous/Server/Log.pm	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/lib/Nebulous/Server/Log.pm	(revision 17695)
@@ -0,0 +1,62 @@
+# Copyright (c) 2004  Joshua Hoblitt
+#
+# $Id: Log.pm,v 1.7 2008-05-07 00:02:10 jhoblitt Exp $
+
+package Nebulous::Server::Log;
+
+use strict;
+use warnings FATAL => qw( all );
+
+our $VERSION = '0.02';
+
+use Log::Log4perl;
+use Nebulous::Server::Config;
+
+sub init {
+    my ($self, $config) = @_;
+
+    my $dsn         = $config->dsn;
+    my $dbuser      = $config->dbuser;
+    my $dbpasswd    = $config->dbpasswd;
+
+    my $conf = <<END;
+    log4perl.category.Nebulous.Server = WARN, SERVERLOGFILE
+
+    log4perl.appender.SERVERLOGFILE           = Log::Log4perl::Appender::File
+    log4perl.appender.SERVERLOGFILE.filename  = /tmp/nebulous_server.log
+    log4perl.appender.SERVERLOGFILE.mode      = append
+    log4perl.appender.SERVERLOGFILE.layout    = Log::Log4perl::Layout::PatternLayout
+    log4perl.appender.SERVERLOGFILE.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} | %H | %p | %M - %m%n
+#   date | hostname | priority | method/sub - message\n
+
+    log4perl.appender.SQLLOG            = Log::Log4perl::Appender::DBI
+    log4perl.appender.SQLLOG.datasource = $dsn
+    log4perl.appender.SQLLOG.username   = $dbuser
+    log4perl.appender.SQLLOG.password   = $dbpasswd
+    log4perl.appender.SQLLOG.sql        = \
+    INSERT INTO log (timestamp, hostname, level, sub, message) \
+    VALUES          (%d,        %H,       %p,    %M,  %m)
+#        VALUES          (?,         ?,        ?,     ?,   ?)
+#        log4perl.appender.SQLLOG.params.1   = %d
+#        log4perl.appender.SQLLOG.params.2   = %H
+#        log4perl.appender.SQLLOG.params.3   = %p
+#        log4perl.appender.SQLLOG.params.4   = %M
+#        log4perl.appender.SQLLOG.params.5   = %m
+#        log4perl.appender.SQLLOG.usePreparedStmt = 1
+    log4perl.appender.SQLLOG.bufferSize = 2
+    log4perl.appender.SQLLOG.layout     = Log::Log4perl::Layout::NoopLayout
+    log4perl.appender.SQLLOG.warp_message = 0
+END
+
+    if ($INC{'Apache/DBI.pm'} && $ENV{MOD_PERL}) {
+        Log::Log4perl::init_once( \$conf );
+    } else {
+        Log::Log4perl::init( \$conf );
+    }
+
+    return 1;
+}
+
+1;
+
+__END__
Index: /tags/rel-0_10/Nebulous-Server/lib/Nebulous/Server/SOAP.pm
===================================================================
--- /tags/rel-0_10/Nebulous-Server/lib/Nebulous/Server/SOAP.pm	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/lib/Nebulous/Server/SOAP.pm	(revision 17695)
@@ -0,0 +1,78 @@
+# Copyright (c) 2004  Joshua Hoblitt
+#
+# $Id: SOAP.pm,v 1.4 2007-05-02 00:42:52 jhoblitt Exp $
+
+package Nebulous::Server::SOAP;
+
+use strict;
+use warnings FATAL => qw( all );
+
+our $VERSION = '0.02';
+
+import SOAP::Data 'name'; 
+use Apache2::Const -compile => qw(OK);
+use Nebulous::Server;
+use SOAP::Lite;
+
+our $AUTOLOAD;
+
+our @args;
+our $neb;
+
+sub new_on_init {
+    my $self = shift;
+
+    require mod_perl2;
+    require Apache2::Module;
+    require Apache2::ServerUtil;
+
+    @args = @_;
+
+    my $s = Apache2::ServerUtil->server;
+    $s->push_handlers(PerlChildInitHandler => \&init);
+
+    return $self;
+}
+
+sub init {
+    my $self = shift;
+
+    $neb = Nebulous::Server->new(@args);        
+
+    return Apache2::Const::OK;
+}
+
+sub AUTOLOAD {
+    my $self = shift;
+
+    my ( $package, $method ) = $AUTOLOAD =~ m/(?:(.+)::)([^:]+)$/;
+    return undef if $method eq 'DESTROY';
+
+    init() unless defined $neb;
+
+    die "process $$ has not been initialized"
+        unless defined $neb;
+
+    die "method $method does not exist in Nebulous::Server"
+        unless defined ${Nebulous::Server::}{$method};
+
+    # create a sub and install it in the package so successive calls to the
+    # same method don't have to go through AUTOLOAD
+    my $method_sub;
+    eval q|
+    $method_sub = sub {
+        my $self = shift;
+        return name( result => $neb->| . $method . q|( @_ ) );
+    }
+    |;
+    die $@ if $@;
+
+    no strict 'refs';
+    *{"${package}::${method}"} = $method_sub;
+    use strict;
+
+    # invoke the method we just created
+    return $self->$method(@_);
+}
+
+1;
Index: /tags/rel-0_10/Nebulous-Server/lib/Nebulous/Server/SQL.pm
===================================================================
--- /tags/rel-0_10/Nebulous-Server/lib/Nebulous/Server/SQL.pm	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/lib/Nebulous/Server/SQL.pm	(revision 17695)
@@ -0,0 +1,483 @@
+# Copyright (c) 2004  Joshua Hoblitt
+#
+# $Id: SQL.pm,v 1.50 2008-03-21 01:24:36 jhoblitt Exp $
+
+package Nebulous::Server::SQL;
+
+use strict;
+use warnings FATAL => qw( all );
+
+our $VERSION = '0.02';
+
+use base qw( Class::Accessor::Fast );
+
+my %sql = (
+    set_transaction_model   => qq{
+        SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED
+    },
+    update_instance_uri => qq{
+        UPDATE instance
+        SET vol_id = ?, uri = ?
+        WHERE ins_id = ?
+    },
+    last_insert_id      => qq{
+        SELECT LAST_INSERT_ID()
+    },
+    new_object          => qq{
+        INSERT INTO storage_object
+        (so_id, ext_id, type)
+        VALUES (?, ?, 'REG_FILE')
+    },
+    new_object_attr  => qq{
+        INSERT INTO storage_object_attr
+        (so_id, read_lock, write_lock)
+        VALUES (?, 0, NULL)
+    },
+    delete_object       => qq{
+        DELETE FROM storage_object
+        WHERE storage_object.so_id = ?
+    },
+    new_object_instance => qq{
+        INSERT INTO instance 
+        VALUES (NULL, LAST_INSERT_ID(), ?, 'error', NULL, NULL, NULL, NULL)
+    },
+    new_instance        => qq{
+        INSERT INTO instance
+        VALUES (NULL, ?, ?, 'error', NULL, NULL, NULL, NULL)
+    },
+    get_object          => qq{
+        SELECT
+            so_id,
+            ext_id,
+            read_lock,
+            write_lock,
+            epoch,
+            mtime
+        FROM storage_object
+        JOIN storage_object_attr
+        USING (so_id)
+        WHERE ext_id = ?
+    },
+    stat_object          => qq{
+        SELECT
+            storage_object.so_id,
+            storage_object.ext_id,
+            read_lock,
+            write_lock,
+            storage_object_attr.epoch,
+            storage_object_attr.mtime,
+            COUNT(instance.so_id) as instances
+        FROM storage_object
+        JOIN storage_object_attr
+            USING (so_id)
+        JOIN instance
+            USING (so_id)
+        WHERE ext_id = ?
+        GROUP BY storage_object.so_id
+    },
+    # Note: this sets an update lock
+    get_object_locks    => qq{
+        SELECT
+            storage_object.so_id,
+            read_lock,
+            write_lock 
+        FROM storage_object
+        JOIN storage_object_attr
+        USING (so_id)
+        WHERE ext_id = ?
+        FOR UPDATE
+    },
+    new_object_xattr  => qq{
+        INSERT INTO storage_object_xattr
+            SELECT
+                so_id,
+                ?,
+                ?        
+            FROM storage_object
+            WHERE ext_id = ?
+    },
+    replace_object_xattr  => qq{
+        REPLACE INTO storage_object_xattr
+            SELECT
+                so_id,
+                ?,
+                ?        
+            FROM storage_object
+            WHERE ext_id = ?
+    },
+    list_object_xattr    => qq{
+        SELECT storage_object_xattr.name
+        FROM storage_object
+        JOIN storage_object_xattr
+        USING (so_id)
+        WHERE ext_id = ?
+    },
+    get_object_xattr    => qq{
+        SELECT storage_object_xattr.value
+        FROM storage_object
+        JOIN storage_object_xattr
+        USING (so_id)
+        WHERE ext_id = ?
+            AND name = ?
+    },
+    remove_object_xattr    => qq{
+        DELETE FROM storage_object_xattr
+        WHERE so_id = (SELECT so_id from storage_object where ext_id = ?)
+            AND name = ?
+    },
+    set_write_lock      => qq{
+        UPDATE storage_object_attr
+        JOIN storage_object
+        USING(so_id)
+        SET write_lock = 'write'
+        WHERE storage_object.ext_id = ?
+    },
+    delete_write_lock   => qq{
+        UPDATE storage_object_attr
+        JOIN storage_object
+        USING(so_id)
+        SET write_lock = NULL
+        WHERE ext_id = ?
+    },
+    increment_read_lock => qq{
+        UPDATE storage_object_attr
+        JOIN storage_object
+        USING(so_id)
+        SET read_lock = read_lock + 1
+        WHERE ext_id = ?
+    },
+    decrement_read_lock => qq{
+        UPDATE storage_object_attr
+        JOIN storage_object
+        USING(so_id)
+        SET read_lock = read_lock - 1
+        WHERE ext_id = ?
+    },
+    delete_instance     => qq{
+        DELETE FROM instance
+        WHERE uri = ?
+    },
+    get_object_from_uri   => qq{
+        SELECT so_id
+        FROM instance
+        WHERE uri = ?
+    },
+    get_instance_count   => qq{
+        SELECT count(ins_id)
+        FROM instance
+        WHERE so_id = ?
+    },
+    get_object_instances    => qq{
+        SELECT
+            storage_object.so_id,
+            uri,
+            assigned_vol_id,
+            available
+        FROM storage_object
+        JOIN instance
+            USING (so_id)
+        JOIN mountedvol
+            USING(vol_id)
+        WHERE ext_id = ?
+            AND available = ?
+    },
+    get_object_instances_by_vol_name => qq{
+        SELECT
+            storage_object.so_id,
+            uri,
+            assigned_vol_id
+        FROM storage_object
+        JOIN instance
+            USING (so_id)
+        JOIN mountedvol
+            USING(vol_id)
+        WHERE ext_id = ?
+            AND name = ?
+            AND available = ?
+    },
+    get_storage_volume_by_name   => qq{
+        SELECT
+            vol_id,
+            path,
+            xattr,
+            total - used as free
+        FROM mountedvol
+        WHERE
+            used / total < ?
+            AND name = ?
+            AND available = ?
+            AND allocate = ?
+        ORDER BY free DESC
+        LIMIT 1
+    },
+    get_storage_volume          => qq{
+        SELECT
+            vol_id,
+            path,
+            xattr,
+            total - used as free
+        FROM mountedvol
+        WHERE
+            used / total < ?
+            AND available = ?
+            AND allocate = ?
+        ORDER BY free DESC
+        LIMIT 1
+    },
+    new_volume          => qq{
+        INSERT INTO volume (name, path, allocate, available, xattr)
+        VALUES (?, ?, TRUE, TRUE, FALSE)
+    },
+    get_volume_by_name => qq{
+        SELECT vol_id, name, path
+        FROM volume
+        WHERE name = ?
+    },
+    find_objects => qq{
+        SELECT *
+        FROM storage_object
+        WHERE ext_id REGEXP ?
+    },
+    rename_object => qq{
+        UPDATE storage_object
+        SET ext_id = ?
+        WHERE ext_id = ?
+    },
+    find_objects_with_unavailable_instances => qq{
+        SELECT
+            storage_object.so_id,
+            ext_id,
+            count(ins_id) as instances,
+            count(mountedvol.vol_id) as available_instances,
+            count(mountedvol.vol_id) < count(ins_id) as need_recovery,
+            count(mountedvol.vol_id) > 0 as recoverable,
+            storage_object_xattr.value as copies
+        FROM storage_object
+        JOIN storage_object_attr
+            USING(so_id)
+        JOIN instance
+            USING(so_id)
+        LEFT JOIN storage_object_xattr
+            ON storage_object.so_id = storage_object_xattr.so_id
+            AND storage_object_xattr.name = 'copies'
+        LEFT JOIN mountedvol
+            USING(vol_id)
+        GROUP BY so_id
+        HAVING need_recovery = 1
+    },
+    get_mounted_volumes => qq{
+        SELECT * FROM mountedvol ORDER BY vol_id
+    },
+);
+
+{
+    my @schema;
+
+    local $/ = '###';
+
+    foreach my $statement (<DATA>) {
+        last unless ( $statement =~ /\S+/ );
+        $statement =~ s/###//g;
+        push @schema, $statement;
+    }
+
+    $sql{get_db_schema} = \@schema;
+}
+
+{
+    my @clear = split /;/, <<END;
+SET FOREIGN_KEY_CHECKS=0;
+DROP TABLE IF EXISTS storage_object;
+DROP TABLE IF EXISTS storage_object_attr;
+DROP TABLE IF EXISTS storage_object_xattr;
+DROP TABLE IF EXISTS instance;
+DROP TABLE IF EXISTS lock_record;
+DROP TABLE IF EXISTS volume;
+DROP TABLE IF EXISTS mount;
+DROP TABLE IF EXISTS log;
+DROP TABLE IF EXISTS mountedvol;
+DROP PROCEDURE IF EXISTS getmountedvol;
+SET FOREIGN_KEY_CHECKS=1
+END
+    $sql{get_db_clear} = \@clear;
+}
+
+__PACKAGE__->mk_ro_accessors( keys %sql );
+
+sub new {
+    my $class = shift;
+
+    my $self = \%sql;
+
+    bless $self, $class || ref $class;
+
+    return $self;
+}
+
+1;
+
+__DATA__
+CREATE TABLE storage_object (
+    so_id BIGINT NOT NULL AUTO_INCREMENT,
+    ext_id VARCHAR(255) NOT NULL UNIQUE,
+    type enum('REG_FILE'),
+    PRIMARY KEY(so_id),
+    KEY(ext_id(64)),
+    KEY(type)
+) ENGINE=innodb;
+
+###
+
+CREATE TABLE storage_object_attr (
+    so_id BIGINT NOT NULL AUTO_INCREMENT,
+    FOREIGN KEY(so_id) REFERENCES storage_object(so_id) ON DELETE CASCADE,
+    read_lock TINYINT DEFAULT 0 NOT NULL,
+    write_lock ENUM( 'write' ),
+    epoch TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    mtime TIMESTAMP,
+    PRIMARY KEY(so_id)
+) ENGINE=innodb;
+
+###
+
+CREATE TABLE storage_object_xattr (
+    so_id BIGINT NOT NULL AUTO_INCREMENT,
+    FOREIGN KEY(so_id) REFERENCES storage_object(so_id) ON DELETE CASCADE,
+    name VARCHAR(255),
+    value BLOB,
+    PRIMARY KEY(so_id, name)
+) ENGINE=innodb;
+
+###
+
+CREATE TABLE lock_record (
+    so_id BIGINT NOT NULL,
+    FOREIGN KEY(so_id) REFERENCES storage_object(so_id),
+    type ENUM( 'read', 'write' ) NOT NULL,
+    epoch TIMESTAMP,
+    KEY(so_ID)
+) ENGINE=innodb;
+
+###
+
+CREATE TABLE volume (
+    vol_id INT NOT NULL AUTO_INCREMENT,
+    name VARCHAR(255) UNIQUE NOT NULL,
+    path VARCHAR(255) NOT NULL,
+    allocate BOOLEAN DEFAULT FALSE,
+    available BOOLEAN DEFAULT FALSE,
+    xattr BOOLEAN DEFAULT FALSE,
+    PRIMARY KEY(vol_id),
+    KEY(name(16)),
+    KEY(path(255)),
+    KEY(allocate),
+    KEY(available)
+) ENGINE=innodb;
+
+###
+
+CREATE TABLE instance (
+    ins_id BIGINT NOT NULL AUTO_INCREMENT,
+    so_id BIGINT NOT NULL,
+    FOREIGN KEY(so_id) REFERENCES storage_object(so_id),
+    vol_id INT NOT NULL,
+    FOREIGN KEY(vol_id) REFERENCES volume(vol_id),
+    uri VARCHAR(255) NOT NULL UNIQUE,
+    sha1sum CHAR(40) ASCII,
+    assigned_vol_id INT,
+    epoch TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    mtime TIMESTAMP,
+    PRIMARY KEY(ins_id),
+    KEY(so_id),
+    KEY(vol_id),
+    KEY(uri(64))
+) ENGINE=innodb;
+
+###
+
+CREATE TABLE mount (
+    mountpoint VARCHAR(255) NOT NULL,
+    total BIGINT NOT NULL,
+    used BIGINT NOT NULL,
+    PRIMARY KEY(mountpoint)
+) ENGINE=innodb;
+
+###
+
+CREATE TABLE log (
+    timestamp TIMESTAMP,
+    hostname VARCHAR(255),
+    level VARCHAR(255),
+    sub VARCHAR(255),
+    message VARCHAR(2048) NOT NULL,
+    PRIMARY KEY(timestamp)
+) ENGINE=innodb;
+
+###
+
+CREATE TABLE mountedvol(
+    mountpoint VARCHAR(255) NOT NULL,
+    FOREIGN KEY(mountpoint) REFERENCES mount(mountpoint) ON DELETE CASCADE,
+    total BIGINT NOT NULL,
+    used BIGINT NOT NULL,
+    vol_id INT NOT NULL,
+    FOREIGN KEY(vol_id) REFERENCES volume(vol_id) ON DELETE CASCADE,
+    name VARCHAR(255) NOT NULL,
+    path VARCHAR(255) NOT NULL,
+    FOREIGN KEY(path) REFERENCES volume(path) ON DELETE CASCADE,
+    allocate BOOLEAN DEFAULT FALSE,
+    available BOOLEAN DEFAULT FALSE,
+    xattr BOOLEAN DEFAULT FALSE,
+    KEY(vol_id),
+    KEY(allocate),
+    KEY(available)
+) ENGINE=innodb;
+
+###
+
+CREATE PROCEDURE getmountedvol() DETERMINISTIC
+BEGIN
+    DECLARE done BOOLEAN DEFAULT FALSE;
+    DECLARE vol_idvar INT;
+    DECLARE namevar VARCHAR(255);
+    DECLARE pathvar VARCHAR(255);
+    DECLARE allocatevar BOOLEAN;
+    DECLARE availablevar BOOLEAN;
+    DECLARE xattrvar BOOLEAN;
+    DECLARE trans_level VARCHAR(255);
+    DECLARE cur1 CURSOR FOR SELECT vol_id, name, path, allocate, available, xattr FROM volume;
+    DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = TRUE;
+
+    -- store the current transaction level
+    SELECT @@session.tx_isolation INTO trans_level; 
+
+    -- set trans level to repeatable-read so the volume table does not change
+    -- out from under our cursor
+    SET @@session.tx_isolation = 'REPEATABLE-READ';
+
+    -- iterate over the volume table finding the coresponding entry in the
+    -- mount table and inserting union of the volume & mount row into the
+    -- mountedvol table
+    OPEN cur1;
+
+    DELETE FROM mountedvol;
+
+    myloop: LOOP
+        FETCH cur1 INTO vol_idvar, namevar, pathvar, allocatevar, availablevar, xattrvar;
+        IF `done` THEN LEAVE myloop; END IF;
+        INSERT INTO mountedvol
+            SELECT mountpoint, total, used, vol_idvar, namevar, pathvar, allocatevar, availablevar, xattrvar
+            FROM
+                (SELECT *, INSTR(pathvar, mountpoint) = 1 as substring
+                FROM mount
+                ORDER BY substring DESC, LENGTH(mountpoint) DESC
+                LIMIT 1) as bar;
+    END LOOP myloop;
+    
+    CLOSE cur1;
+
+    -- restore the original transaction level
+    SET @@session.tx_isolation = trans_level;
+
+    COMMIT;
+END 
Index: /tags/rel-0_10/Nebulous-Server/lib/Test/Nebulous.pm
===================================================================
--- /tags/rel-0_10/Nebulous-Server/lib/Test/Nebulous.pm	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/lib/Test/Nebulous.pm	(revision 17695)
@@ -0,0 +1,94 @@
+# Copyright (C) 2004  Joshua Hoblitt
+#
+# $Id: Nebulous.pm,v 1.1 2008-03-20 21:13:28 jhoblitt Exp $
+
+package Test::Nebulous;
+
+use strict;
+
+our $VERSION = '0.01';
+
+use base qw( Exporter );
+
+use DBI;
+use File::Path qw( mkpath rmtree );
+use File::Temp qw( tempdir );
+use Nebulous::Server::SQL;
+
+our @EXPORT = qw( $NEB_DB $NEB_USER $NEB_PASS );
+
+our $NEB_DB     = $ENV{'NEB_DB'}   || "DBI:mysql:database=test:host=localhost";
+our $NEB_USER   = $ENV{'NEB_USER'} || "test";
+our $NEB_PASS   = $ENV{'NEB_PASS'} || '';
+
+my $dbh = DBI->connect( $NEB_DB, $NEB_USER, $NEB_PASS );
+my $sql = Nebulous::Server::SQL->new;
+
+# suppress uninitalized warnings
+my $dir1 = "";
+my $dir2 = "";
+my $dir3 = "";
+my $dir4 = "";
+my $dir5 = "";
+my $dir6 = "";
+
+sub setup {
+    my $self = shift;
+
+    $self->cleanup;
+
+    # create directories after cleanup
+    $dir1 = tempdir( CLEANUP => 0 );
+    $dir2 = tempdir( CLEANUP => 0 );
+    $dir3 = tempdir( CLEANUP => 0 );
+    $dir4 = tempdir( CLEANUP => 0 );
+    $dir5 = tempdir( CLEANUP => 0 );
+    $dir6 = tempdir( CLEANUP => 0 );
+
+    foreach my $statement (@{ $sql->get_db_schema }) {
+        $dbh->do( $statement );
+    }
+
+    # node01/node02: allocate = TRUE
+    $dbh->do(qq{ INSERT INTO volume (vol_id, name, path, allocate, available) VALUES (1, 'node01', ?, TRUE, TRUE) }, undef, $dir1);
+    $dbh->do(qq{ INSERT INTO mount VALUES (?, 10e10, 10e7) }, undef, $dir1);
+
+    $dbh->do(qq{ INSERT INTO volume (vol_id, name, path, allocate, available) VALUES (2, 'node02', ?, TRUE, TRUE) }, undef, $dir2);
+    $dbh->do(qq{ INSERT INTO mount VALUES (?, 10e10, 10e8) }, undef, $dir2);
+
+    # node03: allocate = TRUE, volume full
+    $dbh->do(qq{ INSERT INTO volume (vol_id, name, path, allocate, available) VALUES (3, 'node03', ?, TRUE, TRUE) }, undef, $dir3);
+    $dbh->do(qq{ INSERT INTO mount VALUES (?, 10e10, 10e10) }, undef, $dir3);
+
+    # node04: allocate = FALSE, available = FALSE
+    $dbh->do(qq{ INSERT INTO volume (vol_id, name, path, allocate, available) VALUES (4, 'node04', ?, FALSE, FALSE) }, undef, $dir4);
+    $dbh->do(qq{ INSERT INTO mount VALUES (?, 10e10, 10e7) }, undef, $dir4);
+
+    # node05: allocate = FALSE, available = TRUE
+    $dbh->do(qq{ INSERT INTO volume (vol_id, name, path, allocate, available) VALUES (5, 'node05', ?, FALSE, TRUE) }, undef, $dir5);
+    $dbh->do(qq{ INSERT INTO mount VALUES (?, 10e10, 10e7) }, undef, $dir5);
+
+    # node06: allocate = TRUE, available = FALSE
+    $dbh->do(qq{ INSERT INTO volume (vol_id, name, path, allocate, available) VALUES (6, 'node06', ?, TRUE, FALSE) }, undef, $dir4);
+    $dbh->do(qq{ INSERT INTO mount VALUES (?, 10e10, 10e7) }, undef, $dir6);
+
+    $dbh->do(qq{ call getmountedvol() });
+}
+
+sub cleanup {
+    foreach my $statement (@{ $sql->get_db_clear }) {
+        $dbh->do( $statement );
+    }
+
+    # on the first call to setup the $dir[12] will be ""
+    rmtree([$dir1], 0, 1) if -e $dir1;
+    rmtree([$dir2], 0, 1) if -e $dir2;
+    rmtree([$dir3], 0, 1) if -e $dir3;
+    rmtree([$dir4], 0, 1) if -e $dir4;
+    rmtree([$dir5], 0, 1) if -e $dir5;
+    rmtree([$dir6], 0, 1) if -e $dir6;
+}
+
+1;
+
+__END__
Index: /tags/rel-0_10/Nebulous-Server/scripts/bench_test.pl
===================================================================
--- /tags/rel-0_10/Nebulous-Server/scripts/bench_test.pl	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/scripts/bench_test.pl	(revision 17695)
@@ -0,0 +1,41 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings FATAL => qw( all );
+
+use lib "./lib";
+
+use Benchmark qw( timethese );
+use Nebulous::Client;
+
+my $neb = Nebulous::Client->new(
+    proxy   => 'http://localhost:80/nebulous'
+);
+
+my $key = shift || 'foobar';
+
+eval { $neb->delete( $key ); };
+
+my $fh = $neb->create( $key );
+close $fh;
+
+
+timethese( -3,
+    {
+        'stat' => sub {
+            $neb->stat( $key );
+        },
+    }
+);
+
+$neb->delete( $key );
+
+timethese( -3,
+    {
+        'create/delete' => sub {
+            my $fh = $neb->create( $key );
+            close $fh;
+            $neb->delete( $key );
+        },
+    }
+);
Index: /tags/rel-0_10/Nebulous-Server/scripts/nebulous.cgi
===================================================================
--- /tags/rel-0_10/Nebulous-Server/scripts/nebulous.cgi	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/scripts/nebulous.cgi	(revision 17695)
@@ -0,0 +1,15 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2004  Joshua Hoblitt
+#
+# $Id
+ 
+use strict;
+use warnings FATAL => qw( all );
+
+use SOAP::Transport::HTTP;
+use Nebulous::Server;
+   
+SOAP::Transport::HTTP::CGI
+    -> dispatch_to( 'Nebulous::Server' )
+    -> handle;
Index: /tags/rel-0_10/Nebulous-Server/scripts/ptest.pl
===================================================================
--- /tags/rel-0_10/Nebulous-Server/scripts/ptest.pl	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/scripts/ptest.pl	(revision 17695)
@@ -0,0 +1,49 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings FATAL => qw( all );
+
+use lib "./lib";
+
+use Nebulous::Client;
+
+my $neb = Nebulous::Client->new(
+    proxy   => 'http://localhost:80/nebulous'
+);
+
+my $key = shift || 'foobar';
+my $kids = shift || 1;
+
+foreach my $id ( 1..$kids ) {
+    my $pid = fork;
+
+    unless ( $pid )  {
+        my $fname = "${key}_$id";
+        my $fh = $neb->open_create( $fname );
+        die "can't create file $fname" unless $fh;
+
+        print $fh "fooby\n";
+
+        close $fh;
+
+        $fh = $neb->open( $fname, 'read' ) or die "can't open file";
+        close $fh;
+
+        $neb->lock( $fname, 'read' );
+        $neb->unlock( $fname, 'read' );
+        $neb->replicate( $fname );
+        $neb->cull( $fname );
+        $neb->find( $fname );
+        $neb->copy( $fname, $fname . "_copy" );
+        $neb->move( $fname, $fname . "_move" );
+        $neb->delete( $fname . "_copy" );
+        $neb->delete( $fname . "_move" );
+
+        exit 0;
+    }
+}
+
+while ( $kids ) {
+    wait();
+    $kids --;
+}
Index: /tags/rel-0_10/Nebulous-Server/t/.cvsignore
===================================================================
--- /tags/rel-0_10/Nebulous-Server/t/.cvsignore	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/t/.cvsignore	(revision 17695)
@@ -0,0 +1,3 @@
+TEST
+htdocs
+logs
Index: /tags/rel-0_10/Nebulous-Server/t/00_distribution.t
===================================================================
--- /tags/rel-0_10/Nebulous-Server/t/00_distribution.t	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/t/00_distribution.t	(revision 17695)
@@ -0,0 +1,25 @@
+#!/usr/bin/env perl
+
+# Copyright (C) 2005  Joshua Hoblitt
+#
+# $Id: 00_distribution.t,v 1.2 2007-05-02 01:07:56 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use lib qw( ./lib ./t );
+
+use Test::More;
+
+# example taken from Test::Distribution Pod
+
+BEGIN {
+    eval {
+        require Test::Distribution;
+    };
+    if($@) {
+        plan skip_all => 'Test::Distribution not installed';
+    } else {
+        import Test::Distribution not => [qw( versions use podcover )];
+    }
+}
Index: /tags/rel-0_10/Nebulous-Server/t/01_load.t
===================================================================
--- /tags/rel-0_10/Nebulous-Server/t/01_load.t	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/t/01_load.t	(revision 17695)
@@ -0,0 +1,18 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2004-2005  Joshua Hoblitt
+#
+# $Id: 01_load.t,v 1.9 2008-03-21 01:24:00 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use lib qw( ./t ./lib );
+
+use Test::More tests => 5;
+
+BEGIN { use_ok( 'Nebulous::Keys' ); }
+BEGIN { use_ok( 'Nebulous::Server' ); }
+BEGIN { use_ok( 'Nebulous::Server::Log' ); }
+BEGIN { use_ok( 'Nebulous::Server::SOAP' ); }
+BEGIN { use_ok( 'Nebulous::Server::SQL' ); }
Index: /tags/rel-0_10/Nebulous-Server/t/02_server_setup.t
===================================================================
--- /tags/rel-0_10/Nebulous-Server/t/02_server_setup.t	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/t/02_server_setup.t	(revision 17695)
@@ -0,0 +1,39 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2004-2005  Joshua Hoblitt
+#
+# $Id: 02_server_setup.t,v 1.6 2008-02-02 01:51:29 jhoblitt Exp $
+
+use strict;
+use warnings;
+
+use Test::More tests => 2;
+
+use lib qw( ./t ./lib );
+
+use Nebulous::Server;
+use Test::Nebulous;
+
+Test::Nebulous->setup;
+
+isa_ok(
+    Nebulous::Server->new(
+        dsn         => $NEB_DB,
+        dbuser      => $NEB_USER,
+        dbpasswd    => $NEB_PASS,
+    ),
+    "Nebulous::Server"
+);
+
+Test::Nebulous->setup;
+
+eval {
+    Nebulous::Server->new(
+        dsn         => "DBI:mysql:database=foobar:host=localhost",
+        dbuser      => "baz",
+        dbpasswd    =>"boo",
+    );
+};
+like( $@, qr/DBI connect.*? failed/, "bad dsn/user/pass" );
+
+Test::Nebulous->cleanup;
Index: /tags/rel-0_10/Nebulous-Server/t/03_server_create_object.t
===================================================================
--- /tags/rel-0_10/Nebulous-Server/t/03_server_create_object.t	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/t/03_server_create_object.t	(revision 17695)
@@ -0,0 +1,364 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2004-2005  Joshua Hoblitt
+#
+# $Id: 03_server_create_object.t,v 1.26 2008-05-06 22:10:58 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use Test::More tests => 70;
+
+use lib qw( ./t ./lib );
+
+use File::ExtAttr qw( getfattr );
+use Nebulous::Server;
+use Test::Nebulous;
+use Test::URI;
+use URI::Split qw( uri_split );
+
+my $test_xattr = undef;
+
+my $neb = Nebulous::Server->new(
+    dsn         => $NEB_DB,
+    dbuser      => $NEB_USER,
+    dbpasswd    => $NEB_PASS,
+);
+
+Test::Nebulous->setup;
+
+{
+    # key
+    my $uri = $neb->create_object("foo");
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    ok($neb->find_instances("foo"), 'object key exists');
+    uri_scheme_ok($uri, 'file');
+
+SKIP: {
+    skip "requires xattr support", 1 unless $test_xattr;
+    is(getfattr($path, 'user.nebulous_key'), 'foo', 'user.nebulous_key xattr');
+}
+}
+
+Test::Nebulous->setup;
+
+{
+    # key
+    my $uri = $neb->create_object("neb:/foo");
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    ok($neb->find_instances("foo"), 'object key exists');
+    uri_scheme_ok($uri, 'file');
+
+SKIP: {
+    skip "requires xattr support", 1 unless $test_xattr;
+    is(getfattr($path, 'user.nebulous_key'), 'foo', 'user.nebulous_key xattr');
+}
+}
+
+Test::Nebulous->setup;
+
+{
+    # key
+    my $uri = $neb->create_object("neb://node01/foo");
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    ok($neb->find_instances("foo"), 'object key exists');
+    uri_scheme_ok($uri, 'file');
+
+SKIP: {
+    skip "requires xattr support", 1 unless $test_xattr;
+    is(getfattr($path, 'user.nebulous_key'), 'foo', 'user.nebulous_key xattr');
+}
+}
+
+Test::Nebulous->setup;
+
+{
+    # key
+    my $uri = $neb->create_object("neb://node02/foo");
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    ok($neb->find_instances("foo"), 'object key exists');
+    uri_scheme_ok($uri, 'file');
+
+SKIP: {
+    skip "requires xattr support", 1 unless $test_xattr;
+    is(getfattr($path, 'user.nebulous_key'), 'foo', 'user.nebulous_key xattr');
+}
+}
+
+Test::Nebulous->setup;
+
+{
+    # key
+    my $uri = $neb->create_object("/foo");
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    ok($neb->find_instances("foo"), 'object key exists');
+    uri_scheme_ok($uri, 'file');
+
+SKIP: {
+    skip "requires xattr support", 1 unless $test_xattr;
+    is(getfattr($path, 'user.nebulous_key'), '/foo', 'user.nebulous_key xattr');
+}
+}
+
+Test::Nebulous->setup;
+{
+    # key
+    my $uri = $neb->create_object("neb:///foo");
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    ok($neb->find_instances("foo"), 'object key exists');
+    uri_scheme_ok($uri, 'file');
+
+SKIP: {
+    skip "requires xattr support", 1 unless $test_xattr;
+    is(getfattr($path, 'user.nebulous_key'), 'foo', 'user.nebulous_key xattr');
+}
+}
+
+Test::Nebulous->setup;
+
+{
+    # key
+    my $uri = $neb->create_object("/foo/");
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    ok($neb->find_instances("foo"), 'object key exists');
+    uri_scheme_ok($uri, 'file');
+
+SKIP: {
+    skip "requires xattr support", 1 unless $test_xattr;
+    is(getfattr($path, 'user.nebulous_key'), '/foo/', 'user.nebulous_key xattr');
+}
+}
+
+Test::Nebulous->setup;
+
+{
+    # key
+    my $uri = $neb->create_object("neb:/foo/");
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    ok($neb->find_instances("foo"), 'object key exists');
+    uri_scheme_ok($uri, 'file');
+
+SKIP: {
+    skip "requires xattr support", 1 unless $test_xattr;
+    is(getfattr($path, 'user.nebulous_key'), '/foo/', 'user.nebulous_key xattr');
+}
+}
+
+Test::Nebulous->setup;
+
+{
+    # key
+    my $uri = $neb->create_object("foo/");
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    ok($neb->find_instances("foo"), 'object key exists');
+    uri_scheme_ok($uri, 'file');
+
+SKIP: {
+    skip "requires xattr support", 1 unless $test_xattr;
+    is(getfattr($path, 'user.nebulous_key'), 'foo/', 'user.nebulous_key xattr');
+}
+}
+
+Test::Nebulous->setup;
+
+{
+    # key
+    my $uri = $neb->create_object("foo/bar");
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    ok($neb->find_instances("foo/bar"), 'object key exists');
+    uri_scheme_ok($uri, 'file');
+
+SKIP: {
+    skip "requires xattr support", 1 unless $test_xattr;
+    is(getfattr($path, 'user.nebulous_key'), 'foo/bar', 'user.nebulous_key xattr');
+}
+}
+
+Test::Nebulous->setup;
+
+{
+    # key
+    my $uri = $neb->create_object("/foo/bar");
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    ok($neb->find_instances("foo/bar"), 'object key exists');
+    uri_scheme_ok($uri, 'file');
+
+SKIP: {
+    skip "requires xattr support", 1 unless $test_xattr;
+    is(getfattr($path, 'user.nebulous_key'), '/foo/bar', 'user.nebulous_key xattr');
+}
+}
+
+Test::Nebulous->setup;
+
+{
+    # key, volume
+    my $uri = $neb->create_object("foo", "node01");
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    ok($neb->find_instances("foo"), 'object key exists');
+    uri_scheme_ok($uri, 'file');
+}
+
+Test::Nebulous->setup;
+
+{
+    # volume name override
+    my $uri = $neb->create_object("neb://node02/foo", "node01");
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    ok($neb->find_instances("foo"), 'object key exists');
+    uri_scheme_ok($uri, 'file');
+}
+
+Test::Nebulous->setup;
+
+{
+    # volume name override 
+    # OK because the volume arg overrides the key's  implied volume
+    my $uri = $neb->create_object("neb://99/foo", "node01");
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    ok($neb->find_instances("foo"), 'object key exists');
+    uri_scheme_ok($uri, 'file');
+}
+
+Test::Nebulous->setup;
+
+{
+    # undef volume name
+    # OK because the undef is ignored
+    my $uri = $neb->create_object("neb://node01/foo", undef);
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    ok($neb->find_instances("foo"), 'object key exists');
+    uri_scheme_ok($uri, 'file');
+}
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+    $neb->create_object("foo");
+};
+like($@, qr/Duplicate entry/, "object already exists");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("neb:/foo");
+    $neb->create_object("neb:/foo");
+};
+like($@, qr/Duplicate entry/, "object already exists");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo", 'node03');
+};
+like($@, qr/node03 is not available/, "request volume this is full");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("neb://node03/foo");
+};
+like($@, qr/node03 is not available/, "request volume this is full");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo", 'node04');
+};
+like($@, qr/node04 is not available/, "request volume with allocate = FALSE, available = FALSE");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("neb://node04/foo");
+};
+like($@, qr/node04 is not available/, "request volume with allocate = FALSE, available = FALSE");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo", 'node05');
+};
+like($@, qr/node05 is not available/, "request volume with allocate = FALSE , available = TRUE");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("neb://node05/foo");
+};
+like($@, qr/node05 is not available/, "request volume with allocate = FALSE , available = TRUE");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo", 'node06');
+};
+like($@, qr/node06 is not available/, "request volume with allocate = TRUE, available = FALSE");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("neb://node06/foo");
+};
+like($@, qr/node06 is not available/, "request volume with allocate = TRUE, available = FALSE");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo", 99);
+};
+like($@, qr/did not pass the \'is valid volume name\'/, "volume name doesn't exist");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("neb://99/foo");
+};
+like($@, qr/is not a valid volume name/, "volume name doesn't exist");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object();
+};
+like($@, qr/1 - 2 were expected/, "no params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object(1, "node01", 3);
+};
+like($@, qr/1 - 2 were expected/, "too many params");
+
+Test::Nebulous->cleanup;
Index: /tags/rel-0_10/Nebulous-Server/t/04_server_replicate_object.t
===================================================================
--- /tags/rel-0_10/Nebulous-Server/t/04_server_replicate_object.t	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/t/04_server_replicate_object.t	(revision 17695)
@@ -0,0 +1,140 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2004-2005  Joshua Hoblitt
+#
+# $Id: 04_server_replicate_object.t,v 1.13 2008-05-06 22:10:58 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use Test::More tests => 19;
+
+use lib qw( ./t ./lib );
+
+use File::ExtAttr qw( getfattr );
+use Nebulous::Server;
+use Test::Nebulous;
+use Test::URI;
+use URI::Split qw( uri_split );
+
+my $test_xattr = undef;
+
+my $neb = Nebulous::Server->new(
+    dsn         => $NEB_DB,
+    dbuser      => $NEB_USER,
+    dbpasswd    => $NEB_PASS,
+);
+
+Test::Nebulous->setup;
+
+{
+    # key
+    $neb->create_object("foo");
+    my $uri = $neb->replicate_object("foo");
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    uri_scheme_ok($uri, 'file');
+
+SKIP: {
+    skip "requires xattr support", 1 unless $test_xattr;
+    is(getfattr($path, 'user.nebulous_key'), 'foo', 'user.nebulous_key xattr');
+}
+}
+
+Test::Nebulous->setup;
+
+{
+    # key, $volume
+    $neb->create_object("foo");
+    my $uri = $neb->replicate_object("foo", "node01");
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    uri_scheme_ok($uri, 'file');
+
+SKIP: {
+    skip "requires xattr support", 1 unless $test_xattr;
+    is(getfattr($path, 'user.nebulous_key'), 'foo', 'user.nebulous_key xattr');
+}
+}
+
+Test::Nebulous->setup;
+
+{
+    # key, $volume is undef
+    $neb->create_object("foo");
+    my $uri = $neb->replicate_object("foo", undef);
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    uri_scheme_ok($uri, 'file');
+
+SKIP: {
+    skip "requires xattr support", 1 unless $test_xattr;
+    is(getfattr($path, 'user.nebulous_key'), 'foo', 'user.nebulous_key xattr');
+}
+}
+
+Test::Nebulous->setup;
+
+{
+    # key, $volume
+    $neb->create_object("foo");
+    my $uri1 = $neb->replicate_object("foo", "node01");
+    my $uri2 = $neb->replicate_object("foo", "node02");
+
+    {
+        my ($scheme, $auth, $path, $query, $frag) = uri_split($uri1);
+        ok(-e $path, "file exists");
+        uri_scheme_ok($uri1, 'file');
+
+SKIP: {
+    skip "requires xattr support", 1 unless $test_xattr;
+        is(getfattr($path, 'user.nebulous_key'), 'foo', 'user.nebulous_key xattr');
+}
+    }
+
+    {
+        my ($scheme, $auth, $path, $query, $frag) = uri_split($uri2);
+        ok(-e $path, "file exists");
+        uri_scheme_ok($uri2, 'file');
+
+SKIP: {
+    skip "requires xattr support", 1 unless $test_xattr;
+        is(getfattr($path, 'user.nebulous_key'), 'foo', 'user.nebulous_key xattr');
+}
+    }
+}
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->replicate_object('foo');
+};
+like($@, qr/is valid object key/, 'storage object does not exist');
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object('foo');
+    $neb->replicate_object('foo', 'bar');
+};
+like($@, qr/is valid volume name/, 'storage volume does not exist');
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->replicate_object();
+};
+like($@, qr/1 - 2 were expected/, 'no params');
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object('foo');
+    $neb->replicate_object('foo', 'node01', 3);
+};
+like($@, qr/1 - 2 were expected/, 'too many params');
+
+Test::Nebulous->cleanup;
Index: /tags/rel-0_10/Nebulous-Server/t/05_server_lock_object.t
===================================================================
--- /tags/rel-0_10/Nebulous-Server/t/05_server_lock_object.t	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/t/05_server_lock_object.t	(revision 17695)
@@ -0,0 +1,126 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2004-2005  Joshua Hoblitt
+#
+# $Id: 05_server_lock_object.t,v 1.10 2008-03-20 21:10:14 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use Test::More tests => 13;
+
+use lib qw( ./t ./lib );
+
+use Nebulous::Server;
+use Test::Nebulous;
+
+my $neb = Nebulous::Server->new(
+    dsn         => $NEB_DB,
+    dbuser      => $NEB_USER,
+    dbpasswd    => $NEB_PASS,
+);
+
+Test::Nebulous->setup;
+
+{
+    $neb->create_object("foo");
+
+    ok($neb->lock_object("foo", "read"), "read lock");
+}
+
+Test::Nebulous->setup;
+
+{
+    $neb->create_object("foo");
+
+    ok($neb->lock_object("foo", "read"), "read lock");
+    ok($neb->lock_object("foo", "read"), "read lock");
+}
+
+Test::Nebulous->setup;
+
+{
+    $neb->create_object("foo");
+
+    ok($neb->lock_object("foo", "write"), "write lock");
+}
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->lock_object("foo", "read");
+};
+like($@, qr/is valid object key/, "storage object does not exist");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->lock_object("foo", "write");
+};
+like($@, qr/is valid object key/, "storage object does not exist");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+
+    $neb->lock_object("foo", "write");
+    $neb->lock_object("foo", "write");
+};
+like($@, qr/can not write lock twice/, "can not write lock twice");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+
+    $neb->lock_object("foo", "read");
+    $neb->lock_object("foo", "write");
+};
+like($@, qr/can not write lock after read lock/, "can not write lock after read lock");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+
+    $neb->lock_object("foo", "write");
+    $neb->lock_object("foo", "read");
+};
+like($@, qr/can not read lock after write lock/, "can not read lock after write lock");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->lock_object();
+};
+like($@, qr/2 were expected/, "no params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+
+    $neb->lock_object("foo");
+};
+like($@, qr/2 were expected/, "not enough params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+
+    $neb->lock_object("foo", "both");
+};
+like($@, qr/is read or write/, "not read or write");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+
+    $neb->lock_object("foo", 'read', 3);
+};
+like($@, qr/2 were expected/, "too many params");
+
+Test::Nebulous->cleanup;
Index: /tags/rel-0_10/Nebulous-Server/t/06_server_unlock_object.t
===================================================================
--- /tags/rel-0_10/Nebulous-Server/t/06_server_unlock_object.t	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/t/06_server_unlock_object.t	(revision 17695)
@@ -0,0 +1,141 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2004-2005  Joshua Hoblitt
+#
+# $Id: 06_server_unlock_object.t,v 1.10 2008-03-20 21:10:14 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use Test::More tests => 14;
+
+use lib qw( ./t ./lib );
+
+use Nebulous::Server;
+use Test::Nebulous;
+
+my $neb = Nebulous::Server->new(
+    dsn         => $NEB_DB,
+    dbuser      => $NEB_USER,
+    dbpasswd    => $NEB_PASS,
+);
+
+Test::Nebulous->setup;
+
+{
+    $neb->create_object("foo");
+
+    $neb->lock_object("foo", "read");
+
+    ok($neb->unlock_object("foo", "read"), "read unlock");
+}
+
+Test::Nebulous->setup;
+
+{
+    $neb->create_object("foo");
+
+    $neb->lock_object("foo", "read");
+    $neb->lock_object("foo", "read");
+
+    ok($neb->unlock_object("foo", "read"), "read unlock");
+    ok($neb->unlock_object("foo", "read"), "read unlock");
+}
+
+Test::Nebulous->setup;
+
+{
+    $neb->create_object("foo");
+
+    $neb->lock_object("foo", "write");
+
+    ok($neb->unlock_object("foo", "write"), "write unlock");
+}
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->unlock_object("foo", "read");
+};
+like($@, qr/is valid object key/, "storage object does not exist");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->unlock_object("foo", "write");
+};
+like($@, qr/is valid object key/, "storage object does not exist");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+
+    $neb->unlock_object("foo", "read");
+};
+like($@, qr/can not remove non-existant read lock/, "no lock set");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+
+    $neb->unlock_object("foo", "write");
+};
+like($@, qr/can not remove non-existant write lock/, "no lock set");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+    $neb->lock_object("foo", "write");
+
+    $neb->unlock_object("foo", "read");
+};
+like($@, qr/can not have a read lock under a write lock/, "read unlock under write lock");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+    $neb->lock_object("foo", "read");
+
+    $neb->unlock_object("foo", "write");
+};
+like($@, qr/can not have a write lock under a read lock/, "write unlock under read lock");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->unlock_object();
+};
+like($@, qr/2 were expected/, "no params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+
+    $neb->unlock_object("foo");
+};
+like($@, qr/2 were expected/, "not enough params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+
+    $neb->unlock_object("foo", "both");
+};
+like($@, qr/is read or write/, "not read or write");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+
+    $neb->unlock_object("foo", 'read', 3);
+};
+like($@, qr/2 were expected/, "too many params");
+
+Test::Nebulous->cleanup;
Index: /tags/rel-0_10/Nebulous-Server/t/07_server_find_instances.t
===================================================================
--- /tags/rel-0_10/Nebulous-Server/t/07_server_find_instances.t	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/t/07_server_find_instances.t	(revision 17695)
@@ -0,0 +1,130 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2004-2005  Joshua Hoblitt
+#
+# $Id: 07_server_find_instances.t,v 1.11 2008-05-06 22:10:58 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use Test::More tests => 17;
+
+use lib qw( ./t ./lib );
+
+use Nebulous::Server;
+use Test::URI;
+use Test::Nebulous;
+
+my $neb = Nebulous::Server->new(
+    dsn         => $NEB_DB,
+    dbuser      => $NEB_USER,
+    dbpasswd    => $NEB_PASS,
+);
+
+Test::Nebulous->setup;
+
+{
+    # key
+    my $uri = $neb->create_object("foo");
+
+    my $locations = $neb->find_instances("foo");
+
+    uri_scheme_ok($locations->[0], 'file');
+    is($uri, $locations->[0], "URIs match");
+}
+
+Test::Nebulous->setup;
+
+{
+    # key
+    my $uri1 = $neb->create_object("foo");
+    my $uri2 = $neb->replicate_object("foo");
+
+    my $locations = $neb->find_instances("foo");
+
+    uri_scheme_ok($locations->[0], 'file');
+    uri_scheme_ok($locations->[1], 'file');
+    ok(eq_set([$uri1, $uri2], $locations), "URIs match");
+}
+
+Test::Nebulous->setup;
+
+{
+    # key, volume
+    my $uri = $neb->create_object('foo', 'node01');
+
+    my $locations = $neb->find_instances('foo', 'node01');
+
+    uri_scheme_ok($locations->[0], 'file');
+    is($uri, $locations->[0], "URIs match");
+}
+
+Test::Nebulous->setup;
+
+{
+    # key, volume
+    my $uri = $neb->create_object('foo', 'node01');
+
+    my $locations = $neb->find_instances('foo', undef);
+
+    uri_scheme_ok($locations->[0], 'file');
+    is($uri, $locations->[0], "URIs match");
+}
+
+Test::Nebulous->setup;
+
+{
+    # key
+    my $uri1 = $neb->create_object("foo");
+    my $uri2 = $neb->replicate_object("foo");
+
+    my $locations = $neb->find_instances("foo", "node01");
+
+    uri_scheme_ok($locations->[0], 'file');
+    uri_scheme_ok($locations->[1], 'file');
+    ok(eq_set([$uri1, $uri2], $locations), "URIs match");
+}
+
+# object exists but instance is on a different volume
+Test::Nebulous->setup;
+
+eval {
+    # key, volume
+    my $uri = $neb->create_object('foo', 'node01');
+
+    my $locations = $neb->find_instances('foo', 'node02');
+};
+like($@, qr/no instances on storage volume/, 'instances on a different volume');
+
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->find_instances('foo');
+};
+like($@, qr/is valid object key/, "storage object does not exist");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object('foo');
+    $neb->find_instances('foo', 'bar');
+};
+like($@, qr/is valid volume name/, "storage volume does not exist");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->find_instances();
+};
+like($@, qr/1 - 2 were expected/, "no params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object('foo');
+    $neb->find_instances('foo', 'node01', 3);
+};
+like($@, qr/1 - 2 were expected/, "too many params");
+
+Test::Nebulous->cleanup;
Index: /tags/rel-0_10/Nebulous-Server/t/08_server_delete_instance.t
===================================================================
--- /tags/rel-0_10/Nebulous-Server/t/08_server_delete_instance.t	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/t/08_server_delete_instance.t	(revision 17695)
@@ -0,0 +1,72 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2004-2005  Joshua Hoblitt
+#
+# $Id: 08_server_delete_instance.t,v 1.10 2008-03-20 21:10:14 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use Test::More tests => 8;
+
+use lib qw( ./t ./lib );
+
+use Nebulous::Server;
+use Test::Nebulous;
+
+my $neb = Nebulous::Server->new(
+    dsn         => $NEB_DB,
+    dbuser      => $NEB_USER,
+    dbpasswd    => $NEB_PASS,
+);
+
+Test::Nebulous->setup;
+
+{
+    my $uri = $neb->create_object("foo");
+
+    ok($neb->delete_instance($uri), "delete instance");
+}
+
+Test::Nebulous->setup;
+
+{
+    my $uri1 = $neb->create_object("foo");
+    my $uri2 = $neb->replicate_object("foo");
+
+    ok($neb->delete_instance($uri1), "delete instance");
+
+    my $locations = $neb->find_instances("foo");
+
+    is($locations->[0], $uri2, "instance remains");
+
+    ok($neb->delete_instance( $uri2 ), "delete instance");
+
+    eval {
+        $neb->find_instances("foo");
+    };
+    like($@, qr/is valid object key/, "storage object was deleted");
+}
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->delete_instance("file:/foo");
+};
+like($@, qr/no instance is associated with uri/, "uri does not exist");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->delete_instance();
+};
+like($@, qr/1 was expected/, "no params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->delete_instance("foo", 2);
+};
+like($@, qr/1 was expected/, "too many params");
+
+Test::Nebulous->cleanup;
Index: /tags/rel-0_10/Nebulous-Server/t/09_server_stat_object.t
===================================================================
--- /tags/rel-0_10/Nebulous-Server/t/09_server_stat_object.t	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/t/09_server_stat_object.t	(revision 17695)
@@ -0,0 +1,71 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2004-2005  Joshua Hoblitt
+#
+# $Id: 09_server_stat_object.t,v 1.14 2008-05-13 03:55:08 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use Test::More tests => 12;
+
+use lib qw( ./t ./lib );
+
+use Nebulous::Server;
+use Test::Nebulous;
+
+my $neb = Nebulous::Server->new(
+    dsn         => $NEB_DB,
+    dbuser      => $NEB_USER,
+    dbpasswd    => $NEB_PASS,
+);
+
+Test::Nebulous->setup;
+{
+    $neb->create_object("foo");
+
+    my $info = $neb->stat_object("foo");
+
+    is(scalar @$info, 7, "number of columns");
+}
+
+Test::Nebulous->setup;
+
+{
+    $neb->create_object("foo", "node01");
+
+    my $info = $neb->stat_object("foo");
+
+    is(scalar @$info, 7,                       "number of columns");
+    is(@$info[0], 1,                           "so_id");
+    is(@$info[1], "foo",                       "ext_id");
+    is(@$info[2], 0,                           "read lock");
+    is(@$info[3], undef,                       "write lock");
+    like(@$info[4], qr/....-..-.. ..:..:../,   "epoch");
+    like(@$info[5], qr/....-..-.. ..:..:../,   "mtime");
+    is(@$info[6], 1,                           "instances");
+}
+
+Test::Nebulous->setup;
+
+{
+    my $stat = $neb->stat_object("foo");
+    is($stat, undef, "object does not exist");
+}
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->stat_object();
+};
+like($@, qr/1 was expected/, "no params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+    $neb->stat_object("foo", 2);
+};
+like($@, qr/1 was expected/, "too many params");
+
+Test::Nebulous->cleanup;
Index: /tags/rel-0_10/Nebulous-Server/t/10_server_is_valid_volume_name.t
===================================================================
--- /tags/rel-0_10/Nebulous-Server/t/10_server_is_valid_volume_name.t	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/t/10_server_is_valid_volume_name.t	(revision 17695)
@@ -0,0 +1,35 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2004-2005  Joshua Hoblitt
+#
+# $Id: 10_server_is_valid_volume_name.t,v 1.5 2008-03-20 21:10:14 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use Test::More tests => 3;
+
+use lib qw( ./t ./lib );
+
+use Nebulous::Server;
+use Test::Nebulous;
+
+my $neb = Nebulous::Server->new(
+    dsn         => $NEB_DB,
+    dbuser      => $NEB_USER,
+    dbpasswd    => $NEB_PASS,
+);
+
+Test::Nebulous->setup;
+
+ok($neb->_is_valid_volume_name('node01'), "valid volume name");
+
+Test::Nebulous->setup;
+
+ok($neb->_is_valid_volume_name('node02'), "valid volume name");
+
+Test::Nebulous->setup;
+
+is($neb->_is_valid_volume_name('node99'), undef, "invalid volume name");
+
+Test::Nebulous->cleanup;
Index: /tags/rel-0_10/Nebulous-Server/t/11_server_is_valid_object_key.t
===================================================================
--- /tags/rel-0_10/Nebulous-Server/t/11_server_is_valid_object_key.t	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/t/11_server_is_valid_object_key.t	(revision 17695)
@@ -0,0 +1,37 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2004-2005  Joshua Hoblitt
+#
+# $Id: 11_server_is_valid_object_key.t,v 1.3 2008-03-20 21:10:14 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use Test::More tests => 2;
+
+use lib qw( ./t ./lib );
+
+use Nebulous::Server;
+use Test::Nebulous;
+
+my $neb = Nebulous::Server->new(
+    dsn         => $NEB_DB,
+    dbuser      => $NEB_USER,
+    dbpasswd    => $NEB_PASS,
+);
+
+Test::Nebulous->setup;
+
+{
+    $neb->create_object('foo');
+
+    ok($neb->_is_valid_object_key('foo'), "valid object key");
+}
+
+Test::Nebulous->setup;
+
+{
+    ok(!$neb->_is_valid_object_key('foo'), "invalid object key");
+}
+
+Test::Nebulous->cleanup;
Index: /tags/rel-0_10/Nebulous-Server/t/12_server_find_objects.t
===================================================================
--- /tags/rel-0_10/Nebulous-Server/t/12_server_find_objects.t	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/t/12_server_find_objects.t	(revision 17695)
@@ -0,0 +1,67 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2004-2005  Joshua Hoblitt
+#
+# $Id: 12_server_find_objects.t,v 1.3 2008-03-20 21:10:14 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use Test::More tests => 6;
+
+use lib qw( ./t ./lib );
+
+use Nebulous::Server;
+use Test::Nebulous;
+
+my $neb = Nebulous::Server->new(
+    dsn         => $NEB_DB,
+    dbuser      => $NEB_USER,
+    dbpasswd    => $NEB_PASS,
+);
+
+Test::Nebulous->setup;
+
+# search for a regex of '' should match nothing
+{
+    # key
+    my $uri = $neb->create_object("foo");
+
+    my $keys = $neb->find_objects();
+
+    is(scalar @$keys, 0, 'number of keys found');
+}
+
+Test::Nebulous->setup;
+
+{
+    # key
+    my $uri = $neb->create_object("foo");
+
+    my $keys = $neb->find_objects("foo");
+
+    is(scalar @$keys, 1, 'number of keys found');
+    is($keys->[0], "foo", "key name");
+}
+
+Test::Nebulous->setup;
+
+{
+    # key
+    my $uri1 = $neb->create_object("foo");
+    my $uri2 = $neb->replicate_object("foo");
+
+    my $keys = $neb->find_objects("foo");
+
+    is(scalar @$keys, 1, 'number of keys found');
+    is($keys->[0], "foo", "key name");
+}
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->find_objects("foo", 3);
+};
+like($@, qr/1 was expected/, "too many params");
+
+Test::Nebulous->cleanup;
Index: /tags/rel-0_10/Nebulous-Server/t/13_server_rename_object.t
===================================================================
--- /tags/rel-0_10/Nebulous-Server/t/13_server_rename_object.t	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/t/13_server_rename_object.t	(revision 17695)
@@ -0,0 +1,72 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2007  Joshua Hoblitt
+#
+# $Id: 13_server_rename_object.t,v 1.4 2008-03-20 21:10:14 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use Test::More tests => 6;
+
+use lib qw( ./t ./lib );
+
+use Nebulous::Server;
+use Test::Nebulous;
+
+my $neb = Nebulous::Server->new(
+    dsn         => $NEB_DB,
+    dbuser      => $NEB_USER,
+    dbpasswd    => $NEB_PASS,
+);
+
+Test::Nebulous->setup;
+
+{
+    my $uri = $neb->create_object("foo");
+
+    $neb->rename_object("foo", "bar");
+
+    my $keys = $neb->find_objects('^foo$');
+    is(scalar @$keys, 0, 'number of keys found');
+    $keys = $neb->find_objects('^bar$');
+    is(scalar @$keys, 1, 'number of keys found');
+}
+
+Test::Nebulous->setup;
+
+# destination key exists
+eval {
+    $neb->create_object('foo');
+    $neb->create_object('bar');
+
+    $neb->rename_object('foo', 'bar');
+};
+like($@, qr/is not valid object key/, "too few params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->rename_object();
+};
+like($@, qr/2 were expected/, "no params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+
+    $neb->rename_object("foo");
+};
+like($@, qr/2 were expected/, "too few params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+
+    $neb->rename_object("foo", "bar", "baz");
+};
+like($@, qr/2 were expected/, "too many params");
+
+Test::Nebulous->cleanup;
Index: /tags/rel-0_10/Nebulous-Server/t/14_server_xattr.t
===================================================================
--- /tags/rel-0_10/Nebulous-Server/t/14_server_xattr.t	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/t/14_server_xattr.t	(revision 17695)
@@ -0,0 +1,186 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2007  Joshua Hoblitt
+#
+# $Id: 14_server_xattr.t,v 1.6 2008-05-10 01:15:13 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use Test::More tests => 32;
+
+use lib qw( ./t ./lib );
+
+use Nebulous::Server;
+use Test::Nebulous;
+
+my $neb = Nebulous::Server->new(
+    dsn         => $NEB_DB,
+    dbuser      => $NEB_USER,
+    dbpasswd    => $NEB_PASS,
+);
+
+# 1 key / xattr
+
+Test::Nebulous->setup;
+
+{
+    my $uri = $neb->create_object('foo');
+
+    ok($neb->setxattr_object('foo', 'bar', 'baz', 'create'), 'set object xattr');
+    {
+        my $xattrs = $neb->listxattr_object('foo');
+        is(scalar @$xattrs, 1, 'number of xattrs');
+        is(@$xattrs[0], 'bar', 'xattr name');
+    }
+
+    my $value = $neb->getxattr_object('foo', 'bar');
+    is($value, 'baz', 'xattr value');
+
+    ok($neb->removexattr_object('foo', 'bar'), "remove object xattr");
+    {
+        my $xattrs = $neb->listxattr_object('foo');
+        is(scalar @$xattrs, 0, 'number of xattrs');
+    }
+}
+
+# multiple xattrs
+
+Test::Nebulous->setup;
+
+{
+    my $uri = $neb->create_object('foo');
+
+    ok($neb->setxattr_object('foo', 'bar', 'baz', 'create'), 'set object xattr');
+    ok($neb->setxattr_object('foo', 'bonk', 'quix', 'create'), 'set object xattr');
+    
+    {
+        my $xattrs = $neb->listxattr_object('foo');
+        is(scalar @$xattrs, 2, 'number of xattrs');
+        is(@$xattrs[0], 'bar', 'xattr name');
+        is(@$xattrs[1], 'bonk', 'xattr name');
+    }
+
+    my $value = $neb->getxattr_object('foo', 'bar');
+    is($value, 'baz', 'xattr value');
+    $value = $neb->getxattr_object('foo', 'bonk');
+    is($value, 'quix', 'xattr value');
+
+    ok($neb->removexattr_object('foo', 'bar'), "remove object xattr");
+    ok($neb->removexattr_object('foo', 'bonk'), "remove object xattr");
+    {
+        my $xattrs = $neb->listxattr_object('foo');
+        is(scalar @$xattrs, 0, 'number of xattrs');
+    }
+}
+
+# replace xattrs
+
+Test::Nebulous->setup;
+
+{
+    my $uri = $neb->create_object('foo');
+
+    ok($neb->setxattr_object('foo', 'bar', 'baz', 'create'), 'set object xattr');
+    ok($neb->setxattr_object('foo', 'bar', 'quix', 'replace'), 're-set object xattr');
+    
+    {
+        my $xattrs = $neb->listxattr_object('foo');
+        is(scalar @$xattrs, 1, 'number of xattrs');
+        is(@$xattrs[0], 'bar', 'xattr name');
+    }
+
+    my $value = $neb->getxattr_object('foo', 'bar');
+    is($value, 'quix', 'xattr value');
+
+    ok($neb->removexattr_object('foo', 'bar'), "remove object xattr");
+    {
+        my $xattrs = $neb->listxattr_object('foo');
+        is(scalar @$xattrs, 0, 'number of xattrs');
+    }
+}
+
+# setxattr_object
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->setxattr_object();
+};
+like($@, qr/4 were expected/, "no params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object('foo');
+
+    $neb->setxattr_object('foo', 'bar');
+};
+like($@, qr/4 were expected/, "too few params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object('foo');
+
+    $neb->setxattr_object('foo', 'bar', 'baz');
+};
+like($@, qr/4 were expected/, "too few params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object('foo');
+
+    $neb->setxattr_object('foo', 'bar', 'baz', 'create', 'quix');
+};
+like($@, qr/4 were expected/, "too many params");
+
+# listxattr_object
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object('foo');
+
+    $neb->listxattr_object();
+};
+like($@, qr/1 was expected/, "no params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object('foo');
+
+    $neb->listxattr_object('foo', 'bar');
+};
+like($@, qr/1 was expected/, "too many params");
+
+# getxattr_object
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->getxattr_object();
+};
+like($@, qr/2 were expected/, "no params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object('foo');
+
+    $neb->getxattr_object('foo');
+};
+like($@, qr/2 were expected/, "too few params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object('foo');
+
+    $neb->getxattr_object('foo', 'bar', 'baz');
+};
+like($@, qr/2 were expected/, "too many params");
+
+Test::Nebulous->cleanup;
Index: /tags/rel-0_10/Nebulous-Server/t/15_mounts.t
===================================================================
--- /tags/rel-0_10/Nebulous-Server/t/15_mounts.t	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/t/15_mounts.t	(revision 17695)
@@ -0,0 +1,65 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2008  Joshua Hoblitt
+#
+# $Id: 15_mounts.t,v 1.1 2008-03-21 01:25:21 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use Test::More tests => 16;
+
+use lib qw( ./t ./lib );
+
+use Nebulous::Server;
+use Test::URI;
+use Test::Nebulous;
+
+my $neb = Nebulous::Server->new(
+    dsn         => $NEB_DB,
+    dbuser      => $NEB_USER,
+    dbpasswd    => $NEB_PASS,
+);
+
+Test::Nebulous->setup;
+
+{
+    # key
+    my $mounts = $neb->mounts();
+
+    is(scalar @$mounts, 6, "number of rows");
+
+    my %row;
+    # first row
+    @row{qw(mountpoint total used vol_id name path allocate available xattr)}
+        = @{$mounts->[0]};
+    
+    is($row{total},     100000000000);
+    is($row{used},      100000000);
+    is($row{vol_id},    1);
+    is($row{name},      "node01");
+    is($row{allocate},  1);
+    is($row{available}, 1);
+    is($row{xattr},     0);
+
+    # 2nd row
+    @row{qw(mountpoint total used vol_id name path allocate available xattr)}
+        = @{$mounts->[1]};
+
+    is($row{total},     100000000000);
+    is($row{used},      1000000000);
+    is($row{vol_id},    2);
+    is($row{name},      "node02");
+    is($row{allocate},  1);
+    is($row{available}, 1);
+    is($row{xattr},     0);
+}
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->mounts("foo");
+};
+like($@, qr/0 were expected/, "no params");
+
+Test::Nebulous->cleanup;
Index: /tags/rel-0_10/Nebulous-Server/t/75_parse_neb_key.t
===================================================================
--- /tags/rel-0_10/Nebulous-Server/t/75_parse_neb_key.t	(revision 17695)
+++ /tags/rel-0_10/Nebulous-Server/t/75_parse_neb_key.t	(revision 17695)
@@ -0,0 +1,170 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2004-2005  Joshua Hoblitt
+#
+# $Id: 75_parse_neb_key.t,v 1.3 2008-03-21 01:23:45 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use Test::More;
+
+plan tests => 36;
+
+use lib qw( ./t ./lib );
+
+use Nebulous::Keys qw( parse_neb_key );
+use Test::Nebulous;
+
+# neb://<volume name>/...
+# neb:path...
+# neb:/path... (same as neb:path, leading '/' is stripped)
+# neb:///path... (same as neb:path)
+
+# key
+{
+    my ($volume, $path) = parse_neb_key('foo/bar/baz/quix');
+
+    is($path, 'foo/bar/baz/quix', 'path');
+    is($volume, undef, 'volume name');
+}
+
+{
+    my ($volume, $path) = parse_neb_key('/foo/bar/baz/quix');
+
+    is($path, 'foo/bar/baz/quix', 'path');
+    is($volume, undef, 'volume name');
+}
+
+{
+    my ($volume, $path) = parse_neb_key('//foo/bar/baz/quix');
+
+    is($path, 'foo/bar/baz/quix', 'path');
+    is($volume, undef, 'volume name');
+}
+
+{
+    my ($volume, $path) = parse_neb_key('///foo/bar/baz/quix');
+
+    is($path, 'foo/bar/baz/quix', 'path');
+    is($volume, undef, 'volume name');
+}
+
+{
+    my ($volume, $path) = parse_neb_key('foo////bar/baz/quix');
+
+    is($path, 'foo/bar/baz/quix', 'path');
+    is($volume, undef, 'volume name');
+}
+
+{
+    my ($volume, $path) = parse_neb_key('foo/bar/baz/quix/');
+
+    is($path, 'foo/bar/baz/quix', 'path');
+    is($volume, undef, 'volume name');
+}
+
+
+# URI w/ volume name
+{
+    my ($volume, $path) = parse_neb_key('neb://foo/bar/baz/quix');
+
+    is($path, 'bar/baz/quix', 'path');
+    is($volume, 'foo', 'volume name');
+}
+
+{
+    my ($volume, $path) = parse_neb_key('neb://foo//bar/baz/quix');
+
+    is($path, 'bar/baz/quix', 'path');
+    is($volume, 'foo', 'volume name');
+}
+
+{
+    my ($volume, $path) = parse_neb_key('neb://foo///bar/baz/quix');
+
+    is($path, 'bar/baz/quix', 'path');
+    is($volume, 'foo', 'volume name');
+}
+
+{
+    my ($volume, $path) = parse_neb_key('neb://foo/bar///baz/quix');
+
+    is($path, 'bar/baz/quix', 'path');
+    is($volume, 'foo', 'volume name');
+}
+
+{
+    my ($volume, $path) = parse_neb_key('neb://foo/bar/baz/quix/');
+
+    is($path, 'bar/baz/quix', 'path');
+    is($volume, 'foo', 'volume name');
+}
+
+
+# URI w/o volume name
+
+{
+    my ($volume, $path) = parse_neb_key('neb:/foo/bar/baz/quix');
+
+    is($path, 'foo/bar/baz/quix', 'path');
+    is($volume, undef, 'volume name');
+}
+
+{
+    my ($volume, $path) = parse_neb_key('neb:///foo/bar/baz/quix');
+
+    is($volume, undef, 'volume name');
+    is($path, 'foo/bar/baz/quix', 'path');
+}
+
+{
+    my ($volume, $path) = parse_neb_key('neb://///foo/bar/baz/quix');
+
+    is($path, 'foo/bar/baz/quix', 'path');
+    is($volume, undef, 'volume name');
+}
+
+# key w/ whitespace
+eval {
+    parse_neb_key('/ foo/bar/baz/quix');
+};
+like( $@, qr/may not contain whitespace/, "whitespace" );
+
+eval {
+    parse_neb_key(' /foo/bar/baz/quix');
+};
+like( $@, qr/may not contain whitespace/, "whitespace" );
+
+eval {
+    parse_neb_key('/foo/bar/baz/quix ');
+};
+like( $@, qr/may not contain whitespace/, "whitespace" );
+
+# URI w/ whitespace
+
+eval {
+    parse_neb_key('neb ://foo/bar/baz/quix');
+};
+like( $@, qr/may not contain whitespace/, "whitespace" );
+
+eval {
+    parse_neb_key('neb:// foo/bar/baz/quix');
+};
+like( $@, qr/may not contain whitespace/, "whitespace" );
+
+eval {
+    parse_neb_key(' neb://foo/bar/baz/quix');
+};
+like( $@, qr/may not contain whitespace/, "whitespace" );
+
+eval {
+    parse_neb_key('neb://foo/bar/baz/quix ');
+};
+like( $@, qr/may not contain whitespace/, "whitespace" );
+
+# URI w/o volume requires leading slash
+eval {
+    my ($volume, $path) = parse_neb_key('neb:foo/bar/baz/quix');
+};
+like( $@, qr/requires a leading slash/, "leading slash" );
Index: /tags/rel-0_10/glueforge/.cvsignore
===================================================================
--- /tags/rel-0_10/glueforge/.cvsignore	(revision 17695)
+++ /tags/rel-0_10/glueforge/.cvsignore	(revision 17695)
@@ -0,0 +1,11 @@
+Makefile
+Makefile.in
+aclocal.m4
+configure
+autom4te.cache
+config.log
+config.status
+install-sh
+missing
+glueforge
+glueforge.1
Index: /tags/rel-0_10/glueforge/INSTALL
===================================================================
--- /tags/rel-0_10/glueforge/INSTALL	(revision 17695)
+++ /tags/rel-0_10/glueforge/INSTALL	(revision 17695)
@@ -0,0 +1,95 @@
+# $Id: INSTALL,v 1.6 2006-01-11 02:12:34 jhoblitt Exp $
+
+Using the glueforge meta-database API generator
+--
+
+- Step 0
+
+Setup your shell's environment for Pan-STARRS CVS.  If you need help setting up
+CVS please see this article:
+
+    http://pan-starrs.ifa.hawaii.edu/project/resources/ps-cvs.html
+
+- Step 1
+
+Install the PS:IPP::Metadata::Config and Template modules.
+
+# PS:IPP::Metadata::Config
+$ cvs co PS-IPP-Metadata-Config
+$ cd PS-IPP-Metadata-Config
+$ perl Build.PL
+$ ./Build
+$ ./Build test
+# if you have sudo
+$ sudo ./Build install
+# if not
+$ su
+$ ./Build install
+
+# Template
+# if you have sudo
+$ sudo perl -MCPAN -e 'install Template'
+# if not
+$ su -
+$ perl -MCPAN -e 'install Template'
+
+If you have never used CPAN before you will be given a series of prompts to
+configure the module.
+
+- Step 2
+
+Checkout the glueforge module from Pan-STARRS CVS and build it.  If you need
+help setting up CVS please see this article:
+
+    http://pan-starrs.ifa.hawaii.edu/project/resources/ps-cvs.html
+
+$ cvs co glueforge
+$ cd glueforge
+$ ./autogen.sh
+$ make
+
+- Step 3
+
+Create a MetadataConfig file that describes the table you want to access.  The
+MetadataConfig file syntax is described in the pslib SDRS.  glueforge specific
+details are included in the "glueforge" man page. For reference, both a simple
+and complex config example is provided in the examples directory.
+
+$ perldoc glueforge
+
+or
+
+$ man glueforge
+
+$ cat examples/simple.md
+
+or
+
+$ cat examples/complex.md
+
+- Step 4
+
+Generate the database bindings with the "glueforge" executable.  The example
+simple.md file will generate code under the directory "foodb".
+
+$./glueforge -t templates/psdb -i examples/simple.md
+$ cd foodb
+
+- Step 5
+
+Package the software for distribution.  This is the same as for any 'standard'
+autotools package.  Please note that you must have pslib installed for the step
+to complete.  If you need help installing pslib please see this article:
+
+    http://pan-starrs.ifa.hawaii.edu/project/IPP/software/jhbuild/
+
+$ jhbuild shell
+$ ./autogen.sh && make dist
+
+This should generate two packages suitable for distribution.
+
+$ ls -la foodb-*
+-rw-r--r--  1 jhoblitt users 216367 Jul 28 17:54 foodb-0.0.1.tar.bz2
+-rw-r--r--  1 jhoblitt users 324926 Jul 28 17:54 foodb-0.0.1.tar.gz
+
+The End.
Index: /tags/rel-0_10/glueforge/Makefile.am
===================================================================
--- /tags/rel-0_10/glueforge/Makefile.am	(revision 17695)
+++ /tags/rel-0_10/glueforge/Makefile.am	(revision 17695)
@@ -0,0 +1,35 @@
+SUBDIRS = templates/psdb templates/latex
+
+bin_SCRIPTS = glueforge
+CLEANFILES = $(bin_SCRIPTS)
+
+do_subst = sed \
+	-e 's,[@]datadir[@],$(datadir),g' \
+	-e 's,[@]PACKAGE_NAME[@],$(PACKAGE_NAME),g' \
+	-e 's,[@]PACKAGE_VERSION[@],$(PACKAGE_VERSION),g'
+
+glueforge: glueforge.in
+	$(do_subst) < $(srcdir)/glueforge.in > glueforge
+	chmod +x glueforge
+
+example_files = \
+   examples/simple.md \
+   examples/complex.md
+
+glueforgedatadir = $(datadir)/@PACKAGE_NAME@
+exampledir = $(glueforgedatadir)/examples
+example_DATA = $(example_files)
+
+install-data-local:
+	-chmod 0755 $(glueforgedatadir) $(exampledir)
+
+man_MANS = \
+    $(top_builddir)/@PACKAGE_NAME@.1
+
+@PACKAGE@.1: glueforge
+	$(POD2MAN) glueforge > @PACKAGE@.1
+
+clean-local:
+	-rm -rf docs @PACKAGE@.1
+
+EXTRA_DIST = autogen.sh glueforge.in $(example_files)
Index: /tags/rel-0_10/glueforge/autogen.sh
===================================================================
--- /tags/rel-0_10/glueforge/autogen.sh	(revision 17695)
+++ /tags/rel-0_10/glueforge/autogen.sh	(revision 17695)
@@ -0,0 +1,103 @@
+#!/bin/sh
+# Run this to generate all the initial makefiles, etc.
+
+srcdir=`dirname $0`
+test -z "$srcdir" && srcdir=.
+
+ORIGDIR=`pwd`
+cd $srcdir
+
+PROJECT=glueforge
+TEST_TYPE=-f
+# change this to be a unique filename in the top level dir
+FILE=autogen.sh
+
+DIE=0
+
+LIBTOOLIZE=libtoolize
+ACLOCAL=aclocal
+AUTOHEADER=autoheader
+AUTOMAKE=automake
+AUTOCONF=autoconf
+
+#($LIBTOOLIZE --version) < /dev/null > /dev/null 2>&1 || {
+#        echo
+#        echo "You must have $LIBTOOlIZE installed to compile $PROJECT."
+#        echo "Download the appropriate package for your distribution,"
+#        echo "or get the source tarball at http://ftp.gnu.org/gnu/libtool/"
+#        DIE=1
+#}
+
+($ACLOCAL --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $ACLOCAL installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+#($AUTOHEADER --version) < /dev/null > /dev/null 2>&1 || {
+#        echo
+#        echo "You must have $AUTOHEADER installed to compile $PROJECT."
+#        echo "Download the appropriate package for your distribution,"
+#        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+#        DIE=1
+#}
+
+($AUTOMAKE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOMAKE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOCONF --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOCONF installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+if test "$DIE" -eq 1; then
+        exit 1
+fi
+
+test $TEST_TYPE $FILE || {
+        echo "You must run this script in the top-level $PROJECT directory"
+        exit 1
+}
+
+if test -z "$*"; then
+        echo "I am going to run ./configure with no arguments - if you wish "
+        echo "to pass any to it, please specify them on the $0 command line."
+fi
+
+#$LIBTOOLIZE --copy --force || echo "$LIBTOOlIZE failed"
+$ACLOCAL -I m4 || echo "$ACLOCAL failed"
+#$AUTOHEADER || echo "$AUTOHEADER failed"
+$AUTOMAKE --add-missing --force-missing --copy || echo "$AUTOMAKE failed"
+$AUTOCONF || echo "$AUTOCONF failed"
+
+cd $ORIGDIR
+
+run_configure=true
+for arg in $*; do
+    case $arg in
+        --no-configure)
+            run_configure=false
+            ;;
+        *)
+            ;;
+    esac
+done
+
+if $run_configure; then
+    $srcdir/configure --enable-maintainer-mode "$@"
+    echo
+    echo "Now type 'make' to compile $PROJECT."
+else
+    echo
+    echo "Now run 'configure' and 'make' to compile $PROJECT."
+fi
Index: /tags/rel-0_10/glueforge/configure.ac
===================================================================
--- /tags/rel-0_10/glueforge/configure.ac	(revision 17695)
+++ /tags/rel-0_10/glueforge/configure.ac	(revision 17695)
@@ -0,0 +1,31 @@
+AC_PREREQ(2.59)
+
+AC_INIT([glueforge], [0.10], [jhoblitt@cpan.org])
+AC_CONFIG_SRCDIR([glueforge.in])
+
+AM_INIT_AUTOMAKE([1.6 foreign dist-bzip2])
+AM_MAINTAINER_MODE
+
+AC_PROG_INSTALL
+
+AC_PROG_PERL_MODULES(
+  [PS::IPP::Metadata::Config], ,
+  [AC_MSG_ERROR(perl module PS::IPP::Metadata::Config is required)]
+)
+
+AC_PROG_PERL_MODULES(
+  [Template], ,
+  [AC_MSG_ERROR(perl module Template is required)]
+)
+
+AC_PATH_PROG([POD2MAN], [pod2man], [missing])
+if test "$POD2MAN" = "missing" ; then
+  AC_MSG_ERROR([pod2man is required])
+fi
+
+AC_CONFIG_FILES([
+  Makefile
+  templates/latex/Makefile
+  templates/psdb/Makefile
+])
+AC_OUTPUT
Index: /tags/rel-0_10/glueforge/examples/complex.md
===================================================================
--- /tags/rel-0_10/glueforge/examples/complex.md	(revision 17695)
+++ /tags/rel-0_10/glueforge/examples/complex.md	(revision 17695)
@@ -0,0 +1,20 @@
+glueforge METADATA
+    pkg_name        STR  foodb
+    pkg_namespace   STR  foo
+END
+
+foo METADATA
+    foo     STR     60      # Primary Key # the name of foo thing
+    bar     S32     0       ## count of bar
+    baz     F32     0.0
+    boing   F64     0.0
+    zot     BOOL    t       # Key
+END
+
+bar METADATA
+    zot     BOOL    t       # Key
+    boing   F64     0.0
+    baz     F32     0.0
+    bar     S32     0       ## count of bar
+    foo     STR     60      # Primary Key # the name of foo thing
+END
Index: /tags/rel-0_10/glueforge/examples/simple.md
===================================================================
--- /tags/rel-0_10/glueforge/examples/simple.md	(revision 17695)
+++ /tags/rel-0_10/glueforge/examples/simple.md	(revision 17695)
@@ -0,0 +1,6 @@
+table   STR     foo
+foo     STR     60      # Primary Key # the name of foo thing
+bar     S32     0       ## count of bar
+baz     F32     0.0
+boing   F64     0.0
+zot     BOOL    t       # Key
Index: /tags/rel-0_10/glueforge/glueforge.in
===================================================================
--- /tags/rel-0_10/glueforge/glueforge.in	(revision 17695)
+++ /tags/rel-0_10/glueforge/glueforge.in	(revision 17695)
@@ -0,0 +1,471 @@
+#!/usr/bin/env perl
+
+# Copyright (C) 2005-2006  Joshua Hoblitt
+#
+# $Id: glueforge.in,v 1.47 2006-02-22 00:26:03 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use vars qw( $VERSION );
+$VERSION = '@PACKAGE_VERSION@';
+
+use PS::IPP::Metadata::Config;
+use Template;
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version );
+use Pod::Usage qw( pod2usage );
+
+my ( $input, $output, $template );
+GetOptions(
+    'input=s'       => \$input,
+    'output=s'      => \$output,
+    'template=s'    => \$template,
+) || pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+pod2usage( -msg => "Required option: --input|-i", -exitval => 2 ) unless $input;
+
+$template = "@datadir@/@PACKAGE_NAME@/templates/psdb" unless $template;
+
+die "file doesn't exist: $input" unless -e $input;
+die "file isn't readable: $input" unless -r $input;
+
+my $config = parse_config( $input );
+
+die "file doesn't not contain a valid config" unless defined $config->[0];
+
+# if the first hash has a class of "metadata" then this is a complex config.
+# Otherwise it is a simple config.
+my $complex = ($config->[0]->{class} eq "metadata") ? 1 : 0;
+
+my $data; # hashref
+if ($complex) {
+    $data = complex_config($config);
+} else {
+    $data = simple_config($config);
+}
+
+# determine output path
+$output = $data->{pkg_name} unless defined $output;
+
+my $ttconfig;
+{
+    # slurp config.pl
+    open(my $fh, "$template/config.pl")
+        or die "can't open $template/config.pl: $!";
+    $ttconfig = do {local $/; <$fh>};
+    close($fh) or die "can't close $template/config.pl: $!";
+}
+
+# configure template file paths
+my %tt;
+eval $ttconfig;
+die "can't eval contents of $template/config.pl: $@" if $@;
+
+my $mangler = Template->new({ INCLUDE_PATH => $template });
+
+foreach my $t ( keys %tt ) {
+    $mangler->process( $t, $data, $tt{$t})
+        or die $mangler->error;
+}
+
+# make autogen.sh executable
+# permissions are in octal
+#chmod 0744, $tt{'autogen_sh.tt'}; 
+
+sub parse_config {
+    my $file = shift;
+
+    my $mdparser = PS::IPP::Metadata::Config->new;
+    $mdparser->overwrite( 1 );
+
+    open( my $data, $file ) or die "can't open file: $!";
+    my $example = do { local $/; <$data> };
+    close( $data ) or die "can't close file: $!";
+
+    my $config = $mdparser->parse( $example )
+        or die "error parsing file: $input";
+
+    return $config;
+}
+
+sub lookup_type {
+    my $type = shift;
+
+    my %primitives = map { $_ => 1 } qw( S8 S16 S32 S64 U8 U16 U32 U64 F32 F64 );
+    my %mtypes = map { $_ => "PS_DATA_$_" } keys %primitives;
+    my %ctypes = map { $_ => "ps$_" } keys %primitives;
+
+    my %ttypes = (
+        S8      => -8,
+        S16     => -16,
+        S32     => -32,
+        S64     => -64,
+        U8      => 8,
+        U16     => 16,
+        U32     => 32,
+        U64     => 64,
+        F32     => 32.32,
+        F64     => 64.64,
+    );
+
+    if ( $primitives{$type} ) {
+        return {
+            ctype   => $ctypes{$type},
+            mtype   => $mtypes{$type},
+            test    => $ttypes{$type},
+        };
+    } elsif ( $type =~ /STR|STRING/ ) {
+        return {
+            ctype   => "char*",
+            mtype   => 'PS_DATA_STR',
+            test    => '"a string"',
+        };
+    } elsif ( $type =~ /BOOL/ ) {
+        return {
+            ctype   => "bool",
+            mtype   => 'PS_DATA_BOOL',
+            test    => 'true',
+        };
+    } else {
+        return undef;
+    }
+}
+
+sub simple_config {
+    my $config = shift;
+
+    # find table name
+    my $table_name;
+    for (my $i = 0; $i < @{$config}; $i++) {
+        my $elem = $config->[$i];
+
+        if ( $elem->{name} eq "table" ) {
+            $table_name = $elem->{value};
+
+            # delete element from array
+            splice @$config, $i, 1;
+
+            last;
+        }
+    }
+
+    my %data;
+
+    # global data
+    $data{pkg_name}         = $table_name . "db";
+    $data{pkg_namespace}    = $table_name;
+
+    # per table data
+    my %table;
+    $table{name}        = $table_name;
+    $table{namespace}   = $table_name;
+    $table{object_name} = $table{namespace} . "Row";
+
+    my @items;
+
+    # setup items
+    for (my $i = 0; $i < @{$config}; $i++) {
+        my $elem = $config->[$i];
+
+        my $mdtypes = lookup_type( $elem->{type} );
+        next if ! defined $mdtypes;
+
+        push @items, {
+            name    => $elem->{name},
+            type    => $elem->{type},
+            ctype   => $mdtypes->{ctype},
+            mtype   => $mdtypes->{mtype},
+            test    => $mdtypes->{test},
+            comment => $elem->{comment},
+            value   => $elem->{value},
+        };
+    }
+
+    $table{columns} = \@items;
+    $data{tables} = [ \%table ];
+
+    return \%data;
+}
+
+sub complex_config {
+    my $config = shift;
+
+    # find the glueforge metadata
+    my $glueforge_meta;
+    for (my $i = 0; $i < @{$config}; $i++) {
+        my $elem = $config->[$i];
+
+        if ($elem->{name} eq "glueforge") {
+            $glueforge_meta = $elem;
+
+            # delete element from array
+            splice @$config, $i, 1;
+
+            last;
+        }
+    }
+
+    die "missing glueforge METADATA" unless defined $glueforge_meta;
+
+    # global data
+
+    # process glueforge metadata for global data
+    my ($pkg_name, $pkg_namespace);
+    foreach my $item (@{$glueforge_meta->{value}}) {
+        if ($item->{name} eq 'pkg_name') {
+            $pkg_name = $item->{value};
+        } elsif ($item->{name} eq 'pkg_namespace') {
+            $pkg_namespace = $item->{value};
+        } else {
+            die "invalid glueforge METADATA key: $item->{name}";
+        }
+    }
+
+    die "pkg_name is required in complex config" unless defined $pkg_name;
+    die "pkg_namespace is required in complex config" unless defined
+        $pkg_namespace;
+
+    my %data;
+    $data{pkg_name}         = $pkg_name;
+    $data{pkg_namespace}    = $pkg_namespace;
+
+    # setup one table per nested metadata
+    my @tables;
+    foreach my $meta (@{$config}) {
+        push @tables, tabledata_from_meta($meta);
+    }
+
+    $data{tables} = \@tables;
+
+    return \%data;
+}
+
+sub tabledata_from_meta {
+    my $meta = shift;   # hashref
+
+    die "not a valid metadata" unless $meta->{class} eq 'metadata';
+
+    my $table_name = $meta->{name};
+
+    my @fields;
+    foreach my $column (@{$meta->{value}}) {
+        my $mdtypes = lookup_type( $column->{type} );
+        next if not defined $mdtypes;
+
+        push @fields, {
+            name    => $column->{name},
+            type    => $column->{type},
+            ctype   => $mdtypes->{ctype},
+            mtype   => $mdtypes->{mtype},
+            test    => $mdtypes->{test},
+            comment => $column->{comment},
+            value   => $column->{value},
+        };
+    }
+
+    my %table = (
+        name        => $table_name,
+        namespace   => $table_name,
+        object_name => $table_name . "Row",
+        columns     => \@fields,
+    );
+
+    return \%table;
+}
+
+__END__
+
+=pod
+
+=head1 NAME
+
+@PACKAGE_NAME@ - auto-generate Pan-STARRS IPP 'meta' database bindings
+
+=head1 SYNOPSIS
+
+    @PACKAGE_NAME@ --input <filename> [--output <dirname>] [--template <dirname>]
+
+=head1 DESCRIPTION
+
+This program generates a set of database table format specific bindings in the
+ANSI C language.  The code generated is largely just wrapper functions around
+the C<pslib> C<psDB> API.  Table information is read in from a configuration
+file in the pslib C<MetadataConfig> format.  A complete autotools configured
+package that builds both static & shared libraries is generated.
+
+=head2 Features
+
+=over 4
+
+=item * Generates a complete autoconf/automake/libtool package.
+
+=item * Generated packages include an C<autogen.sh> script.
+
+=item * Installs a pkgconfig C<.pc> data file.
+
+=item * Includes a table format specific autotest test suite.
+
+=item * Builds both shared and static libraries by default.
+
+=item * Generates man pages and HTML documentation with Doxygen.
+
+=item * Header files should be C++ safe.
+
+=item * Functions to open and close database connections.
+
+=item * Functions to create and destroy a table of the appropriate format.
+
+=item * Functions to insert and pop a row.
+
+=item * Functions to insert and pop a simple object representing a row.
+
+=item * Functions to insert, select, and pop to/from FITS files.
+
+=item * Functions to convert between row structs and pslib C<psMetadata>s.
+
+=back
+
+=head1 TABLE DESCRIPTION FORMAT
+
+There are two slightly different configuration file forms.  A C<simple> format
+for describing a single table and a C<complex> format for describing more then
+one table and/or configuring specific details as to how the package is
+generated.  The syntax for both of these forms follows the C<Configuration
+files> format as described in C<PSDC-430-007>.
+
+=head2 Simple configuration
+
+A simple config file specifies the name of the table, the name and types of all
+columns, and can optionally specify which column(s) are to be used as indexes.
+
+=head2 Example simple configuration
+
+    table   STR     foo
+    foo     STR     60      # Primary Key # the name of foo thing
+    bar     S32     0       ## count of bar
+    baz     F32     0.0
+    boing   F64     0.0
+    zot     BOOL    t       # Key
+
+The key name C<table> is reserved and specifies the name of the database table.
+It may appear any where in the file but it is most I<convenient> to have it as
+the first line of the table description.
+
+The key name C<type> is reserved and I<may not> be used.
+
+The value of a C<STR> column specifies the maximum number of characters that
+the database is required to store for that field.  The values for C<S32>,
+C<F32>, C<F64>, & C<BOOL> columns are ignored.
+
+Indexes maybe specified as comment value of either C<Primary Key> or C<Key>.
+Only one C<Primary Key> may be specified per table.  
+
+Description comments may specified as either a C<# ...> after an index
+specifier or as stand alone comment value of C<## ...>.
+
+The key name C<position> is also reserved as it is used internally by the
+database bindings.
+
+=head2 Complex configuration
+
+A complex config file consists of a series of C<METADATA> blocks.  One (and
+only one) of these blocks I<must> be named C<glueforge>. This block
+contains the global package configuration data.  Only the keys C<pkg_name> and
+C<pkg_namespace> are allowed in this block and they are both required.
+
+All other C<METADATA> blocks describe a single table with the name of the
+C<METATA> block being the tables name.  Otherwise, the format of items
+contained in a C<METADATA> block is the same as for the simple configuration.
+
+Note that nested C<METADATA> blocks are not allowed.
+
+=head2 Example complex configuration
+
+glueforge METADATA
+    pkg_name        STR  foodb 
+    pkg_namespace   STR  foo
+END
+
+foo METADATA
+    foo     STR     60      # Primary Key # the name of foo thing
+    bar     S32     0       ## count of bar
+    baz     F32     0.0
+    boing   F64     0.0
+    zot     BOOL    t       # Key
+END
+
+bar METADATA
+    zot     BOOL    t       # Key
+    boing   F64     0.0
+    baz     F32     0.0
+    bar     S32     0       ## count of bar
+    foo     STR     60      # Primary Key # the name of foo thing
+END
+
+=head1 OPTIONS
+
+=over 4
+
+=item * --input|-i <filename>
+
+File to read table description from.
+
+=item * --output|-o <dirname>
+
+Send the generate files to this directory name.
+
+Defaults to the name of the database table.
+
+=item * --template|-t <dirname>
+
+Directory to load template files from.
+
+Defaults to C<@datadir@/@PACKAGE_NAME@/templates/psdb>.
+
+=back
+
+=head1 BUGS
+
+The format of configuration files is not rigorously checked.  Improperly
+formatted files may not be caught and cause random (possibly silent) errors
+and/or improperly code generation.
+
+=head1 CREDITS
+
+Just me, myself, and I.
+
+=head1 SUPPORT
+
+Please contact the author directly via e-mail.
+
+=head1 AUTHOR
+
+Joshua Hoblitt <jhoblitt@cpan.org>
+
+=head1 COPYRIGHT
+
+Copyright (C) 2005-2006  Joshua Hoblitt.  All rights reserved.
+
+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.
+
+The full text of the license can be found in the L<perlgpl> Pod as supplied
+with Perl 5.8.1 and later.
+
+=head1 SEE ALSO
+
+C<pslib>, C<PSDC-430-007>. L<PS:IPP::Metadata::Config>
+
+=cut
Index: /tags/rel-0_10/glueforge/m4/ac_prog_perl_modules.m4
===================================================================
--- /tags/rel-0_10/glueforge/m4/ac_prog_perl_modules.m4	(revision 17695)
+++ /tags/rel-0_10/glueforge/m4/ac_prog_perl_modules.m4	(revision 17695)
@@ -0,0 +1,53 @@
+dnl @synopsis AC_PROG_PERL_MODULES([MODULES], [ACTION-IF-TRUE], [ACTION-IF-FALSE])
+dnl
+dnl Checks to see if the the given perl modules are available. If true
+dnl the shell commands in ACTION-IF-TRUE are executed. If not the shell
+dnl commands in ACTION-IF-FALSE are run. Note if $PERL is not set (for
+dnl example by calling AC_CHECK_PROG, or AC_PATH_PROG),
+dnl AC_CHECK_PROG(PERL, perl, perl) will be run.
+dnl
+dnl Example:
+dnl
+dnl   AC_CHECK_PERL_MODULES(Text::Wrap Net::LDAP, ,
+dnl                         AC_MSG_WARN(Need some Perl modules)
+dnl
+dnl @category InstalledPackages
+dnl @author Dean Povey <povey@wedgetail.com>
+dnl @version 2002-09-25
+dnl @license AllPermissive
+
+AC_DEFUN([AC_PROG_PERL_MODULES],[
+    ac_perl_modules="$1"
+    # Make sure we have perl
+    if test -z "$PERL"; then
+        AC_CHECK_PROG(PERL,perl,perl)
+    fi
+
+    if test "x$PERL" != x; then
+        ac_perl_modules_failed=0
+        for ac_perl_module in $ac_perl_modules; do
+            AC_MSG_CHECKING(for perl module $ac_perl_module)
+
+            # Would be nice to log result here, but can't rely on autoconf
+            # internals
+            $PERL "-M$ac_perl_module" -e exit > /dev/null 2>&1
+            if test $? -ne 0; then
+                AC_MSG_RESULT(no);
+                ac_perl_modules_failed=1
+            else
+                AC_MSG_RESULT(ok);
+            fi
+        done
+
+        # Run optional shell commands
+        if test "$ac_perl_modules_failed" = 0; then
+            :
+            $2
+        else
+            :
+            $3
+        fi
+    else
+        AC_MSG_WARN(could not find perl)
+    fi
+])
Index: /tags/rel-0_10/glueforge/templates/latex/Makefile.am
===================================================================
--- /tags/rel-0_10/glueforge/templates/latex/Makefile.am	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/latex/Makefile.am	(revision 17695)
@@ -0,0 +1,12 @@
+template_files = \
+    config.pl \
+    tables.tt 
+
+templatedir = $(datadir)/@PACKAGE_NAME@/templates
+latextemplatedir = $(templatedir)/latex
+latextemplate_DATA = $(template_files)
+
+install-data-hook:
+	chmod 0755 $(templatedir) $(latextemplatedir)
+
+EXTRA_DIST = $(template_files)
Index: /tags/rel-0_10/glueforge/templates/latex/config.pl
===================================================================
--- /tags/rel-0_10/glueforge/templates/latex/config.pl	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/latex/config.pl	(revision 17695)
@@ -0,0 +1,3 @@
+%tt = (
+    'tables.tt'         => "$output/tables.tex",
+);
Index: /tags/rel-0_10/glueforge/templates/latex/tables.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/latex/tables.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/latex/tables.tt	(revision 17695)
@@ -0,0 +1,20 @@
+[%- FOREACH table = tables -%]
+\begin{table}[h]
+\begin{center}
+\caption{\label{[% table.namespace %]}[% table.namespace %]}
+\begin{tabular}{|l|l|l|}
+\hline
+name        & type           & description \\
+\hline
+[% FOREACH item = table.columns -%]
+[%- IF item.type == 'STR' -%]
+[% item.name %] & [% item.type %] (up to [% item.value %] chars) & [% item.comment %] \\
+[%- ELSE -%]
+[% item.name %] & [% item.type %] & [% item.comment %] \\
+[% END -%]
+[% END -%]
+\hline
+\end{tabular}
+\end{center}
+\end{table}
+[%- END %]
Index: /tags/rel-0_10/glueforge/templates/psdb/.cvsignore
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/.cvsignore	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/.cvsignore	(revision 17695)
@@ -0,0 +1,2 @@
+Makefile
+Makefile.in
Index: /tags/rel-0_10/glueforge/templates/psdb/Makefile.am
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/Makefile.am	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/Makefile.am	(revision 17695)
@@ -0,0 +1,86 @@
+template_files = \
+    config.pl \
+    alloc.tt \
+    alloc_at.tt \
+    alloc_c.tt \
+    alloc_h.tt \
+    autogen_sh.tt \
+    bootstrap_sh.tt \
+    cleanup.tt \
+    cleanup_at.tt \
+    cleanup_c.tt \
+    cleanup_h.tt \
+    code.tt \
+    configure_ac.tt \
+    createtable.tt \
+    createtable_at.tt \
+    createtable_c.tt \
+    createtable_h.tt \
+    dbcleanup_c.tt \
+    dbsetup_c.tt \
+    deleterowobjects.tt \
+    deleterowobjects_h.tt \
+    doxyfile_in.tt \
+    droptable.tt \
+    droptable_at.tt \
+    droptable_c.tt \
+    droptable_h.tt \
+    header.tt \
+    init.tt \
+    init_at.tt \
+    init_c.tt \
+    init_h.tt \
+    insert.tt \
+    insert_at.tt \
+    insert_c.tt \
+    insert_h.tt \
+    insertfits.tt \
+    insertfits_at.tt \
+    insertfits_c.tt \
+    insertfits_h.tt \
+    insertobject.tt \
+    insertobject_at.tt \
+    insertobject_c.tt \
+    insertobject_h.tt \
+    makefile.tt \
+    metadatafromobject.tt \
+    metadatafromobject_at.tt \
+    metadatafromobject_c.tt \
+    metadatafromobject_h.tt \
+    object_h.tt \
+    objectfrommetadata.tt \
+    objectfrommetadata_at.tt \
+    objectfrommetadata_c.tt \
+    objectfrommetadata_h.tt \
+    pkgconfig_pc_in.tt \
+    pop.tt \
+    pop_at.tt \
+    pop_c.tt \
+    pop_h.tt \
+    popfits.tt \
+    popfits_at.tt \
+    popfits_c.tt \
+    popfits_h.tt \
+    popobject.tt \
+    popobject_at.tt \
+    popobject_c.tt \
+    popobject_h.tt \
+    selectrowobjects.tt \
+    selectrowobjects_h.tt \
+    selectrowsfits.tt \
+    selectrowsfits_at.tt \
+    selectrowsfits_c.tt \
+    selectrowsfits_h.tt \
+    src_makefile_am.tt \
+    tests_makefile_am.tt \
+    testsuite_at.tt \
+    top_makefile_am.tt
+
+templatedir = $(datadir)/@PACKAGE_NAME@/templates
+psdbtemplatedir = $(templatedir)/psdb
+psdbtemplate_DATA = $(template_files)
+
+install-data-hook:
+	chmod 0755 $(templatedir) $(psdbtemplatedir)
+
+EXTRA_DIST = $(template_files)
Index: /tags/rel-0_10/glueforge/templates/psdb/alloc.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/alloc.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/alloc.tt	(revision 17695)
@@ -0,0 +1,40 @@
+[% USE format -%]
+[% indented = format('%-15s %s') -%]
+static void [% table.object_name %]Free([% table.object_name %] *object);
+
+[% table.object_name %] *[% table.object_name %]Alloc(
+[%- SET i = 0 -%]
+[% FOREACH item = table.columns -%]
+[% IF i == 0 %][% i = 1 %][% ELSE %], [% END -%]
+[% IF item.type == "STR" -%]
+const char *[% item.name %]
+[%- ELSE -%]
+[% item.ctype %] [% item.name %]
+[%- END -%]
+[% END -%]
+)
+{
+    [% indented(table.object_name, "*object") %];
+
+    object = psAlloc(sizeof([% table.object_name %]));
+    psMemSetDeallocator(object, (psFreeFunc)[% table.object_name %]Free);
+
+[% FOREACH item = table.columns -%]
+[% IF item.type == "STR" -%]
+    object->[% item.name %] = psStringCopy([% item.name %]);
+[% ELSE -%]
+    object->[% item.name %] = [% item.name %];
+[% END -%]
+[% END -%]
+
+    return object;
+}
+
+static void [% table.object_name %]Free([% table.object_name %] *object)
+{
+[% FOREACH item = table.columns -%]
+[% IF item.type == "STR" -%]
+    psFree(object->[% item.name %]);
+[% END -%]
+[% END -%]
+}
Index: /tags/rel-0_10/glueforge/templates/psdb/alloc_at.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/alloc_at.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/alloc_at.tt	(revision 17695)
@@ -0,0 +1,6 @@
+AT_SETUP([[% table.object_name %]Alloc()])
+AT_KEYWORDS([[% table.object_name %]Alloc])
+
+AT_CHECK([alloc])
+
+AT_CLEANUP
Index: /tags/rel-0_10/glueforge/templates/psdb/alloc_c.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/alloc_c.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/alloc_c.tt	(revision 17695)
@@ -0,0 +1,42 @@
+[% USE format -%]
+[% indented = format('%-15s %s') -%]
+#include <pslib.h>
+#include <[% pkg_name %].h>
+#include <stdlib.h>
+
+#define MAX_STRING_LENGTH 1024
+
+int main ()
+{
+[% FOREACH table = tables -%]
+    {
+        [% indented(table.object_name, "*object") %];
+
+        object = [% table.object_name %]Alloc(
+[%- SET i = 0 -%]
+[%- FOREACH item = table.columns -%]
+        [%- IF i == 0 -%][%- i = 1 -%][%- ELSE -%], [% END -%] [%- item.test -%]
+[%- END -%]
+    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+[% FOREACH item = table.columns -%]
+[% IF item.type == "S32" or item.type == "F32" or item.type == "F64" or item.type == "BOOL" -%]
+        if (!object->[% item.name %] == [% item.test %]) {
+[% ELSIF item.type == "STR" -%]
+        if (strncmp(object->[% item.name %], [% item.test %], MAX_STRING_LENGTH)) {
+[% END -%]
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+[% END -%]
+
+        psFree(object);
+    }
+
+[% END -%]
+    exit(EXIT_SUCCESS);
+}
Index: /tags/rel-0_10/glueforge/templates/psdb/alloc_h.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/alloc_h.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/alloc_h.tt	(revision 17695)
@@ -0,0 +1,18 @@
+[% USE format -%]
+[% indented = format('%-15s %s') -%]
+/** Creates a new [% table.object_name %] object
+ *
+ *  @return A new [% table.object_name %] object or NULL on failure.
+ */
+
+[% table.object_name %] *[% table.object_name %]Alloc(
+[%- SET i = 0 -%]
+[% FOREACH item = table.columns -%]
+[% IF i == 0 %][% i = 1 %][% ELSE %],[% END %]
+[% IF item.type == "STR" -%]
+    [% indented("const char", "*$item.name") %]
+[%- ELSE -%]
+    [% indented("$item.ctype", "$item.name") %]
+[%- END -%]
+[% END %]
+);
Index: /tags/rel-0_10/glueforge/templates/psdb/autogen_sh.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/autogen_sh.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/autogen_sh.tt	(revision 17695)
@@ -0,0 +1,103 @@
+#!/bin/sh
+# Run this to generate all the initial makefiles, etc.
+
+srcdir=`dirname $0`
+test -z "$srcdir" && srcdir=.
+
+ORIGDIR=`pwd`
+cd $srcdir
+
+PROJECT=[% pkg_name %]
+TEST_TYPE=-f
+# change this to be a unique filename in the top level dir
+FILE=autogen.sh
+
+DIE=0
+
+LIBTOOLIZE=libtoolize
+ACLOCAL=aclocal
+AUTOHEADER=autoheader
+AUTOMAKE=automake
+AUTOCONF=autoconf
+
+($LIBTOOLIZE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $LIBTOOlIZE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/libtool/"
+        DIE=1
+}
+
+($ACLOCAL --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $ACLOCAL installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOHEADER --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOHEADER installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+($AUTOMAKE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOMAKE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOCONF --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOCONF installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+if test "$DIE" -eq 1; then
+        exit 1
+fi
+
+test $TEST_TYPE $FILE || {
+        echo "You must run this script in the top-level $PROJECT directory"
+        exit 1
+}
+
+if test -z "$*"; then
+        echo "I am going to run ./configure with no arguments - if you wish "
+        echo "to pass any to it, please specify them on the $0 command line."
+fi
+
+$LIBTOOLIZE --copy --force || echo "$LIBTOOlIZE failed"
+$ACLOCAL || echo "$ACLOCAL failed"
+$AUTOHEADER || echo "$AUTOHEADER failed"
+$AUTOMAKE --add-missing --force-missing --copy || echo "$AUTOMAKE failed"
+$AUTOCONF || echo "$AUTOCONF failed"
+
+cd $ORIGDIR
+
+run_configure=true
+for arg in $*; do
+    case $arg in
+        --no-configure)
+            run_configure=false
+            ;;
+        *)
+            ;;
+    esac
+done
+
+if $run_configure; then
+    $srcdir/configure --enable-maintainer-mode "$@"
+    echo
+    echo "Now type 'make' to compile $PROJECT."
+else
+    echo
+    echo "Now run 'configure' and 'make' to compile $PROJECT."
+fi
Index: /tags/rel-0_10/glueforge/templates/psdb/bootstrap_sh.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/bootstrap_sh.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/bootstrap_sh.tt	(revision 17695)
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+libtoolize --automake --copy \
+&& aclocal \
+&& autoheader \
+&& automake --foreign --add-missing --copy \
+&& autoconf
Index: /tags/rel-0_10/glueforge/templates/psdb/cleanup.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/cleanup.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/cleanup.tt	(revision 17695)
@@ -0,0 +1,4 @@
+void [% pkg_namespace %]Cleanup(psDB *dbh)
+{
+    psDBCleanup(dbh);
+}
Index: /tags/rel-0_10/glueforge/templates/psdb/cleanup_at.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/cleanup_at.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/cleanup_at.tt	(revision 17695)
@@ -0,0 +1,6 @@
+AT_SETUP([[% table.namespace %]Cleanup()])
+AT_KEYWORDS([[% table.namespace %]Cleanup])
+
+AT_CHECK([cleanup])
+
+AT_CLEANUP
Index: /tags/rel-0_10/glueforge/templates/psdb/cleanup_c.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/cleanup_c.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/cleanup_c.tt	(revision 17695)
@@ -0,0 +1,17 @@
+#include <pslib.h>
+#include <[% pkg_name %].h>
+#include <stdlib.h>
+
+int main ()
+{
+    psDB            *dbh;
+
+    dbh = psDBInit("localhost", "test", NULL, "test");
+    if (!dbh) {
+        exit(EXIT_FAILURE);
+    }
+
+    [% pkg_namespace %]Cleanup(dbh);
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/rel-0_10/glueforge/templates/psdb/cleanup_h.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/cleanup_h.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/cleanup_h.tt	(revision 17695)
@@ -0,0 +1,6 @@
+/** Closes a database connection
+ */
+
+void [% pkg_namespace %]Cleanup(
+    psDB            *dbh                ///< Database handle
+);
Index: /tags/rel-0_10/glueforge/templates/psdb/code.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/code.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/code.tt	(revision 17695)
@@ -0,0 +1,26 @@
+#include "[% pkg_name %].h"
+
+[% FOREACH table = tables -%]
+#define [% table.namespace FILTER upper %]_TABLE_NAME "[% table.name %]"
+#define [% table.namespace FILTER upper %]_INDEX_NAME "position"
+[% END -%]
+#define MAX_STRING_LENGTH 1024
+
+[% INCLUDE init.tt %]
+[% INCLUDE cleanup.tt %]
+[% FOREACH table = tables -%]
+[% INCLUDE alloc.tt %]
+[% INCLUDE createtable.tt %]
+[% INCLUDE droptable.tt %]
+[% INCLUDE insert.tt %]
+[% INCLUDE pop.tt %]
+[% INCLUDE insertobject.tt %]
+[% INCLUDE popobject.tt %]
+[% INCLUDE insertfits.tt %]
+[% INCLUDE popfits.tt %]
+[% INCLUDE selectrowsfits.tt %]
+[% INCLUDE metadatafromobject.tt %]
+[% INCLUDE objectfrommetadata.tt -%]
+[% INCLUDE selectrowobjects.tt -%]
+[% INCLUDE deleterowobjects.tt -%]
+[% END -%]
Index: /tags/rel-0_10/glueforge/templates/psdb/config.pl
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/config.pl	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/config.pl	(revision 17695)
@@ -0,0 +1,28 @@
+%tt = (
+    'autogen_sh.tt'         => "$output/autogen.sh",
+    'configure_ac.tt'       => "$output/configure.ac",
+    'pkgconfig_pc_in.tt'    => "$output/$data->{pkg_name}.pc.in",
+    'doxyfile_in.tt'        => "$output/Doxyfile.in",
+    'top_makefile_am.tt'    => "$output/Makefile.am",
+    'src_makefile_am.tt'    => "$output/src/Makefile.am",
+    'header.tt'             => "$output/src/$data->{pkg_name}.h",
+    'code.tt'               => "$output/src/$data->{pkg_name}.c",
+    'tests_makefile_am.tt'  => "$output/tests/Makefile.am",
+    'testsuite_at.tt'       => "$output/tests/testsuite.at",
+    'dbsetup_c.tt'          => "$output/tests/dbsetup.c",
+    'dbcleanup_c.tt'        => "$output/tests/dbcleanup.c",
+    'alloc_c.tt'            => "$output/tests/alloc.c",
+    'init_c.tt'             => "$output/tests/init.c",
+    'cleanup_c.tt'          => "$output/tests/cleanup.c",
+    'createtable_c.tt'      => "$output/tests/createtable.c",
+    'droptable_c.tt'        => "$output/tests/droptable.c",
+    'insert_c.tt'           => "$output/tests/insert.c",
+    'pop_c.tt'              => "$output/tests/pop.c",
+    'insertobject_c.tt'     => "$output/tests/insertobject.c",
+    'popobject_c.tt'        => "$output/tests/popobject.c",
+    'insertfits_c.tt'       => "$output/tests/insertfits.c",
+    'popfits_c.tt'          => "$output/tests/popfits.c",
+    'selectrowsfits_c.tt'   => "$output/tests/selectrowsfits.c",
+    'metadatafromobject_c.tt' => "$output/tests/metadatafromobject.c",
+    'objectfrommetadata_c.tt' => "$output/tests/objectfrommetadata.c",
+);
Index: /tags/rel-0_10/glueforge/templates/psdb/configure_ac.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/configure_ac.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/configure_ac.tt	(revision 17695)
@@ -0,0 +1,39 @@
+AC_PREREQ(2.59)
+
+AC_INIT([[% pkg_name %]], [0.0.1], [pan-starrs.ifa.hawaii.edu])
+AC_CONFIG_SRCDIR([[% pkg_name %].pc.in])
+
+AM_INIT_AUTOMAKE([1.6 foreign dist-bzip2])
+AM_CONFIG_HEADER([config.h])
+AM_MAINTAINER_MODE
+
+AC_CONFIG_TESTDIR([tests])
+AC_CONFIG_FILES([tests/Makefile])
+AM_MISSING_PROG([AUTOM4TE], [autom4te])
+
+AC_LANG(C)
+AC_PROG_CC
+AC_PROG_INSTALL
+AC_PROG_LIBTOOL
+
+PKG_CHECK_MODULES([PSLIB], [pslib >= 0.9.0]) 
+
+AC_PATH_PROG([PERL], [perl], [missing])
+if test "$PERL" = "missing" ; then
+  AC_MSG_ERROR([perl is required])
+fi
+
+AC_PATH_PROG([doxygen], [doxygen], [missing])
+AM_CONDITIONAL([DOXYGEN], test "x$doxygen" = "xmissing")
+
+dnl is this the best was to setup recursive CFLAGS?
+[% pkg_name %]_CFLAGS="-Wall -pedantic -std=c99 -fno-strict-aliasing"
+AC_SUBST([[% pkg_name %]_CFLAGS])
+
+AC_CONFIG_FILES([
+  Makefile
+  src/Makefile
+  [% pkg_name %].pc
+  Doxyfile
+])
+AC_OUTPUT
Index: /tags/rel-0_10/glueforge/templates/psdb/createtable.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/createtable.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/createtable.tt	(revision 17695)
@@ -0,0 +1,43 @@
+bool [% table.namespace %]CreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, [% table.namespace FILTER upper %]_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", [% table.namespace FILTER upper %]_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+[% FOREACH item = table.columns -%]
+[% IF item.comment;
+    SET item.comment = "\"$item.comment\"";
+ELSE;
+    SET item.comment = 'NULL';
+END;
+-%]
+[% IF item.type == "S32" or item.type == "F32" or item.type == "F64" -%]
+    if (!psMetadataAdd[% item.type %](md, PS_LIST_TAIL, "[% item.name %]", 0, [% item.comment %], [% item.value %])) {
+[% ELSIF item.type == "STR" -%]
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "[% item.name %]", 0, [% item.comment %], "[% item.value %]")) {
+[% ELSIF item.type == "BOOL" -%]
+[% IF item.value == 1;
+    SET item.value = "true";
+ELSE;
+    SET item.value = "false";
+END;
+-%]
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "[% item.name %]", PS_DATA_BOOL, [% item.comment %], [% item.value %])) {
+[% END -%]
+        psError(PS_ERR_UNKNOWN, false, "failed to add item [% item.name %]");
+        psFree(md);
+        return false;
+    }
+[% END -%]
+
+    status = psDBCreateTable(dbh, [% table.namespace FILTER upper %]_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
Index: /tags/rel-0_10/glueforge/templates/psdb/createtable_at.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/createtable_at.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/createtable_at.tt	(revision 17695)
@@ -0,0 +1,9 @@
+AT_SETUP([[% table.namespace %]CreateTable()])
+AT_KEYWORDS([[% table.namespace %]CreateTable])
+
+# make sure the table is not there.
+AT_CHECK([dbcleanup])
+AT_CHECK([createtable])
+AT_CHECK([dbcleanup])
+
+AT_CLEANUP
Index: /tags/rel-0_10/glueforge/templates/psdb/createtable_c.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/createtable_c.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/createtable_c.tt	(revision 17695)
@@ -0,0 +1,25 @@
+#include <pslib.h>
+#include <[% pkg_name %].h>
+#include <stdlib.h>
+
+int main ()
+{
+[% FOREACH table = tables -%]
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(![% table.namespace %]CreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+[% END -%]
+    exit(EXIT_SUCCESS);
+}
Index: /tags/rel-0_10/glueforge/templates/psdb/createtable_h.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/createtable_h.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/createtable_h.tt	(revision 17695)
@@ -0,0 +1,8 @@
+/** Creates a new [% table.name %] table
+ *
+ * @return true on success
+ */
+
+bool [% table.namespace %]CreateTable(
+    psDB            *dbh                ///< Database handle
+);
Index: /tags/rel-0_10/glueforge/templates/psdb/dbcleanup_c.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/dbcleanup_c.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/dbcleanup_c.tt	(revision 17695)
@@ -0,0 +1,20 @@
+#include <pslib.h>
+#include <stdlib.h>
+
+int main ()
+{
+    psDB            *dbh;
+
+    dbh = psDBInit("localhost", "test", NULL, "test");
+    if (!dbh) {
+        exit(EXIT_FAILURE);
+    }
+
+[% FOREACH table = tables -%]
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS [% table.name %]");
+[% END -%]
+
+    psDBCleanup(dbh);
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/rel-0_10/glueforge/templates/psdb/dbsetup_c.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/dbsetup_c.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/dbsetup_c.tt	(revision 17695)
@@ -0,0 +1,23 @@
+#include <pslib.h>
+#include <[% pkg_name %].h>
+#include <stdlib.h>
+
+int main ()
+{
+    psDB            *dbh;
+
+    dbh = psDBInit("localhost", "test", NULL, "test");
+    if (!dbh) {
+        exit(EXIT_FAILURE);
+    }
+
+    // remove the table if it already exists
+[% FOREACH table = tables -%]
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS [% table.name %]");
+    [% table.namespace %]CreateTable(dbh);
+
+[% END -%]
+    psDBCleanup(dbh);
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/rel-0_10/glueforge/templates/psdb/deleterowobjects.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/deleterowobjects.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/deleterowobjects.tt	(revision 17695)
@@ -0,0 +1,19 @@
+long long [% table.namespace %]DeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        [% table.object_name %] *object = objects->data[i];
+        psMetadata *where = [% table.namespace %]MetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, [% table.namespace FILTER upper %]_TABLE_NAME, where, limit);
+        psFree(where)
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from [% table.namespace %]");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
Index: /tags/rel-0_10/glueforge/templates/psdb/deleterowobjects_h.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/deleterowobjects_h.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/deleterowobjects_h.tt	(revision 17695)
@@ -0,0 +1,13 @@
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long [% table.namespace %]DeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
Index: /tags/rel-0_10/glueforge/templates/psdb/doxyfile_in.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/doxyfile_in.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/doxyfile_in.tt	(revision 17695)
@@ -0,0 +1,1219 @@
+# Doxyfile 1.4.2
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project
+#
+# All text after a hash (#) is considered a comment and will be ignored
+# The format is:
+#       TAG = value [value, ...]
+# For lists items can also be appended using:
+#       TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ")
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded 
+# by quotes) that should identify the project.
+
+PROJECT_NAME           = @PACKAGE_NAME@
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. 
+# This could be handy for archiving the generated documentation or 
+# if some version control system is used.
+
+PROJECT_NUMBER         = @PACKAGE_VERSION@
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) 
+# base path where the generated documentation will be put. 
+# If a relative path is entered, it will be relative to the location 
+# where doxygen was started. If left blank the current directory will be used.
+
+# @top_builddir@ doesn't work for some reason
+OUTPUT_DIRECTORY       = @builddir@/docs
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 
+# 4096 sub-directories (in 2 levels) under the output directory of each output 
+# format and will distribute the generated files over these directories. 
+# Enabling this option can be useful when feeding doxygen a huge amount of 
+# source files, where putting all generated files in the same directory would 
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS         = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all 
+# documentation generated by doxygen is written. Doxygen will use this 
+# information to generate all constant output in the proper language. 
+# The default language is English, other supported languages are: 
+# Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, 
+# Dutch, Finnish, French, German, Greek, Hungarian, Italian, Japanese, 
+# Japanese-en (Japanese with English messages), Korean, Korean-en, Norwegian, 
+# Polish, Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, 
+# Swedish, and Ukrainian.
+
+OUTPUT_LANGUAGE        = English
+
+# This tag can be used to specify the encoding used in the generated output. 
+# The encoding is not always determined by the language that is chosen, 
+# but also whether or not the output is meant for Windows or non-Windows users. 
+# In case there is a difference, setting the USE_WINDOWS_ENCODING tag to YES 
+# forces the Windows encoding (this is the default for the Windows binary), 
+# whereas setting the tag to NO uses a Unix-style encoding (the default for 
+# all platforms other than Windows).
+
+USE_WINDOWS_ENCODING   = NO
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will 
+# include brief member descriptions after the members that are listed in 
+# the file and class documentation (similar to JavaDoc). 
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend 
+# the brief description of a member or function before the detailed description. 
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the 
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator 
+# that is used to form the text in various listings. Each string 
+# in this list, if found as the leading text of the brief description, will be 
+# stripped from the text and the result after processing the whole list, is 
+# used as the annotated text. Otherwise, the brief description is used as-is. 
+# If left blank, the following values are used ("$name" is automatically 
+# replaced with the name of the entity): "The $name class" "The $name widget" 
+# "The $name file" "is" "provides" "specifies" "contains" 
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF       = 
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then 
+# Doxygen will generate a detailed section even if there is only a brief 
+# description.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all 
+# inherited members of a class in the documentation of that class as if those 
+# members were ordinary class members. Constructors, destructors and assignment 
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full 
+# path before files name in the file list and in the header files. If set 
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES        = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag 
+# can be used to strip a user-defined part of the path. Stripping is 
+# only done if one of the specified strings matches the left-hand part of 
+# the path. The tag can be used to show relative paths in the file list. 
+# If left blank the directory from which doxygen is run is used as the 
+# path to strip.
+
+STRIP_FROM_PATH        = 
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of 
+# the path mentioned in the documentation of a class, which tells 
+# the reader which header file to include in order to use a class. 
+# If left blank only the name of the header file containing the class 
+# definition is used. Otherwise one should specify the include paths that 
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH    = 
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter 
+# (but less readable) file names. This can be useful is your file systems 
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen 
+# will interpret the first line (until the first dot) of a JavaDoc-style 
+# comment as the brief description. If set to NO, the JavaDoc 
+# comments will behave just like the Qt-style comments (thus requiring an 
+# explicit @brief command for a brief description.
+
+JAVADOC_AUTOBRIEF      = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen 
+# treat a multi-line C++ special comment block (i.e. a block of //! or /// 
+# comments) as a brief description. This used to be the default behaviour. 
+# The new default is to treat a multi-line C++ comment block as a detailed 
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the DETAILS_AT_TOP tag is set to YES then Doxygen 
+# will output the detailed description near the top, like JavaDoc.
+# If set to NO, the detailed description appears after the member 
+# documentation.
+
+DETAILS_AT_TOP         = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented 
+# member inherits the documentation from any documented member that it 
+# re-implements.
+
+INHERIT_DOCS           = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC 
+# tag is set to YES, then doxygen will reuse the documentation of the first 
+# member in the group (if any) for the other members of the group. By default 
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce 
+# a new page for each member. If set to NO, the documentation of a member will 
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. 
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE               = 4
+
+# This tag can be used to specify a number of aliases that acts 
+# as commands in the documentation. An alias has the form "name=value". 
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to 
+# put the command \sideeffect (or @sideeffect) in the documentation, which 
+# will result in a user-defined paragraph with heading "Side Effects:". 
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES                = 
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C 
+# sources only. Doxygen will then generate output that is more tailored for C. 
+# For instance, some of the names that are used will be different. The list 
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C  = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java sources 
+# only. Doxygen will then generate output that is more tailored for Java. 
+# For instance, namespaces will be presented as packages, qualified scopes 
+# will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of 
+# the same type (for instance a group of public functions) to be put as a 
+# subgroup of that type (e.g. under the Public Functions section). Set it to 
+# NO to prevent subgrouping. Alternatively, this can be done per class using 
+# the \nosubgrouping command.
+
+SUBGROUPING            = YES
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in 
+# documentation are documented, even if no documentation was available. 
+# Private class members and static file members will be hidden unless 
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL            = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class 
+# will be included in the documentation.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file 
+# will be included in the documentation.
+
+EXTRACT_STATIC         = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) 
+# defined locally in source files will be included in the documentation. 
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. When set to YES local 
+# methods, which are defined in the implementation section but not in 
+# the interface are included in the documentation. 
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all 
+# undocumented members of documented classes, files or namespaces. 
+# If set to NO (the default) these members will be included in the 
+# various overviews, but no documentation section is generated. 
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all 
+# undocumented classes that are normally visible in the class hierarchy. 
+# If set to NO (the default) these classes will be included in the various 
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all 
+# friend (class|struct|union) declarations. 
+# If set to NO (the default) these declarations will be included in the 
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any 
+# documentation blocks found inside the body of a function. 
+# If set to NO (the default) these blocks will be appended to the 
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation 
+# that is typed after a \internal command is included. If the tag is set 
+# to NO (the default) then the documentation will be excluded. 
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate 
+# file names in lower-case letters. If set to YES upper-case letters are also 
+# allowed. This is useful if you have classes or files whose names only differ 
+# in case and if your file system supports case sensitive file names. Windows 
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES       = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen 
+# will show members with their full class and namespace scopes in the 
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen 
+# will put a list of the files that are included by a file in the documentation 
+# of that file.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] 
+# is inserted in the documentation for inline members.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen 
+# will sort the (detailed) documentation of file and class members 
+# alphabetically by member name. If set to NO the members will appear in 
+# declaration order.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the 
+# brief documentation of file, namespace and class members alphabetically 
+# by member name. If set to NO (the default) the members will appear in 
+# declaration order.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be 
+# sorted by fully-qualified names, including namespaces. If set to 
+# NO (the default), the class list will be sorted only by class name, 
+# not including the namespace part. 
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the 
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or 
+# disable (NO) the todo list. This list is created by putting \todo 
+# commands in the documentation.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or 
+# disable (NO) the test list. This list is created by putting \test 
+# commands in the documentation.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or 
+# disable (NO) the bug list. This list is created by putting \bug 
+# commands in the documentation.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or 
+# disable (NO) the deprecated list. This list is created by putting 
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional 
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS       = 
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines 
+# the initial value of a variable or define consists of for it to appear in 
+# the documentation. If the initializer consists of more lines than specified 
+# here it will be hidden. Use a value of 0 to hide initializers completely. 
+# The appearance of the initializer of individual variables and defines in the 
+# documentation can be controlled using \showinitializer or \hideinitializer 
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated 
+# at the bottom of the documentation of classes and structs. If set to YES the 
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES        = YES
+
+# If the sources in your project are distributed over multiple directories 
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy 
+# in the documentation.
+
+SHOW_DIRECTORIES       = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that 
+# doxygen should invoke to get the current version for each file (typically from the 
+# version control system). Doxygen will invoke the program by executing (via 
+# popen()) the command <command> <input-file>, where <command> is the value of 
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file 
+# provided by doxygen. Whatever the progam writes to standard output 
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER    = 
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated 
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are 
+# generated by doxygen. Possible values are YES and NO. If left blank 
+# NO is used.
+
+WARNINGS               = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings 
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will 
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for 
+# potential errors in the documentation, such as not documenting some 
+# parameters in a documented function, or documenting parameters that 
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR      = YES
+
+# This WARN_NO_PARAMDOC option can be abled to get warnings for 
+# functions that are documented, but have no documentation for their parameters 
+# or return value. If set to NO (the default) doxygen will only warn about 
+# wrong or incomplete parameter documentation, but not about the absence of 
+# documentation.
+
+WARN_NO_PARAMDOC       = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that 
+# doxygen can produce. The string should contain the $file, $line, and $text 
+# tags, which will be replaced by the file and line number from which the 
+# warning originated and the warning text. Optionally the format may contain 
+# $version, which will be replaced by the version of the file (if it could 
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning 
+# and error messages should be written. If left blank the output is written 
+# to stderr.
+
+WARN_LOGFILE           = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain 
+# documented source files. You may enter file names like "myfile.cpp" or 
+# directories like "/usr/src/myproject". Separate the files or directories 
+# with spaces.
+
+INPUT                  = @top_srcdir@/src
+
+# If the value of the INPUT tag contains directories, you can use the 
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp 
+# and *.h) to filter out the source-files in the directories. If left 
+# blank the following patterns are tested: 
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx 
+# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm
+
+FILE_PATTERNS          = *.h
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories 
+# should be searched for input files as well. Possible values are YES and NO. 
+# If left blank NO is used.
+
+RECURSIVE              = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should 
+# excluded from the INPUT source files. This way you can easily exclude a 
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE                = 
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or 
+# directories that are symbolic links (a Unix filesystem feature) are excluded 
+# from the input.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the 
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude 
+# certain files from those directories.
+
+EXCLUDE_PATTERNS       = 
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or 
+# directories that contain example code fragments that are included (see 
+# the \include command).
+
+EXAMPLE_PATH           = 
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the 
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp 
+# and *.h) to filter out the source-files in the directories. If left 
+# blank all files are included.
+
+EXAMPLE_PATTERNS       = 
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be 
+# searched for input files to be used with the \include or \dontinclude 
+# commands irrespective of the value of the RECURSIVE tag. 
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or 
+# directories that contain image that are included in the documentation (see 
+# the \image command).
+
+IMAGE_PATH             = 
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should 
+# invoke to filter for each input file. Doxygen will invoke the filter program 
+# by executing (via popen()) the command <filter> <input-file>, where <filter> 
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an 
+# input file. Doxygen will then use the output that the filter program writes 
+# to standard output.  If FILTER_PATTERNS is specified, this tag will be 
+# ignored.
+
+INPUT_FILTER           = 
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern 
+# basis.  Doxygen will compare the file name with each pattern and apply the 
+# filter if there is a match.  The filters are a list of the form: 
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further 
+# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER 
+# is applied to all files.
+
+FILTER_PATTERNS        = 
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using 
+# INPUT_FILTER) will be used to filter the input files when producing source 
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES    = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will 
+# be generated. Documented entities will be cross-referenced with these sources. 
+# Note: To get rid of all source code in the generated output, make sure also 
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body 
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct 
+# doxygen to hide any special comment blocks from generated source code 
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES (the default) 
+# then for each documented function all documented 
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES (the default) 
+# then for each documented function all documented entities 
+# called/used by that function will be listed.
+
+REFERENCES_RELATION    = YES
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen 
+# will generate a verbatim copy of the header file for each class for 
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index 
+# of all compounds will be generated. Enable this if the project 
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX     = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then 
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns 
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all 
+# classes will be put under the same header in the alphabetical index. 
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that 
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX          = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will 
+# generate HTML output.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for 
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank 
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for 
+# each generated HTML page. If it is left blank doxygen will generate a 
+# standard header.
+
+HTML_HEADER            = 
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for 
+# each generated HTML page. If it is left blank doxygen will generate a 
+# standard footer.
+
+HTML_FOOTER            = 
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading 
+# style sheet that is used by each HTML page. It can be used to 
+# fine-tune the look of the HTML output. If the tag is left blank doxygen 
+# will generate a default style sheet. Note that doxygen will try to copy 
+# the style sheet file to the HTML output directory, so don't put your own 
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET        = 
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, 
+# files or namespaces will be aligned in HTML using tables. If set to 
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS     = YES
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files 
+# will be generated that can be used as input for tools like the 
+# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) 
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP      = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can 
+# be used to specify the file name of the resulting .chm file. You 
+# can add a path in front of the file if the result should not be 
+# written to the html output directory.
+
+CHM_FILE               = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can 
+# be used to specify the location (absolute path including file name) of 
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run 
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION           = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag 
+# controls if a separate .chi index file is generated (YES) or that 
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI           = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag 
+# controls whether a binary table of contents is generated (YES) or a 
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members 
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND             = NO
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at 
+# top of each HTML page. The value NO (the default) enables the index and 
+# the value YES disables it.
+
+DISABLE_INDEX          = NO
+
+# This tag can be used to set the number of enum values (range [1..20]) 
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be
+# generated containing a tree-like index structure (just like the one that 
+# is generated for HTML Help). For this to work a browser that supports 
+# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, 
+# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are 
+# probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW      = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be 
+# used to set the initial width (in pixels) of the frame in which the tree 
+# is shown.
+
+TREEVIEW_WIDTH         = 250
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will 
+# generate Latex output.
+
+GENERATE_LATEX         = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be 
+# invoked. If left blank `latex' will be used as the default command name.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to 
+# generate index for LaTeX. If left blank `makeindex' will be used as the 
+# default command name.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact 
+# LaTeX documents. This may be useful for small projects and may help to 
+# save some trees in general.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used 
+# by the printer. Possible values are: a4, a4wide, letter, legal and 
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE             = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX 
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES         = 
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for 
+# the generated latex document. The header should contain everything until 
+# the first chapter. If it is left blank doxygen will generate a 
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER           = 
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated 
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will 
+# contain links (just like the HTML output) instead of page references 
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS         = NO
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of 
+# plain latex in the generated Makefile. Set this option to YES to get a 
+# higher quality PDF documentation.
+
+USE_PDFLATEX           = NO
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. 
+# command to the generated LaTeX files. This will instruct LaTeX to keep 
+# running if errors occur, instead of asking the user for help. 
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE        = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not 
+# include the index chapters (such as File Index, Compound Index, etc.) 
+# in the output.
+
+LATEX_HIDE_INDICES     = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output 
+# The RTF output is optimized for Word 97 and may not look very pretty with 
+# other RTF readers or editors.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact 
+# RTF documents. This may be useful for small projects and may help to 
+# save some trees in general.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated 
+# will contain hyperlink fields. The RTF file will 
+# contain links (just like the HTML output) instead of page references. 
+# This makes the output suitable for online browsing using WORD or other 
+# programs which support those fields. 
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's 
+# config file, i.e. a series of assignments. You only have to provide 
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE    = 
+
+# Set optional variables used in the generation of an rtf document. 
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE    = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will 
+# generate man pages
+
+GENERATE_MAN           = YES
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to 
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION          = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output, 
+# then it will generate one additional man file for each entity 
+# documented in the real man page(s). These additional files 
+# only source the real man page, but without them the man command 
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will 
+# generate an XML file that captures the structure of 
+# the code including all documentation.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT             = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema, 
+# which can be used by a validating XML parser to check the 
+# syntax of the XML files.
+
+XML_SCHEMA             = 
+
+# The XML_DTD tag can be used to specify an XML DTD, 
+# which can be used by a validating XML parser to check the 
+# syntax of the XML files.
+
+XML_DTD                = 
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will 
+# dump the program listings (including syntax highlighting 
+# and cross-referencing information) to the XML output. Note that 
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will 
+# generate an AutoGen Definitions (see autogen.sf.net) file 
+# that captures the structure of the code including all 
+# documentation. Note that this feature is still experimental 
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will 
+# generate a Perl module file that captures the structure of 
+# the code including all documentation. Note that this 
+# feature is still experimental and incomplete at the 
+# moment.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate 
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able 
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be 
+# nicely formatted so it can be parsed by a human reader.  This is useful 
+# if you want to understand what is going on.  On the other hand, if this 
+# tag is set to NO the size of the Perl module output will be much smaller 
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file 
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. 
+# This is useful so different doxyrules.make files included by the same 
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX = 
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor   
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will 
+# evaluate all C-preprocessor directives found in the sources and include 
+# files.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro 
+# names in the source code. If set to NO (the default) only conditional 
+# compilation will be performed. Macro expansion can be done in a controlled 
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES 
+# then the macro expansion is limited to the macros specified with the 
+# PREDEFINED and EXPAND_AS_PREDEFINED tags.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files 
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that 
+# contain include files that are not input files but should be processed by 
+# the preprocessor.
+
+INCLUDE_PATH           = 
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard 
+# patterns (like *.h and *.hpp) to filter out the header-files in the 
+# directories. If left blank, the patterns specified with FILE_PATTERNS will 
+# be used.
+
+INCLUDE_FILE_PATTERNS  = 
+
+# The PREDEFINED tag can be used to specify one or more macro names that 
+# are defined before the preprocessor is started (similar to the -D option of 
+# gcc). The argument of the tag is a list of macros of the form: name 
+# or name=definition (no spaces). If the definition and the = are 
+# omitted =1 is assumed. To prevent a macro definition from being 
+# undefined via #undef or recursively expanded use the := operator 
+# instead of the = operator.
+
+PREDEFINED             = 
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then 
+# this tag can be used to specify a list of macro names that should be expanded. 
+# The macro definition that is found in the sources will be used. 
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+EXPAND_AS_DEFINED      = 
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then 
+# doxygen's preprocessor will remove all function-like macros that are alone 
+# on a line, have an all uppercase name, and do not end with a semicolon. Such 
+# function macros are typically used for boiler-plate code, and will confuse 
+# the parser if not removed.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references   
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles. 
+# Optionally an initial location of the external documentation 
+# can be added for each tagfile. The format of a tag file without 
+# this location is as follows: 
+#   TAGFILES = file1 file2 ... 
+# Adding location for the tag files is done as follows: 
+#   TAGFILES = file1=loc1 "file2 = loc2" ... 
+# where "loc1" and "loc2" can be relative or absolute paths or 
+# URLs. If a location is present for each tag, the installdox tool 
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen 
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES               = 
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create 
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE       = 
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed 
+# in the class index. If set to NO only the inherited external classes 
+# will be listed.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed 
+# in the modules index. If set to NO, only the current project's groups will 
+# be listed.
+
+EXTERNAL_GROUPS        = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script 
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH              = @PERL@
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool   
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will 
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base 
+# or super classes. Setting the tag to NO turns the diagrams off. Note that 
+# this option is superseded by the HAVE_DOT option below. This is only a 
+# fallback. It is recommended to install and use dot, since it yields more 
+# powerful graphs.
+
+CLASS_DIAGRAMS         = YES
+
+# If set to YES, the inheritance and collaboration graphs will hide 
+# inheritance and usage relations if the target is undocumented 
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is 
+# available from the path. This tool is part of Graphviz, a graph visualization 
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section 
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT               = NO
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for each documented class showing the direct and 
+# indirect inheritance relations. Setting this tag to YES will force the 
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for each documented class showing the direct and 
+# indirect implementation dependencies (inheritance, containment, and 
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and 
+# collaboration diagrams in a style similar to the OMG's Unified Modeling 
+# Language.
+
+UML_LOOK               = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the 
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT 
+# tags are set to YES then doxygen will generate a graph for each documented 
+# file showing the direct and indirect include dependencies of the file with 
+# other documented files.
+
+INCLUDE_GRAPH          = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and 
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each 
+# documented header file showing the documented files that directly or 
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will 
+# generate a call dependency graph for every global function or class method. 
+# Note that enabling this option will significantly increase the time of a run. 
+# So in most cases it will be better to enable call graphs for selected 
+# functions only using the \callgraph command.
+
+CALL_GRAPH             = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen 
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES 
+# then doxygen will show the dependencies a directory has on other directories 
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images 
+# generated by dot. Possible values are png, jpg, or gif
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT       = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be 
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH               = 
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that 
+# contain dot files that are included in the documentation (see the 
+# \dotfile command).
+
+DOTFILE_DIRS           = 
+
+# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width 
+# (in pixels) of the graphs generated by dot. If a graph becomes larger than 
+# this value, doxygen will try to truncate the graph, so that it fits within 
+# the specified constraint. Beware that most browsers cannot cope with very 
+# large images.
+
+MAX_DOT_GRAPH_WIDTH    = 1024
+
+# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height 
+# (in pixels) of the graphs generated by dot. If a graph becomes larger than 
+# this value, doxygen will try to truncate the graph, so that it fits within 
+# the specified constraint. Beware that most browsers cannot cope with very 
+# large images.
+
+MAX_DOT_GRAPH_HEIGHT   = 1024
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the 
+# graphs generated by dot. A depth value of 3 means that only nodes reachable 
+# from the root by following a path via at most 3 edges will be shown. Nodes 
+# that lay further from the root node will be omitted. Note that setting this 
+# option to 1 or 2 may greatly reduce the computation time needed for large 
+# code bases. Also note that a graph may be further truncated if the graph's 
+# image dimensions are not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH 
+# and MAX_DOT_GRAPH_HEIGHT). If 0 is used for the depth value (the default), 
+# the graph is not depth-constrained.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent 
+# background. This is disabled by default, which results in a white background. 
+# Warning: Depending on the platform used, enabling this option may lead to 
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to 
+# read).
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output 
+# files in one run (i.e. multiple -o and -T options on the command line). This 
+# makes dot run faster, but since only newer versions of dot (>1.8.10) 
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will 
+# generate a legend page explaining the meaning of the various boxes and 
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will 
+# remove the intermediate dot files that are used to generate 
+# the various graphs.
+
+DOT_CLEANUP            = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to the search engine   
+#---------------------------------------------------------------------------
+
+# The SEARCHENGINE tag specifies whether or not a search engine should be 
+# used. If set to NO the values of all tags below this one will be ignored.
+
+SEARCHENGINE           = NO
Index: /tags/rel-0_10/glueforge/templates/psdb/droptable.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/droptable.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/droptable.tt	(revision 17695)
@@ -0,0 +1,4 @@
+bool [% table.namespace %]DropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, [% table.namespace FILTER upper %]_TABLE_NAME);
+}
Index: /tags/rel-0_10/glueforge/templates/psdb/droptable_at.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/droptable_at.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/droptable_at.tt	(revision 17695)
@@ -0,0 +1,9 @@
+AT_SETUP([[% table.namespace %]DropTable()])
+AT_KEYWORDS([[% table.namespace %]DropTable])
+
+# make sure the table is not there.
+AT_CHECK([dbsetup])
+AT_CHECK([droptable])
+AT_CHECK([dbcleanup])
+
+AT_CLEANUP
Index: /tags/rel-0_10/glueforge/templates/psdb/droptable_c.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/droptable_c.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/droptable_c.tt	(revision 17695)
@@ -0,0 +1,25 @@
+#include <pslib.h>
+#include <[% pkg_name %].h>
+#include <stdlib.h>
+
+int main ()
+{
+[% FOREACH table = tables -%]
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (![% table.namespace %]DropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+[% END -%]
+    exit(EXIT_SUCCESS);
+}
Index: /tags/rel-0_10/glueforge/templates/psdb/droptable_h.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/droptable_h.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/droptable_h.tt	(revision 17695)
@@ -0,0 +1,8 @@
+/** Deletes a [% table.name %] table
+ *
+ * @return true on success
+ */
+
+bool [% table.namespace %]DropTable(
+    psDB            *dbh                ///< Database handle
+);
Index: /tags/rel-0_10/glueforge/templates/psdb/header.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/header.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/header.tt	(revision 17695)
@@ -0,0 +1,39 @@
+#ifndef [% pkg_name FILTER upper %]_H
+#define [% pkg_name FILTER upper %]_H 1
+
+#include <pslib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/// @addtogroup [% pkg_name %]
+/// @{
+
+[% INCLUDE init_h.tt %]
+[% INCLUDE cleanup_h.tt %]
+[% FOREACH table = tables -%]
+[% INCLUDE object_h.tt %]
+[% INCLUDE alloc_h.tt %]
+[% INCLUDE createtable_h.tt %]
+[% INCLUDE droptable_h.tt %]
+[% INCLUDE insert_h.tt %]
+[% INCLUDE pop_h.tt %]
+[% INCLUDE insertobject_h.tt %]
+[% INCLUDE popobject_h.tt %]
+[% INCLUDE insertfits_h.tt %]
+[% INCLUDE popfits_h.tt %]
+[% INCLUDE selectrowsfits_h.tt %]
+[% INCLUDE metadatafromobject_h.tt %]
+[% INCLUDE objectfrommetadata_h.tt -%]
+[% INCLUDE selectrowobjects_h.tt -%]
+[% INCLUDE deleterowobjects_h.tt -%]
+[% END -%]
+
+/// @}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // [% table.namespace FILTER upper %]_DB_H
Index: /tags/rel-0_10/glueforge/templates/psdb/init.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/init.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/init.tt	(revision 17695)
@@ -0,0 +1,4 @@
+psDB *[% pkg_namespace %]Init(const char *host, const char *user, const char *passwd, const char *dbname)
+{
+    return psDBInit(host, user, passwd, dbname);
+}
Index: /tags/rel-0_10/glueforge/templates/psdb/init_at.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/init_at.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/init_at.tt	(revision 17695)
@@ -0,0 +1,6 @@
+AT_SETUP([[% table.namespace %]Init()])
+AT_KEYWORDS([[% table.namespace %]Init])
+
+AT_CHECK([init])
+
+AT_CLEANUP
Index: /tags/rel-0_10/glueforge/templates/psdb/init_c.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/init_c.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/init_c.tt	(revision 17695)
@@ -0,0 +1,17 @@
+#include <pslib.h>
+#include <[% pkg_name %].h>
+#include <stdlib.h>
+
+int main ()
+{
+    psDB            *dbh;
+
+    dbh = [% pkg_namespace %]Init("localhost", "test", NULL, "test");
+    if (!dbh) {
+        exit(EXIT_FAILURE);
+    }
+
+    psDBCleanup(dbh);
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/rel-0_10/glueforge/templates/psdb/init_h.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/init_h.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/init_h.tt	(revision 17695)
@@ -0,0 +1,12 @@
+/** Opens a new database connection
+ *
+ *  @return A new psDB object if the database connection is successful or NULL
+ *  on failure.
+ */
+
+psDB *[% pkg_namespace %]Init(
+    const char      *host,              ///< Database server hostname
+    const char      *user,              ///< Database username
+    const char      *passwd,            ///< Database password
+    const char      *dbname             ///< Database table.namespace
+);
Index: /tags/rel-0_10/glueforge/templates/psdb/insert.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/insert.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/insert.tt	(revision 17695)
@@ -0,0 +1,35 @@
+bool [% table.namespace %]Insert(psDB * dbh,
+[%- SET i = 0 -%]
+[% FOREACH item = table.columns -%]
+[% IF i == 0 %][% i = 1 %] [% ELSE %], [% END -%]
+[% IF item.type == "STR" -%]
+const char *[% item.name %]
+[%- ELSE -%]
+[% item.ctype %] [% item.name %]
+[%- END -%]
+[% END -%]
+)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+[% FOREACH item = table.columns -%]
+[% IF item.type == "S32" or item.type == "F32" or item.type == "F64" -%]
+    if (!psMetadataAdd[% item.type %](md, PS_LIST_TAIL, "[% item.name %]", 0, NULL, [% item.name %])) {
+[% ELSIF item.type == "STR" -%]
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "[% item.name %]", 0, NULL, psStringCopy([% item.name %]))) {
+[% ELSIF item.type == "BOOL" -%]
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "[% item.name %]", PS_DATA_BOOL, NULL, [% item.name %])) {
+[% END -%]
+        psError(PS_ERR_UNKNOWN, false, "failed to add item [% item.name %]");
+        psFree(md);
+        return false;
+    }
+[% END -%]
+
+    status = psDBInsertOneRow(dbh, [% table.namespace FILTER upper %]_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
Index: /tags/rel-0_10/glueforge/templates/psdb/insert_at.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/insert_at.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/insert_at.tt	(revision 17695)
@@ -0,0 +1,8 @@
+AT_SETUP([[% table.namespace %]Insert()])
+AT_KEYWORDS([[% table.namespace %]Insert])
+
+AT_CHECK([dbsetup])
+AT_CHECK([insert])
+AT_CHECK([dbcleanup])
+
+AT_CLEANUP
Index: /tags/rel-0_10/glueforge/templates/psdb/insert_c.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/insert_c.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/insert_c.tt	(revision 17695)
@@ -0,0 +1,32 @@
+[% USE format -%]
+[% indented = format('%-15s %s') -%]
+#include <pslib.h>
+#include <[% pkg_name %].h>
+#include <stdlib.h>
+
+int main ()
+{
+[% FOREACH table = tables -%]
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (![% table.namespace %]Insert(dbh, 
+[%- SET i = 0 -%]
+[%- FOREACH item = table.columns -%]
+[%- IF i == 0 -%][%- i = 1 -%][%- ELSE -%], [% END -%] [%- item.test -%]
+[%- END -%]
+)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+[% END -%]
+    exit(EXIT_SUCCESS);
+}
Index: /tags/rel-0_10/glueforge/templates/psdb/insert_h.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/insert_h.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/insert_h.tt	(revision 17695)
@@ -0,0 +1,21 @@
+[% USE format -%]
+[% indented = format('%-15s %s') -%]
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool [% table.namespace %]Insert(
+    psDB            *dbh,               ///< Database handle
+[%- SET i = 0 -%]
+[% FOREACH item = table.columns -%]
+[% IF i == 0 %][% i = 1 %][% ELSE %],[% END %]
+[% IF item.type == "STR" -%]
+    [% indented("const char", "*$item.name") -%]
+[% ELSE -%]
+    [% indented("$item.ctype", "$item.name") -%]
+[% END -%]
+[% END %]
+);
Index: /tags/rel-0_10/glueforge/templates/psdb/insertfits.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/insertfits.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/insertfits.tt	(revision 17695)
@@ -0,0 +1,34 @@
+bool [% table.namespace %]InsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  [% table.namespace FILTER upper %]_TABLE_NAME
+    if (!psFitsMoveExtName(fits, [% table.namespace FILTER upper %]_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", [% table.namespace FILTER upper %]_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, [% table.namespace FILTER upper %]_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
Index: /tags/rel-0_10/glueforge/templates/psdb/insertfits_at.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/insertfits_at.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/insertfits_at.tt	(revision 17695)
@@ -0,0 +1,15 @@
+AT_SETUP([[% table.namespace %]InsertFits()])
+AT_KEYWORDS([[% table.namespace %]InsertFits])
+
+AT_CHECK([dbsetup])
+# run insert so there is a row in the table
+AT_CHECK([insert])
+# run popfis so there is afits file to read
+AT_CHECK([popfits])
+AT_CHECK([insertfits])
+AT_CHECK([dbcleanup])
+if [ test -f "blargh" ] ; then
+    rm -f "blargh"
+fi
+
+AT_CLEANUP
Index: /tags/rel-0_10/glueforge/templates/psdb/insertfits_c.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/insertfits_c.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/insertfits_c.tt	(revision 17695)
@@ -0,0 +1,40 @@
+#include <pslib.h>
+#include <[% pkg_name %].h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#define TMP_FILENAME "./blargh"
+
+int main ()
+{
+[% FOREACH table = tables -%]
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (![% table.namespace %]InsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+[% END -%]
+    exit(EXIT_SUCCESS);
+}
Index: /tags/rel-0_10/glueforge/templates/psdb/insertfits_h.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/insertfits_h.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/insertfits_h.tt	(revision 17695)
@@ -0,0 +1,14 @@
+/** Insert data from a binary FITS table [% table.object_name %] into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool [% table.namespace %]InsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
Index: /tags/rel-0_10/glueforge/templates/psdb/insertobject.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/insertobject.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/insertobject.tt	(revision 17695)
@@ -0,0 +1,10 @@
+bool [% table.namespace %]InsertObject(psDB *dbh, [% table.object_name %] *object)
+{
+    return [% table.namespace %]Insert(dbh,
+[%- SET i = 0 -%]
+[%- FOREACH item = table.columns -%]
+[%- IF i == 0 -%][%- i = 1 -%] [% ELSE %], [% END -%]
+object->[% item.name -%]
+[%- END -%]
+);
+}
Index: /tags/rel-0_10/glueforge/templates/psdb/insertobject_at.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/insertobject_at.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/insertobject_at.tt	(revision 17695)
@@ -0,0 +1,8 @@
+AT_SETUP([[% table.namespace %]InsertObject()])
+AT_KEYWORDS([[% table.namespace %]InsertObject])
+
+AT_CHECK([dbsetup])
+AT_CHECK([insertobject])
+AT_CHECK([dbcleanup])
+
+AT_CLEANUP
Index: /tags/rel-0_10/glueforge/templates/psdb/insertobject_c.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/insertobject_c.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/insertobject_c.tt	(revision 17695)
@@ -0,0 +1,39 @@
+[% USE format -%]
+[% indented = format('%-15s %s') -%]
+#include <pslib.h>
+#include <[% pkg_name %].h>
+#include <stdlib.h>
+
+int main ()
+{
+[% FOREACH table = tables -%]
+    {
+        psDB            *dbh;
+        [% indented(table.object_name, "*object") %];
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = [% table.object_name %]Alloc(
+[%- SET i = 0 -%]
+[%- FOREACH item = table.columns -%]
+    [%- IF i == 0 -%][%- i = 1 -%][%- ELSE -%], [% END -%] [%- item.test -%]
+[%- END -%]
+);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (![% table.namespace %]InsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+[% END -%]
+    exit(EXIT_SUCCESS);
+}
Index: /tags/rel-0_10/glueforge/templates/psdb/insertobject_h.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/insertobject_h.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/insertobject_h.tt	(revision 17695)
@@ -0,0 +1,13 @@
+[% USE format -%]
+[% indented = format('%-15s %-20s') -%]
+/** Insert a single [% table.object_name %] object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool [% table.namespace %]InsertObject(
+    psDB            *dbh,               ///< Database handle
+    [% indented(table.object_name, "*object") %]///< [% table.object_name %] object
+);
Index: /tags/rel-0_10/glueforge/templates/psdb/makefile.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/makefile.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/makefile.tt	(revision 17695)
@@ -0,0 +1,19 @@
+SHELL   = /bin/sh
+CFLAGS  = -g -O0 -pipe -fpic -Wall -pedantic -std=c99
+LIBS    = -lpslib
+CFLAGS += `mysql_config --cflags`
+LIBS   += `mysql_config --libs`
+
+
+objects=[% table.namespace %]db.o
+
+all: lib[% table.namespace %]db.so
+
+lib[% table.namespace %]db.so: $(objects)
+	$(CC) $(CFLAGS) -shared -o lib[% table.namespace %]db.so $(objects) $(LIBS)
+
+$(objects): %.o : %.c
+	$(CC) $(CFLAGS) -o $@ -c $<
+
+clean:
+	$(RM) *.so *.o core test
Index: /tags/rel-0_10/glueforge/templates/psdb/metadatafromobject.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/metadatafromobject.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/metadatafromobject.tt	(revision 17695)
@@ -0,0 +1,21 @@
+psMetadata *[% table.namespace %]MetadataFromObject(const [% table.object_name %] *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+[% FOREACH item = table.columns -%]
+[% IF item.type == "S32" or item.type == "F32" or item.type == "F64" -%]
+    if (!psMetadataAdd[% item.type %](md, PS_LIST_TAIL, "[% item.name %]", 0, NULL, object->[% item.name %])) {
+[% ELSIF item.type == "STR" -%]
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "[% item.name %]", 0, NULL, psStringCopy(object->[% item.name %]))) {
+[% ELSIF item.type == "BOOL" -%]
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "[% item.name %]", PS_DATA_BOOL, NULL, object->[% item.name %])) {
+[% END -%]
+        psError(PS_ERR_UNKNOWN, false, "failed to add item [% item.name %]");
+        psFree(md);
+        return NULL;
+    }
+[% END -%]
+
+    return md;
+}
Index: /tags/rel-0_10/glueforge/templates/psdb/metadatafromobject_at.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/metadatafromobject_at.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/metadatafromobject_at.tt	(revision 17695)
@@ -0,0 +1,6 @@
+AT_SETUP([[% table.namespace %]MetadataFromObject()])
+AT_KEYWORDS([[% table.namespace %]MetadataFromObject])
+
+AT_CHECK([metadatafromobject])
+
+AT_CLEANUP
Index: /tags/rel-0_10/glueforge/templates/psdb/metadatafromobject_c.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/metadatafromobject_c.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/metadatafromobject_c.tt	(revision 17695)
@@ -0,0 +1,53 @@
+[% USE format -%]
+[% indented = format('%-15s %s') -%]
+#include <pslib.h>
+#include <[% pkg_name %].h>
+#include <stdlib.h>
+#include <string.h>
+
+#define MAX_STRING_LENGTH 1024
+
+int main ()
+{
+[% FOREACH table = tables -%]
+    {
+        psMetadata      *md;
+        [% indented(table.object_name, "*object") %];
+        bool            status;
+
+        object = [% table.object_name %]Alloc(
+[%- SET i = 0 -%]
+[%- FOREACH item = table.columns -%]
+    [%- IF i == 0 -%][%- i = 1 -%][%- ELSE -%], [% END -%] [%- item.test -%]
+[%- END -%]
+);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = [% table.namespace %]MetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+[% FOREACH item = table.columns -%]
+[% IF item.type == "S32" or item.type == "F32" or item.type == "F64" -%]
+        if (!psMetadataLookup[% item.type %](&status, md, "[% item.name %]") == [% item.test %]) {
+[% ELSIF item.type == "STR" -%]
+        if (strncmp(psMetadataLookupPtr(&status, md, "[% item.name %]"), [% item.test %], MAX_STRING_LENGTH)) {
+[% ELSIF item.type == "BOOL" -%]
+        if (!psMetadataLookupBool(&status, md, "[% item.name %]") == [% item.test %]) {
+[% END -%]
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+[% END -%]
+
+        psFree(md);
+    }
+
+[% END -%]
+    exit(EXIT_SUCCESS);
+}
Index: /tags/rel-0_10/glueforge/templates/psdb/metadatafromobject_h.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/metadatafromobject_h.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/metadatafromobject_h.tt	(revision 17695)
@@ -0,0 +1,10 @@
+[% USE format -%]
+[% indented = format('%-15s %-20s') -%]
+/** Convert a [% table.object_name %] into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *[% table.namespace %]MetadataFromObject(
+    [% indented("const $table.object_name", "*object") %]///< fooRow to convert into a psMetadata
+);
Index: /tags/rel-0_10/glueforge/templates/psdb/object_h.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/object_h.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/object_h.tt	(revision 17695)
@@ -0,0 +1,17 @@
+[% USE format -%]
+[% indented = format('%-15s %s') -%]
+/** [% table.object_name %] data structure
+ *
+ * Structure for representing a single row of [% table.name %] table data.
+ */
+
+typedef struct {
+[% SET i = 0 -%]
+[% FOREACH item = table.columns -%]
+[% IF item.type == "STR" -%]
+    [% indented("char", "*$item.name") %];
+[% ELSE -%]
+    [% indented("$item.ctype", "$item.name") %];
+[% END -%]
+[% END -%]
+} [% table.object_name %];
Index: /tags/rel-0_10/glueforge/templates/psdb/objectfrommetadata.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/objectfrommetadata.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/objectfrommetadata.tt	(revision 17695)
@@ -0,0 +1,34 @@
+[% USE format -%]
+[% indented = format('%-15s %s') -%]
+[% table.object_name %] *[% table.namespace %]ObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+[% FOREACH item = table.columns -%]
+[% IF item.type == "STR" -%]
+    [% indented("char", "*$item.name") %];
+[% ELSE-%]
+    [% indented("$item.ctype", "$item.name") %];
+[% END -%]
+[% END -%]
+
+[% FOREACH item = table.columns -%]
+[% IF item.type == "S32" or item.type == "F32" or item.type == "F64" -%]
+    [% item.name %] = psMetadataLookup[% item.type %](&status, md, "[% item.name %]");
+[% ELSIF item.type == "STR" -%]
+    [% item.name %] = psMetadataLookupPtr(&status, md, "[% item.name %]");
+[% ELSIF item.type == "BOOL" -%]
+    [% item.name %] = psMetadataLookupBool(&status, md, "[% item.name %]");
+[% END -%]
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item [% item.name %]");
+        return false;
+    }
+[% END -%]
+
+    return [% table.object_name %]Alloc(
+[%- SET i = 0 -%]
+[% FOREACH item = table.columns -%]
+[% IF i == 0 -%][%- i = 1 -%][% ELSE %], [% END -%]
+[% item.name %]
+[%- END -%]);
+}
Index: /tags/rel-0_10/glueforge/templates/psdb/objectfrommetadata_at.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/objectfrommetadata_at.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/objectfrommetadata_at.tt	(revision 17695)
@@ -0,0 +1,6 @@
+AT_SETUP([[% table.namespace %]ObjectFromMetadata()])
+AT_KEYWORDS([[% table.namespace %]ObjectFromMetadata])
+
+AT_CHECK([objectfrommetadata])
+
+AT_CLEANUP
Index: /tags/rel-0_10/glueforge/templates/psdb/objectfrommetadata_c.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/objectfrommetadata_c.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/objectfrommetadata_c.tt	(revision 17695)
@@ -0,0 +1,55 @@
+[% USE format -%]
+[% indented = format('%-15s %s') -%]
+#include <pslib.h>
+#include <[% pkg_name %].h>
+#include <stdlib.h>
+#include <string.h>
+
+#define MAX_STRING_LENGTH 1024
+
+int main ()
+{
+[% FOREACH table = tables -%]
+    {
+        psMetadata      *md;
+        [% indented(table.object_name, "*object") %];
+
+        md = psMetadataAlloc();
+[% FOREACH item = table.columns -%]
+[% IF item.type == "S32" or item.type == "F32" or item.type == "F64" -%]
+        if (!psMetadataAdd[% item.type %](md, PS_LIST_TAIL, "[% item.name %]", 0, NULL, [% item.test %])) {
+[% ELSIF item.type == "STR" -%]
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "[% item.name %]", 0, NULL, [% item.test %])) {
+[% ELSIF item.type == "BOOL" -%]
+        if (!psMetadataAdd(md, PS_LIST_TAIL, "[% item.name %]", PS_DATA_BOOL, NULL, [% item.test %])) {
+[% END -%]
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+[% END -%]
+
+        object = [% table.namespace %]ObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+[% FOREACH item = table.columns -%]
+[% IF item.type == "S32" or item.type == "F32" or item.type == "F64" or item.type == "BOOL" -%]
+        if (!object->[% item.name %] == [% item.test %]) {
+[% ELSIF item.type == "STR" -%]
+        if (strncmp(object->[% item.name %], [% item.test %], MAX_STRING_LENGTH)) {
+[% END -%]
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+[% END -%]
+
+        psFree(object);
+    }
+
+[% END -%]
+    exit(EXIT_SUCCESS);
+}
Index: /tags/rel-0_10/glueforge/templates/psdb/objectfrommetadata_h.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/objectfrommetadata_h.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/objectfrommetadata_h.tt	(revision 17695)
@@ -0,0 +1,8 @@
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A [% table.object_name %] pointer or NULL on error
+ */
+
+[% table.object_name %] *[% table.namespace %]ObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
Index: /tags/rel-0_10/glueforge/templates/psdb/pkgconfig_pc_in.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/pkgconfig_pc_in.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/pkgconfig_pc_in.tt	(revision 17695)
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: @PACKAGE@
+Description: some library
+Version: @VERSION@
+Requires: pslib
+Libs: -L${libdir} -l@PACKAGE@
+Cflags: -I${includedir}
Index: /tags/rel-0_10/glueforge/templates/psdb/pop.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/pop.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/pop.tt	(revision 17695)
@@ -0,0 +1,74 @@
+[% USE format -%]
+[% indented = format('%-15s %s') -%]
+bool [% table.namespace %]Pop(psDB *dbh, 
+[%- SET i = 0 -%]
+[% FOREACH item = table.columns -%]
+[% IF i == 0 %][% i = 1 %][% ELSE %], [% END -%]
+[% IF item.type == "STR" -%]
+char **[% item.name -%]
+[% ELSE -%]
+[% item.ctype %] *[% item.name -%]
+[% END -%]
+[% END %])
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, [% table.namespace FILTER upper %]_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", [% table.namespace FILTER upper %]_INDEX_NAME);
+        psFree(rowSet);
+        return NULL;
+    }
+
+    row = psArrayGet(rowSet, 0);
+    psMemIncrRefCounter(row);
+    if (!row) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return NULL;
+    }
+    psFree(rowSet);
+
+    rowID = psMetadataLookupS32(&status, row, [% table.namespace FILTER upper %]_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", [% table.namespace FILTER upper %]_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, [% table.namespace FILTER upper %]_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, [% table.namespace FILTER upper %]_TABLE_NAME, popped, 0);
+    if (deleted != 1) {
+        psError(PS_ERR_UNKNOWN, false, "database failed to delete row");
+        psFree(popped);
+        psFree(row);
+        return NULL;
+    }
+
+    psFree(popped);
+
+[% FOREACH item = table.columns -%]
+[% IF item.type == "S32" or item.type == "F32" or item.type == "F64" -%]
+    *[% item.name %] = psMetadataLookup[% item.type %](&status, row, "[% item.name %]");
+[% ELSIF item.type == "STR" -%]
+    *[% item.name %] = psMetadataLookupPtr(&status, row, "[% item.name %]");
+[% ELSIF item.type == "BOOL" -%]
+    *[% item.name %] = psMetadataLookupBool(&status, row, "[% item.name %]");
+[% END -%]
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item [% item.name %]");
+        psFree(row);
+        return false;
+    }
+[% END -%]
+
+    psFree(row);
+
+    return true;
+}
Index: /tags/rel-0_10/glueforge/templates/psdb/pop_at.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/pop_at.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/pop_at.tt	(revision 17695)
@@ -0,0 +1,10 @@
+AT_SETUP([[% table.namespace %]Pop()])
+AT_KEYWORDS([[% table.namespace %]Pop])
+
+AT_CHECK([dbsetup])
+# run insert so there is a row in the table
+AT_CHECK([insert])
+AT_CHECK([pop])
+AT_CHECK([dbcleanup])
+
+AT_CLEANUP
Index: /tags/rel-0_10/glueforge/templates/psdb/pop_c.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/pop_c.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/pop_c.tt	(revision 17695)
@@ -0,0 +1,43 @@
+[% USE format -%]
+[% indented = format('%-15s %s') -%]
+#include <pslib.h>
+#include <[% pkg_name %].h>
+#include <stdlib.h>
+
+int main ()
+{
+[% FOREACH table = tables -%]
+    {
+        psDB            *dbh;
+[% FOREACH item = table.columns -%]
+[% IF item.type == "STR" -%]
+        [% indented("char", "$item.name[256]") %];
+[% ELSE-%]
+        [% indented("$item.ctype", "$item.name") %];
+[% END -%]
+[% END -%]
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (![% table.namespace %]Pop(dbh, 
+[%- SET i = 0 -%]
+[% FOREACH item = table.columns -%]
+[% IF i == 0 %][% i = 1 %][% ELSE %], [% END -%]
+[% IF item.type == "STR" -%]
+(char **)&[% item.name -%]
+[% ELSE-%]
+&[% item.name -%]
+[% END -%]
+[% END %])) { 
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+[% END -%]
+    exit(EXIT_SUCCESS);
+}
Index: /tags/rel-0_10/glueforge/templates/psdb/pop_h.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/pop_h.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/pop_h.tt	(revision 17695)
@@ -0,0 +1,19 @@
+[% USE format -%]
+[% indented = format('%-15s %s') -%]
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool [% table.namespace %]Pop(
+    psDB            *dbh,               ///< Database handle
+[%- SET i = 0 -%]
+[% FOREACH item = table.columns -%]
+[% IF i == 0 %][% i = 1 %][% ELSE %],[% END %]
+[% IF item.type == "STR" -%]
+    [% indented("char", "**$item.name") -%]
+[% ELSE -%]
+    [% indented("$item.ctype", "*$item.name") -%]
+[% END -%]
+[% END %]
+);
Index: /tags/rel-0_10/glueforge/templates/psdb/popfits.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/popfits.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/popfits.tt	(revision 17695)
@@ -0,0 +1,24 @@
+bool [% table.namespace %]PopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (![% table.namespace %]SelectRowsFits(dbh, fits, NULL, limit)) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return false;
+    }
+
+    // remove limit rows from the end of the database
+    if (snprintf(query, MAX_STRING_LENGTH,
+                "DELETE FROM %s ORDER BY %s DESC LIMIT %lld",
+                [% table.namespace FILTER upper %]_TABLE_NAME, [% table.namespace FILTER upper %]_INDEX_NAME, limit) < 0) {
+        psError(PS_ERR_UNKNOWN, true, "query value attempted to exceed %s bytes", MAX_STRING_LENGTH);
+        return false;
+    }
+            
+    if (!p_psDBRunQuery(dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database query failed");
+        return false;
+    }
+
+    return true;
+}
Index: /tags/rel-0_10/glueforge/templates/psdb/popfits_at.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/popfits_at.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/popfits_at.tt	(revision 17695)
@@ -0,0 +1,13 @@
+AT_SETUP([[% table.namespace %]PopFits()])
+AT_KEYWORDS([[% table.namespace %]PopFits])
+
+AT_CHECK([dbsetup])
+# run insert so there is a row in the table
+AT_CHECK([insert])
+AT_CHECK([popfits])
+AT_CHECK([dbcleanup])
+if [ test -f "blargh" ] ; then
+    rm -f "blargh"
+fi
+
+AT_CLEANUP
Index: /tags/rel-0_10/glueforge/templates/psdb/popfits_c.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/popfits_c.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/popfits_c.tt	(revision 17695)
@@ -0,0 +1,40 @@
+#include <pslib.h>
+#include <[% pkg_name %].h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#define TMP_FILENAME "./blargh"
+
+int main ()
+{
+[% FOREACH table = tables -%]
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // allocate a temp
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (![% table.namespace %]PopFits(dbh, fits, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+[% END -%]
+    exit(EXIT_SUCCESS);
+}
Index: /tags/rel-0_10/glueforge/templates/psdb/popfits_h.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/popfits_h.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/popfits_h.tt	(revision 17695)
@@ -0,0 +1,13 @@
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool [% table.namespace %]PopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
Index: /tags/rel-0_10/glueforge/templates/psdb/popobject.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/popobject.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/popobject.tt	(revision 17695)
@@ -0,0 +1,33 @@
+[% USE format -%]
+[% indented = format('%-15s %s') -%]
+[% table.object_name %] *[% table.namespace %]PopObject(psDB *dbh)
+{
+[% FOREACH item = table.columns -%]
+[% IF item.type == "STR" -%]
+    [% indented("char", "$item.name[256]") %];
+[% ELSE-%]
+    [% indented("$item.ctype", "$item.name") %];
+[% END -%]
+[% END -%]
+
+    if (![% table.namespace %]Pop(dbh, 
+[%- SET i = 0 -%]
+[% FOREACH item = table.columns -%]
+[% IF i == 0 %][% i = 1 %][% ELSE %], [% END -%]
+[% IF item.type == "STR" -%]
+(char **)&[% item.name -%]
+[% ELSE-%]
+&[% item.name -%]
+[% END -%]
+[% END %])) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return [% table.object_name %]Alloc(
+[%- SET i = 0 -%]
+[% FOREACH item = table.columns -%]
+[% IF i == 0 -%][%- i = 1 -%][% ELSE %], [% END -%]
+[% item.name %]
+[%- END -%]);
+}
Index: /tags/rel-0_10/glueforge/templates/psdb/popobject_at.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/popobject_at.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/popobject_at.tt	(revision 17695)
@@ -0,0 +1,10 @@
+AT_SETUP([[% table.namespace %]PopObject()])
+AT_KEYWORDS([[% table.namespace %]PopObject])
+
+AT_CHECK([dbsetup])
+# run insert so there is a row in the table
+AT_CHECK([insert])
+AT_CHECK([popobject])
+AT_CHECK([dbcleanup])
+
+AT_CLEANUP
Index: /tags/rel-0_10/glueforge/templates/psdb/popobject_c.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/popobject_c.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/popobject_c.tt	(revision 17695)
@@ -0,0 +1,30 @@
+[% USE format -%]
+[% indented = format('%-15s %s') -%]
+#include <pslib.h>
+#include <[% pkg_name %].h>
+#include <stdlib.h>
+
+int main ()
+{
+[% FOREACH table = tables -%]
+    {
+        psDB            *dbh;
+        [% indented(table.object_name, "*object") %];
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = [% table.namespace %]PopObject(dbh);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+[% END -%]
+    exit(EXIT_SUCCESS);
+}
Index: /tags/rel-0_10/glueforge/templates/psdb/popobject_h.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/popobject_h.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/popobject_h.tt	(revision 17695)
@@ -0,0 +1,8 @@
+/** Removes the last row from the database and returns it
+ *
+ * @return A new [% table.object_name %] on success or NULL on failure.
+ */
+
+[% table.object_name %] *[% table.namespace %]PopObject(
+    psDB            *dbh                ///< Database handle
+);
Index: /tags/rel-0_10/glueforge/templates/psdb/selectrowobjects.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/selectrowobjects.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/selectrowobjects.tt	(revision 17695)
@@ -0,0 +1,33 @@
+psArray *[% table.namespace %]SelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, [% table.namespace FILTER upper %]_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, [% table.namespace FILTER upper %]_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", [% table.namespace FILTER upper %]_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAlloc(rowSet->n);
+    returnSet->n = 0;
+
+    for (i = 0; i < rowSet->n; i++) {
+        psArrayAdd(returnSet, 0, [% table.namespace %]ObjectFromMetadata(rowSet->data[i]));
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
Index: /tags/rel-0_10/glueforge/templates/psdb/selectrowobjects_h.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/selectrowobjects_h.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/selectrowobjects_h.tt	(revision 17695)
@@ -0,0 +1,12 @@
+/** Selects up to limit rows from the database and returns as [% table.object_name %] objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *[% table.namespace %]SelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
Index: /tags/rel-0_10/glueforge/templates/psdb/selectrowsfits.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/selectrowsfits.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/selectrowsfits.tt	(revision 17695)
@@ -0,0 +1,37 @@
+bool [% table.namespace %]SelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, [% table.namespace FILTER upper %]_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)rowSet->data[i], 0, [% table.namespace FILTER upper %]_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", [% table.namespace FILTER upper %]_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    // FIXME: needs to be after a new ext is written?
+    if (!psFitsSetExtName(fits, [% table.namespace FILTER upper %]_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to set FITS HDU extension name");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
Index: /tags/rel-0_10/glueforge/templates/psdb/selectrowsfits_at.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/selectrowsfits_at.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/selectrowsfits_at.tt	(revision 17695)
@@ -0,0 +1,13 @@
+AT_SETUP([[% table.namespace %]SelectRowsFits()])
+AT_KEYWORDS([[% table.namespace %]SelectRowsFits])
+
+AT_CHECK([dbsetup])
+# run insert so there is a row in the table
+AT_CHECK([insert])
+AT_CHECK([selectrowsfits])
+AT_CHECK([dbcleanup])
+if [ test -f "blargh" ] ; then
+    rm -f "blargh"
+fi
+
+AT_CLEANUP
Index: /tags/rel-0_10/glueforge/templates/psdb/selectrowsfits_c.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/selectrowsfits_c.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/selectrowsfits_c.tt	(revision 17695)
@@ -0,0 +1,34 @@
+#include <pslib.h>
+#include <[% pkg_name %].h>
+#include <stdlib.h>
+
+#define TMP_FILENAME "./blargh"
+
+int main ()
+{
+[% FOREACH table = tables -%]
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (![% table.namespace %]SelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+[% END -%]
+    exit(EXIT_SUCCESS);
+}
Index: /tags/rel-0_10/glueforge/templates/psdb/selectrowsfits_h.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/selectrowsfits_h.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/selectrowsfits_h.tt	(revision 17695)
@@ -0,0 +1,16 @@
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool [% table.namespace %]SelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
Index: /tags/rel-0_10/glueforge/templates/psdb/src_makefile_am.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/src_makefile_am.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/src_makefile_am.tt	(revision 17695)
@@ -0,0 +1,10 @@
+AM_CFLAGS = @[% pkg_name %]_CFLAGS@
+
+include_HEADERS = [% pkg_name %].h
+lib_LTLIBRARIES = lib[% pkg_name %].la
+lib[% pkg_name %]_la_CFLAGS  = $(PSLIB_CFLAGS) $(AM_CFLAGS)
+lib[% pkg_name %]_la_LIBS    = $(PSLIB_LIBS) $(AM_LIBS)
+lib[% pkg_name %]_la_LDFLAGS = -release $(PACKAGE_VERSION)
+lib[% pkg_name %]_la_SOURCES = \
+    [% pkg_name %].h \
+    [% pkg_name %].c
Index: /tags/rel-0_10/glueforge/templates/psdb/tests_makefile_am.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/tests_makefile_am.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/tests_makefile_am.tt	(revision 17695)
@@ -0,0 +1,67 @@
+# copied from bison-1.875d
+
+EXTRA_DIST = $(TESTSUITE_AT) testsuite package.m4
+
+DISTCLEANFILES       = atconfig $(check_SCRIPTS)
+MAINTAINERCLEANFILES = Makefile.in $(TESTSUITE)
+
+## ------------ ##
+## package.m4.  ##
+## ------------ ##
+
+$(srcdir)/package.m4: $(top_srcdir)/configure.ac
+	{					\
+	  echo '# Signature of the current package.'; \
+	  echo 'm4_define([AT_PACKAGE_NAME],      [@PACKAGE_NAME@])'; \
+	  echo 'm4_define([AT_PACKAGE_TARNAME],   [@PACKAGE_TARNAME@])'; \
+	  echo 'm4_define([AT_PACKAGE_VERSION],   [@PACKAGE_VERSION@])'; \
+	  echo 'm4_define([AT_PACKAGE_STRING],    [@PACKAGE_STRING@])'; \
+	  echo 'm4_define([AT_PACKAGE_BUGREPORT], [@PACKAGE_BUGREPORT@])'; \
+	} >$(srcdir)/package.m4
+
+## ------------ ##
+## Test suite.  ##
+## ------------ ##
+
+TESTSUITE = $(srcdir)/testsuite
+
+AUTOTEST = $(AUTOM4TE) --language=autotest
+$(TESTSUITE): package.m4 $(TESTSUITE_AT)
+	$(AUTOTEST) -I $(srcdir) testsuite.at -o $@.tmp
+	mv $@.tmp $@
+
+atconfig: $(top_builddir)/config.status
+	cd $(top_builddir) && ./config.status tests/$@
+
+clean-local:
+	test ! -f $(TESTSUITE) || $(SHELL) $(TESTSUITE) --clean
+
+check-local: atconfig $(TESTSUITE)
+	$(SHELL) $(TESTSUITE)
+
+#
+# END BISON
+#
+
+UNITS = \
+	alloc \
+	init \
+	cleanup \
+	createtable \
+    droptable \
+    insert \
+    pop \
+    insertobject \
+    popobject \
+    insertfits \
+    popfits \
+    selectrowsfits \
+    metadatafromobject \
+    objectfrommetadata
+
+AM_CPPFLAGS = -I$(top_srcdir)/src$
+AM_CFLAGS   = @[% pkg_name %]_CFLAGS@ $(PSLIB_CFLAGS)$
+AM_LDFLAGS  = $(PSLIB_LIBS)$
+LDADD       = $(top_builddir)/src/lib[% pkg_name %].la$
+
+check_PROGRAMS = dbsetup dbcleanup $(UNITS)
Index: /tags/rel-0_10/glueforge/templates/psdb/testsuite_at.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/testsuite_at.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/testsuite_at.tt	(revision 17695)
@@ -0,0 +1,33 @@
+m4_version_prereq([2.59])
+
+AT_INIT
+
+###
+[% INCLUDE alloc_at.tt %]
+###
+[% INCLUDE init_at.tt %]
+###
+[% INCLUDE cleanup_at.tt %]
+###
+[% INCLUDE createtable_at.tt %]
+###
+[% INCLUDE droptable_at.tt %]
+###
+[% INCLUDE insert_at.tt %]
+###
+[% INCLUDE pop_at.tt %]
+###
+[% INCLUDE insertobject_at.tt %]
+###
+[% INCLUDE popobject_at.tt %]
+###
+[% INCLUDE insertfits_at.tt %]
+###
+[% INCLUDE popfits_at.tt %]
+###
+[% INCLUDE selectrowsfits_at.tt %]
+###
+[% INCLUDE metadatafromobject_at.tt %]
+###
+[% INCLUDE objectfrommetadata_at.tt %]
+###
Index: /tags/rel-0_10/glueforge/templates/psdb/top_makefile_am.tt
===================================================================
--- /tags/rel-0_10/glueforge/templates/psdb/top_makefile_am.tt	(revision 17695)
+++ /tags/rel-0_10/glueforge/templates/psdb/top_makefile_am.tt	(revision 17695)
@@ -0,0 +1,30 @@
+SUBDIRS = src tests
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA= [% pkg_name %].pc
+
+EXTRA_DIST = [% pkg_name %].pc.in Doxyfile.in
+
+if DOXYGEN
+
+man_MANS = \
+    $(top_builddir)/docs/man/man3/[% pkg_name %].3 \
+[%- FOREACH table = tables %]
+[% IF NOT loop.last -%]
+    $(top_builddir)/docs/man/man3/[% table.object_name %].3 \
+[%- ELSE -%]
+    $(top_builddir)/docs/man/man3/[% table.object_name %].3 
+[% END -%]
+[% END %]
+
+docs/man/man3/[% pkg_name %].3
+[%- FOREACH table = tables -%]
+ docs/man/man3/[% table.object_name %].3
+[%- IF loop.last %]:[% END -%]
+[%- END %]
+	$(DOXYGEN)
+
+endif
+
+clean-local:
+	-rm -rf docs
