diff options
| -rwxr-xr-x | lib/Travelynx.pm | 407 | ||||
| -rw-r--r-- | lib/Travelynx/Command/work.pm | 35 | ||||
| -rwxr-xr-x | lib/Travelynx/Controller/Traveling.pm | 153 | 
3 files changed, 343 insertions, 252 deletions
| diff --git a/lib/Travelynx.pm b/lib/Travelynx.pm index 469e1fe..f5f56b7 100755 --- a/lib/Travelynx.pm +++ b/lib/Travelynx.pm @@ -588,7 +588,7 @@ sub startup {  	);  	$self->helper( -		'checkout' => sub { +		'checkout_p' => sub {  			my ( $self, %opt ) = @_;  			my $station      = $opt{station}; @@ -596,34 +596,29 @@ sub startup {  			my $arr_eva      = $opt{arr_eva};  			my $with_related = $opt{with_related} // 0;  			my $force        = $opt{force}; -			my $uid          = $opt{uid}; -			my $db           = $opt{db} // $self->pg->db; -			my $status       = $self->iris->get_departures( -				station      => $station, -				lookbehind   => 120, -				lookahead    => 180, -				with_related => $with_related, -			); -			$uid //= $self->current_user->{id}; -			my $user     = $self->get_user_status( $uid, $db ); -			my $train_id = $user->{train_id}; +			my $uid          = $opt{uid} // $self->current_user->{id}; +			my $db           = $opt{db}  // $self->pg->db; +			my $user         = $self->get_user_status( $uid, $db ); +			my $train_id     = $user->{train_id}; + +			my $promise = Mojo::Promise->new;  			if ( not $station ) {  				$self->app->log->error("Checkout($uid): station is empty"); -				return ( 1, 'BUG: Checkout station is empty.' ); +				return $promise->resolve( 1, +					'BUG: Checkout station is empty.' );  			}  			if ( not $user->{checked_in} and not $user->{cancelled} ) { -				return ( 0, 'You are not checked into any train' ); -			} -			if ( $status->{errstr} and not $force ) { -				return ( 1, $status->{errstr} ); +				return $promise->resolve( 0, +					'You are not checked into any train' );  			} +  			if ( $dep_eva and $dep_eva != $user->{dep_eva} ) { -				return ( 0, 'race condition' ); +				return $promise->resolve( 0, 'race condition' );  			}  			if ( $arr_eva and $arr_eva != $user->{arr_eva} ) { -				return ( 0, 'race condition' ); +				return $promise->resolve( 0, 'race condition' );  			}  			my $now     = DateTime->now( time_zone => 'Europe/Berlin' ); @@ -632,196 +627,232 @@ sub startup {  				with_data => 1  			); -			# 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 > $user->{sched_departure}->epoch -			} -			@{ $status->{results} }; +			$self->iris->get_departures_p( +				station      => $station, +				lookbehind   => 120, +				lookahead    => 180, +				with_related => $with_related, +			)->then( +				sub { +					my ($status) = @_; -			$train //= List::Util::first { $_->train_id eq $train_id } -			@{ $status->{results} }; +					my $new_checkout_station_id = $status->{station_eva}; -			my $new_checkout_station_id = $status->{station_eva}; +					# Store the intended checkout station regardless of this operation's +					# success. +					# TODO for with_related == 1, the correct EVA may be different +					# and should be fetched from $train later on +					$self->in_transit->set_arrival_eva( +						uid         => $uid, +						db          => $db, +						arrival_eva => $new_checkout_station_id +					); -			# Store the intended checkout station regardless of this operation's -			# success. -			$self->in_transit->set_arrival_eva( -				uid         => $uid, -				db          => $db, -				arrival_eva => $new_checkout_station_id -			); +					# 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 ) +					{ +						$self->in_transit->unset_arrival_data( +							uid => $uid, +							db  => $db +						); +					} -			# 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 ) -			{ -				$self->in_transit->unset_arrival_data( -					uid => $uid, -					db  => $db -				); -			} +					# 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 +						  > $user->{sched_departure}->epoch +					} +					@{ $status->{results} }; -			if ( not defined $train ) { +					$train //= List::Util::first { $_->train_id eq $train_id } +					@{ $status->{results} }; -				# Arrival time via IRIS is unknown, so the train probably has not -				# arrived yet. Fall back to HAFAS. -				# TODO support cases where $station is EVA or DS100 code -				if ( -					my $station_data -					= List::Util::first { $_->[0] eq $station } -					@{ $journey->{route} } -				  ) -				{ -					$station_data = $station_data->[2]; -					if ( $station_data->{sched_arr} ) { -						my $sched_arr -						  = epoch_to_dt( $station_data->{sched_arr} ); -						my $rt_arr = epoch_to_dt( $station_data->{rt_arr} ); -						if ( $rt_arr->epoch == 0 ) { -							$rt_arr = $sched_arr->clone; -							if (    $station_data->{arr_delay} -								and $station_data->{arr_delay} =~ m{^\d+$} ) -							{ -								$rt_arr->add( -									minutes => $station_data->{arr_delay} ); +					if ( not defined $train ) { + +						# Arrival time via IRIS is unknown, so the train probably +						# has not arrived yet. Fall back to HAFAS. +						# TODO support cases where $station is EVA or DS100 code +						if ( +							my $station_data +							= List::Util::first { $_->[0] eq $station } +							@{ $journey->{route} } +						  ) +						{ +							$station_data = $station_data->[2]; +							if ( $station_data->{sched_arr} ) { +								my $sched_arr +								  = epoch_to_dt( $station_data->{sched_arr} ); +								my $rt_arr +								  = epoch_to_dt( $station_data->{rt_arr} ); +								if ( $rt_arr->epoch == 0 ) { +									$rt_arr = $sched_arr->clone; +									if (    $station_data->{arr_delay} +										and $station_data->{arr_delay} +										=~ m{^\d+$} ) +									{ +										$rt_arr->add( minutes => +											  $station_data->{arr_delay} ); +									} +								} +								$self->in_transit->set_arrival_times( +									uid           => $uid, +									db            => $db, +									sched_arrival => $sched_arr, +									rt_arrival    => $rt_arr +								);  							}  						} -						$self->in_transit->set_arrival_times( -							uid           => $uid, -							db            => $db, -							sched_arrival => $sched_arr, -							rt_arrival    => $rt_arr -						); -					} -				} -				if ( not $force ) { +						if ( not $force ) { -					# mustn't be called during a transaction -					if ( not $opt{in_transaction} ) { -						$self->run_hook( $uid, 'update' ); +							# mustn't be called during a transaction +							if ( not $opt{in_transaction} ) { +								$self->run_hook( $uid, 'update' ); +							} +							$promise->resolve( 1, undef ); +							return; +						}  					} -					return ( 1, undef ); -				} -			} +					my $has_arrived = 0; -			my $has_arrived = 0; - -			eval { +					eval { -				my $tx; -				if ( not $opt{in_transaction} ) { -					$tx = $db->begin; -				} +						my $tx; +						if ( not $opt{in_transaction} ) { +							$tx = $db->begin; +						} -				if ( defined $train and not $train->arrival and not $force ) { -					my $train_no = $train->train_no; -					die("Train ${train_no} has no arrival timestamp\n"); -				} -				elsif ( defined $train and $train->arrival ) { -					$self->in_transit->set_arrival( -						uid   => $uid, -						db    => $db, -						train => $train, -						route => [ $self->iris->route_diff($train) ] -					); +						if (    defined $train +							and not $train->arrival +							and not $force ) +						{ +							my $train_no = $train->train_no; +							die("Train ${train_no} has no arrival timestamp\n"); +						} +						elsif ( defined $train and $train->arrival ) { +							$self->in_transit->set_arrival( +								uid   => $uid, +								db    => $db, +								train => $train, +								route => [ $self->iris->route_diff($train) ] +							); -					$has_arrived = $train->arrival->epoch < $now->epoch ? 1 : 0; -					if ($has_arrived) { -						my @unknown_stations -						  = $self->stations->grep_unknown( $train->route ); -						if (@unknown_stations) { -							$self->app->log->warn( -								sprintf( +							$has_arrived +							  = $train->arrival->epoch < $now->epoch ? 1 : 0; +							if ($has_arrived) { +								my @unknown_stations +								  = $self->stations->grep_unknown( +									$train->route ); +								if (@unknown_stations) { +									$self->app->log->warn( +										sprintf(  'Route of %s %s (%s -> %s) contains unknown stations: %s', -									$train->type, -									$train->train_no, -									$train->origin, -									$train->destination, -									join( ', ', @unknown_stations ) -								) +											$train->type, +											$train->train_no, +											$train->origin, +											$train->destination, +											join( ', ', @unknown_stations ) +										) +									); +								} +							} +						} + +						$journey = $self->in_transit->get( +							uid => $uid, +							db  => $db +						); + +						if ( $has_arrived or $force ) { +							$self->journeys->add_from_in_transit( +								db      => $db, +								journey => $journey +							); +							$self->in_transit->delete( +								uid => $uid, +								db  => $db +							); + +							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->journey_stats_cache->invalidate( +								ts  => $cache_ts, +								db  => $db, +								uid => $uid  							);  						} -					} -				} +						elsif ( defined $train +							and $train->arrival_is_cancelled ) +						{ -				$journey = $self->in_transit->get( -					uid => $uid, -					db  => $db -				); +							# This branch is only taken if the deparure was not cancelled, +							# i.e., if the train was supposed to go here but got +							# redirected or cancelled on the way and not from the start on. +							# If the departure itself was cancelled, the user route is +							# cancelled_from action -> 'cancelled journey' panel on main page +							# -> cancelled_to action -> force checkout (causing the +							# previous branch to be taken due to $force) +							$journey->{cancelled} = 1; +							$self->journeys->add_from_in_transit( +								db      => $db, +								journey => $journey +							); +							$self->in_transit->set_cancelled_destination( +								uid                   => $uid, +								db                    => $db, +								cancelled_destination => $train->station, +							); +						} -				if ( $has_arrived or $force ) { -					$self->journeys->add_from_in_transit( -						db      => $db, -						journey => $journey -					); -					$self->in_transit->delete( -						uid => $uid, -						db  => $db -					); +						if ( not $opt{in_transaction} ) { +							$tx->commit; +						} +					}; -					my $cache_ts = $now->clone; -					if ( $journey->{real_departure} -						=~ m{ ^ (?<year> \d{4} ) - (?<month> \d{2} ) }x ) -					{ -						$cache_ts->set( -							year  => $+{year}, -							month => $+{month} -						); +					if ($@) { +						$self->app->log->error("Checkout($uid): $@"); +						$promise->resolve( 1, 'Checkout error: ' . $@ ); +						return;  					} -					$self->journey_stats_cache->invalidate( -						ts  => $cache_ts, -						db  => $db, -						uid => $uid -					); -				} -				elsif ( defined $train and $train->arrival_is_cancelled ) { - -					# This branch is only taken if the deparure was not cancelled, -					# i.e., if the train was supposed to go here but got -					# redirected or cancelled on the way and not from the start on. -					# If the departure itself was cancelled, the user route is -					# cancelled_from action -> 'cancelled journey' panel on main page -					# -> cancelled_to action -> force checkout (causing the -					# previous branch to be taken due to $force) -					$journey->{cancelled} = 1; -					$self->journeys->add_from_in_transit( -						db      => $db, -						journey => $journey -					); -					$self->in_transit->set_cancelled_destination( -						uid                   => $uid, -						db                    => $db, -						cancelled_destination => $train->station, -					); -				} -				if ( not $opt{in_transaction} ) { -					$tx->commit; -				} -			}; - -			if ($@) { -				$self->app->log->error("Checkout($uid): $@"); -				return ( 1, 'Checkout error: ' . $@ ); -			} +					if ( $has_arrived or $force ) { +						if ( not $opt{in_transaction} ) { +							$self->run_hook( $uid, 'checkout' ); +						} +						$promise->resolve( 0, undef ); +						return; +					} +					if ( not $opt{in_transaction} ) { +						$self->run_hook( $uid, 'update' ); +						$self->add_route_timestamps( $uid, $train, 0, 1 ); +					} +					$promise->resolve( 1, undef ); +					return; -			if ( $has_arrived or $force ) { -				if ( not $opt{in_transaction} ) { -					$self->run_hook( $uid, 'checkout' );  				} -				return ( 0, undef ); -			} -			if ( not $opt{in_transaction} ) { -				$self->run_hook( $uid, 'update' ); -				$self->add_route_timestamps( $uid, $train, 0, 1 ); -			} -			return ( 1, undef ); +			)->catch( +				sub { +					my ($err) = @_; +					$promise->resolve( 1, $err ); +					return; +				} +			)->wait; + +			return $promise;  		}  	); @@ -1788,13 +1819,17 @@ sub startup {  					)->then(  						sub {  							$self->log->debug("... handled origin"); -							my ( undef, $err ) = $self->checkout( +							return $self->checkout_p(  								station        => $traewelling->{arr_eva},  								train_id       => 0,  								uid            => $uid,  								in_transaction => 1,  								db             => $db  							); +						} +					)->then( +						sub { +							my ( undef, $err ) = @_;  							if ($err) {  								$self->log->debug("... error: $err");  								return Mojo::Promise->reject($err); diff --git a/lib/Travelynx/Command/work.pm b/lib/Travelynx/Command/work.pm index 3086737..23d2925 100644 --- a/lib/Travelynx/Command/work.pm +++ b/lib/Travelynx/Command/work.pm @@ -86,15 +86,15 @@ sub run {  					# train.  					if ($checked_in) { -                  # check out (adds a cancelled journey and resets journey state -                  # to checkin -						$self->app->checkout( +						# check out (adds a cancelled journey and resets journey state +						# to checkin +						$self->app->checkout_p(  							station => $arr, -							force   => 1, +							force   => 2,  							dep_eva => $dep,  							arr_eva => $arr,  							uid     => $uid -						); +						)->wait;  					}  				}  				else { @@ -153,15 +153,15 @@ sub run {  				if ( $checked_in and $train->arrival_is_cancelled ) { -                  # check out (adds a cancelled journey and resets journey state -                  # to destination selection) -					$self->app->checkout( +					# check out (adds a cancelled journey and resets journey state +					# to destination selection) +					$self->app->checkout_p(  						station => $arr,  						force   => 0,  						dep_eva => $dep,  						arr_eva => $arr,  						uid     => $uid -					); +					)->wait;  				}  				else {  					$self->app->add_route_timestamps( @@ -174,21 +174,24 @@ sub run {  				}  			}  			elsif ( $entry->{real_arr_ts} ) { -				my ( undef, $error ) = $self->app->checkout( +				my ( undef, $error ) = $self->app->checkout_p(  					station => $arr, -					force   => 1, +					force   => 2,  					dep_eva => $dep,  					arr_eva => $arr,  					uid     => $uid -				); -				if ($error) { -					die("${error}\n"); -				} +				)->catch( +					sub { +						my ($error) = @_; +						$self->app->log->error("work($uid)/arrival: $@"); +						$errors += 1; +					} +				)->wait;  			}  		};  		if ($@) { -			$errors += 1;  			$self->app->log->error("work($uid)/arrival: $@"); +			$errors += 1;  		}  		eval { } diff --git a/lib/Travelynx/Controller/Traveling.pm b/lib/Travelynx/Controller/Traveling.pm index 48fa944..3e051a1 100755 --- a/lib/Travelynx/Controller/Traveling.pm +++ b/lib/Travelynx/Controller/Traveling.pm @@ -622,17 +622,26 @@ sub travel_action {  	if ( $params->{action} eq 'checkin' ) {  		my $status = $self->get_user_status; +		my $promise; +  		if (    $status->{checked_in}  			and $status->{arr_eva}  			and $status->{arrival_countdown} <= 0 )  		{ -			$self->checkout( station => $status->{arr_eva} ); +			$promise = $self->checkout_p( station => $status->{arr_eva} ); +		} +		else { +			$promise = Mojo::Promise->resolve;  		}  		$self->render_later; -		$self->checkin_p( -			station  => $params->{station}, -			train_id => $params->{train} +		$promise->then( +			sub { +				return $self->checkin_p( +					station  => $params->{station}, +					train_id => $params->{train} +				); +			}  		)->then(  			sub {  				my $destination = $params->{dest}; @@ -648,17 +657,26 @@ sub travel_action {  				# Silently ignore errors -- if they are permanent, the user will see  				# them when selecting the destination manually. -				my ( $still_checked_in, undef ) = $self->checkout( +				return $self->checkout_p(  					station => $destination,  					force   => 0  				); -				my $station_link = '/s/' . $destination; -				$self->render( -					json => { -						success     => 1, -						redirect_to => $still_checked_in ? '/' : $station_link, -					}, -				); +			} +		)->then( +			sub { +				my ( $still_checked_in, undef ) = @_; +				if ( my $destination = $params->{dest} ) { +					my $station_link = '/s/' . $destination; +					$self->render( +						json => { +							success     => 1, +							redirect_to => $still_checked_in +							? '/' +							: $station_link, +						}, +					); +				} +				return;  			}  		)->catch(  			sub { @@ -673,28 +691,47 @@ sub travel_action {  		)->wait;  	}  	elsif ( $params->{action} eq 'checkout' ) { -		my ( $still_checked_in, $error ) = $self->checkout( +		$self->render_later; +		$self->checkout_p(  			station => $params->{station},  			force   => $params->{force} -		); -		my $station_link = '/s/' . $params->{station}; +		)->then( +			sub { +				my ( $still_checked_in, $error ) = @_; +				my $station_link = '/s/' . $params->{station}; -		if ($error) { -			$self->render( -				json => { -					success => 0, -					error   => $error, -				}, -			); -		} -		else { -			$self->render( -				json => { -					success     => 1, -					redirect_to => $still_checked_in ? '/' : $station_link, -				}, -			); -		} +				if ($error) { +					$self->render( +						json => { +							success => 0, +							error   => $error, +						}, +					); +				} +				else { +					$self->render( +						json => { +							success     => 1, +							redirect_to => $still_checked_in +							? '/' +							: $station_link, +						}, +					); +				} +				return; +			} +		)->catch( +			sub { +				my ($error) = @_; +				$self->render( +					json => { +						success => 0, +						error   => $error, +					}, +				); +				return; +			} +		)->wait;  	}  	elsif ( $params->{action} eq 'undo' ) {  		my $status = $self->get_user_status; @@ -747,27 +784,43 @@ sub travel_action {  		)->wait;  	}  	elsif ( $params->{action} eq 'cancelled_to' ) { -		my ( undef, $error ) = $self->checkout( +		$self->render_later; +		$self->checkout_p(  			station => $params->{station},  			force   => 1 -		); - -		if ($error) { -			$self->render( -				json => { -					success => 0, -					error   => $error, -				}, -			); -		} -		else { -			$self->render( -				json => { -					success     => 1, -					redirect_to => '/', -				}, -			); -		} +		)->then( +			sub { +				my ( undef, $error ) = @_; +				if ($error) { +					$self->render( +						json => { +							success => 0, +							error   => $error, +						}, +					); +				} +				else { +					$self->render( +						json => { +							success     => 1, +							redirect_to => '/', +						}, +					); +				} +				return; +			} +		)->catch( +			sub { +				my ($error) = @_; +				$self->render( +					json => { +						success => 0, +						error   => $error, +					}, +				); +				return; +			} +		)->wait;  	}  	elsif ( $params->{action} eq 'delete' ) {  		my $error = $self->journeys->delete( | 
