Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
6.67% |
6 / 90 |
|
16.67% |
1 / 6 |
CRAP | |
0.00% |
0 / 1 |
LexiconSpeechoidStorage | |
6.67% |
6 / 90 |
|
16.67% |
1 / 6 |
812.33 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
findLexiconNameByLanguage | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
6 | |||
getEntry | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
56 | |||
createEntryItem | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
72 | |||
updateEntryItem | |
19.05% |
4 / 21 |
|
0.00% |
0 / 1 |
41.95 | |||
deleteEntryItem | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
30 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Wikispeech\Lexicon; |
4 | |
5 | /** |
6 | * @file |
7 | * @ingroup Extensions |
8 | * @license GPL-2.0-or-later |
9 | */ |
10 | |
11 | use InvalidArgumentException; |
12 | use MediaWiki\Wikispeech\SpeechoidConnector; |
13 | use MediaWiki\Wikispeech\SpeechoidConnectorException; |
14 | use MWException; |
15 | use WANObjectCache; |
16 | |
17 | /** |
18 | * @since 0.1.8 |
19 | */ |
20 | class LexiconSpeechoidStorage implements LexiconStorage { |
21 | |
22 | /** @var string */ |
23 | public const CACHE_CLASS = 'Wikispeech.LexiconSpeechoidStorage.lexiconName'; |
24 | |
25 | /** @var SpeechoidConnector */ |
26 | private $speechoidConnector; |
27 | |
28 | /** |
29 | * @var mixed Some sort of cache, used to keep track of lexicons per language. |
30 | * Needs to support makeKey(), get() and set(). |
31 | * Normally this would be a WANObjectCache. |
32 | */ |
33 | private $cache; |
34 | |
35 | /** |
36 | * @since 0.1.8 |
37 | * @param SpeechoidConnector $speechoidConnector |
38 | * @param mixed $cache |
39 | */ |
40 | public function __construct( |
41 | SpeechoidConnector $speechoidConnector, |
42 | $cache |
43 | ) { |
44 | $this->speechoidConnector = $speechoidConnector; |
45 | $this->cache = $cache; |
46 | } |
47 | |
48 | /** |
49 | * @since 0.1.8 |
50 | * @param string $language ISO 639 language code passed down to Speechoid |
51 | * @return string|null |
52 | * @throws InvalidArgumentException If $language is not 2 characters long. |
53 | */ |
54 | private function findLexiconNameByLanguage( |
55 | string $language |
56 | ): ?string { |
57 | $language = strtolower( $language ); |
58 | $cacheKey = $this->cache->makeKey( self::CACHE_CLASS, $language ); |
59 | $lexiconName = $this->cache->get( $cacheKey ); |
60 | if ( !$lexiconName ) { |
61 | $lexiconName = $this->speechoidConnector->findLexiconByLanguage( $language ); |
62 | // @todo Consider, if null we'll request on each attempt. |
63 | // @todo Rather we could store as 'NULL' or something and keep track of that. |
64 | // @todo But perhaps it's nicer to allow for new languages without the hour delay? |
65 | $this->cache->set( |
66 | $cacheKey, |
67 | $lexiconName, |
68 | WANObjectCache::TTL_HOUR |
69 | ); |
70 | } |
71 | return $lexiconName; |
72 | } |
73 | |
74 | /** |
75 | * @since 0.1.8 |
76 | * @param string $language |
77 | * @param string $key |
78 | * @return LexiconEntry|null |
79 | * @throws MWException If no lexicon is available for language. |
80 | * @throws SpeechoidConnectorException On unexpected response from Speechoid. |
81 | */ |
82 | public function getEntry( |
83 | string $language, |
84 | string $key |
85 | ): ?LexiconEntry { |
86 | if ( !$language || !$key ) { |
87 | return null; |
88 | } |
89 | |
90 | $lexiconName = $this->findLexiconNameByLanguage( $language ); |
91 | if ( $lexiconName === null ) { |
92 | throw new MWException( "No lexicon available for language $language" ); |
93 | } |
94 | $status = $this->speechoidConnector->lookupLexiconEntries( $lexiconName, [ $key ] ); |
95 | if ( !$status->isOK() ) { |
96 | throw new SpeechoidConnectorException( "Unexpected response from Speechoid: $status" ); |
97 | } |
98 | $deserializedItems = $status->getValue(); |
99 | if ( $deserializedItems === [] ) { |
100 | // no such key in lexicon |
101 | return null; |
102 | } |
103 | $items = []; |
104 | foreach ( $deserializedItems as $deserializedItem ) { |
105 | $item = new LexiconEntryItem(); |
106 | $item->setProperties( $deserializedItem ); |
107 | $items[] = $item; |
108 | } |
109 | |
110 | $entry = new LexiconEntry(); |
111 | $entry->setLanguage( $language ); |
112 | $entry->setKey( $key ); |
113 | $entry->setItems( $items ); |
114 | return $entry; |
115 | } |
116 | |
117 | /** |
118 | * @since 0.1.8 |
119 | * @param string $language |
120 | * @param string $key |
121 | * @param LexiconEntryItem $item |
122 | * @throws InvalidArgumentException If $item->item is null. |
123 | * If Speechoid identity is already set. |
124 | * @throws MWException If no lexicon is available for language. |
125 | * If failed to encode lexicon entry item properties to JSON. |
126 | * If unable to add lexicon entry to Speechoid. |
127 | * If unable to retrieve the created lexicon entry item from Speechoid. |
128 | */ |
129 | public function createEntryItem( |
130 | string $language, |
131 | string $key, |
132 | LexiconEntryItem $item |
133 | ): void { |
134 | if ( $item->getProperties() === null ) { |
135 | // @todo Better sanity check, ensure that required values (IPA, etc) are set. |
136 | throw new InvalidArgumentException( '$item->item must not be null.' ); |
137 | } |
138 | if ( $item->getSpeechoidIdentity() ) { |
139 | throw new InvalidArgumentException( 'Speechoid identity is already set.' ); |
140 | } |
141 | $lexiconName = $this->findLexiconNameByLanguage( $language ); |
142 | if ( $lexiconName === null ) { |
143 | throw new MWException( "No lexicon available for language $language" ); |
144 | } |
145 | $json = $item->toJson(); |
146 | if ( $json === '' ) { |
147 | throw new MWException( 'Failed to encode lexicon entry item properties to JSON.' ); |
148 | } |
149 | $status = $this->speechoidConnector->addLexiconEntry( $lexiconName, $json ); |
150 | if ( !$status->isOK() ) { |
151 | throw new MWException( "Failed to add lexicon entry: $status" ); |
152 | } |
153 | // Speechoid returns the identity. We need the actual entry. |
154 | // Thus we make a new request and find that entry. |
155 | // @todo Implement this when done on server side. https://phabricator.wikimedia.org/T277852 |
156 | |
157 | /** @var int $speechoidIdentity */ |
158 | $speechoidIdentity = $status->getValue(); |
159 | $speechoidEntry = $this->getEntry( $language, $key ); |
160 | if ( $speechoidEntry === null ) { |
161 | throw new MWException( "Expected the created lexicon entry to exist." ); |
162 | } |
163 | $speechoidEntryItem = $speechoidEntry->findItemBySpeechoidIdentity( $speechoidIdentity ); |
164 | if ( $speechoidEntryItem === null ) { |
165 | throw new MWException( 'Expected the created lexicon entry item to exist.' ); |
166 | } |
167 | $item->copyFrom( $speechoidEntryItem ); |
168 | } |
169 | |
170 | /** |
171 | * @since 0.1.8 |
172 | * @param string $language |
173 | * @param string $key |
174 | * @param LexiconEntryItem $item |
175 | * @throws InvalidArgumentException If $item->item is null. |
176 | * If Speechoid identity is not set. |
177 | * @throws MWException If no lexicon is available for language. |
178 | * If failed to encode lexicon entry item properties to JSON. |
179 | */ |
180 | public function updateEntryItem( |
181 | string $language, |
182 | string $key, |
183 | LexiconEntryItem $item |
184 | ): void { |
185 | if ( $item->getProperties() === null ) { |
186 | // @todo Better sanity check, ensure that required values (IPA, etc) are set. |
187 | throw new InvalidArgumentException( '$item->item must not be null.' ); |
188 | } |
189 | $speechoidIdentity = $item->getSpeechoidIdentity(); |
190 | if ( $speechoidIdentity === null ) { |
191 | throw new InvalidArgumentException( 'Speechoid identity not set.' ); |
192 | } |
193 | $lexiconName = $this->findLexiconNameByLanguage( $language ); |
194 | if ( $lexiconName === null ) { |
195 | throw new MWException( "No lexicon available for language $language" ); |
196 | } |
197 | $json = $item->toJson(); |
198 | if ( $json === '' ) { |
199 | throw new MWException( 'Failed to encode lexicon entry item properties to JSON.' ); |
200 | } |
201 | // @todo The lexicon name is embedded in $json here. |
202 | // @todo We want to use our own data model and produce a speechoid object from that instead. |
203 | $status = $this->speechoidConnector->updateLexiconEntry( $json ); |
204 | if ( !$status->isOK() ) { |
205 | throw new MWException( "Failed to update lexicon entry item: $status" ); |
206 | } |
207 | |
208 | // SpeechoidConnector::updateLexiconEntry does not return dbRef, |
209 | // So we need to request the entry again from Speechoid. |
210 | // @todo Ask STTS to return complete result at update. |
211 | $speechoidEntry = $this->getEntry( $language, $key ); |
212 | if ( $speechoidEntry === null ) { |
213 | throw new MWException( "Expected the updated lexicon entry to exist." ); |
214 | } |
215 | $speechoidEntryItem = $speechoidEntry->findItemBySpeechoidIdentity( $speechoidIdentity ); |
216 | if ( $speechoidEntryItem === null ) { |
217 | throw new MWException( 'Expected the updated lexicon entry item to exist.' ); |
218 | } |
219 | $item->copyFrom( $speechoidEntryItem ); |
220 | } |
221 | |
222 | /** |
223 | * @since 0.1.8 |
224 | * @param string $language |
225 | * @param string $key |
226 | * @param LexiconEntryItem $item |
227 | * @throws InvalidArgumentException If $item->item is null. |
228 | * If Speechoid identity is not set. |
229 | * @throws MWException If no lexicon is available for language. |
230 | * If failed to delete the lexicon entry item. |
231 | */ |
232 | public function deleteEntryItem( |
233 | string $language, |
234 | string $key, |
235 | LexiconEntryItem $item |
236 | ): void { |
237 | if ( $item->getProperties() === null ) { |
238 | throw new InvalidArgumentException( '$item->item must not be null.' ); |
239 | } |
240 | $itemSpeechoidIdentity = $item->getSpeechoidIdentity(); |
241 | if ( $itemSpeechoidIdentity === null ) { |
242 | throw new InvalidArgumentException( 'Speechoid identity not set.' ); |
243 | } |
244 | $lexiconName = $this->findLexiconNameByLanguage( $language ); |
245 | if ( $lexiconName === null ) { |
246 | throw new MWException( "No lexicon available for language $language" ); |
247 | } |
248 | $status = $this->speechoidConnector->deleteLexiconEntry( |
249 | $lexiconName, |
250 | $itemSpeechoidIdentity |
251 | ); |
252 | if ( !$status->isOK() ) { |
253 | throw new MWException( "Failed to delete lexicon entry item: $status" ); |
254 | } |
255 | } |
256 | |
257 | } |