You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
545 lines
18 KiB
545 lines
18 KiB
use strict;
|
|
|
|
use DateTime qw();
|
|
use DateTime::TimeZone qw();
|
|
|
|
eval 'use lib "/home/rkrenn/sipwise/git/ngcp-schema/lib";';
|
|
eval 'use lib "/home/rkrenn/sipwise/git/sipwise-base/lib";';
|
|
eval 'use NGCP::Schema;';
|
|
|
|
my $schema = undef;
|
|
if ($@) {
|
|
die('failed to load NGCP::Schema: ' . $@);
|
|
} else {
|
|
print "connecting to ngcp db";
|
|
$schema = NGCP::Schema->connect({
|
|
dsn => "DBI:mysql:database=provisioning;host=192.168.0.29;port=3306",
|
|
user => "root",
|
|
#password => "...",
|
|
mysql_enable_utf8 => "1",
|
|
on_connect_do => "SET NAMES utf8mb4",
|
|
quote_char => "`",
|
|
});
|
|
}
|
|
|
|
goto WRITE_TO_DB;
|
|
|
|
print "\n" . process_file('/home/rkrenn/temp/cftimeset_tz/cftimesets.txt',sub { #'/home/rkrenn/temp/cftimeset_tz/cftimesets.txt'
|
|
my $item = shift;
|
|
my @times;
|
|
print "\nsubscriber id $item->{subscriber_id} - $item->{timezone}: ";
|
|
for my $timeelem (@{$item->{voip_cf_periods}}) {
|
|
delete $timeelem->{'id'};
|
|
push @times, $timeelem;
|
|
}
|
|
|
|
my $converted_times = apply_owner_timezone(
|
|
undef,
|
|
\@times,
|
|
'inflate',
|
|
$item->{timezone},
|
|
);
|
|
|
|
if ($converted_times) {
|
|
#use Data::Dumper;
|
|
#print Dumper($converted_times);
|
|
my $indent = 40;
|
|
for (my $i = 0; $i < ((scalar @{$item->{voip_cf_periods}} > scalar @$converted_times) ? scalar @{$item->{voip_cf_periods}} : scalar @$converted_times); $i++) {
|
|
my $old = $item->{voip_cf_periods}->[$i];
|
|
my $new = $converted_times->[$i];
|
|
my $str = '';
|
|
$str .= ' ' x $indent unless $old;
|
|
$str = print_item($old,'time_set_id',$str);
|
|
$str = print_item($old,'year',$str);
|
|
$str = print_item($old,'month',$str);
|
|
$str = print_item($old,'mday',$str);
|
|
$str = print_item($old,'wday',$str);
|
|
$str = print_item($old,'hour',$str);
|
|
$str = print_item($old,'minute',$str);
|
|
if ($new) {
|
|
$str .= ' ' x ($indent-length($str));
|
|
$str = print_item($new,'time_set_id',$str,1);
|
|
$str = print_item($new,'year',$str);
|
|
$str = print_item($new,'month',$str);
|
|
$str = print_item($new,'mday',$str);
|
|
$str = print_item($new,'wday',$str);
|
|
$str = print_item($new,'hour',$str);
|
|
$str = print_item($new,'minute',$str);
|
|
}
|
|
print $str . "\n";
|
|
}
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}) . ' rows processed';
|
|
|
|
WRITE_TO_DB:
|
|
print "\n" . process_rows('voip_cf_time_sets', sub {
|
|
my $item = shift;
|
|
my @times;
|
|
|
|
my $tz = $schema->resultset('voip_subscriber_timezone')->search_rs({
|
|
subscriber_id => $item->subscriber->voip_subscriber->id
|
|
})->first;
|
|
my $tz_name = normalize_db_tz_name($tz->name) if $tz;
|
|
print "\nsubscriber id " . $item->subscriber->voip_subscriber->id . " - $tz_name: ";
|
|
|
|
for my $time ($item->voip_cf_periods->all) {
|
|
my $timeelem = {$time->get_inflated_columns};
|
|
delete $timeelem->{'id'};
|
|
push @times, $timeelem;
|
|
}
|
|
|
|
my $converted_times = apply_owner_timezone(
|
|
$item->subscriber->voip_subscriber->id,
|
|
\@times,
|
|
'inflate',
|
|
);
|
|
|
|
if ($converted_times) {
|
|
#use Data::Dumper;
|
|
#print Dumper($converted_times);
|
|
|
|
$item->voip_cf_periods->delete;
|
|
for my $t ( @$converted_times ) {
|
|
delete $t->{time_set_id};
|
|
$item->create_related("voip_cf_periods", $t);
|
|
}
|
|
|
|
my $indent = 40;
|
|
for (my $i = 0; $i < ((scalar @times > scalar @$converted_times) ? scalar @times : scalar @$converted_times); $i++) {
|
|
my $old = $times[$i];
|
|
my $new = $converted_times->[$i];
|
|
my $str = '';
|
|
$str .= ' ' x $indent unless $old;
|
|
#$str = print_item($old,'time_set_id',$str);
|
|
$str = print_item($old,'year',$str);
|
|
$str = print_item($old,'month',$str);
|
|
$str = print_item($old,'mday',$str);
|
|
$str = print_item($old,'wday',$str);
|
|
$str = print_item($old,'hour',$str);
|
|
$str = print_item($old,'minute',$str);
|
|
if ($new) {
|
|
$str .= ' ' x ($indent-length($str));
|
|
#$str = print_item($new,'time_set_id',$str,1);
|
|
$str = print_item($new,'year',$str,1);
|
|
$str = print_item($new,'month',$str);
|
|
$str = print_item($new,'mday',$str);
|
|
$str = print_item($new,'wday',$str);
|
|
$str = print_item($new,'hour',$str);
|
|
$str = print_item($new,'minute',$str);
|
|
}
|
|
print $str . "\n";
|
|
}
|
|
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}) . ' rows processed';
|
|
exit;
|
|
|
|
sub apply_owner_timezone {
|
|
|
|
my ($subscriber_id, $times, $mode, $timezone) = @_;
|
|
|
|
my $tz_name;
|
|
if($timezone) {
|
|
if (DateTime::TimeZone->is_valid_name($timezone)) {
|
|
$tz_name = $timezone;
|
|
} else {
|
|
warn("timezone '$timezone' is not a valid time zone");
|
|
return;
|
|
}
|
|
} elsif ($subscriber_id) {
|
|
my $tz = $schema->resultset('voip_subscriber_timezone')->search_rs({
|
|
subscriber_id => $subscriber_id
|
|
})->first;
|
|
$tz_name = normalize_db_tz_name($tz->name) if $tz;
|
|
}
|
|
$times //= [];
|
|
my $tz_local = DateTime::TimeZone->new(name => 'local');
|
|
my ($tz,$offset);
|
|
if ($tz_name
|
|
and ($tz = DateTime::TimeZone->new(name => $tz_name))
|
|
and abs($offset = $tz->offset_for_datetime(DateTime->now()) - $tz_local->offset_for_datetime(DateTime->now())) > 0) {
|
|
#print current_local()->hms;
|
|
my $offset_hrs = int($offset / 3600.0);
|
|
if ($offset_hrs * 3600 != $offset) {
|
|
warn("only full hours supported for timezone offset ($tz_name: $offset seconds = $offset_hrs hours)");
|
|
return;
|
|
}
|
|
|
|
#foreach my $time (@$times) {
|
|
# foreach $field (qw(year month mday) {
|
|
# if (exists $time->{$field} and $time->{$field}) {
|
|
# $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "cannot apply a timezone offset for time with '$field' field ($time->{$field})");
|
|
# return;
|
|
# }
|
|
# }
|
|
#}
|
|
|
|
my $merge = 0;
|
|
if ('deflate' eq $mode) { #for writing to db
|
|
$offset_hrs = $offset_hrs * -1;
|
|
print "cf timeset $mode for timezone $tz_name: offset $offset_hrs hours\n";
|
|
} elsif ('inflate' eq $mode) { #for reading from db
|
|
$merge = 1;
|
|
print "cf timeset $mode for timezone $tz_name: offset $offset_hrs hours\n";
|
|
} else {
|
|
die("invalid mode $mode");
|
|
}
|
|
|
|
my ($yearmonthmday_map,$yearmonthmdays) = array_to_map($times,sub { my $time = shift;
|
|
return (length($time->{year}) > 0 ? $time->{year} : '*') .
|
|
'_' . ($time->{month} || '*') . '_' . ($time->{mday} || '*');
|
|
},undef,'group');
|
|
|
|
$times = [];
|
|
foreach my $yearmonthmday (@$yearmonthmdays) {
|
|
if ($offset_hrs > 0) {
|
|
push(@$times,@{add($yearmonthmday_map->{$yearmonthmday},$offset_hrs,$merge)});
|
|
} else {
|
|
push(@$times,@{subtract($yearmonthmday_map->{$yearmonthmday},$offset_hrs,$merge)});
|
|
}
|
|
}
|
|
|
|
} else {
|
|
print "no timezone to convert to, or zero tz offset\n";
|
|
}
|
|
return $times;
|
|
|
|
}
|
|
|
|
sub process_rows {
|
|
my ($relation,$code) = @_;
|
|
|
|
my $item_rs = $schema->resultset($relation);
|
|
my $page = 1;
|
|
my $count = 0;
|
|
while (my @page = $item_rs->search_rs(undef,{
|
|
page => $page,
|
|
rows => 100,
|
|
})->all) {
|
|
foreach my $item (@page) {
|
|
if (&$code($item)) {
|
|
$count++;
|
|
}
|
|
}
|
|
$page++;
|
|
}
|
|
return $count;
|
|
}
|
|
|
|
sub process_file {
|
|
my ($filename,$code) = @_;
|
|
open(my $fh, '<:encoding(UTF-8)', $filename) or die "Could not open file '$filename' $!";
|
|
my @rows = ();
|
|
while (my $row = <$fh>) {
|
|
my @cleaned = map { $_ = s/NULL//gr =~ s/[\r\n]//gir; length($_) > 0 ? $_ : undef; } split(/,/,$row);
|
|
push(@rows,{
|
|
id => $cleaned[0],
|
|
time_set_id => $cleaned[1],
|
|
year => $cleaned[2],
|
|
month => $cleaned[3],
|
|
mday => $cleaned[4],
|
|
wday => $cleaned[5],
|
|
hour => $cleaned[6],
|
|
minute => $cleaned[7],
|
|
subscriber_id => $cleaned[8],
|
|
timezone => $cleaned[9],
|
|
});
|
|
}
|
|
close $fh;
|
|
my ($map,$ids) = array_to_map(\@rows,sub {
|
|
return shift->{time_set_id};
|
|
},undef,'group');
|
|
my $count = 0;
|
|
foreach my $time_set_id (sort @$ids) {
|
|
my $item = { voip_cf_periods => [] };
|
|
foreach my $row (@{$map->{$time_set_id}}) {
|
|
my $period = { %$row };
|
|
$item->{subscriber_id} = delete $period->{subscriber_id};
|
|
$item->{timezone} = delete $period->{timezone};
|
|
push(@{$item->{voip_cf_periods}},$period);
|
|
}
|
|
if (&$code($item)) {
|
|
$count++;
|
|
}
|
|
}
|
|
return $count;
|
|
}
|
|
|
|
sub add {
|
|
my ($times,$offset_hrs,$merge) = @_;
|
|
|
|
my @result = ();
|
|
foreach my $time (@$times) {
|
|
my $p1 = { %$time };
|
|
unless (length($time->{hour}) > 0) {
|
|
#nothing to do if there is no hour defined:
|
|
push(@result,$p1);
|
|
next;
|
|
}
|
|
my ($hour_start,$hour_end) = split(/\-/, $time->{hour});
|
|
my $hour_range;
|
|
if (defined $hour_end) {
|
|
$hour_range = 1;
|
|
} else {
|
|
$hour_end = $hour_start;
|
|
$hour_range = 0;
|
|
}
|
|
$hour_start += abs($offset_hrs);
|
|
$hour_end += abs($offset_hrs);
|
|
if ($hour_start < 24 and $hour_end < 24) {
|
|
$p1->{hour} = ($hour_range ? $hour_start . '-' . $hour_end : $hour_start);
|
|
push(@result,$p1);
|
|
next;
|
|
}
|
|
my ($wday_start, $wday_end) = split(/\-/, $time->{wday} || '1-7');
|
|
my $wday_range;
|
|
if (defined $wday_end) {
|
|
$wday_range = 1;
|
|
} else {
|
|
$wday_end = $wday_start;
|
|
$wday_range = 0;
|
|
}
|
|
my @nums = ();
|
|
if ($wday_start <= $wday_end) {
|
|
push(@nums,$wday_start .. $wday_end);
|
|
} else {
|
|
push(@nums,$wday_start .. 7);
|
|
push(@nums,1 .. $wday_end);
|
|
}
|
|
my ($p2,$p_shift_wday);
|
|
if ($hour_start > 23 and $hour_end > 23) { #26-28
|
|
$p1->{hour} = ($hour_range ? ($hour_start % 24) . '-' . ($hour_end % 24) : ($hour_start % 24)); #2-4
|
|
$p_shift_wday = $p1;
|
|
} elsif ($hour_start < $hour_end) {
|
|
if ($hour_end > 23) { #17-23 +3-> 20-26
|
|
$p1->{hour} = $hour_start . '-23'; #20-0
|
|
$p2 = { %$time };
|
|
$p2->{hour} = '0-' . ($hour_end % 24); #0-2
|
|
$p_shift_wday = $p2;
|
|
}
|
|
} else {
|
|
if ($hour_start > 23) { #23-17 +3-> 26-20
|
|
$p1->{hour} = ($hour_start % 24) . '-' . $hour_end; #2-20
|
|
$p_shift_wday = $p1;
|
|
}
|
|
}
|
|
if ($p_shift_wday and (scalar @nums) < 7) {
|
|
$p_shift_wday->{wday} = ($wday_range ? (($wday_start) % 7 + 1) . '-' . (($wday_end) % 7 + 1) : (($wday_start) % 7 + 1));
|
|
}
|
|
push(@result,$p1);
|
|
push(@result,$p2) if $p2;
|
|
}
|
|
return ($merge ? merge_adjacent(\@result) : \@result);
|
|
|
|
}
|
|
|
|
sub subtract {
|
|
my ($times,$offset_hrs,$merge) = @_;
|
|
|
|
my @result = ();
|
|
foreach my $time (@$times) {
|
|
my $p1 = { %$time };
|
|
unless (length($time->{hour}) > 0) {
|
|
#nothing to do if there is no hour defined:
|
|
push(@result,$p1);
|
|
next;
|
|
}
|
|
my ($hour_start,$hour_end) = split(/\-/, $time->{hour});
|
|
my $hour_range;
|
|
if (defined $hour_end) {
|
|
$hour_range = 1;
|
|
} else {
|
|
$hour_end = $hour_start;
|
|
$hour_range = 0;
|
|
}
|
|
$hour_start -= abs($offset_hrs);
|
|
$hour_end -= abs($offset_hrs);
|
|
if ($hour_start >= 0 and $hour_end >= 0) {
|
|
$p1->{hour} = ($hour_range ? $hour_start . '-' . $hour_end : $hour_start);
|
|
push(@result,$p1);
|
|
next;
|
|
}
|
|
my ($wday_start, $wday_end) = split(/\-/, $time->{wday} || '1-7');
|
|
my $wday_range;
|
|
if (defined $wday_end) {
|
|
$wday_range = 1;
|
|
} else {
|
|
$wday_end = $wday_start;
|
|
$wday_range = 0;
|
|
}
|
|
my @nums = ();
|
|
if ($wday_start <= $wday_end) {
|
|
push(@nums,$wday_start .. $wday_end);
|
|
} else {
|
|
push(@nums,$wday_start .. 7);
|
|
push(@nums,1 .. $wday_end);
|
|
}
|
|
my ($p2,$p_shift_wday);
|
|
if ($hour_start < 0 and $hour_end < 0) { #-4 - -2
|
|
$p1->{hour} = ($hour_range ? ($hour_start % 24) . '-' . ($hour_end % 24) : ($hour_start % 24)); #20-22
|
|
$p_shift_wday = $p1;
|
|
} elsif ($hour_start < $hour_end) { #-4 - 3
|
|
if ($hour_start < 0) { #0-7 -4-> -4 - 3
|
|
$p1->{hour} = ($hour_start % 24) . '-23'; #20-0
|
|
$p2 = { %$time };
|
|
$p2->{hour} = '0-' . $hour_end; #0-3
|
|
$p_shift_wday = $p1;
|
|
}
|
|
} else {
|
|
if ($hour_end < 0) { #22 - 2 -6-> 16 - -4
|
|
$p1->{hour} = $hour_start . '-' . ($hour_end % 24); #16-20
|
|
$p_shift_wday = $p1;
|
|
}
|
|
}
|
|
if ($p_shift_wday and (scalar @nums) < 7) {
|
|
$p_shift_wday->{wday} = ($wday_range ? (($wday_start - 2) % 7 + 1) . '-' . (($wday_end - 2) % 7 + 1) : (($wday_start - 2) % 7 + 1));
|
|
}
|
|
push(@result,$p1);
|
|
push(@result,$p2) if $p2;
|
|
}
|
|
return ($merge ? merge_adjacent(\@result) : \@result);
|
|
|
|
}
|
|
|
|
sub merge_adjacent {
|
|
my ($times) = @_;
|
|
|
|
my ($wday_map,$wdays) = array_to_map($times,sub { my $time = shift;
|
|
my $wday = $time->{wday} || '1-7';
|
|
$wday = '1-7' if $time->{wday} eq '7-1';
|
|
$wday .= '_' . (defined $time->{minute} ? $time->{minute} : '*');
|
|
return $wday;
|
|
},undef,'group');
|
|
|
|
my @result = ();
|
|
my $idx = 0;
|
|
foreach my $wday (@$wdays) {
|
|
my %hour_start_map = ();
|
|
my %hour_end_map = ();
|
|
my %skip_map = ();
|
|
my $old_idx = $idx;
|
|
foreach my $time (@{$wday_map->{$wday}}) {
|
|
if (length($time->{hour}) > 0) {
|
|
my ($hour_start,$hour_end) = split(/\-/, $time->{hour});
|
|
$hour_end //= $hour_start;
|
|
if ($hour_end >= $hour_start) { #we do not create any adjacent roll-over hours, so we also skip such when merging
|
|
if (not defined $hour_start_map{$hour_start}
|
|
or $hour_end > $hour_start_map{$hour_start}->{hour_end}) {
|
|
$hour_start_map{$hour_start} = { hour_end => $hour_end, idx => $idx, };
|
|
} else {
|
|
$skip_map{$idx} = 0;
|
|
}
|
|
if (not defined $hour_end_map{$hour_end}
|
|
or $hour_start < $hour_end_map{$hour_end}->{hour_start}) {
|
|
$hour_end_map{$hour_end} = { hour_start => $hour_start, }; #, idx => $idx,
|
|
} else {
|
|
$skip_map{$idx} = 0;
|
|
}
|
|
} else {
|
|
$skip_map{$idx} = 1;
|
|
}
|
|
} else {
|
|
$skip_map{$idx} = 1;
|
|
}
|
|
$idx++;
|
|
}
|
|
$idx = $old_idx;
|
|
foreach my $time (@{$wday_map->{$wday}}) {
|
|
my $p = { %$time };
|
|
if (exists $skip_map{$idx}) {
|
|
push(@result,$p) if $skip_map{$idx};
|
|
} else {
|
|
my ($hour_start,$hour_end) = split(/\-/, $time->{hour});
|
|
$hour_end //= $hour_start;
|
|
#if ($hour_end_map{$hour_end}->{idx} == $idx) {
|
|
my $adjacent_start = $hour_end + 1;
|
|
if (exists $hour_start_map{$adjacent_start}) {
|
|
$p->{hour} = $hour_start . '-' . $hour_start_map{$adjacent_start}->{hour_end};
|
|
$skip_map{$hour_start_map{$adjacent_start}->{idx}} = 0;
|
|
}
|
|
push(@result,$p);
|
|
#}
|
|
}
|
|
$idx++;
|
|
}
|
|
}
|
|
return \@result;
|
|
}
|
|
|
|
sub array_to_map {
|
|
|
|
my ($array_ptr,$get_key_code,$get_value_code,$mode) = @_;
|
|
my $map = {};
|
|
my @keys = ();
|
|
my @values = ();
|
|
if (defined $array_ptr and ref $array_ptr eq 'ARRAY') {
|
|
if (defined $get_key_code and ref $get_key_code eq 'CODE') {
|
|
if (not (defined $get_value_code and ref $get_value_code eq 'CODE')) {
|
|
$get_value_code = sub { return shift; };
|
|
}
|
|
$mode = lc($mode);
|
|
if (not ($mode eq 'group' or $mode eq 'first' or $mode eq 'last')) {
|
|
$mode = 'group';
|
|
}
|
|
foreach my $item (@$array_ptr) {
|
|
my $key = &$get_key_code($item);
|
|
if (defined $key) {
|
|
my $value = &$get_value_code($item);
|
|
if (defined $value) {
|
|
if (not exists $map->{$key}) {
|
|
if ($mode eq 'group') {
|
|
$map->{$key} = [ $value ];
|
|
} else {
|
|
$map->{$key} = $value;
|
|
}
|
|
push(@keys,$key);
|
|
} else {
|
|
if ($mode eq 'group') {
|
|
push(@{$map->{$key}}, $value);
|
|
} elsif ($mode eq 'last') {
|
|
$map->{$key} = $value;
|
|
}
|
|
}
|
|
push(@values,$value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ($map,\@keys,\@values);
|
|
|
|
}
|
|
|
|
sub normalize_db_tz_name {
|
|
my $tz = shift;
|
|
if (defined $tz) {
|
|
if (lc($tz) eq 'localtime') {
|
|
$tz = 'local';
|
|
} elsif (lc($tz) eq 'system') {
|
|
$tz = 'local';
|
|
} # else { ... additional cases
|
|
}
|
|
return $tz;
|
|
}
|
|
|
|
sub current_local {
|
|
return DateTime->now(
|
|
time_zone => DateTime::TimeZone->new(name => 'local'),
|
|
);
|
|
}
|
|
|
|
sub print_item {
|
|
my ($item,$field,$str,$no_sep) = @_;
|
|
if ($item) {
|
|
$str .= ',' if (length($str) > 0 and not $no_sep);
|
|
$str .= ($item->{$field} // 'NULL');
|
|
}
|
|
return $str;
|
|
}
|