[Bio] / Sprout / SproutLoad.pm Repository:
ViewVC logotype

Diff of /Sprout/SproutLoad.pm

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 1.17, Mon Sep 19 01:46:51 2005 UTC revision 1.21, Sat Nov 12 03:42:48 2005 UTC
# Line 10  Line 10 
10      use Sprout;      use Sprout;
11      use Stats;      use Stats;
12      use BasicLocation;      use BasicLocation;
13        use HTML;
14    
15  =head1 Sprout Load Methods  =head1 Sprout Load Methods
16    
# Line 367  Line 368 
368                              # We store this evidence in the hash if the usage                              # We store this evidence in the hash if the usage
369                              # is nonzero or no prior evidence has been found. This                              # is nonzero or no prior evidence has been found. This
370                              # insures that if there is duplicate evidence, we                              # insures that if there is duplicate evidence, we
371                              # at least keep the meaningful ones. Only evidence is                              # at least keep the meaningful ones. Only evidence in
372                              # the hash makes it to the output.                              # the hash makes it to the output.
373                              if ($usage || ! exists $evidenceMap{$evidenceKey}) {                              if ($usage || ! exists $evidenceMap{$evidenceKey}) {
374                                  $evidenceMap{$evidenceKey} = $evidenceData;                                  $evidenceMap{$evidenceKey} = $evidenceData;
# Line 599  Line 600 
600    
601      Subsystem      Subsystem
602      Role      Role
603        RoleEC
604      SSCell      SSCell
605      ContainsFeature      ContainsFeature
606      IsGenomeOf      IsGenomeOf
# Line 606  Line 608 
608      OccursInSubsystem      OccursInSubsystem
609      ParticipatesIn      ParticipatesIn
610      HasSSCell      HasSSCell
611        ConsistsOfRoles
612        RoleSubset
613        HasRoleSubset
614        ConsistsOfGenomes
615        GenomeSubset
616        HasGenomeSubset
617        Catalyzes
618        Diagram
619        RoleOccursIn
620    
621  =over 4  =over 4
622    
# Line 615  Line 626 
626    
627  =back  =back
628    
 B<TO DO>  
   
 Generate RoleName table?  
   
629  =cut  =cut
630  #: Return Type $%;  #: Return Type $%;
631  sub LoadSubsystemData {  sub LoadSubsystemData {
# Line 635  Line 642 
642      my $subsysCount = @subsysIDs;      my $subsysCount = @subsysIDs;
643      my $genomeCount = (keys %{$genomeHash});      my $genomeCount = (keys %{$genomeHash});
644      my $featureCount = $genomeCount * 4000;      my $featureCount = $genomeCount * 4000;
645        # Get the map list.
646        my @maps = $fig->all_maps;
647        my $mapCount = @maps;
648      # Create load objects for each of the tables we're loading.      # Create load objects for each of the tables we're loading.
649        my $loadDiagram = $self->_TableLoader('Diagram', $mapCount);
650        my $loadRoleOccursIn = $self->_TableLoader('RoleOccursIn', $featureCount * 6);
651      my $loadSubsystem = $self->_TableLoader('Subsystem', $subsysCount);      my $loadSubsystem = $self->_TableLoader('Subsystem', $subsysCount);
652      my $loadRole = $self->_TableLoader('Role', $featureCount * 6);      my $loadRole = $self->_TableLoader('Role', $featureCount * 6);
653        my $loadRoleEC = $self->_TableLoader('RoleEC', $featureCount * 6);
654        my $loadCatalyzes = $self->_TableLoader('Catalyzes', $genomeCount * $featureCount);
655      my $loadSSCell = $self->_TableLoader('SSCell', $featureCount * $genomeCount);      my $loadSSCell = $self->_TableLoader('SSCell', $featureCount * $genomeCount);
656      my $loadContainsFeature = $self->_TableLoader('ContainsFeature', $featureCount * $subsysCount);      my $loadContainsFeature = $self->_TableLoader('ContainsFeature', $featureCount * $subsysCount);
657      my $loadIsGenomeOf = $self->_TableLoader('IsGenomeOf', $featureCount * $genomeCount);      my $loadIsGenomeOf = $self->_TableLoader('IsGenomeOf', $featureCount * $genomeCount);
# Line 645  Line 659 
659      my $loadOccursInSubsystem = $self->_TableLoader('OccursInSubsystem', $featureCount * 6);      my $loadOccursInSubsystem = $self->_TableLoader('OccursInSubsystem', $featureCount * 6);
660      my $loadParticipatesIn = $self->_TableLoader('ParticipatesIn', $subsysCount * $genomeCount);      my $loadParticipatesIn = $self->_TableLoader('ParticipatesIn', $subsysCount * $genomeCount);
661      my $loadHasSSCell = $self->_TableLoader('HasSSCell', $featureCount * $genomeCount);      my $loadHasSSCell = $self->_TableLoader('HasSSCell', $featureCount * $genomeCount);
662        my $loadRoleSubset = $self->_TableLoader('RoleSubset', $subsysCount * 50);
663        my $loadGenomeSubset = $self->_TableLoader('GenomeSubset', $subsysCount * 50);
664        my $loadConsistsOfRoles = $self->_TableLoader('ConsistsOfRoles', $featureCount * $genomeCount);
665        my $loadConsistsOfGenomes = $self->_TableLoader('ConsistsOfGenomes', $featureCount * $genomeCount);
666        my $loadHasRoleSubset = $self->_TableLoader('HasRoleSubset', $subsysCount * 50);
667        my $loadHasGenomeSubset = $self->_TableLoader('HasGenomeSubset', $subsysCount * 50);
668        # Create load objects for each of the tables we're loading.
669      Trace("Beginning subsystem data load.") if T(2);      Trace("Beginning subsystem data load.") if T(2);
670        # This hash will contain the role for each EC. When we're done, this
671        # information will be used to generate the Catalyzes table.
672        my %ecToRoles = ();
673      # Loop through the subsystems. Our first task will be to create the      # Loop through the subsystems. Our first task will be to create the
674      # roles. We do this by looping through the subsystems and creating a      # roles. We do this by looping through the subsystems and creating a
675      # role hash. The hash tracks each role ID so that we don't create      # role hash. The hash tracks each role ID so that we don't create
676      # duplicates. As we move along, we'll connect the roles and subsystems.      # duplicates. As we move along, we'll connect the roles and subsystems
677        # and memorize up the reactions.
678      my ($genomeID, $roleID);      my ($genomeID, $roleID);
679      my %roleData = ();      my %roleData = ();
680      for my $subsysID (@subsysIDs) {      for my $subsysID (@subsysIDs) {
681          Trace("Creating subsystem $subsysID.") if T(3);          Trace("Creating subsystem $subsysID.") if T(3);
682          $loadSubsystem->Add("subsystemIn");          $loadSubsystem->Add("subsystemIn");
         # Create the subsystem record.  
         $loadSubsystem->Put($subsysID);  
683          # Get the subsystem object.          # Get the subsystem object.
684          my $sub = $fig->get_subsystem($subsysID);          my $sub = $fig->get_subsystem($subsysID);
685          # Connect it to its roles.          # Create the subsystem record.
686            my $curator = $sub->get_curator();
687            my $notes = $sub->get_notes();
688            $loadSubsystem->Put($subsysID, $curator, $notes);
689            # Connect it to its roles. Each role is a column in the subsystem spreadsheet.
690          for (my $col = 0; defined($roleID = $sub->get_role($col)); $col++) {          for (my $col = 0; defined($roleID = $sub->get_role($col)); $col++) {
691                # Connect to this role.
692              $loadOccursInSubsystem->Add("roleIn");              $loadOccursInSubsystem->Add("roleIn");
693              $loadOccursInSubsystem->Put($roleID, $subsysID);              $loadOccursInSubsystem->Put($roleID, $subsysID, $col);
694                # If it's a new role, add it to the role table.
695              if (! exists $roleData{$roleID}) {              if (! exists $roleData{$roleID}) {
696                  $loadRole->Put($roleID);                  # Get the role's abbreviation.
697                    my $abbr = $sub->get_role_abbr($col);
698                    # Add the role.
699                    $loadRole->Put($roleID, $abbr);
700                  $roleData{$roleID} = 1;                  $roleData{$roleID} = 1;
701                    # Check for an EC number.
702                    if ($roleID =~ /\(EC ([^.]+\.[^.]+\.[^.]+\.[^)]+)\)\s*$/) {
703                        my $ec = $1;
704                        $loadRoleEC->Put($roleID, $ec);
705                        $ecToRoles{$ec} = $roleID;
706                    }
707              }              }
708          }          }
709          # Now we create the spreadsheet for the subsystem by matching roles to          # Now we create the spreadsheet for the subsystem by matching roles to
# Line 678  Line 716 
716                  # Count the PEGs and cells found for verification purposes.                  # Count the PEGs and cells found for verification purposes.
717                  my $pegCount = 0;                  my $pegCount = 0;
718                  my $cellCount = 0;                  my $cellCount = 0;
719                    # Create a list for the PEGs we find. This list will be used
720                    # to generate cluster numbers.
721                    my @pegsFound = ();
722                    # Create a hash that maps spreadsheet IDs to PEGs. We will
723                    # use this to generate the ContainsFeature data after we have
724                    # the cluster numbers.
725                    my %cellPegs = ();
726                    # Get the genome's variant code for this subsystem.
727                    my $variantCode = $sub->get_variant_code($row);
728                  # Loop through the subsystem's roles. We use an index because it is                  # Loop through the subsystem's roles. We use an index because it is
729                  # part of the spreadsheet cell ID.                  # part of the spreadsheet cell ID.
730                  for (my $col = 0; defined($roleID = $sub->get_role($col)); $col++) {                  for (my $col = 0; defined($roleID = $sub->get_role($col)); $col++) {
# Line 692  Line 739 
739                          $loadIsGenomeOf->Put($genomeID, $cellID);                          $loadIsGenomeOf->Put($genomeID, $cellID);
740                          $loadIsRoleOf->Put($roleID, $cellID);                          $loadIsRoleOf->Put($roleID, $cellID);
741                          $loadHasSSCell->Put($subsysID, $cellID);                          $loadHasSSCell->Put($subsysID, $cellID);
742                          # Attach the features to it.                          # Remember its features.
743                          for my $pegID (@pegs) {                          push @pegsFound, @pegs;
744                              $loadContainsFeature->Put($cellID, $pegID);                          $cellPegs{$cellID} = \@pegs;
745                              $pegCount++;                          $pegCount += @pegs;
                         }  
746                      }                      }
747                  }                  }
748                  # If we found some cells for this genome, denote it participates in the                  # If we found some cells for this genome, we need to compute clusters and
749                  # subsystem.                  # denote it participates in the subsystem.
750                  if ($pegCount > 0) {                  if ($pegCount > 0) {
751                      Trace("$pegCount PEGs in $cellCount cells for $genomeID.") if T(3);                      Trace("$pegCount PEGs in $cellCount cells for $genomeID.") if T(3);
752                      $loadParticipatesIn->Put($genomeID, $subsysID);                      $loadParticipatesIn->Put($genomeID, $subsysID, $variantCode);
753                        # Partition the PEGs found into clusters.
754                        my @clusters = $fig->compute_clusters(\@pegsFound, $sub);
755                        # Create a hash mapping PEG IDs to cluster numbers.
756                        # We default to -1 for all of them.
757                        my %clusterOf = map { $_ => -1 } @pegsFound;
758                        for (my $i = 0; $i <= $#clusters; $i++) {
759                            my $subList = $clusters[$i];
760                            for my $peg (@{$subList}) {
761                                $clusterOf{$peg} = $i;
762                            }
763                        }
764                        # Create the ContainsFeature data.
765                        for my $cellID (keys %cellPegs) {
766                            my $cellList = $cellPegs{$cellID};
767                            for my $cellPeg (@$cellList) {
768                                $loadContainsFeature->Put($cellID, $cellPeg, $clusterOf{$cellPeg});
769                  }                  }
770              }              }
771          }          }
772      }      }
     # Finish the load.  
     my $retVal = $self->_FinishAll();  
     return $retVal;  
773  }  }
774            # Now we need to generate the subsets. The subset names must be concatenated to
775  =head3 LoadDiagramData          # the subsystem name to make them unique keys. There are two types of subsets:
776            # genome subsets and role subsets. We do the role subsets first.
777  C<< my $stats = $spl->LoadDiagramData(); >>          my @subsetNames = $sub->get_subset_names();
778            for my $subsetID (@subsetNames) {
779  Load the diagram data from FIG into Sprout.              # Create the subset record.
780                my $actualID = "$subsysID:$subsetID";
781  Diagrams are used to organize functional roles. The diagram shows the              $loadRoleSubset->Put($actualID);
782  connections between chemicals that interact with a subsystem.              # Connect the subset to the subsystem.
783                $loadHasRoleSubset->Put($subsysID, $actualID);
784  The following relations are loaded by this method.              # Connect the subset to its roles.
785                my @roles = $sub->get_subset($subsetID);
786      Diagram              for my $roleID (@roles) {
787      RoleOccursIn                  $loadConsistsOfRoles->Put($actualID, $roleID);
788                }
789  =over 4          }
790            # Next the genome subsets.
791  =item RETURNS          @subsetNames = $sub->get_subset_namesR();
792            for my $subsetID (@subsetNames) {
793  Returns a statistics object for the loads.              # Create the subset record.
794                my $actualID = "$subsysID:$subsetID";
795  =back              $loadGenomeSubset->Put($actualID);
796                # Connect the subset to the subsystem.
797  =cut              $loadHasGenomeSubset->Put($subsysID, $actualID);
798  #: Return Type $%;              # Connect the subset to its genomes.
799  sub LoadDiagramData {              my @genomes = $sub->get_subsetR($subsetID);
800      # Get this object instance.              for my $genomeID (@genomes) {
801      my ($self) = @_;                  $loadConsistsOfGenomes->Put($actualID, $genomeID);
802      # Get the FIG object.              }
803      my $fig = $self->{fig};          }
804      # Get the map list.      }
805      my @maps = $fig->all_maps;      # Now we loop through the diagrams. We need to create the diagram records
806      my $mapCount = @maps;      # and link each diagram to its roles. Note that only roles which occur
807      my $genomeCount = (keys %{$self->{genomes}});      # in subsystems (and therefore appear in the %ecToRoles hash) are
808      my $featureCount = $genomeCount * 4000;      # included.
809      # Create load objects for each of the tables we're loading.      for my $map (@maps) {
     my $loadDiagram = $self->_TableLoader('Diagram', $mapCount);  
     my $loadRoleOccursIn = $self->_TableLoader('RoleOccursIn', $featureCount * 6);  
     Trace("Beginning diagram data load.") if T(2);  
     # Loop through the diagrams.  
     for my $map ($fig->all_maps) {  
810          Trace("Loading diagram $map.") if T(3);          Trace("Loading diagram $map.") if T(3);
811          # Get the diagram's descriptive name.          # Get the diagram's descriptive name.
812          my $name = $fig->map_name($map);          my $name = $fig->map_name($map);
# Line 761  Line 815 
815          # A hash is used to prevent duplicates.          # A hash is used to prevent duplicates.
816          my %roleHash = ();          my %roleHash = ();
817          for my $role ($fig->map_to_ecs($map)) {          for my $role ($fig->map_to_ecs($map)) {
818              if (! $roleHash{$role}) {              if (exists $ecToRoles{$role} && ! $roleHash{$role}) {
819                  $loadRoleOccursIn->Put($role, $map);                  $loadRoleOccursIn->Put($ecToRoles{$role}, $map);
820                  $roleHash{$role} = 1;                  $roleHash{$role} = 1;
821              }              }
822          }          }
823      }      }
824        # Before we leave, we must create the Catalyzes table. We start with the reactions,
825        # then use the "ecToRoles" table to convert EC numbers to role IDs.
826        my @reactions = $fig->all_reactions();
827        for my $reactionID (@reactions) {
828            # Get this reaction's list of roles. The results will be EC numbers.
829            my @roles = $fig->catalyzed_by($reactionID);
830            # Loop through the roles, creating catalyzation records.
831            for my $thisRole (@roles) {
832                if (exists $ecToRoles{$thisRole}) {
833                    $loadCatalyzes->Put($ecToRoles{$thisRole}, $reactionID);
834                }
835            }
836        }
837      # Finish the load.      # Finish the load.
838      my $retVal = $self->_FinishAll();      my $retVal = $self->_FinishAll();
839      return $retVal;      return $retVal;
# Line 934  Line 1001 
1001                      # Denote we've seen this timestamp.                      # Denote we've seen this timestamp.
1002                      $seenTimestamps{$time} = 1;                      $seenTimestamps{$time} = 1;
1003                  }                  }
1004                }
1005                  # Now loop through the real annotations.                  # Now loop through the real annotations.
1006                  for my $tuple ($fig->feature_annotations($peg, "raw")) {                  for my $tuple ($fig->feature_annotations($peg, "raw")) {
1007                      my ($fid, $timestamp, $user, $text) = @{$tuple};                      my ($fid, $timestamp, $user, $text) = @{$tuple};
# Line 948  Line 1016 
1016                      $text =~ s/Set master function/Set FIG function/s;                      $text =~ s/Set master function/Set FIG function/s;
1017                      # Insure the time stamp is valid.                      # Insure the time stamp is valid.
1018                      if ($timestamp =~ /^\d+$/) {                      if ($timestamp =~ /^\d+$/) {
1019                          # Here it's a number. We need to insure it's unique.                      # Here it's a number. We need to insure the one we use to form
1020                          while ($seenTimestamps{$timestamp}) {                      # the key is unique.
1021                              $timestamp++;                      my $keyStamp = $timestamp;
1022                        while ($seenTimestamps{$keyStamp}) {
1023                            $keyStamp++;
1024                          }                          }
1025                          $seenTimestamps{$timestamp} = 1;                      $seenTimestamps{$keyStamp} = 1;
1026                          my $annotationID = "$peg:$timestamp";                      my $annotationID = "$peg:$keyStamp";
1027                          # Insure the user exists.                          # Insure the user exists.
1028                          if (! $users{$user}) {                          if (! $users{$user}) {
1029                              $loadSproutUser->Put($user, "SEED user");                              $loadSproutUser->Put($user, "SEED user");
# Line 961  Line 1031 
1031                              $users{$user} = 1;                              $users{$user} = 1;
1032                          }                          }
1033                          # Generate the annotation.                          # Generate the annotation.
1034                          $loadAnnotation->Put($annotationID, $timestamp, "$user\\n$text");                      $loadAnnotation->Put($annotationID, $timestamp, $text);
1035                          $loadIsTargetOfAnnotation->Put($peg, $annotationID);                          $loadIsTargetOfAnnotation->Put($peg, $annotationID);
1036                          $loadMadeAnnotation->Put($user, $annotationID);                          $loadMadeAnnotation->Put($user, $annotationID);
1037                      } else {                      } else {
# Line 971  Line 1041 
1041                  }                  }
1042              }              }
1043          }          }
     }  
1044      # Finish the load.      # Finish the load.
1045      my $retVal = $self->_FinishAll();      my $retVal = $self->_FinishAll();
1046      return $retVal;      return $retVal;
# Line 1128  Line 1197 
1197      return $retVal;      return $retVal;
1198  }  }
1199    
1200    
1201    =head3 LoadReactionData
1202    
1203    C<< my $stats = $spl->LoadReactionData(); >>
1204    
1205    Load the reaction data from FIG into Sprout.
1206    
1207    Reaction data connects reactions to the compounds that participate in them.
1208    
1209    The following relations are loaded by this method.
1210    
1211        Reaction
1212        ReactionURL
1213        Compound
1214        CompoundName
1215        CompoundCAS
1216        IsAComponentOf
1217    
1218    This method proceeds reaction by reaction rather than genome by genome.
1219    
1220    =over 4
1221    
1222    =item RETURNS
1223    
1224    Returns a statistics object for the loads.
1225    
1226    =back
1227    
1228    =cut
1229    #: Return Type $%;
1230    sub LoadReactionData {
1231        # Get this object instance.
1232        my ($self) = @_;
1233        # Get the FIG object.
1234        my $fig = $self->{fig};
1235        # Get the genome hash.
1236        my $genomeHash = $self->{genomes};
1237        my $genomeCount = (keys %{$genomeHash});
1238        # Create load objects for each of the tables we're loading.
1239        my $loadReaction = $self->_TableLoader('Reaction', $genomeCount * 4000);
1240        my $loadReactionURL = $self->_TableLoader('ReactionURL', $genomeCount * 4000);
1241        my $loadCompound = $self->_TableLoader('Compound', $genomeCount * 4000);
1242        my $loadCompoundName = $self->_TableLoader('CompoundName', $genomeCount * 8000);
1243        my $loadCompoundCAS = $self->_TableLoader('CompoundCAS', $genomeCount * 4000);
1244        my $loadIsAComponentOf = $self->_TableLoader('IsAComponentOf', $genomeCount * 12000);
1245        Trace("Beginning reaction/compound data load.") if T(2);
1246        # First we create the compounds.
1247        my @compounds = $fig->all_compounds();
1248        for my $cid (@compounds) {
1249            # Check for names.
1250            my @names = $fig->names_of_compound($cid);
1251            # Each name will be given a priority number, starting with 1.
1252            my $prio = 1;
1253            for my $name (@names) {
1254                $loadCompoundName->Put($cid, $name, $prio++);
1255            }
1256            # Create the main compound record. Note that the first name
1257            # becomes the label.
1258            my $label = (@names > 0 ? $names[0] : $cid);
1259            $loadCompound->Put($cid, $label);
1260            # Check for a CAS ID.
1261            my $cas = $fig->cas($cid);
1262            if ($cas) {
1263                $loadCompoundCAS->Put($cid, $cas);
1264            }
1265        }
1266        # All the compounds are set up, so we need to loop through the reactions next. First,
1267        # we initialize the discriminator index. This is a single integer used to insure
1268        # duplicate elements in a reaction are not accidentally collapsed.
1269        my $discrim = 0;
1270        my @reactions = $fig->all_reactions();
1271        for my $reactionID (@reactions) {
1272            # Create the reaction record.
1273            $loadReaction->Put($reactionID, $fig->reversible($reactionID));
1274            # Compute the reaction's URL.
1275            my $url = HTML::reaction_link($reactionID);
1276            # Put it in the ReactionURL table.
1277            $loadReactionURL->Put($reactionID, $url);
1278            # Now we need all of the reaction's compounds. We get these in two phases,
1279            # substrates first and then products.
1280            for my $product (0, 1) {
1281                # Get the compounds of the current type for the current reaction. FIG will
1282                # give us 3-tuples: [ID, stoichiometry, main-flag]. At this time we do not
1283                # have location data in SEED, so it defaults to the empty string.
1284                my @compounds = $fig->reaction2comp($reactionID, $product);
1285                for my $compData (@compounds) {
1286                    # Extract the compound data from the current tuple.
1287                    my ($cid, $stoich, $main) = @{$compData};
1288                    # Link the compound to the reaction.
1289                    $loadIsAComponentOf->Put($cid, $reactionID, $discrim++, "", $main,
1290                                             $product, $stoich);
1291                }
1292            }
1293        }
1294        # Finish the load.
1295        my $retVal = $self->_FinishAll();
1296        return $retVal;
1297    }
1298    
1299  =head3 LoadGroupData  =head3 LoadGroupData
1300    
1301  C<< my $stats = $spl->LoadGroupData(); >>  C<< my $stats = $spl->LoadGroupData(); >>
# Line 1251  Line 1419 
1419      # Loop through the list, finishing the loads. Note that if the finish fails, we die      # Loop through the list, finishing the loads. Note that if the finish fails, we die
1420      # ignominiously. At some future point, we want to make the loads restartable.      # ignominiously. At some future point, we want to make the loads restartable.
1421      while (my $loader = pop @{$loadList}) {      while (my $loader = pop @{$loadList}) {
1422            # Trace the fact that we're cleaning up.
1423            my $relName = $loader->RelName;
1424            Trace("Finishing load for $relName.") if T(2);
1425          my $stats = $loader->Finish();          my $stats = $loader->Finish();
1426            if ($self->{options}->{dbLoad}) {
1427                # Here we want to use the load file just created to load the database.
1428                Trace("Loading relation $relName.") if T(2);
1429                my $newStats = $self->{sprout}->LoadUpdate(1, [$relName]);
1430                # Accumulate the statistics from the DB load.
1431                $stats->Accumulate($newStats);
1432            }
1433          $retVal->Accumulate($stats);          $retVal->Accumulate($stats);
         my $relName = $loader->RelName;  
1434          Trace("Statistics for $relName:\n" . $stats->Show()) if T(2);          Trace("Statistics for $relName:\n" . $stats->Show()) if T(2);
1435      }      }
1436      # Return the load statistics.      # Return the load statistics.

Legend:
Removed from v.1.17  
changed lines
  Added in v.1.21

MCS Webmaster
ViewVC Help
Powered by ViewVC 1.0.3