1package Board::System;
2################################################################
3# Board::System
4
5# 2002/01/27 Yoichi Imai <yoichi@silver-forest.com>
6
7# Copyright (C) 2002 Yoichi Imai <yoichi@silver-forest.com>
8
9# This program is free software; you can redistribute it and/or
10# modify it under the terms of the GNU General Public License
11# as published by the Free Software Foundation; either version 2
12# of the License, or (at your option) any later version.
13
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17# GNU General Public License for more details.
18
19# You should have received a copy of the GNU General Public License
20# along with this program; if not, write to the Free Software
21# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
22
23# $Id: System.pm,v 1.6 2003/05/28 04:52:53 togawa Exp $
24################################################################
25
26=head1 NAME
27
28Board::System - Board�����ƥ�Υ饤�֥����ʬ
29
30=head1 SYNOPSIS
31
32 my $board = new Board::System;
33 $board->Load();
34 foreach my $article (@{$board->article_list}) { ... }
35
36=head1 DESCRIPTION
37Board�����ƥ�δ�����
38
39=cut
40################################################################
41
42
43use strict;
44use Fcntl ':flock';
45use ObjectTemplate;
46use Exporter;
47use DateTime::Format;
48use CodeConv;
49use Board::Article;
50use vars qw(@ISA @EXPORT);
51@ISA = qw(ObjectTemplate);
52@EXPORT = qw(attributes);
53
54attributes qw(article_list msgid_hash refid_hash);
55
56sub Error($$)
57{
58    my ($self, $msg) = @_;
59    die "Error: $self, $msg\n";
60    exit 1;
61}
62
63sub Load
64{
65    my $self = shift;
66
67    my @article_list;
68    unless(open(FH, $Board::BoardData)) {
69	$self->Error("cannot open : '$Board::BoardData'");
70	return;
71    }
72
73    eval 'flock(FH, LOCK_EX)';
74    foreach my $line (<FH>) {
75	chomp($line);
76	my @list = split("\t", $line);
77	next if $#list == -1; # remove nothing line
78	my $article = new Board::Article;
79	$article->Set(@list);
80	push(@article_list, $article);
81    }
82    eval 'flock(FH, LOCK_UN)';
83    close(FH);
84    $self->article_list(\@article_list);
85}
86
87# this function doesn't read deleted or fake_root message
88sub LoadLightly
89{
90    my $self = shift;
91    my @article_list;
92
93    unless(open(FH, $Board::BoardData)) {
94	$self->Error("cannot open : '$Board::BoardData'");
95	return;
96    }
97
98    eval 'flock(FH, LOCK_EX)';
99    foreach my $line (<FH>) {
100	chomp($line);
101	my @list = split("\t", $line);
102	next if ($#list == -1); # remove nothing line
103	next if ($list[0] eq ''); # deleted message
104	next if ($list[2] eq 'root' && $list[3] ne ''); # fake root message
105	my $article = new Board::Article;
106	$article->Set(@list);
107	push(@article_list, $article);
108    }
109    eval 'flock(FH, LOCK_UN)';
110    close(FH);
111
112    $self->article_list(\@article_list);
113}
114sub Save
115{
116    my $self = shift;
117    my %skip_msgids;
118
119    my @article_list = @{$self->article_list};
120
121    if(@article_list > $Board::MaxArticle) {
122	for(my $i = 0; $i < @article_list - $Board::MaxArticle; $i++) {
123	    unless ($article_list[$i]->IsFakeRoot()) {
124		$article_list[$i]->Delete();
125	    }
126	}
127    }
128
129    $self->CreateMsgidHash();
130    $self->CreateRefidHash();
131
132    # remove blank tree
133    foreach my $root_article (@{$self->refid_hash->{'root'}}) {
134	if($self->IsDeletedTree($root_article)) {
135	    foreach my $msgid ($self->GetTreeMemberMsgids($root_article, ())) {
136		$skip_msgids{$msgid} = 1;
137	    }
138	}
139    }
140
141    unless(open(FH, '>' . $Board::BoardData)) {
142	$self->Error("cannot open(write) : '$Board::BoardData'");
143	return;
144    }
145    eval 'flock(FH, LOCK_EX)';
146    foreach my $article (@article_list) {
147	print FH $article->GetStr() if $article && !($skip_msgids{$article->msgid} == 1);
148    }
149    eval 'flock(FH, LOCK_UN)';
150    close(FH);
151}
152
153sub Add
154{
155    my ($self, $article) = @_;
156    push(@{$self->article_list}, $article);
157}
158
159sub CreateMsgidHash
160{
161    my $self = shift;
162    $self->msgid_hash({});
163    foreach my $article (@{$self->article_list}) {
164	$self->msgid_hash->{$article->msgid} = $article;
165    }
166}
167sub CreateRefidHash
168{
169    my $self = shift;
170    my $refid_hash = {};
171    foreach my $article (@{$self->article_list}) {
172	$refid_hash->{$article->refid} = [] unless (defined($refid_hash->{$article->refid}));
173	push(@{$refid_hash->{$article->refid}}, $article);
174    }
175    $self->refid_hash($refid_hash);
176}
177sub GetNewMsgid
178{
179    my $self = shift;
180    my $new_id;
181
182    foreach my $article (@{$self->article_list}) {
183	$new_id = $article->msgid if ($new_id < $article->msgid);
184    }
185    return $new_id + 1;
186}
187sub AddNewMsg
188{
189    my ($self, $delete_key, $refid, $diary, $ruri_code, $subject, $name, $email, $site, $host, $body) = @_;
190    my $ref;
191
192    my $article = new Board::Article;
193
194    CodeConv::toeuc(\$subject);
195    CodeConv::toeuc(\$name);
196    CodeConv::toeuc(\$email);
197    CodeConv::toeuc(\$site);
198    CodeConv::toeuc(\$body);
199
200    $article->Set($delete_key,
201		  $self->GetNewMsgid,
202		  $refid,
203		  $diary,
204		  $ruri_code,
205		  time(),
206		  $subject,
207		  $name,
208		  $email,
209		  $site,
210		  $host,
211		  $body);
212
213    $self->Add($article);
214
215    return $article;
216}
217sub SearchFakeRoot
218{
219    my ($self, $diary) = @_;
220    my $fake_root;
221
222    foreach my $article (@{$self->article_list}) {
223	$fake_root = $article if ($article->IsRoot() && $article->ref_diary == $diary);
224    }
225    return $fake_root;
226}
227sub IsDeletedTree
228{
229    my ($self, $root_article) = @_;
230
231    return 0 if ($root_article->delete_key ne '' && !($root_article->IsFakeRoot()));
232    foreach my $article (@{$self->refid_hash->{$root_article->msgid}}) {
233	if ($self->IsDeletedTree($article) == 0) {
234	    return 0;
235	}
236    }
237    return 1;
238}
239sub GetTreeMemberMsgids
240{
241    my ($self, $root_article, @list) = @_;
242
243    return @list unless ($root_article);
244
245    push(@list, $root_article->msgid);
246    foreach my $article (@{$self->refid_hash->{$root_article->msgid}}) {
247	push(@list, $self->GetTreeMemberMsgids($article, @list));
248    }
249
250    return @list;
251}
252# not instance method
253sub get_diary_title
254{
255    my $diary = shift;
256    my ($title, $new_count);
257
258    die "diary_title_error" unless $diary =~ /(\d{4})(\d{2})(\d{2})(\d+)/;
259
260    my ($year, $month, $day, $section) = ($1, $2, $3, $4);
261    my $file = "${HNS::System::DiaryDir}/${year}/d${year}${month}${day}.hnf";
262    unless(open(FH, $file)) {
263	die ("cannot open hnf : '$file'");
264	return;
265    }
266
267    $new_count = 1;
268    eval 'flock(FH, LOCK_EX)';
269    foreach my $line (<FH>) {
270	if ($line =~ /^NEW (.*)/) {
271	    $title = $1;
272	} elsif ($line =~ /^LNEW [^ ]* (.*)/) {
273	    $title = $1;
274	} elsif ($line =~ /^RLNEW [^ ]* [^ ]* (.*)/) {
275	    $title = $1;
276	}
277
278	$new_count -= 1 if ($line =~ /^GRP/);
279	if ($line =~ /^R?L?NEW /) {
280	    last if ($new_count == $section);
281	    $new_count++;
282	}
283    }
284    eval 'flock(FH, LOCK_UN)';
285    close(FH);
286
287    CodeConv::toeuc(\$title);
288    return $title;
289}
2901;
291