2019-04-23 19:30:31 +00:00
|
|
|
package Travelynx::Command::work;
|
2020-11-28 20:03:51 +00:00
|
|
|
|
2023-07-03 15:59:25 +00:00
|
|
|
# Copyright (C) 2020-2023 Birte Kristina Friesel
|
2020-11-27 21:12:56 +00:00
|
|
|
#
|
2021-01-29 17:32:13 +00:00
|
|
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
2019-04-23 19:30:31 +00:00
|
|
|
use Mojo::Base 'Mojolicious::Command';
|
2022-05-27 10:04:51 +00:00
|
|
|
use Mojo::Promise;
|
2019-04-23 19:30:31 +00:00
|
|
|
|
|
|
|
use DateTime;
|
2019-05-26 15:28:21 +00:00
|
|
|
use JSON;
|
2019-12-15 12:42:11 +00:00
|
|
|
use List::Util;
|
2019-04-23 19:30:31 +00:00
|
|
|
|
2023-01-02 04:59:16 +00:00
|
|
|
has description => 'Update real-time data of active journeys';
|
2019-04-23 19:30:31 +00:00
|
|
|
|
|
|
|
has usage => sub { shift->extract_usage };
|
|
|
|
|
|
|
|
sub run {
|
|
|
|
my ($self) = @_;
|
|
|
|
|
2021-03-07 18:06:21 +00:00
|
|
|
my $now = DateTime->now( time_zone => 'Europe/Berlin' );
|
|
|
|
my $checkin_deadline = $now->clone->subtract( hours => 48 );
|
|
|
|
my $json = JSON->new;
|
2019-04-23 19:30:31 +00:00
|
|
|
|
2024-06-14 19:49:49 +00:00
|
|
|
if ( -e 'maintenance' ) {
|
2024-06-15 06:06:28 +00:00
|
|
|
$self->app->log->debug('work: "maintenance" file found, aborting');
|
2024-06-14 19:49:49 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-02-20 12:30:51 +00:00
|
|
|
my $num_incomplete = $self->app->in_transit->delete_incomplete_checkins(
|
|
|
|
earlier_than => $checkin_deadline );
|
2019-04-23 19:30:31 +00:00
|
|
|
|
2022-02-20 12:30:51 +00:00
|
|
|
if ($num_incomplete) {
|
|
|
|
$self->app->log->debug("Removed ${num_incomplete} incomplete checkins");
|
2021-03-07 18:06:21 +00:00
|
|
|
}
|
|
|
|
|
2022-12-30 18:49:17 +00:00
|
|
|
my $errors = 0;
|
|
|
|
|
2022-02-20 12:30:51 +00:00
|
|
|
for my $entry ( $self->app->in_transit->get_all_active ) {
|
2019-04-23 19:30:31 +00:00
|
|
|
|
2024-06-12 19:53:35 +00:00
|
|
|
if ( -e 'maintenance' ) {
|
2024-06-15 06:06:28 +00:00
|
|
|
$self->app->log->debug('work: "maintenance" file found, aborting');
|
2024-06-12 19:53:35 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-05-02 08:05:49 +00:00
|
|
|
my $uid = $entry->{user_id};
|
2019-12-23 21:57:45 +00:00
|
|
|
my $dep = $entry->{dep_eva};
|
|
|
|
my $arr = $entry->{arr_eva};
|
2019-05-02 08:05:49 +00:00
|
|
|
my $train_id = $entry->{train_id};
|
2019-04-23 19:30:31 +00:00
|
|
|
|
2024-07-26 16:55:58 +00:00
|
|
|
if ( $entry->{is_hafas} ) {
|
2023-08-13 10:51:15 +00:00
|
|
|
|
2024-07-26 16:55:58 +00:00
|
|
|
$self->app->hafas->get_journey_p(
|
|
|
|
trip_id => $train_id,
|
|
|
|
service => $entry->{backend_name}
|
|
|
|
)->then(
|
2023-08-13 10:51:15 +00:00
|
|
|
sub {
|
|
|
|
my ($journey) = @_;
|
|
|
|
|
|
|
|
my $found_dep;
|
|
|
|
my $found_arr;
|
|
|
|
for my $stop ( $journey->route ) {
|
2023-12-27 09:59:35 +00:00
|
|
|
if ( $stop->loc->eva == $dep ) {
|
2023-08-13 10:51:15 +00:00
|
|
|
$found_dep = $stop;
|
|
|
|
}
|
2023-12-27 09:59:35 +00:00
|
|
|
if ( $arr and $stop->loc->eva == $arr ) {
|
2023-08-13 10:51:15 +00:00
|
|
|
$found_arr = $stop;
|
|
|
|
last;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ( not $found_dep ) {
|
2023-09-04 15:08:17 +00:00
|
|
|
$self->app->log->debug(
|
|
|
|
"Did not find $dep within journey $train_id");
|
2023-09-04 15:05:50 +00:00
|
|
|
return;
|
2023-08-13 10:51:15 +00:00
|
|
|
}
|
|
|
|
|
2024-05-29 19:49:31 +00:00
|
|
|
if ( $found_dep->rt_dep ) {
|
2023-08-13 10:51:15 +00:00
|
|
|
$self->app->in_transit->update_departure_hafas(
|
|
|
|
uid => $uid,
|
|
|
|
journey => $journey,
|
|
|
|
stop => $found_dep,
|
|
|
|
dep_eva => $dep,
|
|
|
|
arr_eva => $arr
|
|
|
|
);
|
2024-08-10 17:52:49 +00:00
|
|
|
if ( $entry->{backend_id} <= 1
|
|
|
|
and $journey->class <= 16
|
2024-05-29 19:49:31 +00:00
|
|
|
and $found_dep->rt_dep->epoch > $now->epoch )
|
|
|
|
{
|
2024-08-08 19:13:39 +00:00
|
|
|
$self->app->add_wagonorder(
|
|
|
|
uid => $uid,
|
|
|
|
train_id => $journey->id,
|
|
|
|
is_departure => 1,
|
|
|
|
eva => $dep,
|
|
|
|
datetime => $found_dep->sched_dep,
|
|
|
|
train_type => $journey->type,
|
|
|
|
train_no => $journey->number,
|
|
|
|
);
|
2024-05-30 09:25:52 +00:00
|
|
|
$self->app->add_stationinfo( $uid, 1, $journey->id,
|
|
|
|
$found_dep->loc->eva );
|
2024-05-29 19:49:31 +00:00
|
|
|
}
|
2023-08-13 10:51:15 +00:00
|
|
|
}
|
|
|
|
|
2024-05-29 19:49:31 +00:00
|
|
|
if ( $found_arr and $found_arr->rt_arr ) {
|
2023-08-13 10:51:15 +00:00
|
|
|
$self->app->in_transit->update_arrival_hafas(
|
|
|
|
uid => $uid,
|
|
|
|
journey => $journey,
|
|
|
|
stop => $found_arr,
|
|
|
|
dep_eva => $dep,
|
|
|
|
arr_eva => $arr
|
|
|
|
);
|
2024-08-10 17:52:49 +00:00
|
|
|
if ( $entry->{backend_id} <= 1
|
|
|
|
and $journey->class <= 16
|
2024-06-02 09:47:19 +00:00
|
|
|
and $found_arr->rt_arr->epoch - $now->epoch < 600 )
|
2024-05-29 19:49:31 +00:00
|
|
|
{
|
2024-08-08 19:13:39 +00:00
|
|
|
$self->app->add_wagonorder(
|
|
|
|
uid => $uid,
|
|
|
|
train_id => $journey->id,
|
|
|
|
is_arrival => 1,
|
|
|
|
eva => $arr,
|
|
|
|
datetime => $found_arr->sched_dep,
|
|
|
|
train_type => $journey->type,
|
|
|
|
train_no => $journey->number,
|
|
|
|
);
|
2024-05-30 09:25:52 +00:00
|
|
|
$self->app->add_stationinfo( $uid, 0, $journey->id,
|
|
|
|
$found_dep->loc->eva, $found_arr->loc->eva );
|
2024-05-29 19:49:31 +00:00
|
|
|
}
|
2023-08-13 10:51:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
)->catch(
|
|
|
|
sub {
|
|
|
|
my ($err) = @_;
|
2024-01-11 17:37:12 +00:00
|
|
|
if ( $err =~ m{svcResL\[0\][.]err is (?:FAIL|PARAMETER)$} )
|
|
|
|
{
|
|
|
|
# HAFAS do be weird. These are not actionable.
|
|
|
|
$self->app->log->debug("work($uid)/journey: $err");
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$self->app->log->error("work($uid)/journey: $err");
|
|
|
|
}
|
2023-08-13 10:51:15 +00:00
|
|
|
}
|
|
|
|
)->wait;
|
|
|
|
|
|
|
|
if ( $arr
|
|
|
|
and $entry->{real_arr_ts}
|
|
|
|
and $now->epoch - $entry->{real_arr_ts} > 600 )
|
|
|
|
{
|
|
|
|
$self->app->checkout_p(
|
|
|
|
station => $arr,
|
|
|
|
force => 2,
|
|
|
|
dep_eva => $dep,
|
|
|
|
arr_eva => $arr,
|
|
|
|
uid => $uid
|
|
|
|
)->wait;
|
|
|
|
}
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
|
2024-07-26 16:55:58 +00:00
|
|
|
# TODO irgendwo ist hier ne race condition wo ein neuer checkin (in HAFAS) mit IRIS-Daten überschrieben wird.
|
|
|
|
# Die ganzen updates brauchen wirklich mal sanity checks mit train id ...
|
|
|
|
|
2019-06-10 16:09:54 +00:00
|
|
|
# Note: IRIS data is not always updated in real-time. Both departure and
|
|
|
|
# arrival delays may take several minutes to appear, especially in case
|
|
|
|
# of large-scale disturbances. We work around this by continuing to
|
|
|
|
# update departure data for up to 15 minutes after departure and
|
|
|
|
# delaying automatic checkout by at least 10 minutes.
|
|
|
|
|
2019-04-23 19:30:31 +00:00
|
|
|
eval {
|
2019-06-10 16:09:54 +00:00
|
|
|
if ( $now->epoch - $entry->{real_dep_ts} < 900 ) {
|
2020-08-06 14:04:12 +00:00
|
|
|
my $status = $self->app->iris->get_departures(
|
|
|
|
station => $dep,
|
|
|
|
lookbehind => 30,
|
|
|
|
lookahead => 30
|
|
|
|
);
|
2019-04-23 19:30:31 +00:00
|
|
|
if ( $status->{errstr} ) {
|
|
|
|
die("get_departures($dep): $status->{errstr}\n");
|
|
|
|
}
|
|
|
|
|
2019-12-15 12:42:11 +00:00
|
|
|
my ($train) = List::Util::first { $_->train_id eq $train_id }
|
|
|
|
@{ $status->{results} };
|
2019-04-23 19:30:31 +00:00
|
|
|
|
|
|
|
if ( not $train ) {
|
2023-09-04 15:05:50 +00:00
|
|
|
$self->app->log->debug(
|
2022-12-23 20:20:32 +00:00
|
|
|
"could not find train $train_id at $dep\n");
|
|
|
|
return;
|
2019-04-23 19:30:31 +00:00
|
|
|
}
|
|
|
|
|
2022-02-20 12:30:51 +00:00
|
|
|
$self->app->in_transit->update_departure(
|
2023-03-10 15:57:31 +00:00
|
|
|
uid => $uid,
|
|
|
|
train => $train,
|
|
|
|
dep_eva => $dep,
|
|
|
|
arr_eva => $arr,
|
|
|
|
route => [ $self->app->iris->route_diff($train) ]
|
2019-04-23 19:30:31 +00:00
|
|
|
);
|
2022-02-20 12:30:51 +00:00
|
|
|
|
2020-02-17 20:13:07 +00:00
|
|
|
if ( $train->departure_is_cancelled and $arr ) {
|
2022-02-20 12:30:51 +00:00
|
|
|
my $checked_in
|
|
|
|
= $self->app->in_transit->update_departure_cancelled(
|
|
|
|
uid => $uid,
|
|
|
|
train => $train,
|
|
|
|
dep_eva => $dep,
|
|
|
|
arr_eva => $arr,
|
|
|
|
);
|
2020-02-17 20:13:07 +00:00
|
|
|
|
|
|
|
# depending on the amount of users in transit, some time may
|
|
|
|
# have passed between fetching $entry from the database and
|
2022-02-20 12:30:51 +00:00
|
|
|
# now. Only check out if the user is still checked into this
|
|
|
|
# train.
|
|
|
|
if ($checked_in) {
|
2020-02-17 20:13:07 +00:00
|
|
|
|
2023-07-23 18:18:10 +00:00
|
|
|
# check out (adds a cancelled journey and resets journey state
|
|
|
|
# to checkin
|
|
|
|
$self->app->checkout_p(
|
2020-09-30 17:12:29 +00:00
|
|
|
station => $arr,
|
2023-07-23 18:18:10 +00:00
|
|
|
force => 2,
|
2023-03-10 16:18:08 +00:00
|
|
|
dep_eva => $dep,
|
|
|
|
arr_eva => $arr,
|
2020-09-30 17:12:29 +00:00
|
|
|
uid => $uid
|
2023-07-23 18:18:10 +00:00
|
|
|
)->wait;
|
2020-02-17 20:13:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$self->app->add_route_timestamps( $uid, $train, 1 );
|
2024-08-08 19:13:39 +00:00
|
|
|
$self->app->add_wagonorder(
|
|
|
|
uid => $uid,
|
|
|
|
train_id => $train->train_id,
|
|
|
|
is_departure => 1,
|
|
|
|
eva => $dep,
|
|
|
|
datetime => $train->sched_departure,
|
|
|
|
train_type => $train->type,
|
|
|
|
train_no => $train->train_no
|
|
|
|
);
|
2024-05-30 09:25:52 +00:00
|
|
|
$self->app->add_stationinfo( $uid, 1, $train->train_id,
|
|
|
|
$dep, $arr );
|
2020-02-17 20:13:07 +00:00
|
|
|
}
|
2019-04-23 19:30:31 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
if ($@) {
|
2022-12-30 18:49:17 +00:00
|
|
|
$errors += 1;
|
2019-04-23 19:30:31 +00:00
|
|
|
$self->app->log->error("work($uid)/departure: $@");
|
|
|
|
}
|
|
|
|
|
|
|
|
eval {
|
|
|
|
if (
|
2019-12-23 21:57:45 +00:00
|
|
|
$arr
|
2019-04-23 19:30:31 +00:00
|
|
|
and ( not $entry->{real_arr_ts}
|
2019-06-10 16:09:54 +00:00
|
|
|
or $now->epoch - $entry->{real_arr_ts} < 600 )
|
2019-04-23 19:30:31 +00:00
|
|
|
)
|
|
|
|
{
|
2020-08-06 14:04:12 +00:00
|
|
|
my $status = $self->app->iris->get_departures(
|
|
|
|
station => $arr,
|
|
|
|
lookbehind => 20,
|
|
|
|
lookahead => 220
|
|
|
|
);
|
2019-04-23 19:30:31 +00:00
|
|
|
if ( $status->{errstr} ) {
|
|
|
|
die("get_departures($arr): $status->{errstr}\n");
|
|
|
|
}
|
|
|
|
|
2019-12-15 12:42:11 +00:00
|
|
|
# Note that a train may pass the same station several times.
|
|
|
|
# Notable example: S41 / S42 ("Ringbahn") both starts and
|
|
|
|
# terminates at Berlin Südkreuz
|
|
|
|
my ($train) = List::Util::first {
|
|
|
|
$_->train_id eq $train_id
|
|
|
|
and $_->sched_arrival
|
|
|
|
and $_->sched_arrival->epoch > $entry->{sched_dep_ts}
|
|
|
|
}
|
|
|
|
@{ $status->{results} };
|
|
|
|
|
|
|
|
$train //= List::Util::first { $_->train_id eq $train_id }
|
|
|
|
@{ $status->{results} };
|
2019-04-23 19:30:31 +00:00
|
|
|
|
|
|
|
if ( not $train ) {
|
2019-06-01 10:38:26 +00:00
|
|
|
|
|
|
|
# If we haven't seen the train yet, its arrival is probably
|
|
|
|
# too far in the future. This is not critical.
|
|
|
|
return;
|
2019-04-23 19:30:31 +00:00
|
|
|
}
|
|
|
|
|
2022-02-20 12:30:51 +00:00
|
|
|
my $checked_in = $self->app->in_transit->update_arrival(
|
|
|
|
uid => $uid,
|
|
|
|
train => $train,
|
|
|
|
route => [ $self->app->iris->route_diff($train) ],
|
2023-03-10 15:57:31 +00:00
|
|
|
dep_eva => $dep,
|
2022-02-20 12:30:51 +00:00
|
|
|
arr_eva => $arr,
|
2019-04-23 19:30:31 +00:00
|
|
|
);
|
2020-02-12 19:38:24 +00:00
|
|
|
|
2022-02-20 12:30:51 +00:00
|
|
|
if ( $checked_in and $train->arrival_is_cancelled ) {
|
|
|
|
|
2023-07-23 18:18:10 +00:00
|
|
|
# check out (adds a cancelled journey and resets journey state
|
|
|
|
# to destination selection)
|
|
|
|
$self->app->checkout_p(
|
2022-02-20 12:30:51 +00:00
|
|
|
station => $arr,
|
|
|
|
force => 0,
|
2023-03-10 16:18:08 +00:00
|
|
|
dep_eva => $dep,
|
|
|
|
arr_eva => $arr,
|
2022-02-20 12:30:51 +00:00
|
|
|
uid => $uid
|
2023-07-23 18:18:10 +00:00
|
|
|
)->wait;
|
2020-02-12 19:38:24 +00:00
|
|
|
}
|
|
|
|
else {
|
2023-04-06 13:16:52 +00:00
|
|
|
$self->app->add_route_timestamps(
|
|
|
|
$uid, $train, 0,
|
|
|
|
(
|
|
|
|
defined $entry->{real_arr_ts}
|
|
|
|
and $now->epoch > $entry->{real_arr_ts}
|
|
|
|
) ? 1 : 0
|
|
|
|
);
|
2024-08-08 19:13:39 +00:00
|
|
|
$self->app->add_wagonorder(
|
|
|
|
uid => $uid,
|
|
|
|
train_id => $train->train_id,
|
|
|
|
is_arrival => 1,
|
|
|
|
eva => $arr,
|
|
|
|
datetime => $train->sched_departure,
|
|
|
|
train_type => $train->type,
|
|
|
|
train_no => $train->train_no
|
|
|
|
);
|
2024-05-30 09:25:52 +00:00
|
|
|
$self->app->add_stationinfo( $uid, 0, $train->train_id,
|
|
|
|
$dep, $arr );
|
2020-02-12 19:38:24 +00:00
|
|
|
}
|
2019-04-23 19:30:31 +00:00
|
|
|
}
|
|
|
|
elsif ( $entry->{real_arr_ts} ) {
|
2023-07-23 18:18:10 +00:00
|
|
|
my ( undef, $error ) = $self->app->checkout_p(
|
2020-09-30 17:12:29 +00:00
|
|
|
station => $arr,
|
2023-07-23 18:18:10 +00:00
|
|
|
force => 2,
|
2023-03-10 16:18:08 +00:00
|
|
|
dep_eva => $dep,
|
|
|
|
arr_eva => $arr,
|
2020-09-30 17:12:29 +00:00
|
|
|
uid => $uid
|
2023-07-23 18:18:10 +00:00
|
|
|
)->catch(
|
|
|
|
sub {
|
|
|
|
my ($error) = @_;
|
2023-08-13 10:51:15 +00:00
|
|
|
$self->app->log->error("work($uid)/arrival: $error");
|
2023-07-23 18:18:10 +00:00
|
|
|
$errors += 1;
|
|
|
|
}
|
|
|
|
)->wait;
|
2019-04-23 19:30:31 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
if ($@) {
|
|
|
|
$self->app->log->error("work($uid)/arrival: $@");
|
2023-07-23 18:18:10 +00:00
|
|
|
$errors += 1;
|
2019-04-23 19:30:31 +00:00
|
|
|
}
|
|
|
|
|
2023-08-13 10:51:15 +00:00
|
|
|
eval { };
|
2019-04-23 19:30:31 +00:00
|
|
|
}
|
2019-05-18 15:11:28 +00:00
|
|
|
|
2022-12-30 15:46:18 +00:00
|
|
|
my $started_at = $now;
|
|
|
|
my $main_finished_at = DateTime->now( time_zone => 'Europe/Berlin' );
|
2023-01-02 04:59:16 +00:00
|
|
|
my $worker_duration = $main_finished_at->epoch - $started_at->epoch;
|
2022-12-30 15:27:51 +00:00
|
|
|
|
|
|
|
if ( $self->app->config->{influxdb}->{url} ) {
|
2023-01-07 12:07:29 +00:00
|
|
|
if ( $self->app->mode eq 'development' ) {
|
|
|
|
$self->app->log->debug( 'POST '
|
|
|
|
. $self->app->config->{influxdb}->{url}
|
|
|
|
. " worker runtime_seconds=${worker_duration},errors=${errors}"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$self->app->ua->post_p( $self->app->config->{influxdb}->{url},
|
|
|
|
"worker runtime_seconds=${worker_duration},errors=${errors}" )
|
|
|
|
->wait;
|
|
|
|
}
|
2023-01-02 04:59:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if ( not $self->app->config->{traewelling}->{separate_worker} ) {
|
|
|
|
$self->app->start('traewelling');
|
2022-12-30 15:27:51 +00:00
|
|
|
}
|
2024-06-23 10:47:41 +00:00
|
|
|
|
|
|
|
# add_wagonorder and add_stationinfo assume a permanently running IOLoop
|
|
|
|
# and do not allow Mojolicious commands to wait until they have completed.
|
|
|
|
# Hence, some add_wagonorder and add_stationinfo calls made here may not
|
|
|
|
# complete before the work command exits, and thus have no effect.
|
|
|
|
#
|
|
|
|
# This is not ideal and will need fixing at some point. Until then, here
|
|
|
|
# is the pragmatic solution for 99% of the associated issues.
|
|
|
|
Mojo::Promise->timer(5)->wait;
|
2019-04-23 19:30:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
1;
|
|
|
|
|
|
|
|
__END__
|
|
|
|
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
|
|
|
|
Usage: index.pl work
|
|
|
|
|
|
|
|
Work Work Work.
|
|
|
|
|
|
|
|
Should be called from a cronjob every three minutes or so.
|