3 ################################################################################
5 # File: shared/Arc/Archive.pm #
7 # Authors: Andre Malo <nd@o3media.de>, 2001-06-16 #
9 # Description: Severance of Threads and archiving #
11 ################################################################################
23 create_forum_xml_string
32 use Time
::German
'localtime';
36 ################################################################################
40 $VERSION = do { my @r =(q
$Revision$ =~ /\d+/g); sprintf "%d."."%02d" x
$#r, @r };
42 ################################################################################
46 use base
qw(Exporter);
47 @EXPORT = qw(cut_tail);
49 ### delete_no_archived () ######################################################
51 # remove no archived branches vom thread
53 # Params: $xml - XML::DOM::Document node
54 # $msg - arrayref - messages
55 # $percent - voting limit (percent)
59 sub delete_no_archived
($) {
62 my ($xml, $sum, $tid, $msg, $percent) = map {$par->{$_}}
63 qw( xml sum tid msg percent);
65 # $oldlevel: contains the level of last checked msg
66 # @path : contains the current branch
67 # %archive : contains the mids, that will be archived
68 # %hidden : contains the invisible mids
70 my ($oldlevel, @path, %archive, %hidden) = (0, 0);
72 # check all messages of thread
74 for my $z (0..$#{$msg}) {
76 if ($msg -> [$z] -> {level
} > $oldlevel) {
77 # this msg is a child of the last one
80 $oldlevel = $msg -> [$z] -> {level
};
83 elsif ($msg -> [$z] -> {level
} < $oldlevel) {
84 # this starts a new subbranch (-1+ level(s))
87 # remove last msg (incl. kids), that is on same level
89 splice @path, $msg -> [$z] -> {level
};
91 $oldlevel = $msg -> [$z] -> {level
};
95 # the msg is a sister of the last one
100 # 'archive' is an admin flag
101 # if set, the message (incl. branch) MUST be archived
103 if (defined $msg->[$z]->{archive
} and $msg->[$z]->{archive
}) {
104 $archive{$msg->[$_]->{mid
}} = 1 for (@path);
107 # notice invisble messages
108 # while they are in @path and archive flag is not set,
109 # they and their kids WON'T be archived
111 $hidden{$z} = 1 if ($msg->[$z]->{deleted
});
113 # if 'archive' is NOT set and message not deleted,
115 unless ($msg->[$z]->{archive
} or $msg->[$z]->{deleted
}) {
116 my $key = $sum->{$tid}->{$msg->[$z]->{mid
}};
118 # ...and they've voted enough, it will be archived
120 if ($percent == 0 or ($key->{views
} and ($key->{votings
} * 100 / $key->{views
}) >= $percent)) {
123 # check on hidden messages in @path
132 # set archive-flag for messages in @path,
133 # unless a parent message is hidden
135 unless ($hidden_in_path) {
136 $archive{$msg->[$_]->{mid
}} = 1 for (@path);
142 # now remove messages without 'archive'-flag
145 for (reverse grep {!$archive{$_->{mid
}}} @
$msg) {
146 my $h = get_message_node
($xml, "t$tid", 'm'.$_->{mid
});
148 # remove message entry
150 $h -> getParentNode
-> removeChild
($h);
152 # remove message text
154 $h = get_body_node
($xml, 'm'.$_->{mid
});
155 $h -> getParentNode
-> removeChild
($h);
159 ### create_arcdir () ###########################################################
161 # check, if specific directories for year and month exist, create
164 # Params: $path - archive root
165 # $time - Thread time (GMT)
167 # Return: List: $path - /path/to/ to archived thread file
168 # $error - error or undef
170 sub create_arcdir
($$) {
171 my ($path, $time) = @_;
173 my ($month, $year) = (localtime ($time))[4,5];
175 # use the 'real' values for directory names
177 $month++; $year+=1900;
179 my $yeardir = $path . $year;
180 my $monthdir = $yeardir . '/' . $month;
181 my $monthpath = $monthdir . '/';
183 mkdir $yeardir, 0777 unless (-d
$yeardir);
184 return ('', "could not create directory '$yeardir'") unless (-d
$yeardir);
186 mkdir $monthdir, 0777 unless (-d
$monthdir);
187 return ('', "could not create directory '$monthdir'") unless (-d
$monthdir);
189 # return path, successfully created
194 ### process_threads () #########################################################
196 # process obsolete threads
197 # (transmit views/votings from cache, do archive, if necessary)
199 # Params: $par - hash reference
200 # (opt, cache, failed, obsolete, messagePath,
201 # archivePath, adminDefault)
205 sub process_threads
($) {
208 my ($opt, $failed, $obsolete, $cache) = map {$par->{$_}} qw
209 ( opt failed obsolete cache
);
211 if ($opt->{exArchiving
}) {
215 my $sum = $cache -> summary
;
218 # iterate over all obsolete threads, that are not failed yet
220 for my $tid (grep {not exists ($failed->{$_})} @
$obsolete) {
221 my $xml = parse_xml_file
($par->{messagePath
}."t$tid.xml");
226 $failed->{$tid} = 'could not parse thread file.';
231 my $tnode = $xml -> getElementsByTagName
('Thread') -> item
(0);
232 my $msg = parse_single_thread
($tnode, KEEP_DELETED
);
234 if ($opt->{archiving
} eq 'UserVotings') {
236 # filter out the bad stuff
238 delete_no_archived
({
243 percent
=> $par->{adminDefault
}->{Voting
}->{Limit
}
247 # save back xml file (into archive)
249 if ($tnode -> hasChildNodes
) {
251 # insert views and votings counter
253 for ($tnode -> getElementsByTagName
('Message')) {
254 my ($id) = $_ -> getAttribute
('id') =~ /(\d+)/;
255 $_ -> setAttribute
('views' => $sum->{$tid}->{$id}->{views
});
256 $_ -> setAttribute
('votings' => $sum->{$tid}->{$id}->{votings
});
259 # create archive dir, unless exists
261 my ($path, $error) = create_arcdir
($par -> {archivePath
}, $msg->[0]->{time});
264 $failed->{$tid} = $error;
269 my $file = "${path}t$tid.xml";
270 save_file
($file => \
($xml -> toString
)) or $failed->{$tid} = "could not save '$file'";
277 @
$failed{@
$obsolete} = 'could not load summary';
282 ### cut_tail () ################################################################
284 # shorten the main file and archive, if necessary
286 # Params: $param - hash reference
287 # (forumFile, messagePath, archivePath, lockFile, adminDefault,
290 # Return: hash reference - empty if all right done
297 $param->{adminDefault
}->{Severance
}->{severance
} ne 'instant' or
298 $param->{adminDefault
}->{Instant
}->{execute
}
300 # run only one instance at the same time
301 # (exlusive lock on sev_lock)
303 my $sev = new Lock
($param->{lockFile
});
304 if ($sev -> lock(LH_EXCL
)) {
306 # lock and parse forum main file
308 my $forum = new Lock
($param->{forumFile
});
309 if ($forum -> lock (LH_EXCL
)) {
315 ) = get_all_threads
($forum->filename, KEEP_DELETED
);
317 # get obsolete threads...
319 my $obsolete = get_obsolete_threads
({
320 parsedThreads
=> $threads,
321 adminDefault
=> $param->{adminDefault
}
324 unless (@
$obsolete) {
325 # nothing to cut - we're ready
330 # ...and delete them from main
334 $obsolete{$_} = $threads->{$_};
335 delete $threads->{$_};
340 my $saved = save_file
(
341 $param -> {forumFile
},
342 create_forum_xml_string
(
346 lastMessage
=> $last_message,
347 lastThread
=> $last_thread
352 # ...and masterlock the obsolete thread files
356 new Lock
($param->{messagePath
}."t$_.xml")->lock(LH_MASTER
) or $failed{$_} = 'could not set master lock';
360 # release forum main file...
365 # ...and now process thread files
367 my $sev_opt = ($param -> {adminDefault
} -> {Severance
} -> {severance
} eq 'instant')
368 ?
$param -> {adminDefault
} -> {Instant
} -> {Severance
}
369 : ($param -> {adminDefault
} -> {Severance
});
371 my $cache = new Posting
::Cache
($param->{cachePath
});
377 obsolete
=> $obsolete,
378 messagePath
=> $param->{messagePath
},
379 archivePath
=> $param->{archivePath
},
380 adminDefault
=> $param->{adminDefault
}
384 # delete processed files, that are not failed
386 for (grep {not exists($failed{$_})} @
$obsolete) {
387 unlink ($param->{messagePath
}."t$_.xml") or $failed{$_} = 'could not delete thread file';
388 #file_removed ($param->{messagePath}."t$_.xml");
390 $cache -> delete_threads
(@
$obsolete);
391 $cache -> garbage_collection
;
394 # we're ready, tell this other instances
405 # keep 'require' happy
410 ### end of Arc::Archive ########################################################