File Coverage

lib/Smartcat/App/Command/push.pm
Criterion Covered Total %
statement 39 152 25.6
branch 0 44 0.0
condition 0 12 0.0
subroutine 13 22 59.0
pod 3 6 50.0
total 55 236 23.3


line stmt bran cond sub pod time code
1             # ABSTRACT: push translation files to Smartcat
2 2     2   1518 use strict;
  2         5  
  2         67  
3 2     2   10 use warnings;
  2         4  
  2         56  
4              
5 2     2   9 use utf8;
  2         4  
  2         14  
6 2     2   59 no utf8;
  2         5  
  2         19  
7              
8             package Smartcat::App::Command::push;
9 2     2   92 use Smartcat::App -command;
  2         4  
  2         13  
10              
11 2     2   622 use File::Basename;
  2         3  
  2         166  
12 2     2   14 use File::Spec::Functions qw(catfile catdir);
  2         5  
  2         125  
13 2     2   14 use File::Find qw(find);
  2         4  
  2         127  
14 2     2   15 use List::Util qw(first);
  2         4  
  2         161  
15              
16 2         124 use Smartcat::App::Constants qw(
17             MAX_ITERATION_WAIT_TIMEOUT
18             ITERATION_WAIT_TIMEOUT
19             DOCUMENT_DISASSEMBLING_SUCCESS_STATUS
20 2     2   14 );
  2         13  
21 2     2   23 use Smartcat::App::Utils;
  2         4  
  2         178  
22              
23 2     2   15 use Carp;
  2         3  
  2         126  
24 2     2   14 use Log::Any qw($log);
  2         5  
  2         15  
25              
26             # How many documents to delete at a time
27             # (there's a limitation on the number of the document due to
28             # the fact that all document IDs are specified in a URL,
29             # and URLs itself have length limitations).
30             my $DELETE_BATCH_SIZE = 20;
31              
32             sub opt_spec {
33 0     0 1   my ($self) = @_;
34              
35 0           my @opts = $self->SUPER::opt_spec();
36              
37 0           push @opts,
38             [ 'disassemble-algorithm-name:s' =>
39             'Optional disassemble file algorithm' ],
40             [ 'preset-disassemble-algorithm:s' =>
41             'Optional disassemble algorithm preset' ],
42             [ 'delete-not-existing' => 'Delete not existing documents' ],
43             $self->project_id_opt_spec,
44             $self->project_workdir_opt_spec,
45             $self->file_params_opt_spec,
46             $self->extract_id_from_name_opt_spec,
47             $self->external_tag_opt_spec,
48             ;
49              
50 0           return @opts;
51             }
52              
53             sub validate_args {
54 0     0 1   my ( $self, $opt, $args ) = @_;
55              
56 0           $self->SUPER::validate_args( $opt, $args );
57 0           $self->validate_project_id( $opt, $args );
58 0           $self->validate_project_workdir( $opt, $args );
59 0           $self->validate_file_params( $opt, $args );
60              
61             $self->app->{rundata}->{disassemble_algorithm_name} =
62             $opt->{disassemble_algorithm_name}
63 0 0         if defined $opt->{disassemble_algorithm_name};
64             $self->app->{rundata}->{preset_disassemble_algorithm} =
65             $opt->{preset_disassemble_algorithm}
66 0 0         if defined $opt->{preset_disassemble_algorithm};
67             $self->app->{rundata}->{delete_not_existing} =
68 0 0         defined $opt->{delete_not_existing} ? $opt->{delete_not_existing} : 0;
69             $self->app->{rundata}->{extract_id_from_name} =
70 0 0         defined $opt->{extract_id_from_name} ? $opt->{extract_id_from_name} : 0;
71             $self->app->{rundata}->{external_tag} =
72 0 0         defined $opt->{external_tag} ? $opt->{external_tag} : "source:Serge";
73             }
74              
75             sub execute {
76 0     0 1   my ( $self, $opt, $args ) = @_;
77              
78 0           my $app = $self->app;
79 0           my $rundata = $app->{rundata};
80             $log->info(
81             sprintf(
82             "Running 'push' command for project '%s' and translation files from '%s'...",
83             $rundata->{project_id},
84             $rundata->{project_workdir}
85             )
86 0           );
87              
88 0           my $project = $app->project_api->get_project;
89 0 0         $app->project_api->update_project_external_tag( $project, $rundata->{external_tag} ) if ($#{ $project->documents } >= 0);
  0            
90 0           my %documents;
91              
92 0           for ( @{ $project->documents } ) {
  0            
93             my $key = &get_document_key(
94             $_->full_path,
95             $_->target_language,
96 0           $rundata->{extract_id_from_name} );
97 0 0         $documents{$key} = [] unless defined $documents{$key};
98 0           push @{ $documents{$key} }, $_;
  0            
99             }
100              
101 0           my %ts_files;
102             find(
103             sub {
104 0     0     my $name = $File::Find::name;
105 0 0         if ($^O !~ /MSWin32/) { # assume we are on Unix if not on Windows
106 0           utf8::decode($name); # assume UTF8 filenames
107 0           utf8::decode($_);
108             }
109              
110 0 0 0       if ( -f $name
      0        
111             && !m/^\.$/
112             && m/$rundata->{filetype}$/ )
113             {
114 0           s/$rundata->{filetype}$//;
115 0           my $path = catfile( dirname($name), $_ );
116              
117             my $key = &get_ts_file_key(
118             $rundata->{project_workdir},
119             $path,
120 0           $rundata->{extract_id_from_name} );
121              
122 0           utf8::decode($key);
123 0 0         $ts_files{$key} = [] unless defined $ts_files{$key};
124 0           push @{ $ts_files{$key} }, $name;
  0            
125             }
126             },
127             $rundata->{project_workdir}
128 0           );
129              
130 0           my %stats;
131 0           $stats{$_}++ for ( keys %documents, keys %ts_files );
132              
133 0           my ( @upload, @obsolete, @update, @skip );
134             push @{
135             exists $ts_files{$_} && !$self->_check_if_files_are_empty( $ts_files{$_} )
136             ? defined $documents{$_} ? \@update : \@upload
137 0 0 0       : defined $documents{$_} ? \@obsolete : \@skip
    0          
    0          
138             },
139             $_
140 0           for ( keys %stats );
141              
142             $log->info(
143             sprintf(
144             "State:\n Upload [%d]\n %s\n Update [%d]\n %s\n Obsolete [%d]\n %s\n Skip [%d]\n %s\n",
145             scalar @upload,
146 0           join( ', ', map { "'$_'" } @upload ),
147             scalar @update,
148 0           join( ', ', map { "'$_'" } @update ),
149             scalar @obsolete,
150 0           join( ', ', map { "'$_'" } @obsolete ),
151             scalar @skip,
152 0           join( ', ', map { "'$_'" } @skip )
  0            
153             )
154             );
155              
156 0           $self->upload( $project, $ts_files{$_} ) for @upload;
157 0           $self->update( $project, $documents{$_}, $ts_files{$_} ) for @update;
158              
159 0 0         if ($rundata->{delete_not_existing}) {
160 0           my @document_ids;
161 0           push( @document_ids, map { $_->id } @{ $documents{$_} } ) for @obsolete;
  0            
  0            
162              
163             # work in batches
164 0           while (scalar(@document_ids) > 0) {
165 0           my @batch = splice(@document_ids, 0, $DELETE_BATCH_SIZE);
166 0           $self->delete( \@batch );
167             }
168             }
169              
170             $log->info(
171             sprintf(
172             "Finished 'push' command for project '%s' and translation files from '%s'.",
173             $rundata->{project_id},
174             $rundata->{project_workdir}
175             )
176 0           );
177             }
178              
179             sub delete {
180 0     0 0   my ( $self, $document_ids ) = @_;
181              
182 0           $self->app->document_api->delete_documents($document_ids);
183             }
184              
185             sub update {
186 0     0 0   my ( $self, $project, $documents, $ts_files ) = @_;
187              
188 0           my $app = $self->app;
189 0           my $api = $app->document_api;
190 0           my $rundata = $app->{rundata};
191              
192             my @target_languages =
193 0           map { &get_language_from_ts_filepath($rundata->{project_workdir}, $_) } @$ts_files;
  0            
194 0           my %doc_and_path_by_lang;
195             my @files_without_documents;
196              
197             #print Dumper $ts_files;
198 0           for (@$ts_files) {
199              
200 0           my $lang = get_language_from_ts_filepath($rundata->{project_workdir}, $_);
201 0     0     my $doc = first { $_->target_language eq $lang } @$documents;
  0            
202              
203             # p $doc;
204 0 0         if ( defined $doc ) {
205 0           $doc_and_path_by_lang{$lang} = { path => $_, doc => $doc };
206             }
207             else {
208 0           push @files_without_documents, $_;
209             }
210             }
211             my @documents_without_files =
212 0           grep { !exists $doc_and_path_by_lang{ $_->target_language } } @$documents;
  0            
213              
214             $log->warn(
215             "No files for documents:"
216             . join( ', ',
217 0 0         map { $_->name . '(' . $_->target_language . ') [' . $_->id . ']' }
  0            
218             @documents_without_files )
219             ) if @documents_without_files;
220              
221 0 0         $log->warn(
222             "No documents for files:" . join( ', ', @files_without_documents ) )
223             if @files_without_documents;
224              
225 0           for ( keys %doc_and_path_by_lang ) {
226 0           my $doc_and_path = $doc_and_path_by_lang{$_};
227            
228 0           $api->update_document( $doc_and_path->{path}, $doc_and_path->{doc}->id );
229            
230 0 0         if ( $rundata->{extract_id_from_name} ) {
231             my $file_name = get_file_name(
232             $doc_and_path->{path},
233             $rundata->{filetype},
234 0           $target_languages[0]);
235 0           my $document_name = $doc_and_path->{doc}->name;
236              
237 0 0         if ($file_name ne $document_name) {
238             $log->info(
239             sprintf(
240             "Renaming document '%s' from '%s' to '%s'.",
241             $doc_and_path->{doc}->id,
242 0           $document_name,
243             $file_name
244             )
245             );
246 0           $api->rename_document( $doc_and_path->{doc}->id, $file_name );
247             }
248             }
249             }
250             }
251              
252             sub _check_if_files_are_empty {
253 0     0     my ($self, $filepaths) = @_;
254              
255 0           my $rundata = $self->app->{rundata};
256              
257 0 0         if ($rundata->{filetype} eq ".po") {
258 0           return are_po_files_empty($filepaths);
259             }
260              
261 0           return 0;
262             }
263              
264             sub upload {
265 0     0 0   my ( $self, $project, $ts_files ) = @_;
266              
267 0           my $rundata = $self->app->{rundata};
268             my @target_languages =
269 0           map { &get_language_from_ts_filepath($rundata->{project_workdir}, $_) } @$ts_files;
  0            
270 0           my @project_target_languages = @{ $project->target_languages };
  0            
271              
272 0 0 0       croak("Conflict: one target language to one file expected.")
273             unless @$ts_files == 1 && @target_languages == 1;
274 0           my $path = shift @$ts_files;
275              
276             my $filename = prepare_document_name( $rundata->{project_workdir}, $path, $rundata->{filetype},
277 0           $target_languages[0] );
278              
279 0           my $external_id;
280 0 0         if ( $rundata->{extract_id_from_name}){
281 0           $external_id = &get_file_id( $path );
282             }
283              
284 0           my $documents = $self->app->project_api->upload_file( $path, $filename, $external_id,
285             \@target_languages );
286             $log->info( "Created documents ids:\n "
287 0           . join( ', ', map { $_->id } @$documents ) );
  0            
288             }
289              
290             1;