1 package Gruta
::Source
::FS
;
8 package Gruta
::Data
::FS
::BASE
;
12 sub ext
{ return '.META'; }
18 $self->source->_assert();
20 return $self->source->{path
} . $self->base() .
21 $self->get('id') . $self->ext();
29 $self->source( $driver );
31 if (not open F
, $self->_filename()) {
38 if(/^([^:]*): (.*)$/) {
39 my ($key, $value) = ($1, $2);
43 if (grep (/^$key$/, $self->fields())) {
44 $self->set($key, $value);
58 $self->source( $driver ) if $driver;
60 my $filename = $self->_filename();
62 open F
, '>' . $filename or croak
"Can't write " . $filename . ': ' . $!;
64 foreach my $k ($self->fields()) {
69 print F
$f . ': ' . ($self->get($k) || '') . "\n";
82 $self->source( $driver ) if $driver;
84 unlink $self->_filename();
89 package Gruta
::Data
::FS
::Story
;
91 use base
'Gruta::Data::Story';
92 use base
'Gruta::Data::FS::BASE';
96 sub base
{ return Gruta
::Data
::FS
::Topic
::base
() . $_[0]->get('topic_id') . '/'; }
98 sub fields
{ grep !/content/, $_[0]->SUPER::fields
(); }
99 sub vfields
{ return ($_[0]->SUPER::vfields
(), 'content'); }
105 $self->SUPER::save
( $driver );
107 my $filename = $self->_filename();
108 $filename =~ s/\.META$//;
110 open F
, '>' . $filename or croak
"Can't write " . $filename . ': ' . $!;
112 print F
$self->get('content') || '';
115 # destroy the topic index, to be rewritten
116 # in the future by _topic_index()
117 $filename =~ s!/[^/]+$!/.INDEX!;
126 my $hits = $self->get('hits') + 1;
128 $self->set('hits', $hits);
131 $self->source->_update_top_ten($hits, $self->get('topic_id'),
141 my $filename = $self->_filename();
142 $filename =~ s/\.META$/.TAGS/;
145 if (open F
, '>' . $filename) {
146 print F
join(', ', @_), "\n";
151 if (open F
, $filename) {
156 @ret = split(/,\s+/, $l);
163 package Gruta
::Data
::FS
::Topic
;
165 use base
'Gruta::Data::Topic';
166 use base
'Gruta::Data::FS::BASE';
168 sub base
{ return '/topics/'; }
174 $self->SUPER::save
( $driver );
176 my $filename = $self->_filename();
177 $filename =~ s/\.META$//;
184 package Gruta
::Data
::FS
::User
;
186 use base
'Gruta::Data::User';
187 use base
'Gruta::Data::FS::BASE';
189 sub ext
{ return ''; }
190 sub base
{ return '/users/'; }
192 package Gruta
::Data
::FS
::Session
;
194 use base
'Gruta::Data::Session';
195 use base
'Gruta::Data::FS::BASE';
197 sub ext
{ return ''; }
198 sub base
{ return '/sids/'; }
200 package Gruta
::Source
::FS
;
207 $self->{path
} or croak
"Invalid path";
217 my $o = ${class}->new( id
=> $id );
221 sub topic
{ return _one
( @_, 'Gruta::Data::FS::Topic' ); }
228 my $path = $self->{path
} . Gruta
::Data
::FS
::Topic
::base
();
230 if (opendir D
, $path) {
231 while (my $id = readdir D
) {
232 next unless -d
$path . $id;
233 next if $id =~ /^\./;
244 sub user
{ return _one
( @_, 'Gruta::Data::FS::User' ); }
251 my $path = $self->{path
} . Gruta
::Data
::FS
::User
::base
();
253 if (opendir D
, $path) {
254 while (my $id = readdir D
) {
255 next if -d
$path . $id;
267 my $topic_id = shift;
270 my $story = Gruta
::Data
::FS
::Story
->new( topic_id
=> $topic_id, id
=> $id );
271 if (not $story->load( $self )) {
273 $story = Gruta
::Data
::FS
::Story
->new( topic_id
=> $topic_id . '-arch',
276 if (not $story->load( $self )) {
281 # now load the content
282 my $file = $story->_filename();
283 $file =~ s/\.META$//;
285 open F
, $file or croak
"Can't open $file content: $!";
287 $story->set('content', join('', <F
>));
295 my $topic_id = shift;
299 my $path = $self->{path
} . Gruta
::Data
::FS
::Topic
::base
() . $topic_id;
301 if (opendir D
, $path) {
302 while (my $id = readdir D
) {
303 if ($id =~ s/\.META$//) {
317 my $topic_id = shift;
319 my $index = $self->{path
} . Gruta
::Data
::FS
::Topic
::base
() .
320 $topic_id . '/.INDEX';
322 if (not open I
, $index) {
325 foreach my $id ($self->stories($topic_id)) {
326 my $story = $self->story($topic_id, $id);
328 push(@i, $story->get('date') . ':' . $id);
331 open I
, '>' . $index or croak
"Can't create INDEX for $topic_id: $!";
334 foreach my $l (reverse(sort(@i))) {
345 sub _update_top_ten
{
348 my $topic_id = shift;
351 my $index = $self->{path
} . Gruta
::Data
::FS
::Topic
::base
() . '/.top_ten';
356 if (open F
, $index) {
358 while (my $l = <F
>) {
361 my ($h, $t, $i) = split(':', $l);
363 if ($u == 0 && $h < $hits) {
365 push(@l, "$hits:$topic_id:$id");
368 if ($t ne $topic_id or $i ne $id) {
376 if ($u == 0 && scalar(@l) < 100) {
378 push(@l, "$hits:$topic_id:$id");
382 if (open F
, '>' . $index) {
402 sub stories_by_date
{
404 my $topic_id = shift;
408 $args{offset
} = 0 if $args{offset
} < 0;
410 open I
, $self->_topic_index($topic_id);
419 my ($date, $id) = (/^(\d*):(.*)$/);
421 # skip future stories
422 next if not $args{future
} and
424 $date > $args{today
};
426 # skip if date is above the threshold
427 next if $args{'to'} and $date > $args{'to'};
429 # exit if date is below the threshold
430 last if $args{'from'} and $date < $args{'from'};
432 # skip offset stories
433 next if $args{'offset'} and ++$o <= $args{'offset'};
437 # exit if we have all we need
438 last if $args{'num'} and $args{'num'} == scalar(@r);
448 my $topic_id = shift;
451 my @q = split(/\s+/,$query);
455 foreach my $id ($self->stories_by_date( $topic_id )) {
457 my $story = $self->story($topic_id, $id);
458 my $content = $story->get('content');
461 # try complete query first
462 if($content =~ /\b$query\b/i) {
468 if(length($q) > 1 and $content =~ /\b$q\b/i) {
475 push(@r, $id) if $found;
481 sub stories_top_ten
{
487 my $index = $self->{path
} . Gruta
::Data
::FS
::Topic
::base
() . '/.top_ten';
489 if (open F
, $index) {
492 while (defined(my $l = <F
>) and $num--) {
494 push(@r, [ split(':', $l) ]);
519 sub session
{ return _one
( @_, 'Gruta::Data::FS::Session' ); }
521 sub purge_old_sessions
{
524 my $path = $self->{path
} . Gruta
::Data
::FS
::Session
::base
();
526 if (opendir D
, $path) {
527 while(my $s = readdir D
) {
555 sub insert_topic
{ $_[0]->_insert($_[1], 'Gruta::Data::FS::Topic'); }
556 sub insert_user
{ $_[0]->_insert($_[1], 'Gruta::Data::FS::User'); }
562 if (not $story->get('id')) {
563 # alloc an id for the story
566 while ($self->story($story->get('topic_id'), $id)) {
570 $story->set('id', $id);
573 $self->_insert($story, 'Gruta::Data::FS::Story');
577 sub insert_session
{ $_[0]->_insert($_[1], 'Gruta::Data::FS::Session'); }
583 mkdir $self->{path
}, 0755;
584 mkdir $self->{path
} . Gruta
::Data
::FS
::Topic
::base
(), 0755;
585 mkdir $self->{path
} . Gruta
::Data
::FS
::User
::base
(), 0755;
586 mkdir $self->{path
} . Gruta
::Data
::FS
::Session
::base
(), 0755;
593 my $s = bless( { @_ }, $class);