Completed
Push — master ( 2ea446...593553 )
by Andrii
8s
created

Storage::getInstance()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 8
ccs 0
cts 5
cp 0
rs 9.4285
cc 2
eloc 4
nc 2
nop 0
crap 6
1
<?php
2
3
/*
4
 * Asset Packagist
5
 *
6
 * @link      https://github.com/hiqdev/asset-packagist
7
 * @package   asset-packagist
8
 * @license   BSD-3-Clause
9
 * @copyright Copyright (c) 2016, HiQDev (http://hiqdev.com/)
10
 */
11
12
namespace hiqdev\assetpackagist\components;
13
14
use hiqdev\assetpackagist\helpers\Locker;
15
use hiqdev\assetpackagist\models\AssetPackage;
16
use Yii;
17
use yii\base\Component;
18
use yii\helpers\Json;
19
20
class Storage extends Component
21
{
22
    protected $_path;
23
    protected $_locker;
24
    
25
    protected static $_instance;
26
27
    public static function getInstance()
28
    {
29
        if (static::$_instance === null) {
30
            static::$_instance = new static();
31
        }
32
33
        return static::$_instance;
34
    }
35
36 1
    public function init()
37
    {
38 1
        $this->_path = Yii::getAlias('@storage', false);
39 1
    }
40
41
    protected function getLocker()
42
    {
43
        if ($this->_locker === null) {
44
            $this->_locker = Locker::getInstance($this->buildPath('lock'));
45
        }
46
47
        return $this->_locker;
48
    }
49
50
    public function getNextID()
51
    {
52
        $this->getLocker()->lock();
53
        {
54
            $nextID = $this->readLastID() + 1;
55
            $this->writeLastID($nextID);
56
        }
57
        $this->getLocker()->release();
58
59
        return $nextID;
60
    }
61
62
    protected function readLastId()
63
    {
64
        $path = $this->getLastIDPath();
65
66
        return (file_exists($path) ? (int) file_get_contents($path) : 0) ?: 1000000;
67
    }
68
69
    protected function writeLastId($value)
70
    {
71
        file_put_contents($this->getLastIDPath(), $value);
72
    }
73
74
    protected function getLastIDPath()
75
    {
76
        return $this->buildPath('lastid');
77
    }
78
79
    public function writePackage(AssetPackage $package)
80
    {
81
        $name = $package->getNormalName();
82
        $data = [
83
            'packages' => [
84
                $name => $package->getReleases(),
85
            ],
86
        ];
87
        $json = Json::encode($data);
88
        $hash = hash('sha256', $json);
89
        $path = $this->buildHashedPath($name, $hash);
90 View Code Duplication
        if (!file_exists($path)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
91
            $this->getLocker()->lock();
92
            {
93
                static::mkdir(dirname($path));
94
                file_put_contents($path, $json);
95
                file_put_contents($this->buildHashedPath($name), $json);
96
                $this->writeProviderLatest($name, $hash);
97
            }
98
            $this->getLocker()->release();
99
        }
100
101
        return $hash;
102
    }
103
104
    /**
105
     * Reads the $package information from the storage.
106
     *
107
     * @param AssetPackage $package
108
     * @return array|null array of two elements:
109
     *  0 - string sha256 hash of the package
110
     *  1 - array[] releases
111
     *
112
     * Returns null, when package does not exist.
113
     */
114
    public function readPackage(AssetPackage $package)
115
    {
116
        $name = $package->getNormalName();
117
        $path = $this->buildHashedPath($name);
118
        if (!file_exists($path)) {
119
            return null;
120
        }
121
        $json = file_get_contents($path);
122
        $updateTime = filemtime($path);
123
        $hash = hash('sha256', $json);
124
        $data = Json::decode($json);
125
        $releases = isset($data['packages'][$name]) ? $data['packages'][$name] : [];
126
127
        return compact('hash', 'releases', 'updateTime');
128
    }
129
130
    public function buildPath()
131
    {
132
        $args = func_get_args();
133
        array_unshift($args, $this->_path);
134
135
        return implode(DIRECTORY_SEPARATOR, $args);
136
    }
137
138
    public function buildHashedPath($name, $hash = 'latest')
139
    {
140
        return $this->buildPath('p', $name, $hash . '.json');
141
    }
142
143
    protected function writeProviderLatest($name, $hash)
144
    {
145
        $latest_path = $this->buildHashedPath('provider-latest');
146
        if (file_exists($latest_path)) {
147
            $data = Json::decode(file_get_contents($latest_path) ?: '[]');
148
        }
149
        if (!isset($data)||!is_array($data)) {
150
            $data = [];
151
        }
152
        if (!isset($data['providers'])) {
153
            $data['providers'] = [];
154
        }
155
        $data['providers'][$name] = ['sha256' => $hash];
156
        $json = Json::encode($data);
157
        $hash = hash('sha256', $json);
158
        $path = $this->buildHashedPath('provider-latest', $hash);
159 View Code Duplication
        if (!file_exists($path)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
160
            $this->getLocker()->lock();
161
            {
162
                static::mkdir(dirname($path));
163
                file_put_contents($path, $json);
164
                file_put_contents($latest_path, $json);
165
                $this->writePackagesJson($hash);
166
            }
167
            $this->getLocker()->release();
168
        }
169
170
        return $hash;
171
    }
172
173
    protected function writePackagesJson($hash)
174
    {
175
        $data = [
176
            'providers-url'     => '/p/%package%/%hash%.json',
177
            'provider-includes' => [
178
                'p/provider-latest/%hash%.json' => [
179
                    'sha256' => $hash,
180
                ],
181
            ],
182
        ];
183
        $this->getLocker()->lock();
184
        {
185
            file_put_contents($this->buildPath('packages.json'), Json::encode($data));
186
        }
187
        $this->getLocker()->release();
188
    }
189
190
    public static function mkdir($dir)
191
    {
192
        if (!file_exists($dir)) {
193
            mkdir($dir, 0777, true);
194
        }
195
    }
196
197
    public function readJson($path)
198
    {
199
        return Json::decode(file_get_contents($this->buildPath($path)));
200
    }
201
202
    protected function readPackagesJson()
203
    {
204
        $data = $this->readJson('packages.json');
205
206
        return $data['provider-includes'];
207
    }
208
209
    protected function readProvider($path)
210
    {
211
        $data = $this->readJson($path);
212
213
        return $data['providers'];
214
    }
215
216
    public function listPackages()
217
    {
218
        $packages = [];
219
        $providers = $this->readPackagesJson();
220
        foreach ($providers as $path => $data) {
221
            $path = strtr($path, ['%hash%' => $data['sha256']]);
222
            $packages = array_merge($packages, $this->readProvider($path));
223
        }
224
225
        return $packages;
226
    }
227
}
228