/[cvs]/nfo/perl/libs/Data/Transfer/Sync/API.pm
ViewVC logotype

Annotation of /nfo/perl/libs/Data/Transfer/Sync/API.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.9 - (hide annotations)
Tue May 13 08:16:44 2003 UTC (21 years, 2 months ago) by joko
Branch: MAIN
Changes since 1.8: +9 -3 lines
minor update: modified header

1 joko 1.9 ## -------------------------------------------------------------------------
2     ##
3     ## $Id: API.pm,v 1.8 2003/03/27 15:31:15 joko Exp $
4 joko 1.1 ##
5     ## Copyright (c) 2002 Andreas Motl <andreas.motl@ilo.de>
6     ##
7 joko 1.9 ## See COPYRIGHT section in associated pod text
8     ## or below for usage and distribution rights.
9 joko 1.1 ##
10 joko 1.9 ## -------------------------------------------------------------------------
11 joko 1.2 ## $Log: API.pm,v $
12 joko 1.9 ## Revision 1.8 2003/03/27 15:31:15 joko
13     ## fixes to modules regarding new namespace(s) below Data::Mungle::*
14     ##
15 joko 1.8 ## Revision 1.7 2003/02/21 08:00:24 joko
16     ## debugging
17     ##
18 joko 1.7 ## Revision 1.6 2003/02/14 12:58:47 joko
19     ## + re-enabled the erase-mechanism
20     ##
21 joko 1.6 ## Revision 1.5 2003/02/11 05:26:04 joko
22     ## + sub _tellWhatIWillDo
23     ## + re-enabled "branch to execution path for special targets" mechanism
24     ##
25 joko 1.5 ## Revision 1.4 2003/02/09 05:03:02 joko
26     ## + minor fix regarding namespace of api versioning extension module
27     ##
28 joko 1.4 ## Revision 1.3 2003/02/09 04:59:27 joko
29     ## + api versioning mechanism
30     ## + major structure changes
31     ## - refactored code to sister modules
32     ##
33 joko 1.3 ## Revision 1.2 2003/01/20 16:59:48 joko
34     ## + cosmetics and debugging
35     ##
36 joko 1.2 ## Revision 1.1 2003/01/19 01:23:04 joko
37     ## + new from Data/Transfer/Sync.pm
38     ##
39 joko 1.1 ## ----------------------------------------------------------------------------------------
40    
41    
42 joko 1.2 package Data::Transfer::Sync::API;
43    
44 joko 1.1 use strict;
45     use warnings;
46 joko 1.2
47 joko 1.3 use base qw( DesignPattern::Bridge );
48    
49 joko 1.1 use mixin::with qw( Data::Transfer::Sync );
50 joko 1.2
51 joko 1.1
52     # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - main
53 joko 1.2
54     use Data::Dumper;
55 joko 1.3 use Hash::Merge qw( merge );
56 joko 1.2
57 joko 1.8 use Data::Mungle::Compare::Struct qw( isEmpty );
58 joko 1.2
59 joko 1.1 # get logger instance
60     my $logger = Log::Dispatch::Config->instance;
61 joko 1.2
62 joko 1.3
63     sub api_constructor {
64 joko 1.1 my $self = shift;
65 joko 1.3 $logger->debug( __PACKAGE__ . "->api_constructor: Loading API");
66     $self->_loadVersionExtensions();
67 joko 1.1 }
68 joko 1.2
69 joko 1.3
70     sub _loadVersionExtensions {
71 joko 1.1 my $self = shift;
72 joko 1.3 my $syncVersion = $self->{sync_version};
73     $syncVersion ||= '';
74     $logger->debug( __PACKAGE__ . "->loadVersionExtensions( version='$syncVersion' )");
75     #print Dumper($self);
76     #exit;
77 joko 1.4 my $module = "API::$syncVersion";
78 joko 1.3 $self->load($module);
79     }
80    
81    
82     sub configure {
83     my $self = shift;
84    
85     $logger->debug( __PACKAGE__ . "->configure");
86    
87     my @args = @_;
88    
89     if (!isEmpty(\@args)) {
90     my %properties = @_;
91     # merge args to properties
92     #map { $self->{$_} = $properties{$_}; } keys %properties;
93     #print Dumper($self);
94     #print Dumper(\%properties);
95     if ($self->{options}) {
96     my $options_new = merge($self->{options}, \%properties);
97     #print Dumper($options_new);
98     $self->{options} = $options_new;
99     #print Dumper($self->{options});
100     } else {
101     $self->{options} = \%properties;
102     }
103     $self->_init();
104     #$self->_initV1();
105     } else {
106     #print "no args!", "\n";
107 joko 1.1 }
108    
109 joko 1.3 #print Dumper($self);
110     #exit;
111    
112     $self->{state}->{configured} = 1;
113     return 1;
114 joko 1.1 }
115 joko 1.2
116 joko 1.3
117     sub setArguments {
118 joko 1.1 my $self = shift;
119 joko 1.3 my $args_raw = shift;
120     $self->{args_raw} = $args_raw;
121     }
122 joko 1.1
123 joko 1.3 sub readArguments {
124     my $self = shift;
125 joko 1.1
126 joko 1.3 my %syncConfig;
127     tie %syncConfig, 'Tie::IxHash';
128     %syncConfig = (
129     map => {
130     moduleName => $self->{args_raw}->{'mapping-module'},
131     },
132     source => {
133     dbKey => $self->{args_raw}->{source},
134     nodeType => $self->{args_raw}->{'source-type'},
135     nodeName => $self->{args_raw}->{'source-node'},
136     },
137     target => {
138     dbKey => $self->{args_raw}->{target},
139     nodeName => $self->{args_raw}->{'target-node'},
140     },
141     process => {
142     mode => $self->{args_raw}->{mode},
143     erase => $self->{args_raw}->{erase},
144     import => $self->{args_raw}->{import},
145     prepare => $self->{args_raw}->{prepare},
146     },
147     # metadata => {
148     # config => $self->{config_metadata},
149     # }
150     );
151 joko 1.1
152 joko 1.3 $self->{args} = \%syncConfig;
153 joko 1.1
154     }
155    
156 joko 1.2
157 joko 1.3 # TODO: some feature to show off the progress of synchronization (cur/max * 100)
158     sub syncNodes {
159 joko 1.1
160     my $self = shift;
161 joko 1.3 my $args = shift;
162    
163 joko 1.7 #$logger->notice( "========================== " . __PACKAGE__ . "->syncNodes ==============");
164 joko 1.3 $logger->debug( __PACKAGE__ . "->syncNodes: starting" );
165 joko 1.1
166 joko 1.3 #print Dumper($self);
167 joko 1.1 #exit;
168    
169 joko 1.3 #print Dumper($self->{options});
170     $self->_prepareOptions();
171 joko 1.1
172 joko 1.6 # trace
173     #print Dumper($self->{options});
174     #exit;
175 joko 1.1
176 joko 1.3 if (!$self->checkOptions()) {
177     $logger->critical( __PACKAGE__ . "->syncNodes: 'Data::Transfer::Sync::checkOptions' failed.");
178 joko 1.1 return;
179     }
180    
181 joko 1.3 if (!$self->{state}->{configured}) {
182     $logger->critical( __PACKAGE__ . "->syncNodes: Synchronization object is not configured/initialized correctly." );
183 joko 1.1 return;
184     }
185    
186 joko 1.5 #print Dumper($args);
187    
188 joko 1.3 # remember arguments through the whole processing
189     $self->{args} = $args;
190 joko 1.1
191 joko 1.3 # hash to hold and/or fill in metadata required for the processing
192     $self->{meta} = {};
193 joko 1.1
194 joko 1.3 # hash to sum up results
195     # TODO: re-implement! (sync-statistics???)
196 joko 1.1
197 joko 1.6 #print Dumper($self->{options}->{process});
198    
199 joko 1.3 # detect synchronization method to determine which optical symbol (directed arrow) to use
200     my $mode = $self->{args}->{mode}; # V1
201     $mode ||= $self->{options}->{process}->{mode}; # V2
202     my $direction_arrow = $self->_getDirectedArrow($mode);
203 joko 1.1
204 joko 1.3 # determine code versions
205     my $code_metadata_version;
206     # first, try to use version supplied by mapping-metadata
207     $code_metadata_version = $self->{options}->{metadata}->{version};
208     # if not set, inherit from global 'sync_version'
209     $code_metadata_version ||= $self->{sync_version};
210    
211     # load additional code from versioned namespace into current instance
212     my $dynModule = "Metadata::$code_metadata_version";
213     $self->load($dynModule);
214    
215     # build metadata using versioned code and stuff
216     $self->options2metadata();
217     $self->options2metadata_accessor();
218    
219 joko 1.5 # branch to execution path for special targets
220     # detect for option 'handler' which could be a CODEref
221     if ($self->{options}->{handler} && ref $self->{options}->{handler} eq 'CODE') {
222     $logger->info( __PACKAGE__ . "->syncNodes: Running (special handler code - no generic sync!) on '$self->{options}->{target}->{dbKey}' with MODE $self->{options}->{process}->{mode}, NODE $self->{options}->{target}->{address}");
223     #print Dumper($self);
224     #exit;
225     # don't do this any more - it wasn't very nice somehow ...
226     #$self->{options}->{handler}->($self->{app}, $self->{options});
227     # .... now: better let the parent application scope handle this via callback
228     # not any more required for this: $self->{app} inside here (which isn't the app we mean here)
229     # required for this: getting the options out of here: establish some getter method! ($self->getOptions(...))
230     # so....
231     #$self->{__bridge}->{parent_ref}->
232    
233     # ahh okay, DesignPattern::Bridge moves closer to some Class::Inner!???
234     # so...
235     # similar like above - but it isn't very nice anyway ... (no privateness, but: who cares?)
236     #print Dumper($self->{__bridge});
237     # just take the global application instance and
238     # throw it into the context of the mapping module - this is heavy! ;-) (but again, who cares...)
239 joko 1.6 # TODO: handle this more abstract *sometime*
240 joko 1.5 #$self->{options}->{handler}->($self->{__bridge}->{parent}->{app}, $self->{options});
241     $self->{options}->{handler}->($self->{__bridge}->{parent}->{process}, $self->{options});
242    
243     return;
244     }
245    
246     # TODO: execution path branch V2!!!
247     # option1: wrap this via callback to parent scope (like current impl. mechanism)
248     # option2: branch directly from here (this needs refactoring of the sub handler)
249    
250 joko 1.3 # tracing
251     #print Dumper($self);
252     #exit;
253 joko 1.1
254 joko 1.7 # V1:
255     $logger->debug( __PACKAGE__ . "->syncNodes: source=$self->{meta}->{source}->{dbKey}/$self->{meta}->{source}->{nodeName} [$self->{meta}->{source}->{nodeType}] $direction_arrow target=$self->{meta}->{target}->{dbKey}/$self->{meta}->{target}->{nodeName} [$self->{meta}->{target}->{nodeType}]" );
256    
257     # V2:
258     my $what = "$self->{meta}->{source}->{dbKey}/$self->{meta}->{source}->{nodeName} [$self->{meta}->{source}->{nodeType}] $direction_arrow $self->{meta}->{target}->{dbKey}/$self->{meta}->{target}->{nodeName} [$self->{meta}->{target}->{nodeType}]";
259     #my $header = ("~.." x 7) . " " . $what . " " . ("~.." x 4);
260     #my $header = ("= " x 7) . " " . $what . " " . ("= " x 4);
261     #my $header = ("~=-_-=" x 3) . " " . $what . " " . ("~=-_-=" x 4);
262     my $header = ("_-=~=-" x 4) . " " . $what . " " . ("_-=~=-" x 4);
263     $logger->notice($header);
264    
265 joko 1.1
266 joko 1.3 return if !$self->buildFieldmapping();
267     return if !$self->_touchNodeSet();
268     return if !$self->_prepare_sync();
269 joko 1.5 $self->_tellWhatIWillDo();
270 joko 1.3
271     # tracing
272     #print Dumper($self);
273     #print Dumper($self->{args});
274     #print Dumper($self->{options});
275     #print Dumper($self->{meta});
276     #print Dumper($self->{metadata});
277     #exit;
278 joko 1.1
279 joko 1.3 # finally, tell the core to start the synchronization process
280     $self->_run();
281    
282     }
283 joko 1.1
284 joko 1.2
285 joko 1.5 my $c_string_default = '';
286     sub c_string {
287     my $value = shift;
288     $value ||= "[$c_string_default]";
289     return $value;
290     }
291    
292     sub _tellWhatIWillDo {
293     my $self = shift;
294    
295    
296     #return;
297    
298     # trace
299     #print Dumper($self->{meta});
300     #exit;
301    
302     $c_string_default = 'n/a';
303     my $source = c_string($self->{opt}->{'source'});
304     my $source_node = c_string($self->{opt}->{'source-node'});
305     my $source_type = c_string($self->{opt}->{'source-type'});
306     my $target = c_string($self->{opt}->{'target'});
307     my $target_node = c_string($self->{opt}->{'target-node'});
308     my $target_type = c_string($self->{opt}->{'target-type'});
309    
310     my $mapping_module = c_string($self->{opt}->{'mapping-module'});
311     my $mode = uc c_string($self->{opt}->{'mode'});
312    
313     #my $ql = "$mode INTO $source NODE $source_node TYPE $source_type SELECT NODE $target_node TYPE $target_type FROM $target USING MODULE $mapping_module;";
314     #$logger->notice( __PACKAGE__ . ": $ql" );
315     my $ql = <<EOT;
316    
317     FETCH DATA
318     FROM STORAGE $self->{meta}->{source}->{dbKey}
319     AT NODE $self->{meta}->{source}->{accessorName}.$self->{meta}->{source}->{nodeName}
320     USING IDENTIFIER $self->{meta}->{source}->{IdentProvider}->{method}.$self->{meta}->{source}->{IdentProvider}->{arg}
321     CONVERT DATA
322     CAST FROM $self->{meta}->{source}->{nodeType} TO $self->{meta}->{target}->{nodeType}
323     MAP ATTRIBUTES FROM @{$self->{meta}->{source}->{childnodes}} TO @{$self->{meta}->{target}->{childnodes}}
324     STORE DATA
325     TO STORAGE $self->{meta}->{target}->{dbKey}
326     AT NODE $self->{meta}->{target}->{accessorName}.$self->{meta}->{target}->{nodeName}
327     USING IDENTIFIER $self->{meta}->{target}->{IdentProvider}->{method}.$self->{meta}->{target}->{IdentProvider}->{arg}
328     EOT
329    
330    
331 joko 1.7 chomp($ql);
332     $logger->info($ql);
333 joko 1.5
334     #exit;
335     return;
336    
337     my $actioning = ucfirst $self->{opt}->{'action'} . 'ing';
338    
339     # FIXME: this is weird!
340     my $long = <<EOT;
341    
342     - $actioning data of type $target_type and
343     filtered by $target_node from the storage named $target
344     to the storage named $source - filtered by $source_node.
345     - Will attempt to convert the data to $source_type.
346     EOT
347     chomp($long);
348     $logger->notice( __PACKAGE__ . ": $long" );
349    
350     }
351    
352    
353 joko 1.3 sub _prepareOptions {
354 joko 1.1
355     my $self = shift;
356 joko 1.3
357     my $opts = $self->{args};
358 joko 1.1
359     # patch options
360 joko 1.3 $opts->{source}->{nodeName} ||= '';
361     $opts->{target}->{nodeName} ||= '';
362 joko 1.1 $opts->{process}->{mode} ||= '';
363 joko 1.3 $opts->{process}->{erase} ||= 0;
364 joko 1.1 $opts->{process}->{prepare} ||= 0;
365 joko 1.3
366     # defaults (mostly for backward-compatibility to V1 -
367     # but code mungled here out of prepareOptions_V1 from Version::V1)
368     $opts->{metadata}->{syncMethod} ||= 'checksum'; # | timestamp
369     $opts->{source}->{ident} ||= 'storage_method:id';
370     $opts->{source}->{exclude} ||= [qw( cs )];
371     $opts->{target}->{ident} ||= 'property:oid';
372     #$map->{source_node} ||= $source_node_name;
373     #$map->{direction} ||= $opts->{mode}; # | PUSH | PULL | FULL
374 joko 1.1
375     # pre-check options
376     if (!$self->_preCheckOptions($opts)) {
377 joko 1.3 $logger->error( __PACKAGE__ . "->_prepareOptions: _preCheckOptions failed.");
378 joko 1.1 return;
379     }
380    
381     # inform user about option preparation
382 joko 1.3 $logger->debug( __PACKAGE__ . "->_prepareOptions( source.node='$opts->{source}->{nodeName}', target.node='$opts->{target}->{nodeName}', mode='$opts->{process}->{mode}', e='$opts->{process}->{erase}', p='$opts->{process}->{prepare}' )");
383 joko 1.1
384     # try to load mapping-metadata-container
385 joko 1.3 # How? Create a new instance of the given
386     # perl module/package name in ->{...}->{moduleName}.
387     # This instance is used later in the innards of the sync,
388     # that's why the module (class) should have a certain layout
389     # enabling the core to use it for gathering metadata while processing.
390     my $mapObject = DesignPattern::Object->fromPackage($opts->{map}->{moduleName});
391 joko 1.1
392     # try to resolve map from metadata-container
393    
394     # type of the item/node
395     my $source_nodeType = $opts->{source}->{nodeType};
396    
397     # check if mapping for certain node is contained in mapping object
398 joko 1.3 if (!$mapObject || !$mapObject->can($source_nodeType)) {
399     $logger->warning( __PACKAGE__ . "->_prepareOptions: Can't access mapping for source-type=\"$source_nodeType\" - please check \"$opts->{map}->{moduleName}\".");
400 joko 1.1 return;
401     }
402    
403     # get map
404     my $map = $mapObject->$source_nodeType;
405     #print Dumper($map);
406    
407 joko 1.3 my $map_version = $map->{metadata}->{version};
408     # FIXME: backward compatibility
409     if (!$map_version) {
410     $self->options_to_V2($map);
411     }
412    
413     # trace
414     #print Dumper($map);
415     #exit;
416     #print "ref: ", ref $map->{target}, "\n";
417     #print "target: ", $map->{target}, "\n";
418    
419 joko 1.1 # check here if "target" is actually a CODEref - in this case: resolve it - deprecated!!! ???
420     if (ref $map->{target}->{address} eq 'CODE') {
421     $map->{target}->{address} = $map->{target}->{address}->($source_nodeType);
422     }
423    
424     # resolve expressions (on nodename-level) here
425 joko 1.3 elsif ($map->{target}->{address} =~ m/^(code|expr):(.+?)$/) {
426 joko 1.1 my $target_dynamic_type = $1;
427     my $target_dynamic_expression = $2;
428     if (lc $target_dynamic_type eq 'code') {
429     $map->{target} = $mapObject->$target_dynamic_expression($map);
430     }
431     }
432    
433 joko 1.3 # merging - V1
434     #map { $opts->{$_} = $map->{$_}; } keys %$map;
435     # trace
436     #print Dumper($self->{options});
437    
438     # merging - V2
439    
440     # merge local map with local opts
441 joko 1.5
442     # delete undef'd items in $map
443    
444 joko 1.3 # enable cloning
445     # FIXME: do we really need cloning here? trade safety/encapsulation for speed?
446     Hash::Merge::set_clone_behavior(1);
447     Hash::Merge::set_behavior( 'RIGHT_PRECEDENT' );
448 joko 1.5 #Hash::Merge::set_behavior( 'STORAGE_PRECEDENT' );
449     #Hash::Merge::set_behavior( 'RETAINMENT_PRECEDENT' );
450     # TODO: add an option to Hash::Merge not to overwrite set items with undefined/empty/not assigned ones
451 joko 1.3 my $locals_merged = merge( $opts, $map );
452    
453     # trace
454     #print Dumper($opts);
455     #print Dumper($map);
456     #print Dumper($locals_merged);
457     #exit;
458    
459     # merge local-merged ones with instance-wide options
460     Hash::Merge::set_clone_behavior(0);
461     $self->{options} = merge( $self->{options}, $locals_merged );
462    
463     # trace
464     #print Dumper($self->{options});
465     #exit;
466    
467 joko 1.1
468     $self->{state}->{options_ready} = 1;
469    
470     return 1;
471    
472     }
473 joko 1.2
474    
475 joko 1.3 sub _preCheckOptions {
476    
477 joko 1.1 my $self = shift;
478 joko 1.3 my $opts = shift;
479    
480     # trace
481     #print Dumper($opts);
482     #exit;
483    
484     if (!$opts->{process}->{mode}) {
485     $logger->error( __PACKAGE__ . "->_preCheckOptions failed: Please specify \"--action=(load|save)\".");
486     return;
487 joko 1.1 }
488    
489 joko 1.3 # the type of the to-be-synced item
490     if (!$opts->{source}->{nodeType}) {
491     $logger->error( __PACKAGE__ . "->_preCheckOptions failed: Please specify \"source-type\".");
492     return;
493 joko 1.1 }
494 joko 1.3 # the name of the (container-) node the items are listed in
495     if (!$opts->{source}->{nodeName}) {
496     $logger->error( __PACKAGE__ . "->_preCheckOptions failed: Please specify \"source-node\".");
497     return;
498 joko 1.2 }
499 joko 1.1
500 joko 1.3 # a "map"-declaration which module to use for mapping- and/or lookup-purposes
501     if (!$opts->{map}) {
502     $logger->warning( __PACKAGE__ . "->_preCheckOptions: No mapping supplied - please check key 'map|mappings' in global configuration or specify additional argument '--mapping-module'.");
503 joko 1.1 return;
504     }
505 joko 1.3 if (!$opts->{map}->{moduleName}) {
506     $logger->warning( __PACKAGE__ . "->_preCheckOptions: Currently only perl-modules can provide mappings: Please specify one with '--mapping-module=My::Mapping::Module'.");
507     return;
508 joko 1.1 }
509    
510 joko 1.3 return 1;
511 joko 1.2
512     }
513 joko 1.1
514    
515 joko 1.2
516 joko 1.3 sub _prepare_sync {
517 joko 1.2
518     my $self = shift;
519 joko 1.1
520     # TODO:
521     # + if action == PUSH: start processing
522     # -+ if action == PULL: swap metadata and start processing
523     # - if action == FULL: start processing, then swap metadata and (re-)start processing
524    
525 joko 1.3 #print "dir: ", $self->{args}->{direction}, "\n";
526    
527 joko 1.1 # manipulate metainfo according to direction of synchronization
528 joko 1.3 if (lc $self->{options}->{process}->{mode} eq 'push') {
529     # just do it ... (don't modify any metadata)
530    
531     } elsif (lc $self->{options}->{process}->{mode} eq 'pull') {
532     # swap source and target metadata
533     # TODO: introduce different mechanism to make more then two partners (descents) possible
534 joko 1.1 ($self->{meta}->{source}, $self->{meta}->{target}) =
535     ($self->{meta}->{target}, $self->{meta}->{source});
536 joko 1.5 #($self->{options}->{source}, $self->{options}->{target}) =
537     # ($self->{options}->{target}, $self->{options}->{source});
538 joko 1.3
539     } elsif (lc $self->{options}->{process}->{mode} eq 'full') {
540     # TODO:
541     # do a pull first and a push afterwards
542     # this requires us to be called somehow recursively - just one recursion level ;-)
543    
544 joko 1.1 } else {
545 joko 1.3 # TODO: are there any other synchronization modes besides PULL, PUSH, FULL?
546    
547 joko 1.1 }
548    
549     # import flag means: prepare the source node to be syncable
550     # this is useful if there are e.g. no "ident" or "checksum" columns yet inside a DBI like (row-based) storage
551 joko 1.6 if ($self->{options}->{process}->{prepare}) {
552 joko 1.1 $self->_prepareNode_MetaProperties('source');
553     $self->_prepareNode_DummyIdent('source');
554     #return;
555     #$self->_erase_all($opts->{source_node});
556     }
557    
558     # erase flag means: erase the target
559     #if ($opts->{erase}) {
560 joko 1.6 if ($self->{options}->{process}->{erase}) {
561 joko 1.1 # TODO: move this method to the scope of the synchronization core and wrap it around different handlers
562     #print "ERASE", "\n";
563     $self->_erase_all('target');
564     }
565 joko 1.2
566     return 1;
567 joko 1.1
568 joko 1.3 }
569    
570     sub _getDirectedArrow {
571     my $self = shift;
572     my $mode = shift;
573     $mode ||= '';
574    
575     if (lc $mode eq 'push') {
576     return '->';
577     } elsif (lc $mode eq 'pull') {
578     return '<-';
579     } elsif (lc $mode eq 'full') {
580     return '<->';
581     } else {
582     return '';
583     }
584 joko 1.1 }
585    
586     1;

MailToCvsAdmin">MailToCvsAdmin
ViewVC Help
Powered by ViewVC 1.1.26 RSS 2.0 feed