Files
racalmas/tools/sync_cms/time_gate.pl

488 lines
13 KiB
Perl

#!/usr/bin/perl -I ../lib #-w
BEGIN{
my $dir='';
$ENV{SCRIPT_FILENAME}||'' if ($dir eq'');
$dir=~s/(.*\/)[^\/]+/$1/ if ($dir ne '');
$dir=$ENV{PWD} if ($dir eq'');
$dir=`pwd` if ($dir eq'');
#local perl installation libs
unshift(@INC,$dir.'/../../perl/lib/');
#calcms libs + configuration
unshift(@INC,$dir.'/../calcms/');
}
#use utf8;
use Data::Dumper;
#require 'time.pl';
use Getopt::Long;
use time;
use DateTime;
use DateTime::Duration;
use strict;
use warnings;
check_running_processes();
my $read_mode='';
my $update_mode='';
my $all_events='';
my $modified_events='';
my $source_config_file='';
my $target_config_file='';
my $block_number=0;
my $block_size=2000;
my $from='';
my $till='';
my $read_only=0;
my $project='';
GetOptions(
"read" => \$read_mode,
"update" => \$update_mode,
"all" => \$all_events,
"modified" => \$modified_events,
"from=s" => \$from,
"till=s" => \$till,
"source=s" => \$source_config_file,
"target=s" => \$target_config_file,
"project=s" => \$project,
"block_number:i" => \$block_number,
"block_size:i" => \$block_size
);
$|=1;
BEGIN {
our $utf8dbi=1;
$ENV{LANG}="en_US.UTF-8";
# print Dumper(\%ENV);
}
#source and taget settings are loaded from config files
our $settings={
};
#user interface
our $ask_before_insert=0;
our $ask_before_update=0;
# end of configuration
if ($update_mode){
$db::write=1;
# print "enter update mode\n";
}elsif($read_mode){
#default
$db::write=0;
# print "enter read-only mode\n";
}else{
print_error("set parameter >read< or >update<");
}
unless ($modified_events || $all_events || $from || $till){
print_error("set one of folling parameters: --modified, --from, --till");
}
init();
my $project_target=$source::settings->{sources}->{$project};
unless (defined $project){
print_error("missing parameter --project") unless(defined $project_target);
print_error("cant find project configuration '$project_target'") unless (-f $project_target);
print_error("cant read project configuration '$project_target'") unless (-r $project_target);
}
my $events=[];
print "TIME_GATE: READ ALL CALENDARS\n";
sync();
$events=compress_events($events);
my $c=0;
if ($project eq ''){
for my $event (@$events){
print_event("[".($c+1)."]",$event);
print "\n";
$c++;
}
}else{
my $source=$source::settings->{sources}->{$project};
my $target='config/target/calcms.cfg';
for my $event (@$events){
my $from=$event->{start};
#print Dumper($event->{end});
#remove a second
my $till=source::get_datetime($event->{end}, $source::settings->{date}->{time_zone})->add(seconds=>-1)->datetime();
print_event("STATION TIMESLOT [".($c+1)."]\t",$event);
print "\n";
$c++;
my $command="perl sync_cms.pl --update --all --from=$from --till=$till --source $source --target $target ";
print_info($command);
print `$command`;
#exit;
}
}
print "\ndone.\n";
exit 0;
sub compress_events{
my $events=shift;
my @results=();
my $old_event={end=>'', start=>'', title=>''};
for my $event(sort {$a->{start} gt $b->{start}} @$events){
# print "$event->{start}\t$event->{end}\t$event->{title}\n";
if (
# (defined $event) && (defined $event->{start}) && (defined $event->{end}) && (defined $event->{title})
( #station continues
($event->{start} eq $old_event->{end})
|| (#multiple entries for same event
($event->{start} ge $old_event->{start})
&& ($event->{end} eq $old_event->{end})
)
)
&& ($event->{title} eq $old_event->{title})
&& (@results>0)
){
$results[-1]->{end}=$event->{end};
# print @results."\tmerge \n";
}else{
push @results,{
start => $event->{start},
end => $event->{end},
title => $event->{title},
};
# print @results."\tinsert \n";
}
$old_event=$results[-1];
}
# print Dumper(\@results);
return \@results;
}
#sync all events, splitting multi-day-requests into multiple 1-day-requests to avoid large result sets
sub sync{
#prepare target
target::init($settings->{target});
print_info("last update: $settings->{source}->{last_update}");
if (my $days=source::split_request($settings->{source})){
#set 1-day start-min and start-max parameters, requires --from and --till values
for my $date (@$days){
for my $key(keys %$date){
$settings->{source}->{$key}=$date->{$key};
}
print "\nrequest ".$settings->{source}->{"start_min"}." to ".$settings->{source}->{"start_max"}."\n";
sync_timespan();
}
}else{
#update without time span (e.g. --modified)
sync_timespan();
}
print_info("\nset last-update time: $settings->{event}->{update_start}");
set_last_update_time($source_config_file,$target_config_file,$settings->{event}->{update_start});
}
#sync all events of a given source timespan
sub sync_timespan{
#get a list of all days and their events
#print Dumper($settings->{source});
my $source_events=source::get_events($settings->{source},$settings->{target});
#print Dumper($source_events);
my @dates=(keys %$source_events);
if (@dates==0){
my $more='';
if ((defined $settings->{source}->{block_number}) && ($settings->{source}->{block_number} ne '0')){
$more='more ';
}elsif ($modified_events){
$more.='modified ';
}
print_info("\n".'no '.$more."entries found.");
}else{
#sort lists of date and time (same time events should be preserved)
for my $date(sort {$a cmp $b} @dates){
# for my $date(@dates){
# print "\n$date:\n";
sync_events($source_events->{$date}, $settings);
}
}
}
#syncronize a list of source events to target events
sub sync_events{
my $source_events=shift;
my $settings=shift;
my $c=0;
$c=$source::settings->{start_index}+0 if (defined $source::settings->{start_index});
# print "<events>\n";
#order processing by start time (TODO: order by last-modified date)
for my $event (sort{$a->{calcms_start} cmp $b->{calcms_start}} @$source_events){
#read event attributes
$event=source::get_event_attributes($event);
$event->{title}=~s/\s//g;
$event->{event}={
title => $event->{title},
start => $event->{start},
end => $event->{end},
status => $event->{status},
};
# print "\n";
#print_event("[".($c+1)."]",$event);
#print "\n".$event->{event}->{title}." ".$project."\n";
if ($event->{event}->{status}eq'canceled'){
print "canceled event:".qq{$event};
}elsif ($event->{event}->{start} eq ''){
print ('WARNING: Cannot read start of event'."\n");
}elsif ($event->{event}->{end} eq ''){
print ('WARNING: Cannot read start of end'."\n");
}elsif ($event->{event}->{title} eq ''){
print ('WARNING: Cannot read start of title'."\n");
}elsif ($project ne ''){
if ($event->{event}->{title} eq $project){
push @$events, $event->{event};
}
}else{
push @$events, $event->{event};
}
$event=undef;
$c++;
}
}
#import requested source and target libs
sub init{
binmode STDOUT, ":utf8";
#require source config file
print_error ("missing source parameter!") unless ($source_config_file=~/\S/);
print_error ("source file: '$source_config_file' does not exist") unless (-e $source_config_file);
print_error ("cannot read source file: '$source_config_file'") unless (-r $source_config_file);
#$settings->{source}=require $source_config_file;
my $configuration = new Config::General($source_config_file);
$settings->{source}=$configuration->{DefaultConfig}->{source};
#require source import lib from config file
my $source_import_lib='lib/source/'.$settings->{source}->{type}.'.pl';
print_error ("missing 'type' in 'source' config ") unless ($settings->{source}->{type}=~/\S/);
print_error ("cannot read source type import lib: '$source_import_lib'")unless (-r $source_import_lib);
require $source_import_lib;
#require target config file
print_error ("missing target parameter!") unless ($target_config_file=~/\S/);
print_error ("target file: '$target_config_file' does not exist") unless (-e $target_config_file);
print_error ("cannot read target file: '$target_config_file'") unless (-r $target_config_file);
#$settings->{target}=require $target_config_file;
$configuration = new Config::General($target_config_file);
$settings->{target}=$configuration->{DefaultConfig}->{target};
#require target import lib from config file
my $target_import_lib='lib/target/'.$settings->{target}->{type}.'.pl';
print_error ("missing 'type' in 'target' config ") unless ($settings->{target}->{type}=~/\S/);
print_error ("cannot read target type import lib: '$target_import_lib'")unless (-r $target_import_lib);
require $target_import_lib;
#print Dumper($settings);
if ((defined $settings->{source}->{read_blocks}) && ($settings->{source}->{read_blocks}==1)){
$settings->{source}->{block_number} =$block_number;
$settings->{source}->{block_size} =$block_size;
}
$settings->{source}->{last_update} =get_last_update_time($source_config_file,$target_config_file);
$settings->{source}->{modified_events} =$modified_events;
if ($from=~/^\d\d\d\d\-\d\d\-\d\d$/){
$from.='T00:00';
}
if ($till=~/^\d\d\d\d\-\d\d\-\d\d$/){
$till.='T23:59';
}
if ($from=~/^([-+]?\d+$)/){
my $days=$1;
my $duration=new DateTime::Duration(days=>$days);
$from=DateTime->today->add_duration($duration);
# print "from:$from\t";
}
if ($till=~/^([-+]?\d+$)/){
my $days=$1+1;
my $duration=new DateTime::Duration(days=>$days);
$till=DateTime->today->add_duration($duration);
# print "till:$till\t";
}
$settings->{source}->{start_min} =$from if defined ($from);
$settings->{source}->{start_max} =$till if defined ($till);
my $gmt_difference =0;#*=3600;
my $now =time();
my $now_gmt =$now-$gmt_difference;
$now =time::time_to_datetime($now);
$now_gmt =time::time_to_datetime($now_gmt);
$settings->{event}={
update_start => time::time_to_datetime(time()),
modified_at => $now,
modified_at_gmt => $now_gmt
};
source::init($settings->{source});
}
# print date/time, title and excerpt of an calendar event
# TODO: replace by output filter (text, html)
sub print_event{
my $header=shift;
my $event=shift;
my $s=$header;
$s=$s." "x (8-length($s));
# print Dumper($event);
my $start=$event->{start}||'';
$start=~s/T/ /g;
$start=~s/\:00$//g;
my $end=$event->{end}||'';
$end=~s/T/ /g;
$end=~s/\:00$//g;
$s.="$start\t$end\t'$event->{title}'";
# print Dumper($event->{event});
print $s;
#excerpt: >$event->{excerpt}<
#content: >$event->{content}<
#content: >$event->{content}<
}
#output usage on error or --help parameter
sub print_usage{
print qq{
update all/modified events from source at target.
USAGE: $0 [--read,--update] [--modified,--all] --source s --target t [--block_number b] [--block_size s]
on using --from and --till requests will be processed as multiple single-day-requests.
parameters:
--read show all events without updating database
--update update target database with source events
--modified process only modified events.
--all' process all events
--source source configuration file
--target target configuration file
--from start of date range: datetime (YYYY-MM-DDTHH:MM::SS) or days from today (e.g. -1 for yesterday, +1 for tomorrow)
--till end of date range: datetime (YYYY-MM-DDTHH:MM::SS) or days from today (e.g. -1 for yesterday, +1 for tomorrow)
--block_number which block is to be syncronized [0..n]. To split up processing into multiple blocks (for machines with small memory resources).
--block_size size of a block, default=20 events
examples:
perl $0 --update --modified --source=config/source/einheit.cfg --target=config/target/calcms.cfg
perl $0 --update --all --from=2009-09-01T00:00:00 --till=2009-11-22T23:59:59 --source=config/source/einheit.cfg --target=config/target/calcms.cfg
};
exit 1;
};
#load last update time out of sync.data
sub get_last_update_time{
my $source=shift;
my $target=shift;
my $date=undef;
return undef unless(-r "sync.data");
open my $DATA, "<:utf8","sync.data" || die ('cannot read update timestamp');
while (<$DATA>){
my $line=$_;
if ($line=~/$source\s+\->\s+$target\s+:\s+(\d{4}\-\d{2}\-\d{2} \d{2}:\d{2}:\d{2})/){
$date=$1;
last;
}
}
close $DATA;
return $date;
}
#save last update time to sync.data
sub set_last_update_time{
my $source =shift;
my $target =shift;
my $date =shift;
my $data='';
if (-r "sync.data"){
open my $DATA, "<:utf8","sync.data";
$data=join("\n",(<$DATA>));
close $DATA;
}
if ($data=~/$source\s+\->\s+$target\s+:\s+(\d{4}\-\d{2}\-\d{2} \d{2}:\d{2}:\d{2})/){
$data=~s/($source\s+\->\s+$target\s+:)\s+\d{4}\-\d{2}\-\d{2} \d{2}:\d{2}:\d{2}/$1\t$date/gi;
}else{
$data.="$source\t\->\t$target\t:\t$date\n";
}
$data=~s/[\r\n]+/\n/g;
open my $DATA2, ">:utf8","sync.data" || die ('cannot write update timestamp');
print $DATA2 $data;
close $DATA2;
# print $data;
}
#default error handling
sub print_error{
print "\nERROR:\t$_[0]\n" ;
print_usage();
}
sub print_info{
my $message=shift;
if ($message=~/^\n/){
$message=~s/^\n//g;
print "\n";
}
print "INFO:\t$message\n";
}
#avoid to run more than one sync process simultaniously
sub check_running_processes{
my $cmd="ps -afex 2>/dev/null | grep $0.pl | grep -v nice | grep -v grep ";
my $ps=`$cmd`;
# print "$ps";
my @lines=(split(/\n/,$ps));
if (@lines>1){
print "ERROR:\tanother ".@lines." synchronization processes '$0.pl' instances are running!".qq{
$cmd
$ps
-> program will exit
};
exit;
}
}