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

FileProvider   A

Complexity

Total Complexity 19

Size/Duplication

Total Lines 155
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 100%

Importance

Changes 4
Bugs 1 Features 2
Metric Value
wmc 19
c 4
b 1
f 2
lcom 1
cbo 5
dl 0
loc 155
ccs 84
cts 84
cp 1
rs 10

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
A getProvider() 0 4 1
A destroy() 0 7 1
A onMessageReceived() 0 6 1
A create() 0 9 2
A publish() 0 15 2
B receive() 0 38 6
A delete() 0 19 3
A cleanUp() 0 20 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
}