/[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.5 - (show annotations)
Thu Dec 5 08:06:05 2002 UTC (21 years, 7 months ago) by joko
Branch: MAIN
Changes since 1.4: +63 -16 lines
+ bugfix with determining empty fields (Null) with DBD::CSV
+ debugging
+ updated comments

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

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