File Coverage

blib/lib/Pootle/Client.pm
Criterion Covered Total %
statement 25 27 92.5
branch n/a
condition n/a
subroutine 9 9 100.0
pod n/a
total 34 36 94.4


line stmt bran cond sub pod time code
1             # Copyright (C) 2017 Koha-Suomi
2             #
3             # This file is part of Pootle::Client.
4              
5             package Pootle::Client;
6              
7 5     5   325573 use Modern::Perl '2015';
  5         42  
  5         35  
8 5     5   707 use utf8;
  5         10  
  5         29  
9             binmode STDOUT, ':encoding(UTF-8)';
10             binmode STDERR, ':encoding(UTF-8)';
11 5     5   211 use feature 'signatures'; no warnings "experimental::signatures";
  5     5   10  
  5         156  
  5         27  
  5         10  
  5         184  
12 5     5   27 use Carp::Always;
  5         10  
  5         123  
13 5     5   27 use Try::Tiny;
  5         11  
  5         278  
14 5     5   31 use Scalar::Util qw(blessed);
  5         7  
  5         340  
15              
16             our $VERSION = '0.07';
17              
18             =head1 Pootle::Client
19              
20             Client to talk with Pootle API v1 nicely
21              
22             See.
23             https://pootle.readthedocs.io/en/stable-2.5.1/api/index.html
24             for more information about the API resources/data_structures this Client returns.
25              
26             Eg. https://pootle.readthedocs.io/en/stable-2.5.1/api/api_project.html#get-a-project
27             maps to Pootle::Resource::Project locally.
28              
29             =head1 REQUIRES
30              
31             Perl 5.20 or newer with support for subroutine signatures
32              
33             =head2 Caches
34              
35             See L, for how the simple caching system works to spare the Pootle-Server from abuse
36              
37             =head2 Logger
38              
39             See L, for how to change Pootle::Client chattiness
40              
41             =head1 Synopsis
42              
43             my $papi = Pootle::Client->new({
44             baseUrl => 'http://translate.example.com',
45             credentials => 'username:password' || 'credentials.txt'}
46             );
47             my $languages = $papi->languages();
48             my $trnsProjs = $papi->searchTranslationProjects(
49             $languages,
50             Pootle::Filters->new({fullname => qr/^Project name/})
51             );
52              
53              
54             =cut
55              
56 5     5   1484 use Params::Validate qw(:all);
  5         22332  
  5         944  
57              
58 5     5   1489 use Pootle::Agent;
  0            
  0            
59             use Pootle::Cache;
60             use Pootle::Filters;
61             use Pootle::Resource::Language;
62             use Pootle::Resource::TranslationProject;
63             use Pootle::Resource::Store;
64             use Pootle::Resource::Unit;
65             use Pootle::Resource::Project;
66              
67             use Pootle::Logger;
68             my $l = bless({}, 'Pootle::Logger'); #Lazy load package logger this way to avoid circular dependency issues with logger includes from many packages
69              
70             =head2 new($params)
71              
72             Instantiates a new Pootle::Client
73              
74             $params HASHRef of parameters {
75             baseUrl => 'http://translate.pootle.url',
76             credentials => 'usename:password' ||
77             'credentials.file.containing.credentials.txt',
78             cacheFile => 'pootle-client.cache',
79             }
80              
81             @returns Pootle::Client
82              
83             =cut
84              
85             sub new($class, @params) {
86             $l->debug("Initializing '$class' with parameters: ".$l->flatten(@params)) if $l->is_debug();
87             my %self = validate(@params, {
88             baseUrl => 1, #Passed to Pootle::Agent
89             credentials => 1, #Passed to Pootle::Agent
90             cacheFile => 0, #Passed to Pootle::Cache
91             });
92             my $s = bless(\%self, $class);
93              
94             $s->{agent} = new Pootle::Agent({baseUrl => $s->{baseUrl}, credentials => $s->{credentials}});
95             $s->{cache} = new Pootle::Cache({cacheFile => $s->{cacheFile}});
96              
97             return $s;
98             }
99              
100             =head1 ACCESSING THE POOTLE API
101              
102             This Client transparently handles authentication based on the credentials supplied.
103             Use the following methods to make API requests.
104              
105             =item language
106              
107             @PARAM1 String, API endpoint to get the resource, eg. /api/v1/languages/124/
108             @RETURNS L
109              
110             =cut
111              
112             sub language($s, $endpoint) {
113             $l->info("getting language $endpoint");
114             my $contentHash = $s->a->request('get', $endpoint, {});
115             return new Pootle::Resource::Language($contentHash);
116             }
117              
118             =item languages
119              
120             @RETURNS ARRAYRef of L,
121             all languages in the Pootle database
122             @CACHED Transiently
123              
124             =cut
125              
126             sub languages($s) {
127             if (my $cached = $s->c->tGet('/api/v1/languages/')) {
128             $l->info("getting languages from cache");
129             return $cached;
130             }
131             $l->info("getting languages");
132              
133             my $contentHash = $s->a->request('get', '/api/v1/languages/', {});
134             my $objs = $contentHash->{objects};
135             for (my $i=0 ; $i<@$objs ; $i++) {
136             $objs->[$i] = new Pootle::Resource::Language($objs->[$i]);
137             }
138             $s->c->tSet('/api/v1/languages/', $objs);
139             return $objs;
140             }
141              
142             =item findLanguages
143              
144             Uses the API to find all languages starting with the given country code
145              
146             @PARAM1 L
147             @RETURNS ARRAYRef of L,
148             all languages starting with the given code.
149             @CACHED Persistently
150              
151             =cut
152              
153             sub findLanguages($s, $filters) {
154             if (my $cached = $s->c->pGet('findLanguages '.$l->flatten($filters))) {
155             $l->info("finding languages from cache");
156             return $cached if $cached;
157             }
158             $l->info("finding languages");
159              
160             my $objects = $filters->filter( $s->languages() );
161              
162             $s->c->pSet('findLanguages '.$l->flatten($filters), $objects);
163             return $objects;
164             }
165              
166             =item translationProject
167              
168             @PARAM1 String, API endpoint to get the resource,
169             eg. /api/v1/translation-projects/124/
170             @RETURNS L
171              
172             =cut
173              
174             sub translationProject($s, $endpoint) {
175             $l->info("getting translation project $endpoint");
176             my $contentHash = $s->a->request('get', $endpoint, {});
177             return new Pootle::Resource::TranslationProject($contentHash);
178             }
179              
180             =item translationProjects
181              
182             @UNIMPLEMENTED
183              
184             This endpoint is unimplemented in the Pootle-Client. Maybe some day it becomes enabled. If it does, this should work out-of-box.
185              
186             It might be better to use searchTranslationProjects() instead, since this API call can be really invasive to the Pootle-server.
187             Really depends on how many translation projects you are after.
188              
189             @RETURNS ARRAYRef of L,
190             all translation projects in the Pootle database
191             @CACHED Transiently
192             @THROWS L
193              
194             =cut
195              
196             sub translationProjects($s) {
197             if (my $cache = $s->c->tGet('/api/v1/translation-projects/')) {
198             $l->info("getting translation projects from cache");
199             return $cache;
200             }
201             $l->info("getting translation projects");
202              
203             my $contentHash = $s->a->request('get', '/api/v1/translation-projects/', {});
204             my $objs = $contentHash->{objects};
205             for (my $i=0 ; $i<@$objs ; $i++) {
206             $objs->[$i] = new Pootle::Resource::TranslationProject($objs->[$i]);
207             }
208             $s->c->tSet('/api/v1/translation-projects/', $objs);
209             return $objs;
210             }
211              
212             =item findTranslationProjects
213              
214             @UNIMPLEMENTED
215              
216             This endpoint is unimplemented in the Pootle-Client. Maybe some day it becomes enabled. If it does, this should work out-of-box.
217              
218             Uses the API to find all translation projects matching the given search expressions
219              
220             @PARAM1 L, Used to select the desired objects
221             @RETURNS ARRAYRef of L.
222             All matched translation projects.
223             @CACHED Persistently
224             @THROWS L
225              
226             =cut
227              
228             sub findTranslationProjects($s, $filters) {
229             if (my $cached = $s->c->pGet('findTranslationProjects '.$l->flatten($filters))) {
230             $l->info("finding translation projects from cache");
231             return $cached;
232             }
233             $l->info("finding translation projects");
234              
235             my $objects = $filters->filter( $s->translationProjects() );
236              
237             $s->c->pSet('findTranslationProjects '.$l->flatten($filters), $objects);
238             return $objects;
239             }
240              
241             =item searchTranslationProjects
242              
243             @PARAM1 L, Filters to pick desired languages
244             or
245             ARRAYRef of L
246             @PARAM2 L, Filters to pick desired projects
247             or
248             ARRAYRef of L
249             @RETURNS ARRAYRef of L,
250             matching the given languages and projects
251             @CACHED Persistently
252              
253             =cut
254              
255             sub searchTranslationProjects($s, $languageFilters, $projectFilters) {
256             if (my $cached = $s->c->pGet('searchTranslationProjects '.$l->flatten($languageFilters).$l->flatten($projectFilters))) {
257             $l->info("searching translation projects from cache");
258             return $cached;
259             }
260             $l->info("searching translation projects");
261              
262             my $languages;
263             if (ref($languageFilters) eq 'ARRAY' && blessed($languageFilters->[0]) && $languageFilters->[0]->isa('Pootle::Resource::Language')) {
264             $languages = $languageFilters;
265             }
266             else {
267             $languages = $s->findLanguages($languageFilters);
268             }
269              
270             my $projects;
271             if (ref($projectFilters) eq 'ARRAY' && blessed($projectFilters->[0]) && $projectFilters->[0]->isa('Pootle::Resource::Project')) {
272             $projects = $projectFilters;
273             }
274             else {
275             $projects = $s->findProjects($projectFilters);
276             }
277              
278             my $sharedTranslationProjectsEndpoints = Pootle::Filters->new()->intersect($languages, $projects, 'translation_projects', 'translation_projects');
279             my @translationProjects;
280             foreach my $intersection (@$sharedTranslationProjectsEndpoints) {
281             push(@translationProjects, $s->translationProject($intersection->attributeValue));
282             }
283              
284             $s->c->pSet('searchTranslationProjects '.$l->flatten($languageFilters).$l->flatten($projectFilters), \@translationProjects);
285             return \@translationProjects;
286             }
287              
288             =item store
289              
290             @PARAM1 String, API endpoint to get the resource, eg. /api/v1/stores/77/
291             @RETURNS L
292              
293             =cut
294              
295             sub store($s, $endpoint) {
296             $l->info("getting store $endpoint");
297             my $contentHash = $s->a->request('get', $endpoint, {});
298             return new Pootle::Resource::Store($contentHash);
299             }
300              
301             =item searchStores
302              
303             @PARAM1 L, Filters to pick desired languages
304             or
305             ARRAYRef of L
306             @PARAM2 L, Filters to pick desired projects
307             or
308             ARRAYRef of L
309             @RETURNS ARRAYRef of L,
310             matching the given languages and projects
311              
312             =cut
313              
314             sub searchStores($s, $languageFilters, $projectFilters) {
315             if (my $cached = $s->c->pGet('searchStores '.$l->flatten($languageFilters).$l->flatten($projectFilters))) {
316             $l->info("searching stores from cache");
317             return $cached;
318             }
319             $l->info("searching stores");
320              
321             my $transProjs = $s->searchTranslationProjects($languageFilters, $projectFilters);
322              
323             my @stores;
324             foreach my $translationProject (@$transProjs) {
325             foreach my $storeUri (@{$translationProject->stores}) {
326             push(@stores, $s->store($storeUri));
327             }
328             }
329              
330             $s->c->pSet('searchStores '.$l->flatten($languageFilters).$l->flatten($projectFilters), \@stores);
331             return \@stores;
332             }
333              
334             =item project
335              
336             @PARAM1 String, API endpoint to get the project, eg. /api/v1/projects/124/
337             @RETURNS L
338              
339             =cut
340              
341             sub project($s, $endpoint) {
342             $l->info("getting project $endpoint");
343             my $contentHash = $s->a->request('get', $endpoint, {});
344             return new Pootle::Resource::Project($contentHash);
345             }
346              
347             =item projects
348              
349             @RETURNS ARRAYRef of L,
350             all projects in the Pootle database
351             @CACHED Transiently
352              
353             =cut
354              
355             sub projects($s) {
356             if(my $cached = $s->c->tGet('/api/v1/projects/')) {
357             $l->info("getting projects from cache");
358             return $cached;
359             }
360             $l->info("getting projects");
361              
362             my $contentHash = $s->a->request('get', '/api/v1/projects/', {});
363             my $objs = $contentHash->{objects};
364             for (my $i=0 ; $i<@$objs ; $i++) {
365             $objs->[$i] = new Pootle::Resource::Project($objs->[$i]);
366             }
367             $s->c->tSet('/api/v1/projects/', $objs);
368             return $objs;
369             }
370              
371             =item findProjects
372              
373             Uses the API to find all projects matching the given search expressions
374              
375             @PARAM1 L, matching criteria for needed objects
376             @RETURNS ARRAYRef of L. All matched projects.
377             @CACHED Persistently
378              
379             =cut
380              
381             sub findProjects($s, $filters) {
382             if (my $cached = $s->c->pGet('findProjects '.$l->flatten($filters))) {
383             $l->info("finding projects from cache");
384             return $cached;
385             }
386             $l->info("finding projects");
387              
388             my $objects = $filters->filter( $s->projects() );
389              
390             $s->c->pSet('findProjects '.$l->flatten($filters), $objects);
391             return $objects;
392             }
393              
394             =item unit
395              
396             @PARAM1 String, API endpoint to get the resource, eg. /api/v1/units/77/
397             @RETURNS L
398              
399             =cut
400              
401             sub unit($s, $endpoint) {
402             $l->info("getting unit $endpoint");
403             my $contentHash = $s->a->request('get', $endpoint, {});
404             return new Pootle::Resource::Unit($contentHash);
405             }
406              
407              
408              
409             =head1 HELPERS
410              
411             =item flushCaches
412              
413             Flushes all caches
414              
415             =cut
416              
417             sub flushCaches($s) {
418             $s->c->flushCaches();
419             }
420              
421             =head1 ACCESSORS
422              
423             =item a
424              
425             @RETURNS L
426              
427             =cut
428              
429             sub a($s) { return $s->{agent} }
430              
431             =item c
432              
433             @RETURNS L
434              
435             =cut
436              
437             sub c($s) { return $s->{cache} }
438              
439             1;