# FILE: fcn-logs.pl
# DESCRIPTION: Manipulate ###-log.txt and ###-search.txt
#-------------------------------------------------------------------------------
# DISCUS COPYRIGHT NOTICE
#
# Discus is copyright (c) 2002 by DiscusWare, LLC, all rights reserved.
# The use of Discus is governed by the Discus License Agreement which is
# available from the Discus WWW site at:
#    http://www.discusware.com/discus/license
#
# Pursuant to the Discus License Agreement, this copyright notice may not be
# removed or altered in any way.
#-------------------------------------------------------------------------------

use strict;
use vars qw($GLOBAL_OPTIONS $DCONF $PARAMS);

###
### Which mini-logs should be kept?
###

my @mini_logs = (1, 7);

sub get_mini_logs {
	return @mini_logs;
}

###
### create_blank_mini_logs
###
### Create blank mini logs
###

sub create_blank_mini_logs {
	my ($topic) = @_;
	dreq("adm-tpc");
	foreach my $m (@mini_logs) {
		topic_error_touch("$DCONF->{admin_dir}/msg_index/$topic-log-$m.txt");
	}	
}

###
### minilog_choose_log
###
### Chooses which minilog to use when performing a search
###

sub minilog_choose_log {
	my ($cutoff, $timecache) = @_;
	return undef if $cutoff == 0;
	$timecache = time if ! defined $timecache;
	my $days = int(($timecache - $cutoff)/86400);
	$days += 1 if ($timecache - $cutoff) % 86400 != 0;
	foreach my $ml (sort { $a <=> $b } @mini_logs) {
		return $ml if $ml >= $days;
	}
	return undef;	
}

###
### minilog_maintenance
###
### Removes outdated entries from recent posting log file, or creates new
### minilogs if one or more is missing (usually called when operations unrelated
### to logs are running, so it sequentially updates the appropriate logs)
###

sub minilog_maintenance {
	return undef if $GLOBAL_OPTIONS->{database};
	my ($threshold, $topic) = @_;
	if ($topic == 0) {
		my @dir = ();
		my $d = {};
		opendir(DIR, "$DCONF->{admin_dir}/msg_index");
		while (my $dir = readdir(DIR)) {
			if ($dir =~ m|^(\d+)-log\.txt$|) {
				$d->{$1} += scalar(@mini_logs);
			} elsif ($dir =~ m|^(\d+)-log-(\d+)\.txt$|) {
				$d->{$1} -= 1;
				
			}
			next if $dir !~ m|^(\d+)-log-(\d+)\.txt$|;
			next if $2 != $threshold && $threshold;
			my @s = stat "$DCONF->{admin_dir}/msg_index/$dir";
			push @dir, { file => $dir, topic => $1, mtime => $s[9], thres => $2 };
		}
		closedir(DIR);
		foreach my $k (keys(%{ $d })) {
			next if $d->{$k} == 0;
			my $rv = create_minilog($k);
			return $rv if $rv == 1;
		}
		my $one = (sort { $a->{mtime} <=> $b->{mtime} } @dir)[0];
		$topic = $one->{topic};
		$threshold = $one->{thres};
	}
	return undef if $topic == 0;
	return undef if ! -e "$DCONF->{admin_dir}/msg_index/$topic-log-$threshold.txt";
	my $cutoff = time - (24*60*60*$threshold);
	my $fsize = 0;
	if (open(FILE, "< $DCONF->{admin_dir}/msg_index/$topic-log-$threshold.txt")) {
		if (open(FILE2, "> $DCONF->{admin_dir}/msg_index/$topic-log-$threshold.NEW")) {
			while (<FILE>) {
				my $h = log_line_to_hash($_);
				if ($h->{time} >= $cutoff) {
					print FILE2 $_;
					$fsize += length($_);
				}
			}
			close (FILE2);
			close (FILE);
		} else {
			log_error("fcn-logs.pl", "minilog_remove_outdated", "Could not open new minilog for writing [$topic]: $!");
			close (FILE);
			return undef;
		}
	} else {
		log_error("fcn-logs.pl", "minilog_remove_outdated", "Could not open old minilog for reading [$topic]: $!");
		return undef;
	}
	rename_file("$DCONF->{admin_dir}/msg_index/$topic-log-$threshold.NEW", $fsize, "$DCONF->{admin_dir}/msg_index/$topic-log-$threshold.txt");
}

###
### create_minilog
###
### Creates a "minilog" - portion of a posting log of messages posted after a given
### time, so searching goes much faster.
###

sub create_minilog {
	my ($topic) = @_;
	return undef if ! -e "$DCONF->{admin_dir}/msg_index/$topic-log.txt";
	my @ml = sort { $a <=> $b } @mini_logs;
	my $max = pop @ml;
	my $time = time;
	my @d = ();
	if (open(FILE, "< $DCONF->{admin_dir}/msg_index/$topic-log.txt")) {
		my $t = $ml[$#ml];
		open (OUT, "> $DCONF->{admin_dir}/msg_index/$topic-log-$max.txt");
		while (<FILE>) {
			my $h = log_line_to_hash($_);
			next if $h->{'time'} < $time-(24*60*60*$max);
			print OUT $_;
			next if $h->{'time'} < $time-(24*60*60*$t);
			push @d, $h;
		}
		close (OUT);
		close (FILE);
		chmod (oct($DCONF->{perms0666}), "$DCONF->{admin_dir}/msg_index/$topic-log-$max.txt");
	}
	while (my $x = pop @ml) {
		my @k = ();
		my $t = $ml[$#ml];
		open (OUT, "> $DCONF->{admin_dir}/msg_index/$topic-log-$x.txt");
		foreach my $d (@d) {
			print OUT log_hash_to_line($d);
			next if $d->{'time'} < $time-(24*60*60*$t);
			push @k, $d;
		}
		close (OUT);
		chmod (oct($DCONF->{perms0666}), "$DCONF->{admin_dir}/msg_index/$topic-log-$x.txt");
		@d = @k;
	}
	return 1;
}

###
### search_line_to_hash
###
### Converts a line to a hash from ###-search.txt.  Note that
### search_line_to_hash(search_hash_to_line($ref)) = $ref
###

sub search_line_to_hash {
	my ($line) = @_;
	chomp $line;
	my $d = {};
	my @s = ('postindex', 'time', 'text');
	my @l = split(/\s+/, $line, 3);
	while (my $p = shift(@s)) {
		$d->{$p} = shift @l;
	}
	return $d;
}

###
### search_hash_to_line
###
### Converts a hash to a line for writing into ###-search.txt.  Note that
### search_hash_to_line(search_line_to_hash($str)) = $str
###

sub search_hash_to_line {
	my ($hash) = @_;
	my $l = join(" ", $hash->{postindex}, $hash->{'time'}, $hash->{text});
	$l .= "\n";
	return $l;
}

###
### log_line_to_hash
###
### Converts a line to a hash from ###-log.txt.  Note that
### log_line_to_hash(log_hash_to_line($ref)) = $ref
###

sub log_line_to_hash {
	my ($line) = @_;
	chomp $line;
	my $d = {};
	my @s = ('postindex', 'username', 'time', 'where', 'remote_addr', 'remote_host', 'firstchars', 'poststr');
	my @l = split(/;/, $line);
	while (my $p = shift(@s)) {
		$d->{$p} = shift @l;
	}
	($d->{topic}, $d->{page}) = split(/\//, $d->{where});
	return $d;
}

###
### log_hash_to_line
###
### Converts a hash to a line for writing into ###-log.txt.  Note that
### log_hash_to_line(log_line_to_hash($str)) = $str
###

sub log_hash_to_line {
	my ($hash) = @_;
	$hash->{'time'} = ( $hash->{'time'} > 0 ? $hash->{'time'} : time );
	$hash->{'remote_addr'} = ( defined $hash->{'remote_addr'} ? $hash->{'remote_addr'} : $ENV{'REMOTE_ADDR'});
	$hash->{'remote_host'} = ( defined $hash->{'remote_host'} ? $hash->{'remote_host'} : $ENV{'REMOTE_HOST'});
	$hash->{'where'} = ( defined $hash->{'where'} ? $hash->{'where'} : "$hash->{topic}/$hash->{page}");
	my $l = join(";", $hash->{postindex}, $hash->{username}, $hash->{'time'}, $hash->{where}, $hash->{remote_addr}, $hash->{remote_host}, $hash->{firstchars}, $hash->{poststr});
	$l .= "\n";
	return $l;
}

###
### log_read_file
###
### Reads from the log file.  Some searching is built in too.
###		bottom_cutoff		Bottom cutoff time
###		top_cutoff			Top cutoff time
###		poster_match		Require matching a poster's name
###		postindex			Return a single record identified by 'postindex'
###

sub log_read_file {
	my ($topics, $param, $searches) = @_;
	my $tpc = {};
	if (ref $topics ne "HASH") {
		foreach (split(/,/, $topics)) {
			$tpc->{$_} = 1;
		}
	} else {
		$tpc = $topics;
	}
	my %pi = {};
	if (defined $searches->{postindex}) {
		if (ref $searches->{postindex} eq "ARRAY") {
			foreach my $j (@{ $searches->{postindex} }) { $pi{$j} = 1; }
		} elsif (ref $searches->{postindex} eq "HASH") {
			foreach my $j (keys(%{ $searches->{postindex} })) { $pi{$j} = 1; }
		} else {
			foreach my $j (split(/,/, $searches->{postindex})) { $pi{$j} = 1; }
		}		
	} else {
		$pi{'*'} = 1;
	}	
	if ($GLOBAL_OPTIONS->{database} && $DCONF->{pro}) {
		dreq("sql-logs-PRO");
		return sql_logs_read_log_file($param, $searches, $tpc);
	} else {
		my $ml_ext = ".txt";
		if (ref $searches eq 'HASH' && $searches->{bottom_cutoff} > 0) {
			my $ml = minilog_choose_log($searches->{bottom_cutoff});
			$ml_ext = "-$ml.txt" if defined $ml;
		}
		my @r = ();
		my $req_re = [];
		my $neg_re = "";
		my $opt_re = "";
		if ($searches->{poster_match} ne "") {
			my ($req, $neg, $opt) = search_create_query_conditions($searches->{query_info}, undef, 1);
			$req_re = $req;
			$neg_re = $neg;
			$opt_re = $opt;
		}
		foreach my $t (keys(%{ $tpc })) {
			my $file = join("/", $DCONF->{admin_dir}, "msg_index", "$t-log$ml_ext");
			$PARAMS->{files_read}++;
			$PARAMS->{file_access}->{$file}->{read} += 1;
			$PARAMS->{file_access}->{$file}->{write} += 0;
			performance_string("< log_read_file $file") if $GLOBAL_OPTIONS->{performance_monitoring};
			open (LOG, "< $file");
W:			while (<LOG>) {
				my $h = log_line_to_hash($_);
				next W if defined $searches->{id} && $h->{username} ne $searches->{id};
				if ($searches->{postindex} && $pi{$h->{postindex}}) {
					push @r, $h;
					delete $pi{$h->{postindex}};
					if (scalar(keys(%pi)) == 0) {
						close (LOG);
						return $h if $searches->{postindex} =~ /^\d+/;
						return \@r;
					}
				}
				next W if defined $searches->{postindex};
				my $poststr = unescape($h->{poststr});
				next W if (defined $searches->{bottom_cutoff} && $h->{'time'} < $searches->{bottom_cutoff});
				next W if (defined $searches->{top_cutoff} && $h->{'time'} > $searches->{top_cutoff});
				next W if ($neg_re ne "" && $poststr =~ /$neg_re/);
				if (ref $req_re eq "ARRAY" && scalar(@{ $req_re })) {
					foreach my $re (@{ $req_re }) {
						next W if $poststr !~ /$re/;						
					}	
				}
				next W if ($opt_re ne "" && $poststr !~ /$opt_re/);
				$h->{topic} = $t;
				push @r, $h;
				if ($param->{no_cutoff} == 0 && $GLOBAL_OPTIONS->{tree_search_absolute_cutoff_point} > 0 && scalar(@r) > $GLOBAL_OPTIONS->{tree_search_absolute_cutoff_point}) {
					close(LOG);
					return \@r;
				}
			}
			close (LOG);
		}
		return \@r;
	}
}

###
### search_create_query_conditions
###
### Creates a full search query
###

sub search_create_query_conditions {
	my ($o, $flag, $no_stop) = @_;
	dreq("search");
	my @r = search_prepare_query($o, undef, $no_stop);
	my @req = @{ $r[0] };
	my @neg = @{ $r[1] };
	my @opt = @{ $r[2] };
	return (\@req, \@neg, \@opt) if $flag == 303;
	my $opt_re = join("|", @opt);
	my $neg_re = join("|", @neg);
	$opt_re = join("", "(", $opt_re, ")") if $opt_re ne "";
	$neg_re = join("", "(", $neg_re, ")") if $neg_re ne "";
	$opt_re = join("", "(?i)", $opt_re) if (! $o->{search_opt}->{sensitive} && $opt_re);
	$neg_re = join("", "(?i)", $neg_re) if (! $o->{search_opt}->{sensitive} && $neg_re);
	return (\@req, $neg_re, $opt_re);
}

###
### log_search_update_entry
###
### Updates a log or search entry within the same topic
###

sub log_search_update_entry {
	my ($topic, $type, $updates) = @_;
	if ($DCONF->{pro} && $GLOBAL_OPTIONS->{database}) {
		dreq("sql-logs-PRO");
		return sql_log_search_update_entry($topic, $type, $updates);
	}
	my %miniseen = {};
	my $timecache = time;
	my @file = ("$DCONF->{admin_dir}/msg_index/$topic-$type.txt");
	while (my $filename = shift @file) {
		&lock("log_search_update_entry", $filename);
		open (FILE1, "< $filename");
		open (FILE2, "> $filename.new");
		my $bytectr = 0;
		my $ctr = 1;
		my %seen = {};
		foreach my $u (@{ $updates }) {
			$seen{$u->{postindex}} = $ctr; $ctr++;
		}
F:		while (<FILE1>) {
			my $hash = $type eq "search" ? search_line_to_hash($_) : log_line_to_hash($_);
			if (! $seen{ $hash->{postindex} }) {
				print FILE2 $_;
				$bytectr += length($_);
				next F;
			} else {
				my $x = $updates->[ $seen{ $hash->{postindex} } -1 ];
				foreach my $k (keys(%{ $x })) {
					$hash->{$k} = $x->{$k};
				}
				my $line = $type eq "search" ? search_hash_to_line($hash) : log_hash_to_line($hash);
				print FILE2 $line;
				delete $seen{ $hash->{postindex} };
				$bytectr += length($line);
				if ($type eq "log") {
M:					foreach my $m (@mini_logs) {
						next M if $miniseen{$m};
						if ($timecache - 24*60*60*$m <= $hash->{'time'}) {
							push @file, "$DCONF->{admin_dir}/msg_index/$topic-$type-$m.txt";
							$miniseen{$m} = 1;
						}
					}
				}					
				if (scalar(keys(%seen)) == 0) {
					while (<FILE1>) {
						$bytectr += length($_);
						print FILE2;
					}
					last F;
				}
			}
		}
		close (FILE1);
		close (FILE2);
		rename_file("$filename.new", $bytectr, "$filename");
		unlock("log_search_update_entry", $filename);
	}
}

###
### search_push_entry
###
### Adds one or more entries to a ###-search.txt file (or database)
###

sub search_push_entry {
	my ($topic, $data, $params) = @_;
	my @d = ref $data eq "ARRAY" ? @{ $data } : ( $data );
	if ($GLOBAL_OPTIONS->{database} && $DCONF->{pro}) {
		dreq("sql-logs-PRO", "dbint");
		return sql_logs_search_push_entry(\@d, $topic, $params);
	} else {
		my @q = ();
		foreach my $hash (@d) {
			push (@q, search_hash_to_line($hash));
		}
		appendfile("$DCONF->{admin_dir}/msg_index/$topic-search.txt", \@q, "search_push_entry", undef);
	}
}

###
### log_push_entry
###
### Adds one or more entries to a ###-log.txt file (or database)
###

sub log_push_entry {
	my ($topic, $data, $params) = @_;
	my @d = ref $data eq "ARRAY" ? @{ $data } : ( $data );
	if ($GLOBAL_OPTIONS->{database} && $DCONF->{pro}) {
		dreq("sql-logs-PRO", "dbint");
		return sql_logs_log_push_entry(\@d, $topic, $params);
	} else {
		my @q = ();
		foreach my $hash (@d) {
			push (@q, log_hash_to_line($hash));
		}
		appendfile("$DCONF->{admin_dir}/msg_index/$topic-log.txt", \@q, "log_push_entry", undef);
		append_minilogs(\@q, $topic);
	}
}

###
### append_minilogs
###
### Writes out new information in posting mini-logs
###

sub append_minilogs {
	my ($data, $topic) = @_;
	my $flag = 0;
	foreach my $mini_log (@mini_logs) {
		return undef if ! -e "$DCONF->{admin_dir}/msg_index/$topic-log-$mini_log.txt";
	}	
	foreach my $mini_log (@mini_logs) {
		appendfile("$DCONF->{admin_dir}/msg_index/$topic-log-$mini_log.txt", $data, "append_minilogs", undef);
	}	
}

###
### search_stop_word_file
###
### Returns the list of stop words from the stop words file
### (or from the cache if this file has already been opened)
###

sub search_stop_word_file {
	return $PARAMS->{'stop_words'} if defined $PARAMS->{'stop_words'};
	my $sw = readfile("$DCONF->{admin_dir}/stopwords.conf", "search_stop_word_file", { no_lock => 1, no_unlock => 1, zero_ok => 1 } );
	foreach my $x (@{$sw}) {
		next if $x =~ m|^\s*#|;
		next if $x !~ m|\S|;
		$x =~ s/\s//g;
		$x = case_lower($x);
		$PARAMS->{'stop_words'}->{$x} = 1;
	}
	return $PARAMS->{'stop_words'};
}

###
### search_stop
###
### Removes words in "stopwords.conf" from posts for writing into search index files
###

sub search_stop {
	my ($input, $flag) = @_;
	my $y = remove_html($input, 1);
	return $y if $GLOBAL_OPTIONS->{'search_stopwords'} eq "0";
	my $l = search_stop_word_file();
	my @q = ();
	foreach my $k (keys(%{ $l })) { push @q, quotemeta($k); }
	my $x = join("|", @q);
	$y =~ s/(^|\b)($x)(\b|$)//gio;
	$y =~ s/\+\+\+ (\w+) \+\+\+ (\d+) \+\+\+ (.*?) \+\+\+/\[$3\]/g;
	return trim($y);
}

###
### delete_entry
###
### Removes things from the search index and the posting logs
###

sub delete_entry {
	my ($arg) = @_;
	if ($GLOBAL_OPTIONS->{database} && $DCONF->{pro}) {
		dreq("sql-logs-PRO");
		return sql_logs_delete_entry($arg);
	} else {
		my @f = ();
		my $tcache = time;
		foreach my $m (@mini_logs) {
			next if $arg->{time} > ($tcache - $m*24*60*60);
			push @f, "$DCONF->{admin_dir}/msg_index/$arg->{topic}-log-$m.txt";
		}
		push @f, "$DCONF->{admin_dir}/msg_index/$arg->{topic}-log.txt";
		foreach my $file (@f) {
			lock($file) if ! $arg->{files_already_locked};
			my $fs = 0;
			if (open (FILE, "< $file")) {
				if (open (FILE2, "> $file.new")) {
					while (<FILE>) {
						my ($msgnum) = split(/;/, $_, 2);
						if (! defined $arg->{messages}->{$msgnum}) {
							print FILE2 $_; $fs += length($_);
						}
					}
					close (FILE2);
				}
				close (FILE);
				rename_file("$file.new", $fs, "$file");
			}
			unlock($file) if ! $arg->{files_already_locked};
		}
		my $fs = 0;
		lock("$DCONF->{admin_dir}/msg_index/$arg->{topic}-search.txt") if ! $arg->{files_already_locked};
		if (open (FILE, "< $DCONF->{admin_dir}/msg_index/$arg->{topic}-search.txt")) {
			if (open (FILE2, "> $DCONF->{admin_dir}/msg_index/$arg->{topic}-search.new")) {
				while (<FILE>) {
					my ($msgnum) = split(/\s+/, $_, 2);
					if (! defined $arg->{messages}->{$msgnum}) {
						print FILE2 $_; $fs += length($_);
					}
				}
				close (FILE2);
			}
			close (FILE);
			rename_file("$DCONF->{admin_dir}/msg_index/$arg->{topic}-search.new", $fs, "$DCONF->{admin_dir}/msg_index/$arg->{topic}-search.txt");
		}
		unlock("$DCONF->{admin_dir}/msg_index/$arg->{topic}-search.txt") if ! $arg->{files_already_locked};
	}
}

###
### update_entry_post_topic
###
### Another function to move posts from one topic to another
###

sub update_entry_post_topic {
	my ($moved, $old, $new) = @_;
	my @m = @{$moved};
	foreach my $m (@m) {
		$m->{old_topic} = $old;
		$m->{new_topic} = $new;
		$m->{postindex} = $m->{number};
	}
	update_entry_post_location(\@m);
}

###
### update_entry_post_location
###
### Allows moving of messages from one page to another, whether or not in
### the same topic.
###

sub update_entry_post_location {
	my ($info) = @_;
	return undef if ref $info ne "ARRAY";
	if ($GLOBAL_OPTIONS->{database} && $DCONF->{pro}) {
		dreq("sql-logs-PRO");
		return sql_logs_update_entry_post_location($info);
	}
	my @posts = @{ $info }; 
	my $timecache = time;
	my $cuts = {};
	my $posthash = {};
	foreach my $post (@posts) {
		my $x = minilog_choose_log($post->{'time'}, $timecache);
		$cuts->{$x} = 1 if defined $x;
		$posthash->{$post->{postindex}} = $post;
	}
	my $min_mini = (sort { $a <=> $b } keys(%{ $cuts }))[0];
	foreach my $i (@mini_logs) {
		$cuts->{$i} = 1 if $i > $min_mini;
	}
	my $p = $posts[0];
	my @source_files = ("$DCONF->{admin_dir}/msg_index/$p->{old_topic}-log.txt");
	foreach my $minilog (keys(%{ $cuts })) {
		push @source_files, "$DCONF->{admin_dir}/msg_index/$p->{old_topic}-log-$minilog.txt";
	}
	push @source_files, "$DCONF->{admin_dir}/msg_index/$p->{old_topic}-search.txt";
	my @dest_files = ("$DCONF->{admin_dir}/msg_index/$p->{new_topic}-log.txt");
	foreach my $minilog (keys(%{ $cuts })) {
		push @dest_files, "$DCONF->{admin_dir}/msg_index/$p->{new_topic}-log-$minilog.txt";
	}
	push @dest_files, "$DCONF->{admin_dir}/msg_index/$p->{new_topic}-search.txt";
	foreach my $SRCFILE (@source_files) {
		&lock("update_entry_post_location", $SRCFILE);
		my $DSTFILE = shift @dest_files;
		open (SRCFILE, "< $SRCFILE");
		open (DSTFILE, "> $DSTFILE.NEW");
		my $exp_len = 0;
		if ($SRCFILE =~ m|\-search\.txt$|) {
			if ($DSTFILE eq $SRCFILE) {
				while (my $line = <SRCFILE>) {
					my $h = search_line_to_hash($line);
					if ($posthash->{$h->{postindex}}->{new_page}) {
						$h->{page} = $posthash->{$h->{postindex}}->{new_page};
					}
					my $ln = search_hash_to_line($h);
					print DSTFILE $ln;
					$exp_len += length($ln);
				}
				close (DSTFILE);
				close (SRCFILE);
				rename_file("$DSTFILE.NEW", $exp_len, "$DSTFILE");
			} else {
				my @newhash = ();
				while (my $line = <SRCFILE>) {
					my $h = search_line_to_hash($line);
					if ($posthash->{$h->{postindex}}) {
						$h->{page} = $posthash->{$h->{postindex}}->{new_page};
						$h->{topic} = $posthash->{$h->{postindex}}->{new_topic};
						my $ln = search_hash_to_line($h);
						push @newhash, $ln;
					} else {
						print DSTFILE $line;
						$exp_len += length($line);						
					}
				}
				close (DSTFILE);
				close (SRCFILE);
				&lock("update_entry_post_location", $DSTFILE);
				open (DSTFILE, ">> $DSTFILE");
				print DSTFILE @newhash;
				close (DSTFILE);
				unlock("update_entry_post_location", $DSTFILE);
				rename_file("$DSTFILE.NEW", $exp_len, "$SRCFILE");
			}
		} else {
			if ($DSTFILE eq $SRCFILE) {
				while (my $line = <SRCFILE>) {
					my $h = log_line_to_hash($line);
					$h->{where} = join("/", $posthash->{$h->{postindex}}->{new_topic}, $posthash->{$h->{postindex}}->{new_page}) if $posthash->{$h->{postindex}}->{new_page};
					my $ln = log_hash_to_line($h);
					print DSTFILE $ln;
					$exp_len += length($ln);
				}
				close (DSTFILE);
				close (SRCFILE);
				rename_file("$DSTFILE.NEW", $exp_len, "$DSTFILE");
			} else {
				my @newhash = ();
				while (my $line = <SRCFILE>) {
					my $h = log_line_to_hash($line);
					if ($posthash->{$h->{postindex}}) {
						$h->{where} = join("/", $posthash->{$h->{postindex}}->{new_topic}, $posthash->{$h->{postindex}}->{new_page});
						my $ln = log_hash_to_line($h);
						push @newhash, $line;
					} else {
						print DSTFILE $line;
						$exp_len += length($line);						
					}
				}
				close (DSTFILE);
				close (SRCFILE);
				&lock("update_entry_post_location", $DSTFILE);
				open (DSTFILE, ">> $DSTFILE");
				print DSTFILE @newhash;
				close (DSTFILE);
				unlock("update_entry_post_location", $DSTFILE);
				rename_file("$DSTFILE.NEW", $exp_len, "$SRCFILE");
			}
		}
		unlock("update_entry_post_location", $SRCFILE);
	}
}

1;
