date>=$a->date); } /** Class spoolhead * class used in thread overviews */ class BananaSpoolHead { /** date (timestamp) */ var $date; /** subject */ var $subject; /** author */ var $from; /** reference of parent */ var $parent; /** paren is direct */ var $parent_direct; /** array of children */ var $children = Array(); /** true if post is read */ var $isread; /** number of posts deeper in this branch of tree */ var $desc; /** same as desc, but counts only unread posts */ var $descunread; /** constructor * @param $_date INTEGER timestamp of post * @param $_subject STRING subject of post * @param $_from STRING author of post * @param $_desc INTEGER desc value (1 for a new post) * @param $_read BOOLEAN true if read * @param $_descunread INTEGER descunread value (0 for a new post) */ function BananaSpoolHead($_date, $_subject, $_from, $_desc=1, $_read=true, $_descunread=0) { $this->date = $_date; $this->subject = $_subject; $this->from = $_from; $this->desc = $_desc; $this->isread = $_read; $this->descunread = $_descunread; } } /** Class spool * builds and updates spool */ define("BANANA_SPOOL_VERSION", '0.2'); class BananaSpool { var $version; /** spool */ var $overview; /** group name */ var $group; /** array msgid => msgnum */ var $ids; /** thread starts */ var $roots; /** constructor * @param $_nntp RESOURCE NNTP socket filehandle * @param $_group STRING group name * @param $_display INTEGER 1 => all posts, 2 => only threads with new posts * @param $_since INTEGER time stamp (used for read/unread) */ function BananaSpool(&$_nntp, $_group, $_display=0, $_since="") { global $news; $this->group = $_group; $groupinfo = $_nntp->group($_group); if (!$groupinfo) { return ($this = null); } $this->_readFromFile(); $do_save = false; $first = max($groupinfo[2]-$news['maxspool'], $groupinfo[1]); $last = $groupinfo[2]; if ($this->version == BANANA_SPOOL_VERSION) { for ($id = min(array_keys($this->overview)); $id<$first; $id++) { $this->delid($id, false); $do_save = true; } $first = max(array_keys($this->overview))+1; } else { unset($this->overview, $this->ids); $this->version = BANANA_SPOOL_VERSION; } if ($first<$last && $groupinfo[0]) { $do_save = true; $this->_updateSpool($_nntp, "$first-$last"); } if ($do_save) { $this->_saveToFile(); } $this->_updateUnread($_nntp, $_since, $_display); } function _readFromFile() { $file = dirname(dirname(__FILE__))."/spool/spool-{$this->group}.dat"; if (file_exists($file)) { $this = unserialize(file_get_contents($file)); } } function _saveToFile() { $file = dirname(dirname(__FILE__))."/spool/spool-{$this->group}.dat"; uasort($this->overview, "spoolcompare"); $this->roots = Array(); foreach($this->overview as $id=>$msg) { if (is_null($msg->parent)) { $this->roots[] = $id; } } file_put_contents($file, serialize($this)); } function _updateSpool(&$_nntp, $arg) { $dates = array_map(strtotime, $_nntp->xhdr("Date", $arg)); $subjects = array_map(headerdecode, $_nntp->xhdr("Subject", $arg)); $froms = array_map(headerdecode, $_nntp->xhdr("From", $arg)); $msgids = $_nntp->xhdr("Message-ID", $arg); $refs = $_nntp->xhdr("References", $arg); if (is_array($this->ids)) { $this->ids = array_merge($this->ids, array_flip($msgids)); } else { $this->ids = array_flip($msgids); } foreach ($msgids as $id=>$msgid) { $msg = new BananaSpoolHead($dates[$id], $subjects[$id], $froms[$id]); $refs[$id] = str_replace('><', '> <', $refs[$id]); $msgrefs = preg_split("/[ \t]/", strtr($refs[$id], $this->ids)); $parents = preg_grep('/^\d+$/', $msgrefs); $msg->parent = array_pop($parents); $msg->parent_direct = preg_match('/^\d+$/', array_pop($msgrefs)); if (isset($this->overview[$id])) { $msg->desc = $this->overview[$id]->desc; $msg->children = $this->overview[$id]->children; } $this->overview[$id] = $msg; if ($p = $msg->parent) { if (empty($this->overview[$p])) { $this->overview[$p] = new BananaSpoolHead($dates[$p], $subjects[$p], $froms[$p], 1); } $this->overview[$p]->children[] = $id; while ($p) { $this->overview[$p]->desc += $msg->desc; $p = $this->overview[$p]->parent; } } } } function _updateUnread(&$nntp, $since, $mode) { if (empty($since)) { return; } if (is_array($newpostsids = $nntp->newnews($since, $this->group))) { $newpostsids = array_intersect($newpostsids, array_keys($this->ids)); foreach ($newpostsids as $mid) { $this->overview[$this->ids[$mid]]->isread = false; $this->overview[$this->ids[$mid]]->descunread = 1; $parentmid = $this->ids[$mid]; while (isset($parentmid)) { $this->overview[$parentmid]->descunread ++; $parentmid = $this->overview[$parentmid]->parent; } } if (count($newpostsids)) { switch ($mode) { case 1: foreach ($this->roots as $k=>$i) { if ($this->overview[$i]->descunread==0) { $this->killdesc($i); unset($this->roots[$k]); } } break; } } } } /** kill post and childrens * @param $_id MSGNUM of post */ function killdesc($_id) { if (sizeof($this->overview[$_id]->children)) { foreach ($this->overview[$_id]->children as $c) { $this->killdesc($c); } } unset($this->overview[$_id]); if (($msgid = array_search($_id, $this->ids)) !== false) { unset($this->ids[$msgid]); } } /** delete a post from overview * @param $_id MSGNUM of post */ function delid($_id, $write=true) { if (isset($this->overview[$_id])) { if (sizeof($this->overview[$_id]->parent)) { $this->overview[$this->overview[$_id]->parent]->children = array_diff($this->overview[$this->overview[$_id]->parent]->children, array($_id)); if (sizeof($this->overview[$_id]->children)) { $this->overview[$this->overview[$_id]->parent]->children = array_merge($this->overview[$this->overview[$_id]->parent]->children, $this->overview[$_id]->children); foreach ($this->overview[$_id]->children as $c) { $this->overview[$c]->parent = $this->overview[$_id]->parent; $this->overview[$c]->parent_direct = false; } } $p = $this->overview[$_id]->parent; while ($p) { $this->overview[$p]->desc--; $p = $this->overview[$p]->parent; } } elseif (sizeof($this->overview[$_id]->children)) { foreach ($this->overview[$_id]->children as $c) { $this->overview[$c]->parent = null; } } unset($this->overview[$_id]); $msgid = array_search($_id, $this->ids); if ($msgid) { unset($this->ids[$msgid]); } if ($write) { $this->_saveToFile(); } } } /** displays children tree of a post * @param $_id INTEGER MSGNUM of post * @param $_index INTEGER linear number of post in the tree * @param $_first INTEGER linear number of first post displayed * @param $_last INTEGER linear number of last post displayed * @param $_ref STRING MSGNUM of current post * @param $_pfx_node STRING prefix used for current node * @param $_pfx_end STRING prefix used for children of current node * @param $_head BOOLEAN true if first post in thread */ function _disp_desc($_id, $_index, $_first=0, $_last=0, $_ref="", $_pfx_node="", $_pfx_end="", $_head=true) { global $css; $spfx_f = ''; $spfx_n = ''; $spfx_Tnd = ''; $spfx_Lnd = ''; $spfx_snd = ''; $spfx_T = ''; $spfx_L = ''; $spfx_s = ''; $spfx_e = ''; $spfx_I = ''; if ($_index + $this->overview[$_id]->desc < $_first || $_index > $_last) { return; } if ($_index>=$_first) { $us = ($_index == $_ref); $hc = empty($this->overview[$_id]->children); echo '