WhatsNewCheck::queryChangesServer()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 7
c 1
b 0
f 0
nc 2
nop 2
dl 0
loc 11
rs 10
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 Psr\Log\LoggerInterface;
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 LoggerInterface */
32
    private $logger;
33
34
    public function __construct(IClientService $clientService, WhatsNewMapper $mapper, LoggerInterface $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
        return self::RESPONSE_NO_CONTENT;
135
    }
136
137
    protected function extractData($body): array
138
    {
139
        $data = [];
140
        if ($body) {
141
            $loadEntities = libxml_disable_entity_loader(true);
142
            $xml = @simplexml_load_string($body);
143
            libxml_disable_entity_loader($loadEntities);
144
            if ($xml !== false) {
145
                $data['changelogURL'] = (string)$xml->changelog['href'];
146
                $data['whatsNew'] = [];
147
                foreach ($xml->whatsNew as $infoSet) {
148
                    $data['whatsNew'][(string)$infoSet['lang']] = [
149
                        'regular' => (array)$infoSet->regular->item,
150
                        'admin' => (array)$infoSet->admin->item,
151
                    ];
152
                }
153
            } else {
154
                libxml_clear_errors();
155
            }
156
        }
157
        return $data;
158
    }
159
160
    protected function cacheResult(WhatsNewResult $entry, string $version)
161
    {
162
        if ($entry->getVersion() === $version) {
163
            $this->mapper->update($entry);
164
        } else {
165
            $entry->setVersion($version);
166
            $this->mapper->insert($entry);
167
        }
168
    }
169
}