FileSpool.php 5.5 KB

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