source: LATMOS-Accounts/lib/LATMOS/Accounts/Synchro.pm @ 296

Last change on this file since 296 was 296, checked in by nanardon, 15 years ago
  • add debug messages
  • Property svn:keywords set to Id Rev
File size: 9.3 KB
Line 
1package LATMOS::Accounts::Synchro;
2
3use 5.010000;
4use strict;
5use warnings;
6use base qw(Config::IniFiles);
7use LATMOS::Accounts::Bases;
8use LATMOS::Accounts::Log;
9use LATMOS::Accounts::Utils qw(exec_command);
10
11=head1 NAME
12
13LATMOS::Accounts::Synchro - Perl extension for blah blah blah
14
15=head1 SYNOPSIS
16
17  use LATMOS::Accounts;
18  blah blah blah
19
20=head1 DESCRIPTION
21
22Stub documentation for LATMOS::Accounts, created by h2xs. It looks like the
23author of the extension was negligent enough to leave the stub
24unedited.
25
26Blah blah blah.
27
28=head1 FUNCTIONS
29
30=cut
31
32our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
33
34=head2 new($from, $to, %options)
35
36Create a new synchronisation where $from and $to are LATMOS::Accounts::Base
37based objects. $to can be an array ref of objects.
38
39=cut
40
41sub new {
42    my ($class, $from, $to, %options) = @_;
43
44    if ($options{state_file} && ! -w $options{state_file}) {
45        # don't exists, we have to create it
46        open(my $handle, '>', $options{state_file}) or return;
47        print $handle "[_default_]\n";
48        close($handle);
49    }
50
51    my $self = Config::IniFiles->new(
52        $options{state_file}
53        ? (-file => $options{state_file})
54        : (),
55    );
56
57    if ($options{state_file} && !$self->GetFileName) {
58        $self->SetFileName($options{state_file});
59    }
60   
61    $self->{from} = $from or do {
62        la_log(LA_ERR, "No database source");
63        return;
64    };
65    $self->{options} = { %options };
66
67    # allow ref and array ref of, eg
68    # to = $foo and $to = [ $foo, $bar ]
69    foreach (ref($to) eq 'ARRAY' ? @{ $to || []} : ($to)) {
70        push(@{$self->{to}}, $_);
71    }
72    bless($self, $class)
73}
74
75sub name {
76    $_[0]->{options}{name}
77}
78
79sub from {
80    my ($self) = @_;
81    return $self->{from}
82}
83
84sub to {
85    my ($self) = @_;
86    return @{$self->{to} || []};
87}
88
89=head2 load_dest
90
91Try to loaded all base, return the count of filtrered base which cannot
92be loaded
93
94=cut
95
96sub load_dest {
97    my ($self) = @_;
98    my @loaded;
99    my $unloaded = 0;
100    foreach ($self->to) {
101        if($_->load) {
102            push(@loaded, $_);
103        } else {
104            $unloaded++;
105        }
106    }
107    $self->{to} = \@loaded;
108    return $unloaded;
109}
110
111sub _traverse {
112    my ($self, $callback) = @_;
113   
114    # listing existing obj one time:
115    foreach my $otype ($self->from->list_supported_objects) {
116       
117        # If no dest support $otype, we skip
118        my @obj_dest_base = grep {
119            $_->is_supported_object($otype)
120        } $self->to or next;
121
122        # loading object list one time for all
123        # TODO optimize this, using rev of objects
124
125        foreach my $destbase (@obj_dest_base) {
126            $callback->($otype, $destbase);
127        }
128    }
129}
130
131sub _traverse_update {
132    my ($self, %options) = @_;
133    $self->_traverse(
134        sub {
135            my ($otype, $destbase) = @_;
136            my @lobjfrom = $self->from->list_objects_from_rev(
137                $otype,
138                $self->val($self->from->label, $destbase->label),
139            );
140            my @common_fields = $options{attr_cb}->($self->from, $destbase, $otype) or return;
141            my %exists = map { $_ => 1 } $destbase->list_objects($otype);
142            foreach my $uid (@lobjfrom) {
143                my $sobj = $self->from->get_object($otype, $uid);
144                if (!$self->{options}{test}) {
145                    my $res = $destbase->sync_object(
146                        $sobj,
147                        nocreate => ($self->{options}{nocreate} || $options{nocreate}),
148                    );
149                    if (defined $res) {
150                        la_log(LA_INFO,
151                            "%s::%s::%s => %s (%s)",
152                            $self->from->label, $otype, $uid,
153                            $destbase->label, $res
154                        ) if ($res);
155                    } else {
156                        la_log(LA_ERR,
157                            "Error syncing %s::%s::%s => %s",
158                            $self->from->label, $otype, $uid, $destbase->label
159                        ); 
160                    }
161                }
162            }
163        }
164    );
165}
166
167sub _traverse_delete {
168    my ($self, %options) = @_;
169    $self->_traverse(
170        sub {
171            my ($otype, $destbase) = @_;
172            my %exists = map { $_ => 1 } $destbase->list_objects($otype);
173            my %srcexists = map { $_ => 1 } $self->from->list_objects($otype);
174            foreach (keys %exists) {
175                if (!$srcexists{$_}) {
176                    if ($destbase->delete_object($otype, $_)) {
177                        print "delete " . $destbase->label . '::' . $otype . '::' . "$_\n";
178                    } else {
179                        la_log(LA_ERR, "cannot delete " . $destbase->label . '::' . $otype . '::' . "$_");
180                    }
181                }
182            }
183        }
184    );
185}
186
187sub enter_synch_mode {
188    my ($self) = @_;
189    my %state = ();
190    $state{$self->from->label} = $self->from->wexported(0);
191    foreach ($self->to) {
192        $state{$_->label} = $_->wexported(1);
193    }
194    la_log(LA_DEBUG, "Entering synch mode, old state: %s", join(', ', map {
195            "$_ => $state{$_}" } sort keys %state));
196    %state
197}
198
199sub leave_synch_mode {
200    my ($self, %state) = @_;
201    la_log(LA_DEBUG, "Leaving synch mode");
202    $self->from->wexported($state{$self->from->label});
203    foreach ($self->to) {
204        $_->wexported($state{$_->label});
205    }
206}
207
208sub sync_object {
209    my ($self, $otype, $uid) = @_;
210    my %state = $self->enter_synch_mode;
211    if (my $sobj = $self->from->get_object($otype, $uid)) {
212        foreach ($self->to) {
213            if (my $res = $_->sync_object($sobj)) {
214                la_log(LA_INFO, $_->label . " $uid ($otype) $res");
215            } else {
216                la_log(LA_ERR, "error synching $uid ($otype) to " . $_->label);
217            }
218        }
219    } else {
220        foreach ($self->to) {
221            if ($_->get_object($otype, $uid)) {
222                $_->get_object($otype, $uid) or do {
223                    la_log("Cannot sync (delete) %s %s");
224                }
225            }
226        }
227    }
228    foreach ($self->to) {
229        $_->commit;
230    }
231
232    $self->run_post_synchro;
233
234    $self->leave_synch_mode(%state);
235
236    1;
237}
238
239sub process {
240    my ($self) = @_;
241
242    $self->from->load or return;
243    # if any cannot be loaded, return,
244    # TODO we need a way to force if some still can be sync
245    $self->load_dest and return;
246   
247    my %state = $self->enter_synch_mode;
248
249    # tracking current base revision:
250    $self->{current_rev} = $self->from->current_rev;
251
252    my %delayed;
253    $self->_traverse_delete;
254    $self->_traverse_update(
255        attr_cb => sub {
256            my ($from, $to, $otype) = @_;
257            my %fields = ();
258            my %delayed = map { $_ => 1 } $to->delayed_fields($otype);
259            foreach ($from->list_canonical_fields($otype, 'r')) {
260                $delayed{$_} and next;
261                $fields{$_} ||= 0; # avoid
262                $fields{$_}++;
263            }
264            foreach ($to->list_canonical_fields($otype, 'w')) {
265                $delayed{$_} and next;
266                $fields{$_} ||= 0; # avoid
267                $fields{$_}++;
268            }
269            # field having value are in both
270            grep { $fields{$_} == 2 } keys %fields;
271        },
272    );
273    $self->_traverse_update(
274        attr_cb => sub {
275            my ($from, $to, $otype) = @_;
276            my %fields = ();
277            my %delayed = map { $_ => 1 } $to->delayed_fields($otype);
278            foreach ($from->list_canonical_fields($otype, 'r')) {
279                $delayed{$_} or next;
280                $fields{$_} ||= 0; # avoid
281                $fields{$_}++;
282            }
283            foreach ($to->list_canonical_fields($otype, 'w')) {
284                $delayed{$_} or next;
285                $fields{$_} ||= 0; # avoid
286                $fields{$_}++;
287            }
288            # field having value are in both
289            grep { $fields{$_} == 2 } keys %fields;
290        },
291        nocreate => 1,
292    ) unless($self->{options}{nocreate} || $self->{options}{test});
293
294    foreach ($self->to) {
295        la_log(LA_DEBUG, "Calling commit on %s", $_->label);
296        $_->commit or next;
297        $self->newval($self->from->label, $_->label, $self->{current_rev}) if($self->{current_rev});
298    }
299
300    $self->run_post_synchro;
301
302    $self->RewriteConfig
303        if($self->GetFileName && !($self->{options}{nocreate} || $self->{options}{test}));
304   
305    $self->leave_synch_mode(%state);
306
307    1;
308}
309
310sub run_post_synchro {
311    my ($self) = @_;
312
313    $self->{options}{post} or return 1;
314
315    la_log(LA_INFO, "Running post synchro `%s'", $self->{options}{post});
316
317    exec_command($self->{options}{post}, $self->{options});
318}
319
320
3211;
322
323__END__
324# Below is stub documentation for your module. You'd better edit it!
325
326=head1 SEE ALSO
327
328Mention other useful documentation such as the documentation of
329related modules or operating system documentation (such as man pages
330in UNIX), or any relevant external documentation such as RFCs or
331standards.
332
333If you have a mailing list set up for your module, mention it here.
334
335If you have a web site set up for your module, mention it here.
336
337=head1 AUTHOR
338
339Thauvin Olivier, E<lt>olivier.thauvin@latmosipsl.frE<gt>
340
341=head1 COPYRIGHT AND LICENSE
342
343Copyright (C) 2009 by Thauvin Olivier
344
345This library is free software; you can redistribute it and/or modify
346it under the same terms as Perl itself, either Perl version 5.10.0 or,
347at your option, any later version of Perl 5 you may have available.
348
349
350=cut
Note: See TracBrowser for help on using the repository browser.