package DBInfoscreen; # 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::EFA; use DBInfoscreen::Helper::HAFAS; use DBInfoscreen::Helper::Wagonorder; use File::Slurp qw(read_file); use JSON; use utf8; sub startup { my ($self) = @_; $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} // '???', ); chomp $self->config->{version}; $self->defaults( version => $self->config->{version} // 'UNKNOWN' ); # 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. for my $cookie ( @{ $self->req->cookies } ) { if ( $cookie->name eq 'theme' ) { $self->session( theme => $cookie->value ); } } } ); $self->attr( cache_iris_main => sub { my ($self) = @_; return Cache::File->new( cache_root => $ENV{DBFAKEDISPLAY_IRIS_CACHE} // '/tmp/dbf-iris-main', default_expires => '6 hours', lock_level => Cache::File::LOCK_LOCAL(), ); } ); $self->attr( cache_iris_rt => sub { my ($self) = @_; return Cache::File->new( cache_root => $ENV{DBFAKEDISPLAY_IRISRT_CACHE} // '/tmp/dbf-iris-realtime', default_expires => '70 seconds', lock_level => Cache::File::LOCK_LOCAL(), ); } ); $self->attr( dbdb_wagon => sub { return JSON->new->utf8->decode( scalar read_file('share/dbdb_wagen.json') ); } ); $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}, ); } ); $self->helper( hafas => sub { my ($self) = @_; state $hafas = DBInfoscreen::Helper::HAFAS->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( wagonorder => sub { my ($self) = @_; state $hafas = DBInfoscreen::Helper::Wagonorder->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( 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; } elsif ( $train_type =~ m{IC2.TWIN} ) { $ret = $wagon_type; } elsif ( not $uic ) { return; } else { $ret = substr( $uic, 4, 5 ); } if ( $train_type =~ m{[.]S(\d)$} ) { $ret .= ".$1"; } elsif ( $train_type =~ m{[.]R$} ) { $ret .= '.r'; } if ( $ret and $self->app->dbdb_wagon->{$ret} ) { return $ret; } return; } ); $self->helper( 'is_important' => sub { my ( $self, $stop ) = @_; # Centraal: dutch main station (Hbf in .nl) # HB: swiss main station (Hbf in .ch) # hl.n.: czech main station (Hbf in .cz) if ( $stop =~ m{ HB $ | hl\.n\. $ | Hbf | Centraal | Flughafen }x ) { return 1; } if ( ( $self->param('hafas') or $self->param('efa') ) and $stop =~ m{ [Bb]ahnhof | Bf }x ) { return 1; } return; } ); $self->helper( 'occupancy_icon' => sub { my ( $self, $occupancy ) = @_; 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; } if ( $occupancy > 3 ) { $text = 'Voraussichtlich überfüllt'; } if ( $occupancy > 2 ) { $text = 'Sehr hohe Auslastung erwartet'; } elsif ( $occupancy > 1 ) { $text = 'Hohe Auslastung erwartet'; } 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] ); } ); $self->helper( 'numeric_platform_part' => sub { my ( $self, $platform ) = @_; if ( not defined $platform ) { return 0; } if ( $platform =~ m{ ^ \d+ $ }x ) { return $platform; } if ( $platform =~ m{ (\d+) }x ) { return $1; } return 0; } ); $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('stationboard#redirect_to_station'); # legacy entry point $r->get('/_auto')->to('static#geostop'); $r->get('/_autostop')->to('static#geostop'); $r->get('/_backend')->to('stationboard#backend_list'); $r->get('/_datenschutz')->to('static#privacy'); $r->post('/_geolocation')->to('stationboard#stations_by_coordinates'); $r->get('/_about')->to('static#about'); $r->get('/_impressum')->to('static#imprint'); $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('/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' => [ format => [ 'html', 'json' ] ] ) ->to( 'stationboard#handle_request', format => undef ); $self->types->type( json => 'application/json; charset=utf-8' ); } 1;