summaryrefslogtreecommitdiff
path: root/lib/DBInfoscreen.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/DBInfoscreen.pm')
-rw-r--r--lib/DBInfoscreen.pm442
1 files changed, 219 insertions, 223 deletions
diff --git a/lib/DBInfoscreen.pm b/lib/DBInfoscreen.pm
index 3b3d5c1..18a2c87 100644
--- a/lib/DBInfoscreen.pm
+++ b/lib/DBInfoscreen.pm
@@ -1,66 +1,68 @@
package DBInfoscreen;
-use Mojo::Base 'Mojolicious';
-# Copyright (C) 2011-2019 Daniel Friesel <derf+dbf@finalrewind.org>
-# License: 2-Clause BSD
+# Copyright (C) 2011-2020 Birte Kristina Friesel
+#
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+use Mojo::Base 'Mojolicious';
use Cache::File;
+use DBInfoscreen::Helper::DBRIS;
+use DBInfoscreen::Helper::EFA;
use DBInfoscreen::Helper::HAFAS;
+use DBInfoscreen::Helper::MOTIS;
use DBInfoscreen::Helper::Wagonorder;
use File::Slurp qw(read_file);
use JSON;
-use Travel::Status::DE::HAFAS;
-use Travel::Status::DE::HAFAS::StopFinder;
-use Travel::Status::DE::IRIS::Stations;
use utf8;
-no if $] >= 5.018, warnings => 'experimental::smartmatch';
-
-our $VERSION = qx{git describe --dirty} || '0.05';
+sub startup {
+ my ($self) = @_;
-chomp $VERSION;
+ $self->config(
+ hypnotoad => {
+ accepts => $ENV{DBFAKEDISPLAY_ACCEPTS} // 100,
+ clients => $ENV{DBFAKEDISPLAY_CLIENTS} // 10,
+ listen => [ $ENV{DBFAKEDISPLAY_LISTEN} // 'http://*:8092' ],
+ pid_file => $ENV{DBFAKEDISPLAY_PID_FILE}
+ // '/tmp/db-fakedisplay.pid',
+ spare => $ENV{DBFAKEDISPLAY_SPARE} // 2,
+ workers => $ENV{DBFAKEDISPLAY_WORKERS} // 2,
+ },
+ lookahead => $ENV{DBFAKEDISPLAY_LOOKAHEAD} // 180,
+ source_url => 'https://github.com/derf/db-fakedisplay',
+ issue_url => 'https://github.com/derf/db-fakedisplay/issues',
+ version => $ENV{DBFAKEDISPLAY_VERSION} // qx{git describe --dirty}
+ // '???',
+ );
-my %default = (
- backend => 'iris',
- mode => 'app',
- admode => 'deparr',
-);
+ chomp $self->config->{version};
+ $self->defaults( version => $self->config->{version} // 'UNKNOWN' );
-sub startup {
- my ($self) = @_;
+ # Generally, the reverse proxy handles compression.
+ # Also, Mojolicious compression breaks legacy callback-based JSON endpoints
+ # for some clients.
+ $self->renderer->compress(0);
$self->hook(
before_dispatch => sub {
my ($self) = @_;
- # The "theme" cookie is set client-side if the theme we delivered was
- # changed by dark mode detection or by using the theme switcher. It's
- # not part of Mojolicious' session data (and can't be, due to
- # signing and HTTPOnly), so we need to add it here.
+ # The "theme" cookie is set client-side if the theme we delivered was
+ # changed by dark mode detection or by using the theme switcher. It's
+ # not part of Mojolicious' session data (and can't be, due to
+ # signing and HTTPOnly), so we need to add it here.
for my $cookie ( @{ $self->req->cookies } ) {
if ( $cookie->name eq 'theme' ) {
$self->session( theme => $cookie->value );
- return;
}
}
}
);
$self->attr(
- cache_hafas => sub {
- my ($self) = @_;
- return Cache::File->new(
- cache_root => $ENV{DBFAKEDISPLAY_HAFAS_CACHE}
- // '/tmp/dbf-hafas',
- default_expires => '180 seconds',
- lock_level => Cache::File::LOCK_LOCAL(),
- );
- }
- );
-
- $self->attr(
cache_iris_main => sub {
my ($self) = @_;
return Cache::File->new(
@@ -85,16 +87,51 @@ sub startup {
);
$self->attr(
- ice_type_map => sub {
- my $ice_type_map = JSON->new->utf8->decode(
- scalar read_file('share/ice_type.json') );
- my $ret;
- while ( my ( $k, $v ) = each %{$ice_type_map} ) {
- if ( $v->{type} ) {
- $ret->{$k} = [ $v->{type}, $v->{short} ];
- }
- }
- return $ret;
+ dbdb_wagon => sub {
+ return JSON->new->utf8->decode(
+ scalar read_file('share/dbdb_wagen.json') );
+ }
+ );
+
+ $self->helper(
+ dbris => sub {
+ my ($self) = @_;
+ state $efa = DBInfoscreen::Helper::DBRIS->new(
+ log => $self->app->log,
+ main_cache => $self->app->cache_iris_main,
+ realtime_cache => $self->app->cache_iris_rt,
+ root_url => $self->url_for('/')->to_abs,
+ user_agent => $self->ua,
+ version => $self->config->{version},
+ );
+ }
+ );
+
+ $self->helper(
+ motis => sub {
+ my ($self) = @_;
+ state $motis = DBInfoscreen::Helper::MOTIS->new(
+ log => $self->app->log,
+ main_cache => $self->app->cache_iris_main,
+ realtime_cache => $self->app->cache_iris_rt,
+ root_url => $self->url_for('/')->to_abs,
+ user_agent => $self->ua,
+ version => $self->config->{version},
+ );
+ }
+ );
+
+ $self->helper(
+ efa => sub {
+ my ($self) = @_;
+ state $efa = DBInfoscreen::Helper::EFA->new(
+ log => $self->app->log,
+ main_cache => $self->app->cache_iris_main,
+ realtime_cache => $self->app->cache_iris_rt,
+ root_url => $self->url_for('/')->to_abs,
+ user_agent => $self->ua,
+ version => $self->config->{version},
+ );
}
);
@@ -107,7 +144,7 @@ sub startup {
realtime_cache => $self->app->cache_iris_rt,
root_url => $self->url_for('/')->to_abs,
user_agent => $self->ua,
- version => $VERSION,
+ version => $self->config->{version},
);
}
);
@@ -116,116 +153,45 @@ sub startup {
wagonorder => sub {
my ($self) = @_;
state $hafas = DBInfoscreen::Helper::Wagonorder->new(
- log => $self->app->log,
- cache => $self->app->cache_iris_main,
- root_url => $self->url_for('/')->to_abs,
- user_agent => $self->ua,
- version => $VERSION,
+ log => $self->app->log,
+ main_cache => $self->app->cache_iris_main,
+ realtime_cache => $self->app->cache_iris_rt,
+ root_url => $self->url_for('/')->to_abs,
+ user_agent => $self->ua,
+ version => $self->config->{version},
);
}
);
$self->helper(
- 'handle_no_results' => sub {
- my ( $self, $backend, $station, $errstr ) = @_;
-
- if ( $backend eq 'ris' ) {
- my $db_service = Travel::Status::DE::HAFAS::get_service('DB');
- my $sf = Travel::Status::DE::HAFAS::StopFinder->new(
- url => $db_service->{stopfinder},
- input => $station,
- );
- my @candidates
- = map { [ $_->{name}, $_->{id} ] } $sf->results;
- if ( @candidates > 1
- or ( @candidates == 1 and $candidates[0][1] ne $station ) )
- {
- $self->render(
- 'landingpage',
- stationlist => \@candidates,
- hide_opts => 0
- );
- return;
- }
+ wagon_image => sub {
+ my ( $self, $train_type, $wagon_type, $uic ) = @_;
+ my $ret;
+ if ( $train_type =~ m{IC(?!E)}
+ and $wagon_type
+ =~ m{ ^ [AB] R? k? [ipv] m m? b? d? s? z f? $ }x )
+ {
+ $ret = $wagon_type;
}
- if ( $backend eq 'iris' ) {
- my @candidates = map { [ $_->[1], $_->[0] ] }
- Travel::Status::DE::IRIS::Stations::get_station($station);
- if ( @candidates > 1
- or ( @candidates == 1 and $candidates[0][1] ne $station ) )
- {
- $self->render(
- 'landingpage',
- stationlist => \@candidates,
- hide_opts => 0
- );
- return;
- }
+ elsif ( $train_type =~ m{IC2.TWIN} ) {
+ $ret = $wagon_type;
}
- $self->render(
- 'landingpage',
- error => ( $errstr // "Got no results for '$station'" ),
- hide_opts => 0
- );
- return;
- }
- );
-
- $self->helper(
- 'handle_no_results_json' => sub {
- my ( $self, $backend, $station, $errstr, $api_version ) = @_;
-
- my $callback = $self->param('callback');
-
- $self->res->headers->access_control_allow_origin(q{*});
- my $json;
- if ($errstr) {
- $json = $self->render_to_string(
- json => {
- api_version => $api_version,
- version => $VERSION,
- error => $errstr,
- }
- );
+ elsif ( not $uic ) {
+ return;
}
else {
- my @candidates = map { { code => $_->[0], name => $_->[1] } }
- Travel::Status::DE::IRIS::Stations::get_station($station);
- if ( @candidates > 1
- or
- ( @candidates == 1 and $candidates[0]{code} ne $station ) )
- {
- $json = $self->render_to_string(
- json => {
- api_version => $api_version,
- version => $VERSION,
- error => 'ambiguous station code/name',
- candidates => \@candidates,
- }
- );
- }
- else {
- $json = $self->render_to_string(
- json => {
- api_version => $api_version,
- version => $VERSION,
- error =>
- ( $errstr // "Got no results for '$station'" )
- }
- );
- }
+ $ret = substr( $uic, 4, 5 );
}
- if ($callback) {
- $self->render(
- data => "$callback($json);",
- format => 'json'
- );
+
+ if ( $train_type =~ m{[.]S(\d)$} ) {
+ $ret .= ".$1";
}
- else {
- $self->render(
- data => $json,
- format => 'json'
- );
+ elsif ( $train_type =~ m{[.]R$} ) {
+ $ret .= '.r';
+ }
+
+ if ( $ret and $self->app->dbdb_wagon->{$ret} ) {
+ return $ret;
}
return;
}
@@ -242,72 +208,83 @@ sub startup {
{
return 1;
}
+ if ( ( $self->param('hafas') or $self->param('efa') )
+ and $stop =~ m{ [Bb]ahnhof | Bf }x )
+ {
+ return 1;
+ }
return;
}
);
$self->helper(
- 'json_route_diff' => sub {
- my ( $self, $route, $sched_route ) = @_;
- my @json_route;
- my @route = @{$route};
- my @sched_route = @{$sched_route};
-
- my $route_idx = 0;
- my $sched_idx = 0;
-
- while ( $route_idx <= $#route and $sched_idx <= $#sched_route ) {
- if ( $route[$route_idx] eq $sched_route[$sched_idx] ) {
- push( @json_route, { name => $route[$route_idx] } );
- $route_idx++;
- $sched_idx++;
- }
+ 'occupancy_icon' => sub {
+ my ( $self, $occupancy ) = @_;
- # this branch is inefficient, but won't be taken frequently
- elsif ( not( $route[$route_idx] ~~ \@sched_route ) ) {
- push(
- @json_route,
- {
- name => $route[$route_idx],
- isAdditional => 1
- }
- );
- $route_idx++;
- }
- else {
- push(
- @json_route,
- {
- name => $sched_route[$sched_idx],
- isCancelled => 1
- }
- );
- $sched_idx++;
- }
+ my @symbols
+ = (
+ qw(help_outline person_outline people priority_high not_interested)
+ );
+ my $text = 'Auslastung unbekannt';
+
+ if ( $occupancy eq 'MANY_SEATS' ) {
+ $occupancy = 1;
+ }
+ elsif ( $occupancy eq 'FEW_SEATS' ) {
+ $occupancy = 2;
+ }
+ elsif ( $occupancy eq 'STANDING_ONLY' ) {
+ $occupancy = 3;
+ }
+ elsif ( $occupancy eq 'FULL' ) {
+ $occupancy = 4;
}
- while ( $route_idx <= $#route ) {
- push(
- @json_route,
- {
- name => $route[$route_idx],
- isAdditional => 1,
- isCancelled => 0
- }
- );
- $route_idx++;
+
+ if ( $occupancy > 3 ) {
+ $text = 'Voraussichtlich überfüllt';
+ }
+ if ( $occupancy > 2 ) {
+ $text = 'Sehr hohe Auslastung erwartet';
}
- while ( $sched_idx <= $#sched_route ) {
- push(
- @json_route,
- {
- name => $sched_route[$sched_idx],
- isAdditional => 0,
- isCancelled => 1
- }
- );
- $sched_idx++;
+ elsif ( $occupancy > 1 ) {
+ $text = 'Hohe Auslastung erwartet';
}
- return @json_route;
+ elsif ( $occupancy > 0 ) {
+ $text = 'Geringe Auslastung erwartet';
+ }
+
+ return ( $text, $symbols[$occupancy] );
+ }
+ );
+
+ $self->helper(
+ 'utilization_icon' => sub {
+ my ( $self, $utilization ) = @_;
+ my ( $first, $second ) = @{ $utilization // [] };
+ $first //= 0;
+ $second //= 0;
+ my $sum = ( $first + $second ) / 2;
+
+ my @symbols
+ = (
+ qw(help_outline person_outline people priority_high not_interested)
+ );
+ my $text = 'Auslastung unbekannt';
+
+ if ( $sum > 3.5 ) {
+ $text = 'Zug ist ausgebucht';
+ }
+ elsif ( $sum >= 2.5 ) {
+ $text = 'Sehr hohe Auslastung';
+ }
+ elsif ( $sum >= 1.5 ) {
+ $text = 'Hohe Auslastung';
+ }
+ elsif ( $sum >= 1 ) {
+ $text = 'Geringe Auslastung';
+ }
+
+ return ( $text, $symbols[$first], $symbols[$second] );
}
);
@@ -331,11 +308,35 @@ sub startup {
}
);
+ $self->helper(
+ 'get_rt_time_class' => sub {
+ my ( $self, $train ) = @_;
+ if ( $train->{has_realtime}
+ and not $train->{is_bit_delayed}
+ and not $train->{is_delayed} )
+ {
+ return 'on-time';
+ }
+ if ( $train->{is_bit_delayed} ) {
+ return 'a-bit-delayed';
+ }
+ if ( $train->{is_delayed} ) {
+ return 'delayed';
+ }
+ return q{};
+ }
+ );
+
my $r = $self->routes;
- $r->get('/_redirect')->to('static#redirect');
+ $r->get('/_redirect')->to('stationboard#redirect_to_station');
+
+ # legacy entry point
+ $r->get('/_auto')->to('static#geostop');
- $r->get('/_auto')->to('static#geolocation');
+ $r->get('/_autostop')->to('static#geostop');
+
+ $r->get('/_backend')->to('stationboard#backend_list');
$r->get('/_datenschutz')->to('static#privacy');
@@ -345,32 +346,27 @@ sub startup {
$r->get('/_impressum')->to('static#imprint');
- $r->get('/_wr/:train/:departure')->to('wagenreihung#wagenreihung');
+ $r->get('/dyn/:av/autocomplete.js')->to('stationboard#autocomplete');
+
+ $r->get('/carriage-formation')->to('wagenreihung#wagenreihung');
+ $r->get('/w/*wagon')->to('wagenreihung#wagen');
$r->get('/_ajax_mapinfo/:tripid/:lineno')->to('map#ajax_route');
$r->get('/map/:tripid/:lineno')->to('map#route');
- $r->get('/intersection/:trips')->to('map#intersection');
-
- $r->get('/map')->to('map#search_form');
- $r->get('/_trainsearch')->to('map#search');
+ $r->get('/coverage/:backend/:service')->to('map#coverage');
+ $r->get( '/z/:train/*station' => [ format => [ 'html', 'json' ] ] )
+ ->to( 'stationboard#station_train_details', format => undef )
+ ->name('train_at_station');
+ $r->get( '/z/:train' => [ format => [ 'html', 'json' ] ] )
+ ->to( 'stationboard#train_details', format => undef )
+ ->name('train');
$self->defaults( layout => 'app' );
- $r->get('/')->to('stationboard#handle_request');
- $r->get('/multi/*station')->to('stationboard#handle_request');
- $r->get('/*station')->to('stationboard#handle_request');
-
- $self->config(
- hypnotoad => {
- accepts => $ENV{DBFAKEDISPLAY_ACCEPTS} // 100,
- clients => $ENV{DBFAKEDISPLAY_CLIENTS} // 10,
- listen => [ $ENV{DBFAKEDISPLAY_LISTEN} // 'http://*:8092' ],
- pid_file => $ENV{DBFAKEDISPLAY_PID_FILE}
- // '/tmp/db-fakedisplay.pid',
- spare => $ENV{DBFAKEDISPLAY_SPARE} // 2,
- workers => $ENV{DBFAKEDISPLAY_WORKERS} // 2,
- },
- );
+ $r->get('/')->to('stationboard#handle_board_request');
+ $r->get('/multi/*station')->to('stationboard#handle_board_request');
+ $r->get( '/*station' => [ format => [ 'html', 'json' ] ] )
+ ->to( 'stationboard#handle_board_request', format => undef );
$self->types->type( json => 'application/json; charset=utf-8' );