FileSpool.php 5.2KB

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