WhatsNewCheck   A
last analyzed

Complexity

Total Complexity 24

Size/Duplication

Total Lines 149
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 78
c 1
b 0
f 0
dl 0
loc 149
rs 10
wmc 24

8 Methods

Rating   Name   Duplication   Size   Complexity  
A normalizeVersion() 0 9 3
A cacheResult() 0 7 2
B check() 0 27 6
A extractData() 0 21 4
A queryChangesServer() 0 11 2
A __construct() 0 5 1
A getChangesForVersion() 0 9 2
A evaluateResponse() 0 14 4
1
<?php
2
declare(strict_types=1);
3
/**
4
 * Audioplayer
5
 *
6
 * This file is licensed under the Affero General Public License version 3 or
7
 * later. See the LICENSE.md file.
8
 *
9
 * @author Marcel Scherello <[email protected]>
10
 * @author Arthur Schiwon <[email protected]>
11
 * @author Christoph Wurst <[email protected]>
12
 * @copyright 2020 Marcel Scherello
13
 */
14
15
namespace OCA\audioplayer\WhatsNew;
16
17
use OCP\AppFramework\Db\DoesNotExistException;
18
use OCP\Http\Client\IClientService;
19
use OCP\Http\Client\IResponse;
20
use OCP\ILogger;
21
22
class WhatsNewCheck
23
{
24
    public const RESPONSE_NO_CONTENT = 0;
25
    public const RESPONSE_USE_CACHE = 1;
26
    public const RESPONSE_HAS_CONTENT = 2;
27
    /** @var IClientService */
28
    protected $clientService;
29
    /** @var WhatsNewMapper */
30
    private $mapper;
31
    /** @var ILogger */
32
    private $logger;
33
34
    public function __construct(IClientService $clientService, WhatsNewMapper $mapper, ILogger $logger)
35
    {
36
        $this->clientService = $clientService;
37
        $this->mapper = $mapper;
38
        $this->logger = $logger;
39
    }
40
41
    /**
42
     * @param string $version
43
     * @return array
44
     * @throws DoesNotExistException
45
     */
46
    public function getChangesForVersion(string $version): array
47
    {
48
        $version = $this->normalizeVersion($version);
49
        $changesInfo = $this->mapper->getChanges($version);
50
        $changesData = json_decode($changesInfo->getData(), true);
51
        if (empty($changesData)) {
52
            throw new DoesNotExistException('Unable to decode changes info');
53
        }
54
        return $changesData;
55
    }
56
57
    /**
58
     * returns a x.y.z form of the provided version. Extra numbers will be
59
     * omitted, missing ones added as zeros.
60
     */
61
    public function normalizeVersion(string $version): string
62
    {
63
        $versionNumbers = array_slice(explode('.', $version), 0, 3);
64
        $versionNumbers[0] = $versionNumbers[0] ?: '0'; // deal with empty input
65
        while (count($versionNumbers) < 3) {
66
            // changelog server expects x.y.z, pad 0 if it is too short
67
            $versionNumbers[] = 0;
68
        }
69
        return implode('.', $versionNumbers);
70
    }
71
72
    /**
73
     * @param string $uri
74
     * @param string $version
75
     * @return array
76
     * @throws \Exception
77
     */
78
    public function check(string $uri, string $version): array
79
    {
80
        try {
81
            $version = $this->normalizeVersion($version);
82
            $changesInfo = $this->mapper->getChanges($version);
83
            if ($changesInfo->getLastCheck() + 1800 > time()) {
84
                return json_decode($changesInfo->getData(), true);
85
            }
86
        } catch (DoesNotExistException $e) {
87
            $changesInfo = new WhatsNewResult();
88
        }
89
90
        $response = $this->queryChangesServer($uri, $changesInfo);
91
92
        switch ($this->evaluateResponse($response)) {
93
            case self::RESPONSE_NO_CONTENT:
94
                return [];
95
            case self::RESPONSE_USE_CACHE:
96
                return json_decode($changesInfo->getData(), true);
97
            case self::RESPONSE_HAS_CONTENT:
98
            default:
99
                $data = $this->extractData($response->getBody());
100
                $changesInfo->setData(json_encode($data));
101
                $changesInfo->setEtag($response->getHeader('Etag'));
102
                $this->cacheResult($changesInfo, $version);
103
104
                return $data;
105
        }
106
    }
107
108
    /**
109
     * @throws \Exception
110
     */
111
    protected function queryChangesServer(string $uri, WhatsNewResult $entry): IResponse
112
    {
113
        $headers = [];
114
        if ($entry->getEtag() !== '') {
115
            $headers['If-None-Match'] = [$entry->getEtag()];
116
        }
117
118
        $entry->setLastCheck(time());
119
        $client = $this->clientService->newClient();
120
        return $client->get($uri, [
121
            'headers' => $headers,
122
        ]);
123
    }
124
125
    protected function evaluateResponse(IResponse $response): int
126
    {
127
        if ($response->getStatusCode() === 304) {
128
            return self::RESPONSE_USE_CACHE;
129
        } elseif ($response->getStatusCode() === 404) {
130
            return self::RESPONSE_NO_CONTENT;
131
        } elseif ($response->getStatusCode() === 200) {
132
            return self::RESPONSE_HAS_CONTENT;
133
        }
134
        $this->logger->debug('Unexpected return code {code} from changelog server', [
135
            'app' => 'core',
136
            'code' => $response->getStatusCode(),
137
        ]);
138
        return self::RESPONSE_NO_CONTENT;
139
    }
140
141
    protected function extractData($body): array
142
    {
143
        $data = [];
144
        if ($body) {
145
            $loadEntities = libxml_disable_entity_loader(true);
146
            $xml = @simplexml_load_string($body);
147
            libxml_disable_entity_loader($loadEntities);
148
            if ($xml !== false) {
149
                $data['changelogURL'] = (string)$xml->changelog['href'];
150
                $data['whatsNew'] = [];
151
                foreach ($xml->whatsNew as $infoSet) {
152
                    $data['whatsNew'][(string)$infoSet['lang']] = [
153
                        'regular' => (array)$infoSet->regular->item,
154
                        'admin' => (array)$infoSet->admin->item,
155
                    ];
156
                }
157
            } else {
158
                libxml_clear_errors();
159
            }
160
        }
161
        return $data;
162
    }
163
164
    protected function cacheResult(WhatsNewResult $entry, string $version)
165
    {
166
        if ($entry->getVersion() === $version) {
167
            $this->mapper->update($entry);
168
        } else {
169
            $entry->setVersion($version);
170
            $this->mapper->insert($entry);
171
        }
172
    }
173
}