In the past Google Calendar exports were done by first removing all events of a day and then create new ones. This takes a lot of time for export and runs into Google Calendar usage limits after some time. By now content will be compared before removing/creating a single event one. To be able to do so, all other sync sources and targets have been removed, so its only possible to export from database to Google Calendar by this change. To trigger an export you need to create a trigger file. run_jobs.pl runs periodically e.g. started by cron and checks if a trigger file exists and start sync_cms.pl to export the selected events to the Google Calendar. Trigger files and jobs are configured at jobs.config. Each job has a source and target file containing the access data for calcms and the calendar. Configuration files have been cleaned up. Old Accounts and passwords have been removed. They hopefully should have been not active for a long time ;-)
293 lines
8.8 KiB
Perl
Executable File
293 lines
8.8 KiB
Perl
Executable File
#!/usr/bin/perl
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
use Data::Dumper;
|
|
use Getopt::Long;
|
|
use Config::General;
|
|
use DateTime;
|
|
use DateTime::Duration;
|
|
use IO::Socket::INET;
|
|
use Fcntl ':flock';
|
|
|
|
use FindBin;
|
|
use lib "$FindBin::Bin/lib";
|
|
use lib "$FindBin::Bin/../../calcms";
|
|
|
|
use Common ( 'info', 'error' );
|
|
use GoogleCalendar;
|
|
use CalcmsEvents;
|
|
|
|
Common::checkSingleInstance();
|
|
|
|
BEGIN {
|
|
$ENV{LANG} = "en_US.UTF-8";
|
|
}
|
|
|
|
$| = 1;
|
|
|
|
my $sourceConfigFile = '';
|
|
my $targetConfigFile = '';
|
|
my $debug = 1;
|
|
|
|
my $from = undef;
|
|
my $till = undef;
|
|
GetOptions(
|
|
"from=s" => \$from,
|
|
"till=s" => \$till,
|
|
"source=s" => \$sourceConfigFile,
|
|
"target=s" => \$targetConfigFile,
|
|
);
|
|
|
|
#source and taget settings are loaded from config files
|
|
my $settings = {};
|
|
|
|
error "set one of folling parameters: --from, --till" unless $from || $till;
|
|
|
|
init();
|
|
sync();
|
|
info "$0 done.";
|
|
exit 0;
|
|
|
|
#sync all events, splitting multi-day-requests into multiple 1-day-requests to avoid large result sets
|
|
sub sync {
|
|
my $timeZone = CalcmsEvents::get('date')->{time_zone};
|
|
my $from = CalcmsEvents::get('start_min');
|
|
my $till = CalcmsEvents::get('start_max');
|
|
|
|
info "sync from $from till $till at $timeZone";
|
|
|
|
#prepare target
|
|
info "last update: " . ( CalcmsEvents::get('last_update') || '' );
|
|
|
|
if ( my $days = CalcmsEvents::splitRequest( $from, $till, $timeZone ) ) {
|
|
for my $date (@$days) {
|
|
syncTimespan( $date->{from}, $date->{till} );
|
|
}
|
|
} else {
|
|
syncTimespan( $from, $till );
|
|
}
|
|
|
|
info "\nset last-update time: $settings->{event}->{update_start}";
|
|
setLastUpdateTime( $sourceConfigFile, $targetConfigFile, $settings->{event}->{update_start} );
|
|
}
|
|
|
|
#sync all events of a given source timespan
|
|
sub syncTimespan {
|
|
my $from = shift;
|
|
my $till = shift;
|
|
|
|
#get a list of all days and their events
|
|
my $sourceEvents = CalcmsEvents::getEvents( $from, $till );
|
|
|
|
my @dates = keys %$sourceEvents;
|
|
if ( scalar @dates == 0 ) {
|
|
info "\nno entries found.";
|
|
return;
|
|
}
|
|
|
|
#sort lists of date and time (same time events should be preserved)
|
|
for my $date ( sort { $a cmp $b } @dates ) {
|
|
syncEvents( $sourceEvents->{$date} );
|
|
}
|
|
|
|
}
|
|
|
|
#syncronize a list of source events to target events
|
|
sub syncEvents($) {
|
|
my $sourceEvents = shift;
|
|
|
|
my @sourceEvents = sort { $a->{calcms_start} cmp $b->{calcms_start} } @$sourceEvents;
|
|
$sourceEvents = \@sourceEvents;
|
|
my $start = $sourceEvents->[0]->{start};
|
|
my $end = $sourceEvents->[-1]->{end};
|
|
|
|
my $targetEvents = GoogleCalendar::getEvents(
|
|
{
|
|
start => $start,
|
|
end => $end
|
|
}
|
|
);
|
|
$targetEvents = $targetEvents->{items};
|
|
info "google:" . scalar(@$targetEvents) . " vs " . scalar(@$sourceEvents);
|
|
|
|
# mark all known target events
|
|
my $targetEventsByKey = {};
|
|
for my $event (@$targetEvents) {
|
|
#print Dumper($event);
|
|
next if $event->{status} eq 'canceled';
|
|
my $key = getGoogleEventToString($event);
|
|
$targetEventsByKey->{$key} = $event;
|
|
}
|
|
|
|
# mark all knwon source events
|
|
my $sourceEventsByKey = {};
|
|
for my $event (@$sourceEvents) {
|
|
$event = CalcmsEvents::mapToSchema($event);
|
|
$event = GoogleCalendar::mapToSchema($event);
|
|
my $key = getCalcmsEventToString($event);
|
|
$sourceEventsByKey->{$key} = $event;
|
|
}
|
|
|
|
# delete target entries without matching source entries
|
|
for my $key ( keys %$targetEventsByKey ) {
|
|
next if defined $sourceEventsByKey->{$key};
|
|
my $event = $targetEventsByKey->{$key};
|
|
info "delete $key ";
|
|
print Dumper($event);
|
|
GoogleCalendar::deleteEvent($event);
|
|
}
|
|
|
|
# insert source entries without matching target entries
|
|
for my $key ( keys %$sourceEventsByKey ) {
|
|
if ( defined $targetEventsByKey->{$key} ) {
|
|
info "$key is up to date";
|
|
next;
|
|
}
|
|
my $event = $sourceEventsByKey->{$key};
|
|
info "insert $key";
|
|
GoogleCalendar::insertEvent($event);
|
|
}
|
|
|
|
}
|
|
|
|
sub getGoogleEventToString {
|
|
my $event = shift;
|
|
my $result = "\n";
|
|
$result .= "start: " . substr( $event->{start}->{dateTime}, 0, 19 ) . "\n";
|
|
$result .= "end : " . substr( $event->{end}->{dateTime}, 0, 19 ) . "\n";
|
|
$result .= "title: $event->{summary}\n";
|
|
$result .= "desc : $event->{description}\n";
|
|
return $result;
|
|
}
|
|
|
|
sub getCalcmsEventToString {
|
|
my $event = shift;
|
|
my $result = "\n";
|
|
$result .= "start: " . substr( $event->{event}->{start_datetime}, 0, 19 ) . "\n";
|
|
$result .= "end : " . substr( $event->{event}->{end_datetime}, 0, 19 ) . "\n";
|
|
$result .= "title: $event->{event}->{title}\n";
|
|
$result .= "desc : $event->{event}->{content}\n";
|
|
return $result;
|
|
}
|
|
|
|
#import requested source and target libs
|
|
sub init {
|
|
binmode STDOUT, ":utf8";
|
|
|
|
{
|
|
#require target config file
|
|
error "missing target parameter!" unless $targetConfigFile =~ /\S/;
|
|
error "target file: '$targetConfigFile' does not exist" unless -e $targetConfigFile;
|
|
error "cannot read target file: '$targetConfigFile'" unless -r $targetConfigFile;
|
|
my $config = new Config::General($targetConfigFile);
|
|
$config = $config->{DefaultConfig}->{target};
|
|
GoogleCalendar::init($config);
|
|
|
|
}
|
|
|
|
{
|
|
#require source config file
|
|
error "missing source parameter!" unless $sourceConfigFile =~ /\S/;
|
|
error "source file: '$sourceConfigFile' does not exist" unless -e $sourceConfigFile;
|
|
error "cannot read source file: '$sourceConfigFile'" unless -r $sourceConfigFile;
|
|
my $config = new Config::General($sourceConfigFile);
|
|
$config = $config->{DefaultConfig}->{source};
|
|
$config->{last_update} = getLastUpdateTime( $sourceConfigFile, $targetConfigFile );
|
|
CalcmsEvents::init($config);
|
|
}
|
|
|
|
$from .= 'T00:00' if $from =~ /^\d\d\d\d\-\d\d\-\d\d$/;
|
|
$till .= 'T23:59' if $till =~ /^\d\d\d\d\-\d\d\-\d\d$/;
|
|
|
|
if ( $from =~ /^([-+]?\d+$)/ ) {
|
|
my $days = $1;
|
|
my $duration = new DateTime::Duration( days => $days );
|
|
$from = DateTime->today->add_duration($duration);
|
|
}
|
|
|
|
if ( $till =~ /^([-+]?\d+$)/ ) {
|
|
my $days = $1 + 1;
|
|
my $duration = new DateTime::Duration( days => $days );
|
|
$till = DateTime->today->add_duration($duration);
|
|
}
|
|
|
|
CalcmsEvents::set( 'start_min', $from ) if defined $from;
|
|
CalcmsEvents::set( 'start_max', $till ) if defined $till;
|
|
|
|
my $now = time();
|
|
$now = time::time_to_datetime($now);
|
|
$settings->{event} = {
|
|
update_start => time::time_to_datetime( time() ),
|
|
modified_at => $now,
|
|
};
|
|
|
|
}
|
|
|
|
#output usage on error or --help parameter
|
|
sub usage {
|
|
print qq{
|
|
update all/modified events from source at target.
|
|
|
|
USAGE: sync_cms.pl [--read,--update] [--modified,--all] --source s --target t
|
|
|
|
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
|
|
|
|
--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)
|
|
|
|
examples:
|
|
update modified
|
|
perl sync_cms.pl --update --source=config/source/program.cfg --target=config/target/calcms.cfg
|
|
update a given time range
|
|
perl sync_cms.pl --update --all --from=2009-09-01T00:00:00 --till=2009-11-22T23:59:59 --source=config/source/program.cfg --target=config/target/calcms.cfg
|
|
update from last 2 days until next 3 days
|
|
perl sync_cms.pl --update --from=-2 --till=+3 --source=config/source/program.cfg --target=config/target/calcms.cfg
|
|
};
|
|
exit 1;
|
|
}
|
|
|
|
#load last update time out of sync.data
|
|
sub getLastUpdateTime {
|
|
my $source = shift;
|
|
my $target = shift;
|
|
|
|
my $date = undef;
|
|
return undef unless -r "sync.data";
|
|
my $content = Common::loadFile("sync.data");
|
|
if ( $content =~ /$source\s+\->\s+$target\s+:\s+(\d{4}\-\d{2}\-\d{2} \d{2}:\d{2}:\d{2})/ ) {
|
|
$date = $1;
|
|
}
|
|
return $date;
|
|
}
|
|
|
|
#save last update time to sync.data
|
|
sub setLastUpdateTime {
|
|
my $source = shift;
|
|
my $target = shift;
|
|
my $date = shift;
|
|
|
|
my $data = '';
|
|
if ( -r "sync.data" ) {
|
|
$data = Common::loadFile("sync.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;
|
|
Common::saveFile( "sync.data", $data );
|
|
}
|
|
|