FileSpool.php 4.7KB

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