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';
94 sub base
{ return Gruta
::Data
::FS
::Topic
::base
() . $_[0]->get('topic_id') . '/'; }
96 sub fields
{ grep !/content/, $_[0]->SUPER::fields
(); }
97 sub vfields
{ return ($_[0]->SUPER::vfields
(), 'content'); }
103 $self->SUPER::save
( $driver );
105 my $filename = $self->_filename();
106 $filename =~ s/\.META$//;
108 open F
, '>' . $filename or die "Can't write " . $filename . ': ' . $!;
110 print F
$self->get('content') || '';
113 # destroy the topic index, to be rewritten
114 # in the future by _topic_index()
115 $filename =~ s!/[^/]+$!/.INDEX!;
124 my $hits = $self->get('hits') + 1;
126 $self->set('hits', $hits);
129 $self->source->_update_top_ten($hits, $self->get('topic_id'),
140 package Gruta
::Data
::FS
::Topic
;
142 use base
'Gruta::Data::Topic';
143 use base
'Gruta::Data::FS::BASE';
145 sub base
{ return '/topics/'; }
151 $self->SUPER::save
( $driver );
153 my $filename = $self->_filename();
154 $filename =~ s/\.META$//;
161 package Gruta
::Data
::FS
::User
;
163 use base
'Gruta::Data::User';
164 use base
'Gruta::Data::FS::BASE';
166 sub ext
{ return ''; }
167 sub base
{ return '/users/'; }
169 package Gruta
::Data
::FS
::Session
;
171 use base
'Gruta::Data::Session';
172 use base
'Gruta::Data::FS::BASE';
174 sub ext
{ return ''; }
175 sub base
{ return '/sids/'; }
177 package Gruta
::Source
::FS
;
182 $self->{path
} or die "Invalid path";
192 my $o = ${class}->new( id
=> $id );
196 sub topic
{ return _one
( @_, 'Gruta::Data::FS::Topic' ); }
203 my $path = $self->{path
} . Gruta
::Data
::FS
::Topic
::base
();
205 if (opendir D
, $path) {
206 while (my $id = readdir D
) {
207 next unless -d
$path . $id;
208 next if $id =~ /^\./;
219 sub user
{ return _one
( @_, 'Gruta::Data::FS::User' ); }
226 my $path = $self->{path
} . Gruta
::Data
::FS
::User
::base
();
228 if (opendir D
, $path) {
229 while (my $id = readdir D
) {
230 next if -d
$path . $id;
242 my $topic_id = shift;
245 my $story = Gruta
::Data
::FS
::Story
->new( topic_id
=> $topic_id, id
=> $id );
246 if (not $story->load( $self )) {
248 $story = Gruta
::Data
::FS
::Story
->new( topic_id
=> $topic_id . '-arch',
251 if (not $story->load( $self )) {
256 # now load the content
257 my $file = $story->_filename();
258 $file =~ s/\.META$//;
260 open F
, $file or die "Can't open $file content: $!";
262 $story->set('content', join('', <F
>));
270 my $topic_id = shift;
274 my $path = $self->{path
} . Gruta
::Data
::FS
::Topic
::base
() . $topic_id;
276 if (opendir D
, $path) {
277 while (my $id = readdir D
) {
278 if ($id =~ s/\.META$//) {
292 my $topic_id = shift;
294 my $index = $self->{path
} . Gruta
::Data
::FS
::Topic
::base
() .
295 $topic_id . '/.INDEX';
297 if (not open I
, $index) {
300 foreach my $id ($self->stories($topic_id)) {
301 my $story = $self->story($topic_id, $id);
303 push(@i, $story->get('date') . ':' . $id);
306 open I
, '>' . $index or die "Can't create INDEX for $topic_id: $!";
309 foreach my $l (reverse(sort(@i))) {
320 sub _update_top_ten
{
323 my $topic_id = shift;
326 my $index = $self->{path
} . Gruta
::Data
::FS
::Topic
::base
() . '/.top_ten';
331 if (open F
, $index) {
333 while (my $l = <F
>) {
336 my ($h, $t, $i) = split(':', $l);
338 if ($u == 0 && $h < $hits) {
340 push(@l, "$hits:$topic_id:$id");
343 if ($t ne $topic_id or $i ne $id) {
351 if ($u == 0 && scalar(@l) < 100) {
353 push(@l, "$hits:$topic_id:$id");
357 if (open F
, '>' . $index) {
377 sub stories_by_date
{
379 my $topic_id = shift;
383 $args{offset
} = 0 if $args{offset
} < 0;
385 open I
, $self->_topic_index($topic_id);
394 my ($date, $id) = (/^(\d*):(.*)$/);
396 # skip future stories
397 next if not $args{future
} and
399 $date > $args{today
};
401 # skip if date is above the threshold
402 next if $args{'to'} and $date > $args{'to'};
404 # exit if date is below the threshold
405 last if $args{'from'} and $date < $args{'from'};
407 # skip offset stories
408 next if $args{'offset'} and ++$o <= $args{'offset'};
412 # exit if we have all we need
413 last if $args{'num'} and $args{'num'} == scalar(@r);
423 my $topic_id = shift;
426 my @q = split(/\s+/,$query);
430 foreach my $id ($self->stories_by_date( $topic_id )) {
432 my $story = $self->story($topic_id, $id);
433 my $content = $story->get('content');
436 # try complete query first
437 if($content =~ /\b$query\b/i) {
443 if(length($q) > 1 and $content =~ /\b$q\b/i) {
450 push(@r, $id) if $found;
456 sub stories_top_ten
{
462 my $index = $self->{path
} . Gruta
::Data
::FS
::Topic
::base
() . '/.top_ten';
464 if (open F
, $index) {
467 while (defined(my $l = <F
>) and $num--) {
469 push(@r, [ split(':', $l) ]);
494 sub session
{ return _one
( @_, 'Gruta::Data::FS::Session' ); }
496 sub purge_old_sessions
{
499 my $path = $self->{path
} . Gruta
::Data
::FS
::Session
::Base
();
501 if (opendir D
, $path) {
502 while(my $s = readdir D
) {
530 sub insert_topic
{ $_[0]->_insert($_[1], 'Gruta::Data::FS::Topic'); }
531 sub insert_user
{ $_[0]->_insert($_[1], 'Gruta::Data::FS::User'); }
537 if (not $story->get('id')) {
538 # alloc an id for the story
541 while ($self->story($story->get('topic_id'), $id)) {
545 $story->set('id', $id);
548 $self->_insert($story, 'Gruta::Data::FS::Story');
552 sub insert_session
{ $_[0]->_insert($_[1], 'Gruta::Data::FS::Session'); }
558 mkdir $self->{path
}, 0755;
559 mkdir $self->{path
} . Gruta
::Data
::FS
::Topic
::base
(), 0755;
560 mkdir $self->{path
} . Gruta
::Data
::FS
::User
::base
(), 0755;
561 mkdir $self->{path
} . Gruta
::Data
::FS
::Session
::base
(), 0755;
568 my $s = bless( { @_ }, $class);