From e54130ad6ea805cc04db17a3f614cb4bd25ca3f0 Mon Sep 17 00:00:00 2001 From: Daniel Friesel Date: Sat, 24 Sep 2022 18:53:04 +0200 Subject: optionally show local transit connections as well --- lib/Travelynx/Command/database.pm | 23 +++ lib/Travelynx/Command/maintenance.pm | 1 + lib/Travelynx/Controller/Account.pm | 32 +++- lib/Travelynx/Controller/Traveling.pm | 285 ++++++++++++++++++++-------------- lib/Travelynx/Model/Users.pm | 27 +++- templates/_checked_in.html.ep | 7 + templates/_transit_fyi.html.ep | 19 +++ templates/account.html.ep | 2 +- templates/use_history.html.ep | 24 +++ 9 files changed, 293 insertions(+), 127 deletions(-) create mode 100644 templates/_transit_fyi.html.ep diff --git a/lib/Travelynx/Command/database.pm b/lib/Travelynx/Command/database.pm index d580c05..33612c3 100644 --- a/lib/Travelynx/Command/database.pm +++ b/lib/Travelynx/Command/database.pm @@ -1083,6 +1083,29 @@ my @migrations = ( } ); }, + + # v25 -> v26 + # travelynx 1.24 adds local transit connections and needs to know targets + # for that to work, as local transit does not support checkins yet. + sub { + my ($db) = @_; + $db->query( + qq{ + create table localtransit ( + user_id integer not null references users (id) primary key, + data jsonb + ); + create view user_transit as select + id, + use_history, + localtransit.data as data + from users + left join localtransit on localtransit.user_id = id + ; + update schema_version set version = 26; + } + ); + }, ); sub setup_db { diff --git a/lib/Travelynx/Command/maintenance.pm b/lib/Travelynx/Command/maintenance.pm index d6380fd..60667b7 100644 --- a/lib/Travelynx/Command/maintenance.pm +++ b/lib/Travelynx/Command/maintenance.pm @@ -149,6 +149,7 @@ sub run { my $transit_res = $db->delete( 'in_transit', { user_id => $uid } ); my $hooks_res = $db->delete( 'webhooks', { user_id => $uid } ); my $trwl_res = $db->delete( 'traewelling', { user_id => $uid } ); + my $lt_res = $db->delete( 'localtransit', { user_id => $uid } ); my $password_res = $db->delete( 'pending_passwords', { user_id => $uid } ); my $user_res = $db->delete( 'users', { id => $uid } ); diff --git a/lib/Travelynx/Controller/Account.pm b/lib/Travelynx/Controller/Account.pm index d55b470..12b179b 100644 --- a/lib/Travelynx/Controller/Account.pm +++ b/lib/Travelynx/Controller/Account.pm @@ -6,7 +6,7 @@ package Travelynx::Controller::Account; use Mojo::Base 'Mojolicious::Controller'; use Crypt::Eksblowfish::Bcrypt qw(bcrypt en_base64); -use UUID::Tiny qw(:std); +use UUID::Tiny qw(:std); # Internal Helpers @@ -498,8 +498,11 @@ sub privacy { sub insight { my ($self) = @_; - my $user = $self->current_user; - my $use_history = $self->users->use_history( uid => $user->{id} ); + my $user = $self->current_user; + my ( $use_history, $destinations ) = $self->users->use_history( + uid => $user->{id}, + with_local_transit => 1 + ); if ( $self->param('action') and $self->param('action') eq 'save' ) { if ( $self->param('on_departure') ) { @@ -516,16 +519,31 @@ sub insight { $use_history &= ~0x02; } + if ( $self->param('local_transit') ) { + $use_history |= 0x04; + } + else { + $use_history &= ~0x04; + } + + if ( $self->param('destinations') ) { + $destinations + = [ split( qr{\r?\n\r?}, $self->param('destinations') ) ]; + } + $self->users->use_history( - uid => $user->{id}, - set => $use_history + uid => $user->{id}, + set => $use_history, + destinations => $destinations ); $self->flash( success => 'use_history' ); $self->redirect_to('account'); } - $self->param( on_departure => $use_history & 0x01 ? 1 : 0 ); - $self->param( on_arrival => $use_history & 0x02 ? 1 : 0 ); + $self->param( on_departure => $use_history & 0x01 ? 1 : 0 ); + $self->param( on_arrival => $use_history & 0x02 ? 1 : 0 ); + $self->param( local_transit => $use_history & 0x04 ? 1 : 0 ); + $self->param( destinations => join( "\n", @{$destinations} ) ); $self->render('use_history'); } diff --git a/lib/Travelynx/Controller/Traveling.pm b/lib/Travelynx/Controller/Traveling.pm index bdf57e2..a4fe72b 100755 --- a/lib/Travelynx/Controller/Traveling.pm +++ b/lib/Travelynx/Controller/Traveling.pm @@ -28,8 +28,11 @@ sub has_str_in_list { sub get_connecting_trains_p { my ( $self, %opt ) = @_; - my $uid = $opt{uid} //= $self->current_user->{id}; - my $use_history = $self->users->use_history( uid => $uid ); + my $uid = $opt{uid} //= $self->current_user->{id}; + my ( $use_history, $lt_stops ) = $self->users->use_history( + uid => $uid, + with_local_transit => 1 + ); my ( $eva, $exclude_via, $exclude_train_id, $exclude_before ); my $now = $self->now->epoch; @@ -72,7 +75,7 @@ sub get_connecting_trains_p { @destinations = grep { $_ ne $exclude_via } @destinations; } - if ( not @destinations ) { + if ( not( @destinations or $use_history & 0x04 and @{$lt_stops} ) ) { return $promise->reject; } @@ -82,44 +85,45 @@ sub get_connecting_trains_p { my $iris_promise = Mojo::Promise->new; - $self->iris->get_departures_p( - station => $eva, - lookbehind => 10, - lookahead => $lookahead, - with_related => 1 - )->then( - sub { - my ($stationboard) = @_; - if ( $stationboard->{errstr} ) { - $iris_promise->reject( $stationboard->{errstr} ); - return; - } - - @{ $stationboard->{results} } = map { $_->[0] } - sort { $a->[1] <=> $b->[1] } - map { [ $_, $_->departure ? $_->departure->epoch : 0 ] } - @{ $stationboard->{results} }; - my @results; - my @cancellations; - my $excluded_train; - my %via_count = map { $_ => 0 } @destinations; - for my $train ( @{ $stationboard->{results} } ) { - if ( not $train->departure ) { - next; - } - if ( $exclude_before - and $train->departure - and $train->departure->epoch < $exclude_before ) - { - next; - } - if ( $exclude_train_id - and $train->train_id eq $exclude_train_id ) - { - $excluded_train = $train; - next; + if (@destinations) { + $self->iris->get_departures_p( + station => $eva, + lookbehind => 10, + lookahead => $lookahead, + with_related => 1 + )->then( + sub { + my ($stationboard) = @_; + if ( $stationboard->{errstr} ) { + $iris_promise->reject( $stationboard->{errstr} ); + return; } + @{ $stationboard->{results} } = map { $_->[0] } + sort { $a->[1] <=> $b->[1] } + map { [ $_, $_->departure ? $_->departure->epoch : 0 ] } + @{ $stationboard->{results} }; + my @results; + my @cancellations; + my $excluded_train; + my %via_count = map { $_ => 0 } @destinations; + for my $train ( @{ $stationboard->{results} } ) { + if ( not $train->departure ) { + next; + } + if ( $exclude_before + and $train->departure + and $train->departure->epoch < $exclude_before ) + { + next; + } + if ( $exclude_train_id + and $train->train_id eq $exclude_train_id ) + { + $excluded_train = $train; + next; + } + # In general, this function is meant to return feasible # connections. However, cancelled connections may also be of # interest and are also useful for logging cancellations. @@ -136,97 +140,105 @@ sub get_connecting_trains_p { # the route. Also note that this specific case is not yet handled # properly by the cancellation logic etc. - if ( $train->departure_is_cancelled ) { - my @via - = ( $train->sched_route_post, $train->sched_route_end ); - for my $dest (@destinations) { - if ( has_str_in_list( $dest, @via ) ) { - push( @cancellations, [ $train, $dest ] ); - next; + if ( $train->departure_is_cancelled ) { + my @via = ( + $train->sched_route_post, $train->sched_route_end + ); + for my $dest (@destinations) { + if ( has_str_in_list( $dest, @via ) ) { + push( @cancellations, [ $train, $dest ] ); + next; + } } } - } - else { - my @via = ( $train->route_post, $train->route_end ); - for my $dest (@destinations) { - if ( $via_count{$dest} < 2 - and has_str_in_list( $dest, @via ) ) - { - push( @results, [ $train, $dest ] ); + else { + my @via = ( $train->route_post, $train->route_end ); + for my $dest (@destinations) { + if ( $via_count{$dest} < 2 + and has_str_in_list( $dest, @via ) ) + { + push( @results, [ $train, $dest ] ); # Show all past and up to two future departures per destination - if ( not $train->departure - or $train->departure->epoch >= $now ) - { - $via_count{$dest}++; + if ( not $train->departure + or $train->departure->epoch >= $now ) + { + $via_count{$dest}++; + } + next; } - next; } } } - } - - @results = map { $_->[0] } - sort { $a->[1] <=> $b->[1] } - map { - [ - $_, - $_->[0]->departure->epoch - // $_->[0]->sched_departure->epoch - ] - } @results; - @cancellations = map { $_->[0] } - sort { $a->[1] <=> $b->[1] } - map { [ $_, $_->[0]->sched_departure->epoch ] } @cancellations; - - # remove trains whose route matches the excluded one's - if ($excluded_train) { - my $route_pre = join( '|', reverse $excluded_train->route_pre ); - @results - = grep { join( '|', $_->[0]->route_post ) ne $route_pre } - @results; - my $route_post = join( '|', $excluded_train->route_post ); - @results - = grep { join( '|', $_->[0]->route_post ) ne $route_post } - @results; - } - # add message IDs and 'transfer short' hints - for my $result (@results) { - my $train = $result->[0]; - my @message_ids - = List::Util::uniq map { $_->[1] } $train->raw_messages; - $train->{message_id} = { map { $_ => 1 } @message_ids }; - my $interchange_duration; - if ( exists $stationinfo->{i} ) { - $interchange_duration - = $stationinfo->{i}{$arr_platform}{ $train->platform }; - $interchange_duration //= $stationinfo->{i}{"*"}; + @results = map { $_->[0] } + sort { $a->[1] <=> $b->[1] } + map { + [ + $_, + $_->[0]->departure->epoch + // $_->[0]->sched_departure->epoch + ] + } @results; + @cancellations = map { $_->[0] } + sort { $a->[1] <=> $b->[1] } + map { [ $_, $_->[0]->sched_departure->epoch ] } + @cancellations; + + # remove trains whose route matches the excluded one's + if ($excluded_train) { + my $route_pre + = join( '|', reverse $excluded_train->route_pre ); + @results + = grep { join( '|', $_->[0]->route_post ) ne $route_pre } + @results; + my $route_post = join( '|', $excluded_train->route_post ); + @results + = grep { join( '|', $_->[0]->route_post ) ne $route_post } + @results; } - if ( defined $interchange_duration ) { - my $interchange_time - = ( $train->departure->epoch - $arr_epoch ) / 60; - if ( $interchange_time < $interchange_duration ) { - $train->{interchange_text} = 'Anschluss knapp'; - $train->{interchange_icon} = 'directions_run'; + + # add message IDs and 'transfer short' hints + for my $result (@results) { + my $train = $result->[0]; + my @message_ids + = List::Util::uniq map { $_->[1] } $train->raw_messages; + $train->{message_id} = { map { $_ => 1 } @message_ids }; + my $interchange_duration; + if ( exists $stationinfo->{i} ) { + $interchange_duration + = $stationinfo->{i}{$arr_platform} + { $train->platform }; + $interchange_duration //= $stationinfo->{i}{"*"}; } - elsif ( $interchange_time == $interchange_duration ) { - $train->{interchange_text} - = 'Anschluss könnte knapp werden'; - $train->{interchange_icon} = 'directions_run'; + if ( defined $interchange_duration ) { + my $interchange_time + = ( $train->departure->epoch - $arr_epoch ) / 60; + if ( $interchange_time < $interchange_duration ) { + $train->{interchange_text} = 'Anschluss knapp'; + $train->{interchange_icon} = 'directions_run'; + } + elsif ( $interchange_time == $interchange_duration ) { + $train->{interchange_text} + = 'Anschluss könnte knapp werden'; + $train->{interchange_icon} = 'directions_run'; + } } } - } - $iris_promise->resolve( [ @results, @cancellations ] ); - return; - } - )->catch( - sub { - $iris_promise->reject(@_); - return; - } - )->wait; + $iris_promise->resolve( [ @results, @cancellations ] ); + return; + } + )->catch( + sub { + $iris_promise->reject(@_); + return; + } + )->wait; + } + else { + $iris_promise->resolve( [] ); + } my $hafas_promise = Mojo::Promise->new; my $rest_api = $self->config->{backend}{hafas_rest_api}; @@ -254,6 +266,7 @@ sub get_connecting_trains_p { my ( $iris, $hafas ) = @_; my @iris_trains = @{ $iris->[0] }; my @hafas_trains = @{ $hafas->[0] }; + my @transit_fyi; my $strp = DateTime::Format::Strptime->new( pattern => '%Y-%m-%dT%H:%M:%S%z', @@ -296,6 +309,44 @@ sub get_connecting_trains_p { } } } + if ( $use_history & 0x04 and @{$lt_stops} ) { + my %via_count = map { $_ => 0 } @{$lt_stops}; + for my $hafas_train (@hafas_trains) { + for + my $stop ( @{ $hafas_train->{nextStopovers} // [] } ) + { + for my $dest ( @{$lt_stops} ) { + if ( $stop->{stop}{name} + and $stop->{stop}{name} eq $dest + and $via_count{$dest} < 2 + and $hafas_train->{when} ) + { + my $departure = $strp->parse_datetime( + $hafas_train->{when} ); + my $arrival + = $strp->parse_datetime( + $stop->{arrival} ); + if ( $departure->epoch >= $exclude_before ) + { + $via_count{$dest}++; + push( + @transit_fyi, + [ + { + line => + $hafas_train->{line} + {name}, + departure => $departure, + }, + $dest, $arrival + ] + ); + } + } + } + } + } + } }; if ($@) { $self->app->log->error( @@ -303,7 +354,7 @@ sub get_connecting_trains_p { ); } - $promise->resolve( \@iris_trains ); + $promise->resolve( \@iris_trains, \@transit_fyi ); return; } )->catch( diff --git a/lib/Travelynx/Model/Users.pm b/lib/Travelynx/Model/Users.pm index dbd2a00..c36fa6d 100644 --- a/lib/Travelynx/Model/Users.pm +++ b/lib/Travelynx/Model/Users.pm @@ -9,6 +9,7 @@ use warnings; use 5.020; use DateTime; +use JSON; my @sb_templates = ( undef, @@ -483,12 +484,34 @@ sub use_history { my $uid = $opt{uid}; my $value = $opt{set}; + if ( $opt{destinations} ) { + $db->insert( + 'localtransit', + { + user_id => $uid, + data => + JSON->new->encode( { destinations => $opt{destinations} } ) + }, + { on_conflict => \'(user_id) do update set data = EXCLUDED.data' } + ); + } + if ($value) { $db->update( 'users', { use_history => $value }, { id => $uid } ); } else { - return $db->select( 'users', ['use_history'], { id => $uid } ) - ->hash->{use_history}; + if ( $opt{with_local_transit} ) { + my $res = $db->select( + 'user_transit', + [ 'use_history', 'data' ], + { id => $uid } + )->expand->hash; + return ( $res->{use_history}, $res->{data}{destinations} // [] ); + } + else { + return $db->select( 'users', ['use_history'], { id => $uid } ) + ->hash->{use_history}; + } } } diff --git a/templates/_checked_in.html.ep b/templates/_checked_in.html.ep index 040c5de..dcf9ddc 100644 --- a/templates/_checked_in.html.ep +++ b/templates/_checked_in.html.ep @@ -200,6 +200,13 @@ % } %= include '_connections', connections => \@connections, checkin_from => $journey->{arrival_countdown} < 0 ? $journey->{arr_ds100} : undef; % } + % if (my @transit_fyi = @{stash('transit_fyi') // []}) { + Nahverkehr + % if ($journey->{arrival_countdown} < 0) { +

Nur zur Information – kein Checkin möglich.

+ % } + %= include '_transit_fyi', transit_fyi => \@transit_fyi; + % } % if (defined $journey->{arrival_countdown} and $journey->{arrival_countdown} <= 0) {

Der automatische Checkout erfolgt wegen gelegentlich veralteter diff --git a/templates/_transit_fyi.html.ep b/templates/_transit_fyi.html.ep new file mode 100644 index 0000000..42233e5 --- /dev/null +++ b/templates/_transit_fyi.html.ep @@ -0,0 +1,19 @@ + + % for my $res (@{$transit_fyi}) { + % my ($info, $via, $via_arr) = @{$res}; + % $via_arr = $via_arr ? $via_arr->strftime('%H:%M') : q{}; + + + + + % } +
+ %= $info->{line} +
+ %= $info->{departure}->strftime('%H:%M') + % if ($info->{departure_delay}) { + %= sprintf('(%+d)', $info->{departure_delay}) + % } +
+ <%= $via %>
<%= $via_arr %> +
diff --git a/templates/account.html.ep b/templates/account.html.ep index 7658237..418291c 100644 --- a/templates/account.html.ep +++ b/templates/account.html.ep @@ -61,7 +61,7 @@ Verbindungen edit - % if ($use_history & 0x03) { + % if ($use_history & 0x07) { Vorschläge aktiv % } % else { diff --git a/templates/use_history.html.ep b/templates/use_history.html.ep index e8e129f..1632b8b 100644 --- a/templates/use_history.html.ep +++ b/templates/use_history.html.ep @@ -47,6 +47,30 @@ ohne Umweg über die Abfahrtstafel möglich. +

+
+ +
+
+
+
+ Zeige beim Reisestatus zusätzlich Anschlussmöglichkeiten an den + Nahverkehr. Diese dienen lediglich zur Information; ein Checkin ist + nicht möglich. Es werden nur Anschlussmöglichkeiten zu Zielen + angezeigt, die im folgenden Feld gelistet sind (ein Ziel pro + Zeile, z.B. „Eichlinghofen H-Bahn, Dortmund“). Falls travelynx in + Zukunft eine Möglichkeit für Checkins in Nahverkehrsmittel erhält, + wird diese Liste ggf. gelöscht. +
+
+
+
+ %= text_area 'destinations', id => 'destinations', class => 'materialize-textarea' +
+
-- cgit v1.2.3