/[pub]/suikawiki/script/lib/SuikaWiki/Implementation.pm
Suika

Contents of /suikawiki/script/lib/SuikaWiki/Implementation.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.14 - (hide annotations) (download)
Mon Mar 29 03:22:50 2004 UTC (21 years, 3 months ago) by wakaba
Branch: MAIN
Changes since 1.13: +3 -3 lines
(engine_version): Increased to 2.9.4

1 wakaba 1.1
2     =head1 NAME
3    
4 wakaba 1.8 SuikaWiki::Implementation - SuikaWiki: WikiEngine Core
5    
6     =head1 DESCRIPTION
7    
8     This module implements core part of the SuikaWiki WikiEngine.
9     All implemented features of WikiEngine can be called directly
10     or indirectly from instance of this module (with some exception
11     such as functions provided by WikiPlugin modules).
12    
13     This module is part of SuikaWiki.
14    
15     =head1 SYNOPSIS
16    
17     require SuikaWiki::Implementation;
18     my $WIKI = new SuikaWiki::Implementation;
19     ...
20     $WIKI->exit;
21    
22     C<lib/suikawiki.pl> might be a good example for instanciating WikiEngine.
23 wakaba 1.1
24     =cut
25    
26     package SuikaWiki::Implementation;
27     use strict;
28 wakaba 1.14 our $VERSION = do{my @r=(q$Revision: 1.13 $=~/\d+/g);sprintf "%d."."%02d" x $#r,@r};
29 wakaba 1.1
30     =head1 METHODS
31    
32     =over 4
33    
34     =item $wiki = SuikaWiki::Implementation->new ()
35    
36     Constructs new instance of wiki implementation
37    
38     =cut
39    
40     sub new ($;%) {
41 wakaba 1.2 my $self = bless {
42 wakaba 1.8 driver_name => 'WikiImplementation',
43     driver_version => '0.0',
44     driver_uri_reference => q<about:>,
45     engine_name => 'SuikaWiki',
46 wakaba 1.14 engine_version => '2.9.4',
47 wakaba 1.8 engine_uri_reference => q<http://suika.fam.cx/~wakaba/-temp/wiki/wiki?SuikaWiki>,
48 wakaba 1.2 }, shift;
49 wakaba 1.1
50     $self;
51     }
52    
53 wakaba 1.2 =item $wiki->init_variables
54    
55     Initialize per-access variables. This method should be called
56     before other init_* methods are to be called.
57    
58     =cut
59    
60     sub init_variables ($) {
61     my $self = shift;
62 wakaba 1.7 $self->close_input;
63 wakaba 1.2 $self->{var} = {};
64 wakaba 1.10 $self->___raise_event (name => 'setting_initial_variables');
65 wakaba 1.2 }
66    
67 wakaba 1.1 =item $wiki->init_plugin
68    
69     Prepares to use wiki plugins
70    
71     =cut
72    
73     sub init_plugin ($) {
74     my $self = shift;
75     require SuikaWiki::Plugin;
76 wakaba 1.5 $self->{plugin} = SuikaWiki::Plugin->new (wiki => $self);
77 wakaba 1.1
78 wakaba 1.10 $self->___raise_event (name => 'plugin_manager_loaded');
79 wakaba 1.1 }
80    
81     =item $wiki->init_view
82    
83     Prepares to use wikiview
84    
85     =cut
86    
87     sub init_view ($) {
88     my $self = shift;
89     require SuikaWiki::View::Implementation;
90     $self->{view} = SuikaWiki::View::Implementation->new (wiki => $self);
91    
92 wakaba 1.10 $self->___raise_event (name => 'view_implementation_loaded');
93 wakaba 1.1 }
94    
95     =item $wiki->init_db
96    
97     Prepares to use wiki database
98    
99     =cut
100    
101     sub init_db ($) {
102 wakaba 1.10 my $wiki = shift;
103     return if ref $wiki->{db}; ## Already initialized
104     $wiki->{config}->{lock}
105 wakaba 1.11 ||= {-directory => $wiki->{config}->{path_to}->{db__lock__dir},
106     -retry => 40,
107 wakaba 1.1 -error_handler => sub {
108 wakaba 1.10 my ($self, %o) = @_;
109     if ($o{level} eq 'fatal' or $wiki->{config}->{debug}->{db}) {
110     if ($wiki->{config}->{path_to}->{db__lock__log_file}) {
111     open LOG, '>>', $wiki->{config}->{path_to}
112     ->{db__lock__log_file};
113     print LOG scalar (gmtime),
114 wakaba 1.1 "\@@{[time]} @{[$$]} {$o{level}}: LOCK: ",
115 wakaba 1.10 $o{msg}, Carp::longmess,"\n";
116     close LOG;
117     }
118     }
119     if ($o{level} eq 'fatal') {
120     die $o{msg};
121     }
122 wakaba 1.1 },
123     };
124 wakaba 1.10 $wiki->{var}->{db}->{lock_prop} = sub {
125 wakaba 1.1 my $prop = shift;
126 wakaba 1.10 my %lock = %{$wiki->{config}->{lock}};
127 wakaba 1.1 $lock{-name} = $prop;
128 wakaba 1.10 $lock{-share} = defined $wiki->{var}->{db}->{read_only}->{$prop}
129     ? $wiki->{var}->{db}->{read_only}->{$prop}
130     : $wiki->{var}->{db}->{read_only}->{'#default'};
131 wakaba 1.13 if ($lock{-share}) {
132     $lock{-module} = (defined $wiki->{var}->{db}->{read_lock_module}->{$prop}
133     ? $wiki->{var}->{db}->{read_lock_module}->{$prop}
134     : $wiki->{var}->{db}->{read_lock_module}->{'#default'});
135     } else {
136     $lock{-module} = (defined $wiki->{var}->{db}->{lock_module}->{$prop}
137     ? $wiki->{var}->{db}->{lock_module}->{$prop}
138     : $wiki->{var}->{db}->{lock_module}->{'#default'});
139     }
140 wakaba 1.1 \%lock;
141     };
142    
143     require SuikaWiki::DB::Logical;
144 wakaba 1.10 $wiki->{db} = new SuikaWiki::DB::Logical;
145 wakaba 1.1
146 wakaba 1.10 $wiki->___raise_event (name => 'database_loaded');
147 wakaba 1.1 }
148    
149 wakaba 1.3 =item $wiki->view_in_mode (%opt)
150    
151     Doing main process in accordance to the mode.
152    
153     Actually, this method only raises an event of 'view_in_mode'.
154     So that "doing main process" code should be registered as an event procedure
155     of 'view_in_mode'.
156    
157     =cut
158    
159     sub view_in_mode ($%) {
160     my ($self, %opt) = @_;
161 wakaba 1.10 $self->___raise_event (name => 'view_in_mode', argv => \%opt);
162 wakaba 1.1 }
163    
164 wakaba 1.5
165 wakaba 1.8 =item $uri = $wiki->uri_reference (%option)
166    
167     Returning URI reference that refers the wiki or a WikiPage.
168    
169     Load {input} before calling this method or specify appropriate C<wiki_uri>
170     option to get proper result.
171    
172     One or two URI reference(s) is returned as C<URI> object.
173     See C<base> option.
174    
175     Available options:
176    
177     =over 4
178    
179     =item anchor_no => positive-integer (default: none)
180    
181     Numeral anchor index. With this option, C<fragment> option
182     is ignored.
183    
184     =item base => URI reference (default: none)
185    
186     Base URI reference. C<wantarray ? (relative, absolute) : relative> is
187     returned when C<base> is specified. Otherwise, C<(absolute, absolute)>
188     is returned.
189    
190     =item fragment => URI reference fragment (default: none)
191    
192     URI refernece fragment. This option value MUST be encoded
193     by URI escape encoding.
194    
195     =item mode => mode-name (default: "default")
196    
197     WikiView mode in which referred.
198 wakaba 1.2
199 wakaba 1.8 =item page => [WikiName] (default: none)
200    
201     WikiName to that WikiPage URI reference is referring.
202    
203     =item param => {name1 => value1, name2 => value2,...} (default: none)
204    
205     Additional query parameters. Names and values are automatically
206     encoded by URI escape encoding if necessary.
207    
208     =item up_to_date => 1/0 (default: 0)
209    
210     "Up-to-date" URI query parameter for cheating cache.
211    
212     =item wiki_uri => URI reference (default: auto)
213    
214     A base URI reference referring the wiki itself.
215    
216     =item with_lm => 1/0 (default: 0)
217    
218     "Last modified" URI query parameter for chating WWW browser history.
219    
220     =back
221 wakaba 1.2
222     =cut
223    
224 wakaba 1.6 sub uri_reference ($;%) {
225 wakaba 1.8 my ($self, %opt) = @_; ## Note: $opt{wiki_uri} must be a URI(.pm) if any.
226     my $uri = $opt{wiki_uri} || $self->___get_wiki_uri;
227 wakaba 1.6
228     ## SuikaWiki 3.0 format
229     my $query_param = qr/[^0-9A-Za-z_.-]/;
230 wakaba 1.7 my @param = map {my $n = $_; $n =~ tr/_/-/;
231     $self->___uri_escape_encode ($n, $query_param).'='.
232 wakaba 1.6 $self->___uri_escape_encode ($opt{param}->{$_}, $query_param)}
233     keys %{$opt{param}};
234     push @param, 'mode='.$self->___uri_escape_encode ($opt{mode}, $query_param)
235     if $opt{mode};
236     push @param, 'x-d='.time if $opt{up_to_date};
237     if ($opt{page}) {
238     if ($opt{with_lm} and ref $self->{db}) {
239     push @param, 'x-lm='
240     . $self->___uri_escape_encode
241     ($self->{db}->get (lastmodified => $opt{page}),
242     $query_param);
243     }
244 wakaba 1.9 my $page = $opt{page}->stringify (wiki => $self);
245 wakaba 1.6 if (@param) {
246     ## TODO: Encode by $wiki->{config}->{charset}->{uri_param_encode}
247     unshift @param, 'mypage='.$self->___uri_escape_encode
248     ($page, $query_param);
249     push @param, '_charset_='.$self->{config}->{charset}->{uri_param_encode};
250     ## TODO: downgrade to &
251 wakaba 1.12 $uri->query (join ';', sort @param);
252 wakaba 1.6 } else {
253     ## TODO: Encode by $wiki->{config}->{charset}->{uri_query_encode}
254     $uri->query ($self->___uri_escape_encode ($page, $query_param));
255     }
256     } elsif (@param) {
257     push @param, '_charset_='.$self->{config}->{charset}->{uri_param_encode};
258 wakaba 1.12 $uri->query (join ';', sort @param);
259 wakaba 1.6 }
260    
261     if ($opt{anchor_no}) {
262     $uri->fragment ('anchor-'.$opt{anchor_no});
263     } elsif ($opt{fragment}) {
264     $uri->fragment ($opt{fragment});
265     }
266    
267     if (defined $opt{base}) {
268     $opt{base} = $self->{input}->request_uri
269     if ref $self->{input} and not ref $opt{base} and $opt{base} eq '1';
270     return wantarray ? ($uri->rel ($opt{base}), $uri) : $uri->rel ($opt{base});
271     } else {
272     return ($uri, $uri);
273     }
274     }
275    
276 wakaba 1.8 =item 1/0 = $wiki->uri_is_part_of_wiki ($uri-reference)
277    
278     Check whether given URI reference is "part of" the wiki.
279    
280     =cut
281    
282 wakaba 1.6 sub uri_is_part_of_wiki ($$) {
283     my ($self, $uri) = @_;
284     my $wiki_uri = ''.$self->___get_wiki_uri;
285 wakaba 1.8 $uri = URI->new (substr ($uri, 0, length ($wiki_uri)));
286     $uri eq $wiki_uri ? 1 : 0;
287 wakaba 1.6 }
288    
289     sub ___get_wiki_uri ($) {
290     my ($self) = shift;
291     my $uri;
292     if (ref $self->{___uri}) {
293     $uri = $self->{___uri}->clone;
294     } elsif (ref $self->{input}) {
295     $uri = $self->{input}->request_uri (no_path_info => 1, no_query => 1);
296     $self->{___uri} = $uri->clone;
297     } else {
298     $uri = URI->new;
299     }
300     $uri;
301     }
302    
303     sub ___uri_escape_encode ($$;$) {
304     my ($self, $s, $char) = @_;
305     $char ||= qr([^0-9A-Za-z_.!~*'();/?:\@&=+\$,-]);
306 wakaba 1.10 ## TODO: Some fix required when utf8'ized
307 wakaba 1.6 # require Encode;
308     # $s = Encode::decode ('utf8', $s);
309     $s =~ s/($char)/sprintf '%%%02X', ord $1/ge;
310     $s;
311     }
312    
313 wakaba 1.9 sub name ($$%) {
314     require SuikaWiki::Name;
315     my ($wiki, $name, %opt) = @_;
316     SuikaWiki::Name->new ($name, wiki => $wiki, %opt);
317     }
318    
319 wakaba 1.10 =item $wiki->close_input
320 wakaba 1.8
321 wakaba 1.10 Closing input manager (C<< $wiki->{input} >>).
322 wakaba 1.8
323     =cut
324    
325 wakaba 1.10 sub close_input ($) {
326 wakaba 1.5 my $self = shift;
327 wakaba 1.10 if (ref $self->{input}) {
328     $self->___raise_event (name => 'input_close');
329     $self->{input}->exit;
330     }
331     delete $self->{input};
332 wakaba 1.5 }
333    
334 wakaba 1.8 =item $wiki->close_view
335    
336     Closing WikiView manager (C<< $wiki->close_view >>).
337    
338     =cut
339    
340 wakaba 1.5 sub close_view ($) {
341     my $self = shift;
342     $self->{view}->exit if ref $self->{view};
343     delete $self->{view};
344     }
345    
346 wakaba 1.10 =item $wiki->close_db
347    
348     Closing WikiDB (C<$wiki->{db}>).
349    
350     Although this method is automatically called by C<< $wiki->exit >>,
351     it is good practice to explicitly close something opened explicitly.
352    
353     This method make C<database_close> event called before database is
354     actually closed. Future version of this module might make event handler
355     being able to cancel closing.
356    
357     Calling this method when database is not opened makes no sense,
358     nor being C<database_close> event raisen.
359    
360     =cut
361    
362     sub close_db ($) {
363     my $self = shift;
364     if (ref $self->{db}) {
365     $self->___raise_event (name => 'database_close');
366     $self->{db}->close;
367     }
368     delete $self->{db};
369     }
370    
371 wakaba 1.8 =item $wiki->close_plugin
372    
373     Closing WikiPlugin manager (C<< $wiki->{plugin} >>).
374     Note that this method does not unload WikiPlugin modules.
375     (They are "merged" to script namespace so that unloading them
376     is almost impossible.)
377    
378     =cut
379    
380 wakaba 1.5 sub close_plugin ($) {
381     my $self = shift;
382     $self->{plugin}->exit if ref $self->{plugin};
383     delete $self->{plugin};
384     }
385    
386 wakaba 1.8 =item 1/0 = $wiki->exit
387 wakaba 1.1
388 wakaba 1.8 Exitign the wiki. This method closes input manager, WikiDB manager,
389     WikiView manager and WikiPlugin manager after C<close> event is raised.
390     Note that C<close> event handler can "cancel" exiting,
391     it makes this method return C<0>.
392    
393     This method is automatically called before C<$wiki> is destoroyed.
394 wakaba 1.1
395     =cut
396    
397     sub exit ($) {
398     my $self = shift;
399 wakaba 1.5 return 0 unless $self->___raise_event (name => 'close');
400 wakaba 1.7 $self->close_input;
401 wakaba 1.5 $self->close_db;
402     $self->close_view;
403     $self->close_plugin;
404     $self->{exited} = 1;
405     1;
406 wakaba 1.1 }
407    
408 wakaba 1.8 ## TODO: Provides "cancelable" to close event.
409    
410 wakaba 1.1 sub DESTROY ($) {
411     my $self = shift;
412 wakaba 1.5 $self->exit unless $self->{exited};
413 wakaba 1.1 }
414    
415     =back
416    
417     =head1 PUBLIC PROPERTIES
418    
419     =over 4
420    
421 wakaba 1.2 =item $wiki->{config}
422    
423     Persistent wiki configureation parameters
424     (that is not changed with the situation when is who accessing in what way)
425    
426     =over 4
427    
428     =item ->{charset}->{internal} = <IANA charset name (in lower case)>
429    
430     Character encoding scheme used in wiki implementation
431    
432     =item ->{charset}->{output} = <IANA charset name (in lower case)>
433    
434     Default character encoding scheme used to output content
435    
436 wakaba 1.5 =item ->{debug}->{$category} = 1/0 (Default 0)
437    
438     Debug mode
439    
440     Categories:
441    
442     =over 4
443    
444     =item db
445    
446     WikiDatabase related features
447    
448 wakaba 1.7 =item general
449    
450     Generic.
451    
452     =item view
453    
454     WikiView related.
455    
456 wakaba 1.5 =back
457    
458 wakaba 1.2 =item ->{entity}->{expires}->{$rulename} = {delta => $seconds}
459    
460     How long outputed entity will be fresh.
461    
462     =item ->{lock}
463 wakaba 1.1
464     Default (prototype) properties to give SuikaWiki::DB::Util::Lock
465    
466 wakaba 1.13 =item ->{nmz__uri_to_uri} = I<CODE>
467    
468     A function that converts URI reference in Namazu index into global
469     URI reference. Example:
470    
471     $wiki->{config}->{nmz__uri_to_uri} = sub {
472     my ($uri, %opt) = @_;
473     $uri =~ m!\w+$!;
474     return $opt{o}->{wiki}->uri_reference (page => $1);
475     };
476    
477 wakaba 1.2 =item ->{page}->{ $name }
478    
479     WikiPage which has feature of $name
480    
481     =item ->{path_to}->{ $name }
482 wakaba 1.1
483     Filesystem path (or path fragment) to $name
484    
485 wakaba 1.2 =back
486    
487 wakaba 1.1 =item $wiki->{db}
488    
489     Wiki main database
490    
491 wakaba 1.8 =item $wiki->{driver_name}
492    
493     Product name of the WikiDriver.
494    
495     For interoperability, only alphanumeric characters and limited symbols
496     (those allowed in RFC 2616 token) should be used as parts of product name.
497    
498     =item $wiki->{driver_version}
499    
500     WikiDriver version in string.
501    
502     For interoperability, only alphanumeric characters and limited symbols
503     (those allowed in RFC 2616 token) should be used as parts of product name.
504    
505     =item $wiki->{engine_name} (Read only)
506    
507     SuikaWiki WikiEngine name
508    
509     =item $wiki->{engine_version} (Read only)
510    
511     SuikaWiki WikiEngine version
512    
513 wakaba 1.1 =item @{$wiki->{event}->{ $event_name }}
514    
515 wakaba 1.10 Event handling procedures. See also EVENT MODEL section.
516 wakaba 1.2
517     =item $wiki->{var}
518    
519     Non-persistent wiki variable options
520     (that might vary with context such as caller's argument values)
521    
522     =over 4
523    
524 wakaba 1.6 =item ->{client}->{downgrade}->{ $feature } = $parameter
525    
526     Whether downgrade is required. See C<Downgrade> plugin module.
527    
528 wakaba 1.2 =item ->{client}->{used_for_negotiation} = [<HTTP field name>s]
529    
530     HTTP (request) header field names used to select variable content.
531     This value will be used to generate HTTP Vary header field.
532    
533     =item ->{client}->{user_agent_name} = <HTTP User-Agent field body value>
534    
535     User agent name provided by such ways as User-Agent field (in HTTP)
536     or HTTP_USER_AGENT meta variable (in HTTP-CGI).
537    
538 wakaba 1.13 =item ->{db}->{lock_module}->{ $prop } = I<module-name>
539    
540     Name for Perl module associated with C<$prop> (read and write mode),
541     implementing WikiDB Locking interface. Special C<$prop> C<#default>
542     provides fallback module name.
543    
544 wakaba 1.2 =item ->{db}->{lock_prop} = sub ($prop)
545    
546     Function returning hash reference of lock options
547     (that will be passed to SuikaWiki::DB::Util::Lock->new).
548    
549     $prop, an argument to the function, is a database property name.
550    
551 wakaba 1.13 =item ->{db}->{read_lock_module}->{ $prop } = I<module-name>
552    
553     Name for Perl module associated with C<$prop> (read-only mode),
554     implementing WikiDB Locking interface. Special C<$prop> C<#default>
555     provides fallback module name.
556    
557 wakaba 1.2 =item ->{db}->{read_only}->{ $prop } = 1/0
558    
559     Whether the database property named as $prop is opened in read only
560     mode or not. Special property name of '#default' is used to set
561     the default value referred when {read_only}->{$prop} is not specified
562     explicily.
563    
564     Note that this value must be set before the instance of database property
565     is loaded.
566    
567 wakaba 1.5 =item ->{error} = [{description => Error 1}, {description => Error 2},...]
568    
569     Trapped errors.
570    
571 wakaba 1.2 =item ->{input}
572    
573     Instance of input parameter interface (such as SuikaWiki::Input::HTTP)
574    
575     =item ->{mode} = mode name
576    
577     Wiki mode name
578    
579     =item ->{page} = [page]
580    
581     WikiPage being referred
582    
583     =back
584    
585     =item $wiki->{view}
586    
587     WikiView implementation (an instance of SuikaWiki::View::Implementation)
588    
589 wakaba 1.10 =back
590    
591     =head1 INTERNAL METHODS
592    
593     This section describes some internal methods which only intend to be
594     used by this module. These methods MUST NOT be used out of this
595     module.
596    
597     =over 4
598    
599     =item 1/0 = $wiki->___raise_event (%option)
600    
601     Raise an event. C<0> is returned if "cancel"ed, otherwise C<1>
602     returned.
603    
604     Options:
605    
606     =over 4
607    
608     =item name => event-name (required)
609    
610     Event name.
611    
612     =item argv => some-value (default: none)
613    
614     Some argument value, which is to be passed to event handlers
615     as C<< $event->{ I<argv_name> } >>.
616    
617     =item argv_name => string (default: same as C<name>)
618    
619     Name of argument. C<argv> (or C<name> if missing) is passed to event
620     handlers with this name.
621    
622     =back
623    
624 wakaba 1.1 =cut
625    
626 wakaba 1.10 sub ___raise_event ($%) {
627     my ($self, %opt) = @_;
628     my $event = {cancel => 0, name => $opt{name},
629     ($opt{argv_name}||$opt{name}) => $opt{argv}};
630     for (@{$self->{event}->{$opt{name}}}) {
631     $_->($self, $event);
632     return 0 if $event->{cancel};
633     }
634     return 1;
635     }
636    
637     =back
638    
639     =head1 EVENT MODEL
640    
641     Wiki implementation object (C<$wiki>) "raise"s some event.
642    
643     WARNING: Event model of SuikaWiki 3 is not completed yet.
644     Future revision of SuikaWiki might introduce incompatible modification
645     to the interface.
646    
647     @@TBD
648    
649     =head2 Events
650    
651     =over 4
652    
653     =item close
654    
655     When the wiki implementation is to be closed.
656    
657     This event is uncancelabel in current implementation.
658    
659     =item database_close
660    
661     When the WikiDatabase is to be closed.
662    
663     This event is uncancelable in current implementation.
664    
665     =item database_loaded
666    
667     When WikiDatabase manager is loaded. This event handler is typically
668     used to set database property module for SuikaWiki::DB::Logical.
669    
670     This event is uncancelable.
671    
672     =item plugin_manager_loaded
673    
674     When WikiPlugin manager is loaded. Note that plugins themselves are not
675     loaded yet.
676    
677     This event is uncancelable.
678    
679     =item setting_initial_variables
680    
681     On the process to set per-access variables.
682     This event is raised before other core modules such as WikiDatabase
683     or WikiPlugin are loaded.
684    
685     This event is uncancelable.
686    
687     =item view_error
688    
689     Something wrong with or something useful message is available from WikiView
690     manager.
691    
692     This event is uncancelable.
693    
694     =item view_in_mode
695    
696     C<view_in_mode> method is called.
697    
698     This event is uncancelable in current implementation.
699    
700     =back
701    
702     =head2 Example
703    
704     ## Installing a new event handler
705     push @{$wiki->{event}->{ $some_event_name }}, sub {
706     my ($wiki, $event) = @_;
707    
708     # something
709    
710     $event->{cancel} = 1 if $some_condition;
711     };
712    
713 wakaba 1.1 =head1 LICENSE
714    
715 wakaba 1.8 Copyright 2003-2004 Wakaba <w@suika.fam.cx>. All rights reserved.
716 wakaba 1.1
717     This program is free software; you can redistribute it and/or
718     modify it under the same terms as Perl itself.
719    
720     =cut
721    
722 wakaba 1.14 1; # $Date: 2004/03/19 11:24:27 $

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24