class BananaMBox implements BananaProtocoleInterface
{
- private $file = null;
- private $filesize = null;
- private $current_id = null;
- private $at_beginning = false;
- private $file_cache = null;
+ private $debug = false;
+ private $bt = array();
private $_lasterrno = 0;
private $_lasterror = null;
-
- private $count = null;
- private $new_messages = null;
- private $messages = null;
-
- /** Build a protocole handler plugged on the given box
- */
+
public function __construct()
{
- $filename = $this->getFileName(Banana::$group);
- if (is_null($filename)) {
- return;
- }
- $this->filesize = filesize($filename);
- $this->file = @fopen($filename, 'r');
- if (!$this->file) {
- $this->_lasterrno = 1;
- $this->_lasterror = _b_('Can\'t open file');
- $this->file = null;
- }
- $this->current_id = 0;
- $this->at_beginning = true;
- }
-
- /** Close the file
- */
- public function __destruct()
- {
- if ($this->file) {
- fclose($this->file);
- }
+ $this->debug = Banana::$debug_mbox;
}
-
- /** Indicate if the Protocole handler has been succesfully built
- */
+
public function isValid()
{
- return !Banana::$group || $this->file;
+ return true;
+ //!Banana::$group || $this->file;
}
- /** Indicate last error n°
+ /** Indicate last error n°
*/
public function lastErrNo()
{
return array(Banana::$group => array('desc' => '', 'msgnum' => 0, 'unread' => 0));
}
- /** Return a message
- * @param id Id of the emssage (can be either an Message-id or a message index)
- * @return A BananaMessage or null if the given id can't be retreived
- */
- public function &getMessage($id)
+ private function &getRawMessage($id)
{
$message = null;
if (!is_numeric($id)) {
- if (!Banana::$spool) {
+ if (!Banana::$spool) {
return $message;
}
$id = Banana::$spool->ids[$id];
}
- $message = $this->readMessages(array($id));
- if (empty($message)) {
- $message = null;
- return $message;
+ $options = array ('-m ' . $id);
+ $this->getMBoxPosition($options, $id);
+ return $this->callHelper('-b', $options);
+ }
+
+ /** Return a message
+ * @param id Id of the emssage (can be either an Message-id or a message index)
+ * @return A BananaMessage or null if the given id can't be retreived
+ */
+ public function &getMessage($id)
+ {
+ $messages =& $this->getRawMessage($id);
+ if ($messages) {
+ $messages = new BananaMessage($messages);
}
- return new BananaMessage($message[$id]['message']);
+ return $messages;
}
/** Return the sources of the given message
*/
public function getMessageSource($id)
- {
- $message = null;
- if (!is_numeric($id)) {
- if (!Banana::$spool) {
- return $message;
- }
- $id = Banana::$spool->ids[$id];
- }
- $message = $this->readMessages(array($id));
- return implode("\n", $message[$id]['message']);
+ {
+ $message =& $this->getRawMessage($id);
+ if ($message) {
+ $message = implode("\n", $message);
+ }
+ return $message;
}
/** Compute the number of messages of the box
*/
private function getCount()
{
- $this->count = count(Banana::$spool->overview);
- $max = @max(array_keys(Banana::$spool->overview));
- if ($max && Banana::$spool->overview[$max]->storage['next'] == $this->filesize) {
- $this->new_messages = 0;
- } else {
- $this->new_messages = $this->countMessages($this->count);
- $this->count += $this->new_messages;
- }
+ $options = array();
+ if (@filesize($this->getFileName()) == Banana::$spool->storage['size']) {
+ return max(Banana::$spool->ids);
+ }
+ $this->getMBoxPosition($options);
+ $val =& $this->callHelper('-c', $options);
+ if (!$val) {
+ return 0;
+ }
+ return intval(trim($val[0]));
}
/** Return the indexes of the messages presents in the Box
*/
public function getIndexes()
{
- if (is_null($this->count)) {
- $this->getCount();
- }
- return array($this->count, 0, $this->count - 1);
+ $count = $this->getCount();
+ return array($count, 0, $count - 1);
}
/** Return the message headers (in BananaMessage) for messages from firstid to lastid
*/
public function &getMessageHeaders($firstid, $lastid, array $msg_headers = array())
{
- $msg_headers = array_map('strtolower', $msg_headers);
- $messages =& $this->readMessages(range($firstid, $lastid), true);
- $msg_headers = array_map('strtolower', $msg_headers);
- $headers = array();
- foreach ($msg_headers as $header) {
- foreach ($messages as $id=>&$message) {
- if (!isset($headers[$id])) {
- $headers[$id] = array('beginning' => $message['beginning'], 'end' => $message['end']);
+ $headers = null;
+ $options = array();
+ $options[] = "-m $firstid:$lastid";
+ $this->getMboxPosition($options, $firstid);
+ $lines =& $this->callHelper('-d', $options, $msg_headers);
+ if (!$lines) {
+ return $headers;
+ }
+ $headers = array();
+ while ($lines) {
+ $id = array_shift($lines);
+ if ($id === '') {
+ continue;
+ }
+ $offset = array_shift($lines);
+ if ($offset === '') {
+ continue;
+ }
+ $id = intval($id);
+ $headers[$id] = array('beginning' => intval($offset));
+ while (true) {
+ $hname = array_shift($lines);
+ if ($hname === '') {
+ break;
}
- if ($header == 'date') {
- $headers[$id][$header] = @strtotime($message['message'][$header]);
+ $hval = array_shift($lines);
+ if ($hname == 'date') {
+ $headers[$id][$hname] = @strtotime($hval);
} else {
- $headers[$id][$header] = @$message['message'][$header];
+ $headers[$id][$hname] = $hval;
}
}
}
- unset($this->messages);
- unset($messages);
+ array_walk_recursive($headers, array('BananaMimePart', 'decodeHeader'));
return $headers;
}
foreach ($messages as $id=>&$data) {
if (isset(Banana::$spool->overview[$id])) {
Banana::$spool->overview[$id]->storage['offset'] = $data['beginning'];
- Banana::$spool->overview[$id]->storage['next'] = $data['end'];
}
}
+ Banana::$spool->storage['size'] = @filesize($this->getFileName());
}
/** Return the indexes of the new messages since the give date
*/
public function getNewIndexes($since)
{
+ $this->open();
+ if (is_null($this->file)) {
+ return array();
+ }
if (is_null($this->new_messages)) {
$this->getCount();
}
*/
public function send(BananaMessage &$message)
{
- return true;
+ $headers = $message->getHeaders();
+ $to = $headers['To'];
+ $subject = $headers['Subject'];
+ unset($headers['To']);
+ unset($headers['Subject']);
+ $hdrs = '';
+ foreach ($headers as $key=>$value) {
+ if (!empty($value)) {
+ $hdrs .= "$key: $value\r\n";
+ }
+ }
+ $body = $message->get(false);
+ return mail($to, $subject, $body, $hdrs);
}
/** Cancel a message
return $file . $mail;
}
+ /** Return the execution backtrace
+ */
+ public function backtrace()
+ {
+ if ($this->debug) {
+ return $this->bt;
+ } else {
+ return null;
+ }
+ }
+
#######
# Filesystem functions
#######
- protected function getFileName($box)
+ protected function getFileName()
{
- if (is_null($box)) {
+ if (is_null(Banana::$group)) {
return null;
}
- @list($mail, $domain) = explode('@', $box);
+ @list($mail, $domain) = explode('@', Banana::$group);
return Banana::$mbox_path . '/' . $mail;
}
#######
# MBox parser
#######
-
- /** Go to the given message
+
+ /** Add the '-p' optioin for callHelper
*/
- private function goTo($id)
+ private function getMBoxPosition(array &$options, $id = null)
{
- if ($this->current_id == $id && $this->at_beginning) {
- return true;
- }
- if ($id == 0) {
- fseek($this->file, 0);
- $this->current_id = 0;
- $this->at_beginning = true;
- return true;
- } elseif (isset(Banana::$spool->overview[$id]) || isset($this->messages[$id])) {
- if (isset(Banana::$spool->overview[$id])) {
- $pos = Banana::$spool->overview[$id]->storage['offset'];
- } else {
- $pos = $this->messages[$id]['beginning'];
- }
- if (fseek($this->file, $pos) == 0) {
- $this->current_id = $id;
- $this->at_beginning = true;
- return true;
+ if (Banana::$spool->overview) {
+ if (!is_null($id) && isset(Banana::$spool->overview[$id])) {
+ $key = $id;
} else {
- $this->current_id = null;
- $this->_lasterrno = 2;
- $this->_lasterror = _b_('Can\'t find message ') . $id;
- return false;
- }
- } else {
- $max = @max(array_keys(Banana::$spool->overview));
- if (is_null($max)) {
- $max = 0;
- }
- if ($id <= $max && $max != 0) {
- $this->current_id = null;
- $this->_lasterrno = 3;
- $this->_lasterror = _b_('Invalid message index ') . $id;
- return false;
- }
- if (!$this->goTo($max)) {
- return false;
- }
- if (feof($this->file)) {
- $this->current_id = null;
- $this->_lasterrno = 4;
- $this->_lasterror = _b_('Requested index does not exists or file has been truncated');
- return false;
- }
- while ($this->readCurrentMessage(true) && $this->current_id < $id);
- if ($this->current_id == $id) {
- return true;
- }
- $this->current_id = null;
- $this->_lasterrno = 5;
- $this->_lasterror = _b_('Requested index does not exists or file has been truncated');
- return false;
- }
- }
-
- private function countMessages($from = 0)
- {
- $this->messages =& $this->readMessages(array($from), true, true);
- return count($this->messages);
- }
-
- /** Read the current message (identified by current_id)
- * @param needFrom_ BOOLEAN is true if the first line *must* be a From_ line
- * @param alignNext BOOLEAN is true if the buffer must be aligned at the beginning of the next From_ line
- * @return message sources (without storage data)
- */
- private function &readCurrentMessage($stripBody = false, $needFrom_ = true, $alignNext = true)
- {
- $file_cache =& $this->file_cache;
- if ($file_cache && $file_cache != ftell($this->file)) {
- $file_cache = null;
- }
- $msg = array();
- $canFrom_ = false;
- $inBody = false;
- while(!feof($this->file)) {
- // Process file cache
- if ($file_cache) { // this is a From_ line
- $needFrom_ = false;
- $this->at_beginning = false;
- $file_cache = null;
- continue;
- }
-
- // Read a line
- $line = rtrim(fgets($this->file), "\r\n");
-
- // Process From_ line
- if ($needFrom_ || !$msg || $canFrom_) {
- if (substr($line, 0, 5) == 'From ') { // this is a From_ line
- if ($needFrom_) {
- $needFrom = false;
- } elseif (!$msg) {
- continue;
- } else {
- $this->current_id++; // we are finally in the next message
- if ($alignNext) { // align the file pointer at the beginning of the new message
- $this->at_beginning = true;
- $file_cache = ftell($this->file);
- }
- break;
- }
- } elseif ($needFrom_) {
- return $msg;
+ $key = max(Banana::$spool->ids);
+ if (!is_null($id) && $key >= $id) {
+ return;
}
}
-
- // Process non-From_ lines
- if (substr($line, 0, 6) == '>From ') { // remove inline From_ quotation
- $line = substr($line, 1);
- }
- if (!$stripBody || !$inBody) {
- $msg[] = $line; // add the line to the message source
- }
- $canFrom_ = empty($line); // check if next line can be a From_ line
- if ($canFrom_ && !$inBody && $stripBody) {
- $inBody = true;
+ if (isset(Banana::$spool->overview[$key]->storage['offset'])) {
+ $options[] = '-p ' . $key . ':' . Banana::$spool->overview[$key]->storage['offset'];
}
- $this->at_beginning = false;
}
- if (!feof($this->file) && !$canFrom_) {
- $msg = array();
- }
- return $msg;
}
- /** Read message with the given ids
- * @param ids ARRAY of ids to look for
- * @param strip BOOLEAN if true, only headers are retrieved
- * @param from BOOLEAN if true, process all messages from max(ids) to the end of the mbox
- * @return Array(Array('message' => message sources (or parsed message headers if $strip is true),
- * 'beginning' => offset of message beginning,
- * 'end' => offset of message end))
- */
- private function &readMessages(array $ids, $strip = false, $from = false)
+ private function &callHelper($action, array $options = array(), array $headers = array())
{
- if ($this->messages) {
- return $this->messages;
+ $action .= ' -f ' . $this->getFileName();
+ $cmd = Banana::$mbox_helper . " $action " . implode(' ', $options) . ' ' . implode(' ', $headers);
+ if ($this->debug) {
+ $start = microtime(true);
}
- sort($ids);
- $messages = array();
- while ((count($ids) || $from) && !feof($this->file)) {
- if (count($ids)) {
- $id = array_shift($ids);
- } else {
- $id++;
- }
- if ($id != $this->current_id || !$this->at_beginning) {
- if (!$this->goTo($id)) {
- continue;
- }
- }
- $beginning = ftell($this->file);
- $message =& $this->readCurrentMessage($strip, false);
- if ($strip) {
- $message =& BananaMimePart::parseHeaders($message);
- }
- $end = ftell($this->file);
- $messages[$id] = array('message' => $message, 'beginning' => $beginning, 'end' => $end);
+ exec($cmd, $out, $return);
+ if ($this->debug) {
+ $this->bt[] = array('action' => $cmd, 'time' => (microtime(true) - $start),
+ 'code' => $return, 'response' => count($out), 'error' => $return ? "Helper failed" : null);
+ }
+ if ($return != 0) {
+ $this->_lasterrorno = 1;
+ $this->_lasterrorcode = "Helper failed";
+ $out = null;
}
- return $messages;
+ return $out;
}
}
-// vim:set et sw=4 sts=4 ts=4:
+// vim:set et sw=4 sts=4 ts=4 enc=utf-8:
?>