FileSpool.php 5.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. <?php
  2. /*
  3. * This file is part of SwiftMailer.
  4. * (c) 2009 Fabien Potencier <fabien.potencier@gmail.com>
  5. *
  6. * For the full copyright and license information, please view the LICENSE
  7. * file that was distributed with this source code.
  8. */
  9. /**
  10. * Stores Messages on the filesystem.
  11. * @package Swift
  12. * @author Fabien Potencier
  13. * @author Xavier De Cock <xdecock@gmail.com>
  14. */
  15. class Swift_FileSpool extends Swift_ConfigurableSpool
  16. {
  17. /** The spool directory */
  18. private $_path;
  19. /**
  20. * File WriteRetry Limit
  21. * @var int
  22. */
  23. private $_retryLimit=10;
  24. /**
  25. * Create a new FileSpool.
  26. * @param string $path
  27. * @throws Swift_IoException
  28. */
  29. public function __construct($path)
  30. {
  31. $this->_path = $path;
  32. if (!file_exists($this->_path)) {
  33. if (!mkdir($this->_path, 0777, true)) {
  34. throw new Swift_IoException('Unable to create Path ['.$this->_path.']');
  35. }
  36. }
  37. }
  38. /**
  39. * Tests if this Spool mechanism has started.
  40. *
  41. * @return boolean
  42. */
  43. public function isStarted()
  44. {
  45. return true;
  46. }
  47. /**
  48. * Starts this Spool mechanism.
  49. */
  50. public function start()
  51. {
  52. }
  53. /**
  54. * Stops this Spool mechanism.
  55. */
  56. public function stop()
  57. {
  58. }
  59. /**
  60. * Allow to manage the enqueuing retry limit.
  61. * Default, is ten and allows over 64^20 different fileNames
  62. *
  63. * @param integer $limit
  64. */
  65. public function setRetryLimit($limit)
  66. {
  67. $this->_retryLimit=$limit;
  68. }
  69. /**
  70. * Queues a message.
  71. * @param Swift_Mime_Message $message The message to store
  72. * @return boolean
  73. * @throws Swift_IoException
  74. */
  75. public function queueMessage(Swift_Mime_Message $message)
  76. {
  77. $ser = serialize($message);
  78. $fileName=$this->_path.'/'.$this->getRandomString(10);
  79. for ($i = 0; $i < $this->_retryLimit; ++$i) {
  80. /* We try an exclusive creation of the file. This is an atomic operation, it avoid locking mechanism */
  81. $fp = @fopen($fileName.'.message', 'x');
  82. if (false !== $fp) {
  83. if (false === fwrite($fp, $ser)) {
  84. return false;
  85. }
  86. return fclose($fp);
  87. } else {
  88. /* The file allready exists, we try a longer fileName */
  89. $fileName.=$this->getRandomString(1);
  90. }
  91. }
  92. throw new Swift_IoException('Unable to create a file for enqueuing Message');
  93. }
  94. /**
  95. * Execute a recovery if for anyreason a process is sending for too long
  96. *
  97. * @param int $timeout in second Defaults is for very slow smtp responses
  98. */
  99. public function recover($timeout=900)
  100. {
  101. foreach (new DirectoryIterator($this->_path) as $file) {
  102. $file = $file->getRealPath();
  103. if (substr($file, -16)=='.message.sending') {
  104. $lockedtime=filectime($file);
  105. if ((time()-$lockedtime)>$timeout) {
  106. rename($file, substr($file, 0, -8));
  107. }
  108. }
  109. }
  110. }
  111. /**
  112. * Sends messages using the given transport instance.
  113. *
  114. * @param Swift_Transport $transport A transport instance
  115. * @param string[] &$failedRecipients An array of failures by-reference
  116. *
  117. * @return int The number of sent emails
  118. */
  119. public function flushQueue(Swift_Transport $transport, &$failedRecipients = null)
  120. {
  121. if (!$transport->isStarted()) {
  122. $transport->start();
  123. }
  124. $failedRecipients = (array) $failedRecipients;
  125. $count = 0;
  126. $time = time();
  127. foreach (new DirectoryIterator($this->_path) as $file) {
  128. $file = $file->getRealPath();
  129. if (substr($file, -8) != '.message') {
  130. continue;
  131. }
  132. /* We try a rename, it's an atomic operation, and avoid locking the file */
  133. if (rename($file, $file.'.sending')) {
  134. $message = unserialize(file_get_contents($file.'.sending'));
  135. $count += $transport->send($message, $failedRecipients);
  136. unlink($file.'.sending');
  137. } else {
  138. /* This message has just been catched by another process */
  139. continue;
  140. }
  141. if ($this->getMessageLimit() && $count >= $this->getMessageLimit()) {
  142. break;
  143. }
  144. if ($this->getTimeLimit() && (time() - $time) >= $this->getTimeLimit()) {
  145. break;
  146. }
  147. }
  148. return $count;
  149. }
  150. /**
  151. * Returns a random string needed to generate a fileName for the queue.
  152. * @param int $count
  153. */
  154. protected function getRandomString($count)
  155. {
  156. // This string MUST stay FS safe, avoid special chars
  157. $base="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-.";
  158. $ret='';
  159. $strlen=strlen($base);
  160. for ($i=0; $i<$count; ++$i) {
  161. $ret.=$base[((int) rand(0,$strlen-1))];
  162. }
  163. return $ret;
  164. }
  165. }