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