UpdateManager::download()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 2
eloc 10
c 1
b 0
f 1
nc 2
nop 1
dl 0
loc 15
rs 9.4285
1
<?php
2
3
/**
4
 * This file is part of the Superdesk Web Publisher Updater Bundle.
5
 *
6
 * Copyright 2015 Sourcefabric z.u. and contributors.
7
 *
8
 * For the full copyright and license information, please see the
9
 * AUTHORS and LICENSE files distributed with this source code.
10
 *
11
 * @copyright 2015 Sourcefabric z.ú.
12
 * @license http://www.superdesk.org/license
13
 */
14
namespace SWP\UpdaterBundle\Manager;
15
16
use SWP\UpdaterBundle\Model\UpdatePackage;
17
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
18
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
19
20
/**
21
 * Update manager.
22
 */
23
class UpdateManager extends AbstractManager
24
{
25
    const UPDATES_ENDPOINT = '/check.json';
26
    const CORE_ENDPOINT = '/core.json';
27
    const LATEST_VERSION_ENDPOINT = '/latest_version.json';
28
29
    const RESOURCE_CORE = 'core';
30
31
    /**
32
     * Available updates channels.
33
     *
34
     * @var array
35
     */
36
    private $channels = array('default', 'security', 'nightly');
37
38
    /**
39
     * Latest update.
40
     *
41
     * @var UpdatePackage
42
     */
43
    private $latestUpdate;
44
45
    /**
46
     * List of available updates.
47
     *
48
     * @var array
49
     */
50
    private $availableUpdates = array();
51
52
    /**
53
     * {@inheritdoc}
54
     */
55
    public function getAvailableUpdates($channel = '')
56
    {
57
        $response = $this->client->call(
58
            self::UPDATES_ENDPOINT,
59
            array(
60
                'coreVersion' => $this->getCurrentVersion(),
61
                'channel' => in_array($channel, $this->channels) ? $channel : $this->channels[0],
62
            )
63
        );
64
65
        $response = $this->parseJson($response);
66
        if (isset($response['_items']) && !empty($response['_items'])) {
67
            foreach ($response['_items'] as $key => $resource) {
68
                foreach ($resource as $value) {
69
                    $this->availableUpdates[$key][] = new UpdatePackage($value);
70
                }
71
            }
72
        }
73
74
        if (empty($this->availableUpdates)) {
75
            throw new NotFoundHttpException('No update packages available.');
76
        }
77
78
        return $this->availableUpdates;
79
    }
80
81
    /**
82
     * {@inheritdoc}
83
     */
84
    public function getCurrentVersion()
85
    {
86
        return $this->currentVersion;
87
    }
88
89
    /**
90
     * Gets the latest version.
91
     *
92
     * @return string|null Latest version
93
     */
94
    public function getLatestVersion()
95
    {
96
        $latestUpdate = $this->getLatestUpdate();
97
        if ($latestUpdate) {
98
            return $latestUpdate->getVersion();
99
        }
100
    }
101
102
    /**
103
     * {@inheritdoc}
104
     */
105
    public function getLatestUpdate()
106
    {
107
        if (!$this->latestUpdate) {
108
            $response = $this->client->call(self::LATEST_VERSION_ENDPOINT);
109
            $this->latestUpdate = new UpdatePackage($this->parseJson($response));
110
        }
111
112
        return $this->latestUpdate;
113
    }
114
115
    /**
116
     * Downloads core updates to the directory
117
     * defined in the config.yml. Defaults to: cache.
118
     */
119
    public function downloadCoreUpdates()
120
    {
121
        foreach ((array) $this->availableUpdates['core'] as $update) {
122
            $this->copyRemoteFile($update->url, $update->getVersion());
123
        }
124
    }
125
126
    /**
127
     * Downloads available updates to the app instance
128
     * (see temp_dir configuration option), by default to app's cache folder.
129
     *
130
     * @param string $resource Resource type (e.g. core, plugin etc.)
131
     *
132
     * @throws UnprocessableEntityHttpException When resource doesn't exist
133
     */
134
    public function download($resource)
135
    {
136
        $this->getAvailableUpdates();
137
        switch ($resource) {
138
            case self::RESOURCE_CORE:
139
                $this->downloadCoreUpdates();
140
                break;
141
142
            default:
143
                throw new UnprocessableEntityHttpException(sprintf(
144
                    'Resource "%s" doesn\'t exist!',
145
                    $resource
146
                ));
147
        }
148
    }
149
150
    /**
151
     * Apply available updates to the app by given resource.
152
     *
153
     * @param string $resource Resource type (e.g. core, plugin etc.)
154
     *
155
     * @throws UnprocessableEntityHttpException When resource doesn't exist
156
     */
157
    public function applyUpdates($resource)
158
    {
159
        $this->getAvailableUpdates();
160
        switch ($resource) {
161
            case self::RESOURCE_CORE:
162
                $this->updateCore();
163
                break;
164
165
            default:
166
                throw new UnprocessableEntityHttpException(sprintf(
167
                    'Resource "%s" doesn\'t exist!',
168
                    $resource
169
                ));
170
        }
171
    }
172
173
    /**
174
     * Updates the core by calling Updater "update" command.
175
     *
176
     * @throws NotFoundHttpException When update package not found
177
     * @throws \RuntimeException     When upgrading failed
178
     */
179
    public function updateCore()
180
    {
181
        $sortedVersions = $this->sortPackagesByVersion($this->availableUpdates['core']);
182
        foreach ($sortedVersions as $update) {
183
            $packageName = $update->getVersion().'.zip';
184
            $packagePath = $this->tempDir.'/'.$packageName;
185
            $this->addLogInfo('Started updating application\'s core...');
186
            if (!file_exists($packagePath)) {
187
                throw new NotFoundHttpException(sprintf(
188
                    'Update packge %s could not be found at %s',
189
                    $packageName,
190
                    $this->tempDir
191
                ));
192
            }
193
194
            $result = Updater::runUpdateCommand(array(
195
                'target' => $this->targetDir,
196
                'temp_dir' => $this->tempDir,
197
                'package_dir' => $packagePath,
198
            ));
199
200
            if ($result !== 0) {
201
                throw new \RuntimeException('Could not update the instance.');
202
            }
203
204
            $this->addLogInfo('Successfully updated application\'s core...');
205
206
            $this->cleanUp($packagePath);
207
        }
208
    }
209
210
    private function cleanUp($packagePath)
211
    {
212
        $this->addLogInfo('Started cleaning up...');
213
        unlink($packagePath);
214
        $this->addLogInfo('Successfully cleaned up...');
215
    }
216
217
    private function parseJson($jsonString)
218
    {
219
        $jsonObj = json_decode($jsonString, true);
220
        if (is_null($jsonObj) || json_last_error() !== JSON_ERROR_NONE) {
221
            throw new \RuntimeException('Response body is not in JSON format.', json_last_error());
222
        }
223
224
        return $jsonObj;
225
    }
226
}
227