summaryrefslogtreecommitdiff
path: root/lib/Travelynx.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Travelynx.pm')
-rwxr-xr-xlib/Travelynx.pm801
1 files changed, 378 insertions, 423 deletions
diff --git a/lib/Travelynx.pm b/lib/Travelynx.pm
index fd59cc0..da10a9a 100755
--- a/lib/Travelynx.pm
+++ b/lib/Travelynx.pm
@@ -191,9 +191,10 @@ sub startup {
$self->helper(
'get_departures' => sub {
- my ( $self, $station, $lookbehind ) = @_;
+ my ( $self, $station, $lookbehind, $lookahead ) = @_;
$lookbehind //= 180;
+ $lookahead //= 30;
my @station_matches
= Travel::Status::DE::IRIS::Stations::get_station($station);
@@ -207,7 +208,7 @@ sub startup {
lookbehind => 20,
datetime => DateTime->now( time_zone => 'Europe/Berlin' )
->subtract( minutes => $lookbehind ),
- lookahead => $lookbehind + 10,
+ lookahead => $lookbehind + $lookahead,
);
return {
results => [ $status->results ],
@@ -241,6 +242,10 @@ sub startup {
'add_journey' => sub {
my ( $self, %opt ) = @_;
+ $self->app->log->error(
+ "add_journey is not implemented at the moment");
+ return ( undef, undef, 'not implemented' );
+
my $user_status = $self->get_user_status;
if ( $user_status->{checked_in} or $user_status->{cancelled} ) {
@@ -326,11 +331,9 @@ sub startup {
$self->helper(
'checkin' => sub {
- my ( $self, $station, $train_id, $action_id ) = @_;
-
- $action_id //= $self->app->action_type->{checkin};
+ my ( $self, $station, $train_id ) = @_;
- my $status = $self->get_departures($station);
+ my $status = $self->get_departures( $station, 140, 30 );
if ( $status->{errstr} ) {
return ( undef, $status->{errstr} );
}
@@ -343,40 +346,35 @@ sub startup {
else {
my $user = $self->get_user_status;
- if ( $user->{checked_in} ) {
+ if ( $user->{checked_in} or $user->{cancelled} ) {
# If a user is already checked in, we assume that they forgot to
# check out and do it for them.
$self->checkout( $station, 1 );
}
- elsif ( $user->{cancelled} ) {
-
- # Same
- $self->checkout( $station, 1,
- $self->app->action_type->{cancelled_to} );
- }
eval {
$self->pg->db->insert(
- 'user_actions',
+ 'in_transit',
{
- user_id => $self->current_user->{id},
- action_id => $action_id,
- station_id => $self->get_station_id(
+ user_id => $self->current_user->{id},
+ cancelled => $train->departure_is_cancelled
+ ? 1
+ : 0,
+ checkin_station_id => $self->get_station_id(
ds100 => $status->{station_ds100},
name => $status->{station_name}
),
- action_time =>
+ checkin_time =>
DateTime->now( time_zone => 'Europe/Berlin' ),
- edited => 0,
- train_type => $train->type,
- train_line => $train->line_no,
- train_no => $train->train_no,
- train_id => $train->train_id,
- sched_time => $train->sched_departure,
- real_time => $train->departure,
- route => join( '|', $train->route ),
- messages => join(
+ train_type => $train->type,
+ train_line => $train->line_no,
+ train_no => $train->train_no,
+ train_id => $train->train_id,
+ sched_departure => $train->sched_departure,
+ real_departure => $train->departure,
+ route => join( '|', $train->route ),
+ messages => join(
'|',
map {
( $_->[0] ? $_->[0]->epoch : q{} ) . ':'
@@ -389,7 +387,7 @@ sub startup {
if ($@) {
my $uid = $self->current_user->{id};
$self->app->log->error(
- "Checkin($uid, $action_id): INSERT failed: $@");
+ "Checkin($uid): INSERT failed: $@");
return ( undef, 'INSERT failed: ' . $@ );
}
return ( $train, undef );
@@ -400,43 +398,86 @@ sub startup {
$self->helper(
'undo' => sub {
- my ( $self, $action_id ) = @_;
-
- my $status = $self->get_user_status;
+ my ( $self, $journey_id ) = @_;
+ my $uid = $self->current_user->{id};
- if ( $action_id < 1 or $status->{action_id} != $action_id ) {
- return
-"Invalid action ID: $action_id != $status->{action_id}. Note that you can only undo your latest action.";
+ if ( $journey_id eq 'in_transit' ) {
+ eval {
+ $self->pg->db->delete( 'in_transit', { user_id => $uid } );
+ };
+ if ($@) {
+ $self->app->log->error("Undo($uid, $journey_id): $@");
+ return "Undo($journey_id): $@";
+ }
+ return undef;
+ }
+ if ( $journey_id !~ m{ ^ \d+ $ }x ) {
+ return 'Invalid Journey ID';
}
eval {
- $self->pg->db->delete( 'user_actions', { id => $action_id } );
+ my $db = $self->pg->db;
+ my $tx = $db->begin;
+
+ my $journey = $db->select(
+ 'journeys',
+ '*',
+ {
+ user_id => $uid,
+ id => $journey_id
+ }
+ )->hash;
+ $db->delete(
+ 'journeys',
+ {
+ user_id => $uid,
+ id => $journey_id
+ }
+ );
+
+ if ( $journey->{edited} ) {
+ die(
+"Cannot undo a journey which has already been edited. Please delete manually.\n"
+ );
+ }
+
+ delete $journey->{edited};
+ delete $journey->{id};
+
+ $db->insert( 'in_transit', $journey );
+
+ my $cache_ts = DateTime->now( time_zone => 'Europe/Berlin' );
+ if ( $journey->{real_departure}
+ =~ m{ ^ (?<year> \d{4} ) - (?<month> \d{2} ) }x )
+ {
+ $cache_ts->set(
+ year => $+{year},
+ month => $+{month}
+ );
+ }
+
+ $self->invalidate_stats_cache( $cache_ts, $db );
+
+ $tx->commit;
};
if ($@) {
- my $uid = $self->current_user->{id};
- $self->app->log->error(
- "Undo($uid, $action_id): DELETE failed: $@");
- return 'DELETE failed: ' . $@;
+ $self->app->log->error("Undo($uid, $journey_id): $@");
+ return "Undo($journey_id): $@";
}
- return;
+ return undef;
}
);
+ # Statistics are partitioned by real_departure, which must be provided
+ # when calling this function e.g. after journey deletion or editing.
+ # If a joureny's real_departure has been edited, this function must be
+ # called twice: once with the old and once with the new value.
$self->helper(
'invalidate_stats_cache' => sub {
- my ( $self, $ts ) = @_;
+ my ( $self, $ts, $db ) = @_;
my $uid = $self->current_user->{id};
- $ts //= DateTime->now( time_zone => 'Europe/Berlin' );
-
- # ts is the checkout timestamp or (for manual entries) the
- # time of arrival. As the journey may span a month or year boundary,
- # there is a total of five cache entries we need to invalidate:
- # * year, month
- # * year
- # * (year, month) - 1 month (with wraparound)
- # * (year) - 1 year
- # * total stats
+ $db //= $self->pg->db;
$self->pg->db->delete(
'journey_stats',
@@ -454,196 +495,207 @@ sub startup {
month => 0,
}
);
- $ts->subtract( months => 1 );
- $self->pg->db->delete(
- 'journey_stats',
- {
- user_id => $uid,
- year => $ts->year,
- month => $ts->month,
- }
- );
- $ts->subtract( months => 11 );
- $self->pg->db->delete(
- 'journey_stats',
- {
- user_id => $uid,
- year => $ts->year,
- month => 0,
- }
- );
- $self->pg->db->delete(
- 'journey_stats',
- {
- user_id => $uid,
- year => 0,
- month => 0,
- }
- );
}
);
$self->helper(
'checkout' => sub {
- my ( $self, $station, $force, $action_id ) = @_;
-
- $action_id //= $self->app->action_type->{checkout};
+ my ( $self, $station, $force ) = @_;
- my $status = $self->get_departures( $station, 180 );
+ my $db = $self->pg->db;
+ my $uid = $self->current_user->{id};
+ my $status = $self->get_departures( $station, 120, 120 );
my $user = $self->get_user_status;
my $train_id = $user->{train_id};
if ( not $user->{checked_in} and not $user->{cancelled} ) {
- return 'You are not checked into any train';
+ return ( 0, 'You are not checked into any train' );
}
if ( $status->{errstr} and not $force ) {
- return $status->{errstr};
+ return ( 1, $status->{errstr} );
}
my $now = DateTime->now( time_zone => 'Europe/Berlin' );
+ my $journey
+ = $db->select( 'in_transit', '*', { user_id => $uid } )->hash;
my ($train)
= first { $_->train_id eq $train_id } @{ $status->{results} };
- if ( not defined $train ) {
- if ($force) {
- eval {
- $self->pg->db->insert(
- 'user_actions',
- {
- user_id => $self->current_user->{id},
- action_id => $action_id,
- station_id => $self->get_station_id(
- ds100 => $status->{station_ds100},
- name => $status->{station_name}
- ),
- action_time => $now,
- edited => 0
- }
- );
- };
- if ($@) {
- my $uid = $self->current_user->{id};
- $self->app->log->error(
-"Force checkout($uid, $action_id): INSERT failed: $@"
- );
- return 'INSERT failed: ' . $@;
- }
- $self->invalidate_stats_cache;
- return;
- }
- else {
- return "Train ${train_id} not found";
- }
+
+ # Store the intended checkout station regardless of this operation's
+ # success.
+ my $new_checkout_station_id = $self->get_station_id(
+ ds100 => $status->{station_ds100},
+ name => $status->{station_name}
+ );
+ $db->update(
+ 'in_transit',
+ {
+ checkout_station_id => $new_checkout_station_id,
+ },
+ { user_id => $uid }
+ );
+
+ # If in_transit already contains arrival data for another estimated
+ # destination, we must invalidate it.
+ if ( defined $journey->{checkout_station_id}
+ and $journey->{checkout_station_id}
+ != $new_checkout_station_id )
+ {
+ $db->update(
+ 'in_transit',
+ {
+ checkout_time => undef,
+ sched_arrival => undef,
+ real_arrival => undef,
+ },
+ { user_id => $uid }
+ );
}
- else {
- eval {
- $self->pg->db->insert(
- 'user_actions',
+
+ if ( not( defined $train or $force ) ) {
+ return ( 1, undef );
+ }
+
+ my $has_arrived = 0;
+
+ eval {
+
+ my $tx = $db->begin;
+
+ if ( defined $train ) {
+ $has_arrived = $train->arrival->epoch < $now->epoch ? 1 : 0;
+ $db->update(
+ 'in_transit',
{
- user_id => $self->current_user->{id},
- action_id => $action_id,
- station_id => $self->get_station_id(
- ds100 => $status->{station_ds100},
- name => $status->{station_name}
- ),
- action_time => $now,
- edited => 0,
- train_type => $train->type,
- train_line => $train->line_no,
- train_no => $train->train_no,
- train_id => $train->train_id,
- sched_time => $train->sched_arrival,
- real_time => $train->arrival,
- route => join( '|', $train->route ),
- messages => join(
+ checkout_time => $now,
+ sched_arrival => $train->sched_arrival,
+ real_arrival => $train->arrival,
+ cancelled => $train->arrival_is_cancelled ? 1 : 0,
+ route => join( '|', $train->route ),
+ messages => join(
'|',
map {
( $_->[0] ? $_->[0]->epoch : q{} ) . ':'
. $_->[1]
} $train->messages
- )
- }
+ ),
+ },
+ { user_id => $uid }
);
- };
- if ($@) {
- my $uid = $self->current_user->{id};
- $self->app->log->error(
- "Checkout($uid, $action_id): INSERT failed: $@");
- return 'INSERT failed: ' . $@;
}
- $self->invalidate_stats_cache;
- return;
+
+ $journey
+ = $db->select( 'in_transit', '*', { user_id => $uid } )->hash;
+
+ if ( $has_arrived or $force ) {
+ $journey->{edited} = 0;
+ $journey->{checkout_time} = $now;
+ $db->insert( 'journeys', $journey );
+ $db->delete( 'in_transit', { user_id => $uid } );
+
+ my $cache_ts = $now->clone;
+ if ( $journey->{real_departure}
+ =~ m{ ^ (?<year> \d{4} ) - (?<month> \d{2} ) }x )
+ {
+ $cache_ts->set(
+ year => $+{year},
+ month => $+{month}
+ );
+ }
+ $self->invalidate_stats_cache( $cache_ts, $db );
+ }
+
+ $tx->commit;
+ };
+
+ if ($@) {
+ $self->app->log->error("Checkout($uid): $@");
+ return ( 1, 'Checkout error: ' . $@ );
}
+
+ if ( $has_arrived or $force ) {
+ return ( 0, undef );
+ }
+ return ( 1, undef );
}
);
$self->helper(
'update_journey_part' => sub {
- my ( $self, $db, $checkin_id, $checkout_id, $key, $value ) = @_;
+ my ( $self, $db, $journey_id, $key, $value ) = @_;
my $rows;
+ my $journey = $self->get_journey(
+ db => $db,
+ journey_id => $journey_id,
+ );
+
eval {
if ( $key eq 'sched_departure' ) {
$rows = $db->update(
- 'user_actions',
+ 'journeys',
{
- sched_time => $value,
+ sched_departure => $value,
+ edited => $journey->{edited} | 0x0001,
},
{
- id => $checkin_id,
- action_id => $self->app->action_type->{checkin},
+ id => $journey_id,
}
)->rows;
}
elsif ( $key eq 'rt_departure' ) {
$rows = $db->update(
- 'user_actions',
+ 'journeys',
{
- real_time => $value,
+ real_departure => $value,
+ edited => $journey->{edited} | 0x0002,
},
{
- id => $checkin_id,
- action_id => $self->app->action_type->{checkin},
+ id => $journey_id,
}
)->rows;
+
+ # stats are partitioned by rt_departure -> both the cache for
+ # the old value (see bottom of this function) and the new value
+ # (here) must be invalidated.
+ $self->invalidate_stats_cache( $value, $db );
}
elsif ( $key eq 'sched_arrival' ) {
$rows = $db->update(
- 'user_actions',
+ 'journeys',
{
- sched_time => $value,
+ sched_arrival => $value,
+ edited => $journey->{edited} | 0x0100,
},
{
- id => $checkout_id,
- action_id => $self->app->action_type->{checkout},
+ id => $journey_id,
}
)->rows;
}
elsif ( $key eq 'rt_arrival' ) {
$rows = $db->update(
- 'user_actions',
+ 'journeys',
{
- real_time => $value,
+ real_arrival => $value,
+ edited => $journey->{edited} | 0x0200,
},
{
- id => $checkout_id,
- action_id => $self->app->action_type->{checkout},
+ id => $journey_id,
}
)->rows;
}
else {
- $self->app->log->error(
-"update_journey_part($checkin_id, $checkout_id): Invalid key $key"
- );
+ die("Invalid key $key\n");
}
};
if ($@) {
$self->app->log->error(
-"update_journey_part($checkin_id, $checkout_id): UPDATE failed: $@"
- );
- return 'UPDATE failed: ' . $@;
+ "update_journey_part($journey_id, $key): $@");
+ return "update_journey_part($key): $@";
}
if ( $rows == 1 ) {
+ $self->invalidate_stats_cache( $journey->{rt_departure}, $db );
return undef;
}
return 'UPDATE failed: did not match any journey part';
@@ -930,14 +982,12 @@ sub startup {
$self->helper(
'delete_journey' => sub {
- my ( $self, $checkin_id, $checkout_id, $checkin_epoch,
- $checkout_epoch )
- = @_;
+ my ( $self, $journey_id, $checkin_epoch, $checkout_epoch ) = @_;
my $uid = $self->current_user->{id};
my @journeys = $self->get_user_travels(
- uid => $uid,
- checkout_id => $checkout_id
+ uid => $uid,
+ journey_id => $journey_id
);
if ( @journeys == 0 ) {
return 'Journey not found';
@@ -947,8 +997,7 @@ sub startup {
# Double-check (comparing both ID and action epoch) to make sure we
# are really deleting the right journey and the user isn't just
# playing around with POST requests.
- if ( $journey->{ids}[0] != $checkin_id
- or $journey->{ids}[1] != $checkout_id
+ if ( $journey->{id} != $journey_id
or $journey->{checkin}->epoch != $checkin_epoch
or $journey->{checkout}->epoch != $checkout_epoch )
{
@@ -958,26 +1007,24 @@ sub startup {
my $rows;
eval {
$rows = $self->pg->db->delete(
- 'user_actions',
+ 'journeys',
{
user_id => $uid,
- id => [ $checkin_id, $checkout_id ]
+ id => $journey_id,
}
)->rows;
};
if ($@) {
- $self->app->log->error(
- "Delete($uid, $checkin_id, $checkout_id): DELETE failed: $@"
- );
+ $self->app->log->error("Delete($uid, $journey_id): $@");
return 'DELETE failed: ' . $@;
}
- if ( $rows == 2 ) {
- $self->invalidate_stats_cache( $journey->{checkout} );
+ if ( $rows == 1 ) {
+ $self->invalidate_stats_cache( $journey->{rt_departure} );
return undef;
}
- return sprintf( 'Deleted %d rows, expected 2', $rows );
+ return sprintf( 'Deleted %d rows, expected 1', $rows );
}
);
@@ -1075,203 +1122,103 @@ sub startup {
# Otherwise, we grab a fresh one.
my $db = $opt{db} // $self->pg->db;
- my $selection = qq{
- user_actions.id as action_log_id, action_id,
- extract(epoch from action_time) as action_time_ts,
- stations.ds100 as ds100, stations.name as name,
- train_type, train_line, train_no, train_id,
- extract(epoch from sched_time) as sched_time_ts,
- extract(epoch from real_time) as real_time_ts,
- route, messages, edited
- };
- $selection =~ tr{\n}{}d;
- my %where = ( user_id => $uid );
+ my %where = (
+ user_id => $uid,
+ cancelled => 0
+ );
my %order = (
order_by => {
- -desc => 'action_time',
+ -desc => 'real_dep_ts',
}
);
- if ( $opt{limit} ) {
- $order{limit} = 10;
+ if ( $opt{cancelled} ) {
+ $where{cancelled} = 1;
}
- if ( $opt{checkout_id} ) {
- $where{'user_actions.id'} = { '<=', $opt{checkout_id} };
- $order{limit} = 2;
+ if ( $opt{limit} ) {
+ $order{limit} = $opt{limit};
}
- elsif ( $opt{after} and $opt{before} ) {
- # Each journey consists of exactly two database entries: one for
- # checkin, one for checkout. A simple query using e.g.
- # after = YYYY-01-01T00:00:00 and before YYYY-02-01T00:00:00
- # will miss journeys where checkin and checkout take place in
- # different months.
- # We therefore add one day to the before timestamp and filter out
- # journeys whose checkin lies outside the originally requested
- # time range afterwards.
- # For an additional twist, get_interval_actions_query filters based
- # on the action time, not actual departure, as force
- # checkout actions lack sched_time and real_time data. By
- # subtracting one day from "after" (i.e., moving it one day into
- # the past), we make sure not to miss journeys where the real departure
- # time falls into the interval, but the checkin time does not.
- # Again, this is addressed in postprocessing at the bottom of this
- # helper.
- # This works under the assumption that there are no DB trains whose
- # journey takes more than 24 hours. If this no longer holds,
- # please adjust the intervals accordingly.
- $where{action_time} = {
- -between => [
- $opt{after}->clone->subtract( days => 1 ),
- $opt{before}->clone->add( days => 1 )
- ]
- };
+ if ( $opt{journey_id} ) {
+ $where{journey_id} = $opt{journey_id};
+ delete $where{cancelled};
}
-
- my @match_actions = (
- $self->app->action_type->{checkout},
- $self->app->action_type->{checkin}
- );
- if ( $opt{cancelled} ) {
- @match_actions = (
- $self->app->action_type->{cancelled_to},
- $self->app->action_type->{cancelled_from}
- );
+ elsif ( $opt{after} and $opt{before} ) {
+ $where{real_dep_ts} = {
+ -between => [ $opt{after}->epoch, $opt{before}->epoch, ] };
}
my @travels;
- my $prev_action = 0;
- my $res = $db->select(
- [
- 'user_actions',
- [
- -left => 'stations',
- id => 'station_id'
- ]
- ],
- $selection,
- \%where,
- \%order
- );
+ my $res = $db->select( 'journeys_str', '*', \%where, \%order );
for my $entry ( $res->hashes->each ) {
- if ( $entry->{action_id} == $match_actions[0]
- or ( $opt{checkout_id} and not @travels ) )
- {
- push(
- @travels,
- {
- ids => [ undef, $entry->{action_log_id} ],
- to_name => $entry->{name},
- sched_arrival =>
- epoch_to_dt( $entry->{sched_time_ts} ),
- rt_arrival => epoch_to_dt( $entry->{real_time_ts} ),
- checkout => epoch_to_dt( $entry->{action_time_ts} ),
- type => $entry->{train_type},
- line => $entry->{train_line},
- no => $entry->{train_no},
- messages => $entry->{messages}
- ? [ split( qr{[|]}, $entry->{messages} ) ]
- : undef,
- route => $entry->{route}
- ? [ split( qr{[|]}, $entry->{route} ) ]
- : undef,
- completed => 0,
- edited => $entry->{edited} << 8,
- }
- );
- }
- elsif (
- (
- $entry->{action_id} == $match_actions[1]
- and $prev_action == $match_actions[0]
- )
- or $opt{checkout_id}
- )
- {
- my $ref = $travels[-1];
- $ref->{ids}->[0] = $entry->{action_log_id};
- $ref->{from_name} = $entry->{name};
- $ref->{completed} = 1;
- $ref->{sched_departure}
- = epoch_to_dt( $entry->{sched_time_ts} );
- $ref->{rt_departure}
- = epoch_to_dt( $entry->{real_time_ts} );
- $ref->{checkin} = epoch_to_dt( $entry->{action_time_ts} );
- $ref->{type} //= $entry->{train_type};
- $ref->{line} //= $entry->{train_line};
- $ref->{no} //= $entry->{train_no};
- $ref->{messages}
- //= [ split( qr{[|]}, $entry->{messages} ) ];
- $ref->{route} //= [ split( qr{[|]}, $entry->{route} ) ];
- $ref->{edited} |= $entry->{edited};
-
- if ( $opt{verbose} ) {
- my @parsed_messages;
- for my $message ( @{ $ref->{messages} // [] } ) {
- my ( $ts, $msg ) = split( qr{:}, $message );
- push( @parsed_messages,
- [ epoch_to_dt($ts), $msg ] );
- }
- $ref->{messages} = [ reverse @parsed_messages ];
- $ref->{sched_duration}
- = $ref->{sched_arrival}
- ? $ref->{sched_arrival}->epoch
- - $ref->{sched_departure}->epoch
- : undef;
- $ref->{rt_duration}
- = $ref->{rt_arrival}
- ? $ref->{rt_arrival}->epoch
- - $ref->{rt_departure}->epoch
- : undef;
- my ( $km, $skip )
- = $self->get_travel_distance( $ref->{from_name},
- $ref->{to_name}, $ref->{route} );
- $ref->{km_route} = $km;
- $ref->{skip_route} = $skip;
- ( $km, $skip )
- = $self->get_travel_distance( $ref->{from_name},
- $ref->{to_name},
- [ $ref->{from_name}, $ref->{to_name} ] );
- $ref->{km_beeline} = $km;
- $ref->{skip_beeline} = $skip;
- my $kmh_divisor
- = ( $ref->{rt_duration} // $ref->{sched_duration}
- // 999999 ) / 3600;
- $ref->{kmh_route}
- = $kmh_divisor ? $ref->{km_route} / $kmh_divisor : -1;
- $ref->{kmh_beeline}
- = $kmh_divisor
- ? $ref->{km_beeline} / $kmh_divisor
- : -1;
- }
- if ( $opt{checkout_id}
- and $entry->{action_id}
- == $self->app->action_type->{cancelled_from} )
- {
- $ref->{cancelled} = 1;
+ my $ref = {
+ id => $entry->{journey_id},
+ type => $entry->{train_type},
+ line => $entry->{train_line},
+ no => $entry->{train_no},
+ from_name => $entry->{dep_name},
+ checkin => epoch_to_dt( $entry->{checkin_ts} ),
+ sched_departure => epoch_to_dt( $entry->{sched_dep_ts} ),
+ rt_departure => epoch_to_dt( $entry->{real_dep_ts} ),
+ to_name => $entry->{arr_name},
+ checkout => epoch_to_dt( $entry->{checkout_ts} ),
+ sched_arrival => epoch_to_dt( $entry->{sched_arr_ts} ),
+ rt_arrival => epoch_to_dt( $entry->{real_arr_ts} ),
+ messages => $entry->{messages}
+ ? [ split( qr{[|]}, $entry->{messages} ) ]
+ : undef,
+ route => $entry->{route}
+ ? [ split( qr{[|]}, $entry->{route} ) ]
+ : undef,
+ edited => $entry->{edited},
+ };
+
+ if ( $opt{verbose} ) {
+ $ref->{cancelled} = $entry->{cancelled};
+ my @parsed_messages;
+ for my $message ( @{ $ref->{messages} // [] } ) {
+ my ( $ts, $msg ) = split( qr{:}, $message );
+ push( @parsed_messages, [ epoch_to_dt($ts), $msg ] );
}
+ $ref->{messages} = [ reverse @parsed_messages ];
+ $ref->{sched_duration}
+ = $ref->{sched_arrival}
+ ? $ref->{sched_arrival}->epoch
+ - $ref->{sched_departure}->epoch
+ : undef;
+ $ref->{rt_duration}
+ = $ref->{rt_arrival}
+ ? $ref->{rt_arrival}->epoch - $ref->{rt_departure}->epoch
+ : undef;
+ my ( $km, $skip )
+ = $self->get_travel_distance( $ref->{from_name},
+ $ref->{to_name}, $ref->{route} );
+ $ref->{km_route} = $km;
+ $ref->{skip_route} = $skip;
+ ( $km, $skip )
+ = $self->get_travel_distance( $ref->{from_name},
+ $ref->{to_name},
+ [ $ref->{from_name}, $ref->{to_name} ] );
+ $ref->{km_beeline} = $km;
+ $ref->{skip_beeline} = $skip;
+ my $kmh_divisor
+ = ( $ref->{rt_duration} // $ref->{sched_duration}
+ // 999999 ) / 3600;
+ $ref->{kmh_route}
+ = $kmh_divisor ? $ref->{km_route} / $kmh_divisor : -1;
+ $ref->{kmh_beeline}
+ = $kmh_divisor
+ ? $ref->{km_beeline} / $kmh_divisor
+ : -1;
}
- $prev_action = $entry->{action_id};
- }
- if ( $opt{before} and $opt{after} ) {
- @travels = grep {
- $_->{rt_departure} >= $opt{after}
- and $_->{rt_departure} < $opt{before}
- } @travels;
+ push( @travels, $ref );
}
- # user_actions are sorted by action_time. As users are allowed to check
- # into trains in arbitrary order, action_time does not always
- # correspond to departure/arrival time, so we ensure a proper sort
- # order here.
- @travels
- = sort { $b->{rt_departure} <=> $a->{rt_departure} } @travels;
-
return @travels;
}
);
@@ -1280,11 +1227,9 @@ sub startup {
'get_journey' => sub {
my ( $self, %opt ) = @_;
+ $opt{cancelled} = 'any';
my @journeys = $self->get_user_travels(%opt);
- if ( @journeys == 0
- or not $journeys[0]{completed}
- or $journeys[0]{ids}[1] != $opt{checkout_id} )
- {
+ if ( @journeys == 0 ) {
return undef;
}
@@ -1298,86 +1243,96 @@ sub startup {
$uid //= $self->current_user->{id};
- my $selection = qq{
- user_actions.id as action_log_id, action_id,
- extract(epoch from action_time) as action_time_ts,
- stations.ds100 as ds100, stations.name as name,
- train_type, train_line, train_no, train_id,
- extract(epoch from sched_time) as sched_time_ts,
- extract(epoch from real_time) as real_time_ts,
- route
- };
- $selection =~ tr{\n}{}d;
+ my $db = $self->pg->db;
+ my $now = DateTime->now( time_zone => 'Europe/Berlin' );
- my $res = $self->pg->db->select(
- [
- 'user_actions',
- [
- -left => 'stations',
- id => 'station_id'
- ]
- ],
- $selection,
- {
- user_id => $uid,
- },
- {
- order_by => {
- -desc => 'action_time',
- },
- limit => 1,
- }
- );
- my $status = $res->hash;
+ my $in_transit
+ = $db->select( 'in_transit_str', '*', { user_id => $uid } )->hash;
- if ($status) {
- my $now = DateTime->now( time_zone => 'Europe/Berlin' );
+ if ($in_transit) {
- my $action_ts = epoch_to_dt( $status->{action_time_ts} );
- my $sched_ts = epoch_to_dt( $status->{sched_time_ts} );
- my $real_ts = epoch_to_dt( $status->{real_time_ts} );
- my $checkin_station_name = $status->{name};
- my @route = split( qr{[|]}, $status->{route} // q{} );
+ my @route = split( qr{[|]}, $in_transit->{route} // q{} );
my @route_after;
my $is_after = 0;
for my $station (@route) {
- if ( $station eq $checkin_station_name ) {
+ if ( $station eq $in_transit->{dep_name} ) {
$is_after = 1;
}
if ($is_after) {
push( @route_after, $station );
}
}
+
+ my $ts = $in_transit->{checkout_ts}
+ // $in_transit->{checkin_ts};
+ my $action_time = epoch_to_dt($ts);
+
return {
- checked_in => (
- $status->{action_id}
- == $self->app->action_type->{checkin}
- ),
- cancelled => (
- $status->{action_id}
- == $self->app->action_type->{cancelled_from}
- ),
- timestamp => $action_ts,
- timestamp_delta => $now->epoch - $action_ts->epoch,
- action_id => $status->{action_log_id},
- sched_ts => $sched_ts,
- real_ts => $real_ts,
- station_ds100 => $status->{ds100},
- station_name => $checkin_station_name,
- train_type => $status->{train_type},
- train_line => $status->{train_line},
- train_no => $status->{train_no},
- train_id => $status->{train_id},
- route => \@route,
- route_after => \@route_after,
+ checked_in => !$in_transit->{cancelled},
+ cancelled => $in_transit->{cancelled},
+ timestamp => $action_time,
+ timestamp_delta => $now->epoch - $action_time->epoch,
+ train_type => $in_transit->{train_type},
+ train_line => $in_transit->{train_line},
+ train_no => $in_transit->{train_no},
+ train_id => $in_transit->{train_id},
+ sched_departure =>
+ epoch_to_dt( $in_transit->{sched_dep_ts} ),
+ real_departure => epoch_to_dt( $in_transit->{real_dep_ts} ),
+ dep_ds100 => $in_transit->{dep_ds100},
+ dep_name => $in_transit->{dep_name},
+ sched_arrival => epoch_to_dt( $in_transit->{sched_arr_ts} ),
+ real_arrival => epoch_to_dt( $in_transit->{real_arr_ts} ),
+ arr_ds100 => $in_transit->{arr_ds100},
+ arr_name => $in_transit->{arr_name},
+ route_after => \@route_after,
};
}
+
+ my $latest = $db->select(
+ 'journeys_str',
+ '*',
+ {
+ user_id => $uid,
+ cancelled => 0
+ },
+ {
+ order_by => { -desc => 'journey_id' },
+ limit => 1
+ }
+ )->hash;
+
+ if ($latest) {
+ my $ts = $latest->{checkout_ts};
+ my $action_time = epoch_to_dt($ts);
+ return {
+ checked_in => 0,
+ cancelled => 0,
+ journey_id => $latest->{journey_id},
+ timestamp => $action_time,
+ timestamp_delta => $now->epoch - $action_time->epoch,
+ train_type => $latest->{train_type},
+ train_line => $latest->{train_line},
+ train_no => $latest->{train_no},
+ train_id => $latest->{train_id},
+ sched_departure => epoch_to_dt( $latest->{sched_dep_ts} ),
+ real_departure => epoch_to_dt( $latest->{real_dep_ts} ),
+ dep_ds100 => $latest->{dep_ds100},
+ dep_name => $latest->{dep_name},
+ sched_arrival => epoch_to_dt( $latest->{sched_arr_ts} ),
+ real_arrival => epoch_to_dt( $latest->{real_arr_ts} ),
+ arr_ds100 => $latest->{arr_ds100},
+ arr_name => $latest->{arr_name},
+ };
+ }
+
return {
- checked_in => 0,
- timestamp => epoch_to_dt(0),
- sched_ts => epoch_to_dt(0),
- real_ts => epoch_to_dt(0),
+ checked_in => 0,
+ cancelled => 0,
+ no_journeys_yet => 1,
+ timestamp => epoch_to_dt(0),
+ timestamp_delta => $now->epoch,
};
}
);