/[cvs]/nfo/perl/libs/Data/Transfer/Sync.pm
ViewVC logotype

Contents of /nfo/perl/libs/Data/Transfer/Sync.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.6 - (show annotations)
Fri Dec 6 04:49:10 2002 UTC (21 years, 7 months ago) by jonen
Branch: MAIN
Changes since 1.5: +7 -1 lines
+ disabled output-puffer here

1 ## $Id: Sync.pm,v 1.5 2002/12/05 08:06:05 joko Exp $
2 ##
3 ## Copyright (c) 2002 Andreas Motl <andreas.motl@ilo.de>
4 ##
5 ## See COPYRIGHT section in pod text below for usage and distribution rights.
6 ##
7 ## ----------------------------------------------------------------------------------------
8 ## $Log: Sync.pm,v $
9 ## Revision 1.5 2002/12/05 08:06:05 joko
10 ## + bugfix with determining empty fields (Null) with DBD::CSV
11 ## + debugging
12 ## + updated comments
13 ##
14 ## Revision 1.4 2002/12/03 15:54:07 joko
15 ## + {import}-flag is now {prepare}-flag
16 ##
17 ## Revision 1.3 2002/12/01 22:26:59 joko
18 ## + minor cosmetics for logging
19 ##
20 ## Revision 1.2 2002/12/01 04:43:25 joko
21 ## + mapping deatil entries may now be either an ARRAY or a HASH
22 ## + erase flag is used now (for export-operations)
23 ## + expressions to refer to values inside deep nested structures
24 ## - removed old mappingV2-code
25 ## + cosmetics
26 ## + sub _erase_all
27 ##
28 ## Revision 1.1 2002/11/29 04:45:50 joko
29 ## + initial check in
30 ##
31 ## Revision 1.1 2002/10/10 03:44:21 cvsjoko
32 ## + new
33 ## ----------------------------------------------------------------------------------------
34
35
36 package Data::Transfer::Sync;
37
38 use strict;
39 use warnings;
40
41 use Data::Dumper;
42 use misc::HashExt;
43 use libp qw( md5_base64 );
44 use libdb qw( quotesql hash2Sql );
45 use Data::Transform::Deep qw( hash2object refexpr2perlref );
46 use Data::Compare::Struct qw( getDifference isEmpty );
47
48 # get logger instance
49 my $logger = Log::Dispatch::Config->instance;
50
51 $| = 1;
52
53 sub new {
54 my $invocant = shift;
55 my $class = ref($invocant) || $invocant;
56 my $self = { @_ };
57 $logger->debug( __PACKAGE__ . "->new(@_)" );
58 bless $self, $class;
59 $self->_init();
60 return $self;
61 }
62
63
64 sub _init {
65 my $self = shift;
66
67 # build new container if necessary
68 $self->{container} = Data::Storage::Container->new() if !$self->{container};
69
70 # add storages to container (optional)
71 foreach (keys %{$self->{storages}}) {
72 $self->{container}->addStorage($_, $self->{storages}->{$_});
73 }
74
75 # tag storages with id-authority and checksum-provider information
76 # TODO: better store tag inside metadata to hold bits together!
77 map { $self->{container}->{storage}->{$_}->{isIdentAuthority} = 1 } @{$self->{id_authorities}};
78 map { $self->{container}->{storage}->{$_}->{isChecksumAuthority} = 1; } @{$self->{checksum_authorities}};
79 map { $self->{container}->{storage}->{$_}->{isWriteProtected} = 1; } @{$self->{write_protected}};
80
81 }
82
83
84 # TODO: some feature to show off the progress of synchronization (cur/max * 100)
85 sub syncNodes {
86
87 my $self = shift;
88 my $args = shift;
89
90 # remember arguments through the whole processing
91 $self->{args} = $args;
92
93 $logger->debug( __PACKAGE__ . "->syncNodes: starting" );
94
95 # hash to hold and/or fill in metadata required for the processing
96 $self->{meta} = {};
97
98 # hash to sum up results
99 my $direction_arrow = '';
100
101 # detect synchronization method to determine which optical symbol (directed arrow) to use
102 if (lc $self->{args}->{direction} eq 'push') {
103 $direction_arrow = '->';
104 } elsif (lc $self->{args}->{direction} eq 'pull') {
105 $direction_arrow = '<-';
106 } elsif (lc $self->{args}->{direction} eq 'full') {
107 $direction_arrow = '<->';
108 } else {
109 }
110
111 # decompose identifiers for each partner
112 # TODO: take this list from already established/given metadata
113 foreach ('source', 'target') {
114
115 # get/set metadata for further processing
116
117 # Partner and Node (e.g.: "L:Country" or "R:countries.csv")
118 if (my $item = $self->{args}->{$_}) {
119 my @item = split(':', $item);
120 $self->{meta}->{$_}->{dbkey} = $item[0];
121 $self->{meta}->{$_}->{node} = $item[1];
122 }
123
124 # Filter
125 if (my $item_filter = $self->{args}->{$_ . '_filter'}) {
126 $self->{meta}->{$_}->{filter} = $item_filter;
127 }
128
129 # IdentProvider
130 if (my $item_ident = $self->{args}->{$_ . '_ident'}) {
131 my @item_ident = split(':', $item_ident);
132 $self->{meta}->{$_}->{IdentProvider} = { method => $item_ident[0], arg => $item_ident[1] };
133 }
134
135 # TODO: ChecksumProvider
136
137 # exclude properties/subnodes
138 if (my $item_exclude = $self->{args}->{$_ . '_exclude'}) {
139 $self->{meta}->{$_}->{subnodes_exclude} = $item_exclude;
140 }
141
142 # TypeProvider
143 if (my $item_type = $self->{args}->{$_ . '_type'}) {
144 my @item_type = split(':', $item_type);
145 $self->{meta}->{$_}->{TypeProvider} = { method => $item_type[0], arg => $item_type[1] };
146 }
147
148 # Callbacks - writers (will be triggered _before_ writing to target)
149 if (my $item_writers = $self->{args}->{$_ . '_callbacks_write'}) {
150 my $descent = $_; # this is important since the following code inside the map wants to use its own context variables
151 map { $self->{meta}->{$descent}->{Callback}->{write}->{$_}++; } @$item_writers;
152 }
153
154 # Callbacks - readers (will be triggered _after_ reading from source)
155 if (my $item_readers = $self->{args}->{$_ . '_callbacks_read'}) {
156 my $descent = $_;
157 map { $self->{meta}->{$descent}->{Callback}->{read}->{$_}++; } @$item_readers;
158 }
159
160 # resolve storage objects
161 #$self->{$_} = $self->{container}->{storage}->{$self->{meta}->{$_}->{dbkey}};
162 # relink references to metainfo
163 $self->{meta}->{$_}->{storage} = $self->{container}->{storage}->{$self->{meta}->{$_}->{dbkey}};
164 #print "iiiiisprov: ", Dumper($self->{meta}->{$_}->{storage}), "\n";
165 }
166
167 $logger->info( __PACKAGE__ . "->syncNodes: source=$self->{meta}->{source}->{dbkey}/$self->{meta}->{source}->{node} $direction_arrow target=$self->{meta}->{target}->{dbkey}/$self->{meta}->{target}->{node}" );
168
169 # build mapping
170 # incoming: and Array of node map entries (Array or Hash) - e.g.
171 # [ 'source:item_name' => 'target:class_val' ]
172 # { source => 'event->startDateTime', target => 'begindate' }
173 foreach (@{$self->{args}->{mapping}}) {
174 if (ref $_ eq 'ARRAY') {
175 my @entry1 = split(':', $_->[0]);
176 my @entry2 = split(':', $_->[1]);
177 my $descent = [];
178 my $node = [];
179 $descent->[0] = $entry1[0];
180 $descent->[1] = $entry2[0];
181 $node->[0] = $entry1[1];
182 $node->[1] = $entry2[1];
183 push @{$self->{meta}->{$descent->[0]}->{childnodes}}, $node->[0];
184 push @{$self->{meta}->{$descent->[1]}->{childnodes}}, $node->[1];
185 } elsif (ref $_ eq 'HASH') {
186 foreach my $entry_key (keys %$_) {
187 my $entry_val = $_->{$entry_key};
188 push @{$self->{meta}->{$entry_key}->{childnodes}}, $entry_val;
189 }
190 }
191
192 }
193
194 # check partners/nodes: does partner exist / is node available?
195 foreach my $partner (keys %{$self->{meta}}) {
196 next if $self->{meta}->{$partner}->{storage}->{locator}->{type} eq 'DBI'; # for DBD::CSV - re-enable for others
197 my $node = $self->{meta}->{$partner}->{node};
198 if (!$self->{meta}->{$partner}->{storage}->existsChildNode($node)) {
199 $logger->critical( __PACKAGE__ . "->syncNodes: Could not reach \"$node\" at \"$partner\"." );
200 return;
201 }
202 }
203
204 # TODO:
205 # + if action == PUSH: start processing
206 # -+ if action == PULL: swap metadata and start processing
207 # - if action == FULL: start processing, then swap metadata and (re-)start processing
208
209 #print Dumper($self->{args});
210
211 # manipulate metainfo according to direction of synchronization
212 if (lc $self->{args}->{direction} eq 'push') {
213 # just do it ...
214 } elsif (lc $self->{args}->{direction} eq 'pull') {
215 #print "=======SWAP", "\n";
216 # swap
217 ($self->{meta}->{source}, $self->{meta}->{target}) =
218 ($self->{meta}->{target}, $self->{meta}->{source});
219 } elsif (lc $self->{args}->{direction} eq 'full') {
220 } else {
221 }
222
223 # import flag means: prepare the source node to be syncable
224 # this is useful if there are e.g. no "ident" or "checksum" columns yet inside a DBI like (row-based) storage
225 if ($self->{args}->{prepare}) {
226 $self->_prepareNode_MetaProperties('source');
227 $self->_prepareNode_DummyIdent('source');
228 #return;
229 #$self->_erase_all($opts->{source_node});
230 }
231
232 # erase flag means: erase the target
233 #if ($opts->{erase}) {
234 if ($self->{args}->{erase}) {
235 # TODO: move this method to the scope of the synchronization core and wrap it around different handlers
236 #print "ERASE", "\n";
237 $self->_erase_all('target');
238 }
239
240 $self->_syncNodes();
241
242 }
243
244
245 # TODO: abstract the hardwired use of "source" and "target" in here somehow - hmmmm....... /(="§/%???
246 sub _syncNodes {
247
248 my $self = shift;
249
250 my $tc = OneLineDumpHash->new( {} );
251 my $results;
252
253 # set of objects is already in $self->{args}
254 # TODO: make independent of the terminology "object..."
255 $results = $self->{args}->{objectSet} if $self->{args}->{objectSet};
256
257 # apply filter
258 if (my $filter = $self->{meta}->{source}->{filter}) {
259 #print Dumper($filter);
260 #exit;
261 $results ||= $self->_getNodeList('source', $filter);
262 }
263
264 # get reference to node list from convenient method provided by CORE-HANDLE
265 #$results ||= $self->{source}->getListUnfiltered($self->{meta}->{source}->{node});
266 #$results ||= $self->{meta}->{source}->{storage}->getListUnfiltered($self->{meta}->{source}->{node});
267 $results ||= $self->_getNodeList('source');
268
269 # checkpoint: do we actually have a list to iterate through?
270 if (!$results || !@{$results}) {
271 $logger->notice( __PACKAGE__ . "->syncNodes: No nodes to synchronize." );
272 return;
273 }
274
275 # dereference
276 my @results = @{$results};
277
278 # iterate through set
279 foreach my $source_node_real (@results) {
280
281 $tc->{total}++;
282
283 #print "======================== iter", "\n";
284
285 # clone object (in case we have to modify it here)
286 # TODO:
287 # - is a "deep_copy" needed here if occouring modifications take place?
288 # - puuhhhh, i guess a deep_copy would destroy tangram mechanisms?
289 # - after all, just take care for now that this object doesn't get updated!
290 my $source_node = $source_node_real;
291
292 # modify entry - handle new style callbacks (the readers)
293 #print Dumper($source_node);
294 #exit;
295
296 my $descent = 'source';
297
298 # handle callbacks right now while scanning them (asymmetric to the writers)
299 my $map_callbacks = {};
300 if (my $callbacks = $self->{meta}->{$descent}->{Callback}) {
301
302 my $error = 0;
303
304 foreach my $node (keys %{$callbacks->{read}}) {
305
306 my $object = $source_node;
307 my $value; # = $source_node->{$node};
308
309 # ------------ half-redundant: make $self->callCallback($object, $value, $opts)
310 my $perl_callback = $self->{meta}->{$descent}->{node} . '::' . $node . '_read';
311 my $evalstring = 'return ' . $perl_callback . '( { object => $object, property => $node, value => $value, storage => $self->{meta}->{$descent}->{storage} } );';
312 #print $evalstring, "\n"; exit;
313 my $cb_result = eval($evalstring);
314 if ($@) {
315 die $@;
316 $error = 1;
317 print $@, "\n";
318 }
319 # ------------ half-redundant: make $self->callCallback($object, $value, $opts)
320
321 $source_node->{$node} = $cb_result;
322
323 }
324
325 }
326
327 #print Dumper($source_node);
328
329 # exclude defined fields (simply delete from object)
330 map { delete $source_node->{$_} } @{$self->{meta}->{source}->{subnodes_exclude}};
331
332 # here we accumulate information about the status of the current node (payload/row/object/item/entry)
333 $self->{node} = {};
334 $self->{node}->{source}->{payload} = $source_node;
335
336 #print "res - ident", "\n";
337
338 # determine ident of entry
339 my $identOK = $self->_resolveNodeIdent('source');
340 #if (!$identOK && lc $self->{args}->{direction} ne 'import') {
341 if (!$identOK) {
342 #print Dumper($self->{meta}->{source});
343 $logger->critical( __PACKAGE__ . "->syncNodes: No ident found in source node \"$self->{meta}->{source}->{node}\", try to \"prepare\" this node first?" );
344 return;
345 }
346
347 #print "statload", "\n";
348 #print "ident: ", $self->{node}->{source}->{ident}, "\n";
349 #print Dumper($self->{node});
350
351 my $statOK = $self->_statloadNode('target', $self->{node}->{source}->{ident});
352
353 #print Dumper($self->{node});
354
355 # mark node as new either if there's no ident or if stat/load failed
356 if (!$statOK) {
357 $self->{node}->{status}->{new} = 1;
358 print "n" if $self->{verbose};
359 }
360
361 #print "checksum", "\n";
362
363 # determine status of entry by synchronization method
364 if ( (lc $self->{args}->{method} eq 'checksum') ) {
365 #if ( $statOK && (lc $self->{args}->{method} eq 'checksum') ) {
366 #if ( !$self->{node}->{status}->{new} && (lc $self->{args}->{method} eq 'checksum') ) {
367
368 # TODO:
369 # is this really worth a "critical"???
370 # no - it should just be a debug appendix i believe
371
372 #print "readcs", "\n";
373
374 # calculate checksum of source node
375 #$self->_calcChecksum('source');
376 if (!$self->_readChecksum('source')) {
377 $logger->critical( __PACKAGE__ . "->_readChecksum: Could not find \"source\" entry with ident=\"$self->{node}->{source}->{ident}\"" );
378 $tc->{skip}++;
379 print "s" if $self->{verbose};
380 next;
381 }
382
383 # get checksum from synchronization target
384 $self->_readChecksum('target');
385 #if (!$self->_readChecksum('target')) {
386 # $logger->critical( __PACKAGE__ . "->_readChecksum: Could not find \"target\" entry with ident=\"$self->{node}->{source}->{ident}\"" );
387 # next;
388 #}
389
390 # pre flight check: do we actually have a checksum provided?
391 #if (!$self->{node}->{source}->{checksum}) {
392 # print "Source checksum for entry with ident \"$self->{node}->{source}->{ident}\" could not be calculated, maybe it's missing?.", "\n";
393 # return;
394 #}
395
396 # determine if entry is "new" or "dirty"
397 # after all, this seems to be the point where the hammer falls.....
398 print "c" if $self->{verbose};
399 $self->{node}->{status}->{new} = !$self->{node}->{target}->{checksum};
400 if (!$self->{node}->{status}->{new}) {
401 $self->{node}->{status}->{dirty} =
402 $self->{node}->{status}->{new} ||
403 (!$self->{node}->{source}->{checksum} || !$self->{node}->{target}->{checksum}) ||
404 ($self->{node}->{source}->{checksum} ne $self->{node}->{target}->{checksum}) ||
405 $self->{args}->{force};
406 }
407
408 }
409
410 # first reaction on entry-status: continue with next entry if the current is already "in sync"
411 if (!$self->{node}->{status}->{new} && !$self->{node}->{status}->{dirty}) {
412 $tc->{in_sync}++;
413 next;
414 }
415
416 # build map to actually transfer the data from source to target
417 $self->_buildMap();
418
419
420 #print Dumper($self->{node}); exit;
421
422 #print "attempt", "\n";
423
424 # additional (new) checks for feature "write-protection"
425 if ($self->{meta}->{target}->{storage}->{isWriteProtected}) {
426 $tc->{attempt_transfer}++;
427 print "\n" if $self->{verbose};
428 $logger->notice( __PACKAGE__ . "->syncNodes: Target is write-protected. Will not insert or modify node. " .
429 "(Ident: $self->{node}->{source}->{ident} " . "Dump:\n" . Dumper($self->{node}->{source}->{payload}) . ")" );
430 print "\n" if $self->{verbose};
431 $tc->{skip}++;
432 next;
433 }
434
435 # transfer contents of map to target
436 if ($self->{node}->{status}->{new}) {
437 $tc->{attempt_new}++;
438 $self->_doTransferToTarget('insert');
439 # asymmetry: refetch node from target to re-calculate new ident and checksum (TODO: is IdentAuthority of relevance here?)
440 #print Dumper($self->{node});
441 $self->_statloadNode('target', $self->{node}->{target}->{ident}, 1);
442 $self->_readChecksum('target');
443
444 } elsif ($self->{node}->{status}->{dirty}) {
445 $tc->{attempt_modify}++;
446 # asymmetry: get ident before updating (TODO: is IdentAuthority of relevance here?)
447 $self->{node}->{target}->{ident} = $self->{node}->{map}->{$self->{meta}->{target}->{IdentProvider}->{arg}};
448 $self->_doTransferToTarget('update');
449 $self->_readChecksum('target');
450 }
451
452 if ($self->{node}->{status}->{ok}) {
453 $tc->{ok}++;
454 print "t" if $self->{verbose};
455 }
456
457 if ($self->{node}->{status}->{error}) {
458 $tc->{error}++;
459 push( @{$tc->{error_per_row}}, $self->{node}->{status}->{error} );
460 print "e" if $self->{verbose};
461 }
462
463 # change ident in source (take from target), if transfer was ok and target is an IdentAuthority
464 # this is (for now) called a "retransmit" indicated by a "r"-character when verbosing
465 if ($self->{node}->{status}->{ok} && $self->{meta}->{target}->{storage}->{isIdentAuthority}) {
466 print "r" if $self->{verbose};
467 #print Dumper($self->{meta});
468 #print Dumper($self->{node});
469 #exit;
470 $self->_doModifySource_IdentChecksum($self->{node}->{target}->{ident});
471 }
472
473 print ":" if $self->{verbose};
474
475 }
476
477 print "\n" if $self->{verbose};
478
479 # build user-message from some stats
480 my $msg = "statistics: $tc";
481
482 if ($tc->{error_per_row}) {
483 $msg .= "\n";
484 $msg .= "errors from \"error_per_row\":" . "\n";
485 $msg .= Dumper($tc->{error_per_row});
486 }
487
488 # todo!!!
489 #sysevent( { usermsg => $msg, level => $level }, $taskEvent );
490 $logger->info( __PACKAGE__ . "->syncNodes: $msg" );
491
492 return $tc;
493
494 }
495
496
497 sub _dumpCompact {
498 my $self = shift;
499
500 #my $vars = \@_;
501 my @data = ();
502
503 my $count = 0;
504 foreach (@_) {
505 my $item = {};
506 foreach my $key (keys %$_) {
507 my $val = $_->{$key};
508
509 #print Dumper($val);
510
511 if (ref $val eq 'Set::Object') {
512 #print "========================= SET", "\n";
513 #print Dumper($val);
514 #print Dumper($val->members());
515 #$val = $val->members();
516 #$vars->[$count]->{$key} = $val->members() if $val->can("members");
517 #$item->{$key} = $val->members() if $val->can("members");
518 $item->{$key} = $val->members();
519 #print Dumper($vars->[$count]->{$key});
520
521 } else {
522 $item->{$key} = $val;
523 }
524
525 }
526 push @data, $item;
527 $count++;
528 }
529
530 #print "Dump:", Dumper(@data), "\n";
531
532 $Data::Dumper::Indent = 0;
533 my $result = Dumper(@data);
534 $Data::Dumper::Indent = 2;
535 return $result;
536
537 }
538
539
540 sub _calcChecksum {
541
542 my $self = shift;
543 my $descent = shift;
544 my $specifier = shift;
545
546 # calculate checksum for current object
547 my $ident = $self->{node}->{$descent}->{ident};
548
549 # build dump of this node
550 my $payload = $self->{node}->{$descent}->{payload};
551 #my $dump = $ident . "\n" . $item->quickdump();
552 #my $dump = $ident . "\n" . Dumper($item);
553 my $dump = $ident . "\n" . $self->_dumpCompact($payload);
554
555 # TODO: $logger->dump( ... );
556 #$logger->debug( __PACKAGE__ . ": " . $dump );
557 #$logger->dump( __PACKAGE__ . ": " . $dump );
558
559 # calculate checksum from dump
560 # md5-based fingerprint, base64 encoded (from Digest::MD5)
561 #my $checksum_cur = md5_base64($objdump) . '==';
562 # 32-bit integer "hash" value (maybe faster?) (from DBI)
563 $self->{node}->{$descent}->{checksum} = DBI::hash($dump, 1);
564
565 # signal good
566 return 1;
567
568 }
569
570
571 sub _readChecksum {
572 my $self = shift;
573
574 my $descent = shift;
575
576 #print "getcheck:", "\n"; print Dumper($self->{node}->{$descent});
577
578 if (!$self->{node}->{$descent}) {
579 # signal checksum bad
580 return;
581 }
582
583 # get checksum for current entry
584 # TODO: don't have the checksum column/property hardcoded as "cs" here, make this configurable somehow
585
586 if ($self->{meta}->{$descent}->{storage}->{isChecksumAuthority}) {
587 #$self->{node}->{$descent}->{checksum} = $entry->{cs};
588 #$self->{node}->{$descent}->{checksum} = $self->_calcChecksum($descent); # $entry->{cs};
589 #print "descent: $descent", "\n";
590 $self->_calcChecksum($descent);
591 #print "checksum: ", $self->{node}->{$descent}->{checksum}, "\n";
592 } else {
593
594 #$self->{node}->{$descent}->{checksum} = $entry->{cs};
595 $self->{node}->{$descent}->{checksum} = $self->{node}->{$descent}->{payload}->{cs};
596 }
597
598 # signal checksum good
599 return 1;
600
601 }
602
603
604 sub _buildMap {
605
606 my $self = shift;
607
608 # field-structure for building sql
609 # mapping of sql-fieldnames to object-attributes
610 $self->{node}->{map} = {};
611
612 # manually set ...
613 # ... object-id
614 $self->{node}->{map}->{$self->{meta}->{target}->{IdentProvider}->{arg}} = $self->{node}->{source}->{ident};
615 # ... checksum
616 $self->{node}->{map}->{cs} = $self->{node}->{source}->{checksum};
617
618 #print "sqlmap: ", Dumper($self->{node}->{map}), "\n";
619
620 # for transferring flat structures via simple (1:1) mapping
621 # TODO: diff per property / property value
622
623 if ($self->{args}->{mapping}) {
624 # apply mapping from $self->{args}->{mapping} to $self->{node}->{map}
625 #foreach my $key (@{$self->{meta}->{source}->{childnodes}}) {
626 my @childnodes = @{$self->{meta}->{source}->{childnodes}};
627 for (my $mapidx = 0; $mapidx <= $#childnodes; $mapidx++) {
628 #my $map_right = $self->{args}->{mapping}->{$key};
629
630 $self->{node}->{source}->{propcache} = {};
631 $self->{node}->{target}->{propcache} = {};
632
633 # get property name
634 $self->{node}->{source}->{propcache}->{property} = $self->{meta}->{source}->{childnodes}->[$mapidx];
635 $self->{node}->{target}->{propcache}->{property} = $self->{meta}->{target}->{childnodes}->[$mapidx];
636 #print "map: $map_right", "\n";
637
638 # get property value
639 my $value;
640
641 # detect for callback - old style - (maybe the better???)
642 if (ref($self->{node}->{target}->{map}) eq 'CODE') {
643 #$value = &$map_right($objClone);
644 } else {
645 # plain (scalar?) value
646 #$value = $objClone->{$map_right};
647 $self->{node}->{source}->{propcache}->{value} = $self->{node}->{source}->{payload}->{$self->{node}->{source}->{propcache}->{property}};
648 }
649 #$self->{node}->{map}->{$key} = $value;
650
651 # detect expression
652 # for transferring deeply nested structures described by expressions
653 #print "val: $self->{node}->{source}->{propcache}->{value}", "\n";
654 if ($self->{node}->{source}->{propcache}->{property} =~ s/^expr://) {
655
656 # create an anonymous sub to act as callback target dispatcher
657 my $cb_dispatcher = sub {
658 #print "=============== CALLBACK DISPATCHER", "\n";
659 #print "ident: ", $self->{node}->{source}->{ident}, "\n";
660 #return $self->{node}->{source}->{ident};
661
662 };
663
664
665 #print Dumper($self->{node});
666
667 # build callback map for helper function
668 #my $cbmap = { $self->{meta}->{source}->{IdentProvider}->{arg} => $cb_dispatcher };
669 my $cbmap = {};
670 my $value = refexpr2perlref($self->{node}->{source}->{payload}, $self->{node}->{source}->{propcache}->{property}, $cbmap);
671 $self->{node}->{source}->{propcache}->{value} = $value;
672 }
673
674 # encode values dependent on type of underlying storage here - expand cases...
675 my $storage_type = $self->{meta}->{target}->{storage}->{locator}->{type};
676 if ($storage_type eq 'DBI') {
677 # ...for sql
678 $self->{node}->{source}->{propcache}->{value} = quotesql($self->{node}->{source}->{propcache}->{value});
679 }
680 elsif ($storage_type eq 'Tangram') {
681 # iso? utf8 already possible?
682
683 } elsif ($storage_type eq 'LDAP') {
684 # TODO: encode utf8 here?
685 }
686
687 # store value to transfer map
688 $self->{node}->{map}->{$self->{node}->{target}->{propcache}->{property}} = $self->{node}->{source}->{propcache}->{value};
689
690 }
691 }
692
693
694 # TODO: $logger->dump( ... );
695 #$logger->debug( "sqlmap:" . "\n" . Dumper($self->{node}->{map}) );
696 #print "sqlmap: ", Dumper($self->{node}->{map}), "\n";
697 #print "entrystatus: ", Dumper($self->{node}), "\n";
698
699 }
700
701 sub _resolveNodeIdent {
702 my $self = shift;
703 my $descent = shift;
704
705 #print Dumper($self->{node}->{$descent});
706
707 # get to the payload
708 #my $item = $specifier->{item};
709 my $payload = $self->{node}->{$descent}->{payload};
710
711 # resolve method to get to the id of the given item
712 # we use global metadata and the given descent for this task
713 #my $ident = $self->{$descent}->id($item);
714 #my $ident = $self->{meta}->{$descent}->{storage}->id($item);
715
716 my $ident;
717 my $provider_method = $self->{meta}->{$descent}->{IdentProvider}->{method};
718 my $provider_arg = $self->{meta}->{$descent}->{IdentProvider}->{arg};
719
720 # resolve to ident
721 if ($provider_method eq 'property') {
722 $ident = $payload->{$provider_arg};
723
724 } elsif ($provider_method eq 'storage_method') {
725 #$ident = $self->{meta}->{$descent}->{storage}->id($item);
726 $ident = $self->{meta}->{$descent}->{storage}->$provider_arg($payload);
727 }
728
729 $self->{node}->{$descent}->{ident} = $ident;
730
731 return 1 if $ident;
732
733 }
734
735
736 sub _modifyNode {
737 my $self = shift;
738 my $descent = shift;
739 my $action = shift;
740 my $map = shift;
741 my $crit = shift;
742
743 # map for new style callbacks
744 my $map_callbacks = {};
745
746 # checks go first!
747
748 # TODO: this should be reviewed first - before extending ;-)
749 # TODO: this should be extended:
750 # count this cases inside the caller to this sub and provide a better overall message
751 # if this counts still zero in the end:
752 # "No nodes have been touched for modify: Do you have column-headers in your csv file?"
753 if (not defined $self->{node}) {
754 #$logger->critical( __PACKAGE__ . "->_modifyNode failed: \"$descent\" node is empty." );
755 #return;
756 }
757
758 # transfer callback nodes from value map to callback map - handle them afterwards! - (new style callbacks)
759 if (my $callbacks = $self->{meta}->{$descent}->{Callback}) {
760 foreach my $callback (keys %{$callbacks->{write}}) {
761 $map_callbacks->{write}->{$callback} = $map->{$callback};
762 delete $map->{$callback};
763 }
764 }
765
766
767 #print Dumper($self->{meta});
768
769 # DBI speaks SQL
770 if ($self->{meta}->{$descent}->{storage}->{locator}->{type} eq 'DBI') {
771
772 #print Dumper($self->{node});
773 my $sql_main;
774 # translate map to sql
775 #print $action, "\n"; exit;
776 #print $self->{meta}->{$descent}->{node}, "\n"; exit;
777 #print "action:";
778 #print $action, "\n";
779 #$action = "anc";
780 #print "yai", "\n";
781 if (lc($action) eq 'insert') {
782 $sql_main = hash2Sql($self->{meta}->{$descent}->{node}, $map, 'SQL_INSERT');
783 } elsif (lc $action eq 'update') {
784 $crit ||= "$self->{meta}->{$descent}->{IdentProvider}->{arg}='$self->{node}->{$descent}->{ident}'";
785 $sql_main = hash2Sql($self->{meta}->{$descent}->{node}, $map, 'SQL_UPDATE', $crit);
786 }
787
788 #print "sql: ", $sql_main, "\n";
789 #exit;
790
791 # transfer data
792 my $sqlHandle = $self->{meta}->{$descent}->{storage}->sendCommand($sql_main);
793
794 # handle errors
795 if ($sqlHandle->err) {
796 #if ($self->{args}->{debug}) { print "sql-error with statement: $sql_main", "\n"; }
797 $self->{node}->{status}->{error} = {
798 statement => $sql_main,
799 state => $sqlHandle->state,
800 err => $sqlHandle->err,
801 errstr => $sqlHandle->errstr,
802 };
803 } else {
804 $self->{node}->{status}->{ok} = 1;
805 }
806
807 # Tangram does it the oo-way (naturally)
808 } elsif ($self->{meta}->{$descent}->{storage}->{locator}->{type} eq 'Tangram') {
809 my $sql_main;
810 my $object;
811
812 # determine classname
813 my $classname = $self->{meta}->{$descent}->{node};
814
815 # properties to exclude
816 my @exclude = @{$self->{meta}->{$descent}->{subnodes_exclude}};
817
818
819 if (my $identProvider = $self->{meta}->{$descent}->{IdentProvider}) {
820 push @exclude, $identProvider->{arg};
821 }
822
823 # new feature:
824 # - check TypeProvider metadata property from other side
825 # - use argument (arg) inside as a classname for object creation on this side
826 #my $otherSide = $self->_otherSide($descent);
827 if (my $typeProvider = $self->{meta}->{$descent}->{TypeProvider}) {
828 #print Dumper($map);
829 $classname = $map->{$typeProvider->{arg}};
830 # remove nodes from map also (push nodes to "subnodes_exclude" list)
831 push @exclude, $typeProvider->{arg};
832 }
833
834 # exclude banned properties (remove from map)
835 #map { delete $self->{node}->{map}->{$_} } @{$self->{args}->{exclude}};
836 map { delete $map->{$_} } @exclude;
837
838 # list of properties
839 my @props = keys %{$map};
840
841 # transfer data
842 if (lc $action eq 'insert') {
843
844 # build array to initialize object
845 #my @initarray = ();
846 #map { push @initarray, $_, undef; } @props;
847
848 # make the object persistent in four steps:
849 # - raw create (perl / class tangram scope)
850 # - engine insert (tangram scope) ... this establishes inheritance - don't try to fill in inherited properties before!
851 # - raw fill-in from hash (perl scope)
852 # - engine update (tangram scope) ... this updates all properties just filled in
853
854 # create new object ...
855 #my $object = $classname->new( @initarray );
856 $object = $classname->new();
857
858 # ... pass to orm ...
859 $self->{meta}->{$descent}->{storage}->insert($object);
860
861 # ... and initialize with empty (undef'd) properties.
862 #print Dumper(@props);
863 map { $object->{$_} = undef; } @props;
864
865 # mix in values ...
866 hash2object($object, $map);
867
868 # ... and re-update@orm.
869 #print Dumper($object);
870 $self->{meta}->{$descent}->{storage}->update($object);
871
872 # asymmetry: get ident after insert
873 # TODO:
874 # - just do this if it is an IdentAuthority
875 # - use IdentProvider metadata here
876 #print Dumper($self->{meta}->{$descent});
877 my $oid = $self->{meta}->{$descent}->{storage}->id($object);
878 #print "oid: $oid", "\n";
879 $self->{node}->{$descent}->{ident} = $oid;
880
881
882 } elsif (lc $action eq 'update') {
883
884 # get fresh object from orm first
885 $object = $self->{meta}->{$descent}->{storage}->load($self->{node}->{$descent}->{ident});
886
887 #print Dumper($self->{node});
888
889 # mix in values
890 #print Dumper($object);
891 hash2object($object, $map);
892 #print Dumper($object);
893 #exit;
894 $self->{meta}->{$descent}->{storage}->update($object);
895 }
896
897 my $error = 0;
898
899 # handle new style callbacks - this is a HACK - do this without an eval!
900 #print Dumper($map);
901 #print "cb: ", Dumper($self->{meta}->{$descent}->{Callback});
902 #print Dumper($map_callbacks);
903 foreach my $node (keys %{$map_callbacks->{write}}) {
904 #print Dumper($node);
905 my $perl_callback = $self->{meta}->{$descent}->{node} . '::' . $node . '_write';
906 my $evalstring = $perl_callback . '( { object => $object, value => $map_callbacks->{write}->{$node}, storage => $self->{meta}->{$descent}->{storage} } );';
907 #print $evalstring, "\n"; exit;
908 eval($evalstring);
909 if ($@) {
910 $error = 1;
911 print $@, "\n";
912 }
913
914 #print "after eval", "\n";
915
916 if (!$error) {
917 # re-update@orm
918 $self->{meta}->{$descent}->{storage}->update($object);
919 }
920 }
921
922 # handle errors
923 if ($error) {
924 #print "error", "\n";
925 =pod
926 my $sqlHandle;
927 #if ($self->{args}->{debug}) { print "sql-error with statement: $sql_main", "\n"; }
928 $self->{node}->{status}->{error} = {
929 statement => $sql_main,
930 state => $sqlHandle->state,
931 err => $sqlHandle->err,
932 errstr => $sqlHandle->errstr,
933 };
934 =cut
935 # rollback....
936 #print "rollback", "\n";
937 $self->{meta}->{$descent}->{storage}->erase($object);
938 #print "after rollback", "\n";
939 } else {
940 $self->{node}->{status}->{ok} = 1;
941 }
942
943
944 }
945
946 }
947
948 # TODO:
949 # this should be split up into...
950 # - a "_statNode" (should just touch the node to check for existance)
951 # - a "_loadNode" (should load node completely)
952 # - maybe additionally a "loadNodeProperty" (may specify properties to load)
953 # - introduce $self->{nodecache} for this purpose
954 # TODO:
955 # should we:
956 # - not pass ident in here but resolve it via "$descent"?
957 # - refactor this and stuff it with additional debug/error message
958 # - this = the way the implicit load mechanism works
959 sub _statloadNode {
960
961 my $self = shift;
962 my $descent = shift;
963 my $ident = shift;
964 my $force = shift;
965
966 # fetch entry to retrieve checksum from
967 # was:
968 if (!$self->{node}->{$descent} || $force) {
969 # is:
970 #if (!$self->{node}->{$descent}->{item} || $force) {
971
972 if (!$ident) {
973 #print "\n", "Attempt to fetch entry implicitely by ident failed: no ident given! This may result in an insert if no write-protection is in the way.", "\n";
974 return;
975 }
976
977 # patch for DBD::CSV
978 if ($ident && $ident eq 'Null') {
979 return;
980 }
981
982 my $result = $self->{meta}->{$descent}->{storage}->sendQuery({
983 node => $self->{meta}->{$descent}->{node},
984 subnodes => [qw( cs )],
985 criterias => [
986 { key => $self->{meta}->{$descent}->{IdentProvider}->{arg},
987 op => 'eq',
988 val => $ident },
989 ]
990 });
991
992 my $entry = $result->getNextEntry();
993 my $status = $result->getStatus();
994
995 #print Dumper($status);
996
997 # TODO: enhance error handling (store inside tc)
998 #if (!$row) {
999 # print "\n", "row error", "\n";
1000 # next;
1001 #}
1002
1003 # these checks run before actually loading payload- and meta-data to node-container
1004
1005 # 1st level - hard error
1006 if ($status && $status->{err}) {
1007 $logger->debug( __PACKAGE__ . "->_statloadNode (ident=\"$ident\") failed - hard error (that's ok)" );
1008 return;
1009 }
1010
1011 # 2nd level - logical (empty/notfound) error
1012 if (($status && $status->{empty}) || !$entry) {
1013 $logger->debug( __PACKAGE__ . "->_statloadNode (ident=\"$ident\") failed - logical error (that's ok)" );
1014 #print "no entry (logical)", "\n";
1015 return;
1016 }
1017
1018 #print Dumper($entry);
1019
1020 # was:
1021 # $self->{node}->{$descent}->{ident} = $ident;
1022 # is:
1023 # TODO: re-resolve ident from entry via metadata "IdentProvider" here - like elsewhere
1024 $self->{node}->{$descent}->{ident} = $ident;
1025 $self->{node}->{$descent}->{payload} = $entry;
1026
1027 }
1028
1029 return 1;
1030
1031 }
1032
1033 sub _doTransferToTarget {
1034 my $self = shift;
1035 my $action = shift;
1036 $self->_modifyNode('target', $action, $self->{node}->{map});
1037 }
1038
1039 sub _doModifySource_IdentChecksum {
1040 my $self = shift;
1041 my $ident_new = shift;
1042 # this changes an old node to a new one including ident and checksum
1043 # TODO:
1044 # - eventually introduce an external resource to store this data to
1045 # - we won't have to "re"-modify the source node here
1046 my $map = {
1047 $self->{meta}->{source}->{IdentProvider}->{arg} => $ident_new,
1048 cs => $self->{node}->{target}->{checksum},
1049 };
1050
1051 #print Dumper($map);
1052 #print Dumper($self->{node});
1053 #exit;
1054
1055 $self->_modifyNode('source', 'update', $map);
1056 }
1057
1058
1059 # this is a shortcut method
1060 # ... let's try to avoid _any_ redundant code in here (ok... - at the cost of method lookups...)
1061 sub _getNodeList {
1062 my $self = shift;
1063 my $descent = shift;
1064 my $filter = shift;
1065 return $self->{meta}->{$descent}->{storage}->getListFiltered($self->{meta}->{$descent}->{node}, $filter);
1066 }
1067
1068
1069 sub _prepareNode_MetaProperties {
1070 my $self = shift;
1071 my $descent = shift;
1072
1073 $logger->info( __PACKAGE__ . "->_prepareNode_MetaProperties( descent $descent )" );
1074
1075 # TODO: this should (better) be: "my $firstnode = $self->_getFirstNode($descent);"
1076 my $list = $self->_getNodeList($descent);
1077
1078 # get first node
1079 my $firstnode = $list->[0];
1080
1081 # check if node contains meta properties/nodes
1082 # TODO: "cs" is hardcoded here!
1083 my @required = ( $self->{meta}->{$descent}->{IdentProvider}->{arg}, 'cs' );
1084 my @found = keys %$firstnode;
1085 #my @diff = getDifference(\@found, \@required);
1086 my $diff = getDifference(\@required, \@found);
1087 #print Dumper(@found);
1088 #print Dumper(@required);
1089 #print Dumper(@diff);
1090 #if (!$#diff || $#diff == -1) {
1091 if (isEmpty($diff)) {
1092 $logger->warning( __PACKAGE__ . "->_prepareNode_MetaProperties: node is lacking meta properties - will try to alter..." );
1093 foreach (@required) {
1094 my $sql = "ALTER TABLE $self->{meta}->{$descent}->{node} ADD COLUMN $_";
1095 #print "sql: $sql", "\n";
1096 my $res = $self->{meta}->{$descent}->{storage}->sendCommand($sql);
1097 #print Dumper($res->getStatus());
1098 }
1099 }
1100
1101 }
1102
1103 sub _prepareNode_DummyIdent {
1104 my $self = shift;
1105 my $descent = shift;
1106
1107 $logger->info( __PACKAGE__ . "->_prepareNode_DummyIdent( descent $descent )" );
1108
1109 my $list = $self->_getNodeList($descent);
1110 #print Dumper($list);
1111 my $i = 0;
1112 my $ident_base = 5678983;
1113 my $ident_appendix = '0001';
1114 foreach my $node (@$list) {
1115 my $ident_dummy = $i + $ident_base;
1116 $ident_dummy .= $ident_appendix;
1117 my $map = {
1118 $self->{meta}->{$descent}->{IdentProvider}->{arg} => $ident_dummy,
1119 cs => undef,
1120 };
1121
1122 # diff lists and ...
1123 my $diff = getDifference([keys %$node], [keys %$map]);
1124 next if $#{$diff} == -1;
1125
1126 # ... build criteria including all columns
1127 my @crits;
1128 foreach my $property (@$diff) {
1129 next if !$property;
1130 my $value = $node->{$property};
1131 next if !$value;
1132 push @crits, "$property='" . quotesql($value) . "'";
1133 }
1134 my $crit = join ' AND ', @crits;
1135 print "p" if $self->{verbose};
1136
1137 #print Dumper($map);
1138 #print Dumper($crit);
1139
1140 $self->_modifyNode($descent, 'update', $map, $crit);
1141 $i++;
1142 }
1143
1144 print "\n" if $self->{verbose};
1145
1146 if (!$i) {
1147 $logger->warning( __PACKAGE__ . "->_prepareNode_DummyIdent: no nodes touched" );
1148 }
1149
1150 }
1151
1152 # TODO: handle this in an abstract way (wipe out use of 'source' and/or 'target' inside core)
1153 sub _otherSide {
1154 my $self = shift;
1155 my $descent = shift;
1156 return 'source' if $descent eq 'target';
1157 return 'target' if $descent eq 'source';
1158 return '';
1159 }
1160
1161 sub _erase_all {
1162 my $self = shift;
1163 my $descent = shift;
1164 #my $node = shift;
1165 my $node = $self->{meta}->{$descent}->{node};
1166 $self->{meta}->{$descent}->{storage}->eraseAll($node);
1167 }
1168
1169
1170 =pod
1171
1172
1173 =head1 DESCRIPTION
1174
1175 Data::Transfer::Sync is a module providing a generic synchronization process
1176 across arbitrary/multiple storages based on a ident/checksum mechanism.
1177 It sits on top of Data::Storage.
1178
1179
1180 =head1 REQUIREMENTS
1181
1182 For full functionality:
1183 Data::Storage
1184 Data::Transform
1185 Data::Compare
1186 ... and all their dependencies
1187
1188
1189 =head1 AUTHORS / COPYRIGHT
1190
1191 The Data::Storage module is Copyright (c) 2002 Andreas Motl.
1192 All rights reserved.
1193
1194 You may distribute it under the terms of either the GNU General Public
1195 License or the Artistic License, as specified in the Perl README file.
1196
1197
1198 =head1 SUPPORT / WARRANTY
1199
1200 Data::Storage is free software. IT COMES WITHOUT WARRANTY OF ANY KIND.
1201
1202
1203
1204 =head1 BUGS
1205
1206 When in "import" mode for windows file - DBD::AutoCSV may hang.
1207 Hint: Maybe the source node contains an ident-, but no checksum-column?
1208
1209
1210 =head1 USER LEVEL ERRORS
1211
1212 =head4 Mapping
1213
1214 - - - - - - - - - - - - - - - - - - - - - - - - - -
1215 info: BizWorks::Process::Setup->syncResource( source_node Currency mode PULL erase 0 import 0 )critical: BizWorks::Process::Setup->startSync: Can't access mapping for node "Currency" - please check BizWorks::ResourceMapping.
1216 - - - - - - - - - - - - - - - - - - - - - - - - - -
1217 You have to create a sub for each node used in synchronization inside named Perl module. The name of this sub _must_ match
1218 the name of the node you want to sync. This sub holds mapping metadata to give the engine hints about how
1219 to access the otherwise generic nodes.
1220 - - - - - - - - - - - - - - - - - - - - - - - - - -
1221
1222
1223 =head4 DBD::AutoCSV's rulebase
1224
1225 - - - - - - - - - - - - - - - - - - - - - - - - - -
1226 info: BizWorks::Process::Setup->syncResource( source_node Currency mode PULL erase 0 import 0 )
1227 info: Data::Transfer::Sync->syncNodes: source=L/Currency <- target=R/currencies.csv
1228
1229 Execution ERROR: Error while scanning: Missing first row or scanrule not applied. at C:/home/amo/develop/netfrag.org/nfo/perl/libs/DBD/CSV.p
1230 m line 165, <GEN9> line 1.
1231 called from C:/home/amo/develop/netfrag.org/nfo/perl/libs/Data/Storage/Handler/DBI.pm at 123.
1232
1233 DBI-Error: DBD::AutoCSV::st fetchrow_hashref failed: Attempt to fetch row from a Non-SELECT statement
1234 notice: Data::Transfer::Sync->syncNodes: No nodes to synchronize.
1235 - - - - - - - - - - - - - - - - - - - - - - - - - -
1236 DBD::AutoCSV contains a rulebase which is spooled down while attempting to guess the style of the csv file regarding
1237 parameters like newline (eol), column-seperation-character (sep_char), quoting character (quote_char).
1238 If this spool runs out of entries and no style could be resolved, DBD::CSV dies causing this "Execution ERROR" which
1239 results in a "DBI-Error" afterwards.
1240 - - - - - - - - - - - - - - - - - - - - - - - - - -
1241
1242
1243 =head4 Check structure of source node
1244
1245 - - - - - - - - - - - - - - - - - - - - - - - - - -
1246 info: Data::Transfer::Sync->syncNodes: source=L/Currency <- target=R/currencies.csv
1247 critical: Data::Transfer::Sync->syncNodes: Can not synchronize: No ident found in source node, maybe try to "import" this node first.
1248 - - - - - - - - - - - - - - - - - - - - - - - - - -
1249 If lowlevel detection succeeds, but no other required informations are found, this message is issued.
1250 "Other informations" might be:
1251 - column-header-row completely missing
1252 - ident column is empty
1253 - - - - - - - - - - - - - - - - - - - - - - - - - -
1254
1255
1256 =head4 Modify structure of source node
1257
1258 - - - - - - - - - - - - - - - - - - - - - - - - - -
1259 info: Data::Transfer::Sync->syncNodes: source=L/Currency <- target=R/currencies.csv
1260 info: Data::Transfer::Sync->_prepareNode_MetaProperties( descent source )
1261 warning: Data::Transfer::Sync->_prepareNode_MetaProperties: node is lacking meta properties - will try to alter...
1262 SQL ERROR: Command 'ALTER' not recognized or not supported!
1263
1264 SQL ERROR: Command 'ALTER' not recognized or not supported!
1265 - - - - - - - - - - - - - - - - - - - - - - - - - -
1266 The Engine found a node which structure does not match the required. It tries to alter this automatically - only when doing "import" -
1267 but the DBD driver (in this case DBD::CSV) gets in the way croaking not to be able to do this.
1268 This could also appear if your database connection has insufficient rights to modify the database structure.
1269 DBD::CSV croaks because it doesn't implement the ALTER command, so please edit your columns manually.
1270 Hint: Add columns with the names of your "ident" and "checksum" property specifications.
1271 - - - - - - - - - - - - - - - - - - - - - - - - - -
1272
1273
1274 =head4 Load source node by ident
1275
1276 - - - - - - - - - - - - - - - - - - - - - - - - - -
1277 info: Data::Transfer::Sync->_prepareNode_DummyIdent( descent source )
1278 pcritical: Data::Transfer::Sync->_modifyNode failed: "source" node is empty.
1279 - - - - - - - - - - - - - - - - - - - - - - - - - -
1280 The source node could not be loaded. Maybe the ident is missing. Please check manually.
1281 Hint: Like above, the ident and/or checksum columns may be missing....
1282 - - - - - - - - - - - - - - - - - - - - - - - - - -
1283
1284
1285 =head1 TODO
1286
1287 - sub _resolveIdentProvider
1288 - wrap _doModifySource and _doTransferTarget around a core function which can change virtually any type of node
1289 - split this module up into Sync.pm, Sync/Core.pm, Sync/Compare.pm and Sync/Compare/Checksum.pm
1290 - introduce _compareNodes as a core method and wrap it around methods in Sync/Compare/Checksum.pm
1291 - introduce Sync/Compare/MyComparisonImplementation.pm
1292 - some generic deferring method - e.g. "$self->defer(action)" - to be able to accumulate a bunch of actions for later processing
1293 - this implies everything done is _really_ split up into generic actions - how else would we defer them???
1294 - example uses:
1295 - fetch whole checksum list from node
1296 - remember source ident retransmits
1297 - remember: this is convenient - and maybe / of course faster - but we'll loose "per-node-atomic" operations
1298 - feature: mechanism to implicit inject checksum property to nodes (alter table / modify schema)
1299 - expand statistics / keep track of:
1300 - touched/untouched nodes
1301 - full sync
1302 - just do a push and a pull for now but use stats for touched nodes in between to speed up things
1303 - introduce some new metadata flags for a synchronization partner which is (e.g.) of "source" or "target":
1304 - isNewNodePropagator
1305 - isWriteProtected
1306
1307
1308 =cut
1309
1310 1;

MailToCvsAdmin">MailToCvsAdmin
ViewVC Help
Powered by ViewVC 1.1.26 RSS 2.0 feed