Completed
Push — master ( 9b6362...d575cd )
by Keith
03:01
created

FileProvider::create()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 3
Bugs 0 Features 2
Metric Value
c 3
b 0
f 2
dl 0
loc 9
ccs 5
cts 5
cp 1
rs 9.6667
cc 2
eloc 6
nc 2
nop 0
crap 2
1
<?php
2
namespace Uecode\Bundle\QPushBundle\Provider;
3
4
use Doctrine\Common\Cache\Cache;
5
use Symfony\Bridge\Monolog\Logger;
6
use Symfony\Component\Filesystem\Filesystem;
7
use Symfony\Component\Finder\Finder;
8
use Symfony\Component\Finder\SplFileInfo;
9
use Uecode\Bundle\QPushBundle\Event\MessageEvent;
10
use Uecode\Bundle\QPushBundle\Message\Message;
11
12
class FileProvider extends AbstractProvider
13
{
14
    protected $filePointerList = [];
15
    protected $queuePath;
16
17 9
    public function __construct($name, array $options, $client, Cache $cache, Logger $logger) {
18 9
        $this->name     = $name;
19
        /* md5 only contain numeric and A to F, so it is file system safe */
20 9
        $this->queuePath = $options['path'].DIRECTORY_SEPARATOR.str_replace('-', '', hash('md5', $name));
21 9
        $this->options  = $options;
22 9
        $this->cache    = $cache;
23 9
        $this->logger   = $logger;
24 9
    }
25
26 1
    public function getProvider()
27
    {
28 1
        return 'File';
29
    }
30
31 7
    public function create()
32
    {
33 7
        $fs = new Filesystem();
34 7
        if (!$fs->exists($this->queuePath)) {
35 7
            $fs->mkdir($this->queuePath);
36 7
            return $fs->exists($this->queuePath);
37
        }
38
        return true;
39
    }
40
41 4
    public function publish(array $message, array $options = [])
42
    {
43 4
        $fileName = microtime(false);
44 4
        $fileName = str_replace(' ', '', $fileName);
45 4
        $path = substr(hash('md5', $fileName), 0, 3);
46 4
        if (!is_dir($this->queuePath.DIRECTORY_SEPARATOR.$path)) {
47 4
            mkdir($this->queuePath.DIRECTORY_SEPARATOR.$path);
48
        }
49 4
        $fs = new Filesystem();
50 4
        $fs->dumpFile(
51 4
            $this->queuePath.DIRECTORY_SEPARATOR.$path.DIRECTORY_SEPARATOR.$fileName.'.json',
52
            json_encode($message)
53
        );
54 4
        return $fileName;
55
    }
56
57
    /**
58
     * @param array $options
59
     * @return Message[]
60
     */
61 6
    public function receive(array $options = [])
62
    {
63 6
        $finder = new Finder();
64
        $finder
65 6
            ->files()
66 6
            ->ignoreDotFiles(true)
67 6
            ->ignoreUnreadableDirs(true)
68 6
            ->ignoreVCS(true)
69 6
            ->name('*.json')
70 6
            ->in($this->queuePath)
71
        ;
72 6
        if ($this->options['message_delay'] > 0) {
73 1
            $finder->date(
74 1
                sprintf('< %d seconds ago', $this->options['message_delay'])
75
            );
76
        }
77
        $finder
78 6
            ->date(
79 6
                sprintf('> %d seconds ago', $this->options['message_expiration'])
80
            )
81
        ;
82 6
        $messages = [];
83
        /** @var SplFileInfo $file */
84 6
        foreach ($finder as $file) {
85 3
            $filePointer = fopen($file->getRealPath(), 'r+');
86 3
            $id = substr($file->getFilename(), 0, -5);
87 3
            if (!isset($this->filePointerList[$id]) && flock($filePointer, LOCK_EX | LOCK_NB)) {
88 3
                $this->filePointerList[$id] = $filePointer;
89 3
                $messages[] = new Message($id, json_decode($file->getContents(), true), []);
90
            } else {
91 1
                fclose($filePointer);
92
            }
93 3
            if (count($messages) === (int) $this->options['messages_to_receive']) {
94 3
                break;
95
            }
96
        }
97 6
        return $messages;
98
    }
99
100 2
    public function delete($id)
101
    {
102 2
        $success = false;
103 2
        if (isset($this->filePointerList[$id])) {
104 2
            $fileName = $id;
105 2
            $path = substr(hash('md5', (string)$fileName), 0, 3);
106 2
            $fs = new Filesystem();
107 2
            $fs->remove(
108 2
                $this->queuePath . DIRECTORY_SEPARATOR . $path . DIRECTORY_SEPARATOR . $fileName . '.json'
109
            );
110 2
            fclose($this->filePointerList[$id]);
111 2
            unset($this->filePointerList[$id]);
112 2
            $success = true;
113
        }
114 2
        if (rand(1,10) === 5) {
115
            $this->cleanUp();
116
        }
117 2
        return $success;
118
    }
119
120 1
    public function cleanUp()
121
    {
122 1
        $finder = new Finder();
123
        $finder
124 1
            ->files()
125 1
            ->in($this->queuePath)
126 1
            ->ignoreDotFiles(true)
127 1
            ->ignoreUnreadableDirs(true)
128 1
            ->ignoreVCS(true)
129 1
            ->depth('< 2')
130 1
            ->name('*.json')
131
        ;
132 1
        $finder->date(
133 1
            sprintf('> %d seconds ago', $this->options['message_expiration'])
134
        );
135
        /** @var SplFileInfo $file */
136 1
        foreach ($finder as $file) {
137
            @unlink($file->getRealPath());
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
138
        }
139 1
    }
140
141 1
    public function destroy()
142
    {
143 1
        $fs = new Filesystem();
144 1
        $fs->remove($this->queuePath);
145 1
        $this->filePointerList = [];
146 1
        return !is_dir($this->queuePath);
147
    }
148
149
    /**
150
     * Removes the message from queue after all other listeners have fired
151
     *
152
     * If an earlier listener has erred or stopped propagation, this method
153
     * will not fire and the Queued Message should become visible in queue again.
154
     *
155
     * Stops Event Propagation after removing the Message
156
     *
157
     * @param MessageEvent $event The SQS Message Event
158
     * @return bool|void
159
     */
160 1
    public function onMessageReceived(MessageEvent $event)
161
    {
162 1
        $id = $event->getMessage()->getId();
163 1
        $this->delete($id);
164 1
        $event->stopPropagation();
165
    }
166
}