Completed
Push — master ( 815ef9...a2c400 )
by Nikola
02:29
created

StreamDestination::count()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
/*
3
 * This file is part of the Backup package, an RunOpenCode project.
4
 *
5
 * (c) 2015 RunOpenCode
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 *
10
 * This project is fork of "kbond/php-backup", for full credits info, please
11
 * view CREDITS file that was distributed with this source code.
12
 */
13
namespace RunOpenCode\Backup\Destination;
14
15
use RunOpenCode\Backup\Backup\Backup;
16
use RunOpenCode\Backup\Backup\File;
17
use RunOpenCode\Backup\Contract\BackupInterface;
18
use RunOpenCode\Backup\Contract\DestinationInterface;
19
use RunOpenCode\Backup\Exception\DestinationException;
20
use Symfony\Component\Filesystem\Filesystem;
21
use Symfony\Component\Finder\Finder;
22
23
/**
24
 * Class StreamDestination
25
 *
26
 * Stream destination is local, mountable, destination.
27
 *
28
 * @package RunOpenCode\Backup\Destination
29
 */
30
class StreamDestination implements DestinationInterface
31
{
32
    /**
33
     * @var BackupInterface[]
34
     */
35
    protected $backups;
36
37
    /**
38
     * @var string
39
     */
40
    protected $directory;
41
42
    /**
43
     * @var Filesystem
44
     */
45
    private $filesystem;
46
47 14
    public function __construct($directory, Filesystem $filesystem = null)
48
    {
49 14
        $this->directory = rtrim($directory, '/\\');
50 14
        $this->filesystem = is_null($filesystem) ? new Filesystem() : $filesystem;
51
52 14
        if (!$this->filesystem->exists($this->directory)) {
53
54 10
            $this->filesystem->mkdir($this->directory);
55
56 14
        } elseif (!is_dir($this->directory)) {
57
            throw new \RuntimeException(sprintf('Provided location "%s" is not directory.', $this->directory));
58 10
        } elseif (!is_writable($this->directory)) {
59
            throw new \RuntimeException(sprintf('Provided location "%s" is not writeable.', $this->directory));
60
        }
61 14
    }
62
63
    /**
64
     * {@inheritdoc}
65
     */
66 14
    public function push(BackupInterface $backup)
67
    {
68
        // Prepare destination.
69 14
        $backupDirectory = $this->directory . DIRECTORY_SEPARATOR . $backup->getName();
70 14
        $this->filesystem->mkdir($backupDirectory);
71
72
        // Get current in backup location (if it is an incremental backup).
73 14
        $existingBackupFiles = $this->getFiles($backupDirectory);
74
75 14
        foreach ($backup->getFiles() as $backupFile) {
76
77
            try {
78
                // Overwrite old, copy new to destination.
79 14
                $this->filesystem->copy($backupFile->getPath(), sprintf('%s%s%s', $backupDirectory, DIRECTORY_SEPARATOR, $backupFile->getRelativePath()));
80 14
            } catch (\Exception $e) {
81
                throw new DestinationException(sprintf('Unable to backup file "%s" to destination "%s".', $backupFile->getPath(), $this->directory), 0, $e);
82
            }
83
84
            // If existing file is overwritten, remove it from the list of current files in backup destination.
85 14
            if (isset($existingBackupFiles[$backupFile->getRelativePath()])) {
86 2
                unset($existingBackupFiles[$backupFile->getRelativePath()]);
87 2
            }
88 14
        }
89
90 7
        try {
91
            // Remove deleted files from source from destination.
92
            $this->filesystem->remove(array_map(function(File $file) { return $file->getPath(); }, $existingBackupFiles));
93
94
            // Cleanup empty directories, there is no need to keep them.
95 14
            $this->removeEmptyDirectories($backupDirectory);
96 14
        } catch (\Exception $e) {
97
            throw new DestinationException(sprintf('Unable to backup cleanup destination "%s" after backup process.', $this->directory), 0, $e);
98
        }
99
100
101
        // Don't reload, just add new backup to list if possible.
102 14
        if (!empty($this->backups)) {
103
            $this->backups[] = $backup;
104
        }
105 14
    }
106
107
    /**
108
     * {@inheritdoc}
109
     */
110 8
    public function getIterator()
111
    {
112 2
        if (is_null($this->backups)) {
113
            $this->load();
114
        }
115
116 2
        return new \ArrayIterator($this->backups);
117 7
    }
118
119
    /**
120
     * {@inheritdoc}
121
     */
122 10
    public function get($name)
123 1
    {
124 10
        if (is_null($this->backups)) {
125
            $this->load();
126
        }
127
128 10
        return $this->backups[$name];
129
    }
130
131
    /**
132
     * {@inheritdoc}
133
     */
134 10
    public function has($name)
135
    {
136 10
        if (is_null($this->backups)) {
137 10
            $this->load();
138 10
        }
139
140 10
        return array_key_exists($name, $this->backups);
141
    }
142
143
    /**
144
     * {@inheritdoc}
145
     */
146
    public function delete($name)
147
    {
148
        try {
149
            $this->filesystem->remove(sprintf('%s%s%s', rtrim($this->directory, DIRECTORY_SEPARATOR), DIRECTORY_SEPARATOR, $name));
150
        } catch (\Exception $e) {
151
            throw new DestinationException(sprintf('Unable to remove backup "%s" from stream destination "%s".', $name, $this->directory), 0, $e);
152
        }
153
154
        if (!is_null($this->backups)) {
155
            unset($this->backups[$name]);
156
        }
157
    }
158
159
    /**
160
     * {@inheritdoc}
161
     */
162
    public function all()
163
    {
164
        if (is_null($this->backups)) {
165
            $this->load();
166
        }
167
168
        return $this->backups;
169
    }
170
171
    /**
172
     * {@inheritdoc}
173
     */
174 2
    public function count()
175
    {
176 2
        return count($this->getIterator());
177
    }
178
179
    /**
180
     * Load backups from destination.
181
     *
182
     * @return BackupInterface[]
183
     */
184 10
    protected function load()
185
    {
186 10
        $this->backups = array();
187
188
        /**
189
         * @var \SplFileInfo $backupDirectory
190
         */
191 10
        foreach (Finder::create()->in($this->directory)->depth(0)->directories()->sortByModifiedTime() as $backupDirectory) {
192
193 10
            $backup = new Backup($backupDirectory->getBasename(), $this->getFiles($backupDirectory->getPathname()), 0, $backupDirectory->getCTime(), $backupDirectory->getMTime());
194
195 10
            $this->backups[$backup->getName()] = $backup;
196 10
        }
197 10
    }
198
199
    /**
200
     * Remove empty directories from destination.
201
     *
202
     * @param $backupDirectory
203
     */
204 14
    protected function removeEmptyDirectories($backupDirectory)
205
    {
206
        /**
207
         * @var \SplFileInfo $dir
208
         */
209 14
        foreach (Finder::create()->directories()->in($backupDirectory)->depth(0) as $dir) {
210
211 3
            if (Finder::create()->files()->in($dir->getPathname())->count() > 0) {
212 2
                $this->removeEmptyDirectories($dir->getPathname());
213 2
            } else {
214 2
                $this->filesystem->remove($dir->getPathname());
215
            }
216 14
        }
217 14
    }
218
219
    /**
220
     * Get all files in path.
221
     *
222
     * @param string $path Path to
223
     * @return File[] List of files in given location
224
     */
225 14
    protected function getFiles($path)
226
    {
227 14
        $result = array();
228
229 14
        foreach (Finder::create()->in($path)->files() as $file) {
230 10
            $file = File::fromSplFileInfo($file, $path);
231 10
            $result[$file->getRelativePath()] = $file;
232 14
        }
233
234 14
        return $result;
235
    }
236
}
237