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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.1 - (show annotations)
Sun Jan 19 01:23:04 2003 UTC (21 years, 5 months ago) by joko
Branch: MAIN
+ new from Data/Transfer/Sync.pm

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

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